Popcorn.JS (Popcorn v1.0.0)

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 it. Use Popcorn.Proxy for GenServer calls and casts.

Values

Messages and run/3 arguments and results are converted between Elixir and JavaScript. See Sharing values across languages for supported types and conversions. Use a tracked_value/0 to keep a JavaScript value's identity.

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_opts()

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

tracked_value()

@opaque tracked_value()

An opaque handle that keeps a JavaScript value alive.

Return new TrackedValue(value, cleanup) from a JavaScript function to create a handle. The cleanup function has the signature () => void. Pass the handle in run/3 arguments to access the original value.

The runtime calls the 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_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.JS.await_ready(),
       {:ok, _value} <- Popcorn.JS.run("() => 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(code, args \\ %{}, opts \\ [])

@spec run(String.t(), map(), run_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 values or reject on failure, like their JavaScript API counterparts. Use trySend, tryCall, or tryCast to receive result objects for expected failures.

Notes:

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

Options

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

Examples

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

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

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

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

@spec run!(String.t(), map(), run_opts()) :: term()

See run/3.

send(message)

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

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