Skip to content

Commit e6ca0ba

Browse files
authored
Make new_window asynchronous (#4297)
* Make new_window asynchronous * fix clippy
1 parent 99a6b23 commit e6ca0ba

File tree

4 files changed

+118
-24
lines changed

4 files changed

+118
-24
lines changed

packages/desktop/src/app.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
ipc::{IpcMessage, UserWindowEvent},
66
query::QueryResult,
77
shortcut::ShortcutRegistry,
8-
webview::WebviewInstance,
8+
webview::{PendingWebview, WebviewInstance},
99
};
1010
use dioxus_core::{ElementId, ScopeId, VirtualDom};
1111
use dioxus_history::History;
@@ -49,7 +49,7 @@ pub(crate) struct App {
4949
/// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
5050
pub(crate) struct SharedContext {
5151
pub(crate) event_handlers: WindowEventHandlers,
52-
pub(crate) pending_webviews: RefCell<Vec<WebviewInstance>>,
52+
pub(crate) pending_webviews: RefCell<Vec<PendingWebview>>,
5353
pub(crate) shortcut_manager: ShortcutRegistry,
5454
pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
5555
pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
@@ -177,9 +177,10 @@ impl App {
177177
}
178178

179179
pub fn handle_new_window(&mut self) {
180-
for handler in self.shared.pending_webviews.borrow_mut().drain(..) {
181-
let id = handler.desktop_context.window.id();
182-
self.webviews.insert(id, handler);
180+
for pending_webview in self.shared.pending_webviews.borrow_mut().drain(..) {
181+
let window = pending_webview.create_window(&self.shared);
182+
let id = window.desktop_context.window.id();
183+
self.webviews.insert(id, window);
183184
_ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
184185
}
185186
}

packages/desktop/src/desktop_context.rs

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ use crate::{
55
ipc::UserWindowEvent,
66
query::QueryEngine,
77
shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
8-
webview::WebviewInstance,
8+
webview::PendingWebview,
99
AssetRequest, Config, WryEventHandler,
1010
};
11-
use dioxus_core::{
12-
prelude::{Callback, ScopeId},
13-
VirtualDom,
11+
use dioxus_core::{prelude::Callback, VirtualDom};
12+
use std::{
13+
future::{Future, IntoFuture},
14+
pin::Pin,
15+
rc::{Rc, Weak},
1416
};
15-
use std::rc::{Rc, Weak};
1617
use tao::{
1718
event::Event,
1819
event_loop::EventLoopWindowTarget,
@@ -99,21 +100,39 @@ impl DesktopService {
99100
}
100101
}
101102

102-
/// Create a new window using the props and window builder
103+
/// Start the creation of a new window using the props and window builder
103104
///
104-
/// Returns the webview handle for the new window.
105-
///
106-
/// You can use this to control other windows from the current window.
105+
/// Returns a future that resolves to the webview handle for the new window. You can use this
106+
/// to control other windows from the current window once the new window is created.
107107
///
108108
/// Be careful to not create a cycle of windows, or you might leak memory.
109-
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> WeakDesktopContext {
110-
let window = WebviewInstance::new(cfg, dom, self.shared.clone());
111-
112-
let cx = window.dom.in_runtime(|| {
113-
ScopeId::ROOT
114-
.consume_context::<Rc<DesktopService>>()
115-
.unwrap()
116-
});
109+
///
110+
/// # Example
111+
///
112+
/// ```rust, no_run
113+
/// use dioxus::prelude::*;
114+
/// fn popup() -> Element {
115+
/// rsx! {
116+
/// div { "This is a popup window!" }
117+
/// }
118+
/// }
119+
///
120+
/// // Create a new window with a component that will be rendered in the new window.
121+
/// let dom = VirtualDom::new(popup);
122+
/// // Create and wait for the window
123+
/// let window = dioxus::desktop::window().new_window(dom, Default::default()).await;
124+
/// // Fullscreen the new window
125+
/// window.set_fullscreen(true);
126+
/// ```
127+
// Note: This method is asynchronous because webview2 does not support creating a new window from
128+
// inside of an existing webview callback. Dioxus runs event handlers synchronously inside of a webview
129+
// callback. See [this page](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#reentrancy) for more information.
130+
//
131+
// Related issues:
132+
// - https://github.com/tauri-apps/wry/issues/583
133+
// - https://github.com/DioxusLabs/dioxus/issues/3080
134+
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {
135+
let (window, context) = PendingWebview::new(dom, cfg);
117136

118137
self.shared
119138
.proxy
@@ -122,7 +141,7 @@ impl DesktopService {
122141

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

125-
Rc::downgrade(&cx)
144+
context
126145
}
127146

128147
/// trigger the drag-window event
@@ -300,3 +319,43 @@ fn is_main_thread() -> bool {
300319
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
301320
result != NO
302321
}
322+
323+
/// A [`DesktopContext`] that is pending creation.
324+
///
325+
/// # Example
326+
/// ```rust
327+
/// // Create a new window asynchronously
328+
/// let pending_context = desktop_service.new_window(dom, config);
329+
/// // Wait for the context to be created
330+
/// let window = pending_context.await;
331+
/// window.set_fullscreen(true);
332+
/// ```
333+
pub struct PendingDesktopContext {
334+
pub(crate) receiver: tokio::sync::oneshot::Receiver<DesktopContext>,
335+
}
336+
337+
impl PendingDesktopContext {
338+
/// Resolve the pending context into a [`DesktopContext`].
339+
pub async fn resolve(self) -> DesktopContext {
340+
self.try_resolve()
341+
.await
342+
.expect("Failed to resolve pending desktop context")
343+
}
344+
345+
/// Try to resolve the pending context into a [`DesktopContext`].
346+
pub async fn try_resolve(
347+
self,
348+
) -> Result<DesktopContext, tokio::sync::oneshot::error::RecvError> {
349+
self.receiver.await
350+
}
351+
}
352+
353+
impl IntoFuture for PendingDesktopContext {
354+
type Output = DesktopContext;
355+
356+
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
357+
358+
fn into_future(self) -> Self::IntoFuture {
359+
Box::pin(self.resolve())
360+
}
361+
}

packages/desktop/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ pub mod trayicon;
4848
// Public exports
4949
pub use assets::AssetRequest;
5050
pub use config::{Config, WindowCloseBehaviour};
51-
pub use desktop_context::{window, DesktopContext, DesktopService, WeakDesktopContext};
51+
pub use desktop_context::{
52+
window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,
53+
};
5254
pub use event_handlers::WryEventHandler;
5355
pub use hooks::*;
5456
pub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};

packages/desktop/src/webview.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::element::DesktopElement;
22
use crate::file_upload::DesktopFileDragEvent;
33
use crate::menubar::DioxusMenu;
4+
use crate::PendingDesktopContext;
45
use crate::{
56
app::SharedContext,
67
assets::AssetHandlerRegistry,
@@ -207,6 +208,7 @@ impl WebviewInstance {
207208

208209
// https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged
209210
#[cfg(target_os = "macos")]
211+
#[allow(deprecated)]
210212
{
211213
use cocoa::appkit::NSWindowCollectionBehavior;
212214
use cocoa::base::id;
@@ -524,3 +526,33 @@ impl SynchronousEventResponse {
524526
Self { prevent_default }
525527
}
526528
}
529+
530+
/// A webview that is queued to be created. We can't spawn webviews outside of the main event loop because it may
531+
/// block on windows so we queue them into the shared context and then create them when the main event loop is ready.
532+
pub(crate) struct PendingWebview {
533+
dom: VirtualDom,
534+
cfg: Config,
535+
sender: tokio::sync::oneshot::Sender<DesktopContext>,
536+
}
537+
538+
impl PendingWebview {
539+
pub(crate) fn new(dom: VirtualDom, cfg: Config) -> (Self, PendingDesktopContext) {
540+
let (sender, receiver) = tokio::sync::oneshot::channel();
541+
let webview = Self { dom, cfg, sender };
542+
let pending = PendingDesktopContext { receiver };
543+
(webview, pending)
544+
}
545+
546+
pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {
547+
let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());
548+
549+
let cx = window.dom.in_runtime(|| {
550+
ScopeId::ROOT
551+
.consume_context::<Rc<DesktopService>>()
552+
.unwrap()
553+
});
554+
_ = self.sender.send(cx);
555+
556+
window
557+
}
558+
}

0 commit comments

Comments
 (0)