The WebKit team at Igalia is happy to announce a new release series of WPE WebKit. This release has two main highlights: the new WPEPlatform API, now stable and enabled by default, and a web process compositor built on the Skia graphics library, replacing the TextureMapper-based one. Read on for the details on both, along with a summary of the other most noteworthy changes from the latest release cycle.
WPEPlatform: the new WPE API
WPEPlatform, the API that has been in the works for the past several release cycles, takes center stage in 2.54: it is now enabled by default and its API is considered stable, so applications can build on it without expecting breaking changes in future releases. Consequently, the traditional libwpe-based API is officially deprecated: it remains available and maintained, but new code should target WPEPlatform, and existing embedders are encouraged to plan their migration. This also applies to Cog, which will not have stable releases beyond the 0.18.x series.
The best part of the new API is how much simpler it is. Under libwpe, an application had to create a view backend (usually through WPEBackend-fdo’s “exportable” backend) and drive rendering, buffer management, and input dispatch itself through its callbacks. WPEPlatform moves all of that into WebKit and the platform implementation, so migrating an application is mostly a matter of deleting code: in the common case, the application constructs its WebKitWebView without a backend, WebKit selects a suitable platform automatically, and everything built on top of the web view carries over unchanged. The platform API only surfaces when an application wants more than the defaults: pinning to a particular platform, handling raw input events, or driving the toplevel window. Each of those is a few lines against a small GObject API rather than a set of C callbacks to implement.
To help embedders make the move, WPEPlatform is now extensively documented. The reference documentation includes an overview of the platform API (covering its relationship to libwpe and what is intentionally not part of the new API), a guide on compiling against it, a tutorial on writing a browser, and a tutorial on writing a WPE platform implementation. A guide on migrating from libwpe, with a symbol-by-symbol mapping table, walks embedders through the process step by step.
Since WPEPlatform is now built by default, the wpe-platform-2.0 pkg-config module is available in regular builds. Conversely, the legacy API can be disabled at build time with ENABLE_WPE_LEGACY_API=OFF when it is not needed.
Additions and final adjustments
New platform APIs added this cycle include:
WPEProcessManagerandWPEProcessLaunchOptions, which allow the embedder to control how the auxiliary WebKit processes are launched and terminated. This is particularly important on Android, where each process is a service that must be started withbindService(), and it removes the last reason a WPEPlatform-only build could not work there. Note that, unlike the rest of WPEPlatform, the process management API is only built when targeting Android and remains experimental: it is not yet generic enough to be enabled everywhere, and it may still change in future releases.- Gamepad rumble support, through
wpe_gamepad_has_rumble()andwpe_gamepad_rumble(), with a built-in implementation based on libmanette. This enables the Gamepad APIvibrationActuatorfor web content. - A new
WPE_SETTING_OVERLAY_SCROLLBARSsetting, enabled by default, which may be disabled by applications to opt into classic, always-visible scrollbars. - A new
WPE_INPUT_PURPOSE_SEARCHinput purpose, allowing input methods to detect when the values of an input field are expected to be search terms.
A few final adjustments were made to the API before declaring it stable, which may require updates to platform implementations and applications developed against the earlier previews:
- The
WPE_SETTING_DISABLE_ANIMATIONSsetting has been replaced byWPE_SETTING_REDUCED_MOTION, matching the dedicated reduced-motion setting introduced in GNOME 50, and a new tri-stateWPE_SETTING_INTERFACE_CONTRASTsetting has been added. Together with the existingWPE_SETTING_DARK_MODEsetting, these make theprefers-reduced-motion,prefers-contrast, andprefers-color-schememedia queries follow the platform settings. wpe_gesture_controller_handle_event()now returns a boolean indicating whether the event was consumed.- The
WPE_DMABUF_BUFFER_FORMATenvironment variable has been renamed toWPE_BUFFER_FORMAT.
Beyond Linux: Android
A good measure of the new API’s maturity is wpe-android, which has been rebuilt this year as a WPEPlatform platform implementation living entirely outside the WebKit tree: Android’s display system now looks to the engine like any other WPE platform, and applications get a convenience Java API modeled after android.webkit.WebView. The new WPEProcessManager API removed the last dependency on libwpe there. You can read more about it in this post.
On the WebKit API side
The shared WebKit API has also seen additions this cycle:
- A new API for page icons:
webkit_favicon_database_get_page_icons()retrieves all the icons declared by a page as a list ofWebKitImageobjects, and theWebKitWebView:page-iconsproperty exposes the icons of the currently loaded page. Each icon implementsGLoadableIcon, so applications can pick the size that best fits their needs. With this, the favicon database is available in WPE for the first time; it can be enabled withwebkit_website_data_manager_set_favicons_enabled(). - A new
WebKitWebView:magnificationproperty to handle visual scaling of the page, independent of the zoom level. WebKitWebsitePolicies:custom-user-agentallows setting a custom User-Agent for a single navigation, by passing the policies towebkit_policy_decision_use_with_policies().webkit_feature_list_find()is a convenience function to look up a feature by identifier in a feature list.
Graphics improvements
A new Skia-based compositor
This cycle brings the largest overhaul of the rendering architecture since the adoption of Skia for 2D rendering: the web process compositor now uses the Skia API instead of the venerable TextureMapper. Layers are composed into the final frame using Skia, which allows sharing a single rendering infrastructure across the whole graphics stack and enables several optimizations:
- Tile contents are recorded into deferred display lists and replayed on the compositor thread, so painting worker threads no longer need to touch the GPU at all.
- Batched painting groups the drawing of many layers into a single Skia call, which improves performance on pages with many layers that can be painted in the same operation.
- Unnecessary clip operations are avoided whenever possible, keeping the batched paths effective.
Beyond raw performance, expressing compositing as Skia draw calls made several features simpler and faster: filters and masks no longer require intermediate offscreen surfaces in most cases, and CSS blend modes, which TextureMapper never implemented, now work in composited layers. And since Skia can target both OpenGL and Vulkan, the compositor no longer stands in the way of Vulkan-based rendering in the future.
The new compositor also works when using the legacy libwpe-based API.
The consolidation on Skia goes beyond compositing: the option to use Cairo for 2D rendering has been removed, making Skia the only 2D rendering implementation. Rendering tiles in the main thread is no longer supported either, so threaded rendering is now the only tile painting path.
Damage-aware compositing
Damage tracking has seen substantial work this cycle. The damage is the region of the view that changed since the previous frame and therefore requires repainting; Paweł Lampe’s introduction to damage propagation covers the concept in depth. Compositing itself now uses this information: each draw the compositor issues is restricted to the damaged rectangles, in a way that preserves the batched painting described above, and damage propagation to the platform is now enabled by default. A new DamageRectangleThreshold preference allows embedders to tune the balance between damage precision and bookkeeping cost.
The performance impact
What drove the compositor rewrite was making it simpler and easier to maintain, as Carlos García Campos explained at the Web Engines Hackfest 2026; performance came later, since TextureMapper started out ahead after more than a decade of tuning. By now the optimization work has more than closed that gap. The public WPE performance dashboard, which continuously runs benchmarks on Raspberry Pi 4 devices, tells the story: comparing the last TextureMapper-based revisions against the current ones, and thus measuring the cumulative effect of the graphics work described in this section, the MotionMark score is up by around 36%. On the composition-focused variant of the benchmark, where the compositor dominates the workload, the score is up by around 45%.
The dashboard also records the GPU load during each run, and there the difference is even more telling. MotionMark increases scene complexity until the browser can no longer sustain the target frame rate, so a higher score already means more work per frame; the Skia compositor delivers that while keeping the GPU markedly less busy. On embedded devices, where the GPU is typically the scarcest resource, this headroom translates directly into smoother pages.
One part of the work goes largely unmeasured here, though. MotionMark animates nearly the entire viewport, so there is hardly anything for damage tracking to save. Real-world content behaves differently: usually a small area of the page is changing while everything else stays still, and skipping all of that quiet area cuts the per-frame GPU work to a fraction.
Other rendering improvements
A GPU atlas is now used for batched raster image uploads, regardless of the compositor in use, and it is reused across frames when the image set does not change, avoiding needless texture allocation and pixel uploads.
Asynchronous scrolling is smoother: several synchronization issues between the main, scrolling, and compositing threads that caused glitches while scrolling have been fixed, and the scrolling thread no longer blocks the compositor to flush its state, removing input-latency stalls on pages with many layers.
Finally, more animations can now run on the compositing thread: CSS animations using the steps() and linear() timing functions no longer force main-thread animation.
Better multimedia on embedded hardware, and a WebRTC transition
Let’s start with the transition: the GStreamer-based WebRTC backend is being replaced with a LibWebRTC-based implementation, which is expected to be available in the next release cycle. As a consequence, WebRTC support, which in previous releases required building with experimental features enabled, is disabled in 2.54.
The rest of the multimedia work moved forward at full speed, with a strong focus on embedded hardware:
- Hardware video decoding and encoding for platforms with a Qualcomm GPU has been added, leveraging the
qtic2vdecandqtic2vencGStreamer elements. - Video decoding limits are now respected in MediaCapabilities queries, and can be overridden with the
WEBKIT_GST_VIDEO_DECODING_LIMITenvironment variable, so a single build can serve devices with different capabilities. - Resource usage on pages containing many videos has been improved, by stopping the pipelines of muted, invisible video elements.
- A new feature flag, enabled by default, allows low-end devices to skip caching pages with multimedia content in the back-forward cache, so that a suspended pipeline cannot hold a scarce hardware decoder hostage.
- Media capability reporting is more accurate: non-AAC
mp4acodecs (MP3, AC-3, E-AC-3) are correctly reported as supported when decoders are present, xHE-AAC support is auto-detected, and Dolby AC-4 is advertised for MSE on systems that support it. - Experimental support for
SourceBuffer.changeType()has been added to the MSE backend, along with a fix for playback stalling at ad transitions on Twitch. - Persistent licenses are now supported in the Thunder CDM for encrypted media.
On top of this, the GStreamer backend has received a substantial amount of memory-safety and lifetime-correctness work that translates into a more stable multimedia experience.
WebXR
The OpenXR-based WebXR implementation continues to progress. The main highlight is support for the WebXR Layers API: quad, cylinder, equirect, and cube layers are now implemented, in addition to the already supported projection layers. Layers can be backed by texture arrays, and XRSession.maxRenderLayers lets content query the compositor’s layer budget.
The backend has also been decoupled from OpenGL ES through an abstract graphics binding, paving the way for a future Vulkan-based binding.
WebXR support remains a build-time option, enabled with the ENABLE_WEBXR=ON CMake option; the Layers support additionally requires ENABLE_WEBXR_LAYERS=ON.
Other improvements to the WPE port
Several long-standing gaps in the WPE port have been closed this cycle:
- A built-in popup menu is now used as the default implementation for
<select>elements when theWebKitWebView::show-option-menusignal is left unhandled, so option menus work out of the box. - Initial drag-and-drop support has been added: web views now handle drags driven by the mouse events they already receive.
- Spell checking is now supported using the Enchant library, enabled by default, and can be toggled at build time with the
ENABLE_SPELLCHECKINGCMake option. - The on-screen keyboard is no longer shown when an element is focused programmatically; it only appears as a result of user interaction.
- Initial support for the Pointer Lock API can be enabled at build time with the
ENABLE_POINTER_LOCKCMake option. - Saving files from the Web Inspector now works in WPE.
Web Platform support
As usual, this list is not exhaustive as WebKit continuously progresses in its support for new standards. Some of the highlights for this release are:
- The Temporal API, a modern replacement for
Date, is now enabled. - WebAssembly JavaScript Promise Integration (JSPI) is now enabled, along with the WebAssembly Multi-Memory and Relaxed SIMD proposals.
- Customizable select elements (
appearance: base-select) are now supported. - The CloseWatcher API and the
closedbyattribute for<dialog>are now enabled. - CSS scroll anchoring is now enabled.
- The CSS @function rule and the if() function are now available.
- Advanced attr() substitution is now available.
- The Origin-Agent-Cluster header is now supported.
- Transferable streams and
ReadableStream.from()are now enabled. - IndexedDB getAllRecords() is now supported.
- Time zone changes in the host system are now picked up at runtime by
DateandIntl.
What’s new for WebKit developers?
WebKit can now use mimalloc as its memory allocator, as an alternative to its own bmalloc. For now it is the default only on some architectures (32-bit ARM, MIPS, RISC-V, and builds supporting 64 KB memory pages); everywhere else bmalloc remains the default, and mimalloc can be enabled with the USE_MIMALLOC build option.
Logging now falls back to the standard error output when journald is not reachable, which is common in minimal containers, and the new WEBKIT_DEBUG_OUTPUT environment variable allows choosing the log destination explicitly.
Profile-guided optimization is now supported in regular CMake builds with Clang, through the ENABLE_LLVM_PROFILE_GENERATION and USE_PGO_PROFILE options.
Finally, a note for packagers: building WPE WebKit now requires Ninja, as the CMake Makefile generator is no longer supported.
Looking forward to 2.56
The 2.56 release series will bring even more improvements, and we expect it to be released during the spring of 2027. Until then!
Claudio is long-time WebKit contributor from Igalia, working in different areas of WebKit, WPE, and the stack around it.