From 490a6b424e81714524150aef96fbf6cf7004b940 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 11 Mar 2024 13:38:32 -0300 Subject: [PATCH] refactor(core): add setup() to the Assets trait (#9147) * feat(core): allow swapping the assets implemenetation * refactor(core): add setup() to the Assets trait * code review --- .changes/assets-setup.md | 5 ++ .changes/context-assets-runtime-generic.md | 5 ++ .changes/context-remove-assets-mut.md | 5 ++ .changes/utils-remove-asset-trait.md | 5 ++ core/tauri-codegen/src/context.rs | 4 +- core/tauri-plugin/Cargo.toml | 2 +- core/tauri-utils/src/assets.rs | 28 ++++------- core/tauri/src/app.rs | 8 ++-- core/tauri/src/lib.rs | 54 ++++++++++++++++++---- core/tauri/src/manager/mod.rs | 10 ++-- core/tauri/src/pattern.rs | 11 ++--- core/tauri/src/protocol/isolation.rs | 8 ++-- core/tauri/src/test/mod.rs | 16 +++---- 13 files changed, 101 insertions(+), 60 deletions(-) create mode 100644 .changes/assets-setup.md create mode 100644 .changes/context-assets-runtime-generic.md create mode 100644 .changes/context-remove-assets-mut.md create mode 100644 .changes/utils-remove-asset-trait.md diff --git a/.changes/assets-setup.md b/.changes/assets-setup.md new file mode 100644 index 000000000..5d107aae9 --- /dev/null +++ b/.changes/assets-setup.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:feat +--- + +The `Assets` trait now include a `setup` method that lets you run initialization code for your custom asset provider. diff --git a/.changes/context-assets-runtime-generic.md b/.changes/context-assets-runtime-generic.md new file mode 100644 index 000000000..c9c65519d --- /dev/null +++ b/.changes/context-assets-runtime-generic.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +The `Context` struct and the `Assets` trait now takes a `R: Runtime` generic. diff --git a/.changes/context-remove-assets-mut.md b/.changes/context-remove-assets-mut.md new file mode 100644 index 000000000..84db6b346 --- /dev/null +++ b/.changes/context-remove-assets-mut.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +Removed `Context::assets_mut` and added `Context::set_assets`. diff --git a/.changes/utils-remove-asset-trait.md b/.changes/utils-remove-asset-trait.md new file mode 100644 index 000000000..c37e15ae2 --- /dev/null +++ b/.changes/utils-remove-asset-trait.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:breaking +--- + +Removed the `assets::Assets` trait which is now part of the `tauri` crate. diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 97182e2fe..0e6c73d13 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -345,10 +345,10 @@ pub fn context_codegen(data: ContextData) -> Result quote!(#root::Pattern::Brownfield(std::marker::PhantomData)), + PatternKind::Brownfield => quote!(#root::Pattern::Brownfield), #[cfg(not(feature = "isolation"))] PatternKind::Isolation { dir: _ } => { - quote!(#root::Pattern::Brownfield(std::marker::PhantomData)) + quote!(#root::Pattern::Brownfield) } #[cfg(feature = "isolation")] PatternKind::Isolation { dir } => { diff --git a/core/tauri-plugin/Cargo.toml b/core/tauri-plugin/Cargo.toml index d701e3b68..b98c274b5 100644 --- a/core/tauri-plugin/Cargo.toml +++ b/core/tauri-plugin/Cargo.toml @@ -30,7 +30,7 @@ runtime = [ ] [dependencies] anyhow = { version = "1", optional = true } serde = { version = "1", optional = true } -tauri-utils = { version = "2.0.0-beta.8", default-features = false, path = "../tauri-utils" } +tauri-utils = { version = "2.0.0-beta.8", default-features = false, features = [ "build" ], path = "../tauri-utils" } serde_json = { version = "1", optional = true } glob = { version = "0.3", optional = true } toml = { version = "0.8", optional = true } diff --git a/core/tauri-utils/src/assets.rs b/core/tauri-utils/src/assets.rs index 64c844b12..4e85bf56c 100644 --- a/core/tauri-utils/src/assets.rs +++ b/core/tauri-utils/src/assets.rs @@ -104,18 +104,6 @@ impl CspHash<'_> { } } -/// Represents a container of file assets that are retrievable during runtime. -pub trait Assets: Send + Sync + 'static { - /// Get the content of the passed [`AssetKey`]. - fn get(&self, key: &AssetKey) -> Option>; - - /// Iterator for the assets. - fn iter(&self) -> Box + '_>; - - /// Gets the hashes for the CSP tag of the HTML on the given path. - fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_>; -} - /// [`Assets`] implementation that only contains compile-time compressed and embedded assets. #[derive(Debug)] pub struct EmbeddedAssets { @@ -139,11 +127,10 @@ impl EmbeddedAssets { html_hashes, } } -} -impl Assets for EmbeddedAssets { + /// Get an asset by key. #[cfg(feature = "compression")] - fn get(&self, key: &AssetKey) -> Option> { + pub fn get(&self, key: &AssetKey) -> Option> { self .assets .get(key.as_ref()) @@ -157,8 +144,9 @@ impl Assets for EmbeddedAssets { .map(Cow::Owned) } + /// Get an asset by key. #[cfg(not(feature = "compression"))] - fn get(&self, key: &AssetKey) -> Option> { + pub fn get(&self, key: &AssetKey) -> Option> { self .assets .get(key.as_ref()) @@ -166,11 +154,13 @@ impl Assets for EmbeddedAssets { .map(|a| Cow::Owned(a.to_vec())) } - fn iter(&self) -> Box + '_> { - Box::new(self.assets.into_iter()) + /// Iterate on the assets. + pub fn iter(&self) -> Box + '_> { + Box::new(self.assets.into_iter().map(|(k, b)| (*k, *b))) } - fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_> { + /// CSP hashes for the given asset. + pub fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_> { Box::new( self .global_hashes diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index f99e39561..fce30fca9 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -266,7 +266,7 @@ impl AssetResolver { } /// Iterate on all assets. - pub fn iter(&self) -> Box + '_> { + pub fn iter(&self) -> Box + '_> { self.manager.assets.iter() } } @@ -1581,7 +1581,7 @@ tauri::Builder::default() feature = "tracing", tracing::instrument(name = "app::build", skip_all) )] - pub fn build(mut self, context: Context) -> crate::Result> { + pub fn build(mut self, context: Context) -> crate::Result> { #[cfg(target_os = "macos")] if self.menu.is_none() && self.enable_macos_default_menu { self.menu = Some(Box::new(|app_handle| { @@ -1749,7 +1749,7 @@ tauri::Builder::default() } /// Runs the configured Tauri application. - pub fn run(self, context: Context) -> crate::Result<()> { + pub fn run(self, context: Context) -> crate::Result<()> { self.build(context)?.run(|_, _| {}); Ok(()) } @@ -1824,6 +1824,8 @@ fn setup(app: &mut App) -> crate::Result<()> { .build_internal(&window_labels, &webview_labels)?; } + app.manager.assets.setup(app); + if let Some(setup) = app.setup.take() { (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?; } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 4d86a963f..6125c30e0 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -192,10 +192,12 @@ pub type SyncTask = Box; use serde::Serialize; use std::{ + borrow::Cow, collections::HashMap, fmt::{self, Debug}, sync::MutexGuard, }; +use utils::assets::{AssetKey, CspHash, EmbeddedAssets}; #[cfg(feature = "wry")] #[cfg_attr(docsrs, doc(cfg(feature = "wry")))] @@ -224,7 +226,6 @@ pub use { }, self::state::{State, StateManager}, self::utils::{ - assets::Assets, config::{Config, WebviewUrl}, Env, PackageInfo, Theme, }, @@ -338,14 +339,47 @@ pub fn dev() -> bool { !cfg!(feature = "custom-protocol") } +/// Represents a container of file assets that are retrievable during runtime. +pub trait Assets: Send + Sync + 'static { + /// Initialize the asset provider. + fn setup(&self, app: &App) { + let _ = app; + } + + /// Get the content of the passed [`AssetKey`]. + fn get(&self, key: &AssetKey) -> Option>; + + /// Iterator for the assets. + fn iter(&self) -> Box + '_>; + + /// Gets the hashes for the CSP tag of the HTML on the given path. + fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_>; +} + +impl Assets for EmbeddedAssets { + fn get(&self, key: &AssetKey) -> Option> { + EmbeddedAssets::get(self, key) + } + + fn iter(&self) -> Box + '_> { + EmbeddedAssets::iter(self) + } + + fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_> { + EmbeddedAssets::csp_hashes(self, html_path) + } +} + /// User supplied data required inside of a Tauri application. /// /// # Stability /// This is the output of the [`generate_context`] macro, and is not considered part of the stable API. /// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself. -pub struct Context { +#[tauri_macros::default_runtime(Wry, wry)] +pub struct Context { pub(crate) config: Config, - pub(crate) assets: Box, + /// Asset provider. + pub assets: Box>, pub(crate) default_window_icon: Option>, pub(crate) app_icon: Option>, #[cfg(all(desktop, feature = "tray-icon"))] @@ -356,7 +390,7 @@ pub struct Context { pub(crate) runtime_authority: RuntimeAuthority, } -impl fmt::Debug for Context { +impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Context"); d.field("config", &self.config) @@ -372,7 +406,7 @@ impl fmt::Debug for Context { } } -impl Context { +impl Context { /// The config the application was prepared with. #[inline(always)] pub fn config(&self) -> &Config { @@ -387,14 +421,14 @@ impl Context { /// The assets to be served directly by Tauri. #[inline(always)] - pub fn assets(&self) -> &dyn Assets { + pub fn assets(&self) -> &dyn Assets { self.assets.as_ref() } - /// A mutable reference to the assets to be served directly by Tauri. + /// Replace the [`Assets`] implementation and returns the previous value so you can use it as a fallback if desired. #[inline(always)] - pub fn assets_mut(&mut self) -> &mut Box { - &mut self.assets + pub fn set_assets(&mut self, assets: Box>) -> Box> { + std::mem::replace(&mut self.assets, assets) } /// The default window icon Tauri should use when creating windows. @@ -459,7 +493,7 @@ impl Context { #[allow(clippy::too_many_arguments)] pub fn new( config: Config, - assets: Box, + assets: Box>, default_window_icon: Option>, app_icon: Option>, package_info: PackageInfo, diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index 8f2fb515d..13ec77b9c 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -24,8 +24,8 @@ use crate::{ event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority}, plugin::PluginStore, - utils::{assets::Assets, config::Config, PackageInfo}, - Context, Pattern, Runtime, StateManager, Window, + utils::{config::Config, PackageInfo}, + Assets, Context, Pattern, Runtime, StateManager, Window, }; use crate::{event::EmitArgs, resources::ResourceTable, Webview}; @@ -48,7 +48,7 @@ struct CspHashStrings { #[allow(clippy::borrowed_box)] pub(crate) fn set_csp( asset: &mut String, - assets: &impl std::borrow::Borrow, + assets: &impl std::borrow::Borrow>, asset_path: &AssetKey, manager: &AppManager, csp: Csp, @@ -179,7 +179,7 @@ pub struct AppManager { pub listeners: Listeners, pub state: Arc, pub config: Config, - pub assets: Box, + pub assets: Box>, pub app_icon: Option>, @@ -216,7 +216,7 @@ impl fmt::Debug for AppManager { impl AppManager { #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn with_handlers( - #[allow(unused_mut)] mut context: Context, + #[allow(unused_mut)] mut context: Context, plugins: PluginStore, invoke_handler: Box>, on_page_load: Option>>, diff --git a/core/tauri/src/pattern.rs b/core/tauri/src/pattern.rs index de2eb8c0b..221fce9fb 100644 --- a/core/tauri/src/pattern.rs +++ b/core/tauri/src/pattern.rs @@ -2,28 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::marker::PhantomData; #[cfg(feature = "isolation")] use std::sync::Arc; use serde::Serialize; use serialize_to_javascript::{default_template, Template}; -use tauri_utils::assets::{Assets, EmbeddedAssets}; - /// The domain of the isolation iframe source. pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost"; /// An application pattern. #[derive(Debug)] -pub enum Pattern { +pub enum Pattern { /// The brownfield pattern. - Brownfield(PhantomData), + Brownfield, /// Isolation pattern. Recommended for security purposes. #[cfg(feature = "isolation")] Isolation { /// The HTML served on `isolation://index.html`. - assets: Arc, + assets: Arc, /// The schema used for the isolation frames. schema: String, @@ -55,7 +52,7 @@ pub(crate) enum PatternObject { impl From<&Pattern> for PatternObject { fn from(pattern: &Pattern) -> Self { match pattern { - Pattern::Brownfield(_) => Self::Brownfield, + Pattern::Brownfield => Self::Brownfield, #[cfg(feature = "isolation")] Pattern::Isolation { .. } => Self::Isolation { side: IsolationSide::default(), diff --git a/core/tauri/src/protocol/isolation.rs b/core/tauri/src/protocol/isolation.rs index 918e26d57..62206a87e 100644 --- a/core/tauri/src/protocol/isolation.rs +++ b/core/tauri/src/protocol/isolation.rs @@ -2,12 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use crate::Assets; use http::header::CONTENT_TYPE; use serialize_to_javascript::Template; -use tauri_utils::{ - assets::{Assets, EmbeddedAssets}, - config::Csp, -}; +use tauri_utils::{assets::EmbeddedAssets, config::Csp}; use std::sync::Arc; @@ -29,7 +27,7 @@ pub fn get( format!("{schema}:") }; - let assets = assets as Arc; + let assets = assets as Arc>; Box::new(move |request, responder| { let response = match request_to_path(&request).as_str() { diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index bb09df1e8..ce7bbad9c 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -57,27 +57,27 @@ use std::{borrow::Cow, collections::HashMap, fmt::Debug}; use crate::{ ipc::{InvokeBody, InvokeError, InvokeResponse, RuntimeAuthority}, webview::InvokeRequest, - App, Builder, Context, Pattern, Webview, + App, Assets, Builder, Context, Pattern, Runtime, Webview, }; use tauri_utils::{ acl::resolved::Resolved, - assets::{AssetKey, Assets, CspHash}, + assets::{AssetKey, CspHash}, config::{AppConfig, Config}, }; /// An empty [`Assets`] implementation. pub struct NoopAsset { - assets: HashMap<&'static str, &'static [u8]>, + assets: HashMap>, csp_hashes: Vec>, } -impl Assets for NoopAsset { +impl Assets for NoopAsset { fn get(&self, key: &AssetKey) -> Option> { None } - fn iter(&self) -> Box + '_> { - Box::new(self.assets.iter()) + fn iter(&self) -> Box + '_> { + Box::new(self.assets.iter().map(|(k, b)| (k.as_str(), b.as_slice()))) } fn csp_hashes(&self, html_path: &AssetKey) -> Box> + '_> { @@ -94,7 +94,7 @@ pub fn noop_assets() -> NoopAsset { } /// Creates a new [`crate::Context`] for testing. -pub fn mock_context(assets: A) -> crate::Context { +pub fn mock_context>(assets: A) -> crate::Context { Context { config: Config { schema: None, @@ -125,7 +125,7 @@ pub fn mock_context(assets: A) -> crate::Context { crate_name: "test", }, _info_plist: (), - pattern: Pattern::Brownfield(std::marker::PhantomData), + pattern: Pattern::Brownfield, runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()), } }