Skip to content

Commit

Permalink
fix(onbeforeunload): an unsafe, cross-compatible hack to workaround #467
Browse files Browse the repository at this point in the history


This hack is needed to address <#467>
because [Dom.handler] <https://ocsigen.org/js_of_ocaml/latest/api/js_of_ocaml/Js_of_ocaml/Dom/index.html#val-handler>
appears to be incompatible with "onbeforeunload" and Firefox.

* Namely, the semantics of [Dom.handler] requires to "return true" to tell [Dom.handler] that we want to unload normally,
* but with some browsers such as Firefox we should actually "return undefined" (better than "return null" BTW),
* but then, "undefined" is seen as "false", implying [Dom.handler] will call "preventDefault",
* end_of_the_story.

Credits: the working code from this commit reuses a patch by @leunam217,
pfitaxel@15780b5

See also: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#example
  • Loading branch information
erikmd committed Mar 4, 2022
1 parent 4f37bb1 commit b9960f7
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
51 changes: 50 additions & 1 deletion src/app/learnocaml_exercise_main.ml
Expand Up @@ -288,7 +288,56 @@ let () =
Ace.focus ace ;
typecheck true
end ;
Window.onbeforeunload (fun _ -> (not (Ace.is_synchronized ace), false));
(* BEGIN HACK, to simplify one day with a later version of js_of_ocaml?
This is needed to address <https://github.com/ocaml-sf/learn-ocaml/issues/467>
because [Dom.handler] <https://ocsigen.org/js_of_ocaml/latest/api/js_of_ocaml/Js_of_ocaml/Dom/index.html#val-handler>
appears to be incompatible with "onbeforeunload" and Firefox.
* Namely, the semantics of [Dom.handler] requires to "return true" to tell [Dom.handler] that we want to unload normally,
* but with some browsers such as Firefox we should actually "return undefined" (better than "return null" BTW),
* but then, "undefined" is seen as "false", implying [Dom.handler] will call "preventDefault",
* end_of_the_story.
*)
(* Copy of private [Dom.window_event] *)
(*
let window_event' () : 'a #Dom.event Js.t = Js.Unsafe.pure_js_expr "event" in
(* Fixed copy of [Dom.handler], working for onbeforeunload
even for Firefox *)
let handler_onbeforeunload :
((#Js_of_ocaml.Dom_html.event as 'b) Js_of_ocaml__.Js.t ->
bool Js_of_ocaml__.Js.t) ->
(* ('a, 'b Js_of_ocaml__.Js.t) Js_of_ocaml.Dom_html.event_listener = *)
('a, 'b Js_of_ocaml__.Js.t -> bool Js.t) Js.meth_callback Js.opt =
fun f ->
Js.some
(Js.Unsafe.callback (fun e ->
(* depending on the internet explorer version, e can be null or undefined. *)
if not (Js.Opt.test (Js.some e))
then (
let e = window_event' () in
let res = f e in
if not (Js.to_bool res) then
(e##.returnValue := res; res)
else
Js.Unsafe.pure_js_expr "undefined"
) else (
let res = f e in
if not (Js.to_bool res) then
((Js.Unsafe.coerce e)##preventDefault (* ; res *))
else
(Js.Unsafe.delete (Js.Unsafe.coerce e) "returnValue";
Js.Unsafe.pure_js_expr "undefined")))) in
*)
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
let () =
Ace.register_sync_observer ace (fun sync ->
if not sync then prompt_before_unload ()
else resume_before_unload ()) in
(* END HACK *)
(* ---- return -------------------------------------------------------- *)
toplevel_launch >>= fun _ ->
typecheck false >>= fun () ->
Expand Down
10 changes: 0 additions & 10 deletions src/utils/js_utils.ml
Expand Up @@ -1158,16 +1158,6 @@ module Window = struct
let head win = Tyxml_js.Of_dom.of_head win##.document##.head
let onunload ?(win = Dom_html.window) f =
win##.onunload := Dom_html.handler (fun ev -> Js.bool (f ev))
let onbeforeunload ?(win = Dom_html.window) f =
win##.onbeforeunload := Dom_html.handler (fun ev ->
let (status, propagate) = f ev in
if status then (
Js.bool propagate
) else
(
Js.(Unsafe.eval_string "undefined");
)
)
let onresize ?(win = Dom_html.window) f =
win##.onresize := Dom_html.handler (fun ev -> Js.bool (f ev))
let prompt ?(win = Dom_html.window) ?(value = "") msg =
Expand Down
3 changes: 0 additions & 3 deletions src/utils/js_utils.mli
Expand Up @@ -384,9 +384,6 @@ module Window : sig
val onunload:
?win:Dom_html.window Js.t ->
(Dom_html.event Js.t -> bool) -> unit
val onbeforeunload:
?win:Dom_html.window Js.t ->
(Dom_html.event Js.t -> bool * bool) -> unit
val onhashchange:
?win:Dom_html.window Js.t ->
(Dom_html.hashChangeEvent Js.t -> bool) -> unit
Expand Down

0 comments on commit b9960f7

Please sign in to comment.