Native modules
Generated from docs/modules.md when the site was built at 7aa23e2.
A native module is an API an app’s own native code gives its pages, the way
ihatemyphone’s phone module gives its Svelte pages the volume, the
screenshots and the assistant setup. WebIDL is the single source of its
types: niso-idl generates the page’s import with WebIDL’s conversions, its
TypeScript declarations, and the native base class from one IDL file. Calls
cross the core’s C ABI (niso_dom_define_module in niso_runtime.h) as
value lists, so a binding for another language (Swift next) needs only its
own generator, not a new protocol.
Adding one
Section titled “Adding one”It takes one IDL file, one Kotlin class and one import. A module is a package, a directory named after it, beside the app:
phone/ phone.webidl the API android/build.gradle.kts an Android library: generates the Java from the IDL android/src/main/kotlin/... the class behind it-
Declare the API as a
[NisoModule=<name>]interface. Operations, attributes, enums, dictionaries, sequences and nullable types convert as WebIDL says. APromise<T>operation settles later, and an event handler attribute (attribute LevelHandler? onlevelchange) is an event the native side sends.enum Stream { "media", "ring" };callback LevelHandler = undefined (Stream stream, double level);[NisoModule=phone]interface Phone {double setLevel(Stream stream, double level);Promise<boolean> screenshot();attribute LevelHandler? onlevelchange;}; -
Implement the generated base class. Each operation and attribute is an abstract method. A promise’s method gets a
Resolverto settle, now or later, from any thread. Each event has a method that sends it. ADOMExceptionit throws reaches the page as itself, anIllegalArgumentExceptionas aTypeError, and anything else as anError.class PhoneModule(context: Context) : Phone(), PageLifecycle {override fun setLevel(stream: Stream, level: Double) = volume.set(stream, level)override fun screenshot(result: Resolver<Boolean>) = save { ok -> result.resolve(ok) }fun changed(stream: Stream) = onlevelchange(stream, level(stream))} -
Import it in the page.
examples/build.mjsresolvesniso:<name>to the IDL file beside the page or above it (directly or in its package), and writes<name>.d.tsbeside the IDL file for editors.import { phone } from 'niso:phone';phone.onlevelchange = (stream, level) => { ... };
The Android app’s settings include every package beside it as a Gradle
project (examples/ihatemyphone/android/settings.gradle.kts), so the app
adds it with implementation(project(":phone")) and gives the instance to
its page: Engine(context, script, name, modules = listOf(phone)). The page
gets it before its script runs. Headless tests define a fake subclass on a
dev.niso.dom.Dom with dom.define(fake), as examples/ihatemyphone/test
does.
Results and events reach the page only as tasks of its event loop, never in
the middle of a script. An event the native side sends while no page has the
module is dropped, so a page reads state through an attribute (phone.shown)
when it starts.
Lifecycle and permissions
Section titled “Lifecycle and permissions”A module that implements dev.niso.host.PageLifecycle hears onResume,
onPause and onNewIntent for its page. PageView sends them from the
lifecycle of the activity or window it is in, with no code in the app.
Permissions follow Tauri’s pattern in IDL. checkPermissions() returns a
dictionary of "granted" | "denied" | "prompt" states, and
requestPermissions() asks and resolves with the new states. ihatemyphone’s
requestPermissions opens the assistant settings and resolves from
onResume, once the user is back.
Not yet
Section titled “Not yet”- Streams: a module streams through events today. A Tauri-style channel, a callback argument to one call, would be a callback type in an operation’s arguments with its own id on the wire. niso-idl rejects that for now.
- Access control: every call goes through
niso_dom_module_call, so a per-page allowlist of modules and members (Tauri’s capabilities) would sit there. - Activity results:
PageLifecyclehas noonActivityResult, because ihatemyphone needs none. Its settings round trip resolves on resume. - Swift: the C ABI is ready, but there is no generator for it yet.