ghcjs / jsaddle

JavaScript interface that works with GHCJS or GHC
118 stars 63 forks source link

Q: Are synchronous, UI-blocking XHR requests meant to be issued by JSaddle? #51

Open Wizek opened 6 years ago

Wizek commented 6 years ago

I've found them when I started digging in the network tab after noticing that my UI was feeling sluggish to use, especially when typing in input fields. I am not yet sure if this sluggishness is due to JSaddle or some other portion of my code, but seeing the following deprecation warning from Chrome gave me pause and thought to ask.

jsaddle.js:134 [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.

I also seem to remember that the UI was more responsive before I upgraded reflex-dom 0.3 to 0.4 — the former of which doesn't use jsaddle, and drives a webkitgtk2 window more directly AFAIU.

I've compiled with GHC, JSaddle.Warp.

I've also noticed a 101 pending websocket connection in the network list, which seemed to have ongoing back and forth messages, so I am all the more confused by the presence of sync xhr.

Is this all by design? Or am I observing some edge-case?

ryantrinkle commented 6 years ago

The issue is that certain situations in javascript require synchronous action. For example, if you want to call e.preventDefault or e.stopPropagation on an event, you must do this before returning. For these situations, JSaddle doesn't have much choice other than using something synchronous, and synchronous XHR gets the job done. It's not a great state of affairs, and there's probably some room for improvement, but mostly this is a workaround for the fundamental semantics of JS not being quite what we'd like.

One thing the current version of reflex-dom doesn't do, but it could do, would be to only mark certain events as synchronous. reflex-dom has a distinction between "event filters", which always run and may call things like preventDefault/stopPropagation, and "event handlers", which only run if someone is actually listening, and cannot call those functions. We could change the way that reflex-dom registers events, such that only filters are registered as synchronous handlers.

Wizek commented 6 years ago

Thanks for the pointer @ryantrinkle. Yeah, preventDefault does play a role in my codebase. E.g. textarea where enter means submit and shift+enter means newline.

I wonder if it could make sense for me/reflex-dom/jsaddle to do the preventDefault on the JS side entirely. Since then it seems it's laughably little code that instigates these ui-blocking sync xhr calls:

enterMayShift <- wrapDomEvent
  (_textAreaElement_raw inp)
  (`DOM.on` DomEv.keyDown)
  (do
    e <- DOM.event
    shiftAndCode <- DomKEv.getShiftKey e &&& DomKEv.getKeyCode e
    when (shiftAndCode == (False, 13)) $ do
      DOM.preventDefault
    return shiftAndCode
    )

But if I were to rewrite this to be native js, how could I hook that in here? How could I register the listener on the correct element? On PostBuild event perhaps? And maybe even more importantly, how can I hook it back to jsaddle/reflex-dom? How will I be able to trigger a reflex event when e.keyCode == 13 && !e.shiftKey? Maybe just pointing me towards a similar example where JS is triggering reflex events could give me enough of an idea to figure the rest out.