fix: Handle unicode in webview events. (#3386)
* Handle unicode in webview events. --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
20cdafbf16
commit
6755accbdc
7 files changed
+60
-18
No files matched your search
Generated
+1
@@ -3679,6 +3679,7 @@ name = "dioxus-desktop"
|
|||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"base64 0.22.1",
|
||||||
"cocoa 0.25.0",
|
"cocoa 0.25.0",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ async-trait = "0.1.68"
|
|||||||
tao = { workspace = true, features = ["rwh_05"] }
|
tao = { workspace = true, features = ["rwh_05"] }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
dioxus-history.workspace = true
|
dioxus-history.workspace = true
|
||||||
|
base64.workspace = true
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ fn app() -> Element {
|
|||||||
test_form_input {}
|
test_form_input {}
|
||||||
test_form_submit {}
|
test_form_submit {}
|
||||||
test_select_multiple_options {}
|
test_select_multiple_options {}
|
||||||
|
test_unicode {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,3 +577,29 @@ fn test_select_multiple_options() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_unicode() -> Element {
|
||||||
|
// emulate an oninput event with a unicode character
|
||||||
|
utils::mock_event_with_extra(
|
||||||
|
"unicode",
|
||||||
|
r#"new InputEvent("input", {
|
||||||
|
inputType: 'insertText',
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
})"#,
|
||||||
|
r#"
|
||||||
|
element.value = "🦀";
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
input {
|
||||||
|
id: "unicode",
|
||||||
|
oninput: move |event| {
|
||||||
|
println!("{:?}", event.data);
|
||||||
|
assert_eq!(event.data.value(), "🦀");
|
||||||
|
RECEIVED_EVENTS.with_mut(|x| *x += 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ use crate::{
|
|||||||
waker::tao_waker,
|
waker::tao_waker,
|
||||||
Config, DesktopContext, DesktopService,
|
Config, DesktopContext, DesktopService,
|
||||||
};
|
};
|
||||||
|
use base64::prelude::BASE64_STANDARD;
|
||||||
use dioxus_core::{Runtime, ScopeId, VirtualDom};
|
use dioxus_core::{Runtime, ScopeId, VirtualDom};
|
||||||
use dioxus_document::Document;
|
use dioxus_document::Document;
|
||||||
use dioxus_history::{History, MemoryHistory};
|
use dioxus_history::{History, MemoryHistory};
|
||||||
@@ -48,7 +49,9 @@ impl WebviewEdits {
|
|||||||
request: wry::http::Request<Vec<u8>>,
|
request: wry::http::Request<Vec<u8>>,
|
||||||
responder: wry::RequestAsyncResponder,
|
responder: wry::RequestAsyncResponder,
|
||||||
) {
|
) {
|
||||||
let body = self.try_handle_event(request).unwrap_or_default();
|
let body = self
|
||||||
|
.try_handle_event(request)
|
||||||
|
.expect("Writing bodies to succeed");
|
||||||
responder.respond(wry::http::Response::new(body))
|
responder.respond(wry::http::Response::new(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,13 +59,27 @@ impl WebviewEdits {
|
|||||||
&self,
|
&self,
|
||||||
request: wry::http::Request<Vec<u8>>,
|
request: wry::http::Request<Vec<u8>>,
|
||||||
) -> Result<Vec<u8>, serde_json::Error> {
|
) -> Result<Vec<u8>, serde_json::Error> {
|
||||||
let data_from_header = request
|
use serde::de::Error;
|
||||||
|
|
||||||
|
// todo(jon):
|
||||||
|
//
|
||||||
|
// I'm a small bit worried about the size of the header being too big on some platforms.
|
||||||
|
// It's unlikely we'll hit the 256k limit (from 2010 browsers...) but it's important to think about
|
||||||
|
// https://stackoverflow.com/questions/3326210/can-http-headers-be-too-big-for-browsers
|
||||||
|
//
|
||||||
|
// Also important to remember here that we don't pass a body from the JavaScript side of things
|
||||||
|
let data = request
|
||||||
.headers()
|
.headers()
|
||||||
.get("dioxus-data")
|
.get("dioxus-data")
|
||||||
.map(|f| f.as_bytes())
|
.ok_or_else(|| Error::custom("dioxus-data header not set"))?;
|
||||||
.expect("dioxus-data header is not a string");
|
|
||||||
|
|
||||||
let response = match serde_json::from_slice(data_from_header) {
|
let as_utf = std::str::from_utf8(data.as_bytes())
|
||||||
|
.map_err(|_| Error::custom("dioxus-data header is not a valid (utf-8) string"))?;
|
||||||
|
|
||||||
|
let data_from_header = base64::Engine::decode(&BASE64_STANDARD, as_utf)
|
||||||
|
.map_err(|_| Error::custom("dioxus-data header is not a base64 string"))?;
|
||||||
|
|
||||||
|
let response = match serde_json::from_slice(&data_from_header) {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
// we need to wait for the mutex lock to let us munge the main thread..
|
// we need to wait for the mutex lock to let us munge the main thread..
|
||||||
let _lock = crate::android_sync_lock::android_runtime_lock();
|
let _lock = crate::android_sync_lock::android_runtime_lock();
|
||||||
@@ -79,15 +96,9 @@ impl WebviewEdits {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = match serde_json::to_vec(&response) {
|
serde_json::to_vec(&response).inspect_err(|err| {
|
||||||
Ok(body) => body,
|
tracing::error!("failed to serialize SynchronousEventResponse: {err:?}");
|
||||||
Err(err) => {
|
})
|
||||||
tracing::error!("failed to serialize SynchronousEventResponse: {err:?}");
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_html_event(&self, event: HtmlEvent) -> SynchronousEventResponse {
|
pub fn handle_html_event(&self, event: HtmlEvent) -> SynchronousEventResponse {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[6449103750905854967, 17669692872757955279, 13069001215487072322, 11420464406527728232, 3770103091118609057, 5444526391971481782, 12156139214887111728, 5052021921702764563, 12925655762638175824, 5638004933879392817]
|
[6449103750905854967, 17669692872757955279, 13069001215487072322, 11420464406527728232, 3770103091118609057, 5444526391971481782, 10130882040196587188, 5052021921702764563, 12925655762638175824, 5638004933879392817]
|
||||||
@@ -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){const 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){const 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);return contents}var toSerializableResizeObserverSize=function(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)}}var serializeInputEvent=function(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},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(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}},serializeMouseEvent=function(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,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,Line 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){const 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){const 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);return contents}var toSerializableResizeObserverSize=function(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)}}var serializeInputEvent=function(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},serializeWheelEvent=function(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}},serializeTouchEvent=function(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:event.changedTouches,target_touches:event.targetTouches,touches:event.touches}},serializePointerEvent=function(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}},serializeMouseEvent=function(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,offset_x:event.offsetX,offset_y:event.offsetY,page_x:event.pageX,page_y:event.pageY,Line truncated
|
||||||
@@ -474,8 +474,10 @@ function handleVirtualdomEventSync(
|
|||||||
// https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)
|
// https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)
|
||||||
//
|
//
|
||||||
// the issue here isn't that big, tbh, but there's a small chance we lose the event due to header max size (16k per header, 32k max)
|
// the issue here isn't that big, tbh, but there's a small chance we lose the event due to header max size (16k per header, 32k max)
|
||||||
xhr.setRequestHeader("dioxus-data", contents);
|
const contents_bytes = new TextEncoder().encode(contents);
|
||||||
xhr.send(contents);
|
const contents_base64 = btoa(String.fromCharCode.apply(null, contents_bytes));
|
||||||
|
xhr.setRequestHeader("dioxus-data", contents_base64)
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
// Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
|
// Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
|
||||||
return JSON.parse(xhr.responseText);
|
return JSON.parse(xhr.responseText);
|
||||||
|
|||||||
Reference in new issue
Block a user