js_of_ocaml icon indicating copy to clipboard operation
js_of_ocaml copied to clipboard

[BUG] Event handler not being executed

Open bikallem opened this issue 7 months ago • 4 comments

Describe the bug I am trying the counters sample from ocaml-vdom repo (https://github.com/LexiFi/ocaml-vdom/tree/master/examples/counters). I have put the (modes wasm) in the dune file. However, trying to view the UI in the browser results in blank screen. I can see that both the counters.bc.wasm.js and counters.bc.wasm files are loaded correctly in the chrome inspect tab. There are no error in the console. It looks like https://github.com/LexiFi/ocaml-vdom/blob/master/examples/counters/counters.ml#L36 is never hit.

As an experiment I tried the following:

let () =
  let readystatechange = Dom_html.Event.make "readystatechange" in
  let _ =
    Dom_html.(
      addEventListener document readystatechange
        (handler (fun _ev ->             
             let state = document##.readyState in
             (* Console.console##log state; *)
             if Js.to_string state = "complete" then run ();
             Js._false))
        Js._false)
  in
  ()

This works intermittently, i.e. sometimes it correctly renders the counter ui and at other times, it renders blank. You have to refresh the browser a few times and that seems to do the trick.

Expected behavior The event handler should load/execute correctly when using ocaml-vdom/Js_browser.

Versions 6.0.1

bikallem avatar Apr 23 '25 21:04 bikallem

The issue is that the Wasm code is fetched and compiled asynchronously. So the load event has already been triggered when the code is executed. This does not happen in JavaScript since there is a mechanism to delay the load event until the contents of all script tags has been executed.

Maybe this can be fixed by generating a module (loaded with <script type="module">), which can contain top-level awaits, rather than a classic script, which much execute synchronously. I need to investigate this at some point.

As a workaround, you can call run immediately when the readyState is complete:

let () =
  if Js.to_string Dom_html.document##.readyState = "complete" then run () else
  let readystatechange = Dom_html.Event.make "readystatechange" in
  let _ =
    Dom_html.(
      addEventListener document readystatechange
        (handler (fun _ev ->             
             let state = document##.readyState in
             (* Console.console##log state; *)
             if Js.to_string state = "complete" then run ();
             Js._false))
        Js._false)
  in
  ()

vouillon avatar Apr 24 '25 15:04 vouillon

As a workaround, you can call run immediately when the readyState is complete:

let () = if Js.to_string Dom_html.document##.readyState = "complete" then run () else let readystatechange = Dom_html.Event.make "readystatechange" in let _ = Dom_html.( addEventListener document readystatechange (handler (fun _ev ->
let state = document##.readyState in (* Console.console##log state; *) if Js.to_string state = "complete" then run (); Js._false)) Js._false) in ()

I believe I tried the last suggestion of adding run() inside an event handler. The issue is that even the event handler doesn't seem to be working consistently. Sometimes they execute correctly and everything renders okay. Other times you have to refresh the browser page a few time before they can render correctly.

bikallem avatar Apr 24 '25 17:04 bikallem

What I meant is that if the event has already been triggered, you can just call run directly.

vouillon avatar Apr 24 '25 20:04 vouillon

The issue is that the Wasm code is fetched and compiled asynchronously. So the load event has already been triggered when the code is executed. This does not happen in JavaScript since there is a mechanism to delay the load event until the contents of all script tags has been executed.

Maybe this can be fixed by generating a module (loaded with <script type="module">), which can contain top-level awaits, rather than a classic script, which much execute synchronously. I need to investigate this at some point.

As a workaround, you can call run immediately when the readyState is complete:

let () = if Js.to_string Dom_html.document##.readyState = "complete" then run () else let readystatechange = Dom_html.Event.make "readystatechange" in let _ = Dom_html.( addEventListener document readystatechange (handler (fun _ev ->
let state = document##.readyState in (* Console.console##log state; *) if Js.to_string state = "complete" then run (); Js._false)) Js._false) in ()

Maybe we should have a helper in the lib to do that

hhugo avatar May 09 '25 08:05 hhugo