mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 10:48:16 +00:00
refactor(core): add support to multiple webviews on a Tauri window (#8280)
* feat: update to latest wry * wry dev branch [skip ci] * fix linux [skip ci] * refactor(runtime): split webview and window types * split dispatch * implement create_webview * move webview message * wip webview mod * create webview manager, finish webview struct and builder * fix tests and docs * rename WindowUrl to WebviewUrl * update examples * event refactor * update JS API * fix events * update example * add WebviewWindow class on JS * fix macos build * allow creating window+webview on the same runtime call * rename tauri://window-created to tauri://webview-created * Window::add_child * use inner_size from webview on macOS * add multiwebview example * automatically resize webviews on window resize * fix tests * set_position, set_size * position, size getters * set_focus * add close fn * update mock runtime * lint [skip ci] * fix inner_size getter [skip ci] * import hwnd [skip ci] * update webview bound ratios on set_size/set_position * add auto_resize option * fix android * fix build on windows * typo * with_webview isnt desktop only * add WebviewWindow rust struct (and builder) * fix build on android * license header * fix macos/windows * fix macos build * resolve todo * handle window not found * hide unstable features * document unstable feature [skip ci] * webview plugin permissions * hide more stuff * fix doctests * typos * add change files * fix examples * rename hook
This commit is contained in:
parent
74a2a6036a
commit
c77b40324e
5
.changes/api-event-resource-refactor.md
Normal file
5
.changes/api-event-resource-refactor.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:breaking
|
||||
---
|
||||
|
||||
Removed event callback's `windowLabel` field and added a `windowSource` object instead.
|
||||
5
.changes/event-target-refactor.md
Normal file
5
.changes/event-target-refactor.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:breaking
|
||||
---
|
||||
|
||||
The event target is now an object so you can target either a window or a webview.
|
||||
5
.changes/hooks-webview.md
Normal file
5
.changes/hooks-webview.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:breaking
|
||||
---
|
||||
|
||||
The `invoke_system`, `on_page_load` hooks now gives you a `Webview` argument instead of a `Window`.
|
||||
5
.changes/move-window-apis.md
Normal file
5
.changes/move-window-apis.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:breaking
|
||||
---
|
||||
|
||||
Moved webview-specific APIs from the `Window` class to the `Webview` class.
|
||||
5
.changes/multiwebview-api.md
Normal file
5
.changes/multiwebview-api.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:feat
|
||||
---
|
||||
|
||||
Added support to multiwebview via the new `window` and `webview` modules.
|
||||
7
.changes/multiwebview.md
Normal file
7
.changes/multiwebview.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": patch:feat
|
||||
"tauri-runtime": patch:feat
|
||||
"tauri-runtime-wry": patch:feat
|
||||
---
|
||||
|
||||
Add multiwebview support behind the `unstable` feature flag. See `WindowBuilder` and `WebviewBuilder` for more information.
|
||||
5
.changes/remove-window-url.md
Normal file
5
.changes/remove-window-url.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-utils": patch:breaking
|
||||
---
|
||||
|
||||
Renamed `config::WindowUrl` to `config::WebviewUrl`.
|
||||
5
.changes/tauri-event-enum-refactor.md
Normal file
5
.changes/tauri-event-enum-refactor.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:breaking
|
||||
---
|
||||
|
||||
Renamed `TauriEvent.WINDOW_FILE_DROP` to `TauriEvent.WEBVIEW_FILE_DROP`, `TauriEvent.WINDOW_FILE_DROP_HOVER` to `TauriEvent.WEBVIEW_FILE_DROP_HOVER` and `TauriEvent.WINDOW_FILE_DROP_CANCELLED` to `TauriEvent.WEBVIEW_FILE_DROP_CANCELLED`.
|
||||
5
.changes/webview-window-api.md
Normal file
5
.changes/webview-window-api.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": patch:breaking
|
||||
---
|
||||
|
||||
Added back the `WebviewWindow` API that exposes functionality of a window that hosts a single webview. The dedicated `Window` and `Webview` types are exposed for multiwebview features.
|
||||
5
.changes/webview-window-refactor.md
Normal file
5
.changes/webview-window-refactor.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:breaking
|
||||
---
|
||||
|
||||
Renamed `Window` to `WebviewWindow`, `WindowBuilder` to `WebviewWindowBuilder`, `Manager::windows` to `Manager::webview_windows` and `Manager::get_window` to `Manager::get_webview_window`.
|
||||
5
.changes/window-data-url-rename.md
Normal file
5
.changes/window-data-url-rename.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:breaking
|
||||
---
|
||||
|
||||
Renamed the `window-data-url` feature flag to `webview-data-url`.
|
||||
@ -10,7 +10,7 @@ use std::{
|
||||
path::PathBuf,
|
||||
};
|
||||
use tauri_codegen::{context_codegen, ContextData};
|
||||
use tauri_utils::config::{AppUrl, WindowUrl};
|
||||
use tauri_utils::config::{AppUrl, WebviewUrl};
|
||||
|
||||
// TODO docs
|
||||
/// A builder for generating a Tauri application context during compile time.
|
||||
@ -88,7 +88,7 @@ impl CodegenContext {
|
||||
&config.build.dist_dir
|
||||
};
|
||||
match app_url {
|
||||
AppUrl::Url(WindowUrl::App(p)) => {
|
||||
AppUrl::Url(WebviewUrl::App(p)) => {
|
||||
println!("cargo:rerun-if-changed={}", config_parent.join(p).display());
|
||||
}
|
||||
AppUrl::Files(files) => {
|
||||
|
||||
@ -15,7 +15,7 @@ use tauri_utils::acl::capability::Capability;
|
||||
use tauri_utils::acl::plugin::Manifest;
|
||||
use tauri_utils::acl::resolved::Resolved;
|
||||
use tauri_utils::assets::AssetKey;
|
||||
use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
|
||||
use tauri_utils::config::{AppUrl, Config, PatternKind, WebviewUrl};
|
||||
use tauri_utils::html::{
|
||||
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
|
||||
};
|
||||
@ -165,8 +165,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
|
||||
let assets = match app_url {
|
||||
AppUrl::Url(url) => match url {
|
||||
WindowUrl::External(_) => Default::default(),
|
||||
WindowUrl::App(path) => {
|
||||
WebviewUrl::External(_) => Default::default(),
|
||||
WebviewUrl::App(path) => {
|
||||
if path.components().count() == 0 {
|
||||
panic!(
|
||||
"The `{}` configuration cannot be empty",
|
||||
|
||||
@ -342,7 +342,7 @@
|
||||
"default": "index.html",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WindowUrl"
|
||||
"$ref": "#/definitions/WebviewUrl"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -571,7 +571,7 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WindowUrl": {
|
||||
"WebviewUrl": {
|
||||
"description": "An URL to open on a Tauri webview window.",
|
||||
"anyOf": [
|
||||
{
|
||||
@ -1451,7 +1451,7 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DmgConfig": {
|
||||
"description": "Configuration for Apple Disk Image (.dmg) bundles.\n\nSee more: https://tauri.app/v1/api/config#dmgconfig",
|
||||
"description": "Configuration for Apple Disk Image (.dmg) bundles.\n\nSee more: <https://tauri.app/v1/api/config#dmgconfig>",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background": {
|
||||
@ -2482,7 +2482,7 @@
|
||||
"description": "The app's external URL, or the path to the directory containing the app assets.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WindowUrl"
|
||||
"$ref": "#/definitions/WebviewUrl"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@ use serde::Deserialize;
|
||||
use std::{fmt::Debug, sync::mpsc::Sender};
|
||||
use tauri_utils::{ProgressBarState, Theme};
|
||||
use url::Url;
|
||||
use webview::{DetachedWebview, PendingWebview};
|
||||
|
||||
/// Types useful for interacting with a user's monitors.
|
||||
pub mod monitor;
|
||||
@ -24,11 +25,11 @@ pub mod webview;
|
||||
pub mod window;
|
||||
|
||||
use monitor::Monitor;
|
||||
use webview::WindowBuilder;
|
||||
use window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent,
|
||||
};
|
||||
use window::{WindowBuilder, WindowId};
|
||||
|
||||
use http::{
|
||||
header::{InvalidHeaderName, InvalidHeaderValue},
|
||||
@ -121,6 +122,8 @@ pub enum Error {
|
||||
Infallible(#[from] std::convert::Infallible),
|
||||
#[error("the event loop has been closed")]
|
||||
EventLoopClosed,
|
||||
#[error("window not found")]
|
||||
WindowNotFound,
|
||||
}
|
||||
|
||||
/// Result type.
|
||||
@ -207,13 +210,20 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
|
||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||
fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy;
|
||||
|
||||
/// Create a new webview window.
|
||||
/// Create a new window.
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
before_webview_creation: Option<F>,
|
||||
before_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>>;
|
||||
|
||||
/// Create a new webview.
|
||||
fn create_webview(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>>;
|
||||
|
||||
/// Run a task on the main thread.
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
@ -262,8 +272,10 @@ pub struct RuntimeInitArgs {
|
||||
|
||||
/// The webview runtime interface.
|
||||
pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
|
||||
/// The message dispatcher.
|
||||
type Dispatcher: Dispatch<T, Runtime = Self>;
|
||||
/// The window message dispatcher.
|
||||
type WindowDispatcher: WindowDispatch<T, Runtime = Self>;
|
||||
/// The webview message dispatcher.
|
||||
type WebviewDispatcher: WebviewDispatch<T, Runtime = Self>;
|
||||
/// The runtime handle type.
|
||||
type Handle: RuntimeHandle<T, Runtime = Self>;
|
||||
/// The proxy type.
|
||||
@ -283,13 +295,20 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
|
||||
/// Gets a runtime handle.
|
||||
fn handle(&self) -> Self::Handle;
|
||||
|
||||
/// Create a new webview window.
|
||||
/// Create a new window.
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self>,
|
||||
before_webview_creation: Option<F>,
|
||||
after_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self>>;
|
||||
|
||||
/// Create a new webview.
|
||||
fn create_webview(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, Self>,
|
||||
) -> Result<DetachedWebview<T, Self>>;
|
||||
|
||||
fn primary_monitor(&self) -> Option<Monitor>;
|
||||
fn available_monitors(&self) -> Vec<Monitor>;
|
||||
|
||||
@ -329,20 +348,14 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
|
||||
fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F);
|
||||
}
|
||||
|
||||
/// Webview dispatcher. A thread-safe handle to the webview API.
|
||||
pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {
|
||||
/// The runtime this [`Dispatch`] runs under.
|
||||
/// Webview dispatcher. A thread-safe handle to the webview APIs.
|
||||
pub trait WebviewDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {
|
||||
/// The runtime this [`WebviewDispatch`] runs under.
|
||||
type Runtime: Runtime<T>;
|
||||
|
||||
/// The window builder type.
|
||||
type WindowBuilder: WindowBuilder;
|
||||
|
||||
/// Run a task on the main thread.
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
/// Registers a window event handler.
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId;
|
||||
|
||||
/// Runs a closure with the platform webview object as argument.
|
||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
@ -363,6 +376,52 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Returns the webview's current URL.
|
||||
fn url(&self) -> Result<Url>;
|
||||
|
||||
/// Returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the window.
|
||||
fn position(&self) -> Result<PhysicalPosition<i32>>;
|
||||
|
||||
/// Returns the physical size of the webviews's client area.
|
||||
fn size(&self) -> Result<PhysicalSize<u32>>;
|
||||
|
||||
// SETTER
|
||||
|
||||
/// Naviagte to the given URL.
|
||||
fn navigate(&self, url: Url) -> Result<()>;
|
||||
|
||||
/// Opens the dialog to prints the contents of the webview.
|
||||
fn print(&self) -> Result<()>;
|
||||
|
||||
/// Closes the webview.
|
||||
fn close(&self) -> Result<()>;
|
||||
|
||||
/// Resizes the webview.
|
||||
fn set_size(&self, size: Size) -> Result<()>;
|
||||
|
||||
/// Updates the webview position.
|
||||
fn set_position(&self, position: Position) -> Result<()>;
|
||||
|
||||
/// Bring the window to front and focus the webview.
|
||||
fn set_focus(&self) -> Result<()>;
|
||||
|
||||
/// Executes javascript on the window this [`WindowDispatch`] represents.
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Window dispatcher. A thread-safe handle to the window APIs.
|
||||
pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {
|
||||
/// The runtime this [`WindowDispatch`] runs under.
|
||||
type Runtime: Runtime<T>;
|
||||
|
||||
/// The window builder type.
|
||||
type WindowBuilder: WindowBuilder;
|
||||
|
||||
/// Run a task on the main thread.
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
|
||||
|
||||
/// Registers a window event handler.
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId;
|
||||
|
||||
// GETTERS
|
||||
|
||||
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||
fn scale_factor(&self) -> Result<f64>;
|
||||
|
||||
@ -459,6 +518,7 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
))]
|
||||
fn default_vbox(&self) -> Result<gtk::Box>;
|
||||
|
||||
/// Raw window handle.
|
||||
fn raw_window_handle(&self) -> Result<raw_window_handle::RawWindowHandle>;
|
||||
|
||||
/// Returns the current window theme.
|
||||
@ -469,21 +529,24 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Centers the window.
|
||||
fn center(&self) -> Result<()>;
|
||||
|
||||
/// Opens the dialog to prints the contents of the webview.
|
||||
fn print(&self) -> Result<()>;
|
||||
|
||||
/// Requests user attention to the window.
|
||||
///
|
||||
/// Providing `None` will unset the request for user attention.
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()>;
|
||||
|
||||
/// Create a new webview window.
|
||||
/// Create a new window.
|
||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||
&mut self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
before_webview_creation: Option<F>,
|
||||
after_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>>;
|
||||
|
||||
/// Create a new webview.
|
||||
fn create_webview(
|
||||
&mut self,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>>;
|
||||
|
||||
/// Updates the window resizable flag.
|
||||
fn set_resizable(&self, resizable: bool) -> Result<()>;
|
||||
|
||||
@ -514,9 +577,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Updates the window title.
|
||||
fn set_title<S: Into<String>>(&self, title: S) -> Result<()>;
|
||||
|
||||
/// Naviagte to the given URL.
|
||||
fn navigate(&self, url: Url) -> Result<()>;
|
||||
|
||||
/// Maximizes the window.
|
||||
fn maximize(&self) -> Result<()>;
|
||||
|
||||
@ -606,9 +666,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
|
||||
/// Starts resize-dragging the window.
|
||||
fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>;
|
||||
|
||||
/// Executes javascript on the window this [`Dispatch`] represents.
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
|
||||
|
||||
/// Sets the taskbar progress state.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
|
||||
@ -2,26 +2,203 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! Items specific to the [`Runtime`](crate::Runtime)'s webview.
|
||||
|
||||
use crate::{window::DetachedWindow, Icon};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri_utils::TitleBarStyle;
|
||||
use tauri_utils::{
|
||||
config::{WindowConfig, WindowEffectsConfig, WindowUrl},
|
||||
Theme,
|
||||
//! A layer between raw [`Runtime`] webviews and Tauri.
|
||||
//!
|
||||
use crate::{
|
||||
window::{
|
||||
dpi::{Position, Size},
|
||||
is_label_valid,
|
||||
},
|
||||
Runtime, UserEvent,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use tauri_utils::config::{WebviewUrl, WindowConfig, WindowEffectsConfig};
|
||||
use url::Url;
|
||||
|
||||
use std::{fmt, path::PathBuf};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
type UriSchemeProtocol = dyn Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
|
||||
type WebResourceRequestHandler =
|
||||
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
|
||||
|
||||
type NavigationHandler = dyn Fn(&Url) -> bool + Send;
|
||||
|
||||
type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
|
||||
|
||||
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
|
||||
|
||||
/// Download event.
|
||||
pub enum DownloadEvent<'a> {
|
||||
/// Download requested.
|
||||
Requested {
|
||||
/// The url being downloaded.
|
||||
url: Url,
|
||||
/// Represents where the file will be downloaded to.
|
||||
/// Can be used to set the download location by assigning a new path to it.
|
||||
/// The assigned path _must_ be absolute.
|
||||
destination: &'a mut PathBuf,
|
||||
},
|
||||
/// Download finished.
|
||||
Finished {
|
||||
/// The URL of the original download request.
|
||||
url: Url,
|
||||
/// Potentially representing the filesystem path the file was downloaded to.
|
||||
path: Option<PathBuf>,
|
||||
/// Indicates if the download succeeded or not.
|
||||
success: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub struct CreationContext<'a, 'b> {
|
||||
pub env: &'a mut jni::JNIEnv<'b>,
|
||||
pub activity: &'a jni::objects::JObject<'b>,
|
||||
pub webview: &'a jni::objects::JObject<'b>,
|
||||
}
|
||||
|
||||
/// Kind of event for the page load handler.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PageLoadEvent {
|
||||
/// Page started to load.
|
||||
Started,
|
||||
/// Page finished loading.
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// A webview that has yet to be built.
|
||||
pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
|
||||
/// The label that the webview will be named.
|
||||
pub label: String,
|
||||
|
||||
/// The [`WebviewAttributes`] that the webview will be created with.
|
||||
pub webview_attributes: WebviewAttributes,
|
||||
|
||||
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
|
||||
|
||||
/// How to handle IPC calls on the webview.
|
||||
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
|
||||
|
||||
/// A handler to decide if incoming url is allowed to navigate.
|
||||
pub navigation_handler: Option<Box<NavigationHandler>>,
|
||||
|
||||
/// The resolved URL to load on the webview.
|
||||
pub url: String,
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub on_webview_created:
|
||||
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
|
||||
|
||||
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||
|
||||
pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
|
||||
|
||||
pub download_handler: Option<Arc<DownloadHandler>>,
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
|
||||
/// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`].
|
||||
pub fn new(
|
||||
webview_attributes: WebviewAttributes,
|
||||
label: impl Into<String>,
|
||||
) -> crate::Result<Self> {
|
||||
let label = label.into();
|
||||
if !is_label_valid(&label) {
|
||||
Err(crate::Error::InvalidWindowLabel)
|
||||
} else {
|
||||
Ok(Self {
|
||||
webview_attributes,
|
||||
uri_scheme_protocols: Default::default(),
|
||||
label,
|
||||
ipc_handler: None,
|
||||
navigation_handler: None,
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
on_webview_created: None,
|
||||
web_resource_request_handler: None,
|
||||
on_page_load_handler: None,
|
||||
download_handler: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_uri_scheme_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>(
|
||||
&mut self,
|
||||
uri_scheme: N,
|
||||
protocol: H,
|
||||
) {
|
||||
let uri_scheme = uri_scheme.into();
|
||||
self
|
||||
.uri_scheme_protocols
|
||||
.insert(uri_scheme, Box::new(protocol));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn on_webview_created<
|
||||
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
|
||||
>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.on_webview_created.replace(Box::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A webview that is not yet managed by Tauri.
|
||||
#[derive(Debug)]
|
||||
pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
|
||||
/// Name of the window
|
||||
pub label: String,
|
||||
|
||||
/// The [`crate::WebviewDispatch`] associated with the window.
|
||||
pub dispatcher: R::WebviewDispatcher,
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
label: self.label.clone(),
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
|
||||
/// Only use the [`DetachedWebview`]'s label to represent its hash.
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.label.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
|
||||
impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
|
||||
/// Only use the [`DetachedWebview`]'s label to compare equality.
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.label.eq(&other.label)
|
||||
}
|
||||
}
|
||||
|
||||
/// The attributes used to create an webview.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebviewAttributes {
|
||||
pub url: WindowUrl,
|
||||
pub url: WebviewUrl,
|
||||
pub user_agent: Option<String>,
|
||||
pub initialization_scripts: Vec<String>,
|
||||
pub data_directory: Option<PathBuf>,
|
||||
@ -31,12 +208,19 @@ pub struct WebviewAttributes {
|
||||
pub additional_browser_args: Option<String>,
|
||||
pub window_effects: Option<WindowEffectsConfig>,
|
||||
pub incognito: bool,
|
||||
pub transparent: bool,
|
||||
pub bounds: Option<(Position, Size)>,
|
||||
pub auto_resize: bool,
|
||||
}
|
||||
|
||||
impl From<&WindowConfig> for WebviewAttributes {
|
||||
fn from(config: &WindowConfig) -> Self {
|
||||
let mut builder = Self::new(config.url.clone());
|
||||
builder = builder.incognito(config.incognito);
|
||||
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||
{
|
||||
builder = builder.transparent(config.transparent);
|
||||
}
|
||||
builder = builder.accept_first_mouse(config.accept_first_mouse);
|
||||
if !config.file_drop_enabled {
|
||||
builder = builder.disable_file_drop_handler();
|
||||
@ -53,9 +237,10 @@ impl From<&WindowConfig> for WebviewAttributes {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
impl WebviewAttributes {
|
||||
/// Initializes the default attributes for a webview.
|
||||
pub fn new(url: WindowUrl) -> Self {
|
||||
pub fn new(url: WebviewUrl) -> Self {
|
||||
Self {
|
||||
url,
|
||||
user_agent: None,
|
||||
@ -67,6 +252,9 @@ impl WebviewAttributes {
|
||||
additional_browser_args: None,
|
||||
window_effects: None,
|
||||
incognito: false,
|
||||
transparent: false,
|
||||
bounds: None,
|
||||
auto_resize: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,202 +323,22 @@ impl WebviewAttributes {
|
||||
self.incognito = incognito;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Do **NOT** implement this trait except for use in a custom [`Runtime`](crate::Runtime).
|
||||
///
|
||||
/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
|
||||
pub trait WindowBuilderBase: fmt::Debug + Clone + Sized {}
|
||||
|
||||
/// A builder for all attributes related to a single webview.
|
||||
///
|
||||
/// This trait is only meant to be implemented by a custom [`Runtime`](crate::Runtime)
|
||||
/// and not by applications.
|
||||
pub trait WindowBuilder: WindowBuilderBase {
|
||||
/// Initializes a new window attributes builder.
|
||||
fn new() -> Self;
|
||||
|
||||
/// Initializes a new webview builder from a [`WindowConfig`]
|
||||
fn with_config(config: WindowConfig) -> Self;
|
||||
|
||||
/// Show window in the center of the screen.
|
||||
#[must_use]
|
||||
fn center(self) -> Self;
|
||||
|
||||
/// The initial position of the window's.
|
||||
#[must_use]
|
||||
fn position(self, x: f64, y: f64) -> Self;
|
||||
|
||||
/// Window size.
|
||||
#[must_use]
|
||||
fn inner_size(self, width: f64, height: f64) -> Self;
|
||||
|
||||
/// Window min inner size.
|
||||
#[must_use]
|
||||
fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
|
||||
|
||||
/// Window max inner size.
|
||||
#[must_use]
|
||||
fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
/// When resizable is set to false, native window's maximize button is automatically disabled.
|
||||
#[must_use]
|
||||
fn resizable(self, resizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native maximize button is enabled or not.
|
||||
/// If resizable is set to false, this setting is ignored.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
|
||||
/// - **Linux / iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn maximizable(self, maximizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native minimize button is enabled or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux / iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn minimizable(self, minimizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native close button is enabled or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
|
||||
/// Depending on the system, this function may not have any effect when called on a window that is already visible"
|
||||
/// - **iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn closable(self, closable: bool) -> Self;
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
#[must_use]
|
||||
fn title<S: Into<String>>(self, title: S) -> Self;
|
||||
|
||||
/// Whether to start the window in fullscreen or not.
|
||||
#[must_use]
|
||||
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||
|
||||
/// Whether the window will be initially focused or not.
|
||||
#[must_use]
|
||||
fn focused(self, focused: bool) -> Self;
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
#[must_use]
|
||||
fn maximized(self, maximized: bool) -> Self;
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
#[must_use]
|
||||
fn visible(self, visible: bool) -> Self;
|
||||
|
||||
/// Whether the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
/// Enable or disable transparency for the WebView.
|
||||
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
|
||||
)]
|
||||
#[must_use]
|
||||
fn transparent(self, transparent: bool) -> Self;
|
||||
pub fn transparent(mut self, transparent: bool) -> Self {
|
||||
self.transparent = transparent;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
/// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
|
||||
#[must_use]
|
||||
fn decorations(self, decorations: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be below other windows.
|
||||
#[must_use]
|
||||
fn always_on_bottom(self, always_on_bottom: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
#[must_use]
|
||||
fn always_on_top(self, always_on_top: bool) -> Self;
|
||||
|
||||
/// Whether the window should be visible on all workspaces or virtual desktops.
|
||||
#[must_use]
|
||||
fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
|
||||
|
||||
/// Prevents the window contents from being captured by other apps.
|
||||
#[must_use]
|
||||
fn content_protected(self, protected: bool) -> Self;
|
||||
|
||||
/// Sets the window icon.
|
||||
fn icon(self, icon: Icon) -> crate::Result<Self>;
|
||||
|
||||
/// Sets whether or not the window icon should be added to the taskbar.
|
||||
#[must_use]
|
||||
fn skip_taskbar(self, skip: bool) -> Self;
|
||||
|
||||
/// Sets whether or not the window has shadow.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows:**
|
||||
/// - `false` has no effect on decorated window, shadows are always ON.
|
||||
/// - `true` will make ndecorated window have a 1px white border,
|
||||
/// and on Windows 11, it will have a rounded corners.
|
||||
/// - **Linux:** Unsupported.
|
||||
#[must_use]
|
||||
fn shadow(self, enable: bool) -> Self;
|
||||
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
fn parent_window(self, parent: HWND) -> Self;
|
||||
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn parent_window(self, parent: *mut std::ffi::c_void) -> Self;
|
||||
|
||||
/// Set an owner to the window to be created.
|
||||
///
|
||||
/// From MSDN:
|
||||
/// - An owned window is always above its owner in the z-order.
|
||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
fn owner_window(self, owner: HWND) -> Self;
|
||||
|
||||
/// Hide the titlebar. Titlebar buttons will still be visible.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn title_bar_style(self, style: TitleBarStyle) -> Self;
|
||||
|
||||
/// Hide the window title.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn hidden_title(self, hidden: bool) -> Self;
|
||||
|
||||
/// Defines the window [tabbing identifier] for macOS.
|
||||
///
|
||||
/// Windows with matching tabbing identifiers will be grouped together.
|
||||
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
|
||||
///
|
||||
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn tabbing_identifier(self, identifier: &str) -> Self;
|
||||
|
||||
/// Forces a theme or uses the system settings if None was provided.
|
||||
fn theme(self, theme: Option<Theme>) -> Self;
|
||||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
pub fn auto_resize(mut self) -> Self {
|
||||
self.auto_resize = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// IPC handler.
|
||||
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWindow<T, R>, String) + Send>;
|
||||
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, String) + Send>;
|
||||
|
||||
@ -2,73 +2,27 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! A layer between raw [`Runtime`] webview windows and Tauri.
|
||||
//! A layer between raw [`Runtime`] windows and Tauri.
|
||||
|
||||
use crate::{
|
||||
webview::{WebviewAttributes, WebviewIpcHandler},
|
||||
Dispatch, Runtime, UserEvent, WindowBuilder,
|
||||
webview::{DetachedWebview, PendingWebview},
|
||||
Icon, Runtime, UserEvent, WindowDispatch,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tauri_utils::{config::WindowConfig, Theme};
|
||||
use url::Url;
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
path::PathBuf,
|
||||
sync::{mpsc::Sender, Arc},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
use self::dpi::PhysicalPosition;
|
||||
|
||||
type UriSchemeProtocol = dyn Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
|
||||
type WebResourceRequestHandler =
|
||||
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
|
||||
|
||||
type NavigationHandler = dyn Fn(&Url) -> bool + Send;
|
||||
|
||||
type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
|
||||
|
||||
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
|
||||
|
||||
/// Download event.
|
||||
pub enum DownloadEvent<'a> {
|
||||
/// Download requested.
|
||||
Requested {
|
||||
/// The url being downloaded.
|
||||
url: Url,
|
||||
/// Represents where the file will be downloaded to.
|
||||
/// Can be used to set the download location by assigning a new path to it.
|
||||
/// The assigned path _must_ be absolute.
|
||||
destination: &'a mut PathBuf,
|
||||
},
|
||||
/// Download finished.
|
||||
Finished {
|
||||
/// The URL of the original download request.
|
||||
url: Url,
|
||||
/// Potentially representing the filesystem path the file was downloaded to.
|
||||
path: Option<PathBuf>,
|
||||
/// Indicates if the download succeeded or not.
|
||||
success: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Kind of event for the page load handler.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PageLoadEvent {
|
||||
/// Page started to load.
|
||||
Started,
|
||||
/// Page finished loading.
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// UI scaling utilities.
|
||||
pub mod dpi;
|
||||
|
||||
@ -238,45 +192,216 @@ impl<'de> Deserialize<'de> for CursorIcon {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub struct CreationContext<'a, 'b> {
|
||||
pub env: &'a mut jni::JNIEnv<'b>,
|
||||
pub activity: &'a jni::objects::JObject<'b>,
|
||||
pub webview: &'a jni::objects::JObject<'b>,
|
||||
/// Do **NOT** implement this trait except for use in a custom [`Runtime`]
|
||||
///
|
||||
/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
|
||||
pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}
|
||||
|
||||
/// A builder for all attributes related to a single window.
|
||||
///
|
||||
/// This trait is only meant to be implemented by a custom [`Runtime`]
|
||||
/// and not by applications.
|
||||
pub trait WindowBuilder: WindowBuilderBase {
|
||||
/// Initializes a new window attributes builder.
|
||||
fn new() -> Self;
|
||||
|
||||
/// Initializes a new window builder from a [`WindowConfig`]
|
||||
fn with_config(config: WindowConfig) -> Self;
|
||||
|
||||
/// Show window in the center of the screen.
|
||||
#[must_use]
|
||||
fn center(self) -> Self;
|
||||
|
||||
/// The initial position of the window's.
|
||||
#[must_use]
|
||||
fn position(self, x: f64, y: f64) -> Self;
|
||||
|
||||
/// Window size.
|
||||
#[must_use]
|
||||
fn inner_size(self, width: f64, height: f64) -> Self;
|
||||
|
||||
/// Window min inner size.
|
||||
#[must_use]
|
||||
fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
|
||||
|
||||
/// Window max inner size.
|
||||
#[must_use]
|
||||
fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
/// When resizable is set to false, native window's maximize button is automatically disabled.
|
||||
#[must_use]
|
||||
fn resizable(self, resizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native maximize button is enabled or not.
|
||||
/// If resizable is set to false, this setting is ignored.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
|
||||
/// - **Linux / iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn maximizable(self, maximizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native minimize button is enabled or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux / iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn minimizable(self, minimizable: bool) -> Self;
|
||||
|
||||
/// Whether the window's native close button is enabled or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
|
||||
/// Depending on the system, this function may not have any effect when called on a window that is already visible"
|
||||
/// - **iOS / Android:** Unsupported.
|
||||
#[must_use]
|
||||
fn closable(self, closable: bool) -> Self;
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
#[must_use]
|
||||
fn title<S: Into<String>>(self, title: S) -> Self;
|
||||
|
||||
/// Whether to start the window in fullscreen or not.
|
||||
#[must_use]
|
||||
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||
|
||||
/// Whether the window will be initially focused or not.
|
||||
#[must_use]
|
||||
fn focused(self, focused: bool) -> Self;
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
#[must_use]
|
||||
fn maximized(self, maximized: bool) -> Self;
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
#[must_use]
|
||||
fn visible(self, visible: bool) -> Self;
|
||||
|
||||
/// Whether the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
|
||||
)]
|
||||
#[must_use]
|
||||
fn transparent(self, transparent: bool) -> Self;
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
#[must_use]
|
||||
fn decorations(self, decorations: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be below other windows.
|
||||
#[must_use]
|
||||
fn always_on_bottom(self, always_on_bottom: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
#[must_use]
|
||||
fn always_on_top(self, always_on_top: bool) -> Self;
|
||||
|
||||
/// Whether the window should be visible on all workspaces or virtual desktops.
|
||||
#[must_use]
|
||||
fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
|
||||
|
||||
/// Prevents the window contents from being captured by other apps.
|
||||
#[must_use]
|
||||
fn content_protected(self, protected: bool) -> Self;
|
||||
|
||||
/// Sets the window icon.
|
||||
fn icon(self, icon: Icon) -> crate::Result<Self>;
|
||||
|
||||
/// Sets whether or not the window icon should be added to the taskbar.
|
||||
#[must_use]
|
||||
fn skip_taskbar(self, skip: bool) -> Self;
|
||||
|
||||
/// Sets whether or not the window has shadow.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Windows:**
|
||||
/// - `false` has no effect on decorated window, shadows are always ON.
|
||||
/// - `true` will make ndecorated window have a 1px white border,
|
||||
/// and on Windows 11, it will have a rounded corners.
|
||||
/// - **Linux:** Unsupported.
|
||||
#[must_use]
|
||||
fn shadow(self, enable: bool) -> Self;
|
||||
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
fn parent_window(self, parent: HWND) -> Self;
|
||||
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn parent_window(self, parent: *mut std::ffi::c_void) -> Self;
|
||||
|
||||
/// Set an owner to the window to be created.
|
||||
///
|
||||
/// From MSDN:
|
||||
/// - An owned window is always above its owner in the z-order.
|
||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
fn owner_window(self, owner: HWND) -> Self;
|
||||
|
||||
/// Enables or disables drag and drop support.
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
fn drag_and_drop(self, enabled: bool) -> Self;
|
||||
|
||||
/// Hide the titlebar. Titlebar buttons will still be visible.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
|
||||
|
||||
/// Hide the window title.
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn hidden_title(self, hidden: bool) -> Self;
|
||||
|
||||
/// Defines the window [tabbing identifier] for macOS.
|
||||
///
|
||||
/// Windows with matching tabbing identifiers will be grouped together.
|
||||
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
|
||||
///
|
||||
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||
#[cfg(target_os = "macos")]
|
||||
#[must_use]
|
||||
fn tabbing_identifier(self, identifier: &str) -> Self;
|
||||
|
||||
/// Forces a theme or uses the system settings if None was provided.
|
||||
fn theme(self, theme: Option<Theme>) -> Self;
|
||||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A webview window that has yet to be built.
|
||||
/// A window that has yet to be built.
|
||||
pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
|
||||
/// The label that the window will be named.
|
||||
pub label: String,
|
||||
|
||||
/// The [`WindowBuilder`] that the window will be created with.
|
||||
pub window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
|
||||
pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
|
||||
|
||||
/// The [`WebviewAttributes`] that the webview will be created with.
|
||||
pub webview_attributes: WebviewAttributes,
|
||||
|
||||
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
|
||||
|
||||
/// How to handle IPC calls on the webview window.
|
||||
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
|
||||
|
||||
/// A handler to decide if incoming url is allowed to navigate.
|
||||
pub navigation_handler: Option<Box<NavigationHandler>>,
|
||||
|
||||
pub download_handler: Option<Arc<DownloadHandler>>,
|
||||
|
||||
/// The resolved URL to load on the webview.
|
||||
pub url: String,
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub on_webview_created:
|
||||
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
|
||||
|
||||
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||
|
||||
pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
|
||||
/// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
|
||||
pub webview: Option<PendingWebview<T, R>>,
|
||||
}
|
||||
|
||||
pub fn is_label_valid(label: &str) -> bool {
|
||||
@ -293,10 +418,9 @@ pub fn assert_label_is_valid(label: &str) {
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
/// Create a new [`PendingWindow`] with a label and starting url.
|
||||
/// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
|
||||
pub fn new(
|
||||
window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
|
||||
webview_attributes: WebviewAttributes,
|
||||
window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
|
||||
label: impl Into<String>,
|
||||
) -> crate::Result<Self> {
|
||||
let label = label.into();
|
||||
@ -305,95 +429,51 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||
} else {
|
||||
Ok(Self {
|
||||
window_builder,
|
||||
webview_attributes,
|
||||
uri_scheme_protocols: Default::default(),
|
||||
label,
|
||||
ipc_handler: None,
|
||||
navigation_handler: None,
|
||||
download_handler: None,
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
on_webview_created: None,
|
||||
web_resource_request_handler: None,
|
||||
on_page_load_handler: None,
|
||||
webview: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`PendingWindow`] from a [`WindowConfig`] with a label and starting url.
|
||||
pub fn with_config(
|
||||
window_config: WindowConfig,
|
||||
webview_attributes: WebviewAttributes,
|
||||
label: impl Into<String>,
|
||||
) -> crate::Result<Self> {
|
||||
let window_builder =
|
||||
<<R::Dispatcher as Dispatch<T>>::WindowBuilder>::with_config(window_config);
|
||||
|
||||
let label = label.into();
|
||||
if !is_label_valid(&label) {
|
||||
Err(crate::Error::InvalidWindowLabel)
|
||||
} else {
|
||||
Ok(Self {
|
||||
window_builder,
|
||||
webview_attributes,
|
||||
uri_scheme_protocols: Default::default(),
|
||||
label,
|
||||
ipc_handler: None,
|
||||
navigation_handler: None,
|
||||
download_handler: None,
|
||||
url: "tauri://localhost".to_string(),
|
||||
#[cfg(target_os = "android")]
|
||||
on_webview_created: None,
|
||||
web_resource_request_handler: None,
|
||||
on_page_load_handler: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_uri_scheme_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>(
|
||||
&mut self,
|
||||
uri_scheme: N,
|
||||
protocol: H,
|
||||
) {
|
||||
let uri_scheme = uri_scheme.into();
|
||||
self
|
||||
.uri_scheme_protocols
|
||||
.insert(uri_scheme, Box::new(protocol));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn on_webview_created<
|
||||
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
|
||||
>(
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.on_webview_created.replace(Box::new(f));
|
||||
/// Sets a webview to be created on the window.
|
||||
pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
|
||||
self.webview.replace(webview);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A webview window that is not yet managed by Tauri.
|
||||
/// Identifier of a window.
|
||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub struct WindowId(u32);
|
||||
|
||||
impl From<u32> for WindowId {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A window that is not yet managed by Tauri.
|
||||
#[derive(Debug)]
|
||||
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
|
||||
/// The identifier of the window.
|
||||
pub id: WindowId,
|
||||
/// Name of the window
|
||||
pub label: String,
|
||||
|
||||
/// The [`Dispatch`] associated with the window.
|
||||
pub dispatcher: R::Dispatcher,
|
||||
/// The [`WindowDispatch`] associated with the window.
|
||||
pub dispatcher: R::WindowDispatcher,
|
||||
|
||||
/// The webview dispatcher in case this window has an attached webview.
|
||||
pub webview: Option<DetachedWebview<T, R>>,
|
||||
}
|
||||
|
||||
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
label: self.label.clone(),
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
webview: self.webview.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ fn default_true() -> bool {
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
#[non_exhaustive]
|
||||
pub enum WindowUrl {
|
||||
pub enum WebviewUrl {
|
||||
/// An external URL.
|
||||
External(Url),
|
||||
/// The path portion of an app URL.
|
||||
@ -56,7 +56,7 @@ pub enum WindowUrl {
|
||||
App(PathBuf),
|
||||
}
|
||||
|
||||
impl fmt::Display for WindowUrl {
|
||||
impl fmt::Display for WebviewUrl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::External(url) => write!(f, "{url}"),
|
||||
@ -65,7 +65,7 @@ impl fmt::Display for WindowUrl {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WindowUrl {
|
||||
impl Default for WebviewUrl {
|
||||
fn default() -> Self {
|
||||
Self::App("index.html".into())
|
||||
}
|
||||
@ -353,7 +353,7 @@ pub struct Size {
|
||||
|
||||
/// Configuration for Apple Disk Image (.dmg) bundles.
|
||||
///
|
||||
/// See more: https://tauri.app/v1/api/config#dmgconfig
|
||||
/// See more: <https://tauri.app/v1/api/config#dmgconfig>
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
@ -1055,7 +1055,7 @@ pub struct WindowConfig {
|
||||
pub label: String,
|
||||
/// The window webview URL.
|
||||
#[serde(default)]
|
||||
pub url: WindowUrl,
|
||||
pub url: WebviewUrl,
|
||||
/// The user agent for the webview
|
||||
#[serde(alias = "user-agent")]
|
||||
pub user_agent: Option<String>,
|
||||
@ -1213,7 +1213,7 @@ impl Default for WindowConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: default_window_label(),
|
||||
url: WindowUrl::default(),
|
||||
url: WebviewUrl::default(),
|
||||
user_agent: None,
|
||||
file_drop_enabled: true,
|
||||
center: false,
|
||||
@ -1791,7 +1791,7 @@ fn default_min_sdk_version() -> u32 {
|
||||
#[non_exhaustive]
|
||||
pub enum AppUrl {
|
||||
/// The app's external URL, or the path to the directory containing the app assets.
|
||||
Url(WindowUrl),
|
||||
Url(WebviewUrl),
|
||||
/// An array of files to embed on the app.
|
||||
Files(Vec<PathBuf>),
|
||||
}
|
||||
@ -1910,13 +1910,13 @@ impl Default for BuildConfig {
|
||||
}
|
||||
|
||||
fn default_dev_path() -> AppUrl {
|
||||
AppUrl::Url(WindowUrl::External(
|
||||
AppUrl::Url(WebviewUrl::External(
|
||||
Url::parse("http://localhost:8080").unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
fn default_dist_dir() -> AppUrl {
|
||||
AppUrl::Url(WindowUrl::App("../dist".into()))
|
||||
AppUrl::Url(WebviewUrl::App("../dist".into()))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@ -2145,9 +2145,9 @@ mod build {
|
||||
};
|
||||
}
|
||||
|
||||
impl ToTokens for WindowUrl {
|
||||
impl ToTokens for WebviewUrl {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let prefix = quote! { ::tauri::utils::config::WindowUrl };
|
||||
let prefix = quote! { ::tauri::utils::config::WebviewUrl };
|
||||
|
||||
tokens.append_all(match self {
|
||||
Self::App(path) => {
|
||||
@ -2775,10 +2775,10 @@ mod test {
|
||||
// create a build config
|
||||
let build = BuildConfig {
|
||||
runner: None,
|
||||
dev_path: AppUrl::Url(WindowUrl::External(
|
||||
dev_path: AppUrl::Url(WebviewUrl::External(
|
||||
Url::parse("http://localhost:8080").unwrap(),
|
||||
)),
|
||||
dist_dir: AppUrl::Url(WindowUrl::App("../dist".into())),
|
||||
dist_dir: AppUrl::Url(WebviewUrl::App("../dist".into())),
|
||||
before_dev_command: None,
|
||||
before_build_command: None,
|
||||
before_bundle_command: None,
|
||||
@ -2792,7 +2792,7 @@ mod test {
|
||||
assert_eq!(d_bundle, tauri.bundle);
|
||||
assert_eq!(
|
||||
d_path,
|
||||
AppUrl::Url(WindowUrl::External(
|
||||
AppUrl::Url(WebviewUrl::External(
|
||||
Url::parse("http://localhost:8080").unwrap()
|
||||
))
|
||||
);
|
||||
|
||||
@ -17,6 +17,7 @@ rust-version = { workspace = true }
|
||||
no-default-features = true
|
||||
features = [
|
||||
"wry",
|
||||
"unstable",
|
||||
"custom-protocol",
|
||||
"tray-icon",
|
||||
"devtools",
|
||||
@ -132,6 +133,7 @@ default = [
|
||||
"tray-icon?/common-controls-v6",
|
||||
"muda/common-controls-v6"
|
||||
]
|
||||
unstable = [ ]
|
||||
tray-icon = [ "dep:tray-icon" ]
|
||||
tracing = [
|
||||
"dep:tracing",
|
||||
@ -155,7 +157,7 @@ macos-private-api = [
|
||||
"tauri-runtime/macos-private-api",
|
||||
"tauri-runtime-wry/macos-private-api"
|
||||
]
|
||||
window-data-url = [ "data-url" ]
|
||||
webview-data-url = [ "data-url" ]
|
||||
protocol-asset = [ "http-range" ]
|
||||
config-json5 = [ "tauri-macros/config-json5" ]
|
||||
config-toml = [ "tauri-macros/config-toml" ]
|
||||
@ -170,6 +172,11 @@ path = "../../examples/commands/main.rs"
|
||||
name = "helloworld"
|
||||
path = "../../examples/helloworld/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "multiwebview"
|
||||
path = "../../examples/multiwebview/main.rs"
|
||||
required-features = [ "unstable" ]
|
||||
|
||||
[[example]]
|
||||
name = "multiwindow"
|
||||
path = "../../examples/multiwindow/main.rs"
|
||||
|
||||
@ -94,11 +94,29 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
|
||||
("set_ignore_cursor_events", false),
|
||||
("start_dragging", false),
|
||||
("set_progress_bar", false),
|
||||
("print", false),
|
||||
("set_icon", false),
|
||||
("toggle_maximize", false),
|
||||
// internal
|
||||
("internal_toggle_maximize", true),
|
||||
("internal_on_mousemove", true),
|
||||
("internal_on_mousedown", true),
|
||||
],
|
||||
),
|
||||
(
|
||||
"webview",
|
||||
&[
|
||||
("create_webview", false),
|
||||
("create_webview_window", false),
|
||||
// getters
|
||||
("webview_position", true),
|
||||
("webview_size", true),
|
||||
// setters
|
||||
("webview_close", false),
|
||||
("set_webview_size", false),
|
||||
("set_webview_position", false),
|
||||
("set_webview_focus", false),
|
||||
("print", false),
|
||||
// internal
|
||||
("internal_toggle_devtools", true),
|
||||
],
|
||||
),
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-create-webview"
|
||||
description = "Enables the create_webview command without any pre-configured scope."
|
||||
commands.allow = ["create_webview"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-create-webview"
|
||||
description = "Denies the create_webview command without any pre-configured scope."
|
||||
commands.deny = ["create_webview"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-create-webview-window"
|
||||
description = "Enables the create_webview_window command without any pre-configured scope."
|
||||
commands.allow = ["create_webview_window"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-create-webview-window"
|
||||
description = "Denies the create_webview_window command without any pre-configured scope."
|
||||
commands.deny = ["create_webview_window"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-set-webview-focus"
|
||||
description = "Enables the set_webview_focus command without any pre-configured scope."
|
||||
commands.allow = ["set_webview_focus"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-set-webview-focus"
|
||||
description = "Denies the set_webview_focus command without any pre-configured scope."
|
||||
commands.deny = ["set_webview_focus"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-set-webview-position"
|
||||
description = "Enables the set_webview_position command without any pre-configured scope."
|
||||
commands.allow = ["set_webview_position"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-set-webview-position"
|
||||
description = "Denies the set_webview_position command without any pre-configured scope."
|
||||
commands.deny = ["set_webview_position"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-set-webview-size"
|
||||
description = "Enables the set_webview_size command without any pre-configured scope."
|
||||
commands.allow = ["set_webview_size"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-set-webview-size"
|
||||
description = "Denies the set_webview_size command without any pre-configured scope."
|
||||
commands.deny = ["set_webview_size"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-webview-close"
|
||||
description = "Enables the webview_close command without any pre-configured scope."
|
||||
commands.allow = ["webview_close"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-webview-close"
|
||||
description = "Denies the webview_close command without any pre-configured scope."
|
||||
commands.deny = ["webview_close"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-webview-position"
|
||||
description = "Enables the webview_position command without any pre-configured scope."
|
||||
commands.allow = ["webview_position"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-webview-position"
|
||||
description = "Denies the webview_position command without any pre-configured scope."
|
||||
commands.deny = ["webview_position"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-webview-size"
|
||||
description = "Enables the webview_size command without any pre-configured scope."
|
||||
commands.allow = ["webview_size"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-webview-size"
|
||||
description = "Denies the webview_size command without any pre-configured scope."
|
||||
commands.deny = ["webview_size"]
|
||||
@ -0,0 +1,8 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
[default]
|
||||
description = "Default permissions for the plugin."
|
||||
permissions = ["allow-webview-position", "allow-webview-size", "allow-internal-toggle-devtools"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-internal-on-mousedown"
|
||||
description = "Enables the internal_on_mousedown command without any pre-configured scope."
|
||||
commands.allow = ["internal_on_mousedown"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-internal-on-mousedown"
|
||||
description = "Denies the internal_on_mousedown command without any pre-configured scope."
|
||||
commands.deny = ["internal_on_mousedown"]
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../../.schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-internal-on-mousemove"
|
||||
description = "Enables the internal_on_mousemove command without any pre-configured scope."
|
||||
commands.allow = ["internal_on_mousemove"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-internal-on-mousemove"
|
||||
description = "Denies the internal_on_mousemove command without any pre-configured scope."
|
||||
commands.deny = ["internal_on_mousemove"]
|
||||
@ -5,4 +5,4 @@
|
||||
|
||||
[default]
|
||||
description = "Default permissions for the plugin."
|
||||
permissions = ["allow-scale-factor", "allow-inner-position", "allow-outer-position", "allow-inner-size", "allow-outer-size", "allow-is-fullscreen", "allow-is-minimized", "allow-is-maximized", "allow-is-focused", "allow-is-decorated", "allow-is-resizable", "allow-is-maximizable", "allow-is-minimizable", "allow-is-closable", "allow-is-visible", "allow-title", "allow-current-monitor", "allow-primary-monitor", "allow-available-monitors", "allow-theme", "allow-internal-toggle-maximize", "allow-internal-toggle-devtools"]
|
||||
permissions = ["allow-scale-factor", "allow-inner-position", "allow-outer-position", "allow-inner-size", "allow-outer-size", "allow-is-fullscreen", "allow-is-minimized", "allow-is-maximized", "allow-is-focused", "allow-is-decorated", "allow-is-resizable", "allow-is-maximizable", "allow-is-minimizable", "allow-is-closable", "allow-is-visible", "allow-title", "allow-current-monitor", "allow-primary-monitor", "allow-available-monitors", "allow-theme", "allow-internal-toggle-maximize", "allow-internal-on-mousemove", "allow-internal-on-mousedown"]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -8,27 +8,27 @@ use crate::{
|
||||
channel::ChannelDataIpcQueue, CallbackFn, Invoke, InvokeError, InvokeHandler, InvokeResponder,
|
||||
InvokeResponse,
|
||||
},
|
||||
manager::{window::UriSchemeProtocol, AppManager, Asset},
|
||||
manager::{
|
||||
webview::{UriSchemeProtocol, WebviewLabelDef},
|
||||
AppManager, Asset,
|
||||
},
|
||||
plugin::{Plugin, PluginStore},
|
||||
runtime::{
|
||||
webview::WebviewAttributes,
|
||||
window::{PendingWindow, WindowEvent as RuntimeWindowEvent},
|
||||
ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
|
||||
window::WindowEvent as RuntimeWindowEvent, ExitRequestedEventAction,
|
||||
RunEvent as RuntimeRunEvent,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
utils::config::Config,
|
||||
utils::{assets::Assets, Env},
|
||||
window::PageLoadPayload,
|
||||
webview::PageLoadPayload,
|
||||
Context, DeviceEventFilter, EventLoopMessage, Icon, Manager, Monitor, Runtime, Scopes,
|
||||
StateManager, Theme, Window,
|
||||
StateManager, Theme, Webview, WebviewWindowBuilder, Window,
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
use crate::menu::{Menu, MenuEvent};
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId};
|
||||
#[cfg(desktop)]
|
||||
use crate::window::WindowMenu;
|
||||
use raw_window_handle::HasRawDisplayHandle;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
use tauri_macros::default_runtime;
|
||||
@ -66,8 +66,8 @@ pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEve
|
||||
/// A closure that is run when the Tauri application is setting up.
|
||||
pub type SetupHook<R> =
|
||||
Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||
/// A closure that is run once every time a window is created and loaded.
|
||||
pub type OnPageLoad<R> = dyn Fn(&Window<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
|
||||
/// A closure that is run every time a page starts or finishes loading.
|
||||
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
|
||||
|
||||
/// Api exposed on the `ExitRequested` event.
|
||||
#[derive(Debug)]
|
||||
@ -262,7 +262,7 @@ impl AppHandle<crate::Wry> {
|
||||
/// Sends a window message to the event loop.
|
||||
pub fn send_tao_window_event(
|
||||
&self,
|
||||
window_id: tauri_runtime_wry::WindowId,
|
||||
window_id: tauri_runtime_wry::TaoWindowId,
|
||||
message: tauri_runtime_wry::WindowMessage,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
@ -287,7 +287,7 @@ impl<R: Runtime> Clone for AppHandle<R> {
|
||||
impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
|
||||
/// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail.
|
||||
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
|
||||
Ok(command.message.window().app_handle)
|
||||
Ok(command.message.webview().window().app_handle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,7 +403,6 @@ impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
pub struct App<R: Runtime> {
|
||||
runtime: Option<R>,
|
||||
pending_windows: Option<Vec<PendingWindow<EventLoopMessage, R>>>,
|
||||
setup: Option<SetupHook<R>>,
|
||||
manager: Arc<AppManager<R>>,
|
||||
handle: AppHandle<R>,
|
||||
@ -623,7 +622,7 @@ macro_rules! shared_app_impl {
|
||||
let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none();
|
||||
if has_app_wide_menu {
|
||||
window.set_menu(menu.clone())?;
|
||||
window.menu_lock().replace(WindowMenu {
|
||||
window.menu_lock().replace(crate::window::WindowMenu {
|
||||
is_app_wide: true,
|
||||
menu: menu.clone(),
|
||||
});
|
||||
@ -769,6 +768,7 @@ impl<R: Runtime> App<R> {
|
||||
self.handle.plugin(crate::path::plugin::init())?;
|
||||
self.handle.plugin(crate::event::plugin::init())?;
|
||||
self.handle.plugin(crate::window::plugin::init())?;
|
||||
self.handle.plugin(crate::webview::plugin::init())?;
|
||||
self.handle.plugin(crate::app::plugin::init())?;
|
||||
self.handle.plugin(crate::resources::plugin::init())?;
|
||||
#[cfg(desktop)]
|
||||
@ -949,9 +949,6 @@ pub struct Builder<R: Runtime> {
|
||||
/// Page load hook.
|
||||
on_page_load: Option<Arc<OnPageLoad<R>>>,
|
||||
|
||||
/// windows to create when starting up.
|
||||
pending_windows: Vec<PendingWindow<EventLoopMessage, R>>,
|
||||
|
||||
/// All passed plugins
|
||||
plugins: PluginStore<R>,
|
||||
|
||||
@ -1014,7 +1011,7 @@ impl<R: Runtime> Builder<R> {
|
||||
invoke_handler: Box::new(|_| false),
|
||||
invoke_responder: None,
|
||||
invoke_initialization_script: InvokeInitializationScript {
|
||||
process_ipc_message_fn: crate::manager::window::PROCESS_IPC_MESSAGE_FN,
|
||||
process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,
|
||||
os_name: std::env::consts::OS,
|
||||
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
|
||||
use_custom_protocol: cfg!(ipc_custom_protocol),
|
||||
@ -1023,7 +1020,6 @@ impl<R: Runtime> Builder<R> {
|
||||
.unwrap()
|
||||
.into_string(),
|
||||
on_page_load: None,
|
||||
pending_windows: Default::default(),
|
||||
plugins: PluginStore::default(),
|
||||
uri_scheme_protocols: Default::default(),
|
||||
state: StateManager::new(),
|
||||
@ -1082,7 +1078,7 @@ impl<R: Runtime> Builder<R> {
|
||||
#[must_use]
|
||||
pub fn invoke_system<F>(mut self, initialization_script: String, responder: F) -> Self
|
||||
where
|
||||
F: Fn(&Window<R>, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static,
|
||||
F: Fn(&Webview<R>, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static,
|
||||
{
|
||||
self.invoke_initialization_script = initialization_script;
|
||||
self.invoke_responder.replace(Arc::new(responder));
|
||||
@ -1092,15 +1088,20 @@ impl<R: Runtime> Builder<R> {
|
||||
/// Defines the setup hook.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use tauri::Manager;
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let main_window = app.get_window("main").unwrap();
|
||||
/// main_window.set_title("Tauri!");
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
feature = "unstable",
|
||||
doc = r####"
|
||||
```
|
||||
use tauri::Manager;
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let main_window = app.get_window("main").unwrap();
|
||||
main_window.set_title("Tauri!");
|
||||
Ok(())
|
||||
});
|
||||
```
|
||||
"####
|
||||
)]
|
||||
#[must_use]
|
||||
pub fn setup<F>(mut self, setup: F) -> Self
|
||||
where
|
||||
@ -1114,7 +1115,7 @@ impl<R: Runtime> Builder<R> {
|
||||
#[must_use]
|
||||
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
|
||||
where
|
||||
F: Fn(&Window<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,
|
||||
F: Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,
|
||||
{
|
||||
self.on_page_load.replace(Arc::new(on_page_load));
|
||||
self
|
||||
@ -1173,7 +1174,7 @@ impl<R: Runtime> Builder<R> {
|
||||
/// refers to a different `T`.
|
||||
///
|
||||
/// Managed state can be retrieved by any command handler via the
|
||||
/// [`State`] guard. In particular, if a value of type `T`
|
||||
/// [`crate::State`] guard. In particular, if a value of type `T`
|
||||
/// is managed by Tauri, adding `State<T>` to the list of arguments in a
|
||||
/// command handler instructs Tauri to retrieve the managed value.
|
||||
/// Additionally, [`state`](crate::Manager#method.state) can be used to retrieve the value manually.
|
||||
@ -1448,7 +1449,7 @@ impl<R: Runtime> Builder<R> {
|
||||
}
|
||||
|
||||
/// Builds the application.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(clippy::type_complexity, unused_mut)]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(name = "app::build", skip_all)
|
||||
@ -1474,18 +1475,6 @@ impl<R: Runtime> Builder<R> {
|
||||
(self.invoke_responder, self.invoke_initialization_script),
|
||||
));
|
||||
|
||||
// set up all the windows defined in the config
|
||||
for config in manager.config().tauri.windows.clone() {
|
||||
let label = config.label.clone();
|
||||
let webview_attributes = WebviewAttributes::from(&config);
|
||||
|
||||
self.pending_windows.push(PendingWindow::with_config(
|
||||
config,
|
||||
webview_attributes,
|
||||
label,
|
||||
)?);
|
||||
}
|
||||
|
||||
let runtime_args = RuntimeInitArgs {
|
||||
#[cfg(windows)]
|
||||
msg_hook: {
|
||||
@ -1542,7 +1531,6 @@ impl<R: Runtime> Builder<R> {
|
||||
#[allow(unused_mut)]
|
||||
let mut app = App {
|
||||
runtime: Some(runtime),
|
||||
pending_windows: Some(self.pending_windows),
|
||||
setup: Some(self.setup),
|
||||
manager: manager.clone(),
|
||||
handle: AppHandle {
|
||||
@ -1686,53 +1674,24 @@ unsafe impl<R: Runtime> HasRawDisplayHandle for App<R> {
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::setup"))]
|
||||
fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
|
||||
let pending_windows = app.pending_windows.take();
|
||||
if let Some(pending_windows) = pending_windows {
|
||||
let window_labels = pending_windows
|
||||
.iter()
|
||||
.map(|p| p.label.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let window_labels = app
|
||||
.config()
|
||||
.tauri
|
||||
.windows
|
||||
.iter()
|
||||
.map(|p| p.label.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let webview_labels = window_labels
|
||||
.iter()
|
||||
.map(|label| WebviewLabelDef {
|
||||
window_label: label.clone(),
|
||||
label: label.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let app_handle = app.handle();
|
||||
let manager = app.manager();
|
||||
|
||||
for pending in pending_windows {
|
||||
let pending = manager
|
||||
.window
|
||||
.prepare_window(app_handle.clone(), pending, &window_labels)?;
|
||||
|
||||
#[cfg(desktop)]
|
||||
let window_menu = app.manager.menu.menu_lock().as_ref().map(|m| WindowMenu {
|
||||
is_app_wide: true,
|
||||
menu: m.clone(),
|
||||
});
|
||||
|
||||
#[cfg(desktop)]
|
||||
let handler = manager
|
||||
.menu
|
||||
.prepare_window_menu_creation_handler(window_menu.as_ref());
|
||||
#[cfg(not(desktop))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
let handler: Option<Box<dyn Fn(tauri_runtime::window::RawWindow<'_>) + Send>> = None;
|
||||
|
||||
let window_effects = pending.webview_attributes.window_effects.clone();
|
||||
let detached = if let RuntimeOrDispatch::RuntimeHandle(runtime) = app_handle.runtime() {
|
||||
runtime.create_window(pending, handler)?
|
||||
} else {
|
||||
// the AppHandle's runtime is always RuntimeOrDispatch::RuntimeHandle
|
||||
unreachable!()
|
||||
};
|
||||
let window = manager.window.attach_window(
|
||||
app_handle.clone(),
|
||||
detached,
|
||||
#[cfg(desktop)]
|
||||
None,
|
||||
);
|
||||
|
||||
if let Some(effects) = window_effects {
|
||||
crate::vibrancy::set_window_effects(&window, Some(effects))?;
|
||||
}
|
||||
}
|
||||
for window_config in app.config().tauri.windows.clone() {
|
||||
WebviewWindowBuilder::from_config(app.handle(), window_config)
|
||||
.build_internal(&window_labels, &webview_labels)?;
|
||||
}
|
||||
|
||||
if let Some(setup) = app.setup.take() {
|
||||
@ -1753,6 +1712,7 @@ fn on_event_loop_event<R: Runtime, F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
|
||||
event: RuntimeWindowEvent::Destroyed,
|
||||
} = &event
|
||||
{
|
||||
// TODO: destroy webviews
|
||||
manager.window.on_window_close(label);
|
||||
}
|
||||
|
||||
@ -1804,7 +1764,7 @@ fn on_event_loop_event<R: Runtime, F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
|
||||
listener(app_handle, e.clone());
|
||||
}
|
||||
for (label, listener) in &*app_handle.manager.menu.event_listeners.lock().unwrap() {
|
||||
if let Some(w) = app_handle.get_window(label) {
|
||||
if let Some(w) = app_handle.manager().get_window(label) {
|
||||
listener(&w, e.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use tauri_utils::acl::{
|
||||
ExecutionContext,
|
||||
};
|
||||
|
||||
use crate::{ipc::InvokeError, Runtime};
|
||||
use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
|
||||
|
||||
use super::{CommandArg, CommandItem};
|
||||
|
||||
@ -127,8 +127,8 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman
|
||||
.and_then(|scope_id| {
|
||||
command
|
||||
.message
|
||||
.window
|
||||
.manager
|
||||
.webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.scope_manager
|
||||
.get_command_scope_typed(&scope_id)
|
||||
@ -161,8 +161,8 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman
|
||||
fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
|
||||
let scope = command
|
||||
.message
|
||||
.window
|
||||
.manager
|
||||
.webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.scope_manager
|
||||
.get_global_scope_typed();
|
||||
|
||||
@ -37,6 +37,9 @@ pub enum Error {
|
||||
/// Window label must be unique.
|
||||
#[error("a window with label `{0}` already exists")]
|
||||
WindowLabelAlreadyExists(String),
|
||||
/// Webview label must be unique.
|
||||
#[error("a webview with label `{0}` already exists")]
|
||||
WebviewLabelAlreadyExists(String),
|
||||
/// Embedded asset not found.
|
||||
#[error("asset not found: {0}")]
|
||||
AssetNotFound(String),
|
||||
@ -71,7 +74,7 @@ pub enum Error {
|
||||
IsolationPattern(#[from] tauri_utils::pattern::isolation::Error),
|
||||
/// An invalid window URL was provided. Includes details about the error.
|
||||
#[error("invalid window url: {0}")]
|
||||
InvalidWindowUrl(&'static str),
|
||||
InvalidWebviewUrl(&'static str),
|
||||
/// Invalid glob pattern.
|
||||
#[error("invalid glob pattern: {0}")]
|
||||
GlobPattern(#[from] glob::PatternError),
|
||||
@ -136,6 +139,12 @@ pub enum Error {
|
||||
/// The anyhow crate error.
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
/// webview not found.
|
||||
#[error("webview not found")]
|
||||
WebviewNotFound,
|
||||
/// API requires the unstable feature flag.
|
||||
#[error("this feature requires the `unstable` flag on Cargo.toml")]
|
||||
UnstableFeatureNotSupported,
|
||||
}
|
||||
|
||||
/// `Result<T, ::tauri::Error>`
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{Runtime, Window};
|
||||
use crate::{Runtime, Webview};
|
||||
|
||||
use super::{EmitArgs, Event, EventId};
|
||||
|
||||
@ -25,7 +25,7 @@ enum Pending<R: Runtime> {
|
||||
|
||||
/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
|
||||
struct Handler<R: Runtime> {
|
||||
window: Option<Window<R>>,
|
||||
webview: Option<Webview<R>>,
|
||||
callback: Box<dyn Fn(Event) + Send>,
|
||||
}
|
||||
|
||||
@ -127,12 +127,12 @@ impl<R: Runtime> Listeners<R> {
|
||||
pub(crate) fn listen<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: String,
|
||||
window: Option<Window<R>>,
|
||||
webview: Option<Webview<R>>,
|
||||
handler: F,
|
||||
) -> EventId {
|
||||
let id = self.next_event_id();
|
||||
let handler = Handler {
|
||||
window,
|
||||
webview,
|
||||
callback: Box::new(handler),
|
||||
};
|
||||
|
||||
@ -145,13 +145,13 @@ impl<R: Runtime> Listeners<R> {
|
||||
pub(crate) fn once<F: FnOnce(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: String,
|
||||
window: Option<Window<R>>,
|
||||
webview: Option<Webview<R>>,
|
||||
handler: F,
|
||||
) {
|
||||
let self_ = self.clone();
|
||||
let handler = Cell::new(Some(handler));
|
||||
|
||||
self.listen(event, window, move |event| {
|
||||
self.listen(event, webview, move |event| {
|
||||
self_.unlisten(event.id);
|
||||
let handler = handler
|
||||
.take()
|
||||
@ -173,7 +173,7 @@ impl<R: Runtime> Listeners<R> {
|
||||
/// Emits the given event with its payload based on a filter.
|
||||
pub(crate) fn emit_filter<F>(&self, emit_args: &EmitArgs, filter: Option<F>) -> crate::Result<()>
|
||||
where
|
||||
F: Fn(&Window<R>) -> bool,
|
||||
F: Fn(&Webview<R>) -> bool,
|
||||
{
|
||||
let mut maybe_pending = false;
|
||||
match self.inner.handlers.try_lock() {
|
||||
@ -185,7 +185,7 @@ impl<R: Runtime> Listeners<R> {
|
||||
.iter()
|
||||
.filter(|h| {
|
||||
h.1
|
||||
.window
|
||||
.webview
|
||||
.as_ref()
|
||||
.map(|w| {
|
||||
// clippy sees this as redundant closure but
|
||||
@ -222,14 +222,14 @@ impl<R: Runtime> Listeners<R> {
|
||||
|
||||
/// Emits the given event with its payload.
|
||||
pub(crate) fn emit(&self, emit_args: &EmitArgs) -> crate::Result<()> {
|
||||
self.emit_filter(emit_args, None::<&dyn Fn(&Window<R>) -> bool>)
|
||||
self.emit_filter(emit_args, None::<&dyn Fn(&Webview<R>) -> bool>)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::test::MockRuntime;
|
||||
use crate::{event::EventSource, test::MockRuntime};
|
||||
use proptest::prelude::*;
|
||||
|
||||
// dummy event handler function
|
||||
@ -289,7 +289,12 @@ mod test {
|
||||
// call listen with key and the event_fn dummy func
|
||||
listeners.listen(key.clone(), None, event_fn);
|
||||
// call on event with key and d.
|
||||
listeners.emit(&EmitArgs { event_name: key.clone(), event: serde_json::to_string(&key).unwrap(), source_window_label: "null".into(), payload: serde_json::to_string(&d).unwrap() })?;
|
||||
listeners.emit(&EmitArgs {
|
||||
event_name: key.clone(),
|
||||
event: serde_json::to_string(&key).unwrap(),
|
||||
source: serde_json::to_string(&EventSource::Global).unwrap(),
|
||||
payload: serde_json::to_string(&d).unwrap()
|
||||
})?;
|
||||
|
||||
// lock the mutex
|
||||
let l = listeners.inner.handlers.lock().unwrap();
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
mod listener;
|
||||
pub(crate) mod plugin;
|
||||
pub(crate) use listener::Listeners;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Checks if an event name is valid.
|
||||
pub fn is_event_name_valid(event: &str) -> bool {
|
||||
@ -24,6 +24,15 @@ pub fn assert_event_name_is_valid(event: &str) {
|
||||
/// Unique id of an event.
|
||||
pub type EventId = u32;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EventSource {
|
||||
Global,
|
||||
Window { label: String },
|
||||
Webview { label: String },
|
||||
}
|
||||
|
||||
/// Serialized emit arguments.
|
||||
#[derive(Clone)]
|
||||
pub struct EmitArgs {
|
||||
@ -31,24 +40,20 @@ pub struct EmitArgs {
|
||||
pub event_name: String,
|
||||
/// Serialized event name.
|
||||
pub event: String,
|
||||
/// Serialized source window label ("null" for global events)
|
||||
pub source_window_label: String,
|
||||
/// Serialized [`EventSource`].
|
||||
pub source: String,
|
||||
/// Serialized payload.
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
impl EmitArgs {
|
||||
pub fn from<S: Serialize>(
|
||||
event: &str,
|
||||
source_window_label: Option<&str>,
|
||||
payload: S,
|
||||
) -> crate::Result<Self> {
|
||||
pub fn from<S: Serialize>(event: &str, source: &EventSource, payload: S) -> crate::Result<Self> {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("window::emit::serialize").entered();
|
||||
Ok(EmitArgs {
|
||||
event_name: event.into(),
|
||||
event: serde_json::to_string(event)?,
|
||||
source_window_label: serde_json::to_string(&source_window_label)?,
|
||||
source: serde_json::to_string(source)?,
|
||||
payload: serde_json::to_string(&payload)?,
|
||||
})
|
||||
}
|
||||
@ -77,7 +82,7 @@ pub fn listen_js(
|
||||
listeners_object_name: &str,
|
||||
event: &str,
|
||||
event_id: EventId,
|
||||
window_label: Option<&str>,
|
||||
serialized_source: &str,
|
||||
handler: &str,
|
||||
) -> String {
|
||||
format!(
|
||||
@ -92,28 +97,23 @@ pub fn listen_js(
|
||||
const eventListeners = window['{listeners}'][{event}]
|
||||
const listener = {{
|
||||
id: {event_id},
|
||||
windowLabel: {window_label},
|
||||
source: {source},
|
||||
handler: {handler}
|
||||
}};
|
||||
eventListeners.push(listener);
|
||||
}})()
|
||||
",
|
||||
listeners = listeners_object_name,
|
||||
window_label = if let Some(l) = window_label {
|
||||
crate::runtime::window::assert_label_is_valid(l);
|
||||
format!("'{l}'")
|
||||
} else {
|
||||
"null".to_owned()
|
||||
},
|
||||
source = serialized_source,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn emit_js(event_emit_function_name: &str, emit_args: &EmitArgs) -> crate::Result<String> {
|
||||
Ok(format!(
|
||||
"(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
|
||||
"(function () {{ const fn = window['{}']; fn && fn({{event: {}, source: {}, payload: {}}}) }})()",
|
||||
event_emit_function_name,
|
||||
emit_args.event,
|
||||
emit_args.source_window_label,
|
||||
emit_args.source,
|
||||
emit_args.payload
|
||||
))
|
||||
}
|
||||
@ -143,7 +143,7 @@ pub fn event_initialization_script(function: &str, listeners: &str) -> String {
|
||||
|
||||
for (let i = listeners.length - 1; i >= 0; i--) {{
|
||||
const listener = listeners[i]
|
||||
if (listener.windowLabel === null || eventData.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
|
||||
if (listener.source.kind === 'global' || eventData.source.kind === 'global' || listener.source.kind === 'window' || eventData.source.kind === 'window' || listener.source.label === eventData.source.label) {{
|
||||
eventData.id = listener.id
|
||||
listener.handler(eventData)
|
||||
}}
|
||||
|
||||
@ -7,9 +7,11 @@ use serde_json::Value as JsonValue;
|
||||
use tauri_runtime::window::is_label_valid;
|
||||
|
||||
use crate::plugin::{Builder, TauriPlugin};
|
||||
use crate::{command, ipc::CallbackFn, EventId, Manager, Result, Runtime, Window};
|
||||
use crate::sealed::ManagerBase;
|
||||
use crate::{command, ipc::CallbackFn, EventId, Manager, Result, Runtime};
|
||||
use crate::{AppHandle, Webview};
|
||||
|
||||
use super::is_event_name_valid;
|
||||
use super::{is_event_name_valid, EventSource};
|
||||
|
||||
pub struct EventName(String);
|
||||
|
||||
@ -35,25 +37,25 @@ impl<'de> Deserialize<'de> for EventName {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowLabel(String);
|
||||
pub struct WebviewLabel(String);
|
||||
|
||||
impl AsRef<str> for WindowLabel {
|
||||
impl AsRef<str> for WebviewLabel {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for WindowLabel {
|
||||
impl<'de> Deserialize<'de> for WebviewLabel {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let event_id = String::deserialize(deserializer)?;
|
||||
if is_label_valid(&event_id) {
|
||||
Ok(WindowLabel(event_id))
|
||||
Ok(WebviewLabel(event_id))
|
||||
} else {
|
||||
Err(serde::de::Error::custom(
|
||||
"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.",
|
||||
"Webview label must include only alphanumeric characters, `-`, `/`, `:` and `_`.",
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -61,30 +63,45 @@ impl<'de> Deserialize<'de> for WindowLabel {
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub fn listen<R: Runtime>(
|
||||
window: Window<R>,
|
||||
app: AppHandle<R>,
|
||||
webview: Webview<R>,
|
||||
event: EventName,
|
||||
window_label: Option<WindowLabel>,
|
||||
webview_label: Option<WebviewLabel>,
|
||||
handler: CallbackFn,
|
||||
) -> Result<EventId> {
|
||||
window.listen_js(window_label.map(|l| l.0), event.0, handler)
|
||||
if let Some(l) = webview_label {
|
||||
app
|
||||
.manager()
|
||||
.get_webview(&l.0)
|
||||
.ok_or(crate::Error::WebviewNotFound)?
|
||||
.listen_js(EventSource::Webview { label: l.0 }, event.0, handler)
|
||||
} else {
|
||||
webview.listen_js(EventSource::Global, event.0, handler)
|
||||
}
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub fn unlisten<R: Runtime>(window: Window<R>, event: EventName, event_id: EventId) -> Result<()> {
|
||||
window.unlisten_js(event.as_ref(), event_id)
|
||||
pub fn unlisten<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
event: EventName,
|
||||
event_id: EventId,
|
||||
) -> Result<()> {
|
||||
webview.unlisten_js(event.as_ref(), event_id)
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub fn emit<R: Runtime>(
|
||||
window: Window<R>,
|
||||
app: AppHandle<R>,
|
||||
webview: Webview<R>,
|
||||
event: EventName,
|
||||
window_label: Option<WindowLabel>,
|
||||
target: Option<EventSource>,
|
||||
payload: Option<JsonValue>,
|
||||
) -> Result<()> {
|
||||
if let Some(label) = window_label {
|
||||
window.emit_filter(&event.0, payload, |w| label.as_ref() == w.label())
|
||||
} else {
|
||||
window.emit(&event.0, payload)
|
||||
let target = target.unwrap_or(EventSource::Global);
|
||||
match target {
|
||||
EventSource::Global => app.emit(&event.0, payload),
|
||||
EventSource::Webview { label } => webview.emit_to(&label, &event.0, payload),
|
||||
EventSource::Window { label } => webview.window().emit_to(&label, &event.0, payload),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ use crate::{
|
||||
command,
|
||||
command::{CommandArg, CommandItem},
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
Manager, Runtime, State, Window,
|
||||
Manager, Runtime, State, Webview,
|
||||
};
|
||||
|
||||
use super::{CallbackFn, InvokeBody, InvokeError, IpcResponse, Request, Response};
|
||||
@ -58,7 +58,7 @@ impl Serialize for Channel {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tauri::{ipc::JavaScriptChannelId, Runtime, Window};
|
||||
/// use tauri::{ipc::JavaScriptChannelId, Runtime, Webview};
|
||||
///
|
||||
/// #[derive(serde::Deserialize)]
|
||||
/// #[serde(rename_all = "camelCase")]
|
||||
@ -68,8 +68,8 @@ impl Serialize for Channel {
|
||||
/// }
|
||||
///
|
||||
/// #[tauri::command]
|
||||
/// fn add_button<R: Runtime>(window: Window<R>, button: Button) {
|
||||
/// let channel = button.on_click.channel_on(window);
|
||||
/// fn add_button<R: Runtime>(webview: Webview<R>, button: Button) {
|
||||
/// let channel = button.on_click.channel_on(webview);
|
||||
/// channel.send("clicked").unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
@ -87,9 +87,9 @@ impl FromStr for JavaScriptChannelId {
|
||||
}
|
||||
|
||||
impl JavaScriptChannelId {
|
||||
/// Gets a [`Channel`] for this channel ID on the given [`Window`].
|
||||
pub fn channel_on<R: Runtime>(&self, window: Window<R>) -> Channel {
|
||||
Channel::from_callback_fn(window, self.0)
|
||||
/// Gets a [`Channel`] for this channel ID on the given [`Webview`].
|
||||
pub fn channel_on<R: Runtime>(&self, webview: Webview<R>) -> Channel {
|
||||
Channel::from_callback_fn(webview, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,16 +131,16 @@ impl Channel {
|
||||
channel
|
||||
}
|
||||
|
||||
pub(crate) fn from_callback_fn<R: Runtime>(window: Window<R>, callback: CallbackFn) -> Self {
|
||||
pub(crate) fn from_callback_fn<R: Runtime>(webview: Webview<R>, callback: CallbackFn) -> Self {
|
||||
Channel::new_with_id(callback.0, move |body| {
|
||||
let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
window
|
||||
webview
|
||||
.state::<ChannelDataIpcQueue>()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(data_id, body);
|
||||
window.eval(&format!(
|
||||
webview.eval(&format!(
|
||||
"window.__TAURI_INTERNALS__.invoke('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then(window['_' + {}]).catch(console.error)",
|
||||
callback.0
|
||||
))
|
||||
@ -160,15 +160,15 @@ impl Channel {
|
||||
}
|
||||
|
||||
impl<'de, R: Runtime> CommandArg<'de, R> for Channel {
|
||||
/// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`Channel`].
|
||||
/// Grabs the [`Webview`] from the [`CommandItem`] and returns the associated [`Channel`].
|
||||
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
|
||||
let name = command.name;
|
||||
let arg = command.key;
|
||||
let window = command.message.window();
|
||||
let webview = command.message.webview();
|
||||
let value: String =
|
||||
Deserialize::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e))?;
|
||||
JavaScriptChannelId::from_str(&value)
|
||||
.map(|id| id.channel_on(window))
|
||||
.map(|id| id.channel_on(webview))
|
||||
.map_err(|_| {
|
||||
InvokeError::from_anyhow(anyhow::anyhow!(
|
||||
"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format"
|
||||
|
||||
@ -18,7 +18,8 @@ use tauri_utils::acl::resolved::ResolvedCommand;
|
||||
|
||||
use crate::{
|
||||
command::{CommandArg, CommandItem},
|
||||
Runtime, StateManager, Window,
|
||||
webview::Webview,
|
||||
Runtime, StateManager,
|
||||
};
|
||||
|
||||
pub(crate) mod channel;
|
||||
@ -33,10 +34,10 @@ pub type InvokeHandler<R> = dyn Fn(Invoke<R>) -> bool + Send + Sync + 'static;
|
||||
|
||||
/// A closure that is responsible for respond a JS message.
|
||||
pub type InvokeResponder<R> =
|
||||
dyn Fn(&Window<R>, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static;
|
||||
dyn Fn(&Webview<R>, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static;
|
||||
/// Similar to [`InvokeResponder`] but taking owned arguments.
|
||||
pub type OwnedInvokeResponder<R> =
|
||||
dyn FnOnce(Window<R>, String, InvokeResponse, CallbackFn, CallbackFn) + Send + 'static;
|
||||
dyn FnOnce(Webview<R>, String, InvokeResponse, CallbackFn, CallbackFn) + Send + 'static;
|
||||
|
||||
/// Possible values of an IPC payload.
|
||||
#[derive(Debug, Clone)]
|
||||
@ -243,7 +244,7 @@ impl From<InvokeError> for InvokeResponse {
|
||||
/// Resolver of a invoke message.
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
pub struct InvokeResolver<R: Runtime> {
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,
|
||||
cmd: String,
|
||||
pub(crate) callback: CallbackFn,
|
||||
@ -253,7 +254,7 @@ pub struct InvokeResolver<R: Runtime> {
|
||||
impl<R: Runtime> Clone for InvokeResolver<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
window: self.window.clone(),
|
||||
webview: self.webview.clone(),
|
||||
responder: self.responder.clone(),
|
||||
cmd: self.cmd.clone(),
|
||||
callback: self.callback,
|
||||
@ -264,14 +265,14 @@ impl<R: Runtime> Clone for InvokeResolver<R> {
|
||||
|
||||
impl<R: Runtime> InvokeResolver<R> {
|
||||
pub(crate) fn new(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,
|
||||
cmd: String,
|
||||
callback: CallbackFn,
|
||||
error: CallbackFn,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
webview,
|
||||
responder,
|
||||
cmd,
|
||||
callback,
|
||||
@ -287,7 +288,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
{
|
||||
crate::async_runtime::spawn(async move {
|
||||
Self::return_task(
|
||||
self.window,
|
||||
self.webview,
|
||||
self.responder,
|
||||
task,
|
||||
self.cmd,
|
||||
@ -309,7 +310,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
Err(err) => InvokeResponse::Err(err),
|
||||
};
|
||||
Self::return_result(
|
||||
self.window,
|
||||
self.webview,
|
||||
self.responder,
|
||||
response,
|
||||
self.cmd,
|
||||
@ -322,7 +323,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
/// Reply to the invoke promise with a serializable value.
|
||||
pub fn respond<T: IpcResponse>(self, value: Result<T, InvokeError>) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
self.webview,
|
||||
self.responder,
|
||||
value.into(),
|
||||
self.cmd,
|
||||
@ -339,7 +340,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
/// Reject the invoke promise with a value.
|
||||
pub fn reject<T: Serialize>(self, value: T) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
self.webview,
|
||||
self.responder,
|
||||
Result::<(), _>::Err(value).into(),
|
||||
self.cmd,
|
||||
@ -351,7 +352,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
/// Reject the invoke promise with an [`InvokeError`].
|
||||
pub fn invoke_error(self, error: InvokeError) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
self.webview,
|
||||
self.responder,
|
||||
error.into(),
|
||||
self.cmd,
|
||||
@ -366,7 +367,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
|
||||
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
|
||||
pub async fn return_task<T, F>(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,
|
||||
task: F,
|
||||
cmd: String,
|
||||
@ -378,7 +379,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
{
|
||||
let result = task.await;
|
||||
Self::return_closure(
|
||||
window,
|
||||
webview,
|
||||
responder,
|
||||
|| result,
|
||||
cmd,
|
||||
@ -388,7 +389,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
}
|
||||
|
||||
pub(crate) fn return_closure<T: IpcResponse, F: FnOnce() -> Result<T, InvokeError>>(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,
|
||||
f: F,
|
||||
cmd: String,
|
||||
@ -396,7 +397,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
error_callback: CallbackFn,
|
||||
) {
|
||||
Self::return_result(
|
||||
window,
|
||||
webview,
|
||||
responder,
|
||||
f().into(),
|
||||
cmd,
|
||||
@ -406,7 +407,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
}
|
||||
|
||||
pub(crate) fn return_result(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,
|
||||
response: InvokeResponse,
|
||||
cmd: String,
|
||||
@ -414,7 +415,7 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
error_callback: CallbackFn,
|
||||
) {
|
||||
(responder.lock().unwrap().take().expect("resolver consumed"))(
|
||||
window,
|
||||
webview,
|
||||
cmd,
|
||||
response,
|
||||
success_callback,
|
||||
@ -427,8 +428,8 @@ impl<R: Runtime> InvokeResolver<R> {
|
||||
#[default_runtime(crate::Wry, wry)]
|
||||
#[derive(Debug)]
|
||||
pub struct InvokeMessage<R: Runtime> {
|
||||
/// The window that received the invoke message.
|
||||
pub(crate) window: Window<R>,
|
||||
/// The webview that received the invoke message.
|
||||
pub(crate) webview: Webview<R>,
|
||||
/// Application managed state.
|
||||
pub(crate) state: Arc<StateManager>,
|
||||
/// The IPC command.
|
||||
@ -442,7 +443,7 @@ pub struct InvokeMessage<R: Runtime> {
|
||||
impl<R: Runtime> Clone for InvokeMessage<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
window: self.window.clone(),
|
||||
webview: self.webview.clone(),
|
||||
state: self.state.clone(),
|
||||
command: self.command.clone(),
|
||||
payload: self.payload.clone(),
|
||||
@ -452,16 +453,16 @@ impl<R: Runtime> Clone for InvokeMessage<R> {
|
||||
}
|
||||
|
||||
impl<R: Runtime> InvokeMessage<R> {
|
||||
/// Create an new [`InvokeMessage`] from a payload send to a window.
|
||||
/// Create an new [`InvokeMessage`] from a payload send by a webview.
|
||||
pub(crate) fn new(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
state: Arc<StateManager>,
|
||||
command: String,
|
||||
payload: InvokeBody,
|
||||
headers: HeaderMap,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
webview,
|
||||
state,
|
||||
command,
|
||||
payload,
|
||||
@ -475,16 +476,16 @@ impl<R: Runtime> InvokeMessage<R> {
|
||||
&self.command
|
||||
}
|
||||
|
||||
/// The window that received the invoke.
|
||||
/// The webview that received the invoke.
|
||||
#[inline(always)]
|
||||
pub fn window(&self) -> Window<R> {
|
||||
self.window.clone()
|
||||
pub fn webview(&self) -> Webview<R> {
|
||||
self.webview.clone()
|
||||
}
|
||||
|
||||
/// A reference to window that received the invoke.
|
||||
/// A reference to webview that received the invoke.
|
||||
#[inline(always)]
|
||||
pub fn window_ref(&self) -> &Window<R> {
|
||||
&self.window
|
||||
pub fn webview_ref(&self) -> &Webview<R> {
|
||||
&self.webview
|
||||
}
|
||||
|
||||
/// A reference to the payload the invoke received.
|
||||
|
||||
@ -6,7 +6,7 @@ use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
manager::AppManager,
|
||||
window::{InvokeRequest, UriSchemeProtocolHandler},
|
||||
webview::{InvokeRequest, UriSchemeProtocolHandler},
|
||||
Runtime,
|
||||
};
|
||||
use http::{
|
||||
@ -23,7 +23,7 @@ const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
|
||||
pub fn message_handler<R: Runtime>(
|
||||
manager: Arc<AppManager<R>>,
|
||||
) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {
|
||||
Box::new(move |window, request| handle_ipc_message(request, &manager, &window.label))
|
||||
Box::new(move |webview, request| handle_ipc_message(request, &manager, &webview.label))
|
||||
}
|
||||
|
||||
pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeProtocolHandler {
|
||||
@ -48,7 +48,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
|
||||
|
||||
match *request.method() {
|
||||
Method::POST => {
|
||||
if let Some(window) = manager.get_window(&label) {
|
||||
if let Some(webview) = manager.get_webview(&label) {
|
||||
match parse_invoke_request(&manager, request) {
|
||||
Ok(request) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
@ -62,9 +62,9 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
|
||||
#[cfg(feature = "tracing")]
|
||||
let request_span = tracing::trace_span!("ipc::request::handle", cmd = request.cmd);
|
||||
|
||||
window.on_message(
|
||||
webview.on_message(
|
||||
request,
|
||||
Box::new(move |_window, _cmd, response, _callback, _error| {
|
||||
Box::new(move |_webview, _cmd, response, _callback, _error| {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _respond_span = tracing::trace_span!(
|
||||
parent: &request_span,
|
||||
@ -125,7 +125,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
|
||||
.body(
|
||||
"failed to acquire window reference"
|
||||
"failed to acquire webview reference"
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into(),
|
||||
@ -164,7 +164,7 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
|
||||
fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, label: &str) {
|
||||
if let Some(window) = manager.get_window(label) {
|
||||
if let Some(webview) = manager.get_webview(label) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span =
|
||||
tracing::trace_span!("ipc::request", kind = "post-message", request = message).entered();
|
||||
@ -261,15 +261,16 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
#[cfg(feature = "tracing")]
|
||||
let request_span = tracing::trace_span!("ipc::request::handle", cmd = request.cmd);
|
||||
|
||||
window.on_message(
|
||||
webview.on_message(
|
||||
request,
|
||||
Box::new(move |window, cmd, response, callback, error| {
|
||||
Box::new(move |webview, cmd, response, callback, error| {
|
||||
use crate::ipc::{
|
||||
format_callback::{
|
||||
format as format_callback, format_result as format_callback_result,
|
||||
},
|
||||
Channel,
|
||||
};
|
||||
use crate::sealed::ManagerBase;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
@ -280,11 +281,11 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
.entered();
|
||||
|
||||
// the channel data command is the only command that uses a custom protocol on Linux
|
||||
if window.manager.window.invoke_responder.is_none()
|
||||
if webview.manager().webview.invoke_responder.is_none()
|
||||
&& cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
|
||||
{
|
||||
fn responder_eval<R: Runtime>(
|
||||
window: &crate::Window<R>,
|
||||
webview: &crate::Webview<R>,
|
||||
js: crate::Result<String>,
|
||||
error: CallbackFn,
|
||||
) {
|
||||
@ -294,7 +295,7 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
.expect("unable to serialize response error string to json"),
|
||||
};
|
||||
|
||||
let _ = window.eval(&eval_js);
|
||||
let _ = webview.eval(&eval_js);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
@ -315,10 +316,10 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
if !(cfg!(target_os = "macos") || cfg!(target_os = "ios"))
|
||||
&& matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
|
||||
{
|
||||
let _ = Channel::from_callback_fn(window, callback).send(v);
|
||||
let _ = Channel::from_callback_fn(webview, callback).send(v);
|
||||
} else {
|
||||
responder_eval(
|
||||
&window,
|
||||
&webview,
|
||||
format_callback_result(Result::<_, ()>::Ok(v), callback, error),
|
||||
error,
|
||||
)
|
||||
@ -327,17 +328,17 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
InvokeResponse::Ok(InvokeBody::Raw(v)) => {
|
||||
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
|
||||
responder_eval(
|
||||
&window,
|
||||
&webview,
|
||||
format_callback_result(Result::<_, ()>::Ok(v), callback, error),
|
||||
error,
|
||||
);
|
||||
} else {
|
||||
let _ =
|
||||
Channel::from_callback_fn(window, callback).send(InvokeBody::Raw(v.clone()));
|
||||
Channel::from_callback_fn(webview, callback).send(InvokeBody::Raw(v.clone()));
|
||||
}
|
||||
}
|
||||
InvokeResponse::Err(e) => responder_eval(
|
||||
&window,
|
||||
&webview,
|
||||
format_callback_result(Result::<(), _>::Err(&e.0), callback, error),
|
||||
error,
|
||||
),
|
||||
@ -350,7 +351,7 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::trace!("ipc.request.error {}", e);
|
||||
|
||||
let _ = window.eval(&format!(
|
||||
let _ = webview.eval(&format!(
|
||||
r#"console.error({})"#,
|
||||
serde_json::Value::String(e.to_string())
|
||||
));
|
||||
|
||||
@ -13,14 +13,15 @@
|
||||
//! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that can be enabled or disabled:
|
||||
//!
|
||||
//! - **wry** *(enabled by default)*: Enables the [wry](https://github.com/tauri-apps/wry) runtime. Only disable it if you want a custom runtime.
|
||||
//! - **unstable**: Enables unstable features. Be careful, it might introduce breaking changes in future minor releases.
|
||||
//! - **tracing**: Enables [`tracing`](https://docs.rs/tracing/latest/tracing) for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers.
|
||||
//! - **test**: Enables the [`test`] module exposing unit test helpers.
|
||||
//! - **test**: Enables the [`mod@test`] module exposing unit test helpers.
|
||||
//! - **objc-exception**: Wrap each msg_send! in a @try/@catch and panics if an exception is caught, preventing Objective-C from unwinding into Rust.
|
||||
//! - **linux-ipc-protocol**: Use custom protocol for faster IPC on Linux. Requires webkit2gtk v2.40 or above.
|
||||
//! - **linux-libxdo**: Enables linking to libxdo which enables Cut, Copy, Paste and SelectAll menu items to work on Linux.
|
||||
//! - **isolation**: Enables the isolation pattern. Enabled by default if the `tauri > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file.
|
||||
//! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one.
|
||||
//! - **devtools**: Enables the developer tools (Web inspector) and [`Window::open_devtools`]. Enabled by default on debug builds.
|
||||
//! - **devtools**: Enables the developer tools (Web inspector) and [`window::Window#method.open_devtools`]. Enabled by default on debug builds.
|
||||
//! On macOS it uses private APIs, so you can't enable it if your app will be published to the App Store.
|
||||
//! - **native-tls**: Provides TLS support to connect over HTTPS.
|
||||
//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL.
|
||||
@ -28,7 +29,7 @@
|
||||
//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website).
|
||||
//! - **tray-icon**: Enables application tray icon APIs. Enabled by default if the `trayIcon` config is defined on the `tauri.conf.json` file.
|
||||
//! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file.
|
||||
//! - **window-data-url**: Enables usage of data URLs on the webview.
|
||||
//! - **webview-data-url**: Enables usage of data URLs on the webview.
|
||||
//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.
|
||||
//! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`.
|
||||
//! - **config-toml**: Adds support to TOML format for the configuration `Tauri.toml`.
|
||||
@ -66,6 +67,7 @@ pub use cocoa;
|
||||
#[doc(hidden)]
|
||||
pub use embed_plist;
|
||||
pub use error::{Error, Result};
|
||||
use event::EventSource;
|
||||
pub use resources::{Resource, ResourceId, ResourceTable};
|
||||
#[cfg(target_os = "ios")]
|
||||
#[doc(hidden)]
|
||||
@ -86,6 +88,7 @@ pub mod plugin;
|
||||
pub(crate) mod protocol;
|
||||
mod resources;
|
||||
mod vibrancy;
|
||||
pub mod webview;
|
||||
pub mod window;
|
||||
use tauri_runtime as runtime;
|
||||
#[cfg(target_os = "ios")]
|
||||
@ -216,13 +219,18 @@ pub use {
|
||||
self::state::{State, StateManager},
|
||||
self::utils::{
|
||||
assets::Assets,
|
||||
config::{Config, WindowUrl},
|
||||
config::{Config, WebviewUrl},
|
||||
Env, PackageInfo, Theme,
|
||||
},
|
||||
self::window::{Monitor, Window, WindowBuilder},
|
||||
self::webview::{Webview, WebviewWindow, WebviewWindowBuilder},
|
||||
self::window::{Monitor, Window},
|
||||
scope::*,
|
||||
};
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
pub use {self::webview::WebviewBuilder, self::window::WindowBuilder};
|
||||
|
||||
/// The Tauri version.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@ -637,7 +645,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
self.manager().once(event.into(), None, handler)
|
||||
}
|
||||
|
||||
/// Emits an event to all windows.
|
||||
/// Emits an event to all webviews.
|
||||
///
|
||||
/// If using [`Window`] to emit the event, it will be used as the source.
|
||||
///
|
||||
@ -647,7 +655,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
///
|
||||
/// #[tauri::command]
|
||||
/// fn synchronize(app: tauri::AppHandle) {
|
||||
/// // emits the synchronized event to all windows
|
||||
/// // emits the synchronized event to all webviews
|
||||
/// app.emit("synchronized", ());
|
||||
/// }
|
||||
/// ```
|
||||
@ -656,10 +664,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
tracing::instrument("app::emit", skip(self, payload))
|
||||
)]
|
||||
fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
|
||||
self.manager().emit(event, None, payload)
|
||||
self.manager().emit(event, EventSource::Global, payload)
|
||||
}
|
||||
|
||||
/// Emits an event to the window with the specified label.
|
||||
/// Emits an event to the webview with the specified label.
|
||||
///
|
||||
/// If using [`Window`] to emit the event, it will be used as the source.
|
||||
///
|
||||
@ -683,10 +691,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
|
||||
self
|
||||
.manager()
|
||||
.emit_filter(event, None, payload, |w| label == w.label())
|
||||
.emit_filter(event, EventSource::Global, payload, |w| label == w.label())
|
||||
}
|
||||
|
||||
/// Emits an event to specific windows based on a filter.
|
||||
/// Emits an event to specific webviews based on a filter.
|
||||
///
|
||||
/// If using [`Window`] to emit the event, it will be used as the source.
|
||||
///
|
||||
@ -710,25 +718,75 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
|
||||
fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
|
||||
where
|
||||
S: Serialize + Clone,
|
||||
F: Fn(&Window<R>) -> bool,
|
||||
F: Fn(&Webview<R>) -> bool,
|
||||
{
|
||||
self.manager().emit_filter(event, None, payload, filter)
|
||||
self
|
||||
.manager()
|
||||
.emit_filter(event, EventSource::Global, payload, filter)
|
||||
}
|
||||
|
||||
/// Fetch a single window from the manager.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
fn get_window(&self, label: &str) -> Option<Window<R>> {
|
||||
self.manager().get_window(label)
|
||||
}
|
||||
|
||||
/// Fetch the focused window. Returns `None` if there is not any focused window.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
fn get_focused_window(&self) -> Option<Window<R>> {
|
||||
self.manager().get_focused_window()
|
||||
}
|
||||
|
||||
/// Fetch all managed windows.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
fn windows(&self) -> HashMap<String, Window<R>> {
|
||||
self.manager().windows()
|
||||
}
|
||||
|
||||
/// Fetch a single webview from the manager.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
fn get_webview(&self, label: &str) -> Option<Webview<R>> {
|
||||
self.manager().get_webview(label)
|
||||
}
|
||||
|
||||
/// Fetch all managed webviews.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
|
||||
fn webviews(&self) -> HashMap<String, Webview<R>> {
|
||||
self.manager().webviews()
|
||||
}
|
||||
|
||||
/// Fetch a single webview window from the manager.
|
||||
fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {
|
||||
self.manager().get_webview(label).and_then(|webview| {
|
||||
if webview.window().webview_window {
|
||||
Some(WebviewWindow { webview })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch all managed webview windows.
|
||||
fn webview_windows(&self) -> HashMap<String, WebviewWindow<R>> {
|
||||
self
|
||||
.manager()
|
||||
.webviews()
|
||||
.into_iter()
|
||||
.filter_map(|(label, webview)| {
|
||||
if webview.window().webview_window {
|
||||
Some((label, WebviewWindow { webview }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
}
|
||||
|
||||
/// Add `state` to the state managed by the application.
|
||||
///
|
||||
/// If the state for the `T` type has previously been set, the state is unchanged and false is returned. Otherwise true is returned.
|
||||
@ -884,7 +942,7 @@ pub(crate) mod sealed {
|
||||
RuntimeHandle(R::Handle),
|
||||
|
||||
/// A dispatcher to the running [`Runtime`].
|
||||
Dispatch(R::Dispatcher),
|
||||
Dispatch(R::WindowDispatcher),
|
||||
}
|
||||
|
||||
/// Managed handle to the application runtime.
|
||||
|
||||
@ -23,22 +23,23 @@ use tauri_utils::{
|
||||
use crate::{
|
||||
app::{AppHandle, GlobalWindowEventListener, OnPageLoad},
|
||||
command::RuntimeAuthority,
|
||||
event::{assert_event_name_is_valid, Event, EventId, Listeners},
|
||||
event::{assert_event_name_is_valid, Event, EventId, EventSource, Listeners},
|
||||
ipc::{Invoke, InvokeHandler, InvokeResponder},
|
||||
plugin::PluginStore,
|
||||
utils::{
|
||||
assets::Assets,
|
||||
config::{AppUrl, Config, WindowUrl},
|
||||
config::{AppUrl, Config, WebviewUrl},
|
||||
PackageInfo,
|
||||
},
|
||||
Context, Pattern, Runtime, StateManager, Window,
|
||||
};
|
||||
use crate::{event::EmitArgs, resources::ResourceTable};
|
||||
use crate::{event::EmitArgs, resources::ResourceTable, Webview};
|
||||
|
||||
#[cfg(desktop)]
|
||||
mod menu;
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
mod tray;
|
||||
pub mod webview;
|
||||
pub mod window;
|
||||
|
||||
#[derive(Default)]
|
||||
@ -181,6 +182,7 @@ pub struct Asset {
|
||||
pub struct AppManager<R: Runtime> {
|
||||
pub runtime_authority: RuntimeAuthority,
|
||||
pub window: window::WindowManager<R>,
|
||||
pub webview: webview::WebviewManager<R>,
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
pub tray: tray::TrayManager<R>,
|
||||
#[cfg(desktop)]
|
||||
@ -231,7 +233,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
plugins: PluginStore<R>,
|
||||
invoke_handler: Box<InvokeHandler<R>>,
|
||||
on_page_load: Option<Arc<OnPageLoad<R>>>,
|
||||
uri_scheme_protocols: HashMap<String, Arc<window::UriSchemeProtocol<R>>>,
|
||||
uri_scheme_protocols: HashMap<String, Arc<webview::UriSchemeProtocol<R>>>,
|
||||
state: StateManager,
|
||||
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
||||
#[cfg(desktop)] window_menu_event_listeners: HashMap<
|
||||
@ -250,11 +252,14 @@ impl<R: Runtime> AppManager<R> {
|
||||
runtime_authority: RuntimeAuthority::new(context.resolved_acl),
|
||||
window: window::WindowManager {
|
||||
windows: Mutex::default(),
|
||||
default_icon: context.default_window_icon,
|
||||
event_listeners: Arc::new(window_event_listeners),
|
||||
},
|
||||
webview: webview::WebviewManager {
|
||||
webviews: Mutex::default(),
|
||||
invoke_handler,
|
||||
on_page_load,
|
||||
default_icon: context.default_window_icon,
|
||||
uri_scheme_protocols: Mutex::new(uri_scheme_protocols),
|
||||
event_listeners: Arc::new(window_event_listeners),
|
||||
invoke_responder,
|
||||
invoke_initialization_script,
|
||||
},
|
||||
@ -308,7 +313,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
/// In dev mode, this will be based on the `devPath` configuration value.
|
||||
pub(crate) fn get_url(&self) -> Cow<'_, Url> {
|
||||
match self.base_path() {
|
||||
AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url),
|
||||
AppUrl::Url(WebviewUrl::External(url)) => Cow::Borrowed(url),
|
||||
_ => self.protocol_url(),
|
||||
}
|
||||
}
|
||||
@ -417,7 +422,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
}
|
||||
|
||||
pub fn run_invoke_handler(&self, invoke: Invoke<R>) -> bool {
|
||||
(self.window.invoke_handler)(invoke)
|
||||
(self.webview.invoke_handler)(invoke)
|
||||
}
|
||||
|
||||
pub fn extend_api(&self, plugin: &str, invoke: Invoke<R>) -> bool {
|
||||
@ -447,7 +452,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
pub fn listen<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: String,
|
||||
window: Option<Window<R>>,
|
||||
window: Option<Webview<R>>,
|
||||
handler: F,
|
||||
) -> EventId {
|
||||
assert_event_name_is_valid(&event);
|
||||
@ -461,43 +466,39 @@ impl<R: Runtime> AppManager<R> {
|
||||
pub fn once<F: FnOnce(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: String,
|
||||
window: Option<String>,
|
||||
webview: Option<String>,
|
||||
handler: F,
|
||||
) {
|
||||
assert_event_name_is_valid(&event);
|
||||
self
|
||||
.listeners()
|
||||
.once(event, window.and_then(|w| self.get_window(&w)), handler)
|
||||
.once(event, webview.and_then(|w| self.get_webview(&w)), handler)
|
||||
}
|
||||
|
||||
pub fn emit_filter<S, F>(
|
||||
&self,
|
||||
event: &str,
|
||||
source_window_label: Option<&str>,
|
||||
source: EventSource,
|
||||
payload: S,
|
||||
filter: F,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
S: Serialize + Clone,
|
||||
F: Fn(&Window<R>) -> bool,
|
||||
F: Fn(&Webview<R>) -> bool,
|
||||
{
|
||||
assert_event_name_is_valid(event);
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("emit::run").entered();
|
||||
|
||||
let emit_args = EmitArgs::from(event, source_window_label, payload)?;
|
||||
let emit_args = EmitArgs::from(event, &source, payload)?;
|
||||
|
||||
self
|
||||
.window
|
||||
.windows_lock()
|
||||
.webview
|
||||
.webviews_lock()
|
||||
.values()
|
||||
.filter(|w| {
|
||||
w.has_js_listener(None, event)
|
||||
|| w.has_js_listener(source_window_label.map(Into::into), event)
|
||||
})
|
||||
.filter(|w| w.has_js_listener(&source, event))
|
||||
.filter(|w| filter(w))
|
||||
.try_for_each(|window| window.emit_js(&emit_args))?;
|
||||
.try_for_each(|webview| webview.emit_js(&emit_args))?;
|
||||
|
||||
self.listeners().emit_filter(&emit_args, Some(filter))?;
|
||||
|
||||
@ -507,24 +508,20 @@ impl<R: Runtime> AppManager<R> {
|
||||
pub fn emit<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: &str,
|
||||
source_window_label: Option<&str>,
|
||||
source: EventSource,
|
||||
payload: S,
|
||||
) -> crate::Result<()> {
|
||||
assert_event_name_is_valid(event);
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("emit::run").entered();
|
||||
|
||||
let emit_args = EmitArgs::from(event, source_window_label, payload)?;
|
||||
let emit_args = EmitArgs::from(event, &source, payload)?;
|
||||
|
||||
self
|
||||
.window
|
||||
.windows_lock()
|
||||
.webview
|
||||
.webviews_lock()
|
||||
.values()
|
||||
.filter(|w| {
|
||||
w.has_js_listener(None, event)
|
||||
|| w.has_js_listener(source_window_label.map(Into::into), event)
|
||||
})
|
||||
.filter(|w| w.has_js_listener(&source, event))
|
||||
.try_for_each(|window| window.emit_js(&emit_args))?;
|
||||
|
||||
self.listeners().emit(&emit_args)?;
|
||||
@ -549,6 +546,14 @@ impl<R: Runtime> AppManager<R> {
|
||||
self.window.windows_lock().clone()
|
||||
}
|
||||
|
||||
pub fn get_webview(&self, label: &str) -> Option<Webview<R>> {
|
||||
self.webview.webviews_lock().get(label).cloned()
|
||||
}
|
||||
|
||||
pub fn webviews(&self) -> HashMap<String, Webview<R>> {
|
||||
self.webview.webviews_lock().clone()
|
||||
}
|
||||
|
||||
/// Resources table managed by the application.
|
||||
pub(crate) fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
|
||||
self
|
||||
@ -607,7 +612,7 @@ mod test {
|
||||
generate_context,
|
||||
plugin::PluginStore,
|
||||
test::{mock_app, MockRuntime},
|
||||
App, Manager, StateManager, Window, WindowBuilder, Wry,
|
||||
App, Manager, StateManager, WebviewWindow, WebviewWindowBuilder, Wry,
|
||||
};
|
||||
|
||||
use super::AppManager;
|
||||
@ -650,21 +655,21 @@ mod test {
|
||||
|
||||
struct EventSetup {
|
||||
app: App<MockRuntime>,
|
||||
window: Window<MockRuntime>,
|
||||
webview: WebviewWindow<MockRuntime>,
|
||||
tx: Sender<(&'static str, String)>,
|
||||
rx: Receiver<(&'static str, String)>,
|
||||
}
|
||||
|
||||
fn setup_events() -> EventSetup {
|
||||
let app = mock_app();
|
||||
let window = WindowBuilder::new(&app, "main", Default::default())
|
||||
let webview = WebviewWindowBuilder::new(&app, "main", Default::default())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let tx_ = tx.clone();
|
||||
window.listen(TEST_EVENT_NAME, move |evt| {
|
||||
webview.listen(TEST_EVENT_NAME, move |evt| {
|
||||
tx_
|
||||
.send((
|
||||
WINDOW_LISTEN_ID,
|
||||
@ -674,7 +679,7 @@ mod test {
|
||||
});
|
||||
|
||||
let tx_ = tx.clone();
|
||||
window.listen_global(TEST_EVENT_NAME, move |evt| {
|
||||
webview.listen_global(TEST_EVENT_NAME, move |evt| {
|
||||
tx_
|
||||
.send((
|
||||
WINDOW_LISTEN_GLOBAL_ID,
|
||||
@ -695,7 +700,7 @@ mod test {
|
||||
|
||||
EventSetup {
|
||||
app,
|
||||
window,
|
||||
webview,
|
||||
tx,
|
||||
rx,
|
||||
}
|
||||
@ -718,7 +723,7 @@ mod test {
|
||||
fn app_global_events() {
|
||||
let EventSetup {
|
||||
app,
|
||||
window: _,
|
||||
webview: _,
|
||||
tx: _,
|
||||
rx,
|
||||
} = setup_events();
|
||||
@ -744,14 +749,14 @@ mod test {
|
||||
fn window_global_events() {
|
||||
let EventSetup {
|
||||
app: _,
|
||||
window,
|
||||
webview,
|
||||
tx: _,
|
||||
rx,
|
||||
} = setup_events();
|
||||
|
||||
let mut received = Vec::new();
|
||||
let payload = "global-payload";
|
||||
window.emit(TEST_EVENT_NAME, payload).unwrap();
|
||||
webview.emit(TEST_EVENT_NAME, payload).unwrap();
|
||||
while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
|
||||
assert_eq!(p, payload);
|
||||
received.push(source);
|
||||
@ -770,15 +775,15 @@ mod test {
|
||||
fn window_local_events() {
|
||||
let EventSetup {
|
||||
app,
|
||||
window,
|
||||
webview,
|
||||
tx,
|
||||
rx,
|
||||
} = setup_events();
|
||||
|
||||
let mut received = Vec::new();
|
||||
let payload = "global-payload";
|
||||
window
|
||||
.emit_to(window.label(), TEST_EVENT_NAME, payload)
|
||||
webview
|
||||
.emit_to(webview.label(), TEST_EVENT_NAME, payload)
|
||||
.unwrap();
|
||||
while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
|
||||
assert_eq!(p, payload);
|
||||
@ -787,24 +792,25 @@ mod test {
|
||||
assert_events(&received, &[WINDOW_LISTEN_ID]);
|
||||
|
||||
received.clear();
|
||||
let other_window_listen_id = "OtherWindow::listen";
|
||||
let other_window = WindowBuilder::new(&app, "other", Default::default())
|
||||
let other_webview_listen_id = "OtherWebview::listen";
|
||||
let other_webview = WebviewWindowBuilder::new(&app, "other", Default::default())
|
||||
.build()
|
||||
.unwrap();
|
||||
other_window.listen(TEST_EVENT_NAME, move |evt| {
|
||||
|
||||
other_webview.listen(TEST_EVENT_NAME, move |evt| {
|
||||
tx.send((
|
||||
other_window_listen_id,
|
||||
other_webview_listen_id,
|
||||
serde_json::from_str::<String>(evt.payload()).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
});
|
||||
window
|
||||
.emit_to(other_window.label(), TEST_EVENT_NAME, payload)
|
||||
webview
|
||||
.emit_to(other_webview.label(), TEST_EVENT_NAME, payload)
|
||||
.unwrap();
|
||||
while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
|
||||
assert_eq!(p, payload);
|
||||
received.push(source);
|
||||
}
|
||||
assert_events(&received, &[other_window_listen_id]);
|
||||
assert_events(&received, &[other_webview_listen_id]);
|
||||
}
|
||||
}
|
||||
|
||||
599
core/tauri/src/manager/webview.rs
Normal file
599
core/tauri/src/manager/webview.rs
Normal file
@ -0,0 +1,599 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
fs::create_dir_all,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
use tauri_runtime::webview::{DetachedWebview, PendingWebview};
|
||||
use tauri_utils::config::WebviewUrl;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
app::{OnPageLoad, UriSchemeResponder},
|
||||
ipc::{InvokeHandler, InvokeResponder},
|
||||
pattern::PatternJavascript,
|
||||
sealed::ManagerBase,
|
||||
webview::PageLoadPayload,
|
||||
AppHandle, EventLoopMessage, Manager, Runtime, Webview, Window,
|
||||
};
|
||||
|
||||
use super::AppManager;
|
||||
|
||||
// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address
|
||||
// and we do not get a secure context without the custom protocol that proxies to the dev server
|
||||
// additionally, we need the custom protocol to inject the initialization scripts on Android
|
||||
// must also keep in sync with the `let mut response` assignment in prepare_uri_scheme_protocol
|
||||
pub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile));
|
||||
|
||||
pub(crate) const PROCESS_IPC_MESSAGE_FN: &str =
|
||||
include_str!("../../scripts/process-ipc-message-fn.js");
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/isolation.js")]
|
||||
pub(crate) struct IsolationJavascript<'a> {
|
||||
pub(crate) isolation_src: &'a str,
|
||||
pub(crate) style: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/ipc.js")]
|
||||
pub(crate) struct IpcJavascript<'a> {
|
||||
pub(crate) isolation_origin: &'a str,
|
||||
}
|
||||
|
||||
/// Uses a custom URI scheme handler to resolve file requests
|
||||
pub struct UriSchemeProtocol<R: Runtime> {
|
||||
/// Handler for protocol
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub protocol:
|
||||
Box<dyn Fn(&AppHandle<R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct WebviewLabelDef {
|
||||
pub window_label: String,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
pub struct WebviewManager<R: Runtime> {
|
||||
pub webviews: Mutex<HashMap<String, Webview<R>>>,
|
||||
/// The JS message handler.
|
||||
pub invoke_handler: Box<InvokeHandler<R>>,
|
||||
/// The page load hook, invoked when the webview performs a navigation.
|
||||
pub on_page_load: Option<Arc<OnPageLoad<R>>>,
|
||||
/// The webview protocols available to all webviews.
|
||||
pub uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,
|
||||
|
||||
/// Responder for invoke calls.
|
||||
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||
/// The script that initializes the invoke system.
|
||||
pub invoke_initialization_script: String,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("WebviewManager")
|
||||
.field(
|
||||
"invoke_initialization_script",
|
||||
&self.invoke_initialization_script,
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> WebviewManager<R> {
|
||||
pub(crate) fn register_uri_scheme_protocol<N: Into<String>>(
|
||||
&self,
|
||||
uri_scheme: N,
|
||||
protocol: Arc<UriSchemeProtocol<R>>,
|
||||
) {
|
||||
let uri_scheme = uri_scheme.into();
|
||||
self
|
||||
.uri_scheme_protocols
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(uri_scheme, protocol);
|
||||
}
|
||||
|
||||
/// Get a locked handle to the webviews.
|
||||
pub(crate) fn webviews_lock(&self) -> MutexGuard<'_, HashMap<String, Webview<R>>> {
|
||||
self.webviews.lock().expect("poisoned webview manager")
|
||||
}
|
||||
|
||||
fn prepare_pending_webview<M: Manager<R>>(
|
||||
&self,
|
||||
mut pending: PendingWebview<EventLoopMessage, R>,
|
||||
label: &str,
|
||||
window_label: &str,
|
||||
window_labels: &[String],
|
||||
webview_labels: &[WebviewLabelDef],
|
||||
manager: &M,
|
||||
) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
|
||||
let app_manager = manager.manager();
|
||||
|
||||
let is_init_global = app_manager.config.build.with_global_tauri;
|
||||
let plugin_init = app_manager
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.initialization_script();
|
||||
|
||||
let pattern_init = PatternJavascript {
|
||||
pattern: (&*app_manager.pattern).into(),
|
||||
}
|
||||
.render_default(&Default::default())?;
|
||||
|
||||
let mut webview_attributes = pending.webview_attributes;
|
||||
|
||||
let ipc_init = IpcJavascript {
|
||||
isolation_origin: &match &*app_manager.pattern {
|
||||
#[cfg(feature = "isolation")]
|
||||
crate::Pattern::Isolation { schema, .. } => crate::pattern::format_real_schema(schema),
|
||||
_ => "".to_string(),
|
||||
},
|
||||
}
|
||||
.render_default(&Default::default())?;
|
||||
|
||||
let mut webview_labels = webview_labels.to_vec();
|
||||
if !webview_labels.iter().any(|w| w.label == label) {
|
||||
webview_labels.push(WebviewLabelDef {
|
||||
window_label: window_label.to_string(),
|
||||
label: label.to_string(),
|
||||
});
|
||||
}
|
||||
webview_attributes = webview_attributes
|
||||
.initialization_script(
|
||||
r#"
|
||||
if (!window.__TAURI_INTERNALS__) {
|
||||
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
||||
value: {
|
||||
plugins: {}
|
||||
}
|
||||
})
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.initialization_script(&self.invoke_initialization_script)
|
||||
.initialization_script(&format!(
|
||||
r#"
|
||||
Object.defineProperty(window.__TAURI_INTERNALS__, 'metadata', {{
|
||||
value: {{
|
||||
windows: {window_labels_array}.map(function (label) {{ return {{ label: label }} }}),
|
||||
webviews: {webview_labels_array}.map(function (label) {{ return {{ label: label }} }}),
|
||||
currentWindow: {{ label: {current_window_label} }},
|
||||
currentWebview: {{ label: {current_webview_label} }}
|
||||
}}
|
||||
}})
|
||||
"#,
|
||||
window_labels_array = serde_json::to_string(&window_labels)?,
|
||||
webview_labels_array = serde_json::to_string(&webview_labels)?,
|
||||
current_window_label = serde_json::to_string(window_label)?,
|
||||
current_webview_label = serde_json::to_string(&label)?,
|
||||
))
|
||||
.initialization_script(&self.initialization_script(
|
||||
app_manager,
|
||||
&ipc_init.into_string(),
|
||||
&pattern_init.into_string(),
|
||||
&plugin_init,
|
||||
is_init_global,
|
||||
)?);
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation { schema, .. } = &*app_manager.pattern {
|
||||
webview_attributes = webview_attributes.initialization_script(
|
||||
&IsolationJavascript {
|
||||
isolation_src: &crate::pattern::format_real_schema(schema),
|
||||
style: tauri_utils::pattern::isolation::IFRAME_STYLE,
|
||||
}
|
||||
.render_default(&Default::default())?
|
||||
.into_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pending.webview_attributes = webview_attributes;
|
||||
|
||||
let mut registered_scheme_protocols = Vec::new();
|
||||
|
||||
for (uri_scheme, protocol) in &*self.uri_scheme_protocols.lock().unwrap() {
|
||||
registered_scheme_protocols.push(uri_scheme.clone());
|
||||
let protocol = protocol.clone();
|
||||
let app_handle = Mutex::new(manager.app_handle().clone());
|
||||
pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p, responder| {
|
||||
(protocol.protocol)(
|
||||
&app_handle.lock().unwrap(),
|
||||
p,
|
||||
UriSchemeResponder(responder),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let window_url = Url::parse(&pending.url).unwrap();
|
||||
let window_origin = if window_url.scheme() == "data" {
|
||||
"null".into()
|
||||
} else if (cfg!(windows) || cfg!(target_os = "android"))
|
||||
&& window_url.scheme() != "http"
|
||||
&& window_url.scheme() != "https"
|
||||
{
|
||||
format!("http://{}.localhost", window_url.scheme())
|
||||
} else {
|
||||
format!(
|
||||
"{}://{}{}",
|
||||
window_url.scheme(),
|
||||
window_url.host().unwrap(),
|
||||
window_url
|
||||
.port()
|
||||
.map(|p| format!(":{p}"))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
};
|
||||
|
||||
if !registered_scheme_protocols.contains(&"tauri".into()) {
|
||||
let web_resource_request_handler = pending.web_resource_request_handler.take();
|
||||
let protocol = crate::protocol::tauri::get(
|
||||
manager.manager_owned(),
|
||||
&window_origin,
|
||||
web_resource_request_handler,
|
||||
);
|
||||
pending.register_uri_scheme_protocol("tauri", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
registered_scheme_protocols.push("tauri".into());
|
||||
}
|
||||
|
||||
if !registered_scheme_protocols.contains(&"ipc".into()) {
|
||||
let protocol =
|
||||
crate::ipc::protocol::get(manager.manager_owned().clone(), pending.label.clone());
|
||||
pending.register_uri_scheme_protocol("ipc", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
registered_scheme_protocols.push("ipc".into());
|
||||
}
|
||||
|
||||
let label = pending.label.clone();
|
||||
let app_manager_ = manager.manager_owned();
|
||||
let on_page_load_handler = pending.on_page_load_handler.take();
|
||||
pending
|
||||
.on_page_load_handler
|
||||
.replace(Box::new(move |url, event| {
|
||||
let payload = PageLoadPayload { url: &url, event };
|
||||
|
||||
if let Some(w) = app_manager_.get_webview(&label) {
|
||||
if let Some(on_page_load) = &app_manager_.webview.on_page_load {
|
||||
on_page_load(&w, &payload);
|
||||
}
|
||||
|
||||
app_manager_
|
||||
.plugins
|
||||
.lock()
|
||||
.unwrap()
|
||||
.on_page_load(&w, &payload);
|
||||
}
|
||||
|
||||
if let Some(handler) = &on_page_load_handler {
|
||||
handler(url, event);
|
||||
}
|
||||
}));
|
||||
|
||||
#[cfg(feature = "protocol-asset")]
|
||||
if !registered_scheme_protocols.contains(&"asset".into()) {
|
||||
let asset_scope = app_manager
|
||||
.state()
|
||||
.get::<crate::Scopes>()
|
||||
.asset_protocol
|
||||
.clone();
|
||||
let protocol = crate::protocol::asset::get(asset_scope.clone(), window_origin.clone());
|
||||
pending.register_uri_scheme_protocol("asset", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation {
|
||||
assets,
|
||||
schema,
|
||||
key: _,
|
||||
crypto_keys,
|
||||
} = &*app_manager.pattern
|
||||
{
|
||||
let protocol = crate::protocol::isolation::get(assets.clone(), *crypto_keys.aes_gcm().raw());
|
||||
pending.register_uri_scheme_protocol(schema, move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
}
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
fn initialization_script(
|
||||
&self,
|
||||
app_manager: &AppManager<R>,
|
||||
ipc_script: &str,
|
||||
pattern_script: &str,
|
||||
plugin_initialization_script: &str,
|
||||
with_global_tauri: bool,
|
||||
) -> crate::Result<String> {
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/init.js")]
|
||||
struct InitJavascript<'a> {
|
||||
#[raw]
|
||||
pattern_script: &'a str,
|
||||
#[raw]
|
||||
ipc_script: &'a str,
|
||||
#[raw]
|
||||
bundle_script: &'a str,
|
||||
#[raw]
|
||||
core_script: &'a str,
|
||||
#[raw]
|
||||
event_initialization_script: &'a str,
|
||||
#[raw]
|
||||
plugin_initialization_script: &'a str,
|
||||
#[raw]
|
||||
freeze_prototype: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/core.js")]
|
||||
struct CoreJavascript<'a> {
|
||||
os_name: &'a str,
|
||||
}
|
||||
|
||||
let bundle_script = if with_global_tauri {
|
||||
include_str!("../../scripts/bundle.global.js")
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let freeze_prototype = if app_manager.config.tauri.security.freeze_prototype {
|
||||
include_str!("../../scripts/freeze_prototype.js")
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
InitJavascript {
|
||||
pattern_script,
|
||||
ipc_script,
|
||||
bundle_script,
|
||||
core_script: &CoreJavascript {
|
||||
os_name: std::env::consts::OS,
|
||||
}
|
||||
.render_default(&Default::default())?
|
||||
.into_string(),
|
||||
event_initialization_script: &crate::event::event_initialization_script(
|
||||
app_manager.listeners().function_name(),
|
||||
app_manager.listeners().listeners_object_name(),
|
||||
),
|
||||
plugin_initialization_script,
|
||||
freeze_prototype,
|
||||
}
|
||||
.render_default(&Default::default())
|
||||
.map(|s| s.into_string())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn prepare_webview<M: Manager<R>>(
|
||||
&self,
|
||||
manager: &M,
|
||||
mut pending: PendingWebview<EventLoopMessage, R>,
|
||||
window_label: &str,
|
||||
window_labels: &[String],
|
||||
webview_labels: &[WebviewLabelDef],
|
||||
) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
|
||||
if self.webviews_lock().contains_key(&pending.label) {
|
||||
return Err(crate::Error::WebviewLabelAlreadyExists(pending.label));
|
||||
}
|
||||
|
||||
let app_manager = manager.manager();
|
||||
|
||||
#[allow(unused_mut)] // mut url only for the data-url parsing
|
||||
let mut url = match &pending.webview_attributes.url {
|
||||
WebviewUrl::App(path) => {
|
||||
let url = if PROXY_DEV_SERVER {
|
||||
Cow::Owned(Url::parse("tauri://localhost").unwrap())
|
||||
} else {
|
||||
app_manager.get_url()
|
||||
};
|
||||
// ignore "index.html" just to simplify the url
|
||||
if path.to_str() != Some("index.html") {
|
||||
url
|
||||
.join(&path.to_string_lossy())
|
||||
.map_err(crate::Error::InvalidUrl)
|
||||
// this will never fail
|
||||
.unwrap()
|
||||
} else {
|
||||
url.into_owned()
|
||||
}
|
||||
}
|
||||
WebviewUrl::External(url) => {
|
||||
let config_url = app_manager.get_url();
|
||||
let is_local = config_url.make_relative(url).is_some();
|
||||
let mut url = url.clone();
|
||||
if is_local && PROXY_DEV_SERVER {
|
||||
url.set_scheme("tauri").unwrap();
|
||||
url.set_host(Some("localhost")).unwrap();
|
||||
}
|
||||
url
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "webview-data-url"))]
|
||||
if url.scheme() == "data" {
|
||||
return Err(crate::Error::InvalidWebviewUrl(
|
||||
"data URLs are not supported without the `webview-data-url` feature.",
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "webview-data-url")]
|
||||
if let Some(csp) = app_manager.csp() {
|
||||
if url.scheme() == "data" {
|
||||
if let Ok(data_url) = data_url::DataUrl::process(url.as_str()) {
|
||||
let (body, _) = data_url.decode_to_vec().unwrap();
|
||||
let html = String::from_utf8_lossy(&body).into_owned();
|
||||
// naive way to check if it's an html
|
||||
if html.contains('<') && html.contains('>') {
|
||||
let document = tauri_utils::html::parse(html);
|
||||
tauri_utils::html::inject_csp(&document, &csp.to_string());
|
||||
url.set_path(&format!("{},{}", mime::TEXT_HTML, document.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pending.url = url.to_string();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
pending = pending.on_webview_created(move |ctx| {
|
||||
let plugin_manager = ctx
|
||||
.env
|
||||
.call_method(
|
||||
ctx.activity,
|
||||
"getPluginManager",
|
||||
"()Lapp/tauri/plugin/PluginManager;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
|
||||
// tell the manager the webview is ready
|
||||
ctx.env.call_method(
|
||||
plugin_manager,
|
||||
"onWebViewCreated",
|
||||
"(Landroid/webkit/WebView;)V",
|
||||
&[ctx.webview.into()],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let label = pending.label.clone();
|
||||
pending = self.prepare_pending_webview(
|
||||
pending,
|
||||
&label,
|
||||
window_label,
|
||||
window_labels,
|
||||
webview_labels,
|
||||
manager,
|
||||
)?;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
|
||||
{
|
||||
pending.ipc_handler = Some(crate::ipc::protocol::message_handler(
|
||||
manager.manager_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
// in `windows`, we need to force a data_directory
|
||||
// but we do respect user-specification
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if pending.webview_attributes.data_directory.is_none() {
|
||||
let local_app_data = manager.path().resolve(
|
||||
&app_manager.config.tauri.bundle.identifier,
|
||||
crate::path::BaseDirectory::LocalData,
|
||||
);
|
||||
if let Ok(user_data_dir) = local_app_data {
|
||||
pending.webview_attributes.data_directory = Some(user_data_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the directory is created and available to prevent a panic
|
||||
if let Some(user_data_dir) = &pending.webview_attributes.data_directory {
|
||||
if !user_data_dir.exists() {
|
||||
create_dir_all(user_data_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
let pattern = app_manager.pattern.clone();
|
||||
let navigation_handler = pending.navigation_handler.take();
|
||||
let app_manager = manager.manager_owned();
|
||||
let label = pending.label.clone();
|
||||
pending.navigation_handler = Some(Box::new(move |url| {
|
||||
// always allow navigation events for the isolation iframe and do not emit them for consumers
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation { schema, .. } = &*pattern {
|
||||
if url.scheme() == schema
|
||||
&& url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(handler) = &navigation_handler {
|
||||
if !handler(url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let webview = app_manager.webview.webviews_lock().get(&label).cloned();
|
||||
if let Some(w) = webview {
|
||||
app_manager
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.on_navigation(&w, url)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
pub(crate) fn attach_webview(
|
||||
&self,
|
||||
window: Window<R>,
|
||||
webview: DetachedWebview<EventLoopMessage, R>,
|
||||
) -> Webview<R> {
|
||||
let webview = Webview::new(window, webview);
|
||||
|
||||
// insert the webview into our manager
|
||||
{
|
||||
self
|
||||
.webviews_lock()
|
||||
.insert(webview.label().to_string(), webview.clone());
|
||||
}
|
||||
|
||||
// let plugins know that a new webview has been added to the manager
|
||||
let manager = webview.manager_owned().clone();
|
||||
let webview_ = webview.clone();
|
||||
// run on main thread so the plugin store doesn't dead lock with the event loop handler in App
|
||||
let _ = webview.run_on_main_thread(move || {
|
||||
manager
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.webview_created(webview_);
|
||||
});
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
webview
|
||||
.with_webview(|w| {
|
||||
unsafe { crate::ios::on_webview_created(w.inner() as _, w.view_controller() as _) };
|
||||
})
|
||||
.expect("failed to run on_webview_created hook");
|
||||
}
|
||||
|
||||
webview
|
||||
}
|
||||
|
||||
pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {
|
||||
let script = script.into();
|
||||
self
|
||||
.webviews_lock()
|
||||
.values()
|
||||
.try_for_each(|webview| webview.eval(&script))
|
||||
}
|
||||
|
||||
pub fn labels(&self) -> HashSet<String> {
|
||||
self.webviews_lock().keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
@ -3,42 +3,28 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
fs::create_dir_all,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
use tauri_runtime::{
|
||||
webview::WindowBuilder,
|
||||
window::WindowBuilder,
|
||||
window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
DetachedWindow, FileDropEvent, PendingWindow,
|
||||
},
|
||||
};
|
||||
use tauri_utils::config::WindowUrl;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
app::{GlobalWindowEventListener, OnPageLoad, UriSchemeResponder},
|
||||
ipc::{InvokeHandler, InvokeResponder},
|
||||
pattern::PatternJavascript,
|
||||
window::PageLoadPayload,
|
||||
AppHandle, EventLoopMessage, Icon, Manager, Runtime, Scopes, Window, WindowEvent,
|
||||
app::GlobalWindowEventListener, event::EventSource, AppHandle, EventLoopMessage, Icon, Manager,
|
||||
Runtime, Scopes, Window, WindowEvent,
|
||||
};
|
||||
|
||||
use super::AppManager;
|
||||
|
||||
// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address
|
||||
// and we do not get a secure context without the custom protocol that proxies to the dev server
|
||||
// additionally, we need the custom protocol to inject the initialization scripts on Android
|
||||
// must also keep in sync with the `let mut response` assignment in prepare_uri_scheme_protocol
|
||||
pub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile));
|
||||
|
||||
const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
|
||||
const WINDOW_MOVED_EVENT: &str = "tauri://move";
|
||||
const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
|
||||
@ -51,403 +37,34 @@ const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop";
|
||||
const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover";
|
||||
const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled";
|
||||
|
||||
pub(crate) const PROCESS_IPC_MESSAGE_FN: &str =
|
||||
include_str!("../../scripts/process-ipc-message-fn.js");
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/isolation.js")]
|
||||
pub(crate) struct IsolationJavascript<'a> {
|
||||
pub(crate) isolation_src: &'a str,
|
||||
pub(crate) style: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/ipc.js")]
|
||||
pub(crate) struct IpcJavascript<'a> {
|
||||
pub(crate) isolation_origin: &'a str,
|
||||
}
|
||||
|
||||
/// Uses a custom URI scheme handler to resolve file requests
|
||||
pub struct UriSchemeProtocol<R: Runtime> {
|
||||
/// Handler for protocol
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub protocol:
|
||||
Box<dyn Fn(&AppHandle<R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>,
|
||||
}
|
||||
|
||||
pub struct WindowManager<R: Runtime> {
|
||||
pub windows: Mutex<HashMap<String, Window<R>>>,
|
||||
/// The JS message handler.
|
||||
pub invoke_handler: Box<InvokeHandler<R>>,
|
||||
/// The page load hook, invoked when the webview performs a navigation.
|
||||
pub on_page_load: Option<Arc<OnPageLoad<R>>>,
|
||||
pub default_icon: Option<Icon>,
|
||||
/// The webview protocols available to all windows.
|
||||
pub uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,
|
||||
|
||||
/// Window event listeners to all windows.
|
||||
pub event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
|
||||
|
||||
/// Responder for invoke calls.
|
||||
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||
/// The script that initializes the invoke system.
|
||||
pub invoke_initialization_script: String,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for WindowManager<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("WindowManager")
|
||||
.field("default_window_icon", &self.default_icon)
|
||||
.field(
|
||||
"invoke_initialization_script",
|
||||
&self.invoke_initialization_script,
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> WindowManager<R> {
|
||||
pub(crate) fn register_uri_scheme_protocol<N: Into<String>>(
|
||||
&self,
|
||||
uri_scheme: N,
|
||||
protocol: Arc<UriSchemeProtocol<R>>,
|
||||
) {
|
||||
let uri_scheme = uri_scheme.into();
|
||||
self
|
||||
.uri_scheme_protocols
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(uri_scheme, protocol);
|
||||
}
|
||||
|
||||
/// Get a locked handle to the windows.
|
||||
pub(crate) fn windows_lock(&self) -> MutexGuard<'_, HashMap<String, Window<R>>> {
|
||||
self.windows.lock().expect("poisoned window manager")
|
||||
}
|
||||
|
||||
fn prepare_pending_window(
|
||||
&self,
|
||||
mut pending: PendingWindow<EventLoopMessage, R>,
|
||||
label: &str,
|
||||
window_labels: &[String],
|
||||
app_handle: AppHandle<R>,
|
||||
) -> crate::Result<PendingWindow<EventLoopMessage, R>> {
|
||||
let is_init_global = app_handle.manager.config.build.with_global_tauri;
|
||||
let plugin_init = app_handle
|
||||
.manager
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.initialization_script();
|
||||
|
||||
let pattern_init = PatternJavascript {
|
||||
pattern: (&*app_handle.manager.pattern).into(),
|
||||
}
|
||||
.render_default(&Default::default())?;
|
||||
|
||||
let mut webview_attributes = pending.webview_attributes;
|
||||
|
||||
let ipc_init = IpcJavascript {
|
||||
isolation_origin: &match &*app_handle.manager.pattern {
|
||||
#[cfg(feature = "isolation")]
|
||||
crate::Pattern::Isolation { schema, .. } => crate::pattern::format_real_schema(schema),
|
||||
_ => "".to_string(),
|
||||
},
|
||||
}
|
||||
.render_default(&Default::default())?;
|
||||
|
||||
let mut window_labels = window_labels.to_vec();
|
||||
let l = label.to_string();
|
||||
if !window_labels.contains(&l) {
|
||||
window_labels.push(l);
|
||||
}
|
||||
webview_attributes = webview_attributes
|
||||
.initialization_script(
|
||||
r#"
|
||||
if (!window.__TAURI_INTERNALS__) {
|
||||
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
||||
value: {
|
||||
plugins: {}
|
||||
}
|
||||
})
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.initialization_script(&self.invoke_initialization_script)
|
||||
.initialization_script(&format!(
|
||||
r#"
|
||||
Object.defineProperty(window.__TAURI_INTERNALS__, 'metadata', {{
|
||||
value: {{
|
||||
windows: {window_labels_array}.map(function (label) {{ return {{ label: label }} }}),
|
||||
currentWindow: {{ label: {current_window_label} }}
|
||||
}}
|
||||
}})
|
||||
"#,
|
||||
window_labels_array = serde_json::to_string(&window_labels)?,
|
||||
current_window_label = serde_json::to_string(&label)?,
|
||||
))
|
||||
.initialization_script(&self.initialization_script(
|
||||
&app_handle.manager,
|
||||
&ipc_init.into_string(),
|
||||
&pattern_init.into_string(),
|
||||
&plugin_init,
|
||||
is_init_global,
|
||||
)?);
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation { schema, .. } = &*app_handle.manager.pattern {
|
||||
webview_attributes = webview_attributes.initialization_script(
|
||||
&IsolationJavascript {
|
||||
isolation_src: &crate::pattern::format_real_schema(schema),
|
||||
style: tauri_utils::pattern::isolation::IFRAME_STYLE,
|
||||
}
|
||||
.render_default(&Default::default())?
|
||||
.into_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pending.webview_attributes = webview_attributes;
|
||||
|
||||
let mut registered_scheme_protocols = Vec::new();
|
||||
|
||||
for (uri_scheme, protocol) in &*self.uri_scheme_protocols.lock().unwrap() {
|
||||
registered_scheme_protocols.push(uri_scheme.clone());
|
||||
let protocol = protocol.clone();
|
||||
let app_handle = Mutex::new(app_handle.clone());
|
||||
pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p, responder| {
|
||||
(protocol.protocol)(
|
||||
&app_handle.lock().unwrap(),
|
||||
p,
|
||||
UriSchemeResponder(responder),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let window_url = Url::parse(&pending.url).unwrap();
|
||||
let window_origin = if window_url.scheme() == "data" {
|
||||
"null".into()
|
||||
} else if (cfg!(windows) || cfg!(target_os = "android"))
|
||||
&& window_url.scheme() != "http"
|
||||
&& window_url.scheme() != "https"
|
||||
{
|
||||
format!("http://{}.localhost", window_url.scheme())
|
||||
} else {
|
||||
format!(
|
||||
"{}://{}{}",
|
||||
window_url.scheme(),
|
||||
window_url.host().unwrap(),
|
||||
window_url
|
||||
.port()
|
||||
.map(|p| format!(":{p}"))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
};
|
||||
|
||||
if !registered_scheme_protocols.contains(&"tauri".into()) {
|
||||
let web_resource_request_handler = pending.web_resource_request_handler.take();
|
||||
let protocol = crate::protocol::tauri::get(
|
||||
app_handle.manager.clone(),
|
||||
&window_origin,
|
||||
web_resource_request_handler,
|
||||
);
|
||||
pending.register_uri_scheme_protocol("tauri", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
registered_scheme_protocols.push("tauri".into());
|
||||
}
|
||||
|
||||
if !registered_scheme_protocols.contains(&"ipc".into()) {
|
||||
let protocol = crate::ipc::protocol::get(app_handle.manager.clone(), pending.label.clone());
|
||||
pending.register_uri_scheme_protocol("ipc", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
registered_scheme_protocols.push("ipc".into());
|
||||
}
|
||||
|
||||
let label = pending.label.clone();
|
||||
let manager = app_handle.manager.clone();
|
||||
let on_page_load_handler = pending.on_page_load_handler.take();
|
||||
pending
|
||||
.on_page_load_handler
|
||||
.replace(Box::new(move |url, event| {
|
||||
let payload = PageLoadPayload { url: &url, event };
|
||||
|
||||
if let Some(w) = manager.get_window(&label) {
|
||||
if let Some(on_page_load) = &manager.window.on_page_load {
|
||||
on_page_load(&w, &payload);
|
||||
}
|
||||
|
||||
manager.plugins.lock().unwrap().on_page_load(&w, &payload);
|
||||
}
|
||||
|
||||
if let Some(handler) = &on_page_load_handler {
|
||||
handler(url, event);
|
||||
}
|
||||
}));
|
||||
|
||||
#[cfg(feature = "protocol-asset")]
|
||||
if !registered_scheme_protocols.contains(&"asset".into()) {
|
||||
let asset_scope = app_handle
|
||||
.manager
|
||||
.state()
|
||||
.get::<crate::Scopes>()
|
||||
.asset_protocol
|
||||
.clone();
|
||||
let protocol = crate::protocol::asset::get(asset_scope.clone(), window_origin.clone());
|
||||
pending.register_uri_scheme_protocol("asset", move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation {
|
||||
assets,
|
||||
schema,
|
||||
key: _,
|
||||
crypto_keys,
|
||||
} = &*app_handle.manager.pattern
|
||||
{
|
||||
let protocol = crate::protocol::isolation::get(assets.clone(), *crypto_keys.aes_gcm().raw());
|
||||
pending.register_uri_scheme_protocol(schema, move |request, responder| {
|
||||
protocol(request, UriSchemeResponder(responder))
|
||||
});
|
||||
}
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
fn initialization_script(
|
||||
&self,
|
||||
app_manager: &AppManager<R>,
|
||||
ipc_script: &str,
|
||||
pattern_script: &str,
|
||||
plugin_initialization_script: &str,
|
||||
with_global_tauri: bool,
|
||||
) -> crate::Result<String> {
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/init.js")]
|
||||
struct InitJavascript<'a> {
|
||||
#[raw]
|
||||
pattern_script: &'a str,
|
||||
#[raw]
|
||||
ipc_script: &'a str,
|
||||
#[raw]
|
||||
bundle_script: &'a str,
|
||||
#[raw]
|
||||
core_script: &'a str,
|
||||
#[raw]
|
||||
event_initialization_script: &'a str,
|
||||
#[raw]
|
||||
plugin_initialization_script: &'a str,
|
||||
#[raw]
|
||||
freeze_prototype: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("../../scripts/core.js")]
|
||||
struct CoreJavascript<'a> {
|
||||
os_name: &'a str,
|
||||
}
|
||||
|
||||
let bundle_script = if with_global_tauri {
|
||||
include_str!("../../scripts/bundle.global.js")
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let freeze_prototype = if app_manager.config.tauri.security.freeze_prototype {
|
||||
include_str!("../../scripts/freeze_prototype.js")
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
InitJavascript {
|
||||
pattern_script,
|
||||
ipc_script,
|
||||
bundle_script,
|
||||
core_script: &CoreJavascript {
|
||||
os_name: std::env::consts::OS,
|
||||
}
|
||||
.render_default(&Default::default())?
|
||||
.into_string(),
|
||||
event_initialization_script: &crate::event::event_initialization_script(
|
||||
app_manager.listeners().function_name(),
|
||||
app_manager.listeners().listeners_object_name(),
|
||||
),
|
||||
plugin_initialization_script,
|
||||
freeze_prototype,
|
||||
}
|
||||
.render_default(&Default::default())
|
||||
.map(|s| s.into_string())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn prepare_window(
|
||||
&self,
|
||||
app_handle: AppHandle<R>,
|
||||
mut pending: PendingWindow<EventLoopMessage, R>,
|
||||
window_labels: &[String],
|
||||
) -> crate::Result<PendingWindow<EventLoopMessage, R>> {
|
||||
if self.windows_lock().contains_key(&pending.label) {
|
||||
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
|
||||
}
|
||||
#[allow(unused_mut)] // mut url only for the data-url parsing
|
||||
let mut url = match &pending.webview_attributes.url {
|
||||
WindowUrl::App(path) => {
|
||||
let url = if PROXY_DEV_SERVER {
|
||||
Cow::Owned(Url::parse("tauri://localhost").unwrap())
|
||||
} else {
|
||||
app_handle.manager.get_url()
|
||||
};
|
||||
// ignore "index.html" just to simplify the url
|
||||
if path.to_str() != Some("index.html") {
|
||||
url
|
||||
.join(&path.to_string_lossy())
|
||||
.map_err(crate::Error::InvalidUrl)
|
||||
// this will never fail
|
||||
.unwrap()
|
||||
} else {
|
||||
url.into_owned()
|
||||
}
|
||||
}
|
||||
WindowUrl::External(url) => {
|
||||
let config_url = app_handle.manager.get_url();
|
||||
let is_local = config_url.make_relative(url).is_some();
|
||||
let mut url = url.clone();
|
||||
if is_local && PROXY_DEV_SERVER {
|
||||
url.set_scheme("tauri").unwrap();
|
||||
url.set_host(Some("localhost")).unwrap();
|
||||
}
|
||||
url
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "window-data-url"))]
|
||||
if url.scheme() == "data" {
|
||||
return Err(crate::Error::InvalidWindowUrl(
|
||||
"data URLs are not supported without the `window-data-url` feature.",
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "window-data-url")]
|
||||
if let Some(csp) = app_handle.manager.csp() {
|
||||
if url.scheme() == "data" {
|
||||
if let Ok(data_url) = data_url::DataUrl::process(url.as_str()) {
|
||||
let (body, _) = data_url.decode_to_vec().unwrap();
|
||||
let html = String::from_utf8_lossy(&body).into_owned();
|
||||
// naive way to check if it's an html
|
||||
if html.contains('<') && html.contains('>') {
|
||||
let document = tauri_utils::html::parse(html);
|
||||
tauri_utils::html::inject_csp(&document, &csp.to_string());
|
||||
url.set_path(&format!("{},{}", mime::TEXT_HTML, document.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pending.url = url.to_string();
|
||||
|
||||
if !pending.window_builder.has_icon() {
|
||||
if let Some(default_window_icon) = self.default_icon.clone() {
|
||||
@ -457,99 +74,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
pending = pending.on_webview_created(move |ctx| {
|
||||
let plugin_manager = ctx
|
||||
.env
|
||||
.call_method(
|
||||
ctx.activity,
|
||||
"getPluginManager",
|
||||
"()Lapp/tauri/plugin/PluginManager;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
|
||||
// tell the manager the webview is ready
|
||||
ctx.env.call_method(
|
||||
plugin_manager,
|
||||
"onWebViewCreated",
|
||||
"(Landroid/webkit/WebView;)V",
|
||||
&[ctx.webview.into()],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let label = pending.label.clone();
|
||||
pending = self.prepare_pending_window(
|
||||
pending,
|
||||
&label,
|
||||
window_labels,
|
||||
#[allow(clippy::redundant_clone)]
|
||||
app_handle.clone(),
|
||||
)?;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
|
||||
{
|
||||
pending.ipc_handler = Some(crate::ipc::protocol::message_handler(
|
||||
app_handle.manager.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
// in `Windows`, we need to force a data_directory
|
||||
// but we do respect user-specification
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if pending.webview_attributes.data_directory.is_none() {
|
||||
let local_app_data = app_handle.path().resolve(
|
||||
&app_handle.manager.config.tauri.bundle.identifier,
|
||||
crate::path::BaseDirectory::LocalData,
|
||||
);
|
||||
if let Ok(user_data_dir) = local_app_data {
|
||||
pending.webview_attributes.data_directory = Some(user_data_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the directory is created and available to prevent a panic
|
||||
if let Some(user_data_dir) = &pending.webview_attributes.data_directory {
|
||||
if !user_data_dir.exists() {
|
||||
create_dir_all(user_data_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
let pattern = app_handle.manager.pattern.clone();
|
||||
let navigation_handler = pending.navigation_handler.take();
|
||||
let manager = app_handle.manager.clone();
|
||||
let label = pending.label.clone();
|
||||
pending.navigation_handler = Some(Box::new(move |url| {
|
||||
// always allow navigation events for the isolation iframe and do not emit them for consumers
|
||||
#[cfg(feature = "isolation")]
|
||||
if let crate::Pattern::Isolation { schema, .. } = &*pattern {
|
||||
if url.scheme() == schema
|
||||
&& url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(handler) = &navigation_handler {
|
||||
if !handler(url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let window = manager.window.windows_lock().get(&label).cloned();
|
||||
if let Some(w) = window {
|
||||
manager
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.on_navigation(&w, url)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
@ -557,6 +81,7 @@ impl<R: Runtime> WindowManager<R> {
|
||||
&self,
|
||||
app_handle: AppHandle<R>,
|
||||
window: DetachedWindow<EventLoopMessage, R>,
|
||||
multiwebview: bool,
|
||||
#[cfg(desktop)] menu: Option<crate::window::WindowMenu<R>>,
|
||||
) -> Window<R> {
|
||||
let window = Window::new(
|
||||
@ -565,6 +90,7 @@ impl<R: Runtime> WindowManager<R> {
|
||||
app_handle,
|
||||
#[cfg(desktop)]
|
||||
menu,
|
||||
multiwebview,
|
||||
);
|
||||
|
||||
let window_ = window.clone();
|
||||
@ -593,18 +119,9 @@ impl<R: Runtime> WindowManager<R> {
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.created(window_);
|
||||
.window_created(window_);
|
||||
});
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
window
|
||||
.with_webview(|w| {
|
||||
unsafe { crate::ios::on_webview_created(w.inner() as _, w.view_controller() as _) };
|
||||
})
|
||||
.expect("failed to run on_webview_created hook");
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
@ -612,14 +129,6 @@ impl<R: Runtime> WindowManager<R> {
|
||||
self.windows_lock().remove(label);
|
||||
}
|
||||
|
||||
pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {
|
||||
let script = script.into();
|
||||
self
|
||||
.windows_lock()
|
||||
.values()
|
||||
.try_for_each(|window| window.eval(&script))
|
||||
}
|
||||
|
||||
pub fn labels(&self) -> HashSet<String> {
|
||||
self.windows_lock().keys().cloned().collect()
|
||||
}
|
||||
@ -640,7 +149,14 @@ fn on_window_event<R: Runtime>(
|
||||
WindowEvent::Resized(size) => window.emit(WINDOW_RESIZED_EVENT, size)?,
|
||||
WindowEvent::Moved(position) => window.emit(WINDOW_MOVED_EVENT, position)?,
|
||||
WindowEvent::CloseRequested { api } => {
|
||||
if window.has_js_listener(Some(window.label().into()), WINDOW_CLOSE_REQUESTED_EVENT) {
|
||||
if window.webviews().iter().any(|w| {
|
||||
w.has_js_listener(
|
||||
&EventSource::Window {
|
||||
label: window.label().into(),
|
||||
},
|
||||
WINDOW_CLOSE_REQUESTED_EVENT,
|
||||
)
|
||||
}) {
|
||||
api.prevent_close();
|
||||
}
|
||||
window.emit(WINDOW_CLOSE_REQUESTED_EVENT, ())?;
|
||||
@ -648,10 +164,10 @@ fn on_window_event<R: Runtime>(
|
||||
WindowEvent::Destroyed => {
|
||||
window.emit(WINDOW_DESTROYED_EVENT, ())?;
|
||||
let label = window.label();
|
||||
let windows_map = manager.window.windows_lock();
|
||||
let windows = windows_map.values();
|
||||
for window in windows {
|
||||
window.eval(&format!(
|
||||
let webviews_map = manager.webview.webviews_lock();
|
||||
let webviews = webviews_map.values();
|
||||
for webview in webviews {
|
||||
webview.eval(&format!(
|
||||
r#"(function () {{ const metadata = window.__TAURI_INTERNALS__.metadata; if (metadata != null) {{ metadata.windows = window.__TAURI_INTERNALS__.metadata.windows.filter(w => w.label !== "{label}"); }} }})()"#,
|
||||
))?;
|
||||
}
|
||||
|
||||
@ -16,7 +16,8 @@ use crate::{
|
||||
ipc::{channel::JavaScriptChannelId, Channel},
|
||||
plugin::{Builder, TauriPlugin},
|
||||
resources::{ResourceId, ResourceTable},
|
||||
AppHandle, IconDto, Manager, RunEvent, Runtime, State, Window,
|
||||
sealed::ManagerBase,
|
||||
AppHandle, IconDto, Manager, RunEvent, Runtime, State, Webview, Window,
|
||||
};
|
||||
use tauri_macros::do_menu_item;
|
||||
|
||||
@ -97,19 +98,19 @@ struct SubmenuPayload {
|
||||
impl SubmenuPayload {
|
||||
pub fn create_item<R: Runtime>(
|
||||
self,
|
||||
window: &Window<R>,
|
||||
webview: &Webview<R>,
|
||||
resources_table: &MutexGuard<'_, ResourceTable>,
|
||||
) -> crate::Result<Submenu<R>> {
|
||||
let mut builder = if let Some(id) = self.id {
|
||||
SubmenuBuilder::with_id(window, id, self.text)
|
||||
SubmenuBuilder::with_id(webview, id, self.text)
|
||||
} else {
|
||||
SubmenuBuilder::new(window, self.text)
|
||||
SubmenuBuilder::new(webview, self.text)
|
||||
};
|
||||
if let Some(enabled) = self.enabled {
|
||||
builder = builder.enabled(enabled);
|
||||
}
|
||||
for item in self.items {
|
||||
builder = item.with_item(window, resources_table, |i| Ok(builder.item(i)))?;
|
||||
builder = item.with_item(webview, resources_table, |i| Ok(builder.item(i)))?;
|
||||
}
|
||||
|
||||
builder.build()
|
||||
@ -127,7 +128,7 @@ struct CheckMenuItemPayload {
|
||||
}
|
||||
|
||||
impl CheckMenuItemPayload {
|
||||
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> CheckMenuItem<R> {
|
||||
pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> CheckMenuItem<R> {
|
||||
let mut builder = if let Some(id) = self.id {
|
||||
CheckMenuItemBuilder::with_id(id, self.text)
|
||||
} else {
|
||||
@ -140,11 +141,11 @@ impl CheckMenuItemPayload {
|
||||
builder = builder.enabled(enabled);
|
||||
}
|
||||
|
||||
let item = builder.checked(self.checked).build(window);
|
||||
let item = builder.checked(self.checked).build(webview);
|
||||
|
||||
if let Some(handler) = self.handler {
|
||||
let handler = handler.channel_on(window.clone());
|
||||
window
|
||||
let handler = handler.channel_on(webview.clone());
|
||||
webview
|
||||
.state::<MenuChannels>()
|
||||
.0
|
||||
.lock()
|
||||
@ -175,7 +176,7 @@ struct IconMenuItemPayload {
|
||||
}
|
||||
|
||||
impl IconMenuItemPayload {
|
||||
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> IconMenuItem<R> {
|
||||
pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> IconMenuItem<R> {
|
||||
let mut builder = if let Some(id) = self.id {
|
||||
IconMenuItemBuilder::with_id(id, self.text)
|
||||
} else {
|
||||
@ -192,11 +193,11 @@ impl IconMenuItemPayload {
|
||||
Icon::Icon(icon) => builder.icon(icon.into()),
|
||||
};
|
||||
|
||||
let item = builder.build(window);
|
||||
let item = builder.build(webview);
|
||||
|
||||
if let Some(handler) = self.handler {
|
||||
let handler = handler.channel_on(window.clone());
|
||||
window
|
||||
let handler = handler.channel_on(webview.clone());
|
||||
webview
|
||||
.state::<MenuChannels>()
|
||||
.0
|
||||
.lock()
|
||||
@ -218,7 +219,7 @@ struct MenuItemPayload {
|
||||
}
|
||||
|
||||
impl MenuItemPayload {
|
||||
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> MenuItem<R> {
|
||||
pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> MenuItem<R> {
|
||||
let mut builder = if let Some(id) = self.id {
|
||||
MenuItemBuilder::with_id(id, self.text)
|
||||
} else {
|
||||
@ -231,11 +232,11 @@ impl MenuItemPayload {
|
||||
builder = builder.enabled(enabled);
|
||||
}
|
||||
|
||||
let item = builder.build(window);
|
||||
let item = builder.build(webview);
|
||||
|
||||
if let Some(handler) = self.handler {
|
||||
let handler = handler.channel_on(window.clone());
|
||||
window
|
||||
let handler = handler.channel_on(webview.clone());
|
||||
webview
|
||||
.state::<MenuChannels>()
|
||||
.0
|
||||
.lock()
|
||||
@ -254,27 +255,27 @@ struct PredefinedMenuItemPayload {
|
||||
}
|
||||
|
||||
impl PredefinedMenuItemPayload {
|
||||
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> PredefinedMenuItem<R> {
|
||||
pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> PredefinedMenuItem<R> {
|
||||
match self.item {
|
||||
Predefined::Separator => PredefinedMenuItem::separator(window),
|
||||
Predefined::Copy => PredefinedMenuItem::copy(window, self.text.as_deref()),
|
||||
Predefined::Cut => PredefinedMenuItem::cut(window, self.text.as_deref()),
|
||||
Predefined::Paste => PredefinedMenuItem::paste(window, self.text.as_deref()),
|
||||
Predefined::SelectAll => PredefinedMenuItem::select_all(window, self.text.as_deref()),
|
||||
Predefined::Undo => PredefinedMenuItem::undo(window, self.text.as_deref()),
|
||||
Predefined::Redo => PredefinedMenuItem::redo(window, self.text.as_deref()),
|
||||
Predefined::Minimize => PredefinedMenuItem::minimize(window, self.text.as_deref()),
|
||||
Predefined::Maximize => PredefinedMenuItem::maximize(window, self.text.as_deref()),
|
||||
Predefined::Fullscreen => PredefinedMenuItem::fullscreen(window, self.text.as_deref()),
|
||||
Predefined::Hide => PredefinedMenuItem::hide(window, self.text.as_deref()),
|
||||
Predefined::HideOthers => PredefinedMenuItem::hide_others(window, self.text.as_deref()),
|
||||
Predefined::ShowAll => PredefinedMenuItem::show_all(window, self.text.as_deref()),
|
||||
Predefined::CloseWindow => PredefinedMenuItem::close_window(window, self.text.as_deref()),
|
||||
Predefined::Quit => PredefinedMenuItem::quit(window, self.text.as_deref()),
|
||||
Predefined::Separator => PredefinedMenuItem::separator(webview),
|
||||
Predefined::Copy => PredefinedMenuItem::copy(webview, self.text.as_deref()),
|
||||
Predefined::Cut => PredefinedMenuItem::cut(webview, self.text.as_deref()),
|
||||
Predefined::Paste => PredefinedMenuItem::paste(webview, self.text.as_deref()),
|
||||
Predefined::SelectAll => PredefinedMenuItem::select_all(webview, self.text.as_deref()),
|
||||
Predefined::Undo => PredefinedMenuItem::undo(webview, self.text.as_deref()),
|
||||
Predefined::Redo => PredefinedMenuItem::redo(webview, self.text.as_deref()),
|
||||
Predefined::Minimize => PredefinedMenuItem::minimize(webview, self.text.as_deref()),
|
||||
Predefined::Maximize => PredefinedMenuItem::maximize(webview, self.text.as_deref()),
|
||||
Predefined::Fullscreen => PredefinedMenuItem::fullscreen(webview, self.text.as_deref()),
|
||||
Predefined::Hide => PredefinedMenuItem::hide(webview, self.text.as_deref()),
|
||||
Predefined::HideOthers => PredefinedMenuItem::hide_others(webview, self.text.as_deref()),
|
||||
Predefined::ShowAll => PredefinedMenuItem::show_all(webview, self.text.as_deref()),
|
||||
Predefined::CloseWindow => PredefinedMenuItem::close_window(webview, self.text.as_deref()),
|
||||
Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
|
||||
Predefined::About(metadata) => {
|
||||
PredefinedMenuItem::about(window, self.text.as_deref(), metadata.map(Into::into))
|
||||
PredefinedMenuItem::about(webview, self.text.as_deref(), metadata.map(Into::into))
|
||||
}
|
||||
Predefined::Services => PredefinedMenuItem::services(window, self.text.as_deref()),
|
||||
Predefined::Services => PredefinedMenuItem::services(webview, self.text.as_deref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -293,7 +294,7 @@ enum MenuItemPayloadKind {
|
||||
impl MenuItemPayloadKind {
|
||||
pub fn with_item<T, R: Runtime, F: FnOnce(&dyn IsMenuItem<R>) -> crate::Result<T>>(
|
||||
self,
|
||||
window: &Window<R>,
|
||||
webview: &Webview<R>,
|
||||
resources_table: &MutexGuard<'_, ResourceTable>,
|
||||
f: F,
|
||||
) -> crate::Result<T> {
|
||||
@ -301,11 +302,11 @@ impl MenuItemPayloadKind {
|
||||
Self::ExistingItem((rid, kind)) => {
|
||||
do_menu_item!(resources_table, rid, kind, |i| f(&*i))
|
||||
}
|
||||
Self::Submenu(i) => f(&i.create_item(window, resources_table)?),
|
||||
Self::Predefined(i) => f(&i.create_item(window)),
|
||||
Self::Check(i) => f(&i.create_item(window)),
|
||||
Self::Icon(i) => f(&i.create_item(window)),
|
||||
Self::MenuItem(i) => f(&i.create_item(window)),
|
||||
Self::Submenu(i) => f(&i.create_item(webview, resources_table)?),
|
||||
Self::Predefined(i) => f(&i.create_item(webview)),
|
||||
Self::Check(i) => f(&i.create_item(webview)),
|
||||
Self::Icon(i) => f(&i.create_item(webview)),
|
||||
Self::MenuItem(i) => f(&i.create_item(webview)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -327,7 +328,7 @@ struct NewOptions {
|
||||
#[command(root = "crate")]
|
||||
fn new<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
kind: ItemKind,
|
||||
options: Option<NewOptions>,
|
||||
channels: State<'_, MenuChannels>,
|
||||
@ -344,7 +345,7 @@ fn new<R: Runtime>(
|
||||
}
|
||||
if let Some(items) = options.items {
|
||||
for item in items {
|
||||
builder = item.with_item(&window, &resources_table, |i| Ok(builder.item(i)))?;
|
||||
builder = item.with_item(&webview, &resources_table, |i| Ok(builder.item(i)))?;
|
||||
}
|
||||
}
|
||||
let menu = builder.build()?;
|
||||
@ -361,7 +362,7 @@ fn new<R: Runtime>(
|
||||
enabled: options.enabled,
|
||||
items: options.items.unwrap_or_default(),
|
||||
}
|
||||
.create_item(&window, &resources_table)?;
|
||||
.create_item(&webview, &resources_table)?;
|
||||
let id = submenu.id().clone();
|
||||
let rid = resources_table.add(submenu);
|
||||
|
||||
@ -377,7 +378,7 @@ fn new<R: Runtime>(
|
||||
enabled: options.enabled,
|
||||
accelerator: options.accelerator,
|
||||
}
|
||||
.create_item(&window);
|
||||
.create_item(&webview);
|
||||
let id = item.id().clone();
|
||||
let rid = resources_table.add(item);
|
||||
(rid, id)
|
||||
@ -388,7 +389,7 @@ fn new<R: Runtime>(
|
||||
item: options.predefined_item.unwrap(),
|
||||
text: options.text,
|
||||
}
|
||||
.create_item(&window);
|
||||
.create_item(&webview);
|
||||
let id = item.id().clone();
|
||||
let rid = resources_table.add(item);
|
||||
(rid, id)
|
||||
@ -404,7 +405,7 @@ fn new<R: Runtime>(
|
||||
enabled: options.enabled,
|
||||
accelerator: options.accelerator,
|
||||
}
|
||||
.create_item(&window);
|
||||
.create_item(&webview);
|
||||
let id = item.id().clone();
|
||||
let rid = resources_table.add(item);
|
||||
(rid, id)
|
||||
@ -420,7 +421,7 @@ fn new<R: Runtime>(
|
||||
enabled: options.enabled,
|
||||
accelerator: options.accelerator,
|
||||
}
|
||||
.create_item(&window);
|
||||
.create_item(&webview);
|
||||
let id = item.id().clone();
|
||||
let rid = resources_table.add(item);
|
||||
(rid, id)
|
||||
@ -434,23 +435,23 @@ fn new<R: Runtime>(
|
||||
|
||||
#[command(root = "crate")]
|
||||
fn append<R: Runtime>(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
rid: ResourceId,
|
||||
kind: ItemKind,
|
||||
items: Vec<MenuItemPayloadKind>,
|
||||
) -> crate::Result<()> {
|
||||
let resources_table = window.resources_table();
|
||||
let resources_table = webview.resources_table();
|
||||
match kind {
|
||||
ItemKind::Menu => {
|
||||
let menu = resources_table.get::<Menu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| menu.append(i))?;
|
||||
item.with_item(&webview, &resources_table, |i| menu.append(i))?;
|
||||
}
|
||||
}
|
||||
ItemKind::Submenu => {
|
||||
let submenu = resources_table.get::<Submenu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| submenu.append(i))?;
|
||||
item.with_item(&webview, &resources_table, |i| submenu.append(i))?;
|
||||
}
|
||||
}
|
||||
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
|
||||
@ -461,23 +462,23 @@ fn append<R: Runtime>(
|
||||
|
||||
#[command(root = "crate")]
|
||||
fn prepend<R: Runtime>(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
rid: ResourceId,
|
||||
kind: ItemKind,
|
||||
items: Vec<MenuItemPayloadKind>,
|
||||
) -> crate::Result<()> {
|
||||
let resources_table = window.resources_table();
|
||||
let resources_table = webview.resources_table();
|
||||
match kind {
|
||||
ItemKind::Menu => {
|
||||
let menu = resources_table.get::<Menu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| menu.prepend(i))?;
|
||||
item.with_item(&webview, &resources_table, |i| menu.prepend(i))?;
|
||||
}
|
||||
}
|
||||
ItemKind::Submenu => {
|
||||
let submenu = resources_table.get::<Submenu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| submenu.prepend(i))?;
|
||||
item.with_item(&webview, &resources_table, |i| submenu.prepend(i))?;
|
||||
}
|
||||
}
|
||||
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
|
||||
@ -488,25 +489,25 @@ fn prepend<R: Runtime>(
|
||||
|
||||
#[command(root = "crate")]
|
||||
fn insert<R: Runtime>(
|
||||
window: Window<R>,
|
||||
webview: Webview<R>,
|
||||
rid: ResourceId,
|
||||
kind: ItemKind,
|
||||
items: Vec<MenuItemPayloadKind>,
|
||||
mut position: usize,
|
||||
) -> crate::Result<()> {
|
||||
let resources_table = window.resources_table();
|
||||
let resources_table = webview.resources_table();
|
||||
match kind {
|
||||
ItemKind::Menu => {
|
||||
let menu = resources_table.get::<Menu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| menu.insert(i, position))?;
|
||||
item.with_item(&webview, &resources_table, |i| menu.insert(i, position))?;
|
||||
position += 1
|
||||
}
|
||||
}
|
||||
ItemKind::Submenu => {
|
||||
let submenu = resources_table.get::<Submenu<R>>(rid)?;
|
||||
for item in items {
|
||||
item.with_item(&window, &resources_table, |i| submenu.insert(i, position))?;
|
||||
item.with_item(&webview, &resources_table, |i| submenu.insert(i, position))?;
|
||||
position += 1
|
||||
}
|
||||
}
|
||||
@ -639,7 +640,7 @@ async fn popup<R: Runtime>(
|
||||
at: Option<Position>,
|
||||
) -> crate::Result<()> {
|
||||
let window = window
|
||||
.map(|w| app.get_window(&w))
|
||||
.map(|w| app.manager().get_window(&w))
|
||||
.unwrap_or(Some(current_window));
|
||||
|
||||
if let Some(window) = window {
|
||||
@ -692,7 +693,7 @@ async fn set_as_window_menu<R: Runtime>(
|
||||
window: Option<String>,
|
||||
) -> crate::Result<Option<(ResourceId, MenuId)>> {
|
||||
let window = window
|
||||
.map(|w| app.get_window(&w))
|
||||
.map(|w| app.manager().get_window(&w))
|
||||
.unwrap_or(Some(current_window));
|
||||
|
||||
if let Some(window) = window {
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
use crate::{
|
||||
app::UriSchemeResponder,
|
||||
ipc::{Invoke, InvokeHandler},
|
||||
manager::window::UriSchemeProtocol,
|
||||
manager::webview::UriSchemeProtocol,
|
||||
utils::config::PluginConfig,
|
||||
window::PageLoadPayload,
|
||||
AppHandle, Error, RunEvent, Runtime, Window,
|
||||
webview::PageLoadPayload,
|
||||
AppHandle, Error, RunEvent, Runtime, Webview, Window,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JsonValue;
|
||||
@ -50,19 +50,23 @@ pub trait Plugin<R: Runtime>: Send {
|
||||
None
|
||||
}
|
||||
|
||||
/// Callback invoked when the window is created.
|
||||
#[allow(unused_variables)]
|
||||
fn window_created(&mut self, window: Window<R>) {}
|
||||
|
||||
/// Callback invoked when the webview is created.
|
||||
#[allow(unused_variables)]
|
||||
fn created(&mut self, window: Window<R>) {}
|
||||
fn webview_created(&mut self, webview: Webview<R>) {}
|
||||
|
||||
/// Callback invoked when webview tries to navigate to the given Url. Returning falses cancels navigation.
|
||||
#[allow(unused_variables)]
|
||||
fn on_navigation(&mut self, window: &Window<R>, url: &Url) -> bool {
|
||||
fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Callback invoked when the webview performs a navigation to a page.
|
||||
#[allow(unused_variables)]
|
||||
fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) {}
|
||||
fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {}
|
||||
|
||||
/// Callback invoked when the event loop receives a new event.
|
||||
#[allow(unused_variables)]
|
||||
@ -77,10 +81,11 @@ pub trait Plugin<R: Runtime>: Send {
|
||||
|
||||
type SetupHook<R, C> =
|
||||
dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>> + Send;
|
||||
type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
|
||||
type OnWindowReady<R> = dyn FnMut(Window<R>) + Send;
|
||||
type OnWebviewReady<R> = dyn FnMut(Webview<R>) + Send;
|
||||
type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
|
||||
type OnNavigation<R> = dyn Fn(&Window<R>, &Url) -> bool + Send;
|
||||
type OnPageLoad<R> = dyn FnMut(&Window<R>, &PageLoadPayload<'_>) + Send;
|
||||
type OnNavigation<R> = dyn Fn(&Webview<R>, &Url) -> bool + Send;
|
||||
type OnPageLoad<R> = dyn FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send;
|
||||
type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
|
||||
|
||||
/// A handle to a plugin.
|
||||
@ -208,6 +213,7 @@ pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
|
||||
js_init_script: Option<String>,
|
||||
on_navigation: Box<OnNavigation<R>>,
|
||||
on_page_load: Box<OnPageLoad<R>>,
|
||||
on_window_ready: Box<OnWindowReady<R>>,
|
||||
on_webview_ready: Box<OnWebviewReady<R>>,
|
||||
on_event: Box<OnEvent<R>>,
|
||||
on_drop: Option<Box<OnDrop<R>>>,
|
||||
@ -224,6 +230,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
invoke_handler: Box::new(|_| false),
|
||||
on_navigation: Box::new(|_, _| true),
|
||||
on_page_load: Box::new(|_, _| ()),
|
||||
on_window_ready: Box::new(|_| ()),
|
||||
on_webview_ready: Box::new(|_| ()),
|
||||
on_event: Box::new(|_, _| ()),
|
||||
on_drop: None,
|
||||
@ -342,7 +349,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
///
|
||||
/// fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// Builder::new("example")
|
||||
/// .on_navigation(|window, url| {
|
||||
/// .on_navigation(|webview, url| {
|
||||
/// // allow the production URL or localhost on dev
|
||||
/// url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
|
||||
/// })
|
||||
@ -352,7 +359,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
#[must_use]
|
||||
pub fn on_navigation<F>(mut self, on_navigation: F) -> Self
|
||||
where
|
||||
F: Fn(&Window<R>, &Url) -> bool + Send + 'static,
|
||||
F: Fn(&Webview<R>, &Url) -> bool + Send + 'static,
|
||||
{
|
||||
self.on_navigation = Box::new(on_navigation);
|
||||
self
|
||||
@ -367,8 +374,8 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
///
|
||||
/// fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// Builder::new("example")
|
||||
/// .on_page_load(|window, payload| {
|
||||
/// println!("{:?} URL {} in window {}", payload.event(), payload.url(), window.label());
|
||||
/// .on_page_load(|webview, payload| {
|
||||
/// println!("{:?} URL {} in webview {}", payload.event(), payload.url(), webview.label());
|
||||
/// })
|
||||
/// .build()
|
||||
/// }
|
||||
@ -376,12 +383,36 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
#[must_use]
|
||||
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
|
||||
where
|
||||
F: FnMut(&Window<R>, &PageLoadPayload<'_>) + Send + 'static,
|
||||
F: FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send + 'static,
|
||||
{
|
||||
self.on_page_load = Box::new(on_page_load);
|
||||
self
|
||||
}
|
||||
|
||||
/// Callback invoked when the window is created.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
|
||||
///
|
||||
/// fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// Builder::new("example")
|
||||
/// .on_window_ready(|window| {
|
||||
/// println!("created window {}", window.label());
|
||||
/// })
|
||||
/// .build()
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn on_window_ready<F>(mut self, on_window_ready: F) -> Self
|
||||
where
|
||||
F: FnMut(Window<R>) + Send + 'static,
|
||||
{
|
||||
self.on_window_ready = Box::new(on_window_ready);
|
||||
self
|
||||
}
|
||||
|
||||
/// Callback invoked when the webview is created.
|
||||
///
|
||||
/// # Examples
|
||||
@ -391,8 +422,8 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
///
|
||||
/// fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// Builder::new("example")
|
||||
/// .on_webview_ready(|window| {
|
||||
/// println!("created window {}", window.label());
|
||||
/// .on_webview_ready(|webview| {
|
||||
/// println!("created webview {}", webview.label());
|
||||
/// })
|
||||
/// .build()
|
||||
/// }
|
||||
@ -400,7 +431,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
#[must_use]
|
||||
pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
|
||||
where
|
||||
F: FnMut(Window<R>) + Send + 'static,
|
||||
F: FnMut(Webview<R>) + Send + 'static,
|
||||
{
|
||||
self.on_webview_ready = Box::new(on_webview_ready);
|
||||
self
|
||||
@ -578,6 +609,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
|
||||
js_init_script: self.js_init_script,
|
||||
on_navigation: self.on_navigation,
|
||||
on_page_load: self.on_page_load,
|
||||
on_window_ready: self.on_window_ready,
|
||||
on_webview_ready: self.on_webview_ready,
|
||||
on_event: self.on_event,
|
||||
on_drop: self.on_drop,
|
||||
@ -595,6 +627,7 @@ pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
|
||||
js_init_script: Option<String>,
|
||||
on_navigation: Box<OnNavigation<R>>,
|
||||
on_page_load: Box<OnPageLoad<R>>,
|
||||
on_window_ready: Box<OnWindowReady<R>>,
|
||||
on_webview_ready: Box<OnWebviewReady<R>>,
|
||||
on_event: Box<OnEvent<R>>,
|
||||
on_drop: Option<Box<OnDrop<R>>>,
|
||||
@ -635,7 +668,7 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
|
||||
for (uri_scheme, protocol) in &self.uri_scheme_protocols {
|
||||
app
|
||||
.manager
|
||||
.window
|
||||
.webview
|
||||
.register_uri_scheme_protocol(uri_scheme, protocol.clone())
|
||||
}
|
||||
Ok(())
|
||||
@ -645,16 +678,20 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
|
||||
self.js_init_script.clone()
|
||||
}
|
||||
|
||||
fn created(&mut self, window: Window<R>) {
|
||||
(self.on_webview_ready)(window)
|
||||
fn window_created(&mut self, window: Window<R>) {
|
||||
(self.on_window_ready)(window)
|
||||
}
|
||||
|
||||
fn on_navigation(&mut self, window: &Window<R>, url: &Url) -> bool {
|
||||
(self.on_navigation)(window, url)
|
||||
fn webview_created(&mut self, webview: Webview<R>) {
|
||||
(self.on_webview_ready)(webview)
|
||||
}
|
||||
|
||||
fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) {
|
||||
(self.on_page_load)(window, payload)
|
||||
fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
|
||||
(self.on_navigation)(webview, url)
|
||||
}
|
||||
|
||||
fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
|
||||
(self.on_page_load)(webview, payload)
|
||||
}
|
||||
|
||||
fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
|
||||
@ -740,20 +777,28 @@ impl<R: Runtime> PluginStore<R> {
|
||||
}
|
||||
|
||||
/// Runs the created hook for all plugins in the store.
|
||||
pub(crate) fn created(&mut self, window: Window<R>) {
|
||||
pub(crate) fn window_created(&mut self, window: Window<R>) {
|
||||
self.store.iter_mut().for_each(|plugin| {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::trace_span!("plugin::hooks::created", name = plugin.name()).entered();
|
||||
plugin.created(window.clone())
|
||||
plugin.window_created(window.clone())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn on_navigation(&mut self, window: &Window<R>, url: &Url) -> bool {
|
||||
/// Runs the webview created hook for all plugins in the store.
|
||||
pub(crate) fn webview_created(&mut self, webview: Webview<R>) {
|
||||
self
|
||||
.store
|
||||
.iter_mut()
|
||||
.for_each(|plugin| plugin.webview_created(webview.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
|
||||
for plugin in self.store.iter_mut() {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span =
|
||||
tracing::trace_span!("plugin::hooks::on_navigation", name = plugin.name()).entered();
|
||||
if !plugin.on_navigation(window, url) {
|
||||
if !plugin.on_navigation(webview, url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -761,12 +806,12 @@ impl<R: Runtime> PluginStore<R> {
|
||||
}
|
||||
|
||||
/// Runs the on_page_load hook for all plugins in the store.
|
||||
pub(crate) fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) {
|
||||
pub(crate) fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
|
||||
self.store.iter_mut().for_each(|plugin| {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span =
|
||||
tracing::trace_span!("plugin::hooks::on_page_load", name = plugin.name()).entered();
|
||||
plugin.on_page_load(window, payload)
|
||||
plugin.on_page_load(webview, payload)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -157,11 +157,11 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
|
||||
&self,
|
||||
init_fn: unsafe fn() -> *const std::ffi::c_void,
|
||||
) -> Result<PluginHandle<R>, PluginInvokeError> {
|
||||
if let Some(window) = self.handle.manager.windows().values().next() {
|
||||
if let Some(webview) = self.handle.manager.webviews().values().next() {
|
||||
let (tx, rx) = channel();
|
||||
let name = self.name;
|
||||
let config = self.raw_config.clone();
|
||||
window
|
||||
webview
|
||||
.with_webview(move |w| {
|
||||
unsafe {
|
||||
crate::ios::register_plugin(
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{path::SafePathBuf, scope, window::UriSchemeProtocolHandler};
|
||||
use crate::{path::SafePathBuf, scope, webview::UriSchemeProtocolHandler};
|
||||
use http::{header::*, status::StatusCode, Request, Response};
|
||||
use http_range::HttpRange;
|
||||
use std::{borrow::Cow, io::SeekFrom};
|
||||
|
||||
@ -8,7 +8,7 @@ use tauri_utils::assets::{Assets, EmbeddedAssets};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{manager::window::PROCESS_IPC_MESSAGE_FN, window::UriSchemeProtocolHandler};
|
||||
use crate::{manager::webview::PROCESS_IPC_MESSAGE_FN, webview::UriSchemeProtocolHandler};
|
||||
|
||||
pub fn get(assets: Arc<EmbeddedAssets>, aes_gcm_key: [u8; 32]) -> UriSchemeProtocolHandler {
|
||||
Box::new(move |request, responder| {
|
||||
|
||||
@ -7,8 +7,8 @@ use std::{borrow::Cow, sync::Arc};
|
||||
use http::{header::CONTENT_TYPE, Request, Response as HttpResponse, StatusCode};
|
||||
|
||||
use crate::{
|
||||
manager::{window::PROXY_DEV_SERVER, AppManager},
|
||||
window::{UriSchemeProtocolHandler, WebResourceRequestHandler},
|
||||
manager::{webview::PROXY_DEV_SERVER, AppManager},
|
||||
webview::{UriSchemeProtocolHandler, WebResourceRequestHandler},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
|
||||
@ -7,13 +7,15 @@
|
||||
|
||||
use tauri_runtime::{
|
||||
monitor::Monitor,
|
||||
webview::{WindowBuilder, WindowBuilderBase},
|
||||
webview::{DetachedWebview, PendingWebview},
|
||||
window::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent,
|
||||
CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, WindowId,
|
||||
},
|
||||
DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result,
|
||||
RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WindowEventId,
|
||||
window::{WindowBuilder, WindowBuilderBase},
|
||||
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent,
|
||||
Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch,
|
||||
WindowDispatch, WindowEventId,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -36,14 +38,17 @@ use std::{
|
||||
};
|
||||
|
||||
type ShortcutMap = HashMap<String, Box<dyn Fn() + Send + 'static>>;
|
||||
type WindowId = u32;
|
||||
|
||||
enum Message {
|
||||
Task(Box<dyn FnOnce() + Send>),
|
||||
CloseWindow(WindowId),
|
||||
}
|
||||
|
||||
struct Window;
|
||||
struct Webview;
|
||||
|
||||
struct Window {
|
||||
webviews: Vec<Webview>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeContext {
|
||||
@ -52,6 +57,7 @@ pub struct RuntimeContext {
|
||||
shortcuts: Arc<Mutex<ShortcutMap>>,
|
||||
run_tx: SyncSender<Message>,
|
||||
next_window_id: Arc<AtomicU32>,
|
||||
next_webview_id: Arc<AtomicU32>,
|
||||
next_window_event_id: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
@ -82,7 +88,11 @@ impl RuntimeContext {
|
||||
}
|
||||
|
||||
fn next_window_id(&self) -> WindowId {
|
||||
self.next_window_id.fetch_add(1, Ordering::Relaxed)
|
||||
self.next_window_id.fetch_add(1, Ordering::Relaxed).into()
|
||||
}
|
||||
|
||||
fn next_webview_id(&self) -> u32 {
|
||||
self.next_webview_id.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn next_window_event_id(&self) -> WindowEventId {
|
||||
@ -112,13 +122,57 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
_before_webview_creation: Option<F>,
|
||||
_after_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>> {
|
||||
let id = self.context.next_window_id();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
|
||||
let (webview_id, webviews) = if let Some(w) = &pending.webview {
|
||||
(Some(self.context.next_webview_id()), vec![Webview])
|
||||
} else {
|
||||
(None, Vec::new())
|
||||
};
|
||||
|
||||
self
|
||||
.context
|
||||
.windows
|
||||
.borrow_mut()
|
||||
.insert(id, Window { webviews });
|
||||
|
||||
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(),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(DetachedWindow {
|
||||
id,
|
||||
label: pending.label,
|
||||
dispatcher: MockDispatcher {
|
||||
dispatcher: MockWindowDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
},
|
||||
webview,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_webview(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>> {
|
||||
let id = self.context.next_webview_id();
|
||||
let webview = Webview;
|
||||
if let Some(w) = self.context.windows.borrow_mut().get_mut(&window_id) {
|
||||
w.webviews.push(webview);
|
||||
}
|
||||
|
||||
Ok(DetachedWebview {
|
||||
label: pending.label,
|
||||
dispatcher: MockWebviewDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
last_evaluated_script: Default::default(),
|
||||
@ -187,19 +241,25 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockDispatcher {
|
||||
id: WindowId,
|
||||
pub struct MockWebviewDispatcher {
|
||||
id: u32,
|
||||
context: RuntimeContext,
|
||||
url: Arc<Mutex<String>>,
|
||||
last_evaluated_script: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
impl MockDispatcher {
|
||||
impl MockWebviewDispatcher {
|
||||
pub fn last_evaluated_script(&self) -> Option<String> {
|
||||
self.last_evaluated_script.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockWindowDispatcher {
|
||||
id: WindowId,
|
||||
context: RuntimeContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockWindowBuilder {}
|
||||
|
||||
@ -326,6 +386,11 @@ impl WindowBuilder for MockWindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn drag_and_drop(mut self, enabled: bool) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn title_bar_style(self, style: TitleBarStyle) -> Self {
|
||||
self
|
||||
@ -350,19 +415,13 @@ impl WindowBuilder for MockWindowBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
impl<T: UserEvent> WebviewDispatch<T> for MockWebviewDispatcher {
|
||||
type Runtime = MockRuntime;
|
||||
|
||||
type WindowBuilder = MockWindowBuilder;
|
||||
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
|
||||
self.context.send_message(Message::Task(Box::new(f)))
|
||||
}
|
||||
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
|
||||
self.context.next_window_event_id()
|
||||
}
|
||||
|
||||
fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -378,6 +437,15 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
|
||||
self
|
||||
.last_evaluated_script
|
||||
.lock()
|
||||
.unwrap()
|
||||
.replace(script.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn url(&self) -> Result<url::Url> {
|
||||
self
|
||||
.url
|
||||
@ -387,6 +455,56 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
.map_err(|_| Error::FailedToReceiveMessage)
|
||||
}
|
||||
|
||||
fn position(&self) -> Result<PhysicalPosition<i32>> {
|
||||
Ok(PhysicalPosition { x: 0, y: 0 })
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<PhysicalSize<u32>> {
|
||||
Ok(PhysicalSize {
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn navigate(&self, url: Url) -> Result<()> {
|
||||
*self.url.lock().unwrap() = url.to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_size(&self, _size: Size) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_position(&self, _position: Position) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_focus(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
|
||||
type Runtime = MockRuntime;
|
||||
|
||||
type WindowBuilder = MockWindowBuilder;
|
||||
|
||||
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
|
||||
self.context.send_message(Message::Task(Box::new(f)))
|
||||
}
|
||||
|
||||
fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
|
||||
self.context.next_window_event_id()
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> Result<f64> {
|
||||
Ok(1.0)
|
||||
}
|
||||
@ -516,10 +634,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -527,13 +641,56 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&mut self,
|
||||
pending: PendingWindow<T, Self::Runtime>,
|
||||
_before_webview_creation: Option<F>,
|
||||
_after_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self::Runtime>> {
|
||||
let id = self.context.next_window_id();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
|
||||
let (webview_id, webviews) = if let Some(w) = &pending.webview {
|
||||
(Some(self.context.next_webview_id()), vec![Webview])
|
||||
} else {
|
||||
(None, Vec::new())
|
||||
};
|
||||
|
||||
self
|
||||
.context
|
||||
.windows
|
||||
.borrow_mut()
|
||||
.insert(id, Window { webviews });
|
||||
|
||||
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(),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(DetachedWindow {
|
||||
id,
|
||||
label: pending.label,
|
||||
dispatcher: MockDispatcher {
|
||||
dispatcher: MockWindowDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
},
|
||||
webview,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_webview(
|
||||
&mut self,
|
||||
pending: PendingWebview<T, Self::Runtime>,
|
||||
) -> Result<DetachedWebview<T, Self::Runtime>> {
|
||||
let id = self.context.next_webview_id();
|
||||
let webview = Webview;
|
||||
if let Some(w) = self.context.windows.borrow_mut().get_mut(&self.id) {
|
||||
w.webviews.push(webview);
|
||||
}
|
||||
|
||||
Ok(DetachedWebview {
|
||||
label: pending.label,
|
||||
dispatcher: MockWebviewDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
last_evaluated_script: Default::default(),
|
||||
@ -562,11 +719,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn navigate(&self, url: Url) -> Result<()> {
|
||||
*self.url.lock().unwrap() = url.to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn maximize(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -680,15 +832,6 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
|
||||
self
|
||||
.last_evaluated_script
|
||||
.lock()
|
||||
.unwrap()
|
||||
.replace(script.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -720,6 +863,7 @@ impl MockRuntime {
|
||||
shortcuts: Default::default(),
|
||||
run_tx: tx,
|
||||
next_window_id: Default::default(),
|
||||
next_webview_id: Default::default(),
|
||||
next_window_event_id: Default::default(),
|
||||
};
|
||||
Self {
|
||||
@ -731,7 +875,8 @@ impl MockRuntime {
|
||||
}
|
||||
|
||||
impl<T: UserEvent> Runtime<T> for MockRuntime {
|
||||
type Dispatcher = MockDispatcher;
|
||||
type WindowDispatcher = MockWindowDispatcher;
|
||||
type WebviewDispatcher = MockWebviewDispatcher;
|
||||
type Handle = MockRuntimeHandle;
|
||||
type EventLoopProxy = EventProxy;
|
||||
|
||||
@ -757,13 +902,57 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
|
||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||
&self,
|
||||
pending: PendingWindow<T, Self>,
|
||||
_before_webview_creation: Option<F>,
|
||||
_after_window_creation: Option<F>,
|
||||
) -> Result<DetachedWindow<T, Self>> {
|
||||
let id = self.context.next_window_id();
|
||||
self.context.windows.borrow_mut().insert(id, Window);
|
||||
|
||||
let (webview_id, webviews) = if let Some(w) = &pending.webview {
|
||||
(Some(self.context.next_webview_id()), vec![Webview])
|
||||
} else {
|
||||
(None, Vec::new())
|
||||
};
|
||||
|
||||
self
|
||||
.context
|
||||
.windows
|
||||
.borrow_mut()
|
||||
.insert(id, Window { webviews });
|
||||
|
||||
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(),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(DetachedWindow {
|
||||
id,
|
||||
label: pending.label,
|
||||
dispatcher: MockDispatcher {
|
||||
dispatcher: MockWindowDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
},
|
||||
webview,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_webview(
|
||||
&self,
|
||||
window_id: WindowId,
|
||||
pending: PendingWebview<T, Self>,
|
||||
) -> Result<DetachedWebview<T, Self>> {
|
||||
let id = self.context.next_webview_id();
|
||||
let webview = Webview;
|
||||
if let Some(w) = self.context.windows.borrow_mut().get_mut(&window_id) {
|
||||
w.webviews.push(webview);
|
||||
}
|
||||
|
||||
Ok(DetachedWebview {
|
||||
label: pending.label,
|
||||
dispatcher: MockWebviewDispatcher {
|
||||
id,
|
||||
context: self.context.clone(),
|
||||
last_evaluated_script: Default::default(),
|
||||
|
||||
@ -30,14 +30,12 @@
|
||||
//! // Use `tauri::Builder::default()` to use the default runtime rather than the `MockRuntime`;
|
||||
//! // let app = create_app(tauri::Builder::default());
|
||||
//! let app = create_app(mock_builder());
|
||||
//! let window = tauri::WindowBuilder::new(&app, "main", Default::default())
|
||||
//! .build()
|
||||
//! .unwrap();
|
||||
//! let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default()).build().unwrap();
|
||||
//!
|
||||
//! // run the `ping` command and assert it returns `pong`
|
||||
//! let res = tauri::test::get_ipc_response(
|
||||
//! &window,
|
||||
//! tauri::window::InvokeRequest {
|
||||
//! &webview,
|
||||
//! tauri::webview::InvokeRequest {
|
||||
//! cmd: "ping".into(),
|
||||
//! callback: tauri::ipc::CallbackFn(0),
|
||||
//! error: tauri::ipc::CallbackFn(1),
|
||||
@ -58,8 +56,8 @@ use std::{borrow::Cow, collections::HashMap, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
ipc::{InvokeBody, InvokeError, InvokeResponse},
|
||||
window::InvokeRequest,
|
||||
App, Builder, Context, Pattern, Window,
|
||||
webview::InvokeRequest,
|
||||
App, Builder, Context, Pattern, Webview,
|
||||
};
|
||||
use tauri_utils::{
|
||||
acl::resolved::Resolved,
|
||||
@ -181,14 +179,12 @@ pub fn mock_app() -> App<MockRuntime> {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = create_app(mock_builder());
|
||||
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default()).build().unwrap();
|
||||
///
|
||||
/// // run the `ping` command and assert it returns `pong`
|
||||
/// tauri::test::assert_ipc_response(
|
||||
/// &window,
|
||||
/// tauri::window::InvokeRequest {
|
||||
/// &webview,
|
||||
/// tauri::webview::InvokeRequest {
|
||||
/// cmd: "ping".into(),
|
||||
/// callback: tauri::ipc::CallbackFn(0),
|
||||
/// error: tauri::ipc::CallbackFn(1),
|
||||
@ -199,13 +195,16 @@ pub fn mock_app() -> App<MockRuntime> {
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn assert_ipc_response<T: Serialize + Debug + Send + Sync + 'static>(
|
||||
window: &Window<MockRuntime>,
|
||||
pub fn assert_ipc_response<
|
||||
T: Serialize + Debug + Send + Sync + 'static,
|
||||
W: AsRef<Webview<MockRuntime>>,
|
||||
>(
|
||||
webview: &W,
|
||||
request: InvokeRequest,
|
||||
expected: Result<T, T>,
|
||||
) {
|
||||
let response =
|
||||
get_ipc_response(window, request).map(|b| b.deserialize::<serde_json::Value>().unwrap());
|
||||
get_ipc_response(webview, request).map(|b| b.deserialize::<serde_json::Value>().unwrap());
|
||||
assert_eq!(
|
||||
response,
|
||||
expected
|
||||
@ -236,14 +235,12 @@ pub fn assert_ipc_response<T: Serialize + Debug + Send + Sync + 'static>(
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = create_app(mock_builder());
|
||||
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default()).build().unwrap();
|
||||
///
|
||||
/// // run the `ping` command and assert it returns `pong`
|
||||
/// let res = tauri::test::get_ipc_response(
|
||||
/// &window,
|
||||
/// tauri::window::InvokeRequest {
|
||||
/// &webview,
|
||||
/// tauri::webview::InvokeRequest {
|
||||
/// cmd: "ping".into(),
|
||||
/// callback: tauri::ipc::CallbackFn(0),
|
||||
/// error: tauri::ipc::CallbackFn(1),
|
||||
@ -255,12 +252,12 @@ pub fn assert_ipc_response<T: Serialize + Debug + Send + Sync + 'static>(
|
||||
/// assert_eq!(res.unwrap().deserialize::<String>().unwrap(), String::from("pong"));
|
||||
/// }
|
||||
///```
|
||||
pub fn get_ipc_response(
|
||||
window: &Window<MockRuntime>,
|
||||
pub fn get_ipc_response<W: AsRef<Webview<MockRuntime>>>(
|
||||
webview: &W,
|
||||
request: InvokeRequest,
|
||||
) -> Result<InvokeBody, serde_json::Value> {
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
window.clone().on_message(
|
||||
webview.as_ref().clone().on_message(
|
||||
request,
|
||||
Box::new(move |_window, _cmd, response, _callback, _error| {
|
||||
tx.send(response).unwrap();
|
||||
@ -276,7 +273,6 @@ pub fn get_ipc_response(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::WindowBuilder;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::mock_app;
|
||||
@ -285,7 +281,7 @@ mod tests {
|
||||
fn run_app() {
|
||||
let app = mock_app();
|
||||
|
||||
let w = WindowBuilder::new(&app, "main", Default::default())
|
||||
let w = crate::WebviewWindowBuilder::new(&app, "main", Default::default())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
1566
core/tauri/src/webview/mod.rs
Normal file
1566
core/tauri/src/webview/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
244
core/tauri/src/webview/plugin.rs
Normal file
244
core/tauri/src/webview/plugin.rs
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! The tauri plugin to create and manipulate windows from JS.
|
||||
|
||||
use crate::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
#[cfg(desktop)]
|
||||
mod desktop_commands {
|
||||
|
||||
use serde::Deserialize;
|
||||
use tauri_runtime::window::dpi::{Position, Size};
|
||||
use tauri_utils::config::{WebviewUrl, WindowConfig};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
command, sealed::ManagerBase, utils::config::WindowEffectsConfig, AppHandle, Webview,
|
||||
WebviewWindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WebviewConfig {
|
||||
#[serde(default)]
|
||||
url: WebviewUrl,
|
||||
user_agent: Option<String>,
|
||||
file_drop_enabled: Option<bool>,
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
#[serde(default)]
|
||||
transparent: bool,
|
||||
#[serde(default)]
|
||||
accept_first_mouse: bool,
|
||||
window_effects: Option<WindowEffectsConfig>,
|
||||
#[serde(default)]
|
||||
incognito: bool,
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub async fn create_webview_window<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
options: WindowConfig,
|
||||
) -> crate::Result<()> {
|
||||
WebviewWindowBuilder::from_config(&app, options).build()?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "unstable"))]
|
||||
#[command(root = "crate")]
|
||||
pub async fn create_webview() -> crate::Result<()> {
|
||||
Err(crate::Error::UnstableFeatureNotSupported)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[command(root = "crate")]
|
||||
pub async fn create_webview<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
label: String,
|
||||
window_label: String,
|
||||
options: WebviewConfig,
|
||||
) -> crate::Result<()> {
|
||||
let window = app
|
||||
.manager()
|
||||
.get_window(&window_label)
|
||||
.ok_or(crate::Error::WindowNotFound)?;
|
||||
let mut builder = crate::webview::WebviewBuilder::new(label, options.url);
|
||||
|
||||
builder.webview_attributes.user_agent = options.user_agent;
|
||||
builder.webview_attributes.file_drop_handler_enabled =
|
||||
options.file_drop_enabled.unwrap_or(true);
|
||||
builder.webview_attributes.transparent = options.transparent;
|
||||
builder.webview_attributes.accept_first_mouse = options.accept_first_mouse;
|
||||
builder.webview_attributes.window_effects = options.window_effects;
|
||||
builder.webview_attributes.incognito = options.incognito;
|
||||
|
||||
window.add_child(
|
||||
builder,
|
||||
tauri_runtime::window::dpi::LogicalPosition::new(options.x, options.y),
|
||||
tauri_runtime::window::dpi::LogicalSize::new(options.width, options.height),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_webview<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
label: Option<String>,
|
||||
) -> crate::Result<Webview<R>> {
|
||||
match label {
|
||||
Some(l) if !l.is_empty() => webview
|
||||
.manager()
|
||||
.get_webview(&l)
|
||||
.ok_or(crate::Error::WebviewNotFound),
|
||||
_ => Ok(webview),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! getter {
|
||||
($cmd: ident, $ret: ty) => {
|
||||
getter!($cmd, $cmd, $ret)
|
||||
};
|
||||
($fn: ident, $cmd: ident, $ret: ty) => {
|
||||
#[command(root = "crate")]
|
||||
pub async fn $fn<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
label: Option<String>,
|
||||
) -> crate::Result<$ret> {
|
||||
get_webview(webview, label)?.$cmd().map_err(Into::into)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! setter {
|
||||
($cmd: ident) => {
|
||||
setter!($cmd, $cmd);
|
||||
};
|
||||
($fn: ident, $cmd: ident) => {
|
||||
#[command(root = "crate")]
|
||||
pub async fn $fn<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
label: Option<String>,
|
||||
) -> crate::Result<()> {
|
||||
get_webview(webview, label)?.$cmd().map_err(Into::into)
|
||||
}
|
||||
};
|
||||
($fn: ident, $cmd: ident, $input: ty) => {
|
||||
#[command(root = "crate")]
|
||||
pub async fn $fn<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
label: Option<String>,
|
||||
value: $input,
|
||||
) -> crate::Result<()> {
|
||||
get_webview(webview, label)?.$cmd(value).map_err(Into::into)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO
|
||||
getter!(
|
||||
webview_position,
|
||||
position,
|
||||
tauri_runtime::window::dpi::PhysicalPosition<i32>
|
||||
);
|
||||
getter!(
|
||||
webview_size,
|
||||
size,
|
||||
tauri_runtime::window::dpi::PhysicalSize<u32>
|
||||
);
|
||||
//getter!(is_focused, bool);
|
||||
|
||||
setter!(print);
|
||||
setter!(webview_close, close);
|
||||
setter!(set_webview_size, set_size, Size);
|
||||
setter!(set_webview_position, set_position, Position);
|
||||
setter!(set_webview_focus, set_focus);
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
#[command(root = "crate")]
|
||||
pub async fn internal_toggle_devtools<R: Runtime>(
|
||||
webview: crate::Webview<R>,
|
||||
label: Option<String>,
|
||||
) -> crate::Result<()> {
|
||||
let webview = get_webview(webview, label)?;
|
||||
if webview.is_devtools_open() {
|
||||
webview.close_devtools();
|
||||
} else {
|
||||
webview.open_devtools();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[allow(unused_mut)]
|
||||
let mut init_script = String::new();
|
||||
// window.print works on Linux/Windows; need to use the API on macOS
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
{
|
||||
init_script.push_str(include_str!("./scripts/print.js"));
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
{
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("./scripts/toggle-devtools.js")]
|
||||
struct Devtools<'a> {
|
||||
os_name: &'a str,
|
||||
}
|
||||
|
||||
init_script.push_str(
|
||||
&Devtools {
|
||||
os_name: std::env::consts::OS,
|
||||
}
|
||||
.render_default(&Default::default())
|
||||
.unwrap()
|
||||
.into_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut builder = Builder::new("webview");
|
||||
if !init_script.is_empty() {
|
||||
builder = builder.js_init_script(init_script);
|
||||
}
|
||||
|
||||
builder
|
||||
.invoke_handler(|invoke| {
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let handler: Box<dyn Fn(crate::ipc::Invoke<R>) -> bool> =
|
||||
Box::new(crate::generate_handler![
|
||||
desktop_commands::create_webview,
|
||||
desktop_commands::create_webview_window,
|
||||
// getters
|
||||
desktop_commands::webview_position,
|
||||
desktop_commands::webview_size,
|
||||
// setters
|
||||
desktop_commands::webview_close,
|
||||
desktop_commands::set_webview_size,
|
||||
desktop_commands::set_webview_position,
|
||||
desktop_commands::set_webview_focus,
|
||||
desktop_commands::print,
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
desktop_commands::internal_toggle_devtools,
|
||||
]);
|
||||
handler(invoke)
|
||||
}
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
invoke
|
||||
.resolver
|
||||
.reject("Webview API not available on mobile");
|
||||
true
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
@ -3,5 +3,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
window.print = function () {
|
||||
return window.__TAURI_INTERNALS__.invoke('plugin:window|print')
|
||||
return window.__TAURI_INTERNALS__.invoke('plugin:webview|print')
|
||||
}
|
||||
1737
core/tauri/src/webview/webview_window.rs
Normal file
1737
core/tauri/src/webview/webview_window.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -18,9 +18,11 @@ mod desktop_commands {
|
||||
use super::*;
|
||||
use crate::{
|
||||
command,
|
||||
sealed::ManagerBase,
|
||||
utils::config::{WindowConfig, WindowEffectsConfig},
|
||||
AppHandle, CursorIcon, Icon, Manager, Monitor, PhysicalPosition, PhysicalSize, Position, Size,
|
||||
Theme, UserAttentionType, Window, WindowBuilder,
|
||||
window::WindowBuilder,
|
||||
AppHandle, CursorIcon, Icon, Monitor, PhysicalPosition, PhysicalSize, Position, Size, Theme,
|
||||
UserAttentionType, Window,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -65,7 +67,10 @@ mod desktop_commands {
|
||||
|
||||
fn get_window<R: Runtime>(window: Window<R>, label: Option<String>) -> crate::Result<Window<R>> {
|
||||
match label {
|
||||
Some(l) if !l.is_empty() => window.get_window(&l).ok_or(crate::Error::WindowNotFound),
|
||||
Some(l) if !l.is_empty() => window
|
||||
.manager()
|
||||
.get_window(&l)
|
||||
.ok_or(crate::Error::WindowNotFound),
|
||||
_ => Ok(window),
|
||||
}
|
||||
}
|
||||
@ -158,7 +163,6 @@ mod desktop_commands {
|
||||
setter!(start_dragging);
|
||||
setter!(start_resize_dragging, ResizeDirection);
|
||||
setter!(set_progress_bar, ProgressBarState);
|
||||
setter!(print);
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub async fn set_icon<R: Runtime>(
|
||||
@ -199,21 +203,6 @@ mod desktop_commands {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
#[command(root = "crate")]
|
||||
pub async fn internal_toggle_devtools<R: Runtime>(
|
||||
window: Window<R>,
|
||||
label: Option<String>,
|
||||
) -> crate::Result<()> {
|
||||
let window = get_window(window, label)?;
|
||||
if window.is_devtools_open() {
|
||||
window.close_devtools();
|
||||
} else {
|
||||
window.open_devtools();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HitTestResult {
|
||||
Client,
|
||||
@ -300,13 +289,21 @@ mod desktop_commands {
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub async fn on_mousemove<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
|
||||
pub async fn internal_on_mousemove<R: Runtime>(
|
||||
window: Window<R>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
) -> crate::Result<()> {
|
||||
hit_test(window.inner_size()?, x, y, window.scale_factor()?).change_cursor(&window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
pub async fn on_mousedown<R: Runtime>(window: Window<R>, x: i32, y: i32) -> crate::Result<()> {
|
||||
pub async fn internal_on_mousedown<R: Runtime>(
|
||||
window: Window<R>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
) -> crate::Result<()> {
|
||||
let res = hit_test(window.inner_size()?, x, y, window.scale_factor()?);
|
||||
match res {
|
||||
HitTestResult::Client | HitTestResult::NoWhere => {}
|
||||
@ -321,11 +318,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
|
||||
let mut init_script = String::new();
|
||||
// window.print works on Linux/Windows; need to use the API on macOS
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
{
|
||||
init_script.push_str(include_str!("./scripts/print.js"));
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("./scripts/drag.js")]
|
||||
@ -357,24 +349,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.into_string(),
|
||||
);
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
{
|
||||
#[derive(Template)]
|
||||
#[default_template("./scripts/toggle-devtools.js")]
|
||||
struct Devtools<'a> {
|
||||
os_name: &'a str,
|
||||
}
|
||||
|
||||
init_script.push_str(
|
||||
&Devtools {
|
||||
os_name: std::env::consts::OS,
|
||||
}
|
||||
.render_default(&Default::default())
|
||||
.unwrap()
|
||||
.into_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Builder::new("window")
|
||||
.js_init_script(init_script)
|
||||
.invoke_handler(|invoke| {
|
||||
@ -440,14 +414,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
desktop_commands::start_dragging,
|
||||
desktop_commands::start_resize_dragging,
|
||||
desktop_commands::set_progress_bar,
|
||||
desktop_commands::print,
|
||||
desktop_commands::set_icon,
|
||||
desktop_commands::toggle_maximize,
|
||||
desktop_commands::internal_toggle_maximize,
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
desktop_commands::internal_toggle_devtools,
|
||||
desktop_commands::on_mousemove,
|
||||
desktop_commands::on_mousedown,
|
||||
desktop_commands::internal_on_mousemove,
|
||||
desktop_commands::internal_on_mousedown,
|
||||
]);
|
||||
handler(invoke)
|
||||
}
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
const osName = __TEMPLATE_os_name__
|
||||
if (osName !== 'macos') {
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousemove', {
|
||||
window.__TAURI_INTERNALS__.invoke('plugin:window|internal_on_mousemove', {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
})
|
||||
})
|
||||
document.addEventListener('mousedown', (e) => {
|
||||
window.__TAURI_INTERNALS__.invoke('plugin:window|on_mousedown', {
|
||||
window.__TAURI_INTERNALS__.invoke('plugin:window|internal_on_mousedown', {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
})
|
||||
|
||||
84
examples/api/dist/assets/index.js
vendored
84
examples/api/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
2
examples/api/src-tauri/Cargo.lock
generated
2
examples/api/src-tauri/Cargo.lock
generated
@ -3761,7 +3761,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"glob",
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
"tray:default",
|
||||
"app:allow-app-hide",
|
||||
"app:allow-app-show",
|
||||
"window:allow-create",
|
||||
"window:allow-center",
|
||||
"window:allow-request-user-attention",
|
||||
"window:allow-set-resizable",
|
||||
@ -51,9 +50,10 @@
|
||||
"window:allow-set-ignore-cursor-events",
|
||||
"window:allow-start-dragging",
|
||||
"window:allow-set-progress-bar",
|
||||
"window:allow-print",
|
||||
"window:allow-set-icon",
|
||||
"window:allow-toggle-maximize",
|
||||
"webview:allow-create-webview-window",
|
||||
"webview:allow-print",
|
||||
"menu:allow-new",
|
||||
"menu:allow-append",
|
||||
"menu:allow-prepend",
|
||||
|
||||
@ -9,8 +9,8 @@ mod tray;
|
||||
use serde::Serialize;
|
||||
use tauri::{
|
||||
ipc::Channel,
|
||||
window::{PageLoadEvent, WindowBuilder},
|
||||
App, AppHandle, Manager, RunEvent, Runtime, WindowUrl,
|
||||
webview::{PageLoadEvent, WebviewWindowBuilder},
|
||||
App, AppHandle, Manager, RunEvent, Runtime, WebviewUrl,
|
||||
};
|
||||
use tauri_plugin_sample::{PingRequest, SampleExt};
|
||||
|
||||
@ -60,7 +60,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.build()?,
|
||||
));
|
||||
|
||||
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default());
|
||||
let mut window_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
window_builder = window_builder
|
||||
@ -71,10 +71,10 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.menu(tauri::menu::Menu::default(app.handle())?);
|
||||
}
|
||||
|
||||
let window = window_builder.build().unwrap();
|
||||
let webview = window_builder.build()?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
window.open_devtools();
|
||||
webview.open_devtools();
|
||||
|
||||
let value = Some("test".to_string());
|
||||
let response = app.sample().ping(PingRequest {
|
||||
@ -118,16 +118,16 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_page_load(|window, payload| {
|
||||
.on_page_load(|webview, payload| {
|
||||
if payload.event() == PageLoadEvent::Finished {
|
||||
let window_ = window.clone();
|
||||
window.listen("js-event", move |event| {
|
||||
let webview_ = webview.clone();
|
||||
webview.listen("js-event", move |event| {
|
||||
println!("got js-event with message '{:?}'", event.payload());
|
||||
let reply = Reply {
|
||||
data: "something else".to_string(),
|
||||
};
|
||||
|
||||
window_
|
||||
webview_
|
||||
.emit("rust-event", Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
@ -167,7 +167,7 @@ mod tests {
|
||||
#[test]
|
||||
fn run_app() {
|
||||
super::run_app(tauri::test::mock_builder(), |app| {
|
||||
let window = app.get_window("main").unwrap();
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
window.close().unwrap();
|
||||
|
||||
@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{ClickType, TrayIconBuilder},
|
||||
Manager, Runtime, WindowBuilder, WindowUrl,
|
||||
Manager, Runtime, WebviewUrl,
|
||||
};
|
||||
|
||||
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
@ -53,7 +53,7 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
app.remove_tray_by_id("tray-1");
|
||||
}
|
||||
"toggle" => {
|
||||
if let Some(window) = app.get_window("main") {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let new_title = if window.is_visible().unwrap_or_default() {
|
||||
let _ = window.hide();
|
||||
"Show"
|
||||
@ -66,9 +66,11 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
}
|
||||
}
|
||||
"new-window" => {
|
||||
let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into()))
|
||||
.title("Tauri")
|
||||
.build();
|
||||
let _webview =
|
||||
tauri::WebviewWindowBuilder::new(app, "new", WebviewUrl::App("index.html".into()))
|
||||
.title("Tauri")
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
"set-title" => {
|
||||
@ -104,7 +106,7 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if event.click_type == ClickType::Left {
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_window("main") {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
<script>
|
||||
import {
|
||||
getCurrent,
|
||||
LogicalSize,
|
||||
UserAttentionType,
|
||||
PhysicalSize,
|
||||
PhysicalPosition,
|
||||
Effect,
|
||||
EffectState,
|
||||
ProgressBarStatus,
|
||||
Window
|
||||
ProgressBarStatus
|
||||
} from '@tauri-apps/api/window'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webview'
|
||||
|
||||
const appWindow = getCurrent()
|
||||
const webview = WebviewWindow.getCurrent()
|
||||
|
||||
let selectedWindow = appWindow.label
|
||||
const windowMap = {
|
||||
[appWindow.label]: appWindow
|
||||
let selectedWebview = webview.label
|
||||
const webviewMap = {
|
||||
[webview.label]: webview
|
||||
}
|
||||
|
||||
const cursorIconOptions = [
|
||||
@ -86,9 +85,8 @@
|
||||
export let onMessage
|
||||
const mainEl = document.querySelector('main')
|
||||
|
||||
let newWindowLabel
|
||||
let newWebviewLabel
|
||||
|
||||
let urlValue = 'https://tauri.app'
|
||||
let resizable = true
|
||||
let maximizable = true
|
||||
let minimizable = true
|
||||
@ -134,50 +132,53 @@
|
||||
let windowIconPath
|
||||
|
||||
function setTitle_() {
|
||||
windowMap[selectedWindow].setTitle(windowTitle)
|
||||
webviewMap[selectedWebview].setTitle(windowTitle)
|
||||
}
|
||||
|
||||
function hide_() {
|
||||
windowMap[selectedWindow].hide()
|
||||
setTimeout(windowMap[selectedWindow].show, 2000)
|
||||
webviewMap[selectedWebview].hide()
|
||||
setTimeout(webviewMap[selectedWebview].show, 2000)
|
||||
}
|
||||
|
||||
function minimize_() {
|
||||
windowMap[selectedWindow].minimize()
|
||||
setTimeout(windowMap[selectedWindow].unminimize, 2000)
|
||||
webviewMap[selectedWebview].minimize()
|
||||
setTimeout(webviewMap[selectedWebview].unminimize, 2000)
|
||||
}
|
||||
|
||||
function changeIcon() {
|
||||
windowMap[selectedWindow].setIcon(path)
|
||||
webviewMap[selectedWebview].setIcon(path)
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
if (!newWindowLabel) return
|
||||
function createWebviewWindow() {
|
||||
if (!newWebviewLabel) return
|
||||
|
||||
const label = `main-${newWindowLabel}`
|
||||
const webview = new Window(label)
|
||||
windowMap[label] = webview
|
||||
webview.once('tauri://error', function () {
|
||||
onMessage('Error creating new webview')
|
||||
const label = `main-${newWebviewLabel}`
|
||||
const webview = new WebviewWindow(label)
|
||||
webviewMap[label] = webview
|
||||
webview.once('tauri://error', function (e) {
|
||||
onMessage('Error creating new webview ' + JSON.stringify(e))
|
||||
})
|
||||
webview.once('tauri://created', function () {
|
||||
onMessage('webview created')
|
||||
})
|
||||
}
|
||||
|
||||
function loadWindowSize() {
|
||||
windowMap[selectedWindow].innerSize().then((response) => {
|
||||
webviewMap[selectedWebview].innerSize().then((response) => {
|
||||
innerSize = response
|
||||
width = innerSize.width
|
||||
height = innerSize.height
|
||||
})
|
||||
windowMap[selectedWindow].outerSize().then((response) => {
|
||||
webviewMap[selectedWebview].outerSize().then((response) => {
|
||||
outerSize = response
|
||||
})
|
||||
}
|
||||
|
||||
function loadWindowPosition() {
|
||||
windowMap[selectedWindow].innerPosition().then((response) => {
|
||||
webviewMap[selectedWebview].innerPosition().then((response) => {
|
||||
innerPosition = response
|
||||
})
|
||||
windowMap[selectedWindow].outerPosition().then((response) => {
|
||||
webviewMap[selectedWebview].outerPosition().then((response) => {
|
||||
outerPosition = response
|
||||
x = outerPosition.x
|
||||
y = outerPosition.y
|
||||
@ -197,12 +198,12 @@
|
||||
}
|
||||
|
||||
async function requestUserAttention_() {
|
||||
await windowMap[selectedWindow].minimize()
|
||||
await windowMap[selectedWindow].requestUserAttention(
|
||||
await webviewMap[selectedWebview].minimize()
|
||||
await webviewMap[selectedWebview].requestUserAttention(
|
||||
UserAttentionType.Critical
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
await windowMap[selectedWindow].requestUserAttention(null)
|
||||
await webviewMap[selectedWebview].requestUserAttention(null)
|
||||
}
|
||||
|
||||
async function addEffect() {
|
||||
@ -226,66 +227,66 @@
|
||||
|
||||
mainEl.classList.remove('bg-primary')
|
||||
mainEl.classList.remove('dark:bg-darkPrimary')
|
||||
await windowMap[selectedWindow].clearEffects()
|
||||
await windowMap[selectedWindow].setEffects(payload)
|
||||
await webviewMap[selectedWebview].clearEffects()
|
||||
await webviewMap[selectedWebview].setEffects(payload)
|
||||
}
|
||||
|
||||
async function clearEffects() {
|
||||
effects = []
|
||||
await windowMap[selectedWindow].clearEffects()
|
||||
await webviewMap[selectedWebview].clearEffects()
|
||||
mainEl.classList.add('bg-primary')
|
||||
mainEl.classList.add('dark:bg-darkPrimary')
|
||||
}
|
||||
|
||||
$: {
|
||||
windowMap[selectedWindow]
|
||||
webviewMap[selectedWebview]
|
||||
loadWindowPosition()
|
||||
loadWindowSize()
|
||||
}
|
||||
$: windowMap[selectedWindow]?.setResizable(resizable)
|
||||
$: windowMap[selectedWindow]?.setMaximizable(maximizable)
|
||||
$: windowMap[selectedWindow]?.setMinimizable(minimizable)
|
||||
$: windowMap[selectedWindow]?.setClosable(closable)
|
||||
$: webviewMap[selectedWebview]?.setResizable(resizable)
|
||||
$: webviewMap[selectedWebview]?.setMaximizable(maximizable)
|
||||
$: webviewMap[selectedWebview]?.setMinimizable(minimizable)
|
||||
$: webviewMap[selectedWebview]?.setClosable(closable)
|
||||
$: maximized
|
||||
? windowMap[selectedWindow]?.maximize()
|
||||
: windowMap[selectedWindow]?.unmaximize()
|
||||
$: windowMap[selectedWindow]?.setDecorations(decorations)
|
||||
$: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop)
|
||||
$: windowMap[selectedWindow]?.setAlwaysOnBottom(alwaysOnBottom)
|
||||
$: windowMap[selectedWindow]?.setContentProtected(contentProtected)
|
||||
$: windowMap[selectedWindow]?.setFullscreen(fullscreen)
|
||||
? webviewMap[selectedWebview]?.maximize()
|
||||
: webviewMap[selectedWebview]?.unmaximize()
|
||||
$: webviewMap[selectedWebview]?.setDecorations(decorations)
|
||||
$: webviewMap[selectedWebview]?.setAlwaysOnTop(alwaysOnTop)
|
||||
$: webviewMap[selectedWebview]?.setAlwaysOnBottom(alwaysOnBottom)
|
||||
$: webviewMap[selectedWebview]?.setContentProtected(contentProtected)
|
||||
$: webviewMap[selectedWebview]?.setFullscreen(fullscreen)
|
||||
|
||||
$: width &&
|
||||
height &&
|
||||
windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height))
|
||||
webviewMap[selectedWebview]?.setSize(new PhysicalSize(width, height))
|
||||
$: minWidth && minHeight
|
||||
? windowMap[selectedWindow]?.setMinSize(
|
||||
? webviewMap[selectedWebview]?.setMinSize(
|
||||
new LogicalSize(minWidth, minHeight)
|
||||
)
|
||||
: windowMap[selectedWindow]?.setMinSize(null)
|
||||
: webviewMap[selectedWebview]?.setMinSize(null)
|
||||
$: maxWidth > 800 && maxHeight > 400
|
||||
? windowMap[selectedWindow]?.setMaxSize(
|
||||
? webviewMap[selectedWebview]?.setMaxSize(
|
||||
new LogicalSize(maxWidth, maxHeight)
|
||||
)
|
||||
: windowMap[selectedWindow]?.setMaxSize(null)
|
||||
: webviewMap[selectedWebview]?.setMaxSize(null)
|
||||
$: x !== null &&
|
||||
y !== null &&
|
||||
windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y))
|
||||
$: windowMap[selectedWindow]
|
||||
webviewMap[selectedWebview]?.setPosition(new PhysicalPosition(x, y))
|
||||
$: webviewMap[selectedWebview]
|
||||
?.scaleFactor()
|
||||
.then((factor) => (scaleFactor = factor))
|
||||
$: addWindowEventListeners(windowMap[selectedWindow])
|
||||
$: addWindowEventListeners(webviewMap[selectedWebview])
|
||||
|
||||
$: windowMap[selectedWindow]?.setCursorGrab(cursorGrab)
|
||||
$: windowMap[selectedWindow]?.setCursorVisible(cursorVisible)
|
||||
$: windowMap[selectedWindow]?.setCursorIcon(cursorIcon)
|
||||
$: webviewMap[selectedWebview]?.setCursorGrab(cursorGrab)
|
||||
$: webviewMap[selectedWebview]?.setCursorVisible(cursorVisible)
|
||||
$: webviewMap[selectedWebview]?.setCursorIcon(cursorIcon)
|
||||
$: cursorX !== null &&
|
||||
cursorY !== null &&
|
||||
windowMap[selectedWindow]?.setCursorPosition(
|
||||
webviewMap[selectedWebview]?.setCursorPosition(
|
||||
new PhysicalPosition(cursorX, cursorY)
|
||||
)
|
||||
$: windowMap[selectedWindow]?.setIgnoreCursorEvents(cursorIgnoreEvents)
|
||||
$: windowMap[selectedWindow]?.setProgressBar({
|
||||
$: webviewMap[selectedWebview]?.setIgnoreCursorEvents(cursorIgnoreEvents)
|
||||
$: webviewMap[selectedWebview]?.setProgressBar({
|
||||
status: selectedProgressBarStatus,
|
||||
progress
|
||||
})
|
||||
@ -297,21 +298,21 @@
|
||||
class="input grow"
|
||||
type="text"
|
||||
placeholder="New Window label.."
|
||||
bind:value={newWindowLabel}
|
||||
bind:value={newWebviewLabel}
|
||||
/>
|
||||
<button class="btn" on:click={createWindow}>New window</button>
|
||||
<button class="btn" on:click={createWebviewWindow}>New window</button>
|
||||
</div>
|
||||
<br />
|
||||
{#if Object.keys(windowMap).length >= 1}
|
||||
{#if Object.keys(webviewMap).length >= 1}
|
||||
<span class="font-700 text-sm">Selected window:</span>
|
||||
<select class="input" bind:value={selectedWindow}>
|
||||
<select class="input" bind:value={selectedWebview}>
|
||||
<option value="" disabled selected>Choose a window...</option>
|
||||
{#each Object.keys(windowMap) as label}
|
||||
{#each Object.keys(webviewMap) as label}
|
||||
<option value={label}>{label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{#if windowMap[selectedWindow]}
|
||||
{#if webviewMap[selectedWebview]}
|
||||
<br />
|
||||
<div class="flex gap-1 items-center">
|
||||
<label for="windowIconPath"> Icon path </label>
|
||||
@ -329,7 +330,7 @@
|
||||
<button
|
||||
class="btn"
|
||||
title="Unminimizes after 2 seconds"
|
||||
on:click={() => windowMap[selectedWindow].center()}
|
||||
on:click={() => webviewMap[selectedWebview].center()}
|
||||
>
|
||||
Center
|
||||
</button>
|
||||
|
||||
1355
examples/file-associations/src-tauri/Cargo.lock
generated
1355
examples/file-associations/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -41,7 +41,7 @@ fn main() {
|
||||
"".into()
|
||||
};
|
||||
|
||||
tauri::WindowBuilder::new(app, "main", Default::default())
|
||||
tauri::WebviewWindowBuilder::new(app, "main", Default::default())
|
||||
.initialization_script(&format!("window.openedUrls = `{opened_urls}`"))
|
||||
.initialization_script(&format!("console.log(`{opened_urls}`)"))
|
||||
.build()
|
||||
@ -51,19 +51,22 @@ fn main() {
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while running tauri application")
|
||||
.run(|app, event| {
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
if let Some(w) = app.get_window("main") {
|
||||
let urls = urls
|
||||
.iter()
|
||||
.map(|u| u.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let _ = w.eval(&format!("window.onFileOpen(`{urls}`)"));
|
||||
}
|
||||
.run(
|
||||
#[allow(unused_variables)]
|
||||
|app, event| {
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let urls = urls
|
||||
.iter()
|
||||
.map(|u| u.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let _ = w.eval(&format!("window.onFileOpen(`{urls}`)"));
|
||||
}
|
||||
|
||||
app.state::<OpenedUrls>().0.lock().unwrap().replace(urls);
|
||||
}
|
||||
});
|
||||
app.state::<OpenedUrls>().0.lock().unwrap().replace(urls);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
3
examples/multiwebview/README.md
Normal file
3
examples/multiwebview/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Hello World Example
|
||||
|
||||
To execute run the following on the root directory of the repository: `cargo run --example multiwebview --features unstable`.
|
||||
11
examples/multiwebview/index.html
Normal file
11
examples/multiwebview/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to Tauri!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to Tauri!</h1>
|
||||
</body>
|
||||
</html>
|
||||
58
examples/multiwebview/main.rs
Normal file
58
examples/multiwebview/main.rs
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tauri::{LogicalPosition, LogicalSize, WebviewUrl};
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let width = 800.;
|
||||
let height = 600.;
|
||||
let window = tauri::window::WindowBuilder::new(app, "main")
|
||||
.inner_size(width, height)
|
||||
.build()?;
|
||||
|
||||
let _webview1 = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new("main1", WebviewUrl::App(Default::default()))
|
||||
.auto_resize(),
|
||||
LogicalPosition::new(0., 0.),
|
||||
LogicalSize::new(width / 2., height / 2.),
|
||||
)?;
|
||||
let _webview2 = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"main2",
|
||||
WebviewUrl::External("https://github.com/tauri-apps/tauri".parse().unwrap()),
|
||||
)
|
||||
.auto_resize(),
|
||||
LogicalPosition::new(width / 2., 0.),
|
||||
LogicalSize::new(width / 2., height / 2.),
|
||||
)?;
|
||||
let _webview3 = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"main3",
|
||||
WebviewUrl::External("https://tauri.app".parse().unwrap()),
|
||||
)
|
||||
.auto_resize(),
|
||||
LogicalPosition::new(0., height / 2.),
|
||||
LogicalSize::new(width / 2., height / 2.),
|
||||
)?;
|
||||
let _webview4 = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"main4",
|
||||
WebviewUrl::External("https://twitter.com/TauriApps".parse().unwrap()),
|
||||
)
|
||||
.auto_resize(),
|
||||
LogicalPosition::new(width / 2., height / 2.),
|
||||
LogicalSize::new(width / 2., height / 2.),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!(
|
||||
"../../examples/multiwebview/tauri.conf.json"
|
||||
))
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
43
examples/multiwebview/tauri.conf.json
Normal file
43
examples/multiwebview/tauri.conf.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "../../core/tauri-config-schema/schema.json",
|
||||
"build": {
|
||||
"distDir": ["index.html"],
|
||||
"devPath": ["index.html"],
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": ""
|
||||
},
|
||||
"package": {
|
||||
"productName": "MultiWebview",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"../.icons/32x32.png",
|
||||
"../.icons/128x128.png",
|
||||
"../.icons/128x128@2x.png",
|
||||
"../.icons/icon.icns",
|
||||
"../.icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "",
|
||||
"category": "DeveloperTool",
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"exceptionDomain": ""
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": "default-src 'self'; connect-src ipc: http://ipc.localhost"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,87 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#response {
|
||||
white-space: pre-wrap;
|
||||
|
||||
<head>
|
||||
<style>
|
||||
#response {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="window-label"></div>
|
||||
<div id="container"></div>
|
||||
<div id="response"></div>
|
||||
|
||||
<script>
|
||||
const WebviewWindow = window.__TAURI__.webview.WebviewWindow
|
||||
const appWindow = window.__TAURI__.window.getCurrent()
|
||||
const windowLabel = appWindow.label
|
||||
const windowLabelContainer = document.getElementById('window-label')
|
||||
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
|
||||
|
||||
const container = document.getElementById('container')
|
||||
|
||||
function createWindowMessageBtn(label) {
|
||||
const webview = WebviewWindow.getByLabel(label)
|
||||
const button = document.createElement('button')
|
||||
button.innerText = 'Send message to ' + label
|
||||
button.addEventListener('click', function () {
|
||||
webview.emit('clicked', 'message from ' + windowLabel)
|
||||
})
|
||||
container.appendChild(button)
|
||||
}
|
||||
|
||||
// global listener
|
||||
const responseContainer = document.getElementById('response')
|
||||
window.__TAURI__.event.listen('clicked', function (event) {
|
||||
responseContainer.innerHTML +=
|
||||
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
|
||||
})
|
||||
window.__TAURI__.event.listen('tauri://webview-created', function (event) {
|
||||
createWindowMessageBtn(event.payload.label)
|
||||
})
|
||||
|
||||
// listener tied to this window
|
||||
appWindow.listen('clicked', function (event) {
|
||||
responseContainer.innerText +=
|
||||
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
|
||||
})
|
||||
|
||||
const createWindowButton = document.createElement('button')
|
||||
createWindowButton.innerHTML = 'Create window'
|
||||
createWindowButton.addEventListener('click', function () {
|
||||
const id = Math.random().toString().replace('.', '')
|
||||
const webview = new WebviewWindow(id, { tabbingIdentifier: windowLabel })
|
||||
webview.once('tauri://created', function () {
|
||||
responseContainer.innerHTML += 'Created new window'
|
||||
})
|
||||
webview.once('tauri://error', function (e) {
|
||||
responseContainer.innerHTML += 'Error creating new window ' + e.payload
|
||||
})
|
||||
})
|
||||
container.appendChild(createWindowButton)
|
||||
|
||||
const globalMessageButton = document.createElement('button')
|
||||
globalMessageButton.innerHTML = 'Send global message'
|
||||
globalMessageButton.addEventListener('click', function () {
|
||||
// emit to all windows
|
||||
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
|
||||
})
|
||||
container.appendChild(globalMessageButton)
|
||||
|
||||
const allWindows = window.__TAURI__.window.getAll()
|
||||
for (const index in allWindows) {
|
||||
const label = allWindows[index].label
|
||||
if (label === windowLabel) {
|
||||
continue
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
createWindowMessageBtn(label)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<body>
|
||||
<div id="window-label"></div>
|
||||
<div id="container"></div>
|
||||
<div id="response"></div>
|
||||
|
||||
<script>
|
||||
const TauriWindow = window.__TAURI__.window.Window
|
||||
const appWindow = window.__TAURI__.window.getCurrent()
|
||||
const windowLabel = appWindow.label
|
||||
const windowLabelContainer = document.getElementById('window-label')
|
||||
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
|
||||
|
||||
const container = document.getElementById('container')
|
||||
|
||||
function createWindowMessageBtn(label) {
|
||||
const tauriWindow = TauriWindow.getByLabel(label)
|
||||
const button = document.createElement('button')
|
||||
button.innerText = 'Send message to ' + label
|
||||
button.addEventListener('click', function () {
|
||||
tauriWindow.emit('clicked', 'message from ' + windowLabel)
|
||||
})
|
||||
container.appendChild(button)
|
||||
}
|
||||
|
||||
// global listener
|
||||
const responseContainer = document.getElementById('response')
|
||||
window.__TAURI__.event.listen('clicked', function (event) {
|
||||
responseContainer.innerHTML +=
|
||||
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
|
||||
})
|
||||
window.__TAURI__.event.listen('tauri://window-created', function (event) {
|
||||
createWindowMessageBtn(event.payload.label)
|
||||
})
|
||||
|
||||
// listener tied to this window
|
||||
appWindow.listen('clicked', function (event) {
|
||||
responseContainer.innerText +=
|
||||
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
|
||||
})
|
||||
|
||||
const createWindowButton = document.createElement('button')
|
||||
createWindowButton.innerHTML = 'Create window'
|
||||
createWindowButton.addEventListener('click', function () {
|
||||
const tauriWindow = new TauriWindow(
|
||||
Math.random().toString().replace('.', ''),
|
||||
{
|
||||
tabbingIdentifier: windowLabel
|
||||
}
|
||||
)
|
||||
tauriWindow.once('tauri://created', function () {
|
||||
responseContainer.innerHTML += 'Created new webview'
|
||||
})
|
||||
tauriWindow.once('tauri://error', function (e) {
|
||||
responseContainer.innerHTML += 'Error creating new webview'
|
||||
})
|
||||
})
|
||||
container.appendChild(createWindowButton)
|
||||
|
||||
const globalMessageButton = document.createElement('button')
|
||||
globalMessageButton.innerHTML = 'Send global message'
|
||||
globalMessageButton.addEventListener('click', function () {
|
||||
// emit to all windows
|
||||
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
|
||||
})
|
||||
container.appendChild(globalMessageButton)
|
||||
|
||||
const allWindows = window.__TAURI__.window.getAll()
|
||||
for (const index in allWindows) {
|
||||
const label = allWindows[index].label
|
||||
if (label === windowLabel) {
|
||||
continue
|
||||
}
|
||||
createWindowMessageBtn(label)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -4,30 +4,28 @@
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tauri::{window::PageLoadEvent, WindowBuilder};
|
||||
use tauri::{webview::PageLoadEvent, WebviewWindowBuilder};
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.on_page_load(|window, payload| {
|
||||
.on_page_load(|webview, payload| {
|
||||
if payload.event() == PageLoadEvent::Finished {
|
||||
let label = window.label().to_string();
|
||||
window.listen("clicked".to_string(), move |_payload| {
|
||||
let label = webview.label().to_string();
|
||||
webview.listen("clicked".to_string(), move |_payload| {
|
||||
println!("got 'clicked' event on window '{label}'");
|
||||
});
|
||||
}
|
||||
})
|
||||
.setup(|app| {
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = WindowBuilder::new(
|
||||
app,
|
||||
"Rust".to_string(),
|
||||
tauri::WindowUrl::App("index.html".into()),
|
||||
);
|
||||
let mut builder =
|
||||
WebviewWindowBuilder::new(app, "Rust", tauri::WebviewUrl::App("index.html".into()));
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder.tabbing_identifier("Rust");
|
||||
}
|
||||
let _window = builder.title("Tauri - Rust").build()?;
|
||||
let _webview = builder.title("Tauri - Rust").build()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!(
|
||||
|
||||
@ -4,42 +4,39 @@
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tauri::{
|
||||
command,
|
||||
window::{PageLoadEvent, WindowBuilder},
|
||||
Window, WindowUrl,
|
||||
};
|
||||
use tauri::{command, webview::PageLoadEvent, WebviewUrl, WebviewWindowBuilder, Window};
|
||||
|
||||
#[command]
|
||||
async fn create_child_window(id: String, window: Window) {
|
||||
let child = WindowBuilder::new(&window, id, WindowUrl::default())
|
||||
let builder = WebviewWindowBuilder::new(&window, &id, WebviewUrl::default())
|
||||
.title("Child")
|
||||
.inner_size(400.0, 300.0);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let child = child.parent_window(window.ns_window().unwrap());
|
||||
let builder = builder.parent_window(window.ns_window().unwrap());
|
||||
#[cfg(windows)]
|
||||
let child = child.parent_window(window.hwnd().unwrap());
|
||||
let builder = builder.parent_window(window.hwnd().unwrap());
|
||||
|
||||
child.build().unwrap();
|
||||
let _webview = builder.build().unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.on_page_load(|window, payload| {
|
||||
.on_page_load(|webview, payload| {
|
||||
if payload.event() == PageLoadEvent::Finished {
|
||||
let label = window.label().to_string();
|
||||
window.listen("clicked".to_string(), move |_payload| {
|
||||
let label = webview.label().to_string();
|
||||
webview.listen("clicked".to_string(), move |_payload| {
|
||||
println!("got 'clicked' event on window '{label}'");
|
||||
});
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![create_child_window])
|
||||
.setup(|app| {
|
||||
WindowBuilder::new(app, "main".to_string(), WindowUrl::default())
|
||||
let _webview = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.title("Main")
|
||||
.inner_size(600.0, 400.0)
|
||||
.build()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!(
|
||||
|
||||
2185
examples/resources/src-tauri/Cargo.lock
generated
2185
examples/resources/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ tauri-build = { path = "../../../core/tauri-build", features = ["codegen"] }
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tauri = { path = "../../../core/tauri", features = ["shell-execute"] }
|
||||
tauri = { path = "../../../core/tauri" }
|
||||
|
||||
[features]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
|
||||
1
examples/resources/src-tauri/capabilities/.gitignore
vendored
Normal file
1
examples/resources/src-tauri/capabilities/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
schemas/
|
||||
9
examples/resources/src-tauri/capabilities/app.json
Normal file
9
examples/resources/src-tauri/capabilities/app.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "./schemas/desktop-schema.json",
|
||||
"identifier": "app",
|
||||
"permissions": [
|
||||
"event:default",
|
||||
"window:default"
|
||||
],
|
||||
"windows": ["main"]
|
||||
}
|
||||
@ -4,34 +4,43 @@
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
use tauri::{
|
||||
process::{Command, CommandEvent},
|
||||
Manager,
|
||||
};
|
||||
use std::{
|
||||
io::{BufRead, BufReader},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use tauri::Manager;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(move |app| {
|
||||
let window = app.get_window("main").unwrap();
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
let script_path = app
|
||||
.path_resolver()
|
||||
.resolve_resource("assets/index.js")
|
||||
.path()
|
||||
.resolve("assets/index.js", tauri::path::BaseDirectory::Resource)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let (mut rx, _child) = Command::new("node")
|
||||
std::thread::spawn(move || {
|
||||
let mut child = Command::new("node")
|
||||
.args(&[script_path])
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn node");
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let mut stdout = BufReader::new(stdout);
|
||||
|
||||
#[allow(clippy::collapsible_match)]
|
||||
while let Some(event) = rx.recv().await {
|
||||
if let CommandEvent::Stdout(line) = event {
|
||||
window
|
||||
.emit("message", Some(format!("'{}'", line)))
|
||||
.expect("failed to emit event");
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
let n = stdout.read_line(&mut line).unwrap();
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
window
|
||||
.emit("message", Some(format!("'{}'", line)))
|
||||
.expect("failed to emit event");
|
||||
|
||||
line.clear();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -16,8 +16,8 @@ mod rust {
|
||||
pub fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let splashscreen_window = app.get_window("splashscreen").unwrap();
|
||||
let main_window = app.get_window("main").unwrap();
|
||||
let splashscreen_window = app.get_webview_window("splashscreen").unwrap();
|
||||
let main_window = app.get_webview_window("main").unwrap();
|
||||
// we perform the initialization code on a new task so the app doesn't crash
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Initializing...");
|
||||
@ -39,16 +39,16 @@ mod rust {
|
||||
// Application code for a splashscreen system that waits for the UI
|
||||
mod ui {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{Manager, State, Window};
|
||||
use tauri::{Manager, State, WebviewWindow};
|
||||
|
||||
// wrappers around each Window
|
||||
// we use a dedicated type because Tauri can only manage a single instance of a given type
|
||||
struct SplashscreenWindow(Arc<Mutex<Window>>);
|
||||
struct MainWindow(Arc<Mutex<Window>>);
|
||||
struct SplashscreenWindow(Arc<Mutex<WebviewWindow>>);
|
||||
struct MainWindow(Arc<Mutex<WebviewWindow>>);
|
||||
|
||||
#[tauri::command]
|
||||
fn close_splashscreen(
|
||||
_: Window, // force inference of P
|
||||
_: WebviewWindow, // force inference of P
|
||||
splashscreen: State<SplashscreenWindow>,
|
||||
main: State<MainWindow>,
|
||||
) {
|
||||
@ -65,10 +65,10 @@ mod ui {
|
||||
.setup(|app| {
|
||||
// set the splashscreen and main windows to be globally available with the tauri state API
|
||||
app.manage(SplashscreenWindow(Arc::new(Mutex::new(
|
||||
app.get_window("splashscreen").unwrap(),
|
||||
app.get_webview_window("splashscreen").unwrap(),
|
||||
))));
|
||||
app.manage(MainWindow(Arc::new(Mutex::new(
|
||||
app.get_window("main").unwrap(),
|
||||
app.get_webview_window("main").unwrap(),
|
||||
))));
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@ -11,11 +11,24 @@
|
||||
|
||||
import { invoke, transformCallback } from './core'
|
||||
|
||||
type EventSource =
|
||||
| {
|
||||
kind: 'global'
|
||||
}
|
||||
| {
|
||||
kind: 'window'
|
||||
label: string
|
||||
}
|
||||
| {
|
||||
kind: 'webview'
|
||||
label: string
|
||||
}
|
||||
|
||||
interface Event<T> {
|
||||
/** Event name */
|
||||
event: EventName
|
||||
/** The label of the window that emitted this event. */
|
||||
windowLabel: string
|
||||
/** The source of the event. Can be a global event, an event from a window or an event from another webview. */
|
||||
source: EventSource
|
||||
/** Event identifier used to unlisten */
|
||||
id: number
|
||||
/** Event payload */
|
||||
@ -30,14 +43,16 @@ type EventName = `${TauriEvent}` | (string & Record<never, never>)
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* Label of the window the function targets.
|
||||
* Window or webview the function targets.
|
||||
*
|
||||
* When listening to events and using this value,
|
||||
* only events triggered by the window with the given label are received.
|
||||
*
|
||||
* When emitting events, only the window with the given label will receive it.
|
||||
*/
|
||||
target?: string
|
||||
target?:
|
||||
| { kind: 'window'; label: string }
|
||||
| { kind: 'webview'; label: string }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,15 +62,15 @@ enum TauriEvent {
|
||||
WINDOW_RESIZED = 'tauri://resize',
|
||||
WINDOW_MOVED = 'tauri://move',
|
||||
WINDOW_CLOSE_REQUESTED = 'tauri://close-requested',
|
||||
WINDOW_CREATED = 'tauri://window-created',
|
||||
WINDOW_DESTROYED = 'tauri://destroyed',
|
||||
WINDOW_FOCUS = 'tauri://focus',
|
||||
WINDOW_BLUR = 'tauri://blur',
|
||||
WINDOW_SCALE_FACTOR_CHANGED = 'tauri://scale-change',
|
||||
WINDOW_THEME_CHANGED = 'tauri://theme-changed',
|
||||
WINDOW_FILE_DROP = 'tauri://file-drop',
|
||||
WINDOW_FILE_DROP_HOVER = 'tauri://file-drop-hover',
|
||||
WINDOW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled'
|
||||
WEBVIEW_CREATED = 'tauri://webview-created',
|
||||
WEBVIEW_FILE_DROP = 'tauri://file-drop',
|
||||
WEBVIEW_FILE_DROP_HOVER = 'tauri://file-drop-hover',
|
||||
WEBVIEW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,13 +90,13 @@ async function _unlisten(event: string, eventId: number): Promise<void> {
|
||||
|
||||
/**
|
||||
* Listen to an event. The event can be either global or window-specific.
|
||||
* See {@link Event.windowLabel} to check the event source.
|
||||
* See {@link Event.source} to check the event source.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { listen } from '@tauri-apps/api/event';
|
||||
* const unlisten = await listen<string>('error', (event) => {
|
||||
* console.log(`Got error in window ${event.windowLabel}, payload: ${event.payload}`);
|
||||
* console.log(`Got error in window ${event.source}, payload: ${event.payload}`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
@ -102,7 +117,7 @@ async function listen<T>(
|
||||
): Promise<UnlistenFn> {
|
||||
return invoke<number>('plugin:event|listen', {
|
||||
event,
|
||||
windowLabel: options?.target,
|
||||
target: options?.target,
|
||||
handler: transformCallback(handler)
|
||||
}).then((eventId) => {
|
||||
return async () => _unlisten(event, eventId)
|
||||
@ -167,11 +182,18 @@ async function emit(
|
||||
): Promise<void> {
|
||||
await invoke('plugin:event|emit', {
|
||||
event,
|
||||
windowLabel: options?.target,
|
||||
target: options?.target,
|
||||
payload
|
||||
})
|
||||
}
|
||||
|
||||
export type { Event, EventCallback, UnlistenFn, EventName, Options }
|
||||
export type {
|
||||
EventSource,
|
||||
Event,
|
||||
EventCallback,
|
||||
UnlistenFn,
|
||||
EventName,
|
||||
Options
|
||||
}
|
||||
|
||||
export { listen, once, emit, TauriEvent }
|
||||
|
||||
8
tooling/api/src/global.d.ts
vendored
8
tooling/api/src/global.d.ts
vendored
@ -23,6 +23,8 @@ declare global {
|
||||
metadata: {
|
||||
windows: WindowDef[]
|
||||
currentWindow: WindowDef
|
||||
webviews: WebviewDef[]
|
||||
currentWebview: WebviewDef
|
||||
}
|
||||
plugins: {
|
||||
path: {
|
||||
@ -34,6 +36,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
interface WebviewDef {
|
||||
windowLabel: string
|
||||
label: string
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
interface WindowDef {
|
||||
label: string
|
||||
|
||||
@ -17,9 +17,10 @@ import * as app from './app'
|
||||
import * as event from './event'
|
||||
import * as core from './core'
|
||||
import * as window from './window'
|
||||
import * as webview from './webview'
|
||||
import * as path from './path'
|
||||
import * as dpi from './dpi'
|
||||
import * as tray from './tray'
|
||||
import * as menu from './menu'
|
||||
|
||||
export { app, dpi, event, path, core, window, tray, menu }
|
||||
export { app, dpi, event, path, core, window, webview, tray, menu }
|
||||
|
||||
@ -147,7 +147,12 @@ export function mockWindows(
|
||||
mockInternals()
|
||||
window.__TAURI_INTERNALS__.metadata = {
|
||||
windows: [current, ...additionalWindows].map((label) => ({ label })),
|
||||
currentWindow: { label: current }
|
||||
currentWindow: { label: current },
|
||||
webviews: [current, ...additionalWindows].map((label) => ({
|
||||
windowLabel: label,
|
||||
label
|
||||
})),
|
||||
currentWebview: { windowLabel: current, label: current }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
682
tooling/api/src/webview.ts
Normal file
682
tooling/api/src/webview.ts
Normal file
@ -0,0 +1,682 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/**
|
||||
* Provides APIs to create webviews, communicate with other webviews and manipulate the current webview.
|
||||
*
|
||||
* ## Webview events
|
||||
*
|
||||
* Events can be listened to using {@link Webview.listen}:
|
||||
* ```typescript
|
||||
* import { getCurrent } from "@tauri-apps/api/webview";
|
||||
* getCurrent().listen("my-webview-event", ({ event, payload }) => { });
|
||||
* ```
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { PhysicalPosition, PhysicalSize } from './dpi'
|
||||
import type { LogicalPosition, LogicalSize } from './dpi'
|
||||
import type { EventName, EventCallback, UnlistenFn } from './event'
|
||||
import { TauriEvent, emit, listen, once } from './event'
|
||||
import { invoke } from './core'
|
||||
import { Window, getCurrent as getCurrentWindow } from './window'
|
||||
import type { WindowOptions } from './window'
|
||||
|
||||
interface FileDropPayload {
|
||||
paths: string[]
|
||||
position: PhysicalPosition
|
||||
}
|
||||
|
||||
/** The file drop event types. */
|
||||
type FileDropEvent =
|
||||
| ({ type: 'hover' } & FileDropPayload)
|
||||
| ({ type: 'drop' } & FileDropPayload)
|
||||
| { type: 'cancel' }
|
||||
|
||||
/**
|
||||
* Get an instance of `Webview` for the current webview.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function getCurrent(): Webview {
|
||||
return new Webview(
|
||||
getCurrentWindow(),
|
||||
window.__TAURI_INTERNALS__.metadata.currentWebview.label,
|
||||
{
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
skip: true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of instances of `Webview` for all available webviews.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function getAll(): Webview[] {
|
||||
return window.__TAURI_INTERNALS__.metadata.webviews.map(
|
||||
(w) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
new Webview(Window.getByLabel(w.windowLabel)!, w.label, {
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
skip: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
// events that are emitted right here instead of by the created webview
|
||||
const localTauriEvents = ['tauri://created', 'tauri://error']
|
||||
/** @ignore */
|
||||
export type WebviewLabel = string
|
||||
|
||||
/**
|
||||
* Create new webview or get a handle to an existing one.
|
||||
*
|
||||
* Webviews are identified by a *label* a unique identifier that can be used to reference it later.
|
||||
* It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Window } from "@tauri-apps/api/window"
|
||||
* import { Webview } from "@tauri-apps/api/webview"
|
||||
*
|
||||
* // loading embedded asset:
|
||||
* const appWindow = new Window('uniqueLabel')
|
||||
*
|
||||
* const webview = new Webview(appWindow, 'theUniqueLabel', {
|
||||
* url: 'path/to/page.html'
|
||||
* });
|
||||
* // alternatively, load a remote URL:
|
||||
* const webview = new Webview(appWindow, 'theUniqueLabel', {
|
||||
* url: 'https://github.com/tauri-apps/tauri'
|
||||
* });
|
||||
*
|
||||
* webview.once('tauri://created', function () {
|
||||
* // webview successfully created
|
||||
* });
|
||||
* webview.once('tauri://error', function (e) {
|
||||
* // an error happened creating the webview
|
||||
* });
|
||||
*
|
||||
* // emit an event to the backend
|
||||
* await webview.emit("some event", "data");
|
||||
* // listen to an event from the backend
|
||||
* const unlisten = await webview.listen("event name", e => {});
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Webview {
|
||||
/** The webview label. It is a unique identifier for the webview, can be used to reference it later. */
|
||||
label: WebviewLabel
|
||||
/** The window hosting this webview. */
|
||||
window: Window
|
||||
/** Local event listeners. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listeners: Record<string, Array<EventCallback<any>>>
|
||||
|
||||
/**
|
||||
* Creates a new Webview.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Window } from '@tauri-apps/api/window'
|
||||
* import { Webview } from '@tauri-apps/api/webview'
|
||||
* const appWindow = new Window('my-label')
|
||||
* const webview = new Window(appWindow, 'my-label', {
|
||||
* url: 'https://github.com/tauri-apps/tauri'
|
||||
* });
|
||||
* webview.once('tauri://created', function () {
|
||||
* // webview successfully created
|
||||
* });
|
||||
* webview.once('tauri://error', function (e) {
|
||||
* // an error happened creating the webview
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param window the window to add this webview to.
|
||||
* @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
|
||||
* @returns The {@link Webview} instance to communicate with the webview.
|
||||
*/
|
||||
constructor(window: Window, label: WebviewLabel, options: WebviewOptions) {
|
||||
this.window = window
|
||||
this.label = label
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.listeners = Object.create(null)
|
||||
|
||||
// @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
|
||||
if (!options?.skip) {
|
||||
invoke('plugin:webview|create_webview', {
|
||||
windowLabel: window.label,
|
||||
label,
|
||||
options
|
||||
})
|
||||
.then(async () => this.emit('tauri://created'))
|
||||
.catch(async (e: string) => this.emit('tauri://error', e))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Webview for the webview associated with the given label.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Webview } from '@tauri-apps/api/webview';
|
||||
* const mainWebview = Webview.getByLabel('main');
|
||||
* ```
|
||||
*
|
||||
* @param label The webview label.
|
||||
* @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
|
||||
*/
|
||||
static getByLabel(label: string): Webview | null {
|
||||
return getAll().find((w) => w.label === label) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of `Webview` for the current webview.
|
||||
*/
|
||||
static getCurrent(): Webview {
|
||||
return getCurrent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of instances of `Webview` for all available webviews.
|
||||
*/
|
||||
static getAll(): Webview[] {
|
||||
return getAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an event emitted by the backend that is tied to the webview.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* const unlisten = await getCurrent().listen<string>('state-changed', (event) => {
|
||||
* console.log(`Got error: ${payload}`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async listen<T>(
|
||||
event: EventName,
|
||||
handler: EventCallback<T>
|
||||
): Promise<UnlistenFn> {
|
||||
if (this._handleTauriEvent(event, handler)) {
|
||||
return Promise.resolve(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection
|
||||
const listeners = this.listeners[event]
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return listen(event, handler, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an one-off event emitted by the backend that is tied to the webview.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* const unlisten = await getCurrent().once<null>('initialized', (event) => {
|
||||
* console.log(`Webview initialized!`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
|
||||
if (this._handleTauriEvent(event, handler)) {
|
||||
return Promise.resolve(() => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const listeners = this.listeners[event]
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return once(event, handler, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the backend, tied to the webview.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*/
|
||||
async emit(event: string, payload?: unknown): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
source: { kind: 'webview', label: this.label },
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emit(event, payload, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
_handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
if (!(event in this.listeners)) {
|
||||
// eslint-disable-next-line
|
||||
this.listeners[event] = [handler]
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
this.listeners[event].push(handler)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Getters
|
||||
/**
|
||||
* The position of the top-left hand corner of the webview's client area relative to the top-left hand corner of the desktop.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* const position = await getCurrent().position();
|
||||
* ```
|
||||
*
|
||||
* @returns The webview's position.
|
||||
*/
|
||||
async position(): Promise<PhysicalPosition> {
|
||||
return invoke<{ x: number; y: number }>('plugin:webview|webview_position', {
|
||||
label: this.label
|
||||
}).then(({ x, y }) => new PhysicalPosition(x, y))
|
||||
}
|
||||
|
||||
/**
|
||||
* The physical size of the webview's client area.
|
||||
* The client area is the content of the webview, excluding the title bar and borders.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* const size = await getCurrent().size();
|
||||
* ```
|
||||
*
|
||||
* @returns The webview's size.
|
||||
*/
|
||||
async size(): Promise<PhysicalSize> {
|
||||
return invoke<{ width: number; height: number }>(
|
||||
'plugin:webview|webview_size',
|
||||
{
|
||||
label: this.label
|
||||
}
|
||||
).then(({ width, height }) => new PhysicalSize(width, height))
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
/**
|
||||
* Closes the webview.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().close();
|
||||
* ```
|
||||
*
|
||||
* @returns A promise indicating the success or failure of the operation.
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
return invoke('plugin:webview|close', {
|
||||
label: this.label
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the webview.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent, LogicalSize } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().setSize(new LogicalSize(600, 500));
|
||||
* ```
|
||||
*
|
||||
* @param size The logical or physical size.
|
||||
* @returns A promise indicating the success or failure of the operation.
|
||||
*/
|
||||
async setSize(size: LogicalSize | PhysicalSize): Promise<void> {
|
||||
if (!size || (size.type !== 'Logical' && size.type !== 'Physical')) {
|
||||
throw new Error(
|
||||
'the `size` argument must be either a LogicalSize or a PhysicalSize instance'
|
||||
)
|
||||
}
|
||||
|
||||
return invoke('plugin:webview|set_webview_size', {
|
||||
label: this.label,
|
||||
value: {
|
||||
type: size.type,
|
||||
data: {
|
||||
width: size.width,
|
||||
height: size.height
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the webview position.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent, LogicalPosition } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().setPosition(new LogicalPosition(600, 500));
|
||||
* ```
|
||||
*
|
||||
* @param position The new position, in logical or physical pixels.
|
||||
* @returns A promise indicating the success or failure of the operation.
|
||||
*/
|
||||
async setPosition(
|
||||
position: LogicalPosition | PhysicalPosition
|
||||
): Promise<void> {
|
||||
if (
|
||||
!position ||
|
||||
(position.type !== 'Logical' && position.type !== 'Physical')
|
||||
) {
|
||||
throw new Error(
|
||||
'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance'
|
||||
)
|
||||
}
|
||||
|
||||
return invoke('plugin:webview|set_webview_position', {
|
||||
label: this.label,
|
||||
value: {
|
||||
type: position.type,
|
||||
data: {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring the webview to front and focus.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().setFocus();
|
||||
* ```
|
||||
*
|
||||
* @returns A promise indicating the success or failure of the operation.
|
||||
*/
|
||||
async setFocus(): Promise<void> {
|
||||
return invoke('plugin:webview|set_webview_focus', {
|
||||
label: this.label
|
||||
})
|
||||
}
|
||||
|
||||
// Listeners
|
||||
|
||||
/**
|
||||
* Listen to a file drop event.
|
||||
* The listener is triggered when the user hovers the selected files on the webview,
|
||||
* drops the files or cancels the operation.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from "@tauri-apps/api/webview";
|
||||
* const unlisten = await getCurrent().onFileDropEvent((event) => {
|
||||
* if (event.payload.type === 'hover') {
|
||||
* console.log('User hovering', event.payload.paths);
|
||||
* } else if (event.payload.type === 'drop') {
|
||||
* console.log('User dropped', event.payload.paths);
|
||||
* } else {
|
||||
* console.log('File drop cancelled');
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async onFileDropEvent(
|
||||
handler: EventCallback<FileDropEvent>
|
||||
): Promise<UnlistenFn> {
|
||||
const unlistenFileDrop = await this.listen<FileDropPayload>(
|
||||
TauriEvent.WEBVIEW_FILE_DROP,
|
||||
(event) => {
|
||||
handler({
|
||||
...event,
|
||||
payload: {
|
||||
type: 'drop',
|
||||
paths: event.payload.paths,
|
||||
position: mapPhysicalPosition(event.payload.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const unlistenFileHover = await this.listen<FileDropPayload>(
|
||||
TauriEvent.WEBVIEW_FILE_DROP_HOVER,
|
||||
(event) => {
|
||||
handler({
|
||||
...event,
|
||||
payload: {
|
||||
type: 'hover',
|
||||
paths: event.payload.paths,
|
||||
position: mapPhysicalPosition(event.payload.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const unlistenCancel = await this.listen<null>(
|
||||
TauriEvent.WEBVIEW_FILE_DROP_CANCELLED,
|
||||
(event) => {
|
||||
handler({ ...event, payload: { type: 'cancel' } })
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
unlistenFileDrop()
|
||||
unlistenFileHover()
|
||||
unlistenCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition {
|
||||
return new PhysicalPosition(m.x, m.y)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
interface WebviewWindow extends Webview, Window {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
class WebviewWindow {
|
||||
label: string
|
||||
/** Local event listeners. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listeners: Record<string, Array<EventCallback<any>>>
|
||||
|
||||
/**
|
||||
* Creates a new {@link Window} hosting a {@link Webview}.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { WebviewWindow } from '@tauri-apps/api/webview'
|
||||
* const webview = new WebviewWindow('my-label', {
|
||||
* url: 'https://github.com/tauri-apps/tauri'
|
||||
* });
|
||||
* webview.once('tauri://created', function () {
|
||||
* // webview successfully created
|
||||
* });
|
||||
* webview.once('tauri://error', function (e) {
|
||||
* // an error happened creating the webview
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
|
||||
* @returns The {@link WebviewWindow} instance to communicate with the window and webview.
|
||||
*/
|
||||
constructor(
|
||||
label: WebviewLabel,
|
||||
options: Omit<WebviewOptions, 'x' | 'y' | 'width' | 'height'> &
|
||||
WindowOptions = {}
|
||||
) {
|
||||
this.label = label
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.listeners = Object.create(null)
|
||||
|
||||
// @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
|
||||
if (!options?.skip) {
|
||||
invoke('plugin:webview|create_webview_window', {
|
||||
options: {
|
||||
...options,
|
||||
label
|
||||
}
|
||||
})
|
||||
.then(async () => this.emit('tauri://created'))
|
||||
.catch(async (e: string) => this.emit('tauri://error', e))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Webview for the webview associated with the given label.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Webview } from '@tauri-apps/api/webview';
|
||||
* const mainWebview = Webview.getByLabel('main');
|
||||
* ```
|
||||
*
|
||||
* @param label The webview label.
|
||||
* @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
|
||||
*/
|
||||
static getByLabel(label: string): WebviewWindow | null {
|
||||
const webview = getAll().find((w) => w.label === label) ?? null
|
||||
if (webview) {
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
return new WebviewWindow(webview.label, { skip: true })
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of `Webview` for the current webview.
|
||||
*/
|
||||
static getCurrent(): WebviewWindow {
|
||||
const webview = getCurrent()
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
return new WebviewWindow(webview.label, { skip: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of instances of `Webview` for all available webviews.
|
||||
*/
|
||||
static getAll(): WebviewWindow[] {
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
return getAll().map((w) => new WebviewWindow(w.label, { skip: true }))
|
||||
}
|
||||
}
|
||||
|
||||
// order matters, we use window APIs by default
|
||||
applyMixins(WebviewWindow, [Webview, Window])
|
||||
|
||||
/** Extends a base class by other specifed classes */
|
||||
function applyMixins(
|
||||
baseClass: { prototype: unknown },
|
||||
extendedClasses: unknown
|
||||
): void {
|
||||
;(Array.isArray(extendedClasses)
|
||||
? extendedClasses
|
||||
: [extendedClasses]
|
||||
).forEach((extendedClass: { prototype: unknown }) => {
|
||||
Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => {
|
||||
Object.defineProperty(
|
||||
baseClass.prototype,
|
||||
name,
|
||||
// eslint-disable-next-line
|
||||
Object.getOwnPropertyDescriptor(extendedClass.prototype, name) ??
|
||||
Object.create(null)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Configuration for the webview to create.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
interface WebviewOptions {
|
||||
/**
|
||||
* Remote URL or local file path to open.
|
||||
*
|
||||
* - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri webview.
|
||||
* - data: URL such as `data:text/html,<html>...` is only supported with the `webview-data-url` Cargo feature for the `tauri` dependency.
|
||||
* - local file path or route such as `/path/to/page.html` or `/users` is appended to the application URL (the devServer URL on development, or `tauri://localhost/` and `https://tauri.localhost/` on production).
|
||||
*/
|
||||
url?: string
|
||||
/** The initial vertical position. */
|
||||
x: number
|
||||
/** The initial horizontal position. */
|
||||
y: number
|
||||
/** The initial width. */
|
||||
width: number
|
||||
/** The initial height. */
|
||||
height: number
|
||||
/**
|
||||
* Whether the webview is transparent or not.
|
||||
* Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > tauri > macOSPrivateApi`.
|
||||
* WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.
|
||||
*/
|
||||
transparent?: boolean
|
||||
/**
|
||||
* Whether the file drop is enabled or not on the webview. By default it is enabled.
|
||||
*
|
||||
* Disabling it is required to use drag and drop on the frontend on Windows.
|
||||
*/
|
||||
fileDropEnabled?: boolean
|
||||
/**
|
||||
* Whether clicking an inactive webview also clicks through to the webview on macOS.
|
||||
*/
|
||||
acceptFirstMouse?: boolean
|
||||
/**
|
||||
* The user agent for the webview.
|
||||
*/
|
||||
userAgent?: string
|
||||
/**
|
||||
* Whether or not the webview should be launched in incognito mode.
|
||||
*
|
||||
* #### Platform-specific
|
||||
*
|
||||
* - **Android:** Unsupported.
|
||||
*/
|
||||
incognito?: boolean
|
||||
}
|
||||
|
||||
export { Webview, WebviewWindow, getCurrent, getAll }
|
||||
|
||||
export type { FileDropEvent, WebviewOptions }
|
||||
@ -22,7 +22,13 @@ import {
|
||||
PhysicalPosition,
|
||||
PhysicalSize
|
||||
} from './dpi'
|
||||
import type { Event, EventName, EventCallback, UnlistenFn } from './event'
|
||||
import type {
|
||||
Event,
|
||||
EventName,
|
||||
EventCallback,
|
||||
UnlistenFn,
|
||||
EventSource
|
||||
} from './event'
|
||||
import { TauriEvent, emit, listen, once } from './event'
|
||||
import { invoke } from './core'
|
||||
|
||||
@ -67,17 +73,6 @@ interface ScaleFactorChanged {
|
||||
size: PhysicalSize
|
||||
}
|
||||
|
||||
interface FileDropPayload {
|
||||
paths: string[]
|
||||
position: PhysicalPosition
|
||||
}
|
||||
|
||||
/** The file drop event types. */
|
||||
type FileDropEvent =
|
||||
| ({ type: 'hover' } & FileDropPayload)
|
||||
| ({ type: 'drop' } & FileDropPayload)
|
||||
| { type: 'cancel' }
|
||||
|
||||
/**
|
||||
* Attention type to request on a window.
|
||||
*
|
||||
@ -101,15 +96,15 @@ enum UserAttentionType {
|
||||
class CloseRequestedEvent {
|
||||
/** Event name */
|
||||
event: EventName
|
||||
/** The label of the window that emitted this event. */
|
||||
windowLabel: string
|
||||
/** The source of the event. */
|
||||
source: EventSource
|
||||
/** Event identifier used to unlisten */
|
||||
id: number
|
||||
private _preventDefault = false
|
||||
|
||||
constructor(event: Event<null>) {
|
||||
this.event = event.event
|
||||
this.windowLabel = event.windowLabel
|
||||
this.source = event.source
|
||||
this.id = event.id
|
||||
}
|
||||
|
||||
@ -229,13 +224,13 @@ function getAll(): Window[] {
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
// events that are emitted right here instead of by the created webview
|
||||
// events that are emitted right here instead of by the created window
|
||||
const localTauriEvents = ['tauri://created', 'tauri://error']
|
||||
/** @ignore */
|
||||
export type WindowLabel = string
|
||||
|
||||
/**
|
||||
* Create new webview window or get a handle to an existing one.
|
||||
* Create new window or get a handle to an existing one.
|
||||
*
|
||||
* Windows are identified by a *label* a unique identifier that can be used to reference it later.
|
||||
* It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.
|
||||
@ -290,8 +285,8 @@ class Window {
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param label The unique webview window label. Must be alphanumeric: `a-zA-Z-/:_`.
|
||||
* @returns The {@link Window} instance to communicate with the webview.
|
||||
* @param label The unique window label. Must be alphanumeric: `a-zA-Z-/:_`.
|
||||
* @returns The {@link Window} instance to communicate with the window.
|
||||
*/
|
||||
constructor(label: WindowLabel, options: WindowOptions = {}) {
|
||||
this.label = label
|
||||
@ -312,22 +307,18 @@ class Window {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Window for the webview associated with the given label.
|
||||
* Gets the Window associated with the given label.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Window } from '@tauri-apps/api/window';
|
||||
* const mainWindow = Window.getByLabel('main');
|
||||
* ```
|
||||
*
|
||||
* @param label The webview window label.
|
||||
* @returns The Window instance to communicate with the webview or null if the webview doesn't exist.
|
||||
* @param label The window label.
|
||||
* @returns The Window instance to communicate with the window or null if the window doesn't exist.
|
||||
*/
|
||||
static getByLabel(label: string): Window | null {
|
||||
if (getAll().some((w) => w.label === label)) {
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
return new Window(label, { skip: true })
|
||||
}
|
||||
return null
|
||||
return getAll().find((w) => w.label === label) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -352,7 +343,7 @@ class Window {
|
||||
* const focusedWindow = Window.getFocusedWindow();
|
||||
* ```
|
||||
*
|
||||
* @returns The Window instance to communicate with the webview or `undefined` if there is not any focused window.
|
||||
* @returns The Window instance or `undefined` if there is not any focused window.
|
||||
*/
|
||||
static async getFocusedWindow(): Promise<Window | null> {
|
||||
for (const w of getAll()) {
|
||||
@ -364,7 +355,7 @@ class Window {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an event emitted by the backend that is tied to the webview window.
|
||||
* Listen to an event emitted by the backend that is tied to the window.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@ -393,11 +384,13 @@ class Window {
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return listen(event, handler, { target: this.label })
|
||||
return listen(event, handler, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an one-off event emitted by the backend that is tied to the webview window.
|
||||
* Listen to an one-off event emitted by the backend that is tied to the window.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@ -423,11 +416,13 @@ class Window {
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return once(event, handler, { target: this.label })
|
||||
return once(event, handler, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the backend, tied to the webview window.
|
||||
* Emits an event to the backend, tied to the window.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/window';
|
||||
@ -441,11 +436,18 @@ class Window {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({ event, id: -1, windowLabel: this.label, payload })
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
source: { kind: 'window', label: this.label },
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emit(event, payload, { target: this.label })
|
||||
return emit(event, payload, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@ -1713,76 +1715,6 @@ class Window {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to a file drop event.
|
||||
* The listener is triggered when the user hovers the selected files on the window,
|
||||
* drops the files or cancels the operation.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from "@tauri-apps/api/window";
|
||||
* const unlisten = await getCurrent().onFileDropEvent((event) => {
|
||||
* if (event.payload.type === 'hover') {
|
||||
* console.log('User hovering', event.payload.paths);
|
||||
* } else if (event.payload.type === 'drop') {
|
||||
* console.log('User dropped', event.payload.paths);
|
||||
* } else {
|
||||
* console.log('File drop cancelled');
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async onFileDropEvent(
|
||||
handler: EventCallback<FileDropEvent>
|
||||
): Promise<UnlistenFn> {
|
||||
const unlistenFileDrop = await this.listen<FileDropPayload>(
|
||||
TauriEvent.WINDOW_FILE_DROP,
|
||||
(event) => {
|
||||
handler({
|
||||
...event,
|
||||
payload: {
|
||||
type: 'drop',
|
||||
paths: event.payload.paths,
|
||||
position: mapPhysicalPosition(event.payload.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const unlistenFileHover = await this.listen<FileDropPayload>(
|
||||
TauriEvent.WINDOW_FILE_DROP_HOVER,
|
||||
(event) => {
|
||||
handler({
|
||||
...event,
|
||||
payload: {
|
||||
type: 'hover',
|
||||
paths: event.payload.paths,
|
||||
position: mapPhysicalPosition(event.payload.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const unlistenCancel = await this.listen<null>(
|
||||
TauriEvent.WINDOW_FILE_DROP_CANCELLED,
|
||||
(event) => {
|
||||
handler({ ...event, payload: { type: 'cancel' } })
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
unlistenFileDrop()
|
||||
unlistenFileHover()
|
||||
unlistenCancel()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the system theme change.
|
||||
*
|
||||
@ -1991,14 +1923,6 @@ interface Effects {
|
||||
* @since 1.0.0
|
||||
*/
|
||||
interface WindowOptions {
|
||||
/**
|
||||
* Remote URL or local file path to open.
|
||||
*
|
||||
* - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri window.
|
||||
* - data: URL such as `data:text/html,<html>...` is only supported with the `window-data-url` Cargo feature for the `tauri` dependency.
|
||||
* - local file path or route such as `/path/to/page.html` or `/users` is appended to the application URL (the devServer URL on development, or `tauri://localhost/` and `https://tauri.localhost/` on production).
|
||||
*/
|
||||
url?: string
|
||||
/** Show window in the center of the screen.. */
|
||||
center?: boolean
|
||||
/** The initial vertical position. Only applies if `y` is also set. */
|
||||
@ -2059,12 +1983,6 @@ interface WindowOptions {
|
||||
* @since 2.0.0
|
||||
*/
|
||||
shadow?: boolean
|
||||
/**
|
||||
* Whether the file drop is enabled or not on the webview. By default it is enabled.
|
||||
*
|
||||
* Disabling it is required to use drag and drop on the frontend on Windows.
|
||||
*/
|
||||
fileDropEnabled?: boolean
|
||||
/**
|
||||
* The initial window theme. Defaults to the system theme.
|
||||
*
|
||||
@ -2079,10 +1997,6 @@ interface WindowOptions {
|
||||
* If `true`, sets the window title to be hidden on macOS.
|
||||
*/
|
||||
hiddenTitle?: boolean
|
||||
/**
|
||||
* Whether clicking an inactive window also clicks through to the webview on macOS.
|
||||
*/
|
||||
acceptFirstMouse?: boolean
|
||||
/**
|
||||
* Defines the window [tabbing identifier](https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier) on macOS.
|
||||
*
|
||||
@ -2090,18 +2004,6 @@ interface WindowOptions {
|
||||
* If the tabbing identifier is not set, automatic tabbing will be disabled.
|
||||
*/
|
||||
tabbingIdentifier?: string
|
||||
/**
|
||||
* The user agent for the webview.
|
||||
*/
|
||||
userAgent?: string
|
||||
/**
|
||||
* Whether or not the webview should be launched in incognito mode.
|
||||
*
|
||||
* #### Platform-specific
|
||||
*
|
||||
* - **Android:** Unsupported.
|
||||
*/
|
||||
incognito?: boolean
|
||||
/**
|
||||
* Whether the window's native maximize button is enabled or not. Defaults to `true`.
|
||||
*/
|
||||
@ -2207,8 +2109,6 @@ export type {
|
||||
Theme,
|
||||
TitleBarStyle,
|
||||
ScaleFactorChanged,
|
||||
FileDropPayload,
|
||||
FileDropEvent,
|
||||
WindowOptions,
|
||||
Color
|
||||
}
|
||||
|
||||
@ -342,7 +342,7 @@
|
||||
"default": "index.html",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WindowUrl"
|
||||
"$ref": "#/definitions/WebviewUrl"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -571,7 +571,7 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"WindowUrl": {
|
||||
"WebviewUrl": {
|
||||
"description": "An URL to open on a Tauri webview window.",
|
||||
"anyOf": [
|
||||
{
|
||||
@ -1451,7 +1451,7 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DmgConfig": {
|
||||
"description": "Configuration for Apple Disk Image (.dmg) bundles.\n\nSee more: https://tauri.app/v1/api/config#dmgconfig",
|
||||
"description": "Configuration for Apple Disk Image (.dmg) bundles.\n\nSee more: <https://tauri.app/v1/api/config#dmgconfig>",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background": {
|
||||
@ -2482,7 +2482,7 @@
|
||||
"description": "The app's external URL, or the path to the directory containing the app assets.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WindowUrl"
|
||||
"$ref": "#/definitions/WebviewUrl"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
helpers::{
|
||||
app_paths::{app_dir, tauri_dir},
|
||||
command_env,
|
||||
config::{get as get_config, AppUrl, HookCommand, WindowUrl, MERGE_CONFIG_EXTENSION_NAME},
|
||||
config::{get as get_config, AppUrl, HookCommand, WebviewUrl, MERGE_CONFIG_EXTENSION_NAME},
|
||||
resolve_merge_config,
|
||||
updater_signature::{read_key_from_file, secret_key as updater_secret_key, sign_file},
|
||||
},
|
||||
@ -270,7 +270,7 @@ pub fn setup(target: Target, options: &mut Options, mobile: bool) -> Result<AppI
|
||||
)?;
|
||||
}
|
||||
|
||||
if let AppUrl::Url(WindowUrl::App(web_asset_path)) = &config_.build.dist_dir {
|
||||
if let AppUrl::Url(WebviewUrl::App(web_asset_path)) = &config_.build.dist_dir {
|
||||
if !web_asset_path.exists() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".",
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
helpers::{
|
||||
app_paths::{app_dir, tauri_dir},
|
||||
command_env,
|
||||
config::{get as get_config, reload as reload_config, AppUrl, BeforeDevCommand, WindowUrl},
|
||||
config::{get as get_config, reload as reload_config, AppUrl, BeforeDevCommand, WebviewUrl},
|
||||
resolve_merge_config,
|
||||
},
|
||||
interface::{AppInterface, DevProcess, ExitReason, Interface},
|
||||
@ -195,13 +195,13 @@ pub fn setup(target: Target, options: &mut Options, mobile: bool) -> Result<AppI
|
||||
if mobile {
|
||||
let local_ip_address = local_ip_address(options.force_ip_prompt).to_string();
|
||||
before_dev = before_dev.replace("$HOST", &local_ip_address);
|
||||
if let AppUrl::Url(WindowUrl::External(url)) = &mut dev_path {
|
||||
if let AppUrl::Url(WebviewUrl::External(url)) = &mut dev_path {
|
||||
url.set_host(Some(&local_ip_address))?;
|
||||
}
|
||||
} else {
|
||||
before_dev = before_dev.replace(
|
||||
"$HOST",
|
||||
if let AppUrl::Url(WindowUrl::External(url)) = &dev_path {
|
||||
if let AppUrl::Url(WebviewUrl::External(url)) = &dev_path {
|
||||
url.host_str().unwrap_or("0.0.0.0")
|
||||
} else {
|
||||
"0.0.0.0"
|
||||
@ -314,7 +314,7 @@ pub fn setup(target: Target, options: &mut Options, mobile: bool) -> Result<AppI
|
||||
.dev_path
|
||||
.clone();
|
||||
if !options.no_dev_server {
|
||||
if let AppUrl::Url(WindowUrl::App(path)) = &dev_path {
|
||||
if let AppUrl::Url(WebviewUrl::App(path)) = &dev_path {
|
||||
use crate::helpers::web_dev_server::start_dev_server;
|
||||
if path.exists() {
|
||||
let path = path.canonicalize()?;
|
||||
@ -325,7 +325,7 @@ pub fn setup(target: Target, options: &mut Options, mobile: bool) -> Result<AppI
|
||||
};
|
||||
let server_url = start_dev_server(path, ip, options.port)?;
|
||||
let server_url = format!("http://{server_url}");
|
||||
dev_path = AppUrl::Url(WindowUrl::External(server_url.parse().unwrap()));
|
||||
dev_path = AppUrl::Url(WebviewUrl::External(server_url.parse().unwrap()));
|
||||
|
||||
if let Some(c) = &options.config {
|
||||
let mut c: tauri_utils::config::Config = serde_json::from_str(c)?;
|
||||
@ -341,7 +341,7 @@ pub fn setup(target: Target, options: &mut Options, mobile: bool) -> Result<AppI
|
||||
}
|
||||
|
||||
if !options.no_dev_server_wait {
|
||||
if let AppUrl::Url(WindowUrl::External(dev_server_url)) = dev_path {
|
||||
if let AppUrl::Url(WebviewUrl::External(dev_server_url)) = dev_path {
|
||||
let host = dev_server_url
|
||||
.host()
|
||||
.unwrap_or_else(|| panic!("No host name in the URL"));
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
helpers::{
|
||||
app_paths::tauri_dir,
|
||||
config::{
|
||||
get as get_config, reload as reload_config, AppUrl, Config as TauriConfig, WindowUrl,
|
||||
get as get_config, reload as reload_config, AppUrl, Config as TauriConfig, WebviewUrl,
|
||||
},
|
||||
},
|
||||
interface::{AppInterface, AppSettings, DevProcess, Interface, Options as InterfaceOptions},
|
||||
@ -168,7 +168,7 @@ fn setup_dev_config(
|
||||
.dev_path
|
||||
.clone();
|
||||
|
||||
if let AppUrl::Url(WindowUrl::External(url)) = &mut dev_path {
|
||||
if let AppUrl::Url(WebviewUrl::External(url)) = &mut dev_path {
|
||||
let localhost = match url.host() {
|
||||
Some(url::Host::Domain(d)) => d == "localhost",
|
||||
Some(url::Host::Ipv4(i)) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user