Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 13 additions & 33 deletions API-STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This document lists standard JavaScript/Web APIs and their support status in js_
| JSON | Yes | Yes | `Json` · Brr: `Brr.Json` |
| Date | Yes | No | `Js` (Js.date) |
| Math | Yes | No | `Js` (Js.math) |
| Promise | Partial | Yes | `Js.Promise` / lwt bindings — [#2031](https://github.com/ocsigen/js_of_ocaml/issues/2031) · Brr: `Jv.Promise`, `Fut` |
| Promise | Yes | Yes | `Promise` · Brr: `Jv.Promise`, `Fut` |
| Console | Yes | Yes | `Console` · Brr: `Brr.Console` |

## DOM
Expand All @@ -44,7 +44,7 @@ This document lists standard JavaScript/Web APIs and their support status in js_
| API | jsoo | Brr | jsoo Module / Notes |
|-----|------|-----|---------------------|
| XMLHttpRequest | Yes | No | `XmlHttpRequest` |
| Fetch API | No | Yes | [#596](https://github.com/ocsigen/js_of_ocaml/issues/596) · Brr: `Brr_io.Fetch` |
| Fetch API | Yes | Yes | `Fetch` · Brr: `Brr_io.Fetch` |
| MessageChannel / MessagePort | No | Yes | [#1464](https://github.com/ocsigen/js_of_ocaml/issues/1464) · Brr: `Brr_io.Message` |
| WebSocket | Yes | Yes | `WebSockets` · Brr: `Brr_io.Websocket` |
| Server-Sent Events (EventSource) | Yes | No | `EventSource` |
Expand Down Expand Up @@ -111,9 +111,9 @@ This document lists standard JavaScript/Web APIs and their support status in js_
| Wheel Events | Yes | Yes | `Dom_html` · Brr: `Brr.Ev.Wheel` |
| Drag and Drop Events | Yes | Yes | `Dom_html` · Brr: `Brr.Ev.Drag` |
| Clipboard API | No | Yes | Brr: `Brr_io.Clipboard` |
| Fullscreen API | Partial | Yes | `Dom_html` (element/document fullscreen — `requestFullscreen_`/`exitFullscreen_` returns Promise, not yet typed) · Brr: `Brr.El.request_fullscreen`, `Brr.Document.exit_fullscreen` |
| Fullscreen API | Yes | Yes | `Dom_html` (`requestFullscreen` / `exitFullscreen`) · Brr: `Brr.El.request_fullscreen`, `Brr.Document.exit_fullscreen` |
| Gamepad API | No | No | |
| Pointer Lock API | Partial | Yes | `Dom_html` (element/document pointer lock — `requestPointerLock_` returns Promise, not yet typed) · Brr: `Brr.El.request_pointer_lock` |
| Pointer Lock API | Yes | Yes | `Dom_html` (`requestPointerLock`) · Brr: `Brr.El.request_pointer_lock` |
| Selection API | Yes | No | `Dom_html` (`selection`, `range`) |

## Observers
Expand Down Expand Up @@ -151,12 +151,12 @@ This document lists standard JavaScript/Web APIs and their support status in js_
|-----|------|-----|---------------------|
| requestAnimationFrame | Yes | Yes | `Dom_html` (window) · Brr: `Brr.G.request_animation_frame` |
| Performance API (now, mark, measure, entries) | Yes | Partial | `Performance` · Brr: `Brr.Performance` |
| Web Animations API | Yes | No | `Dom_html` (`animate`, `getAnimations`; `animation`, `animationEffect`, `keyframeEffect`, `computedKeyframe`, `animationTimeline`, `documentTimeline`, `optionalEffectTiming`, `computedEffectTiming`, `keyframeAnimationOptions`, `animationPlaybackEvent`); `Animation.finished`/`ready` Promise getters omitted — use `onfinish` event and `pending` property |
| Web Animations API | Yes | No | `Dom_html` (`animate`, `getAnimations`; `animation`, `animationEffect`, `keyframeEffect`, `computedKeyframe`, `animationTimeline`, `documentTimeline`, `optionalEffectTiming`, `computedEffectTiming`, `keyframeAnimationOptions`, `animationPlaybackEvent`) |
| Web Components (Custom Elements, Shadow DOM) | Partial | No | `Dom_html` (Shadow DOM — `attachShadow`, `shadowRoot`, `assignedSlot`, `slot`); Custom Elements not bound |
| Web Crypto API | No | Yes | Brr: `Brr_webcrypto` |
| Notifications API | No | Yes | Brr: `Brr_io.Notification` |
| Broadcast Channel API | No | Yes | Brr: `Brr_io.Message.Broadcast_channel` |
| AbortController / AbortSignal | No | Yes | Brr: `Brr.Abort` |
| AbortController / AbortSignal | Yes | Yes | `Abort` · Brr: `Brr.Abort` |

---

Expand All @@ -167,17 +167,7 @@ widely used the API is in modern web development, whether an open issue exists,
whether Brr already provides it (proving OCaml ecosystem demand), and whether
other APIs depend on it.

### Tier 1 — Critical

These form a dependency chain and should be tackled together.

| API | Issue | In Brr | Why |
|-----|-------|--------|-----|
| Promise (upgrade to full) | [#2031](https://github.com/ocsigen/js_of_ocaml/issues/2031) | Yes | Core async primitive of JavaScript. Prerequisite for idiomatic Fetch, Web Crypto, and most modern APIs. |
| AbortController / AbortSignal | — | Yes | Required for cancelling Fetch requests, event listeners, and streams. Foundational primitive that Fetch and Streams depend on. |
| Fetch API | [#596](https://github.com/ocsigen/js_of_ocaml/issues/596) | Yes | The standard replacement for XHR. Virtually every modern web app uses it. The single most impactful missing binding. |

### Tier 2 — High
### Tier 1 — High

| API | Issue | In Brr | Why |
|-----|-------|--------|-----|
Expand All @@ -188,11 +178,10 @@ These form a dependency chain and should be tackled together.
| MessageChannel / MessagePort | [#1464](https://github.com/ocsigen/js_of_ocaml/issues/1464) | Yes | Structured communication between Workers, iframes, and windows. Needed for non-trivial Worker usage. |
| Notifications API | — | Yes | Common engagement feature in web apps. Small API surface. |

### Tier 3 — Medium
### Tier 2 — Medium

| API | Issue | In Brr | Why |
|-----|-------|--------|-----|
| Fullscreen API (upgrade) | — | Yes | Media players, presentations, games. Element/Document surface is bound; `requestFullscreen`/`exitFullscreen` need a typed Promise return (currently `requestFullscreen_`/`exitFullscreen_` return `unit`). |
| Broadcast Channel API | — | Yes | Cross-tab communication (sync auth state, shared data). Simple API. |
| Web Audio API | — | Yes | Audio processing, games, music apps. Large API surface but well-defined. |
| Media Capture (getUserMedia) | — | Yes | Video calls, camera/mic access. Growing use with remote work tooling. |
Expand All @@ -202,9 +191,8 @@ These form a dependency chain and should be tackled together.
| Streams API | — | No | Modern data processing. Fetch response bodies are ReadableStreams. Increasingly foundational. |
| History API (upgrade to full) | — | Yes | SPA routing depends on pushState/replaceState. Current binding is limited. |
| HTMLMediaElement (upgrade to full) | — | Yes | Better audio/video control. Current binding only covers basic element types. |
| Pointer Lock API (upgrade) | — | Yes | 3D/game applications. Element/Document surface is bound; `requestPointerLock` needs a typed Promise return. |

### Tier 4 — Lower priority
### Tier 3 — Lower priority

| API | Issue | In Brr | Why |
|-----|-------|--------|-----|
Expand All @@ -220,17 +208,9 @@ These form a dependency chain and should be tackled together.

### Suggested implementation order

1. **Promise (full) + AbortController + Fetch API** — as a single effort, since
they are interdependent. Closes the largest gap and addresses the oldest open
feature request ([#596](https://github.com/ocsigen/js_of_ocaml/issues/596), from 2017).
2. **Web Crypto API** — security-critical, no safe workaround.
3. **Clipboard API** — small surface, high user-facing value.
4. **Service Workers + Cache API** — enables PWAs, the main class of apps jsoo
1. **Web Crypto API** — security-critical, no safe workaround.
2. **Clipboard API** — small surface, high user-facing value.
3. **Service Workers + Cache API** — enables PWAs, the main class of apps jsoo
cannot fully support today.
5. **MessageChannel / Notifications / Broadcast Channel** — small APIs that fill
4. **MessageChannel / Notifications / Broadcast Channel** — small APIs that fill
out the remaining communication gaps.
6. **Promise-typed Fullscreen / Pointer Lock / Animation** — once
Promise is upgraded, replace the placeholder `requestFullscreen_` /
`requestPointerLock_` / `exitFullscreen_` methods with proper typed
bindings, and add `Animation.finished` / `Animation.ready` (currently
omitted because their only purpose is to be awaited).
17 changes: 17 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
* Lib: add `Performance` module (#2221)
* Put more values into global variables (#2211)
* Runtime: intial support for quickjs-ng
* Lib: add `Promise` module — bindings to JavaScript promises that
preserve type safety in the presence of `'a Promise.t Promise.t` (#2031)
* Lib: add Lwt interop for `Promise` in `Js_of_ocaml_lwt.Promise`
(`to_lwt` / `of_lwt`)
* Lib: add Promise-typed `Dom_html` bindings — `requestFullscreen`,
`requestPointerLock`, `exitFullscreen`, `mediaElement.play`, and
`Animation.{finished,ready}`. Existing fire-and-forget forms are kept
under the `_`-suffixed names
* Lib: add `Fetch` and `Abort` modules — Fetch API binding with a typed
`AbortController`/`AbortSignal` primitive for cancellation (#596)

## Bug fixes
* Compiler: fix reference unboxing (#2210)
Expand All @@ -33,6 +43,13 @@
* Lib: defer `Intl.{Collator,DateTimeFormat,...}` member lookups so the
`Intl` module no longer throws at load time on hosts where
`globalThis.Intl` is undefined
* Lib: fix method-name mangling on a few bindings where the OCaml name
resolved to the wrong JavaScript identifier:
`Typed_array._BYTES_PER_ELEMENT` (called `BYTES_PER`),
`WebGL._MAX_RENDERBUFFER_SIZE` (called `MAX_RENDERBUFFER`), and
`canvasElement.toDataURL_type_compression` (called `toDataURL_type`).
Renamed to `_BYTES_PER_ELEMENT_`, `_MAX_RENDERBUFFER_SIZE_`, and
`toDataURL_compression` respectively

# 6.3.2 (2026-02-15) - Lille

Expand Down
56 changes: 56 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,59 @@ opam install odoc lwt_log yojson ocp-indent graphics higlo
### Running the tests

Run `make tests`.

## Library binding conventions

When adding new bindings under `lib/js_of_ocaml/`, follow these rules so
that the role of an underscore in a method name is unambiguous.

### Underscores in method names

- **Leading `_`** — pure name mangling. Use it when the JavaScript
identifier is an OCaml keyword (`type`, `method`, `open`, `match`,
`end`, `as`, `effect`, `class`, `for`, `assert`, …) or starts with an
uppercase letter (which OCaml method names cannot). The `ppx_js_internal`
preprocessor strips the leading underscore in the generated JS, so
`method _type` reads `.type` on the JS side. Examples: `_method`,
`_type`, `_open`, `_URL`, `_PI`, `_HORIZONTAL_AXIS`.

- **Trailing `_`** — *not* mangling. Reserved for a deliberate alternate
form of a method that already exists under the unsuffixed name:

- **Argument-overload variant.** Same JS method, different OCaml
signature (e.g. extra optional flag, different result type).
Existing examples: `getInt16_` / `setInt16_` (DataView, take a
little-endian boolean), `bindBuffer_` / `bindFramebuffer_` /
`bindTexture_` (WebGL, accept an `opt` binding).

- **Legacy fire-and-forget kept beside a Promise-typed version.**
When a JS API was previously bound as `foo : unit Js.meth` (ignoring
the returned `Promise`), and you are adding the proper
`foo : unit Promise.t Js.meth`, rename the legacy method to `foo_`
and put the Promise version on the unsuffixed `foo`. Document the
legacy form as fire-and-forget. Existing examples:
`requestFullscreen_`, `exitFullscreen_`, `requestPointerLock_`,
`play_` (HTMLMediaElement).

Older code mixes these roles (`open_`, `type_`, `method_`, `assert_`,
`effect_` use trailing `_` for keyword escape rather than leading);
those are grandfathered. New code should follow the split above.

### Multi-underscore names

The mangler strips at most one leading `_` and then drops the last `_`
and everything after it. So a name with more than one non-leading
underscore resolves to something shorter than you might expect:

| OCaml method | Calls JS identifier |
| ----------------------- | ------------------- |
| `abort_with_reason` | `abort_with` |
| `_BYTES_PER_ELEMENT` | `BYTES_PER` |
| `toDataURL_type_quality`| `toDataURL_type` |

If the underlying JS identifier itself contains underscores (e.g. a
WebGL constant like `MAX_RENDERBUFFER_SIZE`), terminate the OCaml name
with a trailing `_` to anchor the rindex on the very end:
`_MAX_RENDERBUFFER_SIZE_`. If you want a single overload disambiguator
on a method like `toDataURL`, give the OCaml name exactly one trailing
`_<suffix>` (e.g. `toDataURL_quality`, not `toDataURL_type_quality`).
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ let runtime =
; nat
; obj
; parsing
; promise
; stdlib
; sys
; str
Expand Down
2 changes: 2 additions & 0 deletions compiler/lib-runtime-files/tests/all.ml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ let%expect_test _ =
+obj.js
+parsing.js
+prng.js
+promise.js
+runtime_events.js
+stdlib.js
+str.js
Expand Down Expand Up @@ -88,6 +89,7 @@ let%expect_test _ =
+obj.js
+parsing.js
+prng.js
+promise.js
+runtime_events.js
+stdlib.js
+str.js
Expand Down
19 changes: 19 additions & 0 deletions compiler/tests-jsoo/dune
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
test_custom_name
test_text_codec_fallback
test_unix
test_promise
test_lwt_promise
calc_parser
calc_lexer))
(libraries unix compiler-libs.common js_of_ocaml-compiler)
Expand Down Expand Up @@ -120,6 +122,23 @@
(javascript_files custom.js custom.wat))
(modes js wasm))

(test
(name test_promise)
(modules test_promise)
(libraries js_of_ocaml)
(modes js wasm)
(preprocess
(pps ppx_js_internal)))

(test
(name test_lwt_promise)
(package js_of_ocaml-lwt)
(modules test_lwt_promise)
(libraries js_of_ocaml js_of_ocaml-lwt lwt)
(modes js wasm)
(preprocess
(pps ppx_js_internal)))

(library
(name test_custom_name)
(modules test_custom_name)
Expand Down
6 changes: 6 additions & 0 deletions compiler/tests-jsoo/test_lwt_promise.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
start
to_lwt resolved 11
to_lwt rejected with boom
of_lwt round-trip 22
of_lwt failure round-trip
done
65 changes: 65 additions & 0 deletions compiler/tests-jsoo/test_lwt_promise.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
(* Js_of_ocaml
* http://www.ocsigen.org/js_of_ocaml/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*)

(* Round-trip Promise <-> Lwt conversions. *)

open Js_of_ocaml
open Js_of_ocaml_lwt

let log s = print_endline s

let ( let* ) = Lwt.bind

let main : unit Lwt.t =
log "start";
(* Promise -> Lwt: resolved value flows through *)
let* n = Promise.to_lwt (Promise.resolve 11) in
log (Printf.sprintf "to_lwt resolved %d" n);
(* Promise -> Lwt: rejection becomes Promise.Rejected *)
let* () =
Lwt.catch
(fun () ->
let* () =
Promise.to_lwt
(Promise.reject (Promise.error_of_any (Js.Unsafe.inject (Js.string "boom"))))
in
log "to_lwt rejection NOT caught";
Lwt.return ())
(function
| Promise.Rejected e ->
let s : Js.js_string Js.t = Js.Unsafe.coerce (Promise.error_to_any e) in
log (Printf.sprintf "to_lwt rejected with %s" (Js.to_string s));
Lwt.return ()
| exn ->
log (Printf.sprintf "unexpected exn %s" (Printexc.to_string exn));
Lwt.return ())
in
(* Lwt -> Promise: returned value flows through *)
let* n = Promise.to_lwt (Promise.of_lwt (Lwt.return 22)) in
log (Printf.sprintf "of_lwt round-trip %d" n);
(* Lwt -> Promise: failed thread rejects, then to_lwt re-raises *)
let* () =
Lwt.catch
(fun () ->
let* () = Promise.to_lwt (Promise.of_lwt (Lwt.fail (Failure "lwt-boom"))) in
log "of_lwt failure NOT caught";
Lwt.return ())
(fun _ ->
log "of_lwt failure round-trip";
Lwt.return ())
in
log "done";
Lwt.return ()

let () = Lwt.async (fun () -> main)
13 changes: 13 additions & 0 deletions compiler/tests-jsoo/test_promise.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
start
scheduled
then_/map got 2
make got hello
caught boom
finally ok
finally err
all got [10; 20; 30]
race got 42
nested got 7
of_any got 99
reject-with-promise got 555
done
Loading
Loading