Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions packages/desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
ipc::{IpcMessage, UserWindowEvent},
query::QueryResult,
shortcut::ShortcutRegistry,
webview::WebviewInstance,
webview::{PendingWebview, WebviewInstance},
};
use dioxus_core::{ElementId, ScopeId, VirtualDom};
use dioxus_history::History;
Expand Down Expand Up @@ -49,7 +49,7 @@ pub(crate) struct App {
/// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
pub(crate) struct SharedContext {
pub(crate) event_handlers: WindowEventHandlers,
pub(crate) pending_webviews: RefCell<Vec<WebviewInstance>>,
pub(crate) pending_webviews: RefCell<Vec<PendingWebview>>,
pub(crate) shortcut_manager: ShortcutRegistry,
pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
Expand Down Expand Up @@ -177,9 +177,10 @@ impl App {
}

pub fn handle_new_window(&mut self) {
for handler in self.shared.pending_webviews.borrow_mut().drain(..) {
let id = handler.desktop_context.window.id();
self.webviews.insert(id, handler);
for pending_webview in self.shared.pending_webviews.borrow_mut().drain(..) {
let window = pending_webview.create_window(&self.shared);
let id = window.desktop_context.window.id();
self.webviews.insert(id, window);
_ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
}
}
Expand Down
95 changes: 77 additions & 18 deletions packages/desktop/src/desktop_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ use crate::{
ipc::UserWindowEvent,
query::QueryEngine,
shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
webview::WebviewInstance,
webview::PendingWebview,
AssetRequest, Config, WryEventHandler,
};
use dioxus_core::{
prelude::{Callback, ScopeId},
VirtualDom,
use dioxus_core::{prelude::Callback, VirtualDom};
use std::{
future::{Future, IntoFuture},
pin::Pin,
rc::{Rc, Weak},
};
use std::rc::{Rc, Weak};
use tao::{
event::Event,
event_loop::EventLoopWindowTarget,
Expand Down Expand Up @@ -99,21 +100,39 @@ impl DesktopService {
}
}

/// Create a new window using the props and window builder
/// Start the creation of a new window using the props and window builder
///
/// Returns the webview handle for the new window.
///
/// You can use this to control other windows from the current window.
/// Returns a future that resolves to the webview handle for the new window. You can use this
/// to control other windows from the current window once the new window is created.
///
/// Be careful to not create a cycle of windows, or you might leak memory.
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> WeakDesktopContext {
let window = WebviewInstance::new(cfg, dom, self.shared.clone());

let cx = window.dom.in_runtime(|| {
ScopeId::ROOT
.consume_context::<Rc<DesktopService>>()
.unwrap()
});
///
/// # Example
///
/// ```rust, no_run
/// use dioxus::prelude::*;
/// fn popup() -> Element {
/// rsx! {
/// div { "This is a popup window!" }
/// }
/// }
///
/// // Create a new window with a component that will be rendered in the new window.
/// let dom = VirtualDom::new(popup);
/// // Create and wait for the window
/// let window = dioxus::desktop::window().new_window(dom, Default::default()).await;
/// // Fullscreen the new window
/// window.set_fullscreen(true);
/// ```
// Note: This method is asynchronous because webview2 does not support creating a new window from
// inside of an existing webview callback. Dioxus runs event handlers synchronously inside of a webview
// callback. See [this page](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#reentrancy) for more information.
//
// Related issues:
// - https://github.com/tauri-apps/wry/issues/583
// - https://github.com/DioxusLabs/dioxus/issues/3080
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {
let (window, context) = PendingWebview::new(dom, cfg);

self.shared
.proxy
Expand All @@ -122,7 +141,7 @@ impl DesktopService {

self.shared.pending_webviews.borrow_mut().push(window);

Rc::downgrade(&cx)
context
}

/// trigger the drag-window event
Expand Down Expand Up @@ -300,3 +319,43 @@ fn is_main_thread() -> bool {
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
result != NO
}

/// A [`DesktopContext`] that is pending creation.
///
/// # Example
/// ```rust
/// // Create a new window asynchronously
/// let pending_context = desktop_service.new_window(dom, config);
/// // Wait for the context to be created
/// let window = pending_context.await;
/// window.set_fullscreen(true);
/// ```
pub struct PendingDesktopContext {
pub(crate) receiver: tokio::sync::oneshot::Receiver<DesktopContext>,
}

impl PendingDesktopContext {
/// Resolve the pending context into a [`DesktopContext`].
pub async fn resolve(self) -> DesktopContext {
self.try_resolve()
.await
.expect("Failed to resolve pending desktop context")
}

/// Try to resolve the pending context into a [`DesktopContext`].
pub async fn try_resolve(
self,
) -> Result<DesktopContext, tokio::sync::oneshot::error::RecvError> {
self.receiver.await
}
}

impl IntoFuture for PendingDesktopContext {
type Output = DesktopContext;

type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;

fn into_future(self) -> Self::IntoFuture {
Box::pin(self.resolve())
}
}
4 changes: 3 additions & 1 deletion packages/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ pub mod trayicon;
// Public exports
pub use assets::AssetRequest;
pub use config::{Config, WindowCloseBehaviour};
pub use desktop_context::{window, DesktopContext, DesktopService, WeakDesktopContext};
pub use desktop_context::{
window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,
};
pub use event_handlers::WryEventHandler;
pub use hooks::*;
pub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};
Expand Down
32 changes: 32 additions & 0 deletions packages/desktop/src/webview.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::element::DesktopElement;
use crate::file_upload::DesktopFileDragEvent;
use crate::menubar::DioxusMenu;
use crate::PendingDesktopContext;
use crate::{
app::SharedContext,
assets::AssetHandlerRegistry,
Expand Down Expand Up @@ -207,6 +208,7 @@ impl WebviewInstance {

// https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged
#[cfg(target_os = "macos")]
#[allow(deprecated)]
{
use cocoa::appkit::NSWindowCollectionBehavior;
use cocoa::base::id;
Expand Down Expand Up @@ -524,3 +526,33 @@ impl SynchronousEventResponse {
Self { prevent_default }
}
}

/// A webview that is queued to be created. We can't spawn webviews outside of the main event loop because it may
/// block on windows so we queue them into the shared context and then create them when the main event loop is ready.
pub(crate) struct PendingWebview {
dom: VirtualDom,
cfg: Config,
sender: tokio::sync::oneshot::Sender<DesktopContext>,
}

impl PendingWebview {
pub(crate) fn new(dom: VirtualDom, cfg: Config) -> (Self, PendingDesktopContext) {
let (sender, receiver) = tokio::sync::oneshot::channel();
let webview = Self { dom, cfg, sender };
let pending = PendingDesktopContext { receiver };
(webview, pending)
}

pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {
let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());

let cx = window.dom.in_runtime(|| {
ScopeId::ROOT
.consume_context::<Rc<DesktopService>>()
.unwrap()
});
_ = self.sender.send(cx);

window
}
}
Loading