Synchronous prevent default (#2792)

* add prevent default methods to the event

* sync prevent default almost working

* sync prevent default working

* Move event handling into the runtime

* update core tests

* restore desktop file dialog

* implement prevent default on web

* add a hint about the new prevent default method

* fix web prevent default

* Fix CTRL+click on links

* fix values memorize in place test

* Fix a few more tests

* Add a playwright test for sync prevent default

* Fix core doc tests

* create a deprecated VirtualDom::handle_event

* fix macos imports in desktop

* Fix onmounted event

* Fix liveview support

* switch to RefCell for metadata

* Remove println

* remove prevent default attribute

* remove web specific link behavior

* Fix liveview links

* more liveview fixes for link

* Fix merge conflicts

* Fix clippy

* use the new prevent default in the file upload example
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-08-13 11:57:54 -07:00
1 parent 8b62b71e2d
commit cab573eefd
54 files changed
+940 -729

No files matched your search

+162 -48
View File
@@ -1,17 +1,129 @@
use crate::element::DesktopElement;
use crate::file_upload::DesktopFileDragEvent;
use crate::menubar::DioxusMenu;
use crate::{
app::SharedContext, assets::AssetHandlerRegistry, document::DesktopDocument, edits::EditQueue,
app::SharedContext, assets::AssetHandlerRegistry, document::DesktopDocument, edits::WryQueue,
file_upload::NativeFileHover, ipc::UserWindowEvent, protocol, waker::tao_waker, Config,
DesktopContext, DesktopService,
};
use dioxus_core::{ScopeId, VirtualDom};
use dioxus_core::{Runtime, ScopeId, VirtualDom};
use dioxus_hooks::to_owned;
use dioxus_html::document::Document;
use dioxus_html::native_bind::NativeFileEngine;
use dioxus_html::{HasFileData, HtmlEvent, PlatformEventData};
use dioxus_interpreter_js::SynchronousEventResponse;
use futures_util::{pin_mut, FutureExt};
use std::cell::OnceCell;
use std::sync::Arc;
use std::{rc::Rc, task::Waker};
use wry::{RequestAsyncResponder, WebContext, WebViewBuilder};
#[derive(Clone)]
pub(crate) struct WebviewEdits {
runtime: Rc<Runtime>,
pub wry_queue: WryQueue,
desktop_context: Rc<OnceCell<DesktopContext>>,
}
impl WebviewEdits {
fn new(runtime: Rc<Runtime>, wry_queue: WryQueue) -> Self {
Self {
runtime,
wry_queue,
desktop_context: Default::default(),
}
}
fn set_desktop_context(&self, context: DesktopContext) {
_ = self.desktop_context.set(context);
}
pub fn handle_event(
&self,
request: wry::http::Request<Vec<u8>>,
responder: wry::RequestAsyncResponder,
) {
let body = self.try_handle_event(request).unwrap_or_default();
responder.respond(wry::http::Response::new(body))
}
pub fn try_handle_event(
&self,
request: wry::http::Request<Vec<u8>>,
) -> Result<Vec<u8>, serde_json::Error> {
let response = match serde_json::from_slice(request.body()) {
Ok(event) => self.handle_html_event(event),
Err(err) => {
tracing::error!("Error parsing user_event: {:?}", err);
SynchronousEventResponse::new(false)
}
};
let body = match serde_json::to_vec(&response) {
Ok(body) => body,
Err(err) => {
tracing::error!("failed to serialize SynchronousEventResponse: {err:?}");
return Err(err);
}
};
Ok(body)
}
pub fn handle_html_event(&self, event: HtmlEvent) -> SynchronousEventResponse {
let HtmlEvent {
element,
name,
bubbles,
data,
} = event;
let Some(desktop_context) = self.desktop_context.get() else {
tracing::error!(
"Tried to handle event before setting the desktop context on the event handler"
);
return Default::default();
};
let query = desktop_context.query.clone();
let recent_file = desktop_context.file_hover.clone();
// check for a mounted event placeholder and replace it with a desktop specific element
let as_any = match data {
dioxus_html::EventData::Mounted => {
let element = DesktopElement::new(element, desktop_context.clone(), query);
Rc::new(PlatformEventData::new(Box::new(element)))
}
dioxus_html::EventData::Drag(ref drag) => {
// we want to override this with a native file engine, provided by the most recent drag event
if drag.files().is_some() {
let file_event = recent_file.current().unwrap();
let paths = match file_event {
wry::DragDropEvent::Enter { paths, .. } => paths,
wry::DragDropEvent::Drop { paths, .. } => paths,
_ => vec![],
};
Rc::new(PlatformEventData::new(Box::new(DesktopFileDragEvent {
mouse: drag.mouse.clone(),
files: Arc::new(NativeFileEngine::new(paths)),
})))
} else {
data.into_any()
}
}
_ => data.into_any(),
};
let event = dioxus_core::Event::new(as_any, bubbles);
self.runtime.handle_event(&name, event.clone(), element);
// Get the response from the event
SynchronousEventResponse::new(!event.default_action_enabled())
}
}
pub(crate) struct WebviewInstance {
pub dom: VirtualDom,
pub edits: WebviewEdits,
pub desktop_context: DesktopContext,
pub waker: Waker,
@@ -69,56 +181,57 @@ impl WebviewInstance {
}
let mut web_context = WebContext::new(cfg.data_dir.clone());
let edit_queue = EditQueue::default();
let file_hover = NativeFileHover::default();
let edit_queue = WryQueue::default();
let asset_handlers = AssetHandlerRegistry::new(dom.runtime());
let edits = WebviewEdits::new(dom.runtime(), edit_queue.clone());
let file_hover = NativeFileHover::default();
let headless = !cfg.window.window.visible;
// Rust :(
let window_id = window.id();
let custom_head = cfg.custom_head.clone();
let index_file = cfg.custom_index.clone();
let root_name = cfg.root_name.clone();
let asset_handlers_ = asset_handlers.clone();
let edit_queue_ = edit_queue.clone();
let proxy_ = shared.proxy.clone();
let file_hover_ = file_hover.clone();
let request_handler = {
to_owned![
cfg.custom_head,
cfg.custom_index,
cfg.root_name,
asset_handlers,
edits
];
move |request, responder: RequestAsyncResponder| {
// Try to serve the index file first
if let Some(index_bytes) = protocol::index_request(
&request,
custom_head.clone(),
custom_index.clone(),
&root_name,
headless,
) {
return responder.respond(index_bytes);
}
let request_handler = move |request, responder: RequestAsyncResponder| {
// Try to serve the index file first
let index_bytes = protocol::index_request(
&request,
custom_head.clone(),
index_file.clone(),
&root_name,
headless,
);
// Otherwise, try to serve an asset, either from the user or the filesystem
match index_bytes {
Some(body) => responder.respond(body),
None => protocol::desktop_handler(
request,
asset_handlers_.clone(),
&edit_queue_,
responder,
),
// Otherwise, try to serve an asset, either from the user or the filesystem
protocol::desktop_handler(request, asset_handlers.clone(), responder, &edits);
}
};
let ipc_handler = move |payload: wry::http::Request<String>| {
// defer the event to the main thread
let body = payload.into_body();
if let Ok(msg) = serde_json::from_str(&body) {
_ = proxy_.send_event(UserWindowEvent::Ipc { id: window_id, msg });
let ipc_handler = {
let window_id = window.id();
to_owned![shared.proxy];
move |payload: wry::http::Request<String>| {
// defer the event to the main thread
let body = payload.into_body();
if let Ok(msg) = serde_json::from_str(&body) {
_ = proxy.send_event(UserWindowEvent::Ipc { id: window_id, msg });
}
}
};
let file_drop_handler = move |evt| {
// Update the most recent file drop event - when the event comes in from the webview we can use the
// most recent event to build a new event with the files in it.
file_hover_.set(evt);
false
let file_drop_handler = {
to_owned![file_hover];
move |evt| {
// Update the most recent file drop event - when the event comes in from the webview we can use the
// most recent event to build a new event with the files in it.
file_hover.set(evt);
false
}
};
#[cfg(any(
@@ -216,22 +329,23 @@ impl WebviewInstance {
webview,
window,
shared.clone(),
edit_queue,
asset_handlers,
file_hover,
));
// Provide the desktop context to the virtual dom and edit handler
edits.set_desktop_context(desktop_context.clone());
let provider: Rc<dyn Document> = Rc::new(DesktopDocument::new(desktop_context.clone()));
dom.in_runtime(|| {
ScopeId::ROOT.provide_context(desktop_context.clone());
ScopeId::ROOT.provide_context(provider);
});
WebviewInstance {
dom,
edits,
waker: tao_waker(shared.proxy.clone(), desktop_context.window.id()),
desktop_context,
dom,
_menu: menu,
_web_context: web_context,
}
@@ -245,7 +359,7 @@ impl WebviewInstance {
// It will return Pending when it needs to be polled again - nothing is ready
loop {
// If we're waiting for a render, wait for it to finish before we continue
let edits_flushed_poll = self.desktop_context.edit_queue.poll_edits_flushed(&mut cx);
let edits_flushed_poll = self.edits.wry_queue.poll_edits_flushed(&mut cx);
if edits_flushed_poll.is_pending() {
return;
}
@@ -261,8 +375,8 @@ impl WebviewInstance {
}
self.dom
.render_immediate(&mut *self.desktop_context.mutation_state.borrow_mut());
self.desktop_context.send_edits();
.render_immediate(&mut *self.edits.wry_queue.mutation_state_mut());
self.edits.wry_queue.send_edits();
}
}