this repo has no description

feat: cross-origin library loading for interactive OCaml cells

Enable loading .cma.js files from a different origin (e.g., ocaml.org)
by working around Chrome's CORB which blocks cross-origin importScripts
for files with embedded binary CMI data.

worker.ml: detect cross-origin URLs in import_scripts and use
synchronous XHR + eval() instead of importScripts. Same-origin
URLs continue to use the standard path.

jtw_client.ml: create blob: URL wrapper for cross-origin worker.js
(browsers block cross-origin Worker construction), derive stdlib_dcs
URL from findlib_index base, switch to eval_stream for streaming
phrase-by-phrase output.

interactive_extension.ml: always load x-ocaml.js and worker.js from
local _x-ocaml/ path (same-origin), communicate universe URL via
<meta> tag. Set backend to "jtw" when a universe is configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+55 -17
+55 -17
src/jtw_client.ml
··· 11 11 mutable on_message_cb : X_protocol.response -> unit; 12 12 } 13 13 14 + let is_cross_origin url = 15 + try 16 + let page_origin = Jv.to_string (Jv.get (Brr.Window.to_jv Brr.G.window) "origin") in 17 + let url_obj = Jv.new' (Jv.get Jv.global "URL") [| Jv.of_string url |] in 18 + let url_origin = Jv.to_string (Jv.get url_obj "origin") in 19 + page_origin <> url_origin 20 + with _ -> false 21 + 14 22 let make url = 15 - let client = Jtw.create url in 23 + let effective_url = 24 + if is_cross_origin url then begin 25 + (* Cross-origin workers are blocked by browsers. Work around this by 26 + creating a blob: URL that uses importScripts to load the real script. *) 27 + let js_code = Printf.sprintf {|importScripts("%s");|} url in 28 + let blob = Jv.new' (Jv.get Jv.global "Blob") 29 + [| Jv.of_jv_array [| Jv.of_string js_code |]; 30 + Jv.obj [| "type", Jv.of_string "application/javascript" |] |] in 31 + let blob_url = Jv.to_string 32 + (Jv.call (Jv.get Jv.global "URL") "createObjectURL" [| blob |]) in 33 + blob_url 34 + end else 35 + url 36 + in 37 + let client = Jtw.create effective_url in 16 38 { client; on_message_cb = (fun _ -> ()) } 17 39 18 40 let on_message t fn = t.on_message_cb <- fn ··· 97 119 (loc, `String t.type_str, tail) 98 120 99 121 let init ?(findlib_requires = []) ?findlib_index t = 122 + (* Derive stdlib_dcs from findlib_index base URL so cross-origin universes 123 + can find their dynamic_cmis.json at the correct absolute URL. *) 124 + let stdlib_dcs = match findlib_index with 125 + | Some fi -> 126 + (match String.rindex_opt fi '/' with 127 + | Some i -> Some (String.sub fi 0 (i + 1) ^ "lib/ocaml/dynamic_cmis.json") 128 + | None -> None) 129 + | None -> None 130 + in 100 131 let config : Msg.init_config = { 101 132 findlib_requires; 102 - stdlib_dcs = None; 133 + stdlib_dcs; 103 134 findlib_index; 104 135 } in 105 136 Lwt.async (fun () -> ··· 109 140 110 141 let post t (req : X_protocol.request) = 111 142 match req with 112 - | X_protocol.Eval (id, line_number, code) -> 143 + | X_protocol.Eval (id, _line_number, code) -> 144 + let stream = Jtw.eval_stream t.client code in 113 145 Lwt.async (fun () -> 114 - let open Lwt.Infix in 115 146 Lwt.catch (fun () -> 116 - Jtw.eval t.client code >|= fun output -> 117 - let outputs = ref [] in 118 - if output.caml_ppf <> "" then 119 - outputs := X_protocol.Meta output.caml_ppf :: !outputs; 120 - if output.stdout <> "" then 121 - outputs := X_protocol.Stdout output.stdout :: !outputs; 122 - if output.stderr <> "" then 123 - outputs := X_protocol.Stderr output.stderr :: !outputs; 124 - let outputs = List.rev !outputs in 125 - if line_number > 0 then 126 - respond t (X_protocol.Top_response_at (id, line_number, outputs)) 127 - else 128 - respond t (X_protocol.Top_response (id, outputs))) 147 + Lwt_stream.iter (function 148 + | Jtw.Phrase { loc; caml_ppf; _ } -> 149 + let outputs = 150 + if caml_ppf <> "" then [X_protocol.Meta caml_ppf] else [] 151 + in 152 + if outputs <> [] then 153 + respond t (X_protocol.Top_response_at (id, loc, outputs)) 154 + | Jtw.Done { stdout; stderr; caml_ppf; _ } -> 155 + let outputs = ref [] in 156 + if caml_ppf <> "" then 157 + outputs := X_protocol.Meta caml_ppf :: !outputs; 158 + if stdout <> "" then 159 + outputs := X_protocol.Stdout stdout :: !outputs; 160 + if stderr <> "" then 161 + outputs := X_protocol.Stderr stderr :: !outputs; 162 + respond t (X_protocol.Top_response (id, List.rev !outputs)) 163 + | Jtw.Error msg -> 164 + respond t (X_protocol.Top_response 165 + (id, [X_protocol.Stderr msg])) 166 + ) stream) 129 167 (fun _exn -> 130 168 respond t (X_protocol.Top_response 131 169 (id, [X_protocol.Stderr "Internal error during evaluation"]));