ocsigen / js_of_ocaml

Compiler from OCaml to Javascript.
http://ocsigen.org/js_of_ocaml/
Other
943 stars 185 forks source link

[BUG] semantics of (onbeforeunload, Dom_html.handler) incompatible with major browsers #1436

Open erikmd opened 1 year ago

erikmd commented 1 year ago

Describe the bug Adding/removing hooks for the beforeunload event with Dom_html.window##.onbeforeunload := Dom_html.handler … is not working out-of-the-box with both Chromium, Firefox, and Safari. So I had to devise the following hack (unsafe JS code):

  let prompt_before_unload () : unit =
    Js.Unsafe.js_expr "window.onbeforeunload = function(e) {e.preventDefault(); return false;}" in

  let resume_before_unload () : unit =
    Js.Unsafe.js_expr "window.onbeforeunload = null" in

  (…)
  if not sync then prompt_before_unload () else resume_before_unload ()
  (…)

Source code: https://github.com/ocaml-sf/learn-ocaml/blob/a6e4c5e6/src/app/learnocaml_exercise_main.ml#L292-L310

Expected behavior I open this PR in case it is possible for you to tweak Dom_html.handler, and be able to directly use js_of_ocaml's abstractions (not Js.Unsafe.js_expr) for this use case, and stay compatible with (Chromium, Firefox, Safari…)

FTR → I had sketched a possible explanation of why one such tentative code doesn't work in Firefox here: https://github.com/ocaml-sf/learn-ocaml/issues/467#issuecomment-1059802295

Anyway, if ever you'd think it is already possible to implement the snippet above without Js.Unsafe.js_expr (meaning there's no "bug" over there), I'm taker of any idiomatic snippet 👍

Versions Version of packages used to reproduce the bug:

(I'll be happy to provide more details on the versions used if need be)

Cc @yurug FYI

hhugo commented 1 year ago

I think I understand the issue. You should be able to avoid the usage of js_expr by using a well constrained Obj.magic

let unsafe_cast (x : ('c, 'a -> 'b) Js.meth_callback) :  ('c, 'a) Dom.event_listener = Obj.magic x;;

let prompt_before_unload () : unit =
    let cb (e : Dom_html.event Js.t) =  Dom.preventDefault e; Js._false in
    Dom_html.window##.onbeforeunload := unsafe_cast (Js.Unsafe.callback cb);;

let resume_before_unload () : unit = Dom_html.window##.onbeforeunload := Dom.no_handler
erikmd commented 1 year ago

Thanks a lot, @hhugo ! Your approach looks better indeed :)

I'll test this as soon as possible in learn-ocaml.

Just asking in the meantime: do you believe the prompt_before_unload could itself be rewritten directly as an instance of Dom_html.handler ? possibly, modifying/generalizing Dom_html.handler's implementation… See also https://github.com/ocaml-sf/learn-ocaml/issues/467#issuecomment-1059802295

Anyway, more testing is needed! (and sorry if I can't send more feedback quickly enough…)

hhugo commented 1 year ago

Just asking in the meantime: do you believe the prompt_before_unload could itself be rewritten directly as an instance of Dom_html.handler ? possibly, modifying/generalizing Dom_html.handler's implementation…

I'm not sure, you should keep this issue open until one figure out what to do.