Popcorn.Wasm (Popcorn v0.4.0-next.3)

View Source

Connects Elixir processes to JavaScript in the browser page.

Receiving messages

JavaScript calls to popcorn.send(target, payload) deliver {:wasm, payload} to the target process. Use is_message/1 in guards to match for it. Use Popcorn.Proxy for GenServer calls and casts.

Values

From JS side:

  • strings become binaries.
  • arrays become lists.
  • plain objects become maps with string keys.
  • atom() and tuple() helpers send atoms and tuples. Atoms must already exist in the VM.

From VM side:

  • Tuples become arrays.
  • Most atoms become strings, with exception of true, false and nil (mapped to null).
  • PID handles refer to processes in the VM.

Outside the browser

Use available?/0 to check if your code is running natively or in the browser. On a native VM, JavaScript calls use a no-op mock so Popcorn applications can run in tests.

Summary

Types

A JavaScript message with a payload decoded into Elixir terms.

An opaque handle that keeps a JavaScript value alive.

Functions

Returns true if running in the browser.

Waits until the JavaScript Popcorn instance has finished booting.

Matches a message/0 sent from JavaScript.

Runs a JavaScript function on the page and waits for its result.

Sends a message to the JavaScript callbacks registered with popcorn.onEvent().

Types

message()

@type message() :: {:wasm, payload :: term()}

A JavaScript message with a payload decoded into Elixir terms.

run_js_opts()

@type run_js_opts() :: [{:timeout, timeout()}]

tracked_value()

@opaque tracked_value()

An opaque handle that keeps a JavaScript value alive.

Return new TrackedValue(value, cleanup) from JavaScript function to create a handle. Pass the handle in run_js/3 arguments to access the original value.

The runtime calls cleanup function after BEAM garbage collection releases the handle, or when the VM stops.

Note: Garbage collection does not guarantee prompt cleanup. For time-sensitive resources, call an idempotent cleanup function explicitly. This ensures you can call it yourself or it can be called by Popcorn.

Functions

available?()

@spec available?() :: boolean()

Returns true if running in the browser.

await_ready(opts \\ [])

@spec await_ready(run_js_opts()) :: :ok | {:error, :timeout}

Waits until the JavaScript Popcorn instance has finished booting.

Use this only from asynchronous work whose process startup has already been acknowledged, such as the body of a supervised Task:

children = [
  {Task, &MyApp.API.run/0}
]

def run do
  with :ok <- Popcorn.Wasm.await_ready(),
       {:ok, _value} <- Popcorn.Wasm.run_js("() => initializeBrowser()") do
    :ok
  end
end

Options

  • :timeout - the reply timeout, or :infinity. Defaults to 5_000 ms.

Notes

is_message(message)

(macro)

Matches a message/0 sent from JavaScript.

run_js(code, args \\ %{}, opts \\ [])

@spec run_js(String.t(), map(), run_js_opts()) ::
  {:ok, term()} | {:error, :timeout | {:js, term()}}

Runs a JavaScript function on the page and waits for its result.

code defines a function with the signature (args, {send, call, cast}) => result. The bridge converts the args map to JavaScript values and awaits any returned promise. It returns {:ok, value} with the result converted to Elixir terms or {:error, {:js, reason}}. A timeout returns {:error, :timeout} and does not cancel JavaScript execution.

The page's Content Security Policy must permit JavaScript evaluation with unsafe-eval.

JS

The send helper sends a message to a BEAM process. The call and cast helpers use Popcorn.Proxy to contact GenServers. These helpers return promises with the same result objects as their JavaScript API counterparts.

Notes:

  • Calls during application startup run before popcorn.boot() resolves.
  • Do not await a call to the process that executes run_js/3. It will cause deadlocks.

Options

  • :timeout - the reply timeout, or :infinity. Defaults to 5_000 ms.

Examples

Popcorn.Wasm.run_js("({n}) => n + 1", %{n: 1})
#=> {:ok, 2}

Use a tracked_value/0 for values such as DOM elements:

{:ok, element} = Popcorn.Wasm.run_js("() => new TrackedValue(document.body)")
Popcorn.Wasm.run_js!("({element}) => { element.textContent = 'Ready'; }", %{element: element})

run_js!(code, args \\ %{}, opts \\ [])

@spec run_js!(String.t(), map(), run_js_opts()) :: term()

See run_js/3.

send(message)

@spec send(term()) :: :ok

Sends a message to the JavaScript callbacks registered with popcorn.onEvent().