Browse Source

refactor: update to wry 0.9 (#1630)

Lucas Fernandes Nogueira 4 years ago
parent
commit
c31f0978c5

+ 5 - 0
.changes/create-window-refactor.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+The `create_window` API callback now takes two arguments: the `WindowBuilder` and the `WebviewAttributes` and must return a tuple containing both values.

+ 5 - 0
.changes/drag-window-api.md

@@ -0,0 +1,5 @@
+---
+"api": patch
+---
+
+Adds `startDragging` API on the window module.

+ 5 - 0
.changes/window-attributes-rename.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Rename `Attributes` to `WindowBuilder`.

+ 5 - 0
.changes/wry-update.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Update `wry` to v0.9.

+ 2 - 1
core/tauri/Cargo.toml

@@ -24,7 +24,7 @@ thiserror = "1.0.24"
 once_cell = "1.7.2"
 tauri-macros = { version = "1.0.0-beta-rc.1", path = "../tauri-macros" }
 tauri-utils = { version = "1.0.0-beta-rc.1", path = "../tauri-utils" }
-wry = "0.8"
+wry = "0.9"
 rand = "0.8"
 reqwest = { version = "0.11", features = [ "json", "multipart" ] }
 tempfile = "3"
@@ -47,6 +47,7 @@ open = "1.7.0"
 shared_child = "0.3"
 os_pipe = "0.9"
 minisign-verify = "0.1.8"
+image = "0.23"
 
 [build-dependencies]
 cfg_aliases = "0.1.1"

+ 12 - 0
core/tauri/scripts/core.js

@@ -187,6 +187,18 @@ if (!String.prototype.startsWith) {
     );
   }
 
+  // drag region
+  document.addEventListener('mousedown', (e) => {
+    if (e.target.classList.contains('drag-region') && e.buttons === 1) {
+      window.__TAURI__.invoke('tauri', {
+        __tauriModule: "Window",
+        message: {
+          cmd: "startDragging",
+        }
+      })
+    }
+  })
+
   window.__TAURI__.invoke('tauri', {
     __tauriModule: "Event",
     message: {

+ 7 - 3
core/tauri/src/endpoints/window.rs

@@ -89,6 +89,7 @@ pub enum Cmd {
   SetIcon {
     icon: IconDto,
   },
+  StartDragging,
 }
 
 #[cfg(window_create)]
@@ -122,9 +123,11 @@ impl Cmd {
           });
 
           let url = options.url.clone();
-          let pending =
-            crate::runtime::window::PendingWindow::with_config(options, label.clone(), url);
-
+          let pending = crate::runtime::window::PendingWindow::with_config(
+            options,
+            crate::runtime::webview::WebviewAttributes::new(url),
+            label.clone(),
+          );
           window.create_window(pending)?.emit_others(
             &crate::runtime::manager::tauri_event::<P::Event>("tauri://window-created"),
             Some(WindowCreatedEvent {
@@ -160,6 +163,7 @@ impl Cmd {
         Self::SetPosition { x, y } => window.set_position(x, y)?,
         Self::SetFullscreen { fullscreen } => window.set_fullscreen(fullscreen)?,
         Self::SetIcon { icon } => window.set_icon(icon.into())?,
+        Self::StartDragging => window.start_dragging()?,
       }
       Ok(().into())
     }

+ 2 - 1
core/tauri/src/lib.rs

@@ -52,7 +52,7 @@ pub use {
   hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
   runtime::app::{App, Builder},
   runtime::flavors::wry::Wry,
-  runtime::webview::Attributes,
+  runtime::webview::{WebviewAttributes, WindowBuilder},
   runtime::window::export::Window,
 };
 
@@ -92,6 +92,7 @@ macro_rules! tauri_build_context {
 }
 
 /// A icon definition.
+#[derive(Debug, Clone)]
 pub enum Icon {
   /// Icon from file path.
   File(PathBuf),

+ 22 - 9
core/tauri/src/runtime/app.rs

@@ -10,7 +10,7 @@ use crate::{
     flavors::wry::Wry,
     manager::{Args, WindowManager},
     tag::Tag,
-    webview::{Attributes, CustomProtocol},
+    webview::{CustomProtocol, WebviewAttributes, WindowBuilder},
     window::PendingWindow,
     Dispatch, Runtime,
   },
@@ -183,12 +183,23 @@ where
   /// Creates a new webview.
   pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
   where
-    F: FnOnce(<R::Dispatcher as Dispatch>::Attributes) -> <R::Dispatcher as Dispatch>::Attributes,
+    F: FnOnce(
+      <R::Dispatcher as Dispatch>::WindowBuilder,
+      WebviewAttributes,
+    ) -> (
+      <R::Dispatcher as Dispatch>::WindowBuilder,
+      WebviewAttributes,
+    ),
   {
-    let attributes = setup(<R::Dispatcher as Dispatch>::Attributes::new());
-    self
-      .pending_windows
-      .push(PendingWindow::new(attributes, label, url));
+    let (window_attributes, webview_attributes) = setup(
+      <R::Dispatcher as Dispatch>::WindowBuilder::new(),
+      WebviewAttributes::new(url),
+    );
+    self.pending_windows.push(PendingWindow::new(
+      window_attributes,
+      webview_attributes,
+      label,
+    ));
     self
   }
 
@@ -236,9 +247,11 @@ where
         .parse()
         .unwrap_or_else(|_| panic!("bad label found in config: {}", config.label));
 
-      self
-        .pending_windows
-        .push(PendingWindow::with_config(config, label, url));
+      self.pending_windows.push(PendingWindow::with_config(
+        config,
+        WebviewAttributes::new(url),
+        label,
+      ));
     }
 
     manager.initialize_plugins()?;

+ 452 - 284
core/tauri/src/runtime/flavors/wry.rs

@@ -8,58 +8,87 @@ use crate::{
   api::config::WindowConfig,
   runtime::{
     webview::{
-      Attributes, AttributesBase, FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler,
+      FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler, WindowBuilder,
+      WindowBuilderBase,
     },
     window::{DetachedWindow, PendingWindow},
     Dispatch, Params, Runtime,
   },
   Icon,
 };
+
+use image::{GenericImageView, Pixel};
+use wry::{
+  application::{
+    dpi::{LogicalPosition, LogicalSize, Size},
+    event::{Event, WindowEvent},
+    event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget},
+    window::{Fullscreen, Icon as WindowIcon, Window, WindowBuilder as WryWindowBuilder, WindowId},
+  },
+  webview::{
+    FileDropEvent as WryFileDropEvent, RpcRequest as WryRpcRequest, RpcResponse, WebView,
+    WebViewBuilder,
+  },
+};
+
 use std::{
   collections::HashMap,
   convert::TryFrom,
-  path::PathBuf,
-  sync::{Arc, Mutex},
+  sync::{
+    mpsc::{channel, Sender},
+    Arc, Mutex,
+  },
 };
 
-/// Wrapper around a [`wry::Icon`] that can be created from an [`Icon`].
-pub struct WryIcon(wry::Icon);
+type CreateWebviewHandler =
+  Box<dyn FnOnce(&EventLoopWindowTarget<Message>) -> crate::Result<WebView> + Send>;
+
+#[repr(C)]
+#[derive(Debug)]
+struct PixelValue {
+  r: u8,
+  g: u8,
+  b: u8,
+  a: u8,
+}
+
+const PIXEL_SIZE: usize = std::mem::size_of::<PixelValue>();
+
+/// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
+pub struct WryIcon(WindowIcon);
 
 impl TryFrom<Icon> for WryIcon {
   type Error = crate::Error;
   fn try_from(icon: Icon) -> Result<Self, Self::Error> {
-    let icon = match icon {
+    let image = match icon {
       Icon::File(path) => {
-        wry::Icon::from_file(path).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
+        image::open(path).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
       }
       Icon::Raw(raw) => {
-        wry::Icon::from_bytes(raw).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
+        image::load_from_memory(&raw).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
       }
     };
+    let (width, height) = image.dimensions();
+    let mut rgba = Vec::with_capacity((width * height) as usize * PIXEL_SIZE);
+    for (_, _, pixel) in image.pixels() {
+      rgba.extend_from_slice(&pixel.to_rgba().0);
+    }
+    let icon = WindowIcon::from_rgba(rgba, width, height)
+      .map_err(|e| crate::Error::InvalidIcon(e.to_string()))?;
     Ok(Self(icon))
   }
 }
 
-/// Wry attributes.
-#[derive(Default, Clone)]
-pub struct WryAttributes {
-  attributes: wry::Attributes,
-  uri_scheme_protocols: Arc<Mutex<HashMap<String, wry::CustomProtocol>>>,
-}
-
-impl AttributesBase for WryAttributes {}
-impl Attributes for WryAttributes {
-  type Icon = WryIcon;
-
+impl WindowBuilderBase for WryWindowBuilder {}
+impl WindowBuilder for WryWindowBuilder {
   fn new() -> Self {
     Default::default()
   }
 
   fn with_config(config: WindowConfig) -> Self {
-    let mut webview = WryAttributes::default()
+    let mut window = WryWindowBuilder::new()
       .title(config.title.to_string())
-      .width(config.width)
-      .height(config.height)
+      .inner_size(config.width, config.height)
       .visible(config.visible)
       .resizable(config.resizable)
       .decorations(config.decorations)
@@ -68,165 +97,82 @@ impl Attributes for WryAttributes {
       .transparent(config.transparent)
       .always_on_top(config.always_on_top);
 
-    if let Some(min_width) = config.min_width {
-      webview = webview.min_width(min_width);
-    }
-    if let Some(min_height) = config.min_height {
-      webview = webview.min_height(min_height);
-    }
-    if let Some(max_width) = config.max_width {
-      webview = webview.max_width(max_width);
-    }
-    if let Some(max_height) = config.max_height {
-      webview = webview.max_height(max_height);
+    if let (Some(min_width), Some(min_height)) = (config.min_width, config.min_height) {
+      window = window.min_inner_size(min_width, min_height);
     }
-    if let Some(x) = config.x {
-      webview = webview.x(x);
+    if let (Some(max_width), Some(max_height)) = (config.max_width, config.max_height) {
+      window = window.max_inner_size(max_width, max_height);
     }
-    if let Some(y) = config.y {
-      webview = webview.y(y);
+    if let (Some(x), Some(y)) = (config.x, config.y) {
+      window = window.position(x, y);
     }
 
-    webview
-  }
-
-  fn initialization_script(mut self, init: &str) -> Self {
-    self
-      .attributes
-      .initialization_scripts
-      .push(init.to_string());
-    self
-  }
-
-  fn x(mut self, x: f64) -> Self {
-    self.attributes.x = Some(x);
-    self
-  }
-
-  fn y(mut self, y: f64) -> Self {
-    self.attributes.y = Some(y);
-    self
-  }
-
-  fn width(mut self, width: f64) -> Self {
-    self.attributes.width = width;
-    self
-  }
-
-  fn height(mut self, height: f64) -> Self {
-    self.attributes.height = height;
-    self
+    window
   }
 
-  fn min_width(mut self, min_width: f64) -> Self {
-    self.attributes.min_width = Some(min_width);
-    self
+  fn position(self, x: f64, y: f64) -> Self {
+    self.with_position(LogicalPosition::new(x, y))
   }
 
-  fn min_height(mut self, min_height: f64) -> Self {
-    self.attributes.min_height = Some(min_height);
-    self
+  fn inner_size(self, width: f64, height: f64) -> Self {
+    self.with_inner_size(LogicalSize::new(width, height))
   }
 
-  fn max_width(mut self, max_width: f64) -> Self {
-    self.attributes.max_width = Some(max_width);
-    self
+  fn min_inner_size(self, min_width: f64, min_height: f64) -> Self {
+    self.with_min_inner_size(Size::new(LogicalSize::new(min_width, min_height)))
   }
 
-  fn max_height(mut self, max_height: f64) -> Self {
-    self.attributes.max_height = Some(max_height);
-    self
+  fn max_inner_size(self, max_width: f64, max_height: f64) -> Self {
+    self.with_max_inner_size(Size::new(LogicalSize::new(max_width, max_height)))
   }
 
-  fn resizable(mut self, resizable: bool) -> Self {
-    self.attributes.resizable = resizable;
-    self
+  fn resizable(self, resizable: bool) -> Self {
+    self.with_resizable(resizable)
   }
 
-  fn title<S: Into<String>>(mut self, title: S) -> Self {
-    self.attributes.title = title.into();
-    self
+  fn title<S: Into<String>>(self, title: S) -> Self {
+    self.with_title(title.into())
   }
 
-  fn fullscreen(mut self, fullscreen: bool) -> Self {
-    self.attributes.fullscreen = fullscreen;
-    self
+  fn fullscreen(self, fullscreen: bool) -> Self {
+    if fullscreen {
+      self.with_fullscreen(Some(Fullscreen::Borderless(None)))
+    } else {
+      self.with_fullscreen(None)
+    }
   }
 
-  fn maximized(mut self, maximized: bool) -> Self {
-    self.attributes.maximized = maximized;
-    self
+  fn maximized(self, maximized: bool) -> Self {
+    self.with_maximized(maximized)
   }
 
-  fn visible(mut self, visible: bool) -> Self {
-    self.attributes.visible = visible;
-    self
+  fn visible(self, visible: bool) -> Self {
+    self.with_visible(visible)
   }
 
-  fn transparent(mut self, transparent: bool) -> Self {
-    self.attributes.transparent = transparent;
-    self
+  fn transparent(self, transparent: bool) -> Self {
+    self.with_transparent(transparent)
   }
 
-  fn decorations(mut self, decorations: bool) -> Self {
-    self.attributes.decorations = decorations;
-    self
+  fn decorations(self, decorations: bool) -> Self {
+    self.with_decorations(decorations)
   }
 
-  fn always_on_top(mut self, always_on_top: bool) -> Self {
-    self.attributes.always_on_top = always_on_top;
-    self
+  fn always_on_top(self, always_on_top: bool) -> Self {
+    self.with_always_on_top(always_on_top)
   }
 
-  fn icon(mut self, icon: Self::Icon) -> Self {
-    self.attributes.icon = Some(icon.0);
-    self
+  fn icon(self, icon: Icon) -> crate::Result<Self> {
+    Ok(self.with_window_icon(Some(WryIcon::try_from(icon)?.0)))
   }
 
   fn has_icon(&self) -> bool {
-    self.attributes.icon.is_some()
-  }
-
-  fn user_data_path(mut self, user_data_path: Option<PathBuf>) -> Self {
-    self.attributes.user_data_path = user_data_path;
-    self
-  }
-
-  fn url(mut self, url: String) -> Self {
-    self.attributes.url.replace(url);
-    self
-  }
-
-  fn has_uri_scheme_protocol(&self, name: &str) -> bool {
-    self.uri_scheme_protocols.lock().unwrap().contains_key(name)
-  }
-
-  fn register_uri_scheme_protocol<
-    N: Into<String>,
-    H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
-  >(
-    self,
-    uri_scheme: N,
-    protocol: H,
-  ) -> Self {
-    let uri_scheme = uri_scheme.into();
-    self.uri_scheme_protocols.lock().unwrap().insert(
-      uri_scheme.clone(),
-      wry::CustomProtocol {
-        name: uri_scheme,
-        handler: Box::new(move |data| (protocol)(data).map_err(|_| wry::Error::InitScriptError)),
-      },
-    );
-    self
-  }
-
-  fn build(self) -> Self {
-    self
+    self.window.window_icon.is_some()
   }
 }
 
-impl From<wry::RpcRequest> for RpcRequest {
-  fn from(request: wry::RpcRequest) -> Self {
+impl From<WryRpcRequest> for RpcRequest {
+  fn from(request: WryRpcRequest) -> Self {
     Self {
       command: request.method,
       params: request.params,
@@ -234,305 +180,527 @@ impl From<wry::RpcRequest> for RpcRequest {
   }
 }
 
-impl From<wry::FileDropEvent> for FileDropEvent {
-  fn from(event: wry::FileDropEvent) -> Self {
+impl From<WryFileDropEvent> for FileDropEvent {
+  fn from(event: WryFileDropEvent) -> Self {
     match event {
-      wry::FileDropEvent::Hovered(paths) => FileDropEvent::Hovered(paths),
-      wry::FileDropEvent::Dropped(paths) => FileDropEvent::Dropped(paths),
-      wry::FileDropEvent::Cancelled => FileDropEvent::Cancelled,
+      WryFileDropEvent::Hovered(paths) => FileDropEvent::Hovered(paths),
+      WryFileDropEvent::Dropped(paths) => FileDropEvent::Dropped(paths),
+      WryFileDropEvent::Cancelled => FileDropEvent::Cancelled,
     }
   }
 }
 
+#[derive(Debug, Clone)]
+enum WindowMessage {
+  SetResizable(bool),
+  SetTitle(String),
+  Maximize,
+  Unmaximize,
+  Minimize,
+  Unminimize,
+  Show,
+  Hide,
+  Close,
+  SetDecorations(bool),
+  SetAlwaysOnTop(bool),
+  SetWidth(f64),
+  SetHeight(f64),
+  Resize { width: f64, height: f64 },
+  SetMinSize { min_width: f64, min_height: f64 },
+  SetMaxSize { max_width: f64, max_height: f64 },
+  SetX(f64),
+  SetY(f64),
+  SetPosition { x: f64, y: f64 },
+  SetFullscreen(bool),
+  SetIcon(WindowIcon),
+  DragWindow,
+}
+
+#[derive(Debug, Clone)]
+enum WebviewMessage {
+  EvaluateScript(String),
+}
+
+#[derive(Clone)]
+enum Message {
+  Window(WindowId, WindowMessage),
+  Webview(WindowId, WebviewMessage),
+  CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
+}
+
 /// The Tauri [`Dispatch`] for [`Wry`].
 #[derive(Clone)]
 pub struct WryDispatcher {
-  window: wry::WindowProxy,
-  application: wry::ApplicationProxy,
+  window_id: WindowId,
+  proxy: EventLoopProxy<Message>,
 }
 
 impl Dispatch for WryDispatcher {
   type Runtime = Wry;
-  type Icon = WryIcon;
-  type Attributes = WryAttributes;
+  type WindowBuilder = WryWindowBuilder;
 
   fn create_window<M: Params<Runtime = Self::Runtime>>(
     &mut self,
     pending: PendingWindow<M>,
   ) -> crate::Result<DetachedWindow<M>> {
-    let PendingWindow {
-      attributes,
-      rpc_handler,
-      file_drop_handler,
-      label,
-      ..
-    } = pending;
-
-    let proxy = self.application.clone();
-
-    let rpc_handler =
-      rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
-
-    let file_drop_handler = file_drop_handler
-      .map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
-
-    let uri_scheme_protocols = {
-      let mut lock = attributes
-        .uri_scheme_protocols
-        .lock()
-        .expect("poisoned custom protocols");
-      std::mem::take(&mut *lock)
-    };
-
-    let window = self
-      .application
-      .add_window_with_configs(
-        attributes.attributes,
-        rpc_handler,
-        uri_scheme_protocols.into_iter().map(|(_, p)| p).collect(),
-        file_drop_handler,
-      )
-      .map_err(|e| crate::Error::CreateWebview(e.to_string()))?;
-
+    let (tx, rx) = channel();
+    let label = pending.label.clone();
+    let proxy = self.proxy.clone();
+    self
+      .proxy
+      .send_event(Message::CreateWebview(
+        Arc::new(Mutex::new(Some(Box::new(move |event_loop| {
+          create_webview(event_loop, proxy, pending)
+        })))),
+        tx,
+      ))
+      .map_err(|_| crate::Error::FailedToSendMessage)?;
+    let window_id = rx.recv().unwrap();
     let dispatcher = WryDispatcher {
-      window,
-      application: proxy,
+      window_id,
+      proxy: self.proxy.clone(),
     };
-
     Ok(DetachedWindow { label, dispatcher })
   }
 
   fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
     self
-      .window
-      .set_resizable(resizable)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetResizable(resizable),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
     self
-      .window
-      .set_title(title)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetTitle(title.into()),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn maximize(&self) -> crate::Result<()> {
     self
-      .window
-      .maximize()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Maximize))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn unmaximize(&self) -> crate::Result<()> {
     self
-      .window
-      .unmaximize()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Unmaximize))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn minimize(&self) -> crate::Result<()> {
     self
-      .window
-      .minimize()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Minimize))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn unminimize(&self) -> crate::Result<()> {
     self
-      .window
-      .unminimize()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Unminimize))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn show(&self) -> crate::Result<()> {
     self
-      .window
-      .show()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Show))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn hide(&self) -> crate::Result<()> {
     self
-      .window
-      .hide()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Hide))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn close(&self) -> crate::Result<()> {
     self
-      .window
-      .close()
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Close))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
     self
-      .window
-      .set_decorations(decorations)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetDecorations(decorations),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
     self
-      .window
-      .set_always_on_top(always_on_top)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetAlwaysOnTop(always_on_top),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_width(&self, width: f64) -> crate::Result<()> {
     self
-      .window
-      .set_width(width)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetWidth(width),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_height(&self, height: f64) -> crate::Result<()> {
     self
-      .window
-      .set_height(height)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetHeight(height),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn resize(&self, width: f64, height: f64) -> crate::Result<()> {
     self
-      .window
-      .resize(width, height)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::Resize { width, height },
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()> {
     self
-      .window
-      .set_min_size(min_width, min_height)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetMinSize {
+          min_width,
+          min_height,
+        },
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()> {
     self
-      .window
-      .set_max_size(max_width, max_height)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetMaxSize {
+          max_width,
+          max_height,
+        },
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_x(&self, x: f64) -> crate::Result<()> {
     self
-      .window
-      .set_x(x)
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::SetX(x)))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_y(&self, y: f64) -> crate::Result<()> {
     self
-      .window
-      .set_y(y)
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::SetY(y)))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_position(&self, x: f64, y: f64) -> crate::Result<()> {
     self
-      .window
-      .set_position(x, y)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetPosition { x, y },
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
     self
-      .window
-      .set_fullscreen(fullscreen)
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetFullscreen(fullscreen),
+      ))
+      .map_err(|_| crate::Error::FailedToSendMessage)
+  }
+
+  fn set_icon(&self, icon: Icon) -> crate::Result<()> {
+    self
+      .proxy
+      .send_event(Message::Window(
+        self.window_id,
+        WindowMessage::SetIcon(WryIcon::try_from(icon)?.0),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
-  fn set_icon(&self, icon: Self::Icon) -> crate::Result<()> {
+  fn start_dragging(&self) -> crate::Result<()> {
     self
-      .window
-      .set_icon(icon.0)
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::DragWindow))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 
   fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()> {
     self
-      .window
-      .evaluate_script(script)
+      .proxy
+      .send_event(Message::Webview(
+        self.window_id,
+        WebviewMessage::EvaluateScript(script.into()),
+      ))
       .map_err(|_| crate::Error::FailedToSendMessage)
   }
 }
 
-/// A Tauri [`Runtime`] wrapper around [`wry::Application`].
+/// A Tauri [`Runtime`] wrapper around wry.
 pub struct Wry {
-  inner: wry::Application,
+  event_loop: EventLoop<Message>,
+  webviews: HashMap<WindowId, WebView>,
 }
 
 impl Runtime for Wry {
   type Dispatcher = WryDispatcher;
 
   fn new() -> crate::Result<Self> {
-    let app = wry::Application::new().map_err(|e| crate::Error::CreateWebview(e.to_string()))?;
-    Ok(Self { inner: app })
+    let event_loop = EventLoop::<Message>::with_user_event();
+    Ok(Self {
+      event_loop,
+      webviews: Default::default(),
+    })
   }
 
   fn create_window<M: Params<Runtime = Self>>(
     &mut self,
     pending: PendingWindow<M>,
   ) -> crate::Result<DetachedWindow<M>> {
-    let PendingWindow {
-      attributes,
-      rpc_handler,
-      file_drop_handler,
-      label,
-      ..
-    } = pending;
-
-    let proxy = self.inner.application_proxy();
-
-    let rpc_handler =
-      rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
-
-    let file_drop_handler = file_drop_handler
-      .map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
-
-    let uri_scheme_protocols = {
-      let mut lock = attributes
-        .uri_scheme_protocols
-        .lock()
-        .expect("poisoned custom protocols");
-      std::mem::take(&mut *lock)
-    };
-
-    let window = self
-      .inner
-      .add_window_with_configs(
-        attributes.attributes,
-        rpc_handler,
-        uri_scheme_protocols.into_iter().map(|(_, p)| p).collect(),
-        file_drop_handler,
-      )
-      .map_err(|e| crate::Error::CreateWebview(e.to_string()))?;
+    let label = pending.label.clone();
+    let proxy = self.event_loop.create_proxy();
+    let webview = create_webview(&self.event_loop, proxy.clone(), pending)?;
 
     let dispatcher = WryDispatcher {
-      window,
-      application: proxy,
+      window_id: webview.window().id(),
+      proxy,
     };
 
+    self.webviews.insert(webview.window().id(), webview);
+
     Ok(DetachedWindow { label, dispatcher })
   }
 
   fn run(self) {
-    wry::Application::run(self.inner)
+    let mut webviews = self.webviews;
+    self.event_loop.run(move |event, event_loop, control_flow| {
+      *control_flow = ControlFlow::Poll;
+
+      for (_, w) in webviews.iter() {
+        if let Err(e) = w.evaluate_script() {
+          eprintln!("{}", e);
+        }
+      }
+
+      match event {
+        Event::WindowEvent { event, window_id } => match event {
+          WindowEvent::CloseRequested => {
+            webviews.remove(&window_id);
+            if webviews.is_empty() {
+              *control_flow = ControlFlow::Exit;
+            }
+          }
+          WindowEvent::Resized(_) => {
+            if let Err(e) = webviews[&window_id].resize() {
+              eprintln!("{}", e);
+            }
+          }
+          _ => {}
+        },
+        Event::UserEvent(message) => match message {
+          Message::Window(id, window_message) => {
+            if let Some(webview) = webviews.get_mut(&id) {
+              let window = webview.window();
+              match window_message {
+                WindowMessage::SetResizable(resizable) => window.set_resizable(resizable),
+                WindowMessage::SetTitle(title) => window.set_title(&title),
+                WindowMessage::Maximize => window.set_maximized(true),
+                WindowMessage::Unmaximize => window.set_maximized(false),
+                WindowMessage::Minimize => window.set_minimized(true),
+                WindowMessage::Unminimize => window.set_minimized(false),
+                WindowMessage::Show => window.set_visible(true),
+                WindowMessage::Hide => window.set_visible(false),
+                WindowMessage::Close => {
+                  webviews.remove(&id);
+                  if webviews.is_empty() {
+                    *control_flow = ControlFlow::Exit;
+                  }
+                }
+                WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations),
+                WindowMessage::SetAlwaysOnTop(always_on_top) => {
+                  window.set_always_on_top(always_on_top)
+                }
+                WindowMessage::SetWidth(width) => {
+                  let mut size = window.inner_size().to_logical(window.scale_factor());
+                  size.width = width;
+                  window.set_inner_size(size);
+                }
+                WindowMessage::SetHeight(height) => {
+                  let mut size = window.inner_size().to_logical(window.scale_factor());
+                  size.height = height;
+                  window.set_inner_size(size);
+                }
+                WindowMessage::Resize { width, height } => {
+                  window.set_inner_size(LogicalSize::new(width, height));
+                }
+                WindowMessage::SetMinSize {
+                  min_width,
+                  min_height,
+                } => {
+                  window.set_min_inner_size(Some(LogicalSize::new(min_width, min_height)));
+                }
+                WindowMessage::SetMaxSize {
+                  max_width,
+                  max_height,
+                } => {
+                  window.set_max_inner_size(Some(LogicalSize::new(max_width, max_height)));
+                }
+                WindowMessage::SetX(x) => {
+                  if let Ok(outer_position) = window.outer_position() {
+                    let mut outer_position = outer_position.to_logical(window.scale_factor());
+                    outer_position.x = x;
+                    window.set_outer_position(outer_position);
+                  }
+                }
+                WindowMessage::SetY(y) => {
+                  if let Ok(outer_position) = window.outer_position() {
+                    let mut outer_position = outer_position.to_logical(window.scale_factor());
+                    outer_position.y = y;
+                    window.set_outer_position(outer_position);
+                  }
+                }
+                WindowMessage::SetPosition { x, y } => {
+                  window.set_outer_position(LogicalPosition::new(x, y))
+                }
+                WindowMessage::SetFullscreen(fullscreen) => {
+                  if fullscreen {
+                    window.set_fullscreen(Some(Fullscreen::Borderless(None)))
+                  } else {
+                    window.set_fullscreen(None)
+                  }
+                }
+                WindowMessage::SetIcon(icon) => {
+                  window.set_window_icon(Some(icon));
+                }
+                WindowMessage::DragWindow => {
+                  window.drag_window();
+                }
+              }
+            }
+          }
+          Message::Webview(id, webview_message) => {
+            if let Some(webview) = webviews.get_mut(&id) {
+              match webview_message {
+                WebviewMessage::EvaluateScript(script) => {
+                  let _ = webview.dispatch_script(&script);
+                }
+              }
+            }
+          }
+          Message::CreateWebview(handler, sender) => {
+            let handler = {
+              let mut lock = handler.lock().expect("poisoned create webview handler");
+              std::mem::take(&mut *lock).unwrap()
+            };
+            match handler(event_loop) {
+              Ok(webview) => {
+                let window_id = webview.window().id();
+                webviews.insert(window_id, webview);
+                sender.send(window_id).unwrap();
+              }
+              Err(e) => {
+                eprintln!("{}", e);
+              }
+            }
+          }
+        },
+        _ => (),
+      }
+    })
   }
 }
 
+fn create_webview<M: Params<Runtime = Wry>>(
+  event_loop: &EventLoopWindowTarget<Message>,
+  proxy: EventLoopProxy<Message>,
+  pending: PendingWindow<M>,
+) -> crate::Result<WebView> {
+  let PendingWindow {
+    webview_attributes,
+    window_attributes,
+    rpc_handler,
+    file_drop_handler,
+    label,
+    url,
+    ..
+  } = pending;
+
+  let window = window_attributes.build(event_loop).unwrap();
+  let mut webview_builder = WebViewBuilder::new(window)
+    .map_err(|e| crate::Error::CreateWebview(e.to_string()))?
+    .with_url(&url)
+    .unwrap(); // safe to unwrap because we validate the URL beforehand
+  if let Some(handler) = rpc_handler {
+    webview_builder =
+      webview_builder.with_rpc_handler(create_rpc_handler(proxy.clone(), label.clone(), handler));
+  }
+  if let Some(handler) = file_drop_handler {
+    webview_builder =
+      webview_builder.with_file_drop_handler(create_file_drop_handler(proxy, label, handler));
+  }
+  for (scheme, protocol) in webview_attributes.uri_scheme_protocols {
+    webview_builder = webview_builder.with_custom_protocol(scheme, move |_window, url| {
+      protocol(url).map_err(|_| wry::Error::InitScriptError)
+    });
+  }
+  if let Some(data_directory) = webview_attributes.data_directory {
+    webview_builder = webview_builder.with_data_directory(data_directory);
+  }
+  for script in webview_attributes.initialization_scripts {
+    webview_builder = webview_builder.with_initialization_script(&script);
+  }
+
+  webview_builder
+    .build()
+    .map_err(|e| crate::Error::CreateWebview(e.to_string()))
+}
+
 /// Create a wry rpc handler from a tauri rpc handler.
 fn create_rpc_handler<M: Params<Runtime = Wry>>(
-  app_proxy: wry::ApplicationProxy,
+  proxy: EventLoopProxy<Message>,
   label: M::Label,
   handler: WebviewRpcHandler<M>,
-) -> wry::WindowRpcHandler {
+) -> Box<dyn Fn(&Window, WryRpcRequest) -> Option<RpcResponse> + 'static> {
   Box::new(move |window, request| {
     handler(
       DetachedWindow {
         dispatcher: WryDispatcher {
-          window,
-          application: app_proxy.clone(),
+          window_id: window.id(),
+          proxy: proxy.clone(),
         },
         label: label.clone(),
       },
@@ -544,17 +712,17 @@ fn create_rpc_handler<M: Params<Runtime = Wry>>(
 
 /// Create a wry file drop handler from a tauri file drop handler.
 fn create_file_drop_handler<M: Params<Runtime = Wry>>(
-  app_proxy: wry::ApplicationProxy,
+  proxy: EventLoopProxy<Message>,
   label: M::Label,
   handler: FileDropHandler<M>,
-) -> wry::WindowFileDropHandler {
+) -> Box<dyn Fn(&Window, WryFileDropEvent) -> bool + 'static> {
   Box::new(move |window, event| {
     handler(
       event.into(),
       DetachedWindow {
         dispatcher: WryDispatcher {
-          window,
-          application: app_proxy.clone(),
+          window_id: window.id(),
+          proxy: proxy.clone(),
         },
         label: label.clone(),
       },

+ 19 - 23
core/tauri/src/runtime/manager.rs

@@ -15,9 +15,9 @@ use crate::{
   plugin::PluginStore,
   runtime::{
     tag::{tags_to_javascript_array, Tag, ToJsString},
-    webview::{Attributes, CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler},
+    webview::{CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler, WindowBuilder},
     window::{DetachedWindow, PendingWindow},
-    Dispatch, Icon, Runtime,
+    Icon, Runtime,
   },
   sealed::ParamsBase,
   Context, Params, Window,
@@ -29,7 +29,6 @@ use std::marker::PhantomData;
 use std::{
   borrow::Cow,
   collections::{HashMap, HashSet},
-  convert::TryInto,
   fs::create_dir_all,
   sync::{Arc, Mutex, MutexGuard},
 };
@@ -160,13 +159,12 @@ impl<P: Params> WindowManager<P> {
     "tauri://localhost".into()
   }
 
-  fn prepare_attributes(
+  fn prepare_pending_window(
     &self,
-    attrs: <<P::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes,
-    url: String,
+    mut pending: PendingWindow<P>,
     label: P::Label,
     pending_labels: &[P::Label],
-  ) -> crate::Result<<<P::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes> {
+  ) -> crate::Result<PendingWindow<P>> {
     let is_init_global = self.inner.config.build.with_global_tauri;
     let plugin_init = self
       .inner
@@ -175,8 +173,7 @@ impl<P: Params> WindowManager<P> {
       .expect("poisoned plugin store")
       .initialization_script();
 
-    let mut attributes = attrs
-      .url(url)
+    let mut webview_attributes = pending.webview_attributes
       .initialization_script(&self.initialization_script(&plugin_init, is_init_global))
       .initialization_script(&format!(
         r#"
@@ -187,24 +184,23 @@ impl<P: Params> WindowManager<P> {
         current_window_label = label.to_js_string()?,
       ));
 
-    if !attributes.has_icon() {
+    if !pending.window_attributes.has_icon() {
       if let Some(default_window_icon) = &self.inner.default_window_icon {
         let icon = Icon::Raw(default_window_icon.clone());
-        let icon = icon.try_into().expect("infallible icon convert failed");
-        attributes = attributes.icon(icon);
+        pending.window_attributes = pending.window_attributes.icon(icon)?;
       }
     }
 
     for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols {
-      if !attributes.has_uri_scheme_protocol(uri_scheme) {
+      if !webview_attributes.has_uri_scheme_protocol(uri_scheme) {
         let protocol = protocol.clone();
-        attributes = attributes
+        webview_attributes = webview_attributes
           .register_uri_scheme_protocol(uri_scheme.clone(), move |p| (protocol.protocol)(p));
       }
     }
 
-    if !attributes.has_uri_scheme_protocol("tauri") {
-      attributes = attributes
+    if !webview_attributes.has_uri_scheme_protocol("tauri") {
+      webview_attributes = webview_attributes
         .register_uri_scheme_protocol("tauri", self.prepare_uri_scheme_protocol().protocol);
     }
 
@@ -215,11 +211,13 @@ impl<P: Params> WindowManager<P> {
     if let Ok(user_data_dir) = local_app_data {
       // Make sure the directory exist without panic
       if create_dir_all(&user_data_dir).is_ok() {
-        attributes = attributes.user_data_path(Some(user_data_dir));
+        webview_attributes = webview_attributes.data_directory(user_data_dir);
       }
     }
 
-    Ok(attributes)
+    pending.webview_attributes = webview_attributes;
+
+    Ok(pending)
   }
 
   fn prepare_rpc_handler(&self) -> WebviewRpcHandler<P> {
@@ -441,7 +439,7 @@ impl<P: Params> WindowManager<P> {
     mut pending: PendingWindow<P>,
     pending_labels: &[P::Label],
   ) -> crate::Result<PendingWindow<P>> {
-    let (is_local, url) = match &pending.url {
+    let (is_local, url) = match &pending.webview_attributes.url {
       WindowUrl::App(path) => {
         let url = self.get_url();
         (
@@ -457,16 +455,14 @@ impl<P: Params> WindowManager<P> {
       WindowUrl::External(url) => (url.as_str().starts_with("tauri://"), url.to_string()),
     };
 
-    let attributes = pending.attributes.clone();
     if is_local {
       let label = pending.label.clone();
-      pending.attributes = self.prepare_attributes(attributes, url, label, pending_labels)?;
+      pending = self.prepare_pending_window(pending, label, pending_labels)?;
       pending.rpc_handler = Some(self.prepare_rpc_handler());
-    } else {
-      pending.attributes = attributes.url(url);
     }
 
     pending.file_drop_handler = Some(self.prepare_file_drop());
+    pending.url = url;
 
     Ok(pending)
   }

+ 8 - 12
core/tauri/src/runtime/mod.rs

@@ -5,13 +5,9 @@
 //! Internal runtime between Tauri and the underlying webview runtime.
 
 use crate::{
-  runtime::{
-    webview::AttributesBase,
-    window::{DetachedWindow, PendingWindow},
-  },
-  Attributes, Icon, Params,
+  runtime::window::{DetachedWindow, PendingWindow},
+  Icon, Params, WindowBuilder,
 };
-use std::convert::TryFrom;
 
 pub(crate) mod app;
 pub mod flavors;
@@ -43,11 +39,8 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
   /// The runtime this [`Dispatch`] runs under.
   type Runtime: Runtime;
 
-  /// Representation of a window icon.
-  type Icon: TryFrom<Icon, Error = crate::Error>;
-
-  /// The webview builder type.
-  type Attributes: Attributes<Icon = Self::Icon> + AttributesBase + Clone + Send;
+  /// The winoow builder type.
+  type WindowBuilder: WindowBuilder + Clone;
 
   /// Create a new webview window.
   fn create_window<P: Params<Runtime = Self::Runtime>>(
@@ -116,7 +109,10 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
   fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
 
   /// Updates the window icon.
-  fn set_icon(&self, icon: Self::Icon) -> crate::Result<()>;
+  fn set_icon(&self, icon: Icon) -> crate::Result<()>;
+
+  /// Starts dragging the window.
+  fn start_dragging(&self) -> crate::Result<()>;
 
   /// Executes javascript on the window this [`Dispatch`] represents.
   fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;

+ 81 - 63
core/tauri/src/runtime/webview.rs

@@ -5,55 +5,103 @@
 //! Items specific to the [`Runtime`](crate::runtime::Runtime)'s webview.
 
 use crate::runtime::Icon;
-use crate::{api::config::WindowConfig, runtime::window::DetachedWindow};
+use crate::{
+  api::config::{WindowConfig, WindowUrl},
+  runtime::window::DetachedWindow,
+};
 use serde_json::Value as JsonValue;
-use std::{convert::TryFrom, path::PathBuf};
+use std::{collections::HashMap, path::PathBuf};
+
+type UriSchemeProtocol = dyn Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static;
+
+/// The attributes used to create an webview.
+pub struct WebviewAttributes {
+  pub(crate) url: WindowUrl,
+  pub(crate) initialization_scripts: Vec<String>,
+  pub(crate) data_directory: Option<PathBuf>,
+  pub(crate) uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
+}
+
+impl WebviewAttributes {
+  /// Initializes the default attributes for a webview.
+  pub fn new(url: WindowUrl) -> Self {
+    Self {
+      url,
+      initialization_scripts: Vec::new(),
+      data_directory: None,
+      uri_scheme_protocols: Default::default(),
+    }
+  }
+
+  /// Sets the init script.
+  pub fn initialization_script(mut self, script: &str) -> Self {
+    self.initialization_scripts.push(script.to_string());
+    self
+  }
+
+  /// Data directory for the webview.
+  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
+    self.data_directory.replace(data_directory);
+    self
+  }
+
+  /// Whether the webview URI scheme protocol is defined or not.
+  pub fn has_uri_scheme_protocol(&self, name: &str) -> bool {
+    self.uri_scheme_protocols.contains_key(name)
+  }
+
+  /// Registers a webview protocol handler.
+  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
+  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
+  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
+  ///
+  /// # Arguments
+  ///
+  /// * `uri_scheme` The URI scheme to register, such as `example`.
+  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
+  pub fn register_uri_scheme_protocol<
+    N: Into<String>,
+    H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
+  >(
+    mut self,
+    uri_scheme: N,
+    protocol: H,
+  ) -> Self {
+    let uri_scheme = uri_scheme.into();
+    self
+      .uri_scheme_protocols
+      .insert(uri_scheme, Box::new(move |data| (protocol)(data)));
+    self
+  }
+}
 
 /// Do **NOT** implement this trait except for use in a custom [`Runtime`](crate::runtime::Runtime).
 ///
-/// This trait is separate from [`Attributes`] to prevent "accidental" implementation.
-pub trait AttributesBase: Sized {}
+/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
+pub trait WindowBuilderBase: 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::Runtime)
 /// and not by applications.
-pub trait Attributes: AttributesBase {
-  /// Expected icon format.
-  type Icon: TryFrom<Icon, Error = crate::Error>;
-
-  /// Initializes a new webview builder.
+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;
 
-  /// Sets the init script.
-  fn initialization_script(self, init: &str) -> Self;
-
-  /// The horizontal position of the window's top left corner.
-  fn x(self, x: f64) -> Self;
-
-  /// The vertical position of the window's top left corner.
-  fn y(self, y: f64) -> Self;
-
-  /// Window width.
-  fn width(self, width: f64) -> Self;
+  /// The initial position of the window's.
+  fn position(self, x: f64, y: f64) -> Self;
 
-  /// Window height.
-  fn height(self, height: f64) -> Self;
+  /// Window size.
+  fn inner_size(self, min_width: f64, min_height: f64) -> Self;
 
-  /// Window min width.
-  fn min_width(self, min_width: f64) -> Self;
+  /// Window min inner size.
+  fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
 
-  /// Window min height.
-  fn min_height(self, min_height: f64) -> Self;
-
-  /// Window max width.
-  fn max_width(self, max_width: f64) -> Self;
-
-  /// Window max height.
-  fn max_height(self, max_height: f64) -> Self;
+  /// Window max inner size.
+  fn max_inner_size(self, min_width: f64, min_height: f64) -> Self;
 
   /// Whether the window is resizable or not.
   fn resizable(self, resizable: bool) -> Self;
@@ -81,40 +129,10 @@ pub trait Attributes: AttributesBase {
   fn always_on_top(self, always_on_top: bool) -> Self;
 
   /// Sets the window icon.
-  fn icon(self, icon: Self::Icon) -> Self;
+  fn icon(self, icon: Icon) -> crate::Result<Self>;
 
   /// Whether the icon was set or not.
   fn has_icon(&self) -> bool;
-
-  /// User data path for the webview. Actually only supported on Windows.
-  fn user_data_path(self, user_data_path: Option<PathBuf>) -> Self;
-
-  /// Sets the webview url.
-  fn url(self, url: String) -> Self;
-
-  /// Whether the webview URI scheme protocol is defined or not.
-  fn has_uri_scheme_protocol(&self, name: &str) -> bool;
-
-  /// Registers a webview protocol handler.
-  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
-  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
-  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
-  ///
-  /// # Arguments
-  ///
-  /// * `uri_scheme` The URI scheme to register, such as `example`.
-  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
-  fn register_uri_scheme_protocol<
-    N: Into<String>,
-    H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
-  >(
-    self,
-    uri_scheme: N,
-    protocol: H,
-  ) -> Self;
-
-  /// The full attributes.
-  fn build(self) -> Self;
 }
 
 /// Rpc request.

+ 48 - 33
core/tauri/src/runtime/window.rs

@@ -5,68 +5,75 @@
 //! A layer between raw [`Runtime`] webview windows and Tauri.
 
 use crate::{
-  api::config::{WindowConfig, WindowUrl},
+  api::config::WindowConfig,
   event::{Event, EventHandler},
   hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
   runtime::{
     tag::ToJsString,
-    webview::{FileDropHandler, WebviewRpcHandler},
+    webview::{FileDropHandler, WebviewAttributes, WebviewRpcHandler},
     Dispatch, Runtime,
   },
   sealed::{ManagerBase, RuntimeOrDispatch},
-  Attributes, Icon, Manager, Params,
+  Icon, Manager, Params, WindowBuilder,
 };
 use serde::Serialize;
 use serde_json::Value as JsonValue;
-use std::{
-  convert::TryInto,
-  hash::{Hash, Hasher},
-};
+use std::hash::{Hash, Hasher};
 
 /// A webview window that has yet to be built.
 pub struct PendingWindow<M: Params> {
   /// The label that the window will be named.
   pub label: M::Label,
 
-  /// The url the window will open with.
-  pub url: WindowUrl,
+  /// The [`WindowBuilder`] that the window will be created with.
+  pub window_attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
 
-  /// The [`Attributes`] that the webview window be created with.
-  pub attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes,
+  /// The [`WebviewAttributes`] that the webview will be created with.
+  pub webview_attributes: WebviewAttributes,
 
   /// How to handle RPC calls on the webview window.
   pub rpc_handler: Option<WebviewRpcHandler<M>>,
 
   /// How to handle a file dropping onto the webview window.
   pub file_drop_handler: Option<FileDropHandler<M>>,
+
+  /// The resolved URL to load on the webview.
+  pub url: String,
 }
 
 impl<M: Params> PendingWindow<M> {
   /// Create a new [`PendingWindow`] with a label and starting url.
   pub fn new(
-    attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes,
+    window_attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
+    webview_attributes: WebviewAttributes,
     label: M::Label,
-    url: WindowUrl,
   ) -> Self {
     Self {
-      attributes,
+      window_attributes,
+      webview_attributes,
       label,
-      url,
       rpc_handler: None,
       file_drop_handler: None,
+      url: "tauri://localhost".to_string(),
     }
   }
 
   /// Create a new [`PendingWindow`] from a [`WindowConfig`] with a label and starting url.
-  pub fn with_config(window_config: WindowConfig, label: M::Label, url: WindowUrl) -> Self {
+  pub fn with_config(
+    window_config: WindowConfig,
+    webview_attributes: WebviewAttributes,
+    label: M::Label,
+  ) -> Self {
     Self {
-      attributes: <<<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes>::with_config(
-        window_config,
-      ),
+      window_attributes:
+        <<<M::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder>::with_config(
+          window_config,
+        ),
+      webview_attributes,
       label,
-      url,
       rpc_handler: None,
       file_drop_handler: None,
+      url: "tauri://localhost".to_string(),
     }
   }
 }
@@ -173,18 +180,21 @@ pub(crate) mod export {
     /// How to handle this window receiving an [`InvokeMessage`].
     pub(crate) fn on_message(self, command: String, payload: InvokePayload) -> crate::Result<()> {
       let manager = self.manager.clone();
-      if &command == "__initialized" {
-        let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
-        manager.run_on_page_load(self, payload);
-      } else {
-        let message = InvokeMessage::new(self, command.to_string(), payload);
-        if let Some(module) = &message.payload.tauri_module {
-          let module = module.to_string();
-          crate::endpoints::handle(module, message, manager.config(), manager.package_info());
-        } else if command.starts_with("plugin:") {
-          manager.extend_api(message);
-        } else {
-          manager.run_invoke_handler(message);
+      match command.as_str() {
+        "__initialized" => {
+          let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
+          manager.run_on_page_load(self, payload);
+        }
+        _ => {
+          let message = InvokeMessage::new(self, command.to_string(), payload);
+          if let Some(module) = &message.payload.tauri_module {
+            let module = module.to_string();
+            crate::endpoints::handle(module, message, manager.config(), manager.package_info());
+          } else if command.starts_with("plugin:") {
+            manager.extend_api(message);
+          } else {
+            manager.run_invoke_handler(message);
+          }
         }
       }
 
@@ -393,7 +403,12 @@ pub(crate) mod export {
 
     /// Sets this window' icon.
     pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
-      self.window.dispatcher.set_icon(icon.try_into()?)
+      self.window.dispatcher.set_icon(icon)
+    }
+
+    /// Starts dragging the window.
+    pub fn start_dragging(&self) -> crate::Result<()> {
+      self.window.dispatcher.start_dragging()
     }
 
     pub(crate) fn verify_salt(&self, salt: String) -> bool {

+ 4 - 2
examples/multiwindow/src-tauri/src/main.rs

@@ -7,7 +7,7 @@
   windows_subsystem = "windows"
 )]
 
-use tauri::Attributes;
+use tauri::WindowBuilder;
 
 fn main() {
   tauri::Builder::default()
@@ -20,7 +20,9 @@ fn main() {
     .create_window(
       "Rust".to_string(),
       tauri::WindowUrl::App("index.html".into()),
-      |attributes| attributes.title("Tauri - Rust"),
+      |window_attributes, webview_attributes| {
+        (window_attributes.title("Tauri - Rust"), webview_attributes)
+      },
     )
     .run(tauri::generate_context!())
     .expect("failed to run tauri application");

+ 9 - 0
tooling/api/src/window.ts

@@ -472,6 +472,15 @@ export class WindowManager {
       }
     })
   }
+
+  async startDragging(): Promise<void> {
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'startDragging'
+      }
+    })
+  }
 }
 
 const appWindow = new WindowManager()