← Back to blog

WebKit for Oniro on Volla X23

WebKit for Oniro on Volla X23

This post continues my series on porting WebKit to OpenHarmony. Earlier posts covered getting WebKit to compile and render and moving the dependency build to vcpkg. This time the target is Oniro.

Oniro is a vendor-neutral downstream distribution of OpenHarmony. It is built on the same open-source base, but it is operated by the Eclipse Foundation rather than shipped by a single vendor. It comes out of a dual-foundation model: the OpenAtom Foundation stewards OpenHarmony itself, and the Eclipse Foundation runs Oniro as the global-facing downstream. In practice Oniro’s job is to make OpenHarmony approachable outside China, with GitHub mirrors instead of GitCode, English-first documentation, and a focus on hardware people can actually buy. The Eclipse team presented the effort at FOSDEM 2026.


Volla X23

I originally picked up a Volla X23 for Ubuntu Touch projects, but it turns out the same device can also run OpenHarmony and Oniro natively, thanks to the porting work done by the Eclipse team. That made it the obvious place to find out what it takes to get WebKit running on Oniro.

Volla X23 phone

Volla X23

Running Oniro on Volla X23

The Oniro team publishes instructions for building Oniro for the Volla X23 and flashing it. The docs are still a little rough in places, but I managed to get a working image onto the device.

Running WebKit on Oniro and Volla X23

Compiling WebKit for Oniro works exactly the same way as for OpenHarmony: the public OpenHarmony SDK plus vcpkg, following the WebKit for OpenHarmony build. The compile step was a non-event, because it is the same build. Runtime was where Oniro showed its differences.

On the Volla X23, Oniro reaches the phone’s graphics driver through libhybris. The device ships Android GPU blobs (the Mali driver is built for Android’s Bionic libc), and libhybris is a translation layer that lets OpenHarmony’s musl-based stack call into those Android libraries. The Oniro FOSDEM slides explain the reasoning well. It works, and OpenHarmony’s own compositor renders fine, but WebKit’s first launch showed a black web area: the web process was crashing the moment it touched GL.

WebKit does not call OpenGL directly. It goes through libepoxy, a small library that resolves GL function pointers at runtime and picks the right implementation for whatever driver is present. That indirection is normally invisible, but on this device it is exactly where things broke. OpenHarmony’s GL wrapper only wires up its per-thread dispatch table on the Android-style eglMakeCurrent path; on WebKit’s offscreen render threads that table is empty, so the GL calls fall through to a no-context abort. The one entry point that always resolves to real, context-aware driver functions is eglGetProcAddress.

That turned into a few quick and dirty hacks, one per layer of the stack:

  • libhybris: resolve GL entry points through the driver’s own eglGetProcAddress first, and fill in one wrapper (glEGLImageTargetRenderbufferStorageOES) that WebKit needs but libhybris was missing. This is the only Oniro-specific change.
  • libepoxy: on OpenHarmony, route all GL resolution through eglGetProcAddress, and skip a context-query guard that fails on this board and would otherwise drop back into the crashing path.
  • WebKit: resolve one GLES2 symbol and the GL version once, up front, so the very first GL lookup does not mis-fire before a context is current. It is gated to OpenHarmony and inert everywhere else.

With all three in place, the web area renders end to end on the phone, full pages and images included.

One last retail-phone surprise was code signing. Installing the app failed with a signing error because the Volla’s Halium kernel has no code_sign device node, which the userspace still expects for debug profiles. Signing with a release profile skips that path, and the install goes through.


What’s new in WebKit for OpenHarmony

WebKit for OpenHarmony has also picked up new features and one significant design change that are not specific to Oniro. The headline is that the port no longer needs a private patch to the OpenHarmony graphics stack.

The earlier bring-up shared rendered GPU buffers between processes by sending OH_NativeBuffer handles over Unix domain sockets, which the public SDK does not support. That meant carrying a private addition to the graphics stack, so the build was not really “public SDK only”. This round removes it: all of WebKit’s inter-process communication now runs on OpenHarmony’s public binder IPC (IPCKit), and buffers cross inside binder transactions using only public SDK 23 APIs. A few things follow from that.

New OH_NativeBuffer serialization via OHIPCParcel

OpenHarmony 6.1 SDK level 23 added the ability to serialize an OH_NativeBuffer into an IPC parcel (OH_NativeBuffer_WriteToParcel and OH_NativeBuffer_ReadFromParcel). This is the only public way to move a rendered graphics buffer across a process boundary, and it only works inside a binder transaction. WebKit now uses it to hand finished frames from the web process to the UI process compositor.

WebKit IPC uses OpenHarmony IPCKit

Because the buffers have to ride inside a binder transaction, the transport itself has to be binder. So WebKit’s IPC on OpenHarmony moved off Unix domain sockets (the WPE default) and onto IPCKit. Binder calls only go one way, from a proxy to a stub, so each side hosts a stub and holds a proxy to the other, which gives the two-way connection WebKit expects.

Diagram of WebKit's IPCKit-based inter-process communication design

WebKit IPC over OpenHarmony IPCKit

Auxiliary process launching integrated to WebKit

WebKit for OpenHarmony used to delegate process launching to the embedder app through libwpe (ProcessProviderLibWPE), but that was not flexible enough for IPCKit. WebKit now spawns its own child processes with OH_Ability_CreateNativeChildProcess, the only public API that hands back a live binder connection to a process you launched yourself. A nice side effect: every child loads the same libWPEWebKit-2.0.so, and each one learns whether it is the Web, Network, or GPU process from a small startup handshake. The embedder is out of the launch path entirely, which made the app-side code simpler.

Diagram of WebKit spawning its own auxiliary processes via OH_Ability_CreateNativeChildProcess

Auxiliary process launching integrated into WebKit

UIProcess glib main loop integrated to libuv main loop

WPE WebKit’s UI process is driven by a GLib main loop. The earlier version ran it on its own dedicated thread, which works but adds a second event loop and a cross-thread hop for every interaction. On OpenHarmony the ArkTS application loop is a libuv loop, reachable from NAPI, so instead a small message pump now drives GLib directly from libuv’s own poll and dispatch phases. WebKit’s UI process runs cooperatively on the ArkTS thread with no extra thread, using only public libuv and GLib.

Diagram of WebKit's GLib main loop driven from the ArkTS libuv event loop

WebKit's GLib main loop integrated into the ArkTS libuv loop

Smooth rendering with GPU fences

Getting the page to render was not quite the end of it: video, and even a mostly static top bar on a scrolling page, would flicker. The web process renders into a buffer with one GL context, and the UI process samples it with another, and the handoff was only flushing GPU commands rather than waiting for them, so the consumer could read a buffer the producer’s GPU was still drawing into. The fix is an explicit GPU fence in both directions, so each side waits for the other’s GPU work to finish before it touches the shared buffer. The fence is a standard EGL extension the driver already exposes, and WebKit already had the machinery from its Linux path, so this was mostly a matter of routing it through. The result is tear-free rendering with no per-frame stalls.

IME Support

Text input is wired through WPE’s existing WPEInputMethodContext abstraction to OpenHarmony’s native IME C API (IMEKit). Because that seam already exists in WebKit, the whole feature lives on the embedder side with no WebKit patch. OpenHarmony ships a native C API for this, so the integration stays in C++ and supports preedit composition.


Demo

Here is WebKitView running on Oniro on the Volla X23. The demo shows it rendering YouTube and demonstrates IME support with text entered through the on-screen keyboard.

Where things stand

✔ WebKit runs on a real retail phone (Volla X23) on Oniro over libhybris
✔ Cross-process GPU buffers use only the public SDK, no private graphics-stack patch
✔ Multiprocess IPC runs on OpenHarmony’s public binder (IPCKit)
✔ On-screen keyboard and text input work

The build and the WebView integration are on GitHub:


Image Credits:
Part of the Volla X23 model image is by LinuxGamingAlchemy (ut-hw-models, free to use and adapt as UBports promotional material with credit), based on Volla Systeme GmbH product imagery whose original design remains the property of its owner.