Prevent desktop reloads at the wry level (#4422)
* Prevent desktop reloads at the wry level * add a test * only allow one navigation
This commit is contained in:
1 parent
393246b923
commit
0d2e1f4051
7 files changed
+124
-20
No files matched your search
@@ -138,6 +138,11 @@ name = "check_rendering"
|
|||||||
path = "headless_tests/rendering.rs"
|
path = "headless_tests/rendering.rs"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "check_forms"
|
||||||
|
path = "headless_tests/forms.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "check_eval"
|
name = "check_eval"
|
||||||
path = "headless_tests/eval.rs"
|
path = "headless_tests/eval.rs"
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::DesktopContext;
|
||||||
|
use dioxus_document::eval;
|
||||||
|
|
||||||
|
#[path = "./utils.rs"]
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
utils::check_app_exits(check_html_renders);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_html(count: usize) -> String {
|
||||||
|
let html = document::eval(&format!(
|
||||||
|
r#"// Wait until the element is available
|
||||||
|
const interval = setInterval(() => {{
|
||||||
|
const element = document.getElementById('div-{count}');
|
||||||
|
if (element) {{
|
||||||
|
dioxus.send(element.innerHTML);
|
||||||
|
clearInterval(interval);
|
||||||
|
}}
|
||||||
|
}}, 100);"#
|
||||||
|
))
|
||||||
|
.recv::<String>()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("html: {html}");
|
||||||
|
html
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_html_renders() -> Element {
|
||||||
|
let mut signal = use_signal(|| 0);
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
let signal = signal();
|
||||||
|
// Pass the test once the count is greater than 10
|
||||||
|
if signal > 10 {
|
||||||
|
let desktop_context: DesktopContext = consume_context();
|
||||||
|
desktop_context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn(async move {
|
||||||
|
let raw_html = inner_html(signal).await;
|
||||||
|
println!("{raw_html}");
|
||||||
|
// Make sure the html contains count is {signal}
|
||||||
|
assert!(raw_html.contains(&format!("count is {}", signal)));
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
eval(
|
||||||
|
r#"let button = document.querySelector('#increment');
|
||||||
|
button.dispatchEvent(new MouseEvent('click', {
|
||||||
|
view: window,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
buttons: 2,
|
||||||
|
button: 2,
|
||||||
|
}));"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the signal is even, also submit the form. This should not effect the count because the navigation is blocked
|
||||||
|
if signal % 2 == 0 {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
eval(
|
||||||
|
r#"let form = document.getElementById('form-submit');
|
||||||
|
form.dispatchEvent(new Event('click', {
|
||||||
|
view: window,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
buttons: 2,
|
||||||
|
button: 2,
|
||||||
|
}));"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
id: "div-{signal}",
|
||||||
|
h1 { "Form" }
|
||||||
|
form {
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
name: "username",
|
||||||
|
id: "username",
|
||||||
|
value: "goodbye"
|
||||||
|
}
|
||||||
|
input { r#type: "text", name: "full-name", value: "lorem" }
|
||||||
|
input { r#type: "password", name: "password", value: "ipsum" }
|
||||||
|
input {
|
||||||
|
r#type: "radio",
|
||||||
|
name: "color",
|
||||||
|
value: "red",
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
input { r#type: "radio", name: "color", value: "blue" }
|
||||||
|
button { id: "form-submit", r#type: "submit", value: "Submit", "Submit the form" }
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
id: "increment",
|
||||||
|
onclick: move |_| {
|
||||||
|
signal += 1;
|
||||||
|
},
|
||||||
|
"count is {signal}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ use dioxus_history::{History, MemoryHistory};
|
|||||||
use dioxus_hooks::to_owned;
|
use dioxus_hooks::to_owned;
|
||||||
use dioxus_html::{HasFileData, HtmlEvent, PlatformEventData};
|
use dioxus_html::{HasFileData, HtmlEvent, PlatformEventData};
|
||||||
use futures_util::{pin_mut, FutureExt};
|
use futures_util::{pin_mut, FutureExt};
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{cell::OnceCell, time::Duration};
|
use std::{cell::OnceCell, time::Duration};
|
||||||
use std::{rc::Rc, task::Waker};
|
use std::{rc::Rc, task::Waker};
|
||||||
@@ -300,6 +301,8 @@ impl WebviewInstance {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let page_loaded = AtomicBool::new(false);
|
||||||
|
|
||||||
let mut webview = WebViewBuilder::new_with_web_context(&mut web_context)
|
let mut webview = WebViewBuilder::new_with_web_context(&mut web_context)
|
||||||
.with_bounds(wry::Rect {
|
.with_bounds(wry::Rect {
|
||||||
position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),
|
position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),
|
||||||
@@ -311,11 +314,13 @@ impl WebviewInstance {
|
|||||||
.with_transparent(cfg.window.window.transparent)
|
.with_transparent(cfg.window.window.transparent)
|
||||||
.with_url("dioxus://index.html/")
|
.with_url("dioxus://index.html/")
|
||||||
.with_ipc_handler(ipc_handler)
|
.with_ipc_handler(ipc_handler)
|
||||||
.with_navigation_handler(|var| {
|
.with_navigation_handler(move |var| {
|
||||||
// We don't want to allow any navigation
|
// We don't want to allow any navigation
|
||||||
// We only want to serve the index file and assets
|
// We only want to serve the index file and assets
|
||||||
if var.starts_with("dioxus://") || var.starts_with("http://dioxus.") {
|
if var.starts_with("dioxus://") || var.starts_with("http://dioxus.") {
|
||||||
true
|
// After the page has loaded once, don't allow any more navigation
|
||||||
|
let page_loaded = page_loaded.swap(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
!page_loaded
|
||||||
} else {
|
} else {
|
||||||
if var.starts_with("http://")
|
if var.starts_with("http://")
|
||||||
|| var.starts_with("https://")
|
|| var.starts_with("https://")
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[6449103750905854967, 17669692872757955279, 13069001215487072322, 11420464406527728232, 3770103091118609057, 5444526391971481782, 8634731337869623803, 5052021921702764563, 7041565893588933645, 11339769846046015954]
|
[6449103750905854967, 17669692872757955279, 13069001215487072322, 11420464406527728232, 3770103091118609057, 5444526391971481782, 14692042304579321130, 5052021921702764563, 7041565893588933645, 11339769846046015954]
|
||||||
@@ -1 +1 @@
|
|||||||
function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){let formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){let detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);if(event.type==="scroll")extend(serializeScrollEvent(event));return contents}function toSerializableResizeObserverSize(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]}function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}function serializeInputEvent(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents}function serializeWheelEvent(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}}function serializeTouchEvent(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:serializeTouchList(event.changedTouches),target_touches:serializeTouchList(event.targetTouches),touches:serializeTouchList(event.touches)}}function serializePointerEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}}function serializeTouchList(touchList){let serializedTouches=[];for(let i=0;i<touchList.length;i++){let touch=touchList[i];serializedTouches.push({identifier:touch.identifier,cliLine truncated
|
function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){let formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){let detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);if(event.type==="scroll")extend(serializeScrollEvent(event));return contents}function toSerializableResizeObserverSize(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]}function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}function serializeInputEvent(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents}function serializeWheelEvent(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}}function serializeTouchEvent(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:serializeTouchList(event.changedTouches),target_touches:serializeTouchList(event.targetTouches),touches:serializeTouchList(event.touches)}}function serializePointerEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}}function serializeTouchList(touchList){let serializedTouches=[];for(let i=0;i<touchList.length;i++){let touch=touchList[i];serializedTouches.push({identifier:touch.identifier,cliLine truncated
|
||||||
@@ -278,15 +278,6 @@ export class NativeInterpreter extends JSChannel_ {
|
|||||||
bubbles,
|
bubbles,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This should:
|
|
||||||
// - prevent form submissions from navigating
|
|
||||||
// - prevent anchor tags from navigating
|
|
||||||
// - prevent buttons from submitting forms
|
|
||||||
// - let the virtualdom attempt to prevent the event
|
|
||||||
if (event.type === "submit") {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// liveview does not have synchronous event handling, so we need to send the event to the host
|
// liveview does not have synchronous event handling, so we need to send the event to the host
|
||||||
if (this.liveview) {
|
if (this.liveview) {
|
||||||
// Okay, so the user might've requested some files to be read
|
// Okay, so the user might've requested some files to be read
|
||||||
|
|||||||
@@ -101,13 +101,7 @@ impl WebsysDom {
|
|||||||
|
|
||||||
// Prevent the default action if the user set prevent default on the event
|
// Prevent the default action if the user set prevent default on the event
|
||||||
let prevent_default = !event.default_action_enabled();
|
let prevent_default = !event.default_action_enabled();
|
||||||
// Prevent forms from submitting and redirecting
|
if prevent_default {
|
||||||
if name == "submit" {
|
|
||||||
// On forms the default behavior is not to submit, if prevent default is set then we submit the form
|
|
||||||
if !prevent_default {
|
|
||||||
web_sys_event.prevent_default();
|
|
||||||
}
|
|
||||||
} else if prevent_default {
|
|
||||||
web_sys_event.prevent_default();
|
web_sys_event.prevent_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user