Fix desktop effect race condition (#2313)

* don't poll desktop before the render has been applied

* fix desktop headless tests

* move edit queued code into the edit channel and add more comments
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-04-26 12:52:33 -04:00
1 parent a0e06271dd
commit 74352f2f61
6 files changed
+124 -48

No files matched your search

+1 -1
View File
@@ -1 +1 @@
2770005544568683192
5713307201725207733
+1 -1
View File
@@ -1 +1 @@
function retriveValues(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 retriveSelectValue(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(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 serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retriveValues(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=retriveSelectValue(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,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){let files=void 0;if(event.dataTransfer&&event.dataTransfer.files&&event.dataTransfer.files.length>0)files={files:{placeholder:[]}};return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files}};var getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSCLine truncated
function retriveValues(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 retriveSelectValue(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(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 serializeInputEvent=function(event,target){let contents={};if(target instanceof HTMLElement){let values=retriveValues(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=retriveSelectValue(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,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey}},serializeKeyboardEvent=function(event){return{char_code:event.charCode,is_composing:event.isComposing,key:event.key,alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,key_code:event.keyCode,shift_key:event.shiftKey,location:event.location,repeat:event.repeat,which:event.which,code:event.code}},serializeAnimationEvent=function(event){return{animation_name:event.animationName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement}},serializeDragEvent=function(event){let files=void 0;if(event.dataTransfer&&event.dataTransfer.files&&event.dataTransfer.files.length>0)files={files:{placeholder:[]}};return{mouse:{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,...serializeMouseEvent(event)},files}};var getTargetId=function(target){if(!(target instanceof Node))return null;let ourTarget=target,realId=null;while(realId==null){if(ourTarget===null)return null;if(ourTarget instanceof Element)realId=ourTarget.getAttribute("data-dioxus-id");ourTarget=ourTarget.parentNode}return parseInt(realId)},JSChannel_;if(RawInterpreter!==void 0&&RawInterpreter!==null)JSChannel_=RawInterpreter;class NativeInterpreter extends JSCLine truncated
+74 -43
View File
@@ -15,7 +15,7 @@ var JSChannel_: typeof BaseInterpreter;
if (RawInterpreter !== undefined && RawInterpreter !== null) {
// @ts-ignore - this is coming from the host
JSChannel_ = RawInterpreter;
};
}
export class NativeInterpreter extends JSChannel_ {
intercept_link_redirects: boolean;
@@ -40,28 +40,39 @@ export class NativeInterpreter extends JSChannel_ {
// attach an event listener on the body that prevents file drops from navigating
// this is because the browser will try to navigate to the file if it's dropped on the window
window.addEventListener("dragover", function (e) {
// // check which element is our target
if (e.target instanceof Element && e.target.tagName != "INPUT") {
window.addEventListener(
"dragover",
function (e) {
// // check which element is our target
if (e.target instanceof Element && e.target.tagName != "INPUT") {
e.preventDefault();
}
},
false
);
window.addEventListener(
"drop",
function (e) {
let target = e.target;
if (!(target instanceof Element)) {
return;
}
// Dropping a file on the window will navigate to the file, which we don't want
e.preventDefault();
}
}, false);
window.addEventListener("drop", function (e) {
let target = e.target;
if (!(target instanceof Element)) {
return;
}
// Dropping a file on the window will navigate to the file, which we don't want
e.preventDefault();
}, false);
},
false
);
// attach a listener to the route that listens for clicks and prevents the default file dialog
window.addEventListener("click", (event) => {
const target = event.target;
if (target instanceof HTMLInputElement && target.getAttribute("type") === "file") {
if (
target instanceof HTMLInputElement &&
target.getAttribute("type") === "file"
) {
// Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it
let target_id = getTargetId(target);
if (target_id !== null) {
@@ -79,12 +90,12 @@ export class NativeInterpreter extends JSChannel_ {
}
});
// @ts-ignore - wry gives us this
this.ipc = window.ipc;
// make sure we pass the handler to the base interpreter
const handler: EventListener = (event) => this.handleEvent(event, event.type, true);
const handler: EventListener = (event) =>
this.handleEvent(event, event.type, true);
super.initialize(root, handler);
}
@@ -99,7 +110,9 @@ export class NativeInterpreter extends JSChannel_ {
}
}
getClientRect(id: NodeId): { type: string; origin: number[]; size: number[]; } | undefined {
getClientRect(
id: NodeId
): { type: string; origin: number[]; size: number[] } | undefined {
const node = this.nodes[id];
if (node instanceof HTMLElement) {
const rect = node.getBoundingClientRect();
@@ -171,13 +184,15 @@ export class NativeInterpreter extends JSChannel_ {
// liveview does not have syncronous event handling, so we need to send the event to the host
if (this.liveview) {
// Okay, so the user might've requested some files to be read
if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
if (
target instanceof HTMLInputElement &&
(event.type === "change" || event.type === "input")
) {
if (target.getAttribute("type") === "file") {
this.readFiles(target, contents, bubbles, realId, name);
}
}
} else {
const message = this.serializeIpcMessage("user_event", body);
this.ipc.postMessage(message);
@@ -195,8 +210,6 @@ export class NativeInterpreter extends JSChannel_ {
}
}
// This should:
// - prevent form submissions from navigating
// - prevent anchor tags from navigating
@@ -210,7 +223,10 @@ export class NativeInterpreter extends JSChannel_ {
preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
}
if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
if (
preventDefaultRequests &&
preventDefaultRequests.includes(`on${event.type}`)
) {
event.preventDefault();
}
@@ -224,7 +240,11 @@ export class NativeInterpreter extends JSChannel_ {
}
}
handleClickNavigate(event: Event, target: Element, preventDefaultRequests: string) {
handleClickNavigate(
event: Event,
target: Element,
preventDefaultRequests: string
) {
// todo call prevent default if it's the right type of event
if (!this.intercept_link_redirects) {
return;
@@ -279,24 +299,30 @@ export class NativeInterpreter extends JSChannel_ {
}
}
waitForRequest(headless: boolean) {
fetch(new Request(this.editsPath))
.then(response => response.arrayBuffer())
.then(bytes => {
// In headless mode, the requestAnimationFrame callback is never called, so we need to run the bytes directly
if (headless) {
// @ts-ignore
this.run_from_bytes(bytes);
} else {
this.enqueueBytes(bytes);
requestAnimationFrame(() => {
this.flushQueuedBytes();
});
}
// Run the edits the next animation frame
rafEdits(headless: boolean, bytes: ArrayBuffer) {
// In headless mode, the requestAnimationFrame callback is never called, so we need to run the bytes directly
if (headless) {
// @ts-ignore
this.run_from_bytes(bytes);
this.waitForRequest(headless);
} else {
this.enqueueBytes(bytes);
requestAnimationFrame(() => {
this.flushQueuedBytes();
// With request animation frames, we use the next reqwest as a marker to know when the frame is done and it is safe to run effects
this.waitForRequest(headless);
});
}
}
waitForRequest(headless: boolean) {
fetch(new Request(this.editsPath))
.then((response) => response.arrayBuffer())
.then((bytes) => {
this.rafEdits(headless, bytes);
});
}
kickAllStylesheetsOnPage() {
// If this function is being called and we have not explicitly set kickStylesheets to true, then we should
@@ -314,7 +340,13 @@ export class NativeInterpreter extends JSChannel_ {
// A liveview only function
// Desktop will intercept the event before it hits this
async readFiles(target: HTMLInputElement, contents: SerializedEvent, bubbles: boolean, realId: NodeId, name: string) {
async readFiles(
target: HTMLInputElement,
contents: SerializedEvent,
bubbles: boolean,
realId: NodeId,
name: string
) {
let files = target.files!;
let file_contents: { [name: string]: number[] } = {};
@@ -388,7 +420,6 @@ function getTargetId(target: EventTarget): NodeId | null {
return parseInt(realId);
}
// function applyFileUpload() {
// let inputs = document.querySelectorAll("input");
// for (let input of inputs) {