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
10 changes: 4 additions & 6 deletions API-STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This document lists standard JavaScript/Web APIs and their support status in js_
|-----|------|-----|---------------------|
| Web Workers | Yes | Yes | `Worker` · Brr: `Brr_webworkers.Worker` |
| Service Workers | Yes | Yes | `ServiceWorker` · Brr: `Brr_webworkers.Service_worker` |
| Shared Workers | No | Yes | Brr: `Brr_webworkers.Worker.Shared` |
| Shared Workers | Yes | Yes | `SharedWorker` · Brr: `Brr_webworkers.Worker.Shared` |

## File & Binary Data

Expand Down Expand Up @@ -156,7 +156,7 @@ This document lists standard JavaScript/Web APIs and their support status in js_
| Web Components (Custom Elements, Shadow DOM) | Partial | No | `Dom_html` (Shadow DOM — `attachShadow`, `shadowRoot`, `assignedSlot`, `slot`); Custom Elements not bound |
| Web Crypto API | Yes | Yes | `Crypto` · Brr: `Brr_webcrypto` |
| Notifications API | Yes | Yes | `Notification` · Brr: `Brr_io.Notification` |
| Broadcast Channel API | No | Yes | Brr: `Brr_io.Message.Broadcast_channel` |
| Broadcast Channel API | Yes | Yes | `BroadcastChannel` · Brr: `Brr_io.Message.Broadcast_channel` |
| AbortController / AbortSignal | Yes | Yes | `Abort` · Brr: `Brr.Abort` |

---
Expand All @@ -180,11 +180,9 @@ Worker/iframe/window communication.

| API | Issue | In Brr | Why |
|-----|-------|--------|-----|
| 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. |
| Encoding API (TextEncoder/TextDecoder) | — | Partial | Needed for binary protocol work and streaming text. Small surface. |
| Shared Workers | — | Yes | Shared state across tabs. Niche but Brr covers it. |
| IndexedDB | — | No | Client-side database for offline apps. Large API but important for data-heavy PWAs. |
| 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. |
Expand All @@ -205,5 +203,5 @@ Worker/iframe/window communication.

### Suggested implementation order

1. **Broadcast Channel** — small API that fills out the
remaining communication gaps.
1. **Encoding API** — small surface, needed for binary protocol
work and streaming text.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# dev

## Features/Changes
* Lib: add `BroadcastChannel` and `SharedWorker` — bindings to the Broadcast
Channel API and to shared workers (the page-side `SharedWorker` constructors
with their options, and the worker-side `SharedWorkerGlobalScope` with a
`set_onconnect` helper). The wasm runtime no longer loses `connect` events
received while the module is being instantiated (#2400)
* Lib: add `WebGL2` — bindings to the WebGL2 rendering context. The context
inherits every method and constant of `WebGL`, and adds the WebGL2 objects
(vertex array objects, queries, samplers, syncs, transform feedback), 3D and
Expand Down
42 changes: 42 additions & 0 deletions lib/js_of_ocaml/broadcastChannel.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(* Js_of_ocaml library
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)

open! Import

class type broadcastChannel = object ('self)
inherit Dom_html.eventTarget

method name : Js.js_string Js.t Js.readonly_prop

method postMessage : 'a. 'a -> unit Js.meth

method close : unit Js.meth

method onmessage :
('self Js.t, ('self, Js.Unsafe.any) Dom_html.messageEvent Js.t) Dom.event_listener
Js.writeonly_prop

method onmessageerror :
('self Js.t, ('self, Js.Unsafe.any) Dom_html.messageEvent Js.t) Dom.event_listener
Js.writeonly_prop
end

let broadcastChannel : (Js.js_string Js.t -> broadcastChannel Js.t) Js.constr =
Js.Unsafe.global##._BroadcastChannel

let is_supported () = Js.Optdef.test Js.Unsafe.global##._BroadcastChannel
56 changes: 56 additions & 0 deletions lib/js_of_ocaml/broadcastChannel.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
(* Js_of_ocaml library
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)

(** Broadcast Channel API.

A [BroadcastChannel] delivers every posted message to all other channels
with the same name in the same origin (other tabs, windows, workers).

@see <https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API>
@see <https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts> *)

open Js

class type broadcastChannel = object ('self)
inherit Dom_html.eventTarget

method name : js_string t readonly_prop
(** The channel name given to the constructor. *)

method postMessage : 'a. 'a -> unit meth
(** [chan##postMessage msg] delivers a structured clone of [msg] to every
other channel with the same name in the same origin. Raises once the
channel has been closed. *)

method close : unit meth

method onmessage :
('self t, ('self, Unsafe.any) Dom_html.messageEvent t) Dom.event_listener
writeonly_prop

method onmessageerror :
('self t, ('self, Unsafe.any) Dom_html.messageEvent t) Dom.event_listener
writeonly_prop
end

val broadcastChannel : (js_string t -> broadcastChannel t) constr
(** [new%js broadcastChannel name] joins the broadcast channel [name]. *)

val is_supported : unit -> bool
(** Whether the [BroadcastChannel] global is available in the current
environment. *)
2 changes: 2 additions & 0 deletions lib/js_of_ocaml/js_of_ocaml.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
{b This library is provided by the [js_of_ocaml] opam package.} *)

module Abort = Abort
module BroadcastChannel = BroadcastChannel
module CSS = CSS
module Cache = Cache
module Clipboard = Clipboard
Expand Down Expand Up @@ -55,6 +56,7 @@ module Promise = Promise
module ResizeObserver = ResizeObserver
module Regexp = Regexp
module ServiceWorker = ServiceWorker
module SharedWorker = SharedWorker
module Sys_js = Sys_js
module Typed_array = Typed_array
module Url = Url
Expand Down
77 changes: 77 additions & 0 deletions lib/js_of_ocaml/sharedWorker.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
(* Js_of_ocaml library
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)

open! Import

class type sharedWorker = object ('self)
inherit Dom_html.eventTarget

method port : MessageChannel.messagePort Js.t Js.readonly_prop

method onerror :
('self Js.t, Worker.errorEvent Js.t) Dom.event_listener Js.writeonly_prop
end

class type workerOptions = object
method name : Js.js_string Js.t Js.writeonly_prop

method _type : Js.js_string Js.t Js.writeonly_prop

method credentials : Js.js_string Js.t Js.writeonly_prop
end

let empty_worker_options () : workerOptions Js.t = Js.Unsafe.obj [||]

let sharedWorker : (Js.js_string Js.t -> sharedWorker Js.t) Js.constr =
Js.Unsafe.global##._SharedWorker

let sharedWorker_withName :
(Js.js_string Js.t -> Js.js_string Js.t -> sharedWorker Js.t) Js.constr =
Js.Unsafe.global##._SharedWorker

let sharedWorker_withOptions :
(Js.js_string Js.t -> workerOptions Js.t -> sharedWorker Js.t) Js.constr =
Js.Unsafe.global##._SharedWorker

let is_supported () = Js.Optdef.test Js.Unsafe.global##._SharedWorker

class type sharedWorkerGlobalScope = object ('self)
inherit Dom_html.eventTarget

method name : Js.js_string Js.t Js.readonly_prop

method close : unit Js.meth

method onconnect :
('self Js.t, ('self, Js.Unsafe.any) Dom_html.messageEvent Js.t) Dom.event_listener
Js.writeonly_prop
end

let global () : sharedWorkerGlobalScope Js.t = Js.Unsafe.global

let set_onconnect handler =
if not (Js.Optdef.test Js.Unsafe.global##.onconnect)
then invalid_arg "SharedWorker.set_onconnect: onconnect is undefined";
let js_handler (ev : (_, _) Dom_html.messageEvent Js.t) =
let port : MessageChannel.messagePort Js.t =
Js.Unsafe.coerce
(Js.Optdef.get (Js.array_get ev##.ports 0) (fun () -> assert false))
in
handler port
in
Js.Unsafe.global##.onconnect := Js.wrap_callback js_handler
104 changes: 104 additions & 0 deletions lib/js_of_ocaml/sharedWorker.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
(* Js_of_ocaml library
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)

(** Shared Workers.

A [SharedWorker] is a worker shared by every browsing context of the same
origin that creates one with the same script URL and name. Each context
communicates with the worker through the {!MessageChannel.messagePort}
exposed as {!class-type:sharedWorker}'s [port]; inside the worker, each new
connection is delivered as a [connect] event carrying the other end of that
port.

@see <https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker>
@see <https://html.spec.whatwg.org/multipage/workers.html#shared-workers-and-the-sharedworker-interface> *)

open Js

(** {2 Page side} *)

class type sharedWorker = object ('self)
inherit Dom_html.eventTarget

method port : MessageChannel.messagePort t readonly_prop
(** The port used to communicate with the worker. When messages are received
through the [onmessage] property the port is started implicitly;
otherwise call [port##start] to begin receiving. *)

method onerror : ('self t, Worker.errorEvent t) Dom.event_listener writeonly_prop
end

(** Options for the {!sharedWorker_withOptions} constructor. All fields are
optional; create an empty record with {!empty_worker_options}. *)
class type workerOptions = object
method name : js_string t writeonly_prop
(** Identifies the worker: contexts constructing a [SharedWorker] with the
same URL and name share a single worker instance. *)

method _type : js_string t writeonly_prop
(** Either ["classic"] (default) or ["module"]. *)

method credentials : js_string t writeonly_prop
(** One of ["omit"], ["same-origin"] (default) or ["include"]. Only used
with module workers. *)
end

val empty_worker_options : unit -> workerOptions t

val sharedWorker : (js_string t -> sharedWorker t) constr
(** [new%js sharedWorker url] connects to the shared worker running the script
at [url], starting it if it is not running yet. *)

val sharedWorker_withName : (js_string t -> js_string t -> sharedWorker t) constr
(** [new%js sharedWorker_withName url name] connects to the shared worker
identified by [url] and [name]. *)

val sharedWorker_withOptions : (js_string t -> workerOptions t -> sharedWorker t) constr

val is_supported : unit -> bool
(** Whether the [SharedWorker] global is available in the current
environment. *)

(** {2 Worker side} *)

(** The global scope of a running shared worker (its [self]). Only meaningful
when the current code is actually running as a shared worker. *)
class type sharedWorkerGlobalScope = object ('self)
inherit Dom_html.eventTarget

method name : js_string t readonly_prop
(** The name given at construction, or the empty string. *)

method close : unit meth

method onconnect :
('self t, ('self, Unsafe.any) Dom_html.messageEvent t) Dom.event_listener
writeonly_prop
(** Fired each time a new context connects. The first element of the event's
[ports] is the {!MessageChannel.messagePort} to that context. *)
end

val global : unit -> sharedWorkerGlobalScope t
(** The shared worker's global scope. *)

val set_onconnect : (MessageChannel.messagePort t -> unit) -> unit
(** [set_onconnect f] calls [f port] each time a new context connects to the
worker, where [port] is the port to that context (already extracted from
the [connect] event). To receive its messages, set [port##.onmessage] or
add a [message] listener and call [port##start]. Raises [Invalid_argument]
when not running in a shared worker. *)
14 changes: 13 additions & 1 deletion lib/tests-browser/dune
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
(executables
(names test_beforeunload test_wheel test_fetch test_font_face)
(names
test_beforeunload
test_wheel
test_fetch
test_font_face
test_shared_worker
test_shared_worker_worker)
(libraries js_of_ocaml)
(modes js wasm)
(preprocess
Expand All @@ -17,6 +23,9 @@
test_fetch.html
test_font_face.bc.js
test_font_face.html
test_shared_worker.bc.js
test_shared_worker_worker.bc.js
test_shared_worker.html
BebasNeue-Regular.ttf))

(alias
Expand All @@ -31,4 +40,7 @@
test_fetch.html
test_font_face.bc.wasm.js
test_font_face.html
test_shared_worker.bc.wasm.js
test_shared_worker_worker.bc.wasm.js
test_shared_worker.html
BebasNeue-Regular.ttf))
4 changes: 4 additions & 0 deletions lib/tests-browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ <h1>Browser tests</h1>
<a href="test_font_face.html">test_font_face</a>
(<a href="test_font_face.html?wasm">wasm</a>)
</li>
<li>
<a href="test_shared_worker.html">test_shared_worker</a>
(<a href="test_shared_worker.html?wasm">wasm</a>)
</li>
</ul>
</body>
</html>
Loading
Loading