Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Lucas Nogueira 2022-03-06 10:16:28 -03:00
commit 9e31dd5ccb
No known key found for this signature in database
GPG Key ID: FFEA6C72E73482F1
10 changed files with 256 additions and 84 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Added `WindowBuilder::on_web_resource_request`, which allows customizing the tauri custom protocol response.

View File

@ -219,10 +219,10 @@ struct HttpRequestWrapper(HttpRequest);
impl From<&WryHttpRequest> for HttpRequestWrapper {
fn from(req: &WryHttpRequest) -> Self {
Self(HttpRequest {
body: req.body.clone(),
head: HttpRequestPartsWrapper::from(req.head.clone()).0,
})
Self(HttpRequest::new_internal(
HttpRequestPartsWrapper::from(req.head.clone()).0,
req.body.clone(),
))
}
}
@ -242,9 +242,10 @@ impl From<HttpResponseParts> for HttpResponsePartsWrapper {
struct HttpResponseWrapper(WryHttpResponse);
impl From<HttpResponse> for HttpResponseWrapper {
fn from(response: HttpResponse) -> Self {
let (parts, body) = response.into_parts();
Self(WryHttpResponse {
body: response.body,
head: HttpResponsePartsWrapper::from(response.head).0,
body,
head: HttpResponsePartsWrapper::from(parts).0,
})
}
}
@ -1466,6 +1467,26 @@ pub struct Wry {
tray_context: TrayContext,
}
impl fmt::Debug for Wry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Wry");
d.field("main_thread_id", &self.main_thread_id)
.field("global_shortcut_manager", &self.global_shortcut_manager)
.field(
"global_shortcut_manager_handle",
&self.global_shortcut_manager_handle,
)
.field("clipboard_manager", &self.clipboard_manager)
.field("clipboard_manager_handle", &self.clipboard_manager_handle)
.field("event_loop", &self.event_loop)
.field("windows", &self.windows)
.field("web_context", &self.web_context);
#[cfg(feature = "system-tray")]
d.field("tray_context", &self.tray_context);
d.finish()
}
}
/// A handle to the Wry runtime.
#[derive(Debug, Clone)]
pub struct WryHandle {

View File

@ -17,8 +17,8 @@ use super::{
///
/// - **Linux:** Headers are not exposed.
pub struct Request {
pub head: RequestParts,
pub body: Vec<u8>,
head: RequestParts,
body: Vec<u8>,
}
/// Component parts of an HTTP `Request`
@ -47,6 +47,17 @@ impl Request {
}
}
/// Creates a new `Request` with the given head and body.
///
/// # Stability
///
/// This API is used internally. It may have breaking changes in the future.
#[inline]
#[doc(hidden)]
pub fn new_internal(head: RequestParts, body: Vec<u8>) -> Request {
Request { head, body }
}
/// Returns a reference to the associated HTTP method.
#[inline]
pub fn method(&self) -> &Method {
@ -72,6 +83,10 @@ impl Request {
}
/// Consumes the request returning the head and body RequestParts.
///
/// # Stability
///
/// This API is used internally. It may have breaking changes in the future.
#[inline]
pub fn into_parts(self) -> (RequestParts, Vec<u8>) {
(self.head, self.body)

View File

@ -32,8 +32,8 @@ type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
/// ```
///
pub struct Response {
pub head: ResponseParts,
pub body: Vec<u8>,
head: ResponseParts,
body: Vec<u8>,
}
/// Component parts of an HTTP `Response`
@ -42,16 +42,16 @@ pub struct Response {
/// header fields.
#[derive(Clone)]
pub struct ResponseParts {
/// The response's status
/// The response's status.
pub status: StatusCode,
/// The response's version
/// The response's version.
pub version: Version,
/// The response's headers
/// The response's headers.
pub headers: HeaderMap<HeaderValue>,
/// The response's mimetype type
/// The response's mimetype type.
pub mimetype: Option<String>,
}
@ -74,16 +74,39 @@ impl Response {
}
}
/// Returns the `StatusCode`.
/// Consumes the response returning the head and body ResponseParts.
///
/// # Stability
///
/// This API is used internally. It may have breaking changes in the future.
#[inline]
#[doc(hidden)]
pub fn into_parts(self) -> (ResponseParts, Vec<u8>) {
(self.head, self.body)
}
/// Sets the status code.
#[inline]
pub fn set_status(&mut self, status: StatusCode) {
self.head.status = status;
}
/// Returns the [`StatusCode`].
#[inline]
pub fn status(&self) -> StatusCode {
self.head.status
}
/// Sets the mimetype.
#[inline]
pub fn set_mimetype(&mut self, mimetype: Option<String>) {
self.head.mimetype = mimetype;
}
/// Returns a reference to the mime type.
#[inline]
pub fn mimetype(&self) -> Option<String> {
self.head.mimetype.clone()
pub fn mimetype(&self) -> Option<&String> {
self.head.mimetype.as_ref()
}
/// Returns a reference to the associated version.
@ -92,12 +115,24 @@ impl Response {
self.head.version
}
/// Returns a mutable reference to the associated header field map.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap<HeaderValue> {
&mut self.head.headers
}
/// Returns a reference to the associated header field map.
#[inline]
pub fn headers(&self) -> &HeaderMap<HeaderValue> {
&self.head.headers
}
/// Returns a mutable reference to the associated HTTP body.
#[inline]
pub fn body_mut(&mut self) -> &mut Vec<u8> {
&mut self.body
}
/// Returns a reference to the associated HTTP body.
#[inline]
pub fn body(&self) -> &Vec<u8> {

View File

@ -311,7 +311,7 @@ pub trait ClipboardManager: Debug + Clone + Send + Sync {
}
/// The webview runtime interface.
pub trait Runtime: Sized + 'static {
pub trait Runtime: Debug + Sized + 'static {
/// The message dispatcher.
type Dispatcher: Dispatch<Runtime = Self>;
/// The runtime handle type.

View File

@ -23,6 +23,7 @@ use crate::{
sealed::{ManagerBase, RuntimeOrDispatch},
utils::config::{Config, WindowUrl},
utils::{assets::Assets, Env},
window::WindowBuilder,
Context, Invoke, InvokeError, InvokeResponse, Manager, Scopes, StateManager, Window,
};
@ -386,15 +387,12 @@ macro_rules! shared_app_impl {
WebviewAttributes,
),
{
let (window_builder, webview_attributes) = setup(
<R::Dispatcher as Dispatch>::WindowBuilder::new(),
WebviewAttributes::new(url),
);
self.create_new_window(PendingWindow::new(
window_builder,
webview_attributes,
label,
)?)
let mut builder = WindowBuilder::<R>::new(self, label, url);
let (window_builder, webview_attributes) =
setup(builder.window_builder, builder.webview_attributes);
builder.window_builder = window_builder;
builder.webview_attributes = webview_attributes;
builder.build()
}
#[cfg(feature = "system-tray")]
@ -1310,9 +1308,10 @@ impl<R: Runtime> Builder<R> {
let mut main_window = None;
for pending in self.pending_windows {
let pending = app
.manager
.prepare_window(app.handle.clone(), pending, &window_labels)?;
let pending =
app
.manager
.prepare_window(app.handle.clone(), pending, &window_labels, None)?;
let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
let _window = app.manager.attach_window(app.handle(), detached);
#[cfg(feature = "updater")]

View File

@ -174,7 +174,6 @@ pub type Result<T> = std::result::Result<T, Error>;
/// A task to run on the main thread.
pub type SyncTask = Box<dyn FnOnce() + Send>;
use crate::runtime::window::PendingWindow;
use serde::Serialize;
use std::{collections::HashMap, fmt, sync::Arc};
@ -600,7 +599,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
/// Prevent implementation details from leaking out of the [`Manager`] trait.
pub(crate) mod sealed {
use crate::{app::AppHandle, manager::WindowManager};
use tauri_runtime::{Runtime, RuntimeHandle};
use tauri_runtime::Runtime;
/// A running [`Runtime`] or a dispatcher to it.
pub enum RuntimeOrDispatch<'r, R: Runtime> {
@ -614,51 +613,12 @@ pub(crate) mod sealed {
Dispatch(R::Dispatcher),
}
#[derive(Clone, serde::Serialize)]
struct WindowCreatedEvent {
label: String,
}
/// Managed handle to the application runtime.
pub trait ManagerBase<R: Runtime> {
/// The manager behind the [`Managed`] item.
fn manager(&self) -> &WindowManager<R>;
fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
fn managed_app_handle(&self) -> AppHandle<R>;
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
fn create_new_window(
&self,
pending: crate::PendingWindow<R>,
) -> crate::Result<crate::Window<R>> {
use crate::runtime::Dispatch;
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
let pending = self
.manager()
.prepare_window(self.managed_app_handle(), pending, &labels)?;
let window = match self.runtime() {
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending),
RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
}
.map(|window| {
self
.manager()
.attach_window(self.managed_app_handle(), window)
})?;
self.manager().emit_filter(
"tauri://window-created",
None,
Some(WindowCreatedEvent {
label: window.label().into(),
}),
|w| w != &window,
)?;
Ok(window)
}
}
}

View File

@ -129,11 +129,16 @@ fn set_csp<R: Runtime>(
let csp = Csp::DirectiveMap(csp).to_string();
#[cfg(target_os = "linux")]
{
*asset = asset.replacen(tauri_utils::html::CSP_TOKEN, &csp, 1);
*asset = set_html_csp(asset, &csp);
}
csp
}
#[cfg(target_os = "linux")]
fn set_html_csp(html: &str, csp: &str) -> String {
html.replacen(tauri_utils::html::CSP_TOKEN, csp, 1)
}
// inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297
fn replace_with_callback<F: FnMut() -> String>(
original: &str,
@ -383,6 +388,9 @@ impl<R: Runtime> WindowManager<R> {
label: &str,
window_labels: &[String],
app_handle: AppHandle<R>,
web_resource_request_handler: Option<
Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
>,
) -> crate::Result<PendingWindow<R>> {
let is_init_global = self.inner.config.build.with_global_tauri;
let plugin_init = self
@ -470,7 +478,10 @@ impl<R: Runtime> WindowManager<R> {
}
if !registered_scheme_protocols.contains(&"tauri".into()) {
pending.register_uri_scheme_protocol("tauri", self.prepare_uri_scheme_protocol());
pending.register_uri_scheme_protocol(
"tauri",
self.prepare_uri_scheme_protocol(web_resource_request_handler),
);
registered_scheme_protocols.push("tauri".into());
}
@ -788,6 +799,9 @@ impl<R: Runtime> WindowManager<R> {
#[allow(clippy::type_complexity)]
fn prepare_uri_scheme_protocol(
&self,
web_resource_request_handler: Option<
Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
>,
) -> Box<dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync>
{
let manager = self.clone();
@ -801,11 +815,28 @@ impl<R: Runtime> WindowManager<R> {
.to_string()
.replace("tauri://localhost", "");
let asset = manager.get_asset(path)?;
let mut response = HttpResponseBuilder::new().mimetype(&asset.mime_type);
if let Some(csp) = asset.csp_header {
response = response.header("Content-Security-Policy", csp);
let mut builder = HttpResponseBuilder::new().mimetype(&asset.mime_type);
if let Some(csp) = &asset.csp_header {
builder = builder.header("Content-Security-Policy", csp);
}
response.body(asset.bytes)
let mut response = builder.body(asset.bytes)?;
if let Some(handler) = &web_resource_request_handler {
handler(request, &mut response);
// if it's an HTML file, we need to set the CSP meta tag on Linux
#[cfg(target_os = "linux")]
if let (Some(original_csp), Some(response_csp)) = (
asset.csp_header,
response.headers().get("Content-Security_Policy"),
) {
let response_csp = String::from_utf8_lossy(response_csp.as_bytes());
if response_csp != original_csp {
let body = set_html_csp(&String::from_utf8_lossy(response.body()), &response_csp);
*response.body_mut() = body.as_bytes().to_vec();
}
}
}
Ok(response)
})
}
@ -990,6 +1021,9 @@ impl<R: Runtime> WindowManager<R> {
app_handle: AppHandle<R>,
mut pending: PendingWindow<R>,
window_labels: &[String],
web_resource_request_handler: Option<
Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
>,
) -> crate::Result<PendingWindow<R>> {
if self.windows_lock().contains_key(&pending.label) {
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
@ -1043,7 +1077,13 @@ impl<R: Runtime> WindowManager<R> {
if is_local {
let label = pending.label.clone();
pending = self.prepare_pending_window(pending, &label, window_labels, app_handle.clone())?;
pending = self.prepare_pending_window(
pending,
&label,
window_labels,
app_handle.clone(),
web_resource_request_handler,
)?;
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle.clone()));
}

View File

@ -489,6 +489,7 @@ impl TrayHandle for MockTrayHandler {
}
}
#[derive(Debug)]
pub struct MockRuntime {
pub context: RuntimeContext,
global_shortcut_manager: MockGlobalShortcutManager,

View File

@ -15,6 +15,7 @@ use crate::{
hooks::{InvokePayload, InvokeResponder},
manager::WindowManager,
runtime::{
http::{Request as HttpRequest, Response as HttpResponse},
menu::Menu,
monitor::Monitor as RuntimeMonitor,
webview::{WebviewAttributes, WindowBuilder as _},
@ -22,7 +23,7 @@ use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
},
Dispatch, Runtime, UserAttentionType,
Dispatch, Runtime, RuntimeHandle, UserAttentionType,
},
sealed::ManagerBase,
sealed::RuntimeOrDispatch,
@ -37,11 +38,17 @@ use windows::Win32::Foundation::HWND;
use tauri_macros::default_runtime;
use std::{
fmt,
hash::{Hash, Hasher},
path::PathBuf,
sync::Arc,
};
#[derive(Clone, Serialize)]
struct WindowCreatedEvent {
label: String,
}
/// Monitor descriptor.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
@ -98,14 +105,27 @@ pub enum RuntimeHandleOrDispatch<R: Runtime> {
/// A builder for a webview window managed by Tauri.
#[default_runtime(crate::Wry, wry)]
#[derive(Debug)]
pub struct WindowBuilder<R: Runtime> {
manager: WindowManager<R>,
runtime: RuntimeHandleOrDispatch<R>,
app_handle: AppHandle<R>,
label: String,
pub(crate) window_builder: <R::Dispatcher as Dispatch>::WindowBuilder,
webview_attributes: WebviewAttributes,
pub(crate) webview_attributes: WebviewAttributes,
web_resource_request_handler: Option<Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>>,
}
impl<R: Runtime> fmt::Debug for WindowBuilder<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowBuilder")
.field("manager", &self.manager)
.field("runtime", &self.runtime)
.field("app_handle", &self.app_handle)
.field("label", &self.label)
.field("window_builder", &self.window_builder)
.field("webview_attributes", &self.webview_attributes)
.finish()
}
}
impl<R: Runtime> ManagerBase<R> for WindowBuilder<R> {
@ -141,16 +161,92 @@ impl<R: Runtime> WindowBuilder<R> {
label: label.into(),
window_builder: <R::Dispatcher as Dispatch>::WindowBuilder::new(),
webview_attributes: WebviewAttributes::new(url),
web_resource_request_handler: None,
}
}
/// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.
///
/// Currently only implemented for the `tauri` URI protocol.
///
/// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,
/// but it might be implemented in the future. **Always** check the request URL.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::{
/// utils::config::{Csp, CspDirectiveSources, WindowUrl},
/// runtime::http::header::HeaderValue,
/// window::WindowBuilder,
/// };
/// use std::collections::HashMap;
/// tauri::Builder::default()
/// .setup(|app| {
/// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into()))
/// .on_web_resource_request(|request, response| {
/// if request.uri().starts_with("tauri://") {
/// // if we have a CSP header, Tauri is loading an HTML file
/// // for this example, let's dynamically change the CSP
/// if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
/// // use the tauri helper to parse the CSP policy to a map
/// let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
/// csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
/// // use the tauri helper to get a CSP string from the map
/// let csp_string = Csp::from(csp_map).to_string();
/// *csp = HeaderValue::from_str(&csp_string).unwrap();
/// }
/// }
/// })
/// .build()
/// .unwrap();
/// Ok(())
/// });
/// ```
pub fn on_web_resource_request<F: Fn(&HttpRequest, &mut HttpResponse) + Send + Sync + 'static>(
mut self,
f: F,
) -> Self {
self.web_resource_request_handler.replace(Box::new(f));
self
}
/// Creates a new webview window.
pub fn build(self) -> crate::Result<Window<R>> {
self.create_new_window(PendingWindow::new(
pub fn build(mut self) -> crate::Result<Window<R>> {
let web_resource_request_handler = self.web_resource_request_handler.take();
let pending = PendingWindow::new(
self.window_builder.clone(),
self.webview_attributes.clone(),
self.label.clone(),
)?)
)?;
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
let pending = self.manager().prepare_window(
self.managed_app_handle(),
pending,
&labels,
web_resource_request_handler,
)?;
let window = match self.runtime() {
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending),
RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
}
.map(|window| {
self
.manager()
.attach_window(self.managed_app_handle(), window)
})?;
self.manager().emit_filter(
"tauri://window-created",
None,
Some(WindowCreatedEvent {
label: window.label().into(),
}),
|w| w != &window,
)?;
Ok(window)
}
// --------------------------------------------- Window builder ---------------------------------------------