js_of_ocaml
js_of_ocaml copied to clipboard
[BUG] Event handler not being executed
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
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
()
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.
What I meant is that if the event has already been triggered, you can just call run directly.
The issue is that the Wasm code is fetched and compiled asynchronously. So the
loadevent 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
runimmediately when thereadyStateiscomplete: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