March 31, 2026

Introducing the JetStream 3 Benchmark Suite

Surfin’ Safari

Today, alongside our colleagues at Google and Mozilla, we announced JetStream 3.0, a major update to the cross-browser benchmark suite. While the shared announcement covers the breadth of the suite and the collaborative effort behind it, we wanted to take a moment to dive deeper into how the WebKit team approached these challenges and the engineering work in JavaScriptCore behind our improvements.

Benchmarks are among the best tools browser engine developers use to drive performance. The web is ever evolving though, and any benchmark will get out of date with new best practices. Moreover, when the most accessible optimizations have been addressed in a benchmark, subsequent optimizations tend to become less and less general and more specific to that exact workload. JetStream 3 represents both a refresh and a fundamental shift in how we measure performance, particularly regarding WebAssembly and the scale of modern web applications.

The Evolution of WebAssembly Benchmarking

One of the most significant changes in JetStream 3 is how we measure WebAssembly (Wasm) workloads. To understand why we changed it, we have to look back at where Wasm started. When JetStream 2 was released, WebAssembly was in its infancy. The earliest adopters of Wasm were large C/C++ projects that previously compiled to an earlier technology, asm.js. We anticipated large C/C++ applications (like video games) where users would be more willing to tolerate a long, one-time startup cost in exchange for high-throughput performance afterward. Consequently, JetStream 2 scored Wasm in two distinct phases: Startup and Runtime.

An Infinity Problem

Over the years, browser engines have become remarkably efficient at instantiating WebAssembly modules. As startup times improved, the incentive to pursue even micro-optimizations actually compounded. Shaving 0.1 ms off a 100 ms workload is indistinguishable from noise; however, once engines successfully reduced that instantiation time to just 2 ms, that same 0.1 ms improvement suddenly represented a 5% performance gain. In WebKit, for instance, we optimized the startup path so aggressively that for certain smaller workloads, our startup time effectively reached zero seconds. In JetStream 2, each iteration’s time was computed with Date.now(), which rounds down so any sub-1 ms time becomes 0 ms.

This created a unique challenge for the benchmark. The scoring formula was Score = 5000 / Time. Thus, when the time hit zero, the score became infinity. We eventually had to patch the benchmark harness in JetStream 2.2, clamping the score to 5000, so that a 0 ms sub-score didn’t obsolete all other scores.

While getting an infinite score sounds like a victory, it was a clear sign that browser engines had outgrown JetStream 2’s Wasm subtests. On the modern web, Wasm is in the critical path for many page loads. It is used in libraries, image decoders, and UI frameworks. A “zero” startup time in a microbenchmark doesn’t account for how well the code runs immediately after instantiation, which got lost in the long, single Runtime score. JavaScript benchmarks in JetStream 2 measured the full execution lifecycle, so we concluded that WebAssembly scoring should work the same way.

In JetStream 3, we retired the separate, single iteration, Startup/Runtime scoring for Wasm. Instead, we have adopted the same scoring methodology used for JavaScript benchmarks; Running the same code for many iterations and extracting:

  • First Iteration: Captures compilation and initial setup (the “startup” cost before).
  • Worst Case Iterations: Captures “jank” and garbage collection or tiering pauses.
  • Average Case Iterations: Captures sustained throughput.

These scores are then geometrically averaged to compute the subtest’s overall score, which in turn feeds into the geometric mean of the full benchmark score. This shift forces engines to optimize the entire lifecycle of a WebAssembly instance, ensuring that Wasm integrates smoothly into the interactive web.

Escaping the Microbenchmark Trap

Another major goal for JetStream 3 was to move away from small workloads. In the early days of browser competition, small, tight loops, typically called “Microbenchmarks”, were often used to prove one engine was faster than another. While useful for regression testing, microbenchmarks are dangerous for long-term optimization. They encourage engines to over-fit to specific patterns, creating overly specialized optimizations for a specific hot loop or function that might not translate to (or worse, even harm) general performance.

JetStream 3 focuses on larger, longer-running workloads. By increasing the code size and complexity, we dilute the impact of any single “hot” function. This forces the engine to be generally efficient across a variety of optimizations not only in the JIT compilers, but also in standard library functionality.

These new workloads also ensure we are optimizing for the features developers are actually using today:

  • JavaScript: Private/public class fields, Promises/async functions, and modern RegExp features.
  • WebAssembly: WasmGC (Garbage Collection), SIMD (Single Instruction, Multiple Data), and Exception Handling.

Optimizing JavaScriptCore for JetStream 3

With the new benchmark in place, we turned to the engineering work needed to perform well on it. To that end, the WebKit team has made significant architectural improvements to JavaScriptCore over the last year.

WebAssembly GC

JetStream 3 includes WasmGC workloads compiled from Dart, Java, and Kotlin. These languages exercise different allocation patterns and usage strategies, which stress-tests the engine’s GC implementation broadly.

Inlining GC allocations

When a WasmGC program creates a struct or an array, the browser’s engine needs to allocate memory for that object on the garbage collected heap. In our initial implementation, every allocation went through a general-purpose C++ function call. This is similar to how calling malloc works in C: the program jumps to a shared allocation routine, which finds available memory, sets it up, and returns a pointer. While correct, this overhead adds up quickly in WasmGC workloads that create millions of small objects, which is typical of languages like Dart and Kotlin that compile to WasmGC.

We optimized this in two phases. First, we changed the memory layout of GC objects. Originally, each struct and array had a separate, out-of-line backing store for its fields. This meant that creating a struct required two allocations: one for the object header and one for the field data. It also meant that every field access required following an extra pointer. We changed both structs (291579@main) and arrays (291760@main) to store their field data directly after the object header in a single contiguous allocation, eliminating the second allocation and the pointer indirection. This single change was a roughly 40% improvement on the WasmGC subtests.

Second, we inlined the allocation fast path directly into the generated machine code. Rather than calling out to a C++ function for every allocation, the compiler now emits a short sequence of instructions that bumps a pointer, writes the object header, and returns, all without leaving the generated code. The slow path (when the current memory block is full and a new one needs to be obtained) still calls into C++, but the fast path handles the vast majority of allocations. We added this optimization to both of our WebAssembly compiler tiers: the Build Bytecode Quickly (BBQ) baseline compiler (292808@main, 292925@main) and the Optimized Machine-code Generator (OMG) optimizing compiler (298551@main). We also embedded runtime type information directly into each GC object (306226@main), so that type checks (such as checking whether a cast is valid) can be done with a simple pointer comparison rather than looking up type metadata through a separate table.

GC Type Reference Counting

In WebAssembly, every function and GC object has a concrete type defined in the module’s type section. The engine needs to keep these type definitions alive for as long as any object or function references them. Wasm type definitions may be shared across Workers, while JSC’s garbage collector is per-Worker and not global across the process. To solve this mismatch, we use reference counting. In our initial version of WasmGC, each object held a reference to its type definition, and when the object was destroyed, the reference was released.

One of the performance problems we saw initially was that every time the garbage collector destroyed a WasmGC object, it had to decrement the reference count on the object’s type. For workloads that create and destroy large numbers of short-lived objects (which is often common in garbage-collected languages), this meant that garbage collection was spending a large fraction of its time just updating these reference counts. On top of that, looking up type information required acquiring a global lock and searching a hash table; this created contention when multiple threads were compiling or running WebAssembly code simultaneously.

We attacked this from several angles. The most impactful change was eliminating the need for GC objects to have destructors at all (292257@main). Previously, each GC object had a destructor that released its type reference. By restructuring how type information is stored, so that the garbage collector can find type definitions through the object’s Structure (discussed in more detail in a previous blog post) rather than reference counting them per-object, we removed the destructor entirely. This was a roughly 40% improvement on the Dart-flute-wasm subtest, because the garbage collector could now sweep dead GC objects without doing any per-object cleanup work. Finally, we also moved type references directly into the type definition records themselves (300159@main), removing the need for global locks and hash table lookups when accessing a type’s runtime type information (e.g. for casting).

GC Type Checks

Languages that compile to Wasm with Wasm GC features rely heavily on type checks. Operations like downcasting an object to a more specific type (ref.cast), testing whether an object is an instance of a type (ref.test), and verifying function signatures at indirect call sites (call_indirect) all require the engine to determine at runtime whether one type is a subtype of another. In our initial implementation, each of these checks was a call into a C++ runtime function that walked the type hierarchy, which was expensive for workloads that perform millions of casts per second.

We addressed this by inlining the type checks directly into the generated machine code using a technique known as Cohen’s type display algorithm. The idea is that each type stores a fixed-size array, called a display, containing pointers to all of its ancestor types. To check whether object A is a subtype of type B, the engine only needs to look up a single entry in A’s display at B’s inheritance depth (i.e. the number of superclasses B has) and compare pointers, rather than walking a chain of parent pointers. We first inlined this into our optimizing compiler, OMG (298798@main), and then into our baseline compiler, BBQ (298842@main), so that even code that has not yet been fully optimized benefits. To make the display lookup as fast as possible, we embedded the first six display entries directly into each type’s runtime type information record (299056@main), so that the common case of shallow type hierarchies requires no extra pointer indirection and stays within a single cache line.

We also found that many type checks could be eliminated entirely. The Wasm type system carries static type information through the wasm binary, and in many cases the engine can prove at compile time that a value already satisfies the required type, in particular, when inlining a generic leaf function. We propagated this static type information from the parser through to code generation (298875@main), so that when the compiler can prove a value is already a GC object, it skips the runtime cell and object checks that would otherwise be emitted (298961@main). Together, these changes ensure that type checking overhead is minimal across all execution tiers.

Better Inlining Heuristics with Callsite Feedback

When a compiler inlines a function, it replaces a function call with the body of the called function. This avoids the overhead of the call itself and, more importantly, gives the compiler the opportunity to optimize the caller and callee together. For example, if the caller always passes a constant to a parameter, the inlined body can be specialized for that constant.

However, inlining has a cost: it makes the compiled code larger. If the compiler inlines too aggressively, the generated code may end up duplicating the same logic repeatedly, which is worse for the CPU’s instruction cache. Secondly, the more code inlined, the longer compilations take, which in a JIT can have a significant performance impact. This means the compiler needs good heuristics to decide which call sites are worth inlining and which are not.

Our initial Wasm inlining implementation made these decisions one call site at a time. Each call was evaluated independently: if the callee was small enough and the call was hot enough, it would be inlined. The problem is that this local, greedy approach does not consider the overall picture. It might inline several less-important calls and then run out of its code size budget before reaching a call that would have been far more beneficial to inline.

We replaced this with a non-local inlining decision system (302215@main) that considers all the call sites in a function simultaneously. The algorithm assigns a priority to each candidate call site based on how frequently it is executed, how large the callee is, and how much optimization opportunity inlining would unlock. It then inlines call sites in priority order until the code size budget is exhausted. This is particularly important for WasmGC workloads, where source-language compilers for Dart, Kotlin, and Java tend to emit many small functions in order to keep download sizes small, expecting the browser’s compiler to inline the important ones.

Polymorphic Indirect Call Inlining

Languages that compile to WasmGC, such as Dart and Kotlin, rely heavily on virtual method dispatch. When you call a method on an object in these languages, the actual function that gets called depends on the runtime type of the object. In the generated WebAssembly, this is represented as an indirect call: instead of calling a specific function, the code loads a function pointer from a table and calls through it. Indirect calls are significantly more expensive than direct calls because the processor cannot predict where the call will go, and the compiler cannot inline a function if it does not know which function will be called.

To address this, we added profiling to our baseline compiler, BBQ, that records which function is actually called at each indirect call site as the program runs (299870@main). When our optimizing compiler, OMG, later recompiles the function, it uses this profile data. If a call site always calls the same function (monomorphic), OMG can generate a guard that checks “is the target still the expected function?” and then either takes a fast path that calls (or inlines) the known function directly, or falls back to the slow indirect call path. This guard-and-inline pattern turns what was an unpredictable indirect call into a predictable direct call in the common case.

We extended this approach to handle call sites that call a small number of different functions (301972@main). Rather than giving up when a call site has more than one target, we now profile up to three distinct callees. The optimizing compiler can then generate a sequence of checks, one for each known target, each followed by an inlined copy of that target’s code, with a final fallback for any unknown callee. The fallback is annotated as rare (308557@main), which tells later compiler passes to optimize for the expected case where one of the known targets matches. This optimization is not something a source language compiler can do statically, since it does not know which concrete types will appear at a given call site until the program actually runs.

Abstract Heaps

We introduced a concept called “Abstract Heaps” to our Wasm compiler pipeline, which we already had in the JS pipeline (300394@main, 300472@main, 300499@main, 305726@main). Abstract Heaps allow the compiler to reason about the side effects of Wasm instructions more precisely based on types. This enables type-based-alias-analysis (TBAA), which can describe the abstract heap access of each operation. By understanding which parts of the heap are affected by specific operations, we can eliminate redundant loads and stores in Wasm. This is particularly useful in combination with inlining, which can reveal more information about the objects being allocated. While the source language compiler can do some of this before sending the code to the browser, not all do for a number of reasons. For example, code size is very important so a source language compiler may choose not to inline in order to reduce the time to launch of an application. Additionally, as mentioned above, JavaScriptCore profiles indirect function calls and potentially inlines them, which isn’t feasible statically.

Register Allocator Improvements

A register allocator is the part of a compiler that decides which program values live in CPU registers (fast) and which must be “spilled” to memory (slow). It is one of the most performance-critical compiler passes because nearly every instruction in the generated code is affected by its decisions. It is also one of the most compilation-time-critical passes, especially for WebAssembly where compilation speed directly impacts page load time.

We replaced our previous register allocator, which dynamically switched between a graph coloring algorithm (iterated register coalescing) and a linear scan algorithm, with a new greedy register allocator. The greedy algorithm works by processing each program value’s “live range” (the span of instructions during which the value is needed) in priority order. For each value, it attempts to assign a register. If no register is available, it considers whether evicting a lower-priority value from its register would be beneficial. In our measurements, greedy allocation produces similar code quality to graph coloring, while running much faster. It also produces better code than linear scan because it can make more globally informed decisions about which values deserve registers the most.

The core data structure that tracks which registers are in use at which points in time was originally built on a balanced binary tree (std::set), which required many pointer-chasing operations across different cache lines. We replaced it with a B+ tree specifically designed for interval queries (299207@main), where each tree node is sized to fit in one or two cache lines. This made the register allocation phase nearly 2x faster for large functions, which matters because WebAssembly functions generated from languages like Dart and Kotlin can contain tens of thousands of values.

Beyond the core algorithm, we added several important features. Live range splitting (304261@main) allows the allocator to keep a spilled value in a register for the portion of code where it is most heavily used, even if it cannot stay in a register for its entire lifetime. Coalescing with pinned registers (304889@main) eliminates unnecessary copy instructions when WebAssembly runtime values, such as the pointer to the current instance, are already in a known register. Proactive spill slot coalescing (306945@main) reduces the size of the stack frame by sharing memory between spilled values that are never alive at the same time. And for values that are simple constants, the allocator can now rematerialize them (recompute them from scratch) at each use point rather than loading them from the stack (292654@main, 302864@main), which frees up a register for other values.

The improved compilation speed of the greedy allocator also had an indirect benefit: it allowed us to raise the maximum function size that our optimizing compiler will attempt to compile (292197@main). Previously, very large functions were left in the baseline tier because the optimizing compiler took too long. With the greedy allocator, we can now fully optimize larger functions without unacceptable compilation pauses. Once the greedy allocator had proven itself across all configurations, we removed the old linear scan allocator entirely (305981@main).

IPInt support for SIMD

JavaScriptCore has three execution tiers for WebAssembly: BBQ and OMG are the JIT tiers, as described above, but there is also an interpreter tier, IPInt. IPInt executes WebAssembly bytecode directly without any compilation, taking inspiration from the Wizard engine. This means IPInt can begin running code nearly instantly, only requiring a small amount of metadata. BBQ and OMG, being JIT compilers, take longer to generate code than IPInt and have a larger memory footprint but have higher throughput.

In JetStream 3, there are several workloads (transformersjs-bert-wasm and dotnet-aot-wasm) that use SIMD instructions. Previously, as IPInt did not support SIMD instructions, when a WebAssembly module used SIMD, the engine had to synchronously compile the function with BBQ before it could execute. This meant that any module using SIMD lost the near instant-start benefit of the interpreter tier and paid a mandatory compilation cost up front. It also meant that SIMD could not be used at all in JIT-less configurations, which is a requirement in some contexts for security reasons (e.g. when Lockdown Mode is enabled).

We implemented full SIMD support in IPInt, covering all of the standard WebAssembly SIMD instructions on both ARM64 and x86_64. Beyond the instructions themselves, we needed to teach the rest of the interpreter about the 128-bit v128 type: local and global variable access, function calls and tail calls, on-stack replacement to BBQ, exception handling, and WasmGC struct and array fields. We also ensured that SIMD works correctly when the JIT is completely disabled. With all of this in place, we enabled IPInt SIMD by default (301576@main), giving WebAssembly SIMD code the same near instant-start execution that non-SIMD code has always had.

JavaScript improvements

JetStream 3 includes workloads that exercise modern JavaScript features that were not yet standardized or widely adopted when JetStream 2 was released, including BigInt arithmetic, async/await-heavy control flow, and Promise combinators. Optimizing for these workloads led to improvements across several subsystems in JavaScriptCore.

BigInt Improvements

BigInt is a JavaScript feature that allows programs to work with arbitrarily large integers. Unlike regular JavaScript numbers, which are 64-bit floating point values and lose precision for integers beyond 2^53, BigInts can represent integers of any size. JetStream 3 includes workloads that exercise BigInt arithmetic heavily, which exposed several areas where our implementation could be improved.

We made improvements on two fronts: the algorithms used for arithmetic and the memory representation of BigInt values.

For multiplication, we implemented the Comba algorithm (304184@main), which is significantly faster for multiplying small, same-sized BigInts. The traditional “schoolbook” multiplication algorithm processes one digit at a time, writing partial results and carrying. Comba’s algorithm instead accumulates all the partial products for each column of the result at once, which allows the processor to pipeline the multiply-and-add operations more efficiently. We also added a fast path for single-digit multipliers (303956@main), which is the common case for operations like doubling a value.

For division, we imported and adapted a modern division algorithm implementation (304114@main), and then further optimized it with the DIV2BY1 technique (304278@main). DIV2BY1 works by pre-computing the multiplicative inverse of the divisor. Then, instead of dividing, each step of the algorithm multiplies by this inverse, which is faster on modern CPUs where multiplication is much cheaper than division. This is especially effective when dividing repeatedly by the same divisor, which is common in operations like converting a BigInt to a decimal string. We also added a cache for remainder computation (308930@main) that remembers the inverse, so that repeated modulo operations with the same divisor avoid recomputing it.

On the memory representation side, BigInts in JavaScriptCore were historically stored as a small header object with a pointer to a separate heap-allocated array of digits. This meant every BigInt operation required following a pointer to get to the actual digit data, and creating a BigInt required two allocations. We changed this so that the digits are stored directly after the object header in a single contiguous allocation (307016@main), similar to how we improved WasmGC struct layout. We also shrank the minimum BigInt object size to just 16 bytes (308026@main) by storing the sign bit in an existing, unused per-object bitfield rather than using a dedicated byte for it. For arithmetic operations that need temporary working space, we switched from allocating temporary BigInt objects on the garbage-collected heap to using stack-allocated vectors (304337@main), which avoids triggering garbage collection during intermediate computation steps. Lastly, we improved how our JIT compilers speculate on BigInt types (308054@main), allowing the optimizing compiler to generate more efficient code when it can prove that a value is a BigInt.

MicrotaskQueue and Async function improvements

The microtask queue is the mechanism that JavaScript engines use to schedule small units of deferred work. Every time a Promise resolves, the callbacks attached to it are placed on the microtask queue. Every time an async function hits an await, the continuation of that function is scheduled as a microtask. In heavily asynchronous code, such as the doxbee-async benchmark in JetStream 3 that models a chain of database operations, the microtask queue processes millions of tasks per second. This made it a critical performance target.

Our microtask queue was originally implemented in WebCore, WebKit’s web platform layer, and JavaScriptCore called into it through a virtual function interface. Every time the engine needed to schedule or run a microtask, it crossed this boundary, which involved virtual dispatch, redundant safety checks, and prevented the compiler from optimizing across the boundary. We completely rewrote the microtask queue, first extracting it into its own subsystem (291566@main), then moving it into JavaScriptCore (291649@main), and finally moving the last remaining WebCore enqueue code into JSC as well (308483@main). The new implementation uses specialized entry points for calling microtask functions (305407@main) that skip setup work that is redundant when the engine is already running, and uses lightweight tagging techniques (308140@main) to dispatch microtasks without needing to read type metadata from memory.

Alongside the microtask queue rewrite, we systematically moved all of our Promise implementation from JavaScript builtins to C++. In JavaScriptCore, “builtins” are internal functions implemented in JavaScript that ship with the engine and implement parts of the standard library. While convenient to write, they have significant startup costs because they need to be parsed and compiled, and the JIT compilers have limited ability to optimize across the boundary between builtin JavaScript code and the engine’s C++ internals. We rewrote Promise.all (304039@main), Promise.race (303893@main), Promise.allSettled (304081@main), Promise.any (304084@main), Promise.prototype.finally (305248@main), and Promise.resolve/reject (301423@main) in C++. This eliminated the parsing and compilation overhead and allowed us to hand-optimize the hot paths. We also added support in our optimizing JIT compilers for Promise.prototype.then (308636@main), so that when the compiler can prove the promise is a standard Promise (not a subclass with overridden behavior), it can generate the then operation inline without any function call overhead.

For async functions specifically, we made three significant improvements. First, we changed how async functions are resumed after an await. Previously, awaiting a value would create a promise reaction (a callback) that, when triggered, would schedule a microtask to resume the function. This double-dispatch meant each await required two trips through the microtask queue. We changed this so that the microtask queue drives async function resumption directly (303208@main), cutting the overhead in half. Second, for async functions that contain no await (which is more common than it might sound, since conditional code paths may not always reach an await), we inline the entire function body so that it executes synchronously without any generator or microtask overhead at all (304987@main). Third, after each await, the engine must check whether the awaited value is a “thenable” (an object with a then method), because thenables are treated specially by the Promise resolution algorithm. We optimized this check by tracking whether the then property has ever been added to common object types (304952@main), so that for plain objects and iterator results that do not have a then property, the check can be skipped entirely (304355@main).

Performance Results

The combination of these architectural changes resulted in roughly a 10% improvement from Safari 26.0 to Safari 26.4. Because JetStream 3 scores the full lifecycle of each workload, this improvement reflects gains that users experience directly: faster initial page loads from quicker WebAssembly compilation, and smoother interactions from more efficient JavaScript execution and reduced garbage collection pauses.

Horizontal bar chart titled 'JetStream 3 (M4 MacBook Air)' comparing JavaScript benchmark scores between two macOS versions. macOS Tahoe 26.0 scored 359, macOS Tahoe 26.4 scored 395, approximately a 10% performance improvement. The x-axis ranges from 0 to 400, and the legend notes that higher scores are better. Bars are light blue.

Conclusion

JetStream 3 reflects a shift in how we think about browser benchmarking: scoring the full lifecycle rather than isolated phases, measuring real-world workloads rather than microbenchmarks, and developing the suite collaboratively across browsers. Building it pushed us to make meaningful architectural improvements to JavaScriptCore, from WasmGC allocation and inlining to BigInt arithmetic and async function execution. We’re excited about the opportunities provided by the JetStream 3 benchmark to further improve the performance of applications on the web. As always, we will continue our efforts to make the fastest, most secure browser for Safari users.

March 31, 2026 06:30 PM

March 30, 2026

Igalia WebKit Team: WebKit Igalia Periodical #61

Igalia WebKit

Update on what happened in WebKit in the week from March 23 to March 30.

This week comes with a mixed bag of new features, incremental improvements, and a new release with the ever important security issue fixes. Also: more blog posts!

Cross-Port 🐱

Implemented initial support for closedby=any on dialog elements, which adds light dismiss behaviour. This is behind the ClosedbyAttributeEnabled feature flag.

Added the remaining values for the experimental closedby attribute implementation.

MiniBrowser now has a --profile-dir=DIR command line option that can be used to specify a custom directory where website data and cache can be stored, to test, for example, behavior in a clean session.

Multimedia 🎥

GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.

Video decoding limits had been enforced on HTMLMediaElement.canPlayType() so far, but they are now also enforced in MediaCapabilities queries.

Graphics 🖼️

Fixed several OpenGL state restoration bugs in BitmapTexture . These could cause a mismatch between the GL state assumed by Skia and the actual one, leading to rendering artifacts with certain GPU drivers and configurations.

The SKIA_DEBUG CMake option has been enabled for Debug builds, enabling Skia's internal assertions, debug logging, and consistency checks (e.g. bounds checking, resource key diagnostics). It remains off by default for Release and RelWithDebInfo builds, and can still be explicitly configured via -DSKIA_DEBUG=ON|OFF.

WPE WebKit 📟

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

The new WPE_SETTING_OVERLAY_SCROLLBARS setting is now available, and disabling it will use a more traditional, always visible scrollbar style.

Releases 📦️

A new USE_GSTREAMER build option may now be used to toggle the features that require GStreamer at once. This can be used to effectively disable all multimedia support, which previously needed toggling four CMake options.

WebKitGTK 2.52.1 and WPE WebKit 2.52.1 have been released. On top of a small corrections typical of the first point releases in a new stable series, this one includes a number of fixes for security issues, and it is a recommended update. The corresponding security advisory, WSA-2026-0002 (GTK, WPE) has been published as well.

Community & Events 🤝

Simón Pena wrote a blog post showing how to create a minimal WPE launcher, which uses a Fedora Podman container with pre-built WPE WebKit libraries and a launcher with barely 10 lines of code to display a web view. This complements Kate Lee's custom HTML context menu blog post from last week.

That’s all for this week!

By Igalia WebKit Team at March 30, 2026 09:46 PM

March 26, 2026

Release Notes for Safari Technology Preview 240

Surfin’ Safari

Safari Technology Preview Release 240 is now available for download for macOS Tahoe and macOS Sequoia. If you already have Safari Technology Preview installed, you can update it in System Settings under General → Software Update.

This release includes WebKit changes between: 308418@main…309286@main.

CSS

New Features

  • Added support for the revert-rule CSS keyword, which rolls back the cascade to behave as if the current style rule had not been present. (308733@main) (171132753)

Resolved Issues

  • Fixed an issue on macOS where custom CSS scrollbars could be cut off and the scrollbar corner rect was sized incorrectly. (309119@main) (168566468)
  • Fixed hanging-punctuation to support U+0027 (apostrophe) and U+0022 (quotation mark) as hangable quotes. (308597@main) (171672576)
  • Fixed hanging-punctuation to allow ideographic space (U+3000) to hang when used with the first value. (308605@main) (171679311)

Editing

Resolved Issues

  • Fixed an issue where the Font Picker style selection became unusable after changing fonts when editing multiple lines of text. (308562@main) (110651645)
  • Fixed an issue where emoji images were not preserved correctly when copying and pasting content across different websites. (309176@main) (162708499)
  • Fixed an issue where text selection would jump unexpectedly when selecting absolutely-positioned content inside an element with user-select: none. (308451@main) (170475401)

Forms

Resolved Issues

  • Fixed an issue where keyboard tabbing position was lost when a focused button became disabled, causing focus to jump to the beginning of the page. (308991@main) (120676409)

HTML

Resolved Issues

  • Fixed viewport <meta> parsing to correctly treat form feed as ASCII whitespace per the HTML specification. (309044@main) (108440799)
  • Fixed incorrect parsing of pixel-length margin attributes on <body>, <iframe>, and <frame> elements. (308526@main) (171240848)

Media

Resolved Issues

  • Fixed an issue where decoding WebM audio files with more than two channels would fail. (308749@main) (82160691)
  • Fixed MediaCapabilities.decodingInfo() incorrectly reporting VP8 in WebM as not supported. (308789@main) (127339546)
  • Fixed an issue where MP4 files containing Opus audio tracks could not be decoded with decodeAudioData. (309140@main) (170196423)
  • Fixed an issue where Live Text selection was unavailable on paused fullscreen videos. (308498@main) (170817667)
  • Fixed an issue where FairPlay-protected VP9 content failed to play via MediaSource. (308622@main) (171210968)
  • Fixed an issue where autoplay would proceed before default text tracks finished loading. (308796@main) (171699293)
  • Fixed the currentTime getter to return defaultPlaybackStartPosition when no media player exists. (308654@main) (171722368)
  • Fixed HTMLMediaElement to fire a timeupdate event when resetting the playback position during media load as required by the specification. (308695@main) (171785463)
  • Fixed an issue where the media player preload attribute was not properly updated when the autoplay attribute was set. (308815@main) (171883159)

PDF

Resolved Issues

  • Fixed an issue where panning a zoomed-in PDF on iOS would frequently rubber band back to the starting position. (309264@main) (156854435)

Rendering

New Features

  • Added support for subpixel inline layout, enabling more precise text and inline element positioning. (309251@main) (171835370)

Resolved Issues

  • Fixed an issue where table cells with rowspan values exceeding the actual number of rows were incorrectly computing heights. (309153@main) (3209126)
  • Fixed an issue where replaced elements with intrinsic content size keywords like fit-content were not properly accounting for aspect ratio constraints. (308995@main) (168264069)
  • Fixed an issue where table sections with explicit heights did not properly constrain and distribute space among contained rows. (308540@main) (169235210)
  • Fixed an issue where auto table layout did not honor max-width on table cells when distributing width between them. (308934@main) (171459245)
  • Fixed an issue where border-spacing incorrectly included collapsed columns in auto table layout calculations. (308549@main) (171468102)
  • Fixed an issue where percentage-height children of table cells with unresolvable percentage heights were not sized intrinsically. (308547@main) (171469500)
  • Fixed an issue where MathML token elements ignored -webkit-text-fill-color when painting math variant glyphs. (308911@main) (172020318)
  • Fixed an issue where cell backgrounds in collapsed-border tables extended into adjacent cells’ border space at table edges. (308969@main) (172068907)
  • Fixed an issue where list item margins were computed incorrectly when the page was zoomed in or out. (309152@main) (172312498)
  • Fixed an issue where about:blank iframes did not always have a transparent background. (309212@main) (172400258)

SVG

Resolved Issues

  • Fixed an issue where the XML document parser did not defer inline script execution until pending stylesheets had loaded. (308980@main) (122574381)
  • Fixed an issue where SVG elements referencing non-existent filter IDs were not rendered. (308809@main) (164046592)
  • Fixed an issue where removeAttribute for width or height on an SVG root element did not reset to the default 300×150 dimensions. (308906@main) (170233990)
  • Fixed an issue where UI events such as wheel failed to fire for inner SVG elements. (309010@main) (171703966)
  • Fixed onbegin, onend, and onrepeat event handler attributes on SVGAnimationElement to properly map to their corresponding event types. (309273@main) (172533070)

Deprecations

  • Removed the non-standard nearestViewportElement and farthestViewportElement properties from SVGGraphicsElement, aligning with the SVG2 specification. (308436@main) (171262197)

Scrolling

Resolved Issues

  • Fixed a scroll snap issue where navigating between items could incorrectly snap back when layout changes occurred. (308420@main) (166596210)
  • Fixed an issue where pages using the Navigation API could have offset hit test locations, making elements unclickable. (308976@main) (171752650)

Web API

New Features

  • Added support for a wider range of characters in DOM element and attribute names, aligning with the updated WHATWG specification. (308884@main) (95205349)

Resolved Issues

  • Fixed location.ancestorOrigins returning stale origins after an iframe is removed from the document. (309126@main) (169097730)

Web Extensions

Resolved Issues

  • Fixed an issue where extensions with a trailing comma in manifest.json failed to load in Safari. (308935@main) (171749937)

Web Inspector

New Features

  • Added support for showing contrast information in the Color Picker when editing background color properties. (308653@main) (171332734)

Resolved Issues

  • Fixed an issue where previewing resources in the Network tab displayed an error upon navigating away and Preserve Log was enabled. (308559@main) (171216835)
  • Fixed an issue where selected DOM node keys in a Map in the Scope Chain sidebar had unreadable white text on a light background. (309006@main) (171840122)

WebAssembly

Resolved Issues

  • Fixed WebAssembly.compileStreaming and WebAssembly.instantiateStreaming to correctly accept compileOptions for JS string builtins. (308234@main, 308419@main) (170989896)

March 26, 2026 11:15 PM

March 24, 2026

WebKit Features for Safari 26.4

Surfin’ Safari

March has a way of bringing a lot of new things to WebKit — and this year is no exception. With 44 features, 191 fixes and one deprecation, Safari 26.4 is a release packed with web technology.

What’s most exciting? CSS Grid Lanes brings masonry-style layouts to the web platform — a long-awaited solution for building rich visual galleries and much more. WebTransport provides a modern alternative to WebSocket, opening the door to low-latency experiences like multiplayer games, live collaboration tools, and improved video conferencing. And Keyboard Lock API gives web apps better control over keyboard shortcuts.

Beyond the headliners, the heart of this release is focused on what web developers ask for most. We hear you loud and clear. Results from 2025 developer surveys made it clear you want time to catch up with new features, not be swamped with more. You want existing features to work consistently across every browser. You asked for browser engineers working on WebKit to help you by squashing bugs and closing gaps in spec coverage. That’s what this release aims to do.

Most of the features listed in the Safari 26.4 release notes are not entirely new. Many update features where the behavior didn’t quite match the latest web standard. Others make specific combinations of existing features work better together. Here’s one example — we shipped support for the CSS min() and max() functions in 2018, almost two years before any other browser. And we shipped the HTML sizes attribute for responsive images even longer ago, back in 2014. But until now, you couldn’t use min(), max(), or clamp() inside of the sizes attribute in WebKit. In Safari 26.4, you can.

And then there are the bug fixes — hundreds of them across the platform. WebKit engineers went deep to improve specific areas like SVG, Tables, MathML, and CSS Zoom. And we’re continuing our multi-year rewrite of the layout engine. Blocks-in-inline layout is complete in Safari 26.4, work on Flexbox continues, and we’ve now begun rewriting CSS Grid.

As we continue this work, we greatly value your input. If something has been bothering you, it’s worth testing in Safari 26.4 to see if it’s been fixed. If the bug is still there, please file it at bugs.webkit.org. Or if it’s already filed, add additional comments to the existing issue describing your experience and why fixing it matters for you. When multiple websites signal that something needs fixing, that helps. The more concrete you can be — a brief story about why this hurts your users, a link to a real website affected, a snippet of code or reduced test case — the better equipped we are to prioritize and fix what matters most. We are paying attention, even if our response shows up as a shipped fix rather than a reply in the issue.

We care deeply about the experience people have when using Safari. And we care about the experience people have when using any of the millions of iOS, iPadOS, macOS, visionOS, and watchOS apps that are built using WebKit and JavaScriptCore. When our customers open a news app, a shopping app, a travel app, a banking app — there’s a good chance the interface they’re interacting with is powered by the same HTML, CSS, and JavaScript you write every day when making web apps and websites that live at a URL. Every rendering fix, every improved feature, every performance gain we ship benefits everything touched by the web platform.

We are passionate about making experiences our customers love, no matter how they use their device. Web designers and developers are central to those experiences. You are our partners. We truly hope this release makes a real difference in your success.

CSS

Grid Lanes

Safari window showing photo gallery laid out with Grid Lanes, alternating narrow and wide columns, with photos of all sorts of aspect ratios neatly aligned in columns

Safari 26.4 adds support for Grid Lanes. It’s a new layout system that extends CSS Grid with powerful capabilities for creating flexible, flowing layouts. You might be familiar with the patterns it can create. Often called “masonry” or “waterfall” layouts, they were made popular like sites like Pinterest and attainable with Javascript libraries like masonry.js. Now this capability is available in CSS with display: grid-lanes.

And it can do much more than just the classic layout. At its root, Grid Lanes provides a way to pack content of different aspect ratios into either columns or rows. And does so in a way where users can tab through all the visible content, and where new content can be lazy-loaded at the end.

Grid Lanes builds on the foundation of CSS Grid, giving it powerful track sizing and explicit placement capabilities.

.container {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

Safari 26.4 supports the recently-finalized core syntax for Grid Lanes (see Introducing CSS Grid Lanes), including support for flow-tolerance, which gives you control over how aggressively each item tries to “change lanes” to be closest to the start. This affects how much the placement of consecutive items shifts back and forth across the page — a particularly important consideration for accessibility. Try tuning the value, and see how it affects item placement and keyboard navigation across items.

Grid Lanes opens up new possibilities for layouts that have been difficult to achieve with existing CSS. In addition to the Pinterest-style masonry layouts, you can use it to create dynamic arrangements of content in either columns or rows. Check out our Grid Lanes demos for more ideas. Learn about browser support & progressive enhancement strategies in When will CSS Grid Lanes arrive? How long until we can use it?

We also added support for Grid Lanes to Web Inspector.

Same example of Grid Lanes layout, a gallery of photos. This time, with the Web Inspector open to the Grid layout tooling, with item numbers marked on the screen.

The Grid Inspector now supports CSS Grid Lanes with order number overlays that show the placement sequence of grid items. You can toggle order number visibility in the settings and visualize gaps between items arranged by Grid Lanes. Auto-completion for grid-lanes and inline-grid-lanes values in the display property makes it easier to experiment with these new layout options. Learn more in New Safari developer tools provide insight into CSS Grid Lanes.

Name-Only Container Queries

Safari 26.4 adds support for name-only @container queries, allowing you to target named containers without specifying any size conditions. Previously, container queries required both a container name and a condition — like @container sidebar (min-width: 300px). Now you can query just the container name:

@container sidebar {
  .card {
    padding: 1rem;
  }
}

This is useful when you want to apply styles based on which container an element is inside, regardless of that container’s size. It’s particularly handy for component variants or when you’re using container queries more for scoping styles than for responsive behavior. You still define the container name the same way:

.sidebar {
  container-name: sidebar;
  container-type: inline-size;
}

Now you can target elements inside that container without worrying about size breakpoints, giving you more flexibility in how you structure your component styles.

Custom Cursors on ::marker

Safari 26.4 adds support for the cursor property on the ::marker pseudo-element, letting you customize pointer behavior for list markers.

li::marker {
  cursor: pointer;
}

This is one step toward making ::marker more flexible.

Math Typography

Safari 26.4 adds several improvements for mathematical typography, making it easier to display and style mathematical notation on the web.

The math-depth property gives you control over the sizing of nested mathematical expressions. In complex formulas with fractions, superscripts, and subscripts, math-depth automatically scales nested elements appropriately, ensuring readability while maintaining proper mathematical hierarchy.

Safari 26.4 also adds support for font-size: math, which applies proper scaling logic for mathematical content. This works together with math-depth to ensure formulas render at the right size relative to surrounding text.

For layout performance, you can now use CSS size containment with MathML elements via contain-intrinsic-inline-size and contain-intrinsic-block-size. This helps the browser optimize rendering of complex mathematical expressions, particularly when they’re off-screen or being dynamically loaded.

Finally, math-style and math-shift now animate as discrete values, allowing you to create transitions between different mathematical presentation styles.

These additions make MathML more powerful and performant, particularly for educational content, scientific publications, and any site that needs to display mathematical notation.

Along with these new features, a number of MathML bugs were fixed:

  • Fixed default MathML rule thickness to use the font’s underlineThickness metric with a zero fallback. (164693673)
  • Fixed mpadded elements in RTL (dir="rtl") to respect lspace. (164740784)
  • Fixed an issue where empty <msqrt> radical operators in MathML were not painted correctly. (164776629)
  • Fixed MathML <mpadded>, <mfrac>, <munderover>, <mover> and <mspace> elements not updating layout when attributes like width, height, depth, lspace, and voffset etc. changed. (164797996)
  • Fixed MathML boolean attributes so they are now compared ASCII case-insensitively. (164819048)
  • Fixed incorrect positioning of mpadded content in right-to-left mode. (166045517)
  • Fixed an issue where <msqrt> or <mroot> elements did not update their radical operators when children were dynamically added or removed. (166556627)
  • Fixed <mpadded> so that percentage values for width, height, and depth attributes are treated as absent and use content dimensions as defaults, matching the MathML Core specification. (167350169)

Zoom

Safari 26.4 includes a significant set of CSS Zoom fixes. The zoom property in CSS scales an element and its contents. While transform: scale() does something similar, zoom is handy because it affects layout, causing surrounding content to respond to the change in size.

CSS zoom is separate from browser zoom, (the kind triggered by ⌘+ and ⌘−), but the two can compound. An element with zoom: 2 on a page that’s been zoomed in to 150% gets scaled by both factors. Getting everything to calculate correctly in that situation is genuinely tricky, and a lot of the fixes in this release are about getting that right. Properties like width, height, margin, and positional values now correctly apply zoom at used-value time. CSS calc() expressions correctly account for the zoom factor. And getBoundingClientRect() and getClientRects() in JavaScript now return properly scaled values.

Here’s the full list:

  • Fixed width, height, min-width, min-height, max-width and max-height to apply CSS zoom at used-value time. (161848512)
  • Fixed CSS zoom to scale <iframe> element contents. (162314059)
  • Fixed getBoundingClientRect and getClientRects to return scaled lengths according to CSS zoom instead of unscaled values, aligning with the CSS Viewport specification. (162325730)
  • Fixed top, left, right, and bottom to apply CSS zoom at used-value time (162663056)
  • Fixed margin to apply CSS zoom at used-value time. (162907254)
  • Fixed evaluation of calc() expressions to correctly apply the used zoom factor to length values, ensuring properties like line-height and box dimensions scale properly. (163141549)
  • Fixed an issue where calc(em) values for unzoomed properties were incorrectly adjusted. (163267333)
  • Fixed an issue where calc() incorrectly treated margins as present. (163605539)
  • Fixed devicePixelRatio so that page zoom now affects the main frame consistently with iframes, keeping their values synchronized. (163857955)
  • Fixed line-height to correctly scale font-relative units when text zoom is applied. (165073337)
  • Fixed performance and correctness issues with inheritance in the modern CSS Zoom implementation. (167715972)
  • Fixed an issue where <video> poster images were incorrectly double-scaled when zoom was applied by using the cached intrinsic poster size without reapplying zoom. (150976146)

Threaded Scroll-driven Animations

Safari 26.4 adds support for threaded Scroll-driven Animations. Now, the animations driven by scroll position run on the compositor thread, separate from the main thread. You don’t need do anything to get this benefit. When you use animation-timeline: scroll() or animation-timeline: view(), you automatically get the same kind of performance benefit that CSS transitions and keyframe animations have long enjoyed. Your scroll-driven animations will stay smooth even when the main thread is busy.

It also includes the following update:

  • Fixed incorrect overlap calculations for transform animations including translate, scale, and rotate properties. (88383253)

Positioning

Safari 26.4 includes many refinements for Anchor Positioning. It updates absolutely positioned boxes in scrollable containing blocks to allow alignment overflow in scrollable directions, in accordance with a recent CSS spec change. And it folds several more fixes to positioning:

  • Fixed an infinite style resolution loop when a position-try box was inside a display: none subtree. (161570947)
  • Fixed position-area normal alignment to align toward the non-auto inset when only one inset is auto, in accordance with recent CSS spec changes. (163317238)
  • Fixed an issue where fixed-positioned boxes using position-area were incorrectly included in the root scrollable containing block, ensuring they position correctly while still allowing overflow when appropriate. (163955483)
  • Fixed an issue where padding-inline-end was not included in the scrollable overflow for block containers. (170503510)
  • Fixed an issue where position-area for elements in scrollable containers only used the initial scrollport bounds instead of the entire scrollable area. (170503694)
  • Fixed an issue where absolutely-positioned elements inside inline containers did not correctly account for the margin of a preceding block when determining their static position. (170934098)
  • Fixed incorrect width calculation for positioned elements using box-sizing: border-box with an aspect-ratio, ensuring borders and padding are not double-counted. (121500004)
  • Fixed getComputedStyle("top") to correctly resolve percentage values for absolutely positioned elements inside inline containers. (161390162)
  • Fixed an issue where CSS @starting-style entry animations were only applied on the first transition, especially when interacting with anchor positioning or position fallbacks. (163928932)

Layout Engine Improvements

For several years, WebKit engineers have been working to rebuild each layout algorithm on a more modern foundation. Rather than waiting until a rewrite is complete before shipping its benefits, we’ve developed a mechanism so the new algorithm handles what it can as early as possible. Each algorithm starts with simpler use cases, then expands coverage over time as it becomes more capable — until it handles every situation.

Inline layout was completed in Safari 17.4 two years ago. Now, Safari 26.4 completes blocks-in-inline layout — fixing bugs and improving performance when block-level elements appear inside inline elements. Our work on the new Flexbox algorithm continues as well, covering increasingly complex cases over time.

Work on a new Grid layout engine is also now underway. When you use CSS Grid in a simple fashion, the new implementation in Safari 26.4 may be handling your layout. As the work progresses, support for more complex Grid layouts will follow.

Specific changes in Safari 26.4 include:

  • Fixed incorrect min and max width calculations for block-level boxes inside inline content. (166157696)
  • Fixed an issue where getClientRects returned an incomplete list of rectangles for inline boxes containing block elements. (167209147)
  • Fixed baseline alignment for grid items by adding correct first baseline and last baseline row axis handling and properly accounting for baseline offsets. (155967278)

Table Layout

Table layout dates back to the early days of CSS, when the specification left a lot of behavior undefined. Browsers are still working through the consequences.

  • Fixed an issue where max-width was not correctly applied to tables with fixed widths. (96554687)
  • Fixed table layout so that fixed horizontal margins on <caption> elements now contribute to the table’s minimum preferred logical width, preventing captions from causing narrower than expected tables. (120990942)
  • Fixed table column width distribution when a colspan spans mixed percentage and auto-width columns to properly respect percentage constraints. (165561401)
  • Fixed baseline handling for table cells when cell has no inflow children. (160774504)
  • Fixed element.clientWidth and element.clientHeight to correctly include padding for content-box tables. (165515755)
  • Fixed table height calculation to correctly account for captions with orthogonal writing-mode. (167220730)

Additional changes to CSS

Safari 26.4 removes the deprecated FontFaceSet constructor from the CSS Font Loading API, aligning with a CSSWG resolution that deemed it unnecessary. The constructor was rarely used and its removal shouldn’t affect the web.

And this release includes even more improvements to CSS:

  • Fixed an issue where display: list-item was incorrectly supported on fieldset. (95638460)
  • Fixed incorrect sizing and fragment URL handling for SVG images used in -webkit-cross-fade(). (106633417)
  • Fixed text-combine-upright to properly ignore letter-spacing when composing text horizontally, aligning with the CSS Writing Modes specification. (116562622)
  • Fixed an issue where background-blend-mode was not applied correctly when combined with background-clip: text. (120901898)
  • Fixed the UA style sheet to use :focus-visible instead of :focus for outline properties. (123155364)
  • Fixed HighlightRegistry to remove its non-standard constructor and updated tests to use CSS.highlights while ensuring Map.prototype is properly restored after tampering. (125529396)
  • Fixed handling of @property registration so that the initial-value descriptor can be optional. (131288198)
  • Fixed scrolling behavior so that scrollRectToVisible() can bring fixed anchor-positioned boxes outside the viewport into view, improving keyboard navigation. (162378346)
  • Fixed an issue where @font-face and FontFace.family failed when the font family name contained spaces, ensuring the family name is now treated as a plain string instead of being parsed. (162637501)
  • Fixed incorrect underline positioning for text-decoration when inline box sides are trimmed. (163858721)
  • Fixed -webkit-line-clamp so that it no longer propagates into inline-block children. (164488778)
  • Fixed an issue where nested identical CSS filter effects were not rendered. (165163823)
  • Fixed: Refactored the handling of block-level boxes inside inline boxes. (165523565)
  • Fixed an issue where text-decoration: underline appeared higher than expected when text-box-trim was applied to the root inline box. (165614136)
  • Fixed ::first-line pseudo-element to always use inline display to match the CSS Display specification. (166068698)
  • Fixed an issue where shape-outside did not update correctly after web fonts loaded. (166336491)
  • Fixed incorrect text selection ranges for truncated right-to-left content. (166944754)
  • Fixed counter- properties serialization order. (167518994)
  • Fixed outline-width and outline-offset to follow updated computed style resolution rules. (167618367)
  • Fixed the computed style resolution for border-width properties. (167689519)
  • Fixed the computed style resolution for the column-rule-width property. (167725940)
  • Fixed border-width, outline-width, and column-rule-width so they now pixel snap correctly during CSS animations and transitions. (167763497)
  • Fixed the disclosure triangle in <details> elements to use the larger system-ui font variant, preventing it from rendering as an emoji and ensuring consistent display across writing directions. (168364553)
  • Fixed CSS rules within @scope not being applied to <input> and <textarea> elements. (169751338)
  • Fixed an issue where empty CSS rules sharing a selector with non-empty rules could prevent dynamic style updates from applying correctly. (170348749)
  • Fixed an issue where CSS transitions were incorrectly triggered when border-width computed values did not change, causing unexpected layout shifts. (170657059)
  • Fixed an issue where multi-column layout rendered columns at incorrect positions for some fonts. (171016194)

WebTransport

Safari 26.4 adds support for WebTransport, a modern API for low-latency, bidirectional communication between clients and servers.

If you’ve been building real-time applications — like multiplayer games, live collaboration tools, or video conferencing — you’ve likely run into the limitations of existing technologies. WebSockets work well for many use cases, but they’re built on TCP, which means head-of-line blocking can cause delays when packets are lost. HTTP/3 and QUIC solve this problem, but don’t directly offer the same bidirectional streaming capabilities you might need.

WebTransport gives you the best of both worlds. It runs over HTTP/3 and QUIC, providing multiple streams that won’t block each other. You can send data reliably (like WebSockets) or unreliably (when you’d rather drop old data than wait for it), and you can mix both approaches in the same connection. This makes it particularly useful for applications where low latency matters most — think live audio, real-time sensor data, or fast-paced gaming. When the underlying network environment doesn’t support QUIC, WebTransport can run over HTTP/2 and TCP as a fallback with the same API.

The API is built around streams, similar to the Streams API you might already be using elsewhere. You can open bidirectional streams, send datagrams, and handle backpressure naturally. Here’s a basic example:

const url = 'https://example.com:443/webtransport';
const transport = new WebTransport(url);

await transport.ready;

// Send data via a unidirectional stream
const stream = await transport.createUnidirectionalStream();
const writer = stream.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
await writer.close();

WebTransport opens up new possibilities for what you can build on the web, bringing performance characteristics that previously required native applications or complex workarounds.

A related fix also landed:

  • Fixed ReadableStream and WritableStream to correctly pass abort and cancel reasons and improved WebTransport stream handling. (165474756)

Also fixed in Networking:

  • Fixed a regression where fetch() would throw a TypeError when using targetAddressSpace: 'loopback' for localhost requests. (166574523)

Web API

Keyboard Lock API

Safari 26.4 adds support for the Fullscreen Keyboard Lock API, which allows web applications to capture keyboard input that would normally be handled by the browser.

If you’ve ever built a game, a remote desktop client, or a creative tool, you’ve probably encountered this frustration: certain keyboard shortcuts are off-limits. Press the Escape key and you are thrown out of fullscreen mode. There’s been no way to prevent this — until now.

The Keyboard Lock API lets you request access to specific keys when your application needs it. A game can now use the Escape key for its own menu system. To exit fullscreen mode then the user would need to hold down the escape key for 1.5 seconds. A remote desktop client can pass through all keyboard shortcuts to the remote machine.

element.requestFullscreen({ keyboardLock: "browser" });

The browser automatically releases the lock when the user leaves fullscreen or switches tabs. And of course, this only works in secure contexts — sites served over HTTPS.

ReadableByteStream

Safari 26.4 adds support for ReadableByteStream, completing the implementation of byte-oriented streams in the Streams API.

Until now, ReadableStream has primarily worked with chunks of any type — objects, strings, or arbitrary data. ReadableByteStream is specifically designed for efficiently handling binary data, like files, network responses, or media streams.

The key advantage is memory efficiency. With byte streams, you can use BYOB (Bring Your Own Buffer) readers, which let you provide your own ArrayBuffer for the stream to fill. This means you can reuse buffers and avoid unnecessary allocations.

const reader = stream.getReader({ mode: 'byob' });
const buffer = new ArrayBuffer(1024);
const { value, done } = await reader.read(new Uint8Array(buffer));

Safari 26.4 also adds support for using byte streams as fetch request and response bodies, and for reading Blob.stream() with a BYOB reader. If you’re working with video, audio, large files, or any scenario where you’re moving significant amounts of binary data around, byte streams give you the tools to do it efficiently.

A related encoding fix also landed:

  • Fixed incorrect handling of invalid UTF-8 sequences in the TextDecoder streaming decoder to properly manage partial sequence buffers. (166583808)

ReadableStream Async Iteration

Safari 26.4 adds support for ReadableStream.getIterator() and the [@@asyncIterator] method, making it much easier to work with streams using modern JavaScript syntax. Previously, reading from a stream required manually calling getReader() and repeatedly calling read() in a loop. Now you can use for await...of to iterate over stream chunks directly:

const response = await fetch('/data');
const stream = response.body;

for await (const chunk of stream) {
  // Process each chunk as it arrives
  console.log(chunk);
}

This is much cleaner than the previous approach and fits naturally with how you’re already using async iteration elsewhere in JavaScript. It’s particularly nice when you’re processing streaming data — like server-sent events, large file uploads, or any scenario where data arrives over time.

Improvements to Scoped Custom Element Registries

In September, Safari 26.0 was the first browser to ship support for the standardized version of Scoped Custom Element Registries. Before then, custom elements were always registered globally on a single registry shared across the entire document. This created problems when building component libraries or working with multiple frameworks. If two different parts of an application tried to register the same element name, one would fail.

Scoped registries solve this by letting you create separate registries for different parts of the same document:

const starRegistry = new CustomElementRegistry();
starRegistry.define('my-button', MyCustomButton);
document.createElement('my-button', { customElementRegistry: starRegistry });

const bananaRegistry = new CustomElementRegistry();
bananaRegistry.define('my-button', MyFancyButton);
document.createElement('my-button', { customElementRegistry: bananaRegistry });

Safari 26.4 now extends Scoped Custom Element Registries by:

  • Upgrading elements in CustomElementRegistry.prototype.initialize
  • Supporting the customelementregistry content attribute and handling of nullcustomElementRegistry values in document.createElement, document.createElementNS, and element.attachShadow
  • Fixing shadowrootcustomelementregistry attribute serialization to correctly compare ShadowRoot and document registries. (165476421)

Auxiliary Mouse Button Support

Safari 26.4 adds support for auxiliary mouse button values in MouseEvent.button, so you can now detect additional mouse buttons beyond the standard left, middle, and right clicks. If you’re building applications that take advantage of gaming mice or other input devices with extra buttons, you can now respond to those inputs.

element.addEventListener('mousedown', (e) => {
  if (e.button === 3) { /* back button */ }
  if (e.button === 4) { /* forward button */ }
});

The button property will correctly report values for buttons 3, 4, and beyond, matching the behavior in other browsers.

Resource Timing

Safari 26.4 adds two new attributes from the Resource Timing Level 3 specification: finalResponseHeadersStart and firstInterimResponseStart. These give you more precise timing measurements for HTTP responses — particularly useful if you’re using 103 Early Hints.

firstInterimResponseStart captures when the first interim response (like a 103 Early Hints response) was received, and finalResponseHeadersStart captures when the final response headers arrived. Together they let you measure exactly how much time your 103 Early Hints are saving.

const [entry] = performance.getEntriesByType('resource')
  .filter(e => e.name.includes('example.com'));

console.log(entry.firstInterimResponseStart);  // when 103 arrived
console.log(entry.finalResponseHeadersStart);  // when final headers arrived

If you’re not yet using 103 Early Hints, these attributes give you a reason to explore it — you can now actually measure the impact.

MediaDeviceInfo in Secure Contexts Only

Safari 26.4 now exposes the MediaDeviceInfo interface only in secure contexts (HTTPS), aligning with the web specification. If you’re working with navigator.mediaDevices.enumerateDevices() to detect cameras and microphones, make sure your site is served over HTTPS.

Along the same lines:

  • Fixed DeviceMotionEvent and DeviceOrientationEvent interfaces so that they only show up in secure contexts just like the corresponding events and made ondevicemotion and ondeviceorientation enumerable, aligning with the specification. (44804273)

Resolved issues

Safari 26.4 also includes a number of other Web API fixes:

  • Fixed an issue where IntersectionObserver computed the root rectangle incorrectly when overflow clipping was present. (117143395)
  • Fixed Trusted Types to correctly send CSP violation reports when a default policy returns an invalid javascript: URL or throws an exception. (160960418)
  • Fixed Element.requestFullscreen on iOS to correctly reflect hardware keyboard attachment state during fullscreen sessions and exit automatically when the keyboard is detached. (161429040)
  • Fixed event ordering and committed promise timing for intercepted Navigation API traverse navigations. (161445256)
  • Fixed the processing order of Trusted Types for DOM attribute setting. (162143148)
  • Fixed NavigateEvent to correctly fire an AbortSignal when a navigation is aborted. (163957784)
  • Fixed NavigateEvent.sourceElement to correctly reference elements from different browsing contexts. (163962362)
  • Fixed the Navigation API to prevent scripts from flooding the system with navigation requests. (164510890)
  • Fixed an issue where scroll-margin from IntersectionObserver incorrectly applied to scrollers inside cross-origin iframes. (164994009)
  • Fixed DigitalCredential behavior to make user mediation implicitly required. (165597827)
  • Fixed an issue where the Big5 TextDecoder failed to recover and emit ASCII characters after encountering an invalid leading byte. (166672674)
  • Fixed MouseEvent.offsetX and MouseEvent.offsetY so they are now calculated relative to the padding edge of the target element. (168015965)
  • Fixed an issue where pointer events would fail to dispatch after a parent view’s custom gesture recognizer blocked touchesEnded: or touchesCancelled:. (169109808)
  • Fixed an issue where JavaScript-to-native object serialization could fail entirely when any individual key-value pair could not be serialized, causing broken workflows in some apps. (171547386)

JavaScript

Safari 26.4 adds support for iterator sequencing. It lets you chain multiple iterators together into a single sequence using Iterator.concat().

const odds = [1, 3, 5].values();
const evens = [2, 4, 6].values();

for (const n of Iterator.concat(odds, evens)) {
console.log(n); // 1, 3, 5, 2, 4, 6
}

This is a clean alternative to flattening arrays or writing manual chaining logic just to iterate over several collections in sequence.

Resolved issues

Several JavaScript bugs were also fixed:

  • Fixed Date constructor overflow handling so that invalid day values now return NaN. (155776209)
  • Fixed Intl.DateTimeFormat to throw a RangeError for legacy non-IANA timezones, aligning behavior with TC39 standards. (156857252)
  • Fixed Intl.Locale.prototype.getWeekInfo() to remove the minimalDays property for compliance with the specification. (165083619)
  • Fixed Intl.NumberFormat to properly apply minimumFractionDigits and maximumFractionDigits to ensure currency and compact notations behave correctly. (165875014)
  • Fixed %TypedArray%.prototype.includes to correctly check that the index is less than the array length, aligning its behavior with ECMA-262. (167183441)
  • Fixed async functions without any await to inline their bodies for performance and corrected async stack traces with a single function entry for exceptions thrown from or through async functions. (167254635)

HTML

Safari 26.4 adds support for using the min(), max(), and clamp() math functions inside the sizes attribute of <img> elements.

The sizes attribute tells the browser how wide an image will be displayed at different viewport sizes, helping it choose the right image from your srcset. Until now, you were limited to media queries and length values. Now you can use CSS math functions for more precise control:

<img 
  srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
  sizes="min(100vw, 1200px)"
  src="medium.jpg"
  alt="...">

This is particularly useful for responsive images that need to respect both viewport width and maximum content width. You can use clamp() to set minimum, ideal, and maximum sizes:

sizes="clamp(300px, 50vw, 800px)"

This gives you the same flexible sizing control in sizes that you already have in CSS, making responsive images easier to manage.

Resolved issues

  • Fixed an issue where nested about:blank frames were incorrectly treated as self-referencing, preventing them from loading. (148373033)
  • Fixed HTMLImageElement.currentSrc to return an empty string for <img src=""> instead of resolving to the document base URL. (167229274)
  • Fixed image uploading to not transcode images when accept="image/*" is specified. (166124206)
  • Fixed an issue where input fields did not display user input while typing. (163613957)
  • Fixed an issue where input[type="search"] fields with appearance: none incorrectly reserved space for the datalist dropdown button. (166754216)
  • Fixed an incorrect fallback for the menu style for empty lists, improving readability and correctness. (167662316)

SVG

Safari 26.4 adds support for the lighter operator in SVGFECompositeElement, aligning with the Compositing and Blending specification. This gives you an additional blending mode when compositing SVG filter effects, useful for creating additive lighting and glow effects in your SVG graphics. This example shows it in action. With support, there’s a soft glow around each circle. Without it, the glow is just not there.

Resolved issues

Safari 26.4 also includes a large number of SVG bug fixes:

  • Fixed an issue where stroke-dasharray incorrectly propagated to SVG markers when explicitly marked as ‘0’. (46607685)
  • Fixed an issue where foreignObject elements in SVG incorrectly allowed margin collapsing. (97208795)
  • Fixed SVG intrinsic sizing and preferredAspectRatio() to correctly transpose dimensions for vertical writing modes. (103262534)
  • Fixed animation of the stop-color attribute on <stop> elements. (109823555)
  • Fixed an issue where dynamically changing marker-start, marker-mid, or marker-end attributes on SVG elements did not trigger re-rendering. (130678384)
  • Fixed tiling gaps in CSS reference filters using <feDisplacementMap>. (135448018)
  • Fixed SVGLength parsing to correctly return the initial value when encountering parser errors or invalid values. (136102554)
  • Fixed an issue where SVGImage did not respect system dark mode changes. (140661763)
  • Fixed breaking SVG resource referencing when removing a resource which shares its id with other resources. (147015037)
  • Fixed behavior to avoid incorrect pruning of SVG mask subtrees based on visibility. (157729389)
  • Fixed an issue where SVG animateTransform animations on hidden elements were triggering full-page rendering updates each frame. (159647563)
  • Fixed SVGLength.prototype.valueAsString to throw a SyntaxError when assigned an empty string. (165429393)
  • Fixed SVGLength percentage resolution for elements inside non-instanced <symbol> elements. (165431008)
  • Fixed an issue where lengths with leading or trailing whitespace failed to be parsed. (165501190)
  • Fixed an issue where SVGLength.value did not update for font-relative units (e.g., ch, em) after changes to writing-mode. (166190252)
  • Fixed missing gradient fills when using paint-order stroke fill. (166997630)
  • Fixed embedded <svg> elements in <img> without an explicit viewBox to synthesize preserveAspectRatio='none' so the SVG stretches to fill the container. (167121931)
  • Fixed the <stop> element offset attribute in SVG to reject invalid values with trailing characters and correctly fall back to 0. (167356988)
  • Fixed the transform order for clipPath elements so that the bounding box is scaled correctly before applying the local transform. (167417135)
  • Fixed <clipPath> to clip to its <use> child element based on the visibility of the <use> target element. (167491519)
  • Fixed incorrect rendering when combining markerUnits=strokeWidth with vector-effect=non-scaling-stroke. (167493417)
  • Fixed displaying an SVG filter referencing an element with a huge stroke. (167516452)
  • Fixed hit testing for overlapping <text> and <tspan> elements in SVG. (167691166)
  • Fixed rendering of dimensionless SVG images. (168176556)

Safari Compact tabs

The Compact tabs option on macOS and iPadOS makes a return in Safari 26.4. If you preferred the more space-efficient tab design from earlier versions of Safari, you can now enable it again in Safari settings, under Tabs > Tab layout.

Safari settings panel, showing where to switch from Separate to Compact tabs.

Web Authentication

Safari 26.4 adds two important WebAuthn improvements for advanced authentication scenarios.

The PRF (Pseudo-Random Function) extension enables credential-bound cryptographic secrets during both credential creation and authentication flows with security keys. This maps to the CTAP hmac-secret extension and allows you to derive cryptographic keys from WebAuthn credentials — useful for applications that need to encrypt user data with keys tied to their authentication credentials, such as password managers or encrypted storage systems.

Safari 26.4 also adds support for CTAP PIN/UV Auth Protocol 2, which uses HKDF-SHA-256 for key derivation. This enables proper support for newer FIPS-compliant authenticators, which is important for enterprise and government applications with strict security requirements.

Resolved issues

  • Fixed an issue where the excludeCredentials list was not sent over CTAP when its size was 1. (164546088)

Web Inspector

In addition to the new Grid Lanes tooling described above, Safari 26.4 includes numerous Web Inspector enhancements to help you debug and build websites more effectively.

Layer Visualization

The Layers tab now shows actual composited layer snapshots instead of outline representations, giving you a more accurate picture of how your page is being rendered and composited.

The Web Inspector Layers tab, showing a webpage turned in #D space, with the separate layers of the site stacked translucently on top of each other

Developer Experience

A new context menu option lets you copy pretty-printed HTML from DOM nodes in the Elements tab — much easier to read than minified markup. In the settings tab, you can now toggle the visibility of User Agent stylesheet rules, reducing clutter when you’re focused on your own styles.

Web Inspector open on a website, with something in the DOM tree selected. The context menu is open, showing options, and Copy HTML (Formatted) is being chosen.

The Timelines Heap view displays the dominator object when viewing the shortest GC path, making it easier to track down memory leaks. And Web Inspector now fetches CSS property keyword completions based on actual feature support, so auto-complete suggestions reflect what’s actually available.

Worker Debugging

You can now capture console.screenshot images within a Worker, supporting ImageData, ImageBitmap, OffscreenCanvas, and various canvas rendering contexts. Canvas recordings can also be started and stopped from the console within a Worker using console.record() and console.recordEnd().

Additional Improvements

Auto-completion now includes sideways-lr and sideways-rl values for the writing-mode property, making it easier to work with vertical text layouts.

Resolved issues

Safari 26.4 also includes a number of Web Inspector bug fixes:

  • Fixed incorrect breakpoint and search result positions in the Web Inspector after pretty-printing inline scripts containing multi-line template literals. (29417859)
  • Fixed the Styles sidebar filter in Web Inspector to be case-insensitive. (36086981)
  • Fixed an issue where a large number of search results in the Search tab would freeze Web Inspector. (49234522)
  • Fixed an issue where the Console tab search bar in Web Inspector would disappear when the window was too narrow. (50922509)
  • Fixed an issue where CSS properties added to new rules were not applied and were marked as invalid. (103548968)
  • Fixed context menu items to rename Copy HTTP Request and Copy HTTP Response to Copy HTTP Request Headers and Copy HTTP Response Headers for clarity. (117708766)
  • Fixed incorrect grid track sizing display in the Web Inspector when using CSS custom properties. (141709306)
  • Fixed an issue in the Console where the count of identical consecutive messages could be wrong. (162612099)
  • Fixed an issue where breakpoints and search results in Web Inspector could point to the wrong location after a previously formatted source file was reopened in an unformatted state. (165059693)
  • Fixed an issue where the Sources navigation sidebar could be empty when reloading the page. (166141968)
  • Fixed timestamp formatting in the Web Inspector to remove unnecessary trailing .0 values for readability. (166500013)
  • Fixed item order labels in the Web Inspector grid and flex overlays to remove the # symbol, changing from Item #N to Item N. (166767949)
  • Fixed an issue where the text filter in the Sources tab did not apply to the Local Overrides and Console Snippets sections. (169804196)
  • Fixed a performance issue in the Web Inspector search panel by limiting initial results to 100 per resource and adding UI controls to load more or all results, reducing unnecessary UI work for large queries. (169804865)
  • Fixed an issue where the search bar settings icon disappeared when the search field was focused. (169997100)

Media Captions

Safari 26.4 adds improved caption controls on macOS, including a pop-up menu that lets users select and manage caption style profiles and configure subtitle display settings.

These enhancements give users more control over how captions appear — adjusting text size, color, background, and other styling options to match their preferences. If you’re providing video content with captions, users will have a better experience customizing how those captions are displayed.

Resolved issues

Quite a few media bugs were also fixed:

  • Fixed dispatching of enter and exit events on TextTrackCue and VTTCue objects with no associated track, aligning with other browsers. (160195643)
  • Fixed an issue where changing an HTMLMediaElement volume from 0 to 0 did not activate the audio session or update the sleep disabler. (161691743)
  • Fixed an issue where videos would freeze on the first frame when transitioning from encrypted to clear content by ensuring the decoded buffer maintains at least 100ms of frames even when the next frame is far in the future. (162234566)
  • Fixed an issue where the mute button disappeared in macOS inline videos with adjustable sizes. (162897286)
  • Fixed playback of application/ogg blob media. (163119790)
  • Fixed an issue where Video Viewer UI elements overlapped or exited unexpectedly. (164051864)
  • Fixed an issue where empty <track> elements prevented media from advancing its readyState and blocked play() calls. (164125914)
  • Fixed an issue where HTMLMediaElement did not correctly detect new audio or video tracks causing Safari to pause video when leaving a tab. (164514685)
  • Fixed a crash in AudioData.copyTo() when copying the last channel of 3-channel audio. (164730320)
  • Fixed an issue where the ended event for Media Source Extensions might never fire by ensuring buffered ranges update correctly and playback gaps are observed even when the video does not start at time zero. (165430052)
  • Fixed an issue where caption previews were not shown in the default media controls. (165931046)
  • Fixed the caption menu’s On option to correctly enable the highest-scoring text track and mark the appropriate language as checked in the subtitle menu. (166158394)
  • Fixed parseSequenceHeaderOBU to return an AV1CodecConfigurationRecord, fully decode the Sequence Header OBU, and capture the complete color profile. (166439682)
  • Fixed an issue where the macOS inline media controls timeline scrubber overlapped the right container buttons. (167634241)
  • Fixed an issue where WebCodecs VideoDecoder could output H264 frames in the wrong order. (168046597)
  • Fixed an issue where the mute button and volume slider overlapped in the video player controls when using a right-to-left language. (170174446)

WebRTC

Safari 26.4 adds two significant WebRTC enhancements for audio and networking.

On macOS, getUserMedia now supports capturing audio from multiple microphones simultaneously while intelligently managing echo cancellation. The system dynamically migrates existing audio captures to appropriate processing units, making it easier to build applications like podcasting tools, multi-participant recording, or music collaboration apps that need multiple audio inputs.

On iOS, Safari 26.4 adds network slicing support for WebRTC. Network slicing allows different types of traffic to be prioritized differently on cellular networks, which can improve quality and reliability for real-time communication applications in challenging network conditions.

Resolved issues

Several WebRTC bugs were also fixed:

  • Fixed RTCDataChannelInit to support [EnforceRange] on the maxPacketLifeTime and maxRetransmits fields to align with the WebRTC specification. (133630397)
  • Fixed an issue on macOS where calling getUserMedia with echo cancellation disabled could unintentionally affect existing audio tracks. (151143554)
  • Fixed MediaStreamTrackProcessor to respect track.enabled = false. (165199900)
  • Fixed an issue where RTCDataChannel close events did not fire when RTCPeerConnection was closed. (165617848)
  • Fixed RTCConfiguration.iceServers to be a non-optional sequence with an empty array as the default, improving spec compliance and ensuring RTCPeerConnection behaves correctly when iceServers is undefined. (167607478)

Additional resolved issues

There are even more issues that have been fixed in Safari 26.4.

Accessibility

  • Fixed an issue where Voice Control commands could cause Safari to hang. (168364189)
  • Fixed an issue where a button’s label would not update when a descendant’s aria-hidden attribute changed. (169012516)
  • Fixed elements with aria-controls or aria-expanded and the hidden attribute to no longer appear in VoiceOver’s Form Control menu. (169499630)
  • Fixed an issue where VoiceOver would announce with extra verbosity when moving onto the first item of a list on the webpage. (169982730)
  • Fixed an issue where controls with aria-labelledby pointing to visually-hidden elements could not obtain proper bounding box geometry through accessibility APIs. (170639492)

Browser

  • Fixed Safari gesture support to prevent pages that should not be able to scroll, such as with explicit overflow: hidden, from unexpectedly scrolling. (163660111)

Canvas

  • Fixed ImageBitmap created from SVG image sources to correctly honor the flipY orientation. (83959718)

Clipboard

  • Fixed an issue where using the “Copy Image” context menu in Safari would also copy the image URL, causing some sites to paste the URL instead of the image. (76598990)

Editing

  • Fixed incorrect text selection when dragging across pseudo elements. (142905243)
  • Fixed an issue on iOS 26 where the edit menu would not appear when tapping inside an already-focused text field that had a looping content animation. (164290305)
  • Fixed an issue where focusing a hidden editable element would incorrectly display the text cursor and selection at full opacity. (165489471)
  • Fixed a regression where dragging to select text would stop scrolling when the cursor left the window. (169983104)

Events

  • Fixed an issue where mouseleave and mouseout events were not dispatched when a window moved out from under a stationary cursor. (161493924)

Privacy

  • Fixed an issue where cross-browser Private Click Measurement recorded attribution entries even when “Allow privacy-preserving measurement of ad effectiveness” was disabled. (170669444)

Rendering

  • Fixed over-aggressive clipping of child layers in multicolumn layouts to prevent visual overflow issues with position: relative elements and transform:scale() text. (126413036)
  • Fixed unreadable Scroll-to-Text-Fragment highlights on dark pages. (126539910)
  • Fixed an issue where auto-positioned absolutely positioned descendants were not always marked for layout when their parent’s border box moved. (131806062)
  • Fixed an issue where positioned, transformed, or opacity-altered <img> elements with HDR JPEG gainmaps would incorrectly render in SDR. (156858374)
  • Fixed an issue on iPadOS where closing the sidebar or resizing the window could cause the page to remain zoomed in by recalculating the target scale to automatically match the new minimum scale. (157676989)
  • Fixed an issue where fixed positioned elements were not rendered correctly in right-to-left pages using the vertical-rl writing mode. (161712734)
  • Fixed a performance issue in layouts with long pre blocks and word-break: break-all by including whitespace in overflow width calculations. (162695099)
  • Fixed an issue where overconstrained sticky elements were not properly adjusting their insets when the sticky box rectangle was larger than the viewport. (163654023)
  • Fixed an issue where applying word-spacing to ::first-line could cause other lines to disappear. (163779992)
  • Fixed inconsistent text layout when using list-style-type by ensuring outside list markers do not affect intrinsic width calculations. (164650313)
  • Fixed an issue where color fonts could affect the color of other DOM elements. (166631312)
  • Fixed <col> elements with span > 1 not applying their width to all spanned columns during table layout, aligning behavior with other browsers. (167225435)
  • Fixed table layout min-width distribution for spanning cells with mixed percent, fixed, and auto columns. (167684748)
  • Fixed: Improved drop-shadow and blur effects rendering performance. (169472992)

Spatial Web

  • Fixed auto-dimming for playback sessions in visionOS. (163824973)

Storage

  • Fixed an issue where IndexedDB databases might have mismatched metadata version and database name encoding format. (163219457)

WKWebView

  • Fixed an issue where WKWebView apps with a toolbar would fail to display a top scroll edge effect when relying on automatic content inset adjustments. (161370795)

Web Extensions

  • Fixed an issue where window.open() calls from web extensions would incorrectly open “about:blank” instead of the intended URL by ensuring each extension URL loads in a fresh tab configuration. (143901129)

WebAssembly

  • Fixed Error.isError(WebAssembly.Exception) to correctly return false based on current WebAssembly spec semantics. (167110254)

WebGPU

  • Fixed incorrect handling of some PNG pixel formats in WebGPU. (158797747)

Feedback

We love hearing from you. To share your thoughts, find us online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn.

If you run into any issues, we welcome your bug report. Filing issues really does make a difference.

You can also find this information in the Safari release notes.

March 24, 2026 05:00 PM

March 18, 2026

Igalia WebKit Team: WebKit Igalia Periodical #60

Igalia WebKit

Update on what happened in WebKit in the week from March 10 to March 18.

The big ticket item in this week's update are the 2.52.0 releases, which include the work from the last six-month development period, and come with a security advisory. Meanwhile, WPE-Android also gets a release, and a number of featured blog posts.

WPE WebKit 📟

Last week we added support to WPE MiniBrowser to load settings from a key file. This extended the existing --config-file=FILE feature, which previously only loaded WPEPlatform settings under the [wpe-platform] group. Now the feature uses webkit_settings_apply_from_key_file() to load properties such as user-agent or enable-developer-extras from the [websettings] group as well.

Releases 📦️

WebKitGTK 2.52.0 and WPE WebKit 2.52.0 are now available. These include the results of the effort made by the team during the last six months, including rendering improvements and performance optimizations, better security for WebRTC, a more complete WebXR implementation, and a second preview of the WPEPlatform API for the WPE port—among many other changes.

More information about the changes and improvements brought by these major releases can be found at the blog post about WebKitGTK 2.52, and the corresponding one for WPE WebKit 2.52.

Accompanying these releases there is security advisory WSA-2026-0001 (GTK, WPE), with information about solved security issues. As usual, we encourage everybody to use the most recent versions where such issues are known to be fixed.

Bug reports are always welcome at the WebKit Bugzilla.

WPE Android 0.3.3 has been released, and prebuilt packages are available at the Maven Central repository. This is a maintenance release which updates the included WPE WebKit version to 2.50.6 and libsoup to 3.6.6, both of which include security fixes.

Community & Events 🤝

Kate Lee wrote a very interesting blog post showing how to create a small application using the WPEPlatform API to demonstrate one of its newly available features: the Context Menu API. It is rendered entirely as an HTML overlay, enabling richer and more portable context menu implementations.

WebXR support for WebKitGTK and WPE has been reworked and aligned with the modern multi-process architecture, using OpenXR to enable XR device integration on Linux and Android. Sergio Villar wrote a blog post that explains all the work done in the last months around it.

That’s all for this week!

By Igalia WebKit Team at March 18, 2026 07:46 PM

WPE WebKit Blog: WPE WebKit 2.52 highlights

Igalia WebKit

The WebKit team at Igalia is happy to announce a new release series of WPE WebKit. This is a summary of the most noteworthy changes from the latest release cycle.

Graphics improvements

WPE’s graphics support has seen numerous improvements with a positive impact in rendering performance, resource usage, and better rendering. Let’s have a look at some of the most significant changes:

  • Compute the layers tile size, using a different strategy depending on whether GPU rendering is enabled. This optimizes resource usage depending on both hardware and software rendering mode.
  • WPE now uses run-loop observers to properly schedule layer flushing and composition, which results in snappier and better performing rendering and animation.
  • 2D-canvas acceleration has now improved performance, as operations are recorded for batched replay.
  • Text rendering has better performance too.
  • In non-composite mode, it’s now also possible to use damage propagation.
  • Asynchronous scrolling has also seen performance improvements.

On top of this, as usual, many rendering issues have been fixed, making this release of WPE one of the best in terms of graphics support.

Multimedia improvements

WebRTC

When using GstWebRTC, WebRTC network access has been moved to the network process. This also requires librice, and building with the CMake USE_LIBRICE option. When this is enabled, it is still possible to choose the older libnice-based implementation at runtime by setting WEBKIT_GST_DISABLE_WEBRTC_NETWORK_SANDBOX=1 in the environment.

Having WebRTC network access in the network process is a security improvement, as it reduces the surface of attack in other more sensitive processes.

Other multimedia improvements

  • Videos with BT2100-PQ colorspace are now tone-mapped to SDR, ensuring colours do not appear washed out.
  • Support for the Audio Output Devices API, which allows Web content to enumerate audio devices and decide which one to use for output. This feature is disabled by default, and may be previewed using the ExposeSpeakers, ExposeSpeakersWithoutMicrophone, and PerElementSpeakerSelection feature flags.
  • Many code improvements to the GStreamer backend that will result in a more stable multimedia experience.

WebXR

WebXR support through OpenXR has seen substantial development this cycle:

API Changes

The future of the WPE API

The traditional libwpe-based API remains in WPE for this release cycle, but we are planning to sunset it starting with the following one (2.54). This applies to Cog, which is no longer in active development and won’t have any more stable releases beyond the 0.18.x series. While both libwpe and Cog will remain available, we encourage developers to transition to the new WPEPlatform API, which will be considered stable by then.

This means that it is the perfect time to test the WPEPlatform API and provide feedback, as it’s still possible to make changes to it to better suit users’ needs.

WPEPlatform API changes

New platform APIs include:

  • wpe_display_create_toplevel(). This way it’s possible to create a toplevel using the common API which allows the inspector to work when the application is handling toplevels.
  • A new WPEDisplay::disconnected signal has been added, which allows platform implementations to notify when the native display gets “disconnected” and thus no longer usable. Applications can handle it to attempt recovery, or to know when they may free resources.
  • A new WPEView::buffers-changed signal, alongside the associated WPEViewClass.buffers_changed virtual function, have been added. These may be used to know in advance which graphics buffers will be used for rendering the content for a given web view. This feature is mainly useful for platform implementations which may need to perform additional setup in advance, before updated web view contents are provided in the buffers configured by WebKit.
  • Two new functions, wpe_clipboard_content_get_text() and wpe_clipboard_content_get_bytes(), allow applications to obtain the contents held in the clipboard.

The public API has received the following changes, which might require changes to existing platform implementations:

  • Multiple callbacks are now supported for WPEScreenSyncObserver, the API has changed from wpe_screen_sync_observer_set_callback() to a pair of wpe_screen_sync_observer_add_callback()/_remove_callback() functions. The functions to start/stop the observer are no longer available, and instead the observer will be activated automatically when there are one or more callbacks attached to it.

The WPEPlatform API can now be used on Android.

Legacy API

The legacy libwpe-based API can be disabled at build time, by toggling the ENABLE_WPE_LEGACY_API CMake option. This allows removal of uneeded code when an application is exclusively using the new WPEPlatform API.

New WebKit API

Web Standards 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:

Other notes

The Flatpak-based development SDK has been removed. Developers are encouraged to use the WebKit Container SDK instead.

March 18, 2026 12:00 AM

March 17, 2026

Sergio Villar: Implementing WebXR in WebKit for WPE

Igalia WebKit

Since 2022, my main focus has been working on the Wolvic browser, still the only open source WebXR-capable browser for Android/AOSP devices (Meta, Pico, Huawei, Lenovo, Lynx, HTC…) out there. That’s an effort that continues to this day (although to a much lesser extent nowadays). In early 2025, as a consequence of all that work in XR on the web, an opportunity emerged to implement WebXR support in WebKit for the WPE port, and we decided to take it.

March 17, 2026 08:46 AM

March 16, 2026

Kate Lee: Building a Custom HTML Context Menu with the New WPEPlatform API

Igalia WebKit

WPE WebKit is a WebKit port optimized for embedded devices — think set-top boxes, digital signage, kiosk displays, and in-vehicle infotainment systems. It is developed by Igalia and powers web experiences on millions of devices worldwide, from set-top boxes to smart TVs and beyond.

WPE WebKit has recently introduced a brand-new platform API called WPEPlatform, which replaces the legacy libwpe + wpebackend-fdo stack. In this post, I will walk you through building a minimal WPE browser launcher using only the new WPEPlatform API, and demonstrate one of its newly available features: the Context Menu API — rendered entirely as an HTML overlay.

Why a New API? #

The legacy stack (libwpe + wpebackend-fdo + Cog platform plugins) had several pain points: nested Wayland compositor complexity, dependency on Mesa’s now-deprecated EGL_WL_bind_wayland_display extension, rigid C function-pointer tables, and platform code scattered across three libraries.

The new WPEPlatform API replaces all of this with a single, clean GObject-based layer — providing automatic backend creation, DMA-BUF direct buffer sharing, unified window management (fullscreen, maximize, resize, title), and easy language bindings via GObject Introspection.

Timeline: The stable release of WPEPlatform is planned for September 2026. At that point, the legacy API will be officially deprecated. We strongly recommend new projects to adopt the WPEPlatform API from the start.

WPEPlatform Launcher: A Minimal Browser in ~250 Lines #

To demonstrate the new API, I built WPEPlatformLauncher — a minimal but functional WPE WebKit browser that uses only the WPEPlatform API. No legacy libwpe, no wpebackend-fdo, no Cog — just the new API.

The full source code is available at: kate-k-lee/WebKit@aed6402

How Simple Is It? #

Here is the core of the launcher — creating a WebView with the new API:

/* WPEPlatform backend is created automatically — no manual setup needed */
auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"web-context", webContext,
"network-session", networkSession,
"settings", settings,
"user-content-manager", userContentManager,
nullptr));

/* Get the WPEPlatform view — this is where the new API shines */
auto* wpeView = webkit_web_view_get_wpe_view(webView);
auto* toplevel = wpe_view_get_toplevel(wpeView);

/* Window management: fullscreen, resize, title — all built-in */
wpe_toplevel_fullscreen(toplevel);
wpe_toplevel_resize(toplevel, 1920, 1080);
wpe_toplevel_set_title(toplevel, "WPEPlatform Launcher");

/* Input events: just connect a GObject signal */
g_signal_connect(wpeView, "event", G_CALLBACK(onViewEvent), webView);

Compare this with the legacy API, which required:

  1. Manually creating a WPEToolingBackends::ViewBackend
  2. Wrapping it in a WebKitWebViewBackend with a destroy callback
  3. Creating a C++ InputClient class and registering it
  4. Having no window management (no maximize, minimize, title, etc.)

The new API handles backend creation, display detection, and input forwarding automatically.

Keyboard Shortcuts #

Handling keyboard events is straightforward with the WPEPlatform event system:

static gboolean onViewEvent(WPEView* view, WPEEvent* event, WebKitWebView* webView)
{
if (wpe_event_get_event_type(event) != WPE_EVENT_KEYBOARD_KEY_DOWN)
return FALSE;

auto modifiers = wpe_event_get_modifiers(event);
auto keyval = wpe_event_keyboard_get_keyval(event);

/* Ctrl+Q: Quit */
if ((modifiers & WPE_MODIFIER_KEYBOARD_CONTROL) && keyval == WPE_KEY_q) {
g_application_quit(g_application_get_default());
return TRUE;
}

/* F11: Toggle fullscreen via WPEToplevel */
if (keyval == WPE_KEY_F11) {
auto* toplevel = wpe_view_get_toplevel(view);
if (wpe_toplevel_get_state(toplevel) & WPE_TOPLEVEL_STATE_FULLSCREEN)
wpe_toplevel_unfullscreen(toplevel);
else
wpe_toplevel_fullscreen(toplevel);
return TRUE;
}

return FALSE;
}

HTML-Based Context Menu: Solving the “No Native UI” Challenge #

WPE WebKit is designed for embedded environments where there is no native UI toolkit — no GTK, no Qt. This means features like context menus (right-click menus) that desktop browsers take for granted need to be implemented by the application.

The approach: intercept WebKit’s context-menu signal, read the menu items, and render them as an HTML/CSS overlay injected into the page DOM.

The Architecture #

User right-clicks
  → WebKit emits "context-menu" signal
  → onContextMenu() handler:
      1. Reads menu items via webkit_context_menu_get_items()
      2. Gets position via webkit_context_menu_get_position()
      3. Builds JavaScript that creates DOM elements
      4. Injects via webkit_web_view_evaluate_javascript()
      5. Returns TRUE (suppresses default menu)

User clicks a menu item
  → JS: window.webkit.messageHandlers.contextMenuAction.postMessage(actionId)
  → C: onContextMenuAction() receives the action ID
      → Executes: webkit_web_view_go_back(), execute_editing_command("Copy"), etc.

User clicks outside the menu
  → JS: overlay click handler removes the DOM elements

Reading Context Menu Items #

The Context Menu API provides everything we need:

static gboolean onContextMenu(WebKitWebView* webView,
WebKitContextMenu* contextMenu, gpointer /* event */,
WebKitHitTestResult* hitTestResult, gpointer)
{
/* Save hit test result for link-related actions */
savedHitTestResult = WEBKIT_HIT_TEST_RESULT(g_object_ref(hitTestResult));

/* Iterate through menu items */
GList* items = webkit_context_menu_get_items(contextMenu);
for (GList* l = items; l; l = l->next) {
auto* item = WEBKIT_CONTEXT_MENU_ITEM(l->data);

if (webkit_context_menu_item_is_separator(item)) {
/* Render as a horizontal line */
continue;
}

const char* title = webkit_context_menu_item_get_title(item);
auto action = webkit_context_menu_item_get_stock_action(item);
/* Build HTML element with title and action ID */
}

/* Get position for menu placement */
gint posX = 0, posY = 0;
webkit_context_menu_get_position(contextMenu, &posX, &posY);

return TRUE; /* Suppress default menu */
}

The HTML Menu: Dark Theme for Embedded #

The context menu is rendered with a dark theme CSS, designed for embedded/kiosk displays:

#__wpe_ctx_menu {
position: fixed;
min-width: 180px;
background: #2b2b2b;
border: 1px solid #505050;
border-radius: 6px;
padding: 4px 0;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
font-family: system-ui, sans-serif;
font-size: 13px;
color: #e0e0e0;
}

.__wpe_ctx_item:hover {
background: #0060df;
color: #ffffff;
}

Handling Actions via Script Message Handler #

Communication between the HTML menu and the C application uses WebKit’s script message handler mechanism:

/* Register message handler */
auto* ucm = webkit_user_content_manager_new();
webkit_user_content_manager_register_script_message_handler(
ucm, "contextMenuAction", nullptr);
g_signal_connect(ucm, "script-message-received::contextMenuAction",
G_CALLBACK(onContextMenuAction), nullptr);
// In the generated HTML menu item:
item.addEventListener('click', function() {
window.webkit.messageHandlers.contextMenuAction.postMessage(actionId);
});
/* Handle the action in C */
static void onContextMenuAction(WebKitUserContentManager*, JSCValue* value, gpointer)
{
int actionId = jsc_value_to_int32(value);

switch (actionId) {
case WEBKIT_CONTEXT_MENU_ACTION_RELOAD:
webkit_web_view_reload(webView);
break;
case WEBKIT_CONTEXT_MENU_ACTION_COPY:
webkit_web_view_execute_editing_command(webView, "Copy");
break;
case WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK:
webkit_web_view_load_uri(webView,
webkit_hit_test_result_get_link_uri(savedHitTestResult));
break;
/* ... more actions ... */
}
}

Demo #

Here is the WPEPlatformLauncher in action, showing the HTML context menu with various actions:

WPEPlatformLauncher context menu demo

Right-clicking shows the HTML context menu. Clicking “Reload” triggers an actual page reload.

Context menu on a link

Right-clicking a link shows link-specific actions like “Open Link” and “Copy Link Address”.

Building and Running #

I built and ran the WPEPlatformLauncher inside a container using the WebKit Container SDK, which provides a pre-configured development environment with all the dependencies needed to build WPE WebKit.

The WPEPlatformLauncher integrates into the WebKit build system:

# Build WPE WebKit with the launcher
Tools/Scripts/build-webkit --wpe --release

# Run
./WebKitBuild/WPE/Release/bin/WPEPlatformLauncher https://wpewebkit.org

# Run in fullscreen (kiosk mode)
./WebKitBuild/WPE/Release/bin/WPEPlatformLauncher --fullscreen https://your-app.com

The full source is a single main.cpp file (~600 lines including the context menu), integrated into the WebKit tree alongside MiniBrowser:

WebKit/Tools/
├── MiniBrowser/wpe/          ← Existing (supports both old + new API)
├── WPEPlatformLauncher/      ← New (WPEPlatform API only)
│   ├── main.cpp
│   └── CMakeLists.txt
└── PlatformWPE.cmake         ← Modified to add WPEPlatformLauncher

Summary #

The new WPEPlatform API makes building WPE WebKit applications significantly simpler:

  • No manual backend setup — the platform is detected and configured automatically
  • GObject-based — signals, properties, and ref counting instead of C function pointers
  • DMA-BUF direct sharing — no dependency on Mesa’s deprecated EGL extensions
  • Unified window management — fullscreen, maximize, minimize, resize, and title
  • Language binding friendly — works with Python, JavaScript, and more via GObject Introspection

For embedded browser developers building kiosk UIs, set-top box interfaces, or digital signage with WPE WebKit — now is the time to adopt the new API. The stable release is coming in September 2026, and the legacy stack (libwpe, wpebackend-fdo, Cog) will be deprecated at that point.

Resources #

March 16, 2026 12:00 AM

March 12, 2026

Release Notes for Safari Technology Preview 239

Surfin’ Safari

Safari Technology Preview Release 239 is now available for download for macOS Tahoe and macOS Sequoia. If you already have Safari Technology Preview installed, you can update it in System Settings under General → Software Update.

This release includes WebKit changes between: 307619@main…308417@main.

Accessibility

Resolved Issues

  • Fixed an issue where SVG <use> elements referencing <symbol> elements inside an <img> were incorrectly included as unnamed images in VoiceOver’s Images web rotor. (308126@main) (98999595)
  • Fixed an issue where VoiceOver was unable to access aria-owned rows and their cells in grids and tables. (308087@main) (168770938)
  • Fixed an issue where VoiceOver could not find focusable splitter elements when navigating to the next or previous form control. (308089@main) (170187464)
  • Fixed an issue where controls with aria-labelledby did not use the native label’s geometry when both the control and its ARIA label had no visible bounding box. (307727@main) (170518900)

CSS

New Features

  • Added support for the :open pseudo-class for <input> elements. (308148@main) (170804152)

Resolved Issues

  • Fixed an issue where tables with collapsed borders incorrectly calculated the first row width, causing excess border width to spill into the table’s margin area. (308311@main) (149675907)
  • Fixed an issue where inset box-shadow was incorrectly positioned on table cells with collapsed borders. (307661@main) (169254286)
  • Fixed an issue where display: grid subgrids inside grid-lanes containers incorrectly contributed their item intrinsic sizes to the parent’s track sizing algorithm. (308253@main) (170168798)
  • Fixed the baseline calculation for inline-block elements so that when overflow is not visible, the baseline is correctly set to the bottom margin edge. (307718@main) (170575015)
  • Fixed an issue where replaced elements did not correctly apply min-height and min-width constraints in certain configurations. (308212@main) (170765025)
  • Fixed an issue where children with percentage heights inside absolutely positioned elements using intrinsic height values (fit-content, min-content, max-content) incorrectly resolved against the containing block’s height instead of being treated as auto. (308226@main) (171179193)

Editing

Resolved Issues

  • Fixed an issue where execCommand('FormatBlock') did not preserve inline styles of replaced block elements, causing text formatting to be lost when pasting content. (308365@main) (157657531)
  • Fixed an issue where text-indent flickered or was ignored on contenteditable elements while typing. (307646@main) (170280101)

Forms

Resolved Issues

  • Fixed an issue where a readonly date <input> could still be edited via keyboard using the date picker. (307934@main) (169488939)

MathML

Resolved Issues

  • Fixed an issue where dynamic changes to <mo> element attributes did not trigger a relayout. (308014@main) (170907029)
  • Fixed positioning of the <mprescripts> element within <mmultiscripts> layout. (308013@main) (170909975)
  • Fixed an issue where the MathML fraction bar was not painted when its thickness was equal to its width. (308025@main) (170934351)
  • Fixed an issue where <none> and <mprescripts> elements were not laid out as <mrow> elements in MathML. (308050@main) (170940035)

Media

Resolved Issues

  • Fixed an issue where the VideoFrame constructor did not handle the video color range correctly for NV12 (I420 BT601) video frames. (307649@main) (170299037)

PDF

Resolved Issues

  • Fixed a regression where form fields in PDF documents were not readable when editing in dark mode. (308330@main) (171198060)

Rendering

Resolved Issues

  • Fixed an issue where images were incorrectly stretched in certain page layouts. (307751@main) (170270187)

Scrolling

Resolved Issues

  • Fixed an issue where interrupting scroll momentum caused the scrolling container to stop rendering and hit-testing to be misplaced. (308215@main) (116205365)
  • Fixed an issue where pages could become blank and jump to the top after dynamically loading new content when scroll anchoring was enabled. (308352@main) (170889205)
  • Fixed an issue where scroll anchoring could cause pages to scroll to negative offsets. (308320@main) (171221075)

Web API

Resolved Issues

  • Fixed an issue where the change event was not fired on <input> and <textarea> elements when they lost focus while another application was in the foreground. (308203@main) (98526540)
  • Fixed an issue where the dragend event had incorrect coordinates when dragging within a nested <iframe>. (308216@main) (170750013)

Web Inspector

New Features

  • Added color contrast information within the Color Picker in Web Inspector. (308318@main) (113887185)

Resolved Issues

  • Fixed an issue in the Network panel where the Request / Response menu did not remember the user’s previously selected value. (308142@main) (108231795)
  • Fixed an issue in the Elements tab where “Copy HTML” only copied HTML for a single node when multiple nodes are selected. (307826@main) (169196441)

WebAssembly

Resolved Issues

  • Fixed WebAssembly streaming compilation APIs to accept the optional compileOptions parameter required to enable JS String builtins. (308419@main) (170989896)

WebRTC

Resolved Issues

  • Fixed an issue where RTCPeerConnection.addIceCandidate() did not reject when the connection was already closed. (307702@main) (170470988)

March 12, 2026 09:21 PM

March 09, 2026

Igalia WebKit Team: WebKit Igalia Periodical #59

Igalia WebKit

Update on what happened in WebKit in the week from March 2 to March 9.

As part of this week's handful of news, WebKitGTK and WPE WebKit now have support for Gamepad's "VibationActuator" property, the video decoding limit is now configurable at runtime in addition to build time, and an interesting fix that makes WebKit render fonts like other browsers by making it blend text incorrectly (!).

Cross-Port 🐱

Using libmanette's rumble support, enabled Gamepad VibrationActuator for WebKitGTK and WPE WebKit.

With these changes, playEffect() can be used to play dual-rumble vibration effects.

Multimedia 🎥

GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.

VIDEO_DECODING_LIMIT is now configurable at runtime, in addition to build time. That will allow vendors that share a single binary build on different platforms to fine-tune their needs without a rebuild.

Graphics 🖼️

Landed a change that tweaks the text rendering done with Skia. With this change, the text looks more natural now - just like in other browsers. However, this is done by blending text incorrectly as a compromise.

Releases 📦️

One more set of release candidates for the upcoming stable branch, WebKitGTK 2.51.93 and WPE WebKit 2.51.93, have been published. For those interested in previewing the upcoming 2.52.x series this release is expected to be quite stable. Reporting issues in Bugzilla are, as usual, more than welcome.

That’s all for this week!

By Igalia WebKit Team at March 09, 2026 08:02 PM

March 02, 2026

Igalia WebKit Team: WebKit Igalia Periodical #58

Igalia WebKit

Update on what happened in WebKit in the week from February 23 to March 2.

This installment of the periodical brings news about support for Qualcomm qtivdec2 and qtivenc2 on GStreamer, GPU texture atlas creation and replay substitution, enhancement of the scroll gesture in WPE, and two new releases: WebKitGTK 2.51.92 and WPE WebKit 2.51.92.

Cross-Port 🐱

Multimedia 🎥

GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.

Work on adding support for the Qualcomm GStreamer qtivdec2 and qtivenc2 elements is on-going

Graphics 🖼️

Implemented GPU texture atlas creation and replay substitution in the Skia painting engine on GTK/WPE. After recording, raster images are packed into GPU atlases via BitmapTexture, with two upload paths: an optimized DMA-buf path that memory-maps GPU buffers and dispatches uploading to a dedicated worker thread, and a synchronous GL fallback using BitmapTexture::updateContents(). Atlas uploads are synchronized across workers using a countdown-latch fence. During replay, SkiaReplayCanvas intercepts raster image draws and substitutes them with atlas texture draws, mapping source coordinates into atlas space.

WPE WebKit 📟

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

The recent WPE WebKit 2.51.92 release is the first one to have its WPEPlatform documentation online, but it was not included in the tarball. This issue has been corrected and tarballs for future releases will also include this documentation.

Scrolling using touch input with WPEPlatform would result in scrolling faster when more than one touch point was in effect. The gesture detector has been fixed to make scrolling have always a consistent speed.

Releases 📦️

The third —and likely the last— release candidates for the upcoming stable branch, WebKitGTK 2.51.92 and WPE WebKit 2.51.92, have been published. For those interested in previewing the upcoming 2.52.x series this release is expected to be quite stable; but there might be still some rough edges. Reporting issues in Bugzilla are, as usual, more than welcome.

That’s all for this week!

By Igalia WebKit Team at March 02, 2026 08:11 PM

February 26, 2026

Release Notes for Safari Technology Preview 238

Surfin’ Safari

Safari Technology Preview Release 238 is now available for download for macOS Tahoe and macOS Sequoia. If you already have Safari Technology Preview installed, you can update it in System Settings under General → Software Update.

This release includes WebKit changes between: 306596@main…307618@main.

Animations

New Features

  • Enabled threaded time-based animation resolution, synchronizing accelerated time-based animations with scroll-driven animations while preserving enhanced performance by running animations off the main thread. This also allows CSS Motion Path animations to be accelerated. (307252@main) (170131323)

CSS

New Features

  • Added support for the CSS :open pseudo-class, which matches form elements and other elements in an open state. (307295@main) (170108337)

Resolved Issues

  • Fixed scrollable overflow computation for block containers to account for padding-inline-end, aligning with the CSS Overflow specification. (307212@main) (144312078)
  • Fixed rendering of linear gradients when all color stops are at the same position. (306823@main) (169063497)

Editing

New Features

  • Added menu items to convert editable text between Simplified and Traditional Chinese characters in the “Transformations” submenu. (306761@main) (156354464)

Forms

New Features

  • Enabled the customizable <select> element, allowing custom styling and content in <select> dropdowns using appearance: base-select. (307548@main) (170328089)

Networking

Resolved Issues

  • Fixed an issue where Safari’s address bar could display an internationalized domain name (IDN) homograph as a visually identical legitimate Latin domain, enabling potential phishing attacks. (307005@main) (166796168)
  • Fixed incorrect URL query percent-encoding when using non-UTF-8 character encodings such as iso-8859-2, windows-1250, and gbk. (306768@main) (169566553)

Rendering

Resolved Issues

  • Fixed a performance issue causing a hang when rendering tables with a large number of rowspan="0" cells. (306891@main) (146056348)

SVG

Resolved Issues

  • Fixed: Updated the default values of fx and fy attributes on SVGRadialGradientElement to 50% to align with the SVG2 specification. (306811@main) (169645572)
  • Fixed SVGAnimatedRect.baseVal to ignore invalid values set on the viewBox attribute, such as negative width or height, aligning with Firefox and Chrome. (307463@main) (170214971)
  • Fixed SVG length attributes to reset to their default values when removed, rather than retaining previously set values. (307585@main) (170360351)

Scrolling

New Features

  • Enabled scroll anchoring, which prevents visible jumps in scroll position when content is inserted or removed above the viewport. (307475@main) (170279026)

Web API

New Features

  • Added support for ReadableStream.from() to create a ReadableStream from an async iterable or iterable. (306786@main) (169605740)
  • Added support for transferring ReadableStream objects via postMessage(). (307068@main) (169950592)

Resolved Issues

  • Fixed an issue where Speculation Rules did not trigger prefetches for anchor elements that were not themselves visible but had visible descendants. (306730@main) (169561588)
  • Fixed NavigateEvent.canIntercept to correctly return false when navigating to a URL with a different port, aligning with the Navigation API specification. (307316@main) (169845691)

Web Inspector

New Features

  • Improved discoverability of color formats and gamuts in the color picker by adding explicit format and gamut toggle controls. (306691@main) (168216591)

Resolved Issues

  • Fixed an issue where the text filter in the Sources tab did not apply to Local Overrides and Console Snippets sections. (306647@main) (169443917)
  • Fixed an issue where tree outlines in Web Inspector would intermittently show blank content while scrolling when a filter was active. (306680@main) (169502061)
  • Fixed an issue in the Timelines tab where rows containing object previews were sometimes not visible in the heap snapshot data grid. (307358@main) (170164522)
  • Fixed context menu items in the Elements tab to only display relevant options when multiple DOM nodes are selected. (307485@main) (170307979)

WebAssembly

New Features

  • Added support for JavaScript Promise Integration (JSPI) for WebAssembly, enabling WebAssembly code to suspend and resume execution while waiting for JavaScript promises. (307404@main) (170260385)

WebRTC

Resolved Issues

  • Fixed RTCRtpSynchronizationSource.timestamp to use the correct time base, aligning with the WebRTC specification. (307063@main) (169679084)
  • Fixed RTCIceCandidate.toJSON() to include the usernameFragment field in the serialized output. (306845@main) (169679947)
  • Fixed RTCRtpTransceiver.setCodecPreferences() to accept codec entries regardless of the mimeType case, aligning with Firefox and the specification. (307062@main) (169789074)
  • Fixed RTCRtpSender to allow a maxFramerate encoding parameter value of 0, aligning with the specification and other browsers. (307034@main) (169863687)

February 26, 2026 10:36 PM

February 23, 2026

Igalia WebKit Team: WebKit Igalia Periodical #57

Igalia WebKit

Update on what happened in WebKit in the week from February 9 to February 23.

In this week we have a nice fix for video streams timestamps, a fix for a PDF rendering regression, support for rendering video buffers provided by Qualcomm video decoders, and a fix for a font selection issue. Also notable we had a new WPE Android release, and the libsoup 3.6.6 release.

Cross-Port 🐱

Added a new webkit_feature_list_find() convenience function to the public API, which searches for a WebKitFeature given its identifier.

Multimedia 🎥

GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.

Graphics 🖼️

Fixed a PDF rendering regression caused by the canvas 2D operation recording feature, where switching between the recording canvas and the GPU surface canvas failed to preserve the full save/restore nesting, clip stack, and transparency layer state. Replaced the fragile state-copying approach with a state replay mechanism in GraphicsContextSkia that tracks the full sequence of save restore, clip, and transparency layer operations, then reconstructs the exact nesting on the target canvas when flushing a recording.

Added support for rendering video buffers provided by Qualcomm hardware-accelerated decoders, with aid from the EXT_YUV_target OpenGL extension.

Fixed the font selection issue that the system fallback font cache mixed up different font styles.

Releases 📦️

WPE Android 0.3.2 has been released, and prebuilt packages are available at the Maven Central repository. This is a stable maintenance release which updates WPE WebKit to 2.50.5, which is the most recent stable release.

libsoup 3.6.6 has been released with numerous bug and security fixes.

That’s all for this week!

By Igalia WebKit Team at February 23, 2026 07:52 PM

February 12, 2026

Announcing Interop 2026

Surfin’ Safari

Exciting news for web developers, designers, and browser enthusiasts alike — Interop 2026 is here, continuing the mission of improving cross-browser interoperability. For the fifth year in a row, we are pleased to collaborate with Google, Igalia, Microsoft, and Mozilla to make web technology more consistent and reliable across our browsers.

Introducing Interop 2026

Making your website work in every browser can be a challenge, especially if browser engines have implemented the same web technology in slightly different ways. The Interop Project tackles this challenge by bringing the major browser engines together to improve the same set of features during the same year. Each feature is judged on whether or not it fully aligns with its official web standard — the formal technical specifications that define how each web technology should work. This helps accelerate progress toward a more reliable, consistent platform to build on.

Safari has already implemented many of the features included in Interop 2026. In fact, we were the first browser to ship contrast-color(), Media pseudo-classes, shape(), and Scoped Custom Element Registries. Plus, we have support for Anchor Positioning, Style Queries, Custom Highlights, Scroll Snap, View Transitions and much more. We’re excited that these technologies are being included as focus areas in Interop 2026, ensuring they get implemented across all browsers and any remaining interoperability gaps are closed.

We will also be focused on adding support for the following features: advanced attr(), the getAllRecords() method for IndexedDB, WebTransport, and the JavaScript Promise Integration API for Wasm. Together, these four areas make up 20% of the Interop 2026 score. They are exciting new features that solve real needs.

Focus Areas for 2026

The Interop Project measures interoperability through Web Platform Tests — automated tests that check whether browsers conform to web standards. Interop 2026 is ambitious, covering twenty focus areas. Fifteen are brand new. And five are carryovers from Interop 2025.

Anchor positioning

Anchor positioning is a carryover from Interop 2025, where significant progress was made to empower developers to position elements relative to each other. This year’s focus will be on clarifying the spec, resolving test issues, and increasing the reliability of this powerful layout feature.

Advanced attr()

The CSS attr() function lets you bridge the gap between structural data and visual presentation by pulling values directly from HTML attributes into your CSS, making styles more dynamic and context-aware without the overhead of JavaScript. While attr() has long been supported for the content property, advanced attr() extends it to work across all CSS properties with type conversion — letting you use HTML attribute values as colors, lengths, angles, and other data types. Now that security concerns have been worked through in the specification, browser makers are united in our excitement to ship this long-awaited capability with strong interoperability.

Container style queries

Style queries let you apply styles conditionally, based on the value of a custom property (aka, variable) as defined at a certain container. Similar to how Container size queries let your CSS respond to the size of the container, style queries let it respond to theme values, state flags, and other contextual data.

@container style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #ffffff;
  }
}

Style queries started shipping in recent years, including in Safari 18.0. Interop 2026 will help ensure this powerful tool works consistently everywhere.

contrast-color()

The contrast-color() function in CSS returns a color — either black or white. It puts the burden on the browser to choose whichever has higher contrast with the color specified in the function.

.button {
  background: var(--brand-color);
  color: contrast-color(var(--brand-color));
}

By having the browser make the choice, you can architect your design system in a simpler fashion. You don’t need to manually define every color pairing. Safari and Firefox both shipped support in 2025, and now Interop 2026 will ensure this powerful function works consistently across all browsers.

Note, contrast-color() does not magically solve all accessibility concerns. Read about all the details in How to have the browser pick a contrasting color in CSS.

CSS Zoom

The CSS zoom property scales an element and its contents, affecting layout and making the element take up more (or less) space. Unlike transform: scale(), which is purely visual, zoom changes how the element participates in layout.

.card {
  zoom: 1.5; /* Element is 150% larger and takes up more space */
}

While zoom was supported in browsers for years as a non-standard property, it’s been plagued by inconsistencies in edge cases and how it interacts with other layout features. Now that it’s standardized, CSS zoom returns as a focus area in Interop 2026, continuing from 2025.

Custom Highlights

The CSS Custom Highlight API lets you style arbitrary text ranges without adding extra elements to the DOM. Using JavaScript, you create a highlight range, then style it with the pseudo-elements.

The ::highlight() pseudo-element is perfect for highlighting in-page search results, customizing syntax highlighting in code editors, creating an app that allows collaborative editing with user cursors, or any situation where you need to visually mark text without changing the document structure. The ::target-text pseudo-element styles the text that’s scrolled to when a user taps a link with a text fragment.

With implementations progressing across browsers, Interop 2026 ensures these highlighting capabilities work consistently, giving you reliable tools for text-based interactions.

Dialog and popover additions

The <dialog> element and popover attribute have transformed how developers build overlays on the web. Dialog was part of Interop 2022 and Popover was in Interop 2024. This year, three recent enhancements to these features make up this focus area for Interop 2026.

The closedby attribute lets you control how users can dismiss dialogs:

<dialog closedby="any">
<!-- Can be closed by clicking outside or pressing Escape -->
</dialog>

The popover="hint" attribute creates subordinate popovers that don’t dismiss other auto popovers — perfect for tooltips:

<div popover="hint" id="tooltip">
  This tooltip won’t close the menu!
</div>

The :open pseudo-class matches elements with open states, working with <dialog>, <details>, and <select>:

dialog:open {
  animation: slideIn 0.3s;
}

Together, these additions make building accessible, user-friendly UI overlays easier than ever.

Fetch uploads and ranges

The fetch() method is getting three new powerful capabilities for handling uploads and partial content.

ReadableStream request bodies enable true streaming uploads, letting you upload large files or real-time data without loading everything into memory first:

await fetch('/upload', {
  method: 'POST',
  body: readableStream,
  duplex: 'half'
});

Enhanced FormData support improves multipart uploads and responses.

Range header support allows partial content requests, essential for video streaming and resumable downloads:

fetch('/video.mp4', {
  headers: { 'Range': 'bytes=0-1023' }
});

These enhancements bring fetch() up to par with more specialized APIs, reducing the need for custom solutions.

getAllRecords() for IndexedDB

IndexedDB is a low-level API that lets you store large amounts of structured data in the browser, including files and blobs. It’s been supported in browsers for many years.

Now, IndexedDB is getting a significant performance boost with the new getAllRecords() methods for IDBObjectStore and IDBIndex. These methods allow you to retrieve records in batches and in reverse order:

const records = await objectStore.getAllRecords({
  query: IDBKeyRange.bound('A', 'M'),
  count: 100,
  direction: 'prev'
});

It’s just this new method that’s being included in Interop 2026. The score only reports the percentage of getAllRecords() tests that are passing — not all IndexDB tests.

JSPI for Wasm

WebAssembly has opened the door for running high-performance applications in the browser — games, productivity tools, scientific simulations, and more. But there’s been a fundamental mismatch. Many of these applications were originally written for environments where operations like file I/O or network requests are synchronous (blocking), while the web is fundamentally asynchronous.

The JavaScript Promise Integration API (JSPI) bridges this gap. It lets WebAssembly code that expects synchronous operations work smoothly with JavaScript’s Promise-based async APIs, without requiring you to rewrite the entire application. This means you can port existing C, C++, or Rust applications to the web more easily, unlocking a wider range of software that can run in the browser.

Interop 2026 will ensure JSPI works consistently across browsers, making WebAssembly a more viable platform for complex applications.

Media pseudo-classes

We’ve proposed media pseudo-classes for inclusion in the Interop Project for many years in a row. We are excited that it’s being included this year!

Seven CSS pseudo-classes let you apply CSS based on the playback state of <audio> and <video> elements:

These all shipped in Safari many years ago, but without support in any other browser, most developers don’t use them — or even know they exist. Instead developers need JavaScript to sync UI state with media playback state.

It’s far simpler and more efficient to use media state pseudo-classes in CSS.

video:buffering::after {
  content: "Loading...";
}
audio:muted {
  opacity: 0.5;
}

They are especially powerful combined with :has(), since it unlocks the ability to style anything on the page based on playback state, not just elements that are descendants of the media player.

article:has(video:playing) {
  background-color: var(--backgroundColor); 
  color: contrast-color(var(--backgroundColor));
  transition: background-color 0.5s ease;
}

Learn more about the power of :has() in Using :has() as a CSS Parent Selector and much more.

Navigation API

If you’ve built single-page applications, you may have experienced the pain of managing navigation state with history.pushState() and popstate events. Navigation API gives you a cleaner, more powerful way to intercept and control navigation.

This focus area is a continuation of Interop 2025, where significant progress was made to empower developers to initiate, intercept, and modify browser navigation actions. This year continues work on interoperability, to get the overall score up from the 92.3% test pass result during Interop 2025. Plus, there’s one new feature being added — the precommitHandler option. It lets you defer navigation until critical resources are ready, preventing jarring flashes of incomplete content.

navigation.addEventListener('navigate', (e) => {
  e.intercept({
    async precommitHandler() {
      // Load critical resources before commit
      await loadCriticalData();
    },
    async handler() {
      // Render the new view
      renderPage();
    }
  });
});

Interop 2026 will ensure Navigation API works reliably across browsers, a solid foundation for web applications.

Scoped custom element registries

Working with web components, you may have run into a frustrating limitation: the global customElements registry only allows one definition per tag name across your entire application. When two different libraries both define a <my-button> component, they conflict.

The CustomElementRegistry() constructor solves this by letting you create scoped registries. Different parts of your application — or different shadow roots — can have their own definitions for the same tag name.

const registry = new CustomElementRegistry();
registry.define('my-button', MyButtonV2);
shadowRoot.registry = registry;

This is especially valuable for microfrontends, component libraries, and any situation where you’re integrating third-party web components.

Safari 26.0 was the first browser to ship Scoped custom element registries. Inclusion in Interop 2026 will help ensure this capability works consistently across all browsers.

Scroll-driven Animations

Scroll-driven animations let you more easily create animations that respond to scroll position, now entirely in CSS. As a user scrolls, the animation progresses — no JavaScript needed. You can build scroll-triggered reveals, progress indicators, parallax effects, and interactive storytelling experiences.

Define animations with standard CSS keyframes, then connect them to scroll using animation-timeline:

.reveal {
  animation: fade-in linear forwards;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

Use view() to trigger animations as elements enter and exit the viewport, or scroll() to tie animations to a scrolling container’s position. Learn much more in A guide to Scroll-driven Animations with just CSS.

We shipped support for scroll-driven animations in Safari 26.0. Interop 2026 will help ensure this feature works consistently across all browsers.

Scroll Snap

CSS Scroll Snap controls the panning and scrolling behavior within a scroll container, creating carousel-like experiences:

.carousel {
  scroll-snap-type: x mandatory;
  overflow-x: scroll;
}
.carousel > * {
  scroll-snap-align: center;
}

Scroll Snap has been supported in all modern browsers for many years. But like many of the older CSS specifications, multiple rounds of changes to the specification while early versions were already shipping in browsers created a deep lack of interoperability. With a far more mature web standard, it’s time to circle back and improve interoperability. This is the power of the Interop Project — focusing all the browser teams on a particular feature, and using automated tests to find inconsistencies and disagreements.

shape()

For years, when you wanted to create a complex clipping path to use with clip-path or shape-outside you’ve been limited to polygon(), which only supports straight lines, or SVG paths, which aren’t responsive to element size changes.

Now, the shape() function lets you create complex shapes with path-like commands (move, line, curve). It gives you the best of both worlds — curves like SVG paths, but with percentage-based coordinates that adapt as elements resize.

.element {
  clip-path: shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% 100%,
    curve to 0% 100% via 50% 150%,
    close
  );
}

We shipped support for the shape() function in Safari 18.4. And we look forward to Interop 2026 improving browser implementations so you can confidently use it to render of complex, responsive curves.

View transitions

View Transitions was a focus area in Interop 2025, narrowly defined to include same-document view transitions and view-transition-class. These features allow for smooth, animated transitions between UI states within a single page, as well as flexible control over styling those transitions.

While Safari finished Interop 2025 with a score of 99.2% for view transitions, the overall interoperability score is at 90.8% — so the group decided to continue the effort, carrying over the tests from 2025.

For Interop 2026, the focus area expands to also include cross-document view transitions. This allows you to create smooth, animated transitions in the moments between pages as users navigate your site, rather than an abrupt jump when new page loads. Cross-document view transitions shipped in Safari 18.2. Learn more about it in Two lines of Cross-Document View Transitions code you can use on every website today.

Web Compat

Web compatibility refers to whether or not a real world website works correctly in a particular browser. When a site works in one browser, but not another — that’s a “compat” problem. This focus area is made up of a small collection of Web Platform Tests selected because the fact they fail in some browsers causes real websites to not work in other browsers — thus creating problems for both web developers and users.

Each time Web Compat has been a focus area as part of the Interop Project, it’s targeted a different set of compat challenges. This year, Interop 2026’s web compatibility work includes:

WebRTC

WebRTC (Web Real-Time Communication) enables real-time audio, video, and data communication directly between browsers, without requiring plugins or intermediate servers. You can build video conferencing apps, live streaming platforms, peer-to-peer file sharing, and collaborative tools.

Having reached a 91.6% pass rate, WebRTC continues as a focus area in 2026, building on the progress made during Interop 2025. We’re looking forward to fixing the long tail of interop issues of the main spec for WebRTC.

WebTransport

WebTransport provides a modern way to transmit data between client and server using the HTTP/3 protocol. It gives you low-latency bidirectional communication with multiple streams over a single connection. You get both unreliable datagram support (like UDP) for speed and reliable stream support (like TCP) for guaranteed delivery.

const transport = new WebTransport('https://example.com/endpoint');
await transport.ready;
const stream = await transport.createBidirectionalStream();
// Stream data efficiently

WebTransport is ideal for gaming, real-time collaboration tools, and applications where you need more control than WebSocket provides but don’t want to manage WebRTC’s complexity. Being part of Interop 2026 ensures WebTransport works consistently across all browsers, making it a reliable choice for real-time data transmission.

Investigation Efforts: A Look Ahead

In addition to the focus areas, the Interop Project includes four investigation areas. These are projects where teams gather to assess the current state of testing infrastructure and sort through issues that are blocking progress.

Accessibility testing

Continuing from previous years, the Accessibility Testing investigation aims to work towards generating consistent accessibility trees across browsers. This effort will improve the WPT testing infrastructure for accessibility on top of the foundation from Interop 2024. This work ensures that accessibility features are reliable and consistent, helping developers create more inclusive web experiences.

JPEG XL

JPEG XL is a next-generation raster graphics format that supports animation, alpha transparency, and lossy as well as lossless compression. We shipped support for it in Safari 17.0. This investigation will focus on making the feature properly testable by developing comprehensive test suites, opening up the possibility that JPEG XL could be a focus area in the future.

Mobile testing

The Mobile Testing investigation continues work started in 2025. This year, we will focus on improving infrastructure for mobile-specific features like dynamic viewport changes which are crucial for building responsive mobile web experience that billions of users rely on every day.

WebVTT

Continuing from 2025, the WebVTT investigation addresses a critical challenge facing the web platform. Developers cite WebVTT’s inconsistent behavior across browsers as a major reason for choosing other subtitling and captioning solutions. Our investment in WebVTT last year primarily consisted of validating and fixing the existing test suite, as well as making any necessary spec changes along the way. We are excited to continue that effort this year to ensure synchronized text tracks and closed captioning work seamlessly across the web.

A more interoperable web

Interop 2026 brings together twenty focus areas that matter to you as a web developer. Some, like attr() and contrast-color(), give you more flexible ways to architect your CSS. Others, like Scroll-Driven Animations and View Transitions, let you create smoother, more engaging experiences without reaching for JavaScript. Features like WebTransport and the Navigation API give you more powerful tools for building modern web applications.

Just as important are the focus areas working to fix long-standing inconsistencies — ensuring Scroll Snap works reliably, bringing all browsers up to speed on shape(), and solving real-world compatibility problems that have been frustrating developers and breaking sites.

The WebKit team is committed to making these features work consistently across all browsers. Whether you’re building a design system, a single-page application, a video streaming platform, or anything in between, Interop 2026 is working to give you a more reliable foundation to build on.

Here’s to another year of making the web better, together!

February 12, 2026 05:00 PM

Release Notes for Safari Technology Preview 237

Surfin’ Safari

Safari Technology Preview Release 237 is now available for download for macOS Tahoe and macOS Sequoia. If you already have Safari Technology Preview installed, you can update it in System Settings under General → Software Update.

This release includes WebKit changes between: 305774@main…306595@main.

Accessibility

Resolved Issues

  • Fixed slot elements referenced by aria-labelledby to correctly use their assigned slotted content for accessible names and ignore hidden slotted nodes. (305882@main) (114500560)
  • Fixed <meter> element to have consistent labels between aria-label and title attributes. (305883@main) (127460695)
  • Fixed elements with display: contents and content in a shadow root to have their content properly read when referenced by aria-labelledby. (305918@main) (129361833)
  • Fixed aria-labelledby to use the checkbox name instead of its value when the checkbox name comes from an associated <label> element. (305894@main) (141564913)
  • Fixed grid elements with child rows in a shadow root to properly work with VoiceOver. (306159@main) (153134654)
  • Fixed elements with aria-controls or aria-expanded and the hidden attribute to no longer appear in VoiceOver’s Form Control menu. (305902@main) (162783041)
  • Fixed ::first-letter text not being exposed in the accessibility tree when no other text accompanies it. (305884@main) (168458291)

CSS

New Features

  • Added support for the :heading pseudo-class selector. (306151@main) (158759228)

Resolved Issues

  • Fixed CSS rules within @scope not being applied to <input> and <textarea> elements. (306129@main) (168101378)
  • Fixed dynamic flow-tolerance changes to trigger relayout for display: grid-lanes. (306093@main) (168711707)

DOM

Resolved Issues

  • Fixed offsetX and offsetY for SVG elements to use the outermost SVG as the base for coordinate calculation. (305993@main) (168548585)

HTML

New Features

  • Added support for the source attribute on ToggleEvent interface. (306152@main) (152580641)

Networking

Resolved Issues

  • Fixed X-Frame-Options to only strip tab or space characters, not vertical tabs. (306279@main) (126915315)
  • Fixed range request validation to properly handle HTTP 416 (Requested Range Not Satisfiable) responses. (305866@main) (168487440)

Rendering

Resolved Issues

  • Fixed table rendering for rowspanned cells and bottom border color. (306471@main) (94163960)
  • Fixed <marquee> elements causing incorrect table width calculations. (306059@main) (99826593)
  • Fixed table layout to properly handle visibility: collapse on columns. (305997@main) (168556786)
  • Fixed intrinsic sizing for absolutely positioned replaced elements. (306309@main) (168815514)
  • Fixed percentage padding in table cells to resolve against column widths. (306281@main) (168940907)
  • Fixed table height distribution to apply to tbody sections instead of only the first section. (306457@main) (169154677)

SVG

New Features

  • Added support for the color-interpolation attribute for SVG gradients. (305921@main) (87294645)

Resolved Issues

  • Fixed :visited link color to properly propagate to SVG through currentColor. (306387@main) (98776770)
  • Fixed removing an item from SVGTransformList to properly allow attribute removal. (306162@main) (117840533)

Web API

New Features

  • Added support for async iteration over ReadableStream objects using for await...of loops. (305808@main) (168049382)
  • Added support for the Service Worker static routing Resource Timing API. (306006@main) (168499249)
  • Added srgb-linear and display-p3-linear to PredefinedColorSpace. (306563@main) (169340732)

Resolved Issues

  • Fixed Content Security Policy to only recognize ASCII whitespace excluding vertical tabs to align with the specification. (306222@main) (108559413)
  • Fixed emoji input on Google Docs and similar web applications by supressing keypress events for supplementary characters. (305914@main) (122678873)
  • Fixed an issue where selecting credentials in the Digital Credentials API sometimes required a second click to trigger verification. (305868@main) (163295172)
  • Fixed window bar visibility properties (toolbar.visible, statusbar.visible, menubar.visible) to return static values per the HTML specification for privacy and interoperability. (306391@main) (166554327)
  • Fixed layerX and layerY to return correct values with CSS transforms. (306300@main) (168968832)

Web Authentication

Resolved Issues

  • Fixed an issue in Safari Technology Preview where WebAuthn authentication over NFC failed. (306280@main) (168456474)

Web Extensions

New Features

  • Added support for runtime.getDocumentId() web extension API. (305912@main) (168060269)

Web Inspector

New Features

  • Added support to show each individual request when there are redirects in the Network tab. (306537@main) (152606018)

Resolved Issues

  • Fixed an issue where a large number of search results in the Search tab would freeze Web Inspector. (306429@main) (49234522)

WebRTC

New Features

  • Added support for the targetLatency attribute in WebRTC. (306062@main) (168225793)

February 12, 2026 04:59 PM

February 11, 2026

WebKit features for Safari 26.3

Surfin’ Safari

Safari 26.3 is here, with practical improvements for performance and user experience. This release gives you new tools for optimizing how your content is delivered and better control over navigation in single-page applications. We’ve also fixed issues developers have run into with anchor positioning, multi-column layouts, and other features — making them more robust and reliable. Plus, we’ve refined the everyday browsing experience by fixing issues we found while testing real websites.

Video in visionOS

Now in Safari 26.3 in visionOS, fullscreen video playback automatically dims the user’s surroundings to help put the focus on content.

A floating rectangular image of a dog running at a dog show, covered by the site "The Ultimate Spectacular". This rectangle is floating in a world covered by sandy hills, with mountains in the background, and a big cloudy sky above. This is a 3D environment in visionOS. The image is a video that's full brightness, while the environment around it is not as bright as normal. It's dimmed.
Now when a user plays a video in Safari (like this trailer on YouTube for Top Dogs) and enters fullscreen, the world around the video is dimmed in visionOS 26.3.

Zstandard

Safari 26.3 supports Zstandard (Zstd), a compression algorithm you can use to make your website’s files smaller before sending them to browsers. Like gzip and Brotli, it compresses text-based assets — HTML, CSS, JavaScript, JSON, and SVG — so less data travels over the network.

Zstandard decompresses quickly, reducing the workload on users’ devices. It also compresses fast enough to do on-the-fly, whereas Brotli is typically pre-compressed during your build process.

To use it, configure your server to compress responses with Zstandard and send the Content-Encoding: zstd header. Servers will automatically fall back to other compression methods for browsers that don’t have support yet.

Zstandard support is available in Safari 26.3 on iOS 26.3, iPadOS 26.3, visionOS 26.3, and macOS Tahoe 26.3 — and not in Safari 26.3 on earlier versions of macOS. This is because support comes from the system networking stack used by Safari.

Navigation API

When building single-page applications with the Navigation API, you might need a reliable way to cancel ongoing work when a navigation gets interrupted. Maybe the user clicked another link before the previous navigation finished, they hit the back button, or your code called navigation.navigate() again. Whatever the reason, you don’t want to keep processing a navigation that’s no longer relevant.

In Safari 26.3, the Navigation API exposes a AbortSignal on NavigateEvent which triggers when the navigation is aborted, giving you a standard way to clean up and cancel work:

navigation.addEventListener('navigate', (event) => {
  event.intercept({
    async handler() {
      const response = await fetch('/api/data', {
        signal: event.signal  // Automatically cancels if navigation is aborted
      });

      const data = await response.json();
      renderContent(data);
    }
  });
});

If the user navigates away before the fetch completes, the request automatically cancels. You can also listen to the signal’s abort event to clean up other resources like timers or animations.

This gives you fine-grained control over what happens when navigations don’t complete, helping you avoid memory leaks and unnecessary work.

Bug fixes and more

Along with the new features, WebKit for Safari 26.3 includes additional improvements to existing features.

CSS

  • Fixed a style resolution loop that occurred when a position-try box was inside a display: none ancestor. (163691885)
  • Fixed an issue where anchor-positioned elements repeatedly transitioning from display: block to display: none cause position jumps during animation. (163862003)
  • Fixed an issue where fixed-positioned boxes using position-area were incorrectly included in the scrollable containing block calculation. (164017310)
  • Fixed an issue where text-decoration: underline was rendered too high when text-box-trim was applied to the root inline box. (165945326)
  • Fixed a multi-column layout issue where the widows and text-indent properties are applied cause an incorrect indent on the portion of the paragraph that flows into the next column. (165945497)
  • Fixed an issue where CSS cursors like move, all-scroll, ew-resize, and ns-resize did not display correctly. (166731882)

DOM

  • Fixed incorrect timestamp handling and switched to use the raw touch timestamp. (164262652)

Media

  • Fixed an issue where the fullscreen button in visionOS inline video controls did not visually indicate interactivity by extending the glow effect to all button.circular elements. (164259201)
  • Fixed Video Viewer mode for iframe videos on macOS. (164484608)
  • Fixed an issue where Safari could not play live videos when the sourceBuffer content is removed and re-added causing the seek to not complete. (165628836)

Rendering

  • Fixed an issue where positioned or transformed <img> elements containing HDR JPEGs with gain maps would incorrectly render as SDR. (163517157)

Safe Browsing

  • Fixed a bug where if Safe Browsing queried for an entry on the Public Suffix List, and a Safe Browsing vendor responded that the whole effective TLD was unsafe, the whole site would be marked as unsafe. (168155375)

Feedback

We love hearing from you. To share your thoughts, find us online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn.

If you run into any issues, we welcome your bug report. Filing issues really does make a difference.

You can also find this information in the Safari release notes.

February 11, 2026 06:00 PM

February 09, 2026

Igalia WebKit Team: WebKit Igalia Periodical #56

Igalia WebKit

Update on what happened in WebKit in the week from February 2 to February 9.

The main event this week was FOSDEM (pun intended), which included presentations related to WebKit; but also we got a batch of stable and development releases, asynchronous scrolling work, OpenGL logging, cleanups, and improving the inspector for the WPE work.

Cross-Port 🐱

Graphics 🖼️

While asynchronous scrolling for mouse wheel events was already supported, scrollbar layers were still being painted on the main thread. This has been changed to paint scrollbars on the scrolling thread instead, which avoids scrollbars to “lag” behind scrolled content.

Fixed flickering caused by the combination of damage tracking and asynchronous scrolling for mouse wheel events.

It is now possible to enable debug logging for OpenGL contexts using the new GLContext log channel, which takes advantage of the message events produced by the widespread KHR_debug extension.

Figuring out the exact location inside WebKit that triggered an OpenGL issue may still be challenging with this aid, and therefore a backtrace will be appended in case of errors to help pinpoint the source, when the log channel is enabled at the “debug” level with GLContext=debug.

Configuring the build with USE_SKIA=OFF to make WebKit use the Cairo graphics library is no longer supported. Using Skia has been the default since late 2024, and after two full years the 2.54.0 release (due in September 2026) will be the first one where the choice is no longer possible.

WebKitGTK 🖥️

The “on demand” hardware acceleration policy has been rarely used lately, and thus support for it has been removed. Note that this affects only the GTK port when built with GTK 3—the option never existed when using GTK 4.

Existing GTK 3 applications that use WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND will continue to work and do not need rebuilding: they will be promoted to use the “always enabled” policy starting with WebKitGTK 2.54.0 (due in September 2026).

WPE WebKit 📟

The Web Inspector has received support for saving data to local files, allowing things such as saving page resources or exporting the network session to a HAR archive.

Note that using the Web Inspector locally is supported when using the WPEPlatform API, and the keyboard shortcut Ctrl+Shift+I may be used to bring it up.

Releases 📦️

WebKitGTK 2.50.5 and WPE WebKit 2.50.5 have been released. These are stable maintenance releases that improves stability, correct bugs, and fixes small rendering issues.

The second release candidates for the upcoming stable branch, WebKitGTK 2.51.91 and WPE WebKit 2.51.91, have been published as well. Those using those to preview the upcoming 2.52.x series are encouraged to provide bug reports in Bugzilla for any issue they may experience.

Community & Events 🤝

We have published a blog post on our work implementing the Temporal proposal in JavaScriptCore, WebKit's JavaScript engine.

This year's edition of FOSDEM took place in Brussels between January 31st and February 1st, and featured a number of sessions related to WebKitGTK and WPE:

The videos for the talks are already available, too.

That’s all for this week!

By Igalia WebKit Team at February 09, 2026 11:21 PM

February 06, 2026

Interop 2025: A year of convergence

Surfin’ Safari

Interop 2025 has come to a close, and the results speak for themselves. Now in its fourth year, the Interop project brings together Apple, Bocoup, Google, Igalia, Microsoft, and Mozilla to identify the areas of the web platform where interoperability matters most to you as a web developer — and then do the work to get there. This year was the most ambitious yet: the group selected 19 focus areas and 5 investigation areas spanning CSS, JavaScript, Web APIs, and performance. At the start of 2025, only 29% of the selected tests passed across all browsers. By the end of the year, the Interop score reached a 97% pass rate — and all four experimental browsers (Chrome Canary, Edge Dev, Firefox Nightly, and Safari Technology Preview) reached 99%.

Interop 2025 end of year results. Chrome Canary, Edge Dev, Firefox Nightly, and Safari Technology Preview all have a score of 99%. The overall interop score is 97%.

Each year, the Interop project chooses its focus areas through a collaborative process with proposals, research into what web developers need, and debates about priorities. For Interop 2025, our team advocated for including focus areas that we knew would require significant engineering investment from WebKit — because we knew those areas would make a real difference to you. The results show that commitment paid off. Safari made the largest jump of any browser this year, climbing from 43 to 99.

As always, this year’s focus areas were chosen based on developer feedback, including results from the State of CSS survey, and we’re proud of how much ground we covered. The 19 focus areas touched nearly every corner of the platform. On the CSS and UI side, the project tackled Anchor Positioning, View Transitions, @scope, backdrop-filter, text-decoration, Writing modes, Layout (both Flexbox and Grid, continued from prior years), and the <details> element. For APIs and platform features, we worked on the Navigation API, Storage Access API, URLPattern, Modules, the scrollend event, WebRTC, and WebAssembly. And on the health and compatibility front, there was focused work on Core Web Vitals, Pointer and Mouse events, removing Mutation events, and general web compatibility. Five investigation areas — accessibility testing, Gamepad API testing, mobile testing, privacy testing, and WebVTT — laid groundwork for future Interop cycles.

We want to highlight three focus areas that were especially meaningful this year.

  • Anchor positioning lets you position popovers, tooltips, and menus relative to any element purely in CSS — no JavaScript positioning libraries required. It’s one of the most requested CSS features of the last several years, and it now works interoperably across all browsers.
  • Same-document View Transitions allow smooth, animated transitions between UI states natively in the browser, along with the new view-transition-class CSS property for flexible styling of those transitions. We shipped support in fall 2024, in Safari 18.0 and Safari 18.2. Web developers are excited about View Transitions! This extra attention on interoperability across browsers means it’s ready for you to use.
  • Navigation API — a modern replacement for history.pushState() — gives single-page applications proper navigation handling with interception, traversal, and entries. We shipped support in Safari 26.2, and we’re glad to see it arrive interoperably from the start.
The graphs of scores across the year. A black line shows the overall interoperability rising from around 30% in January to 97% at the end. A blue line representing Safari's progress rises from 43% to be the best score at the top in December, almost at 100%. Orange representing Firefox starts just above Safari, and also follows a similar trajectory across the year. Edge and Chrome have kind of flat progress, starting around 80 and converging with all the other lines at the top at the end.

The graph above tells the story of the year: every browser engine invested heavily, and the lines converge at the top. That convergence is what makes the Interop project so valuable — the shared progress that means you can write code once and trust that it works everywhere.

We want to thank our colleagues across the industry who made this possible. Interoperability is one of the foundational strengths of the web, and we remain committed to this collaboration. You can explore the full results, including scores for each individual focus area, on the Interop 2025 dashboard.

February 06, 2026 05:45 PM

February 03, 2026

Release Notes for Safari Technology Preview 236

Surfin’ Safari

Safari Technology Preview Release 236 is now available for download for macOS Tahoe and macOS Sequoia. If you already have Safari Technology Preview installed, you can update it in System Settings under General → Software Update.

This release includes WebKit changes between: 305084@main…305413@main.

CSS

Resolved Issues

  • Fixed handling of padding and margins for flex and grid layouts across all writing modes. (301814@main) (71046552)
  • Fixed text-combine-upright to properly ignore letter-spacing when composing text horizontally, aligning with the CSS Writing Modes specification. (305116@main) (116562622)
  • Fixed an issue where background-blend-mode was not applied correctly when combined with background-clip: text. (305118@main) (120901898)
  • Fixed an issue where CSS @starting-style entry animations were only applied on the first transition, especially when interacting with anchor positioning or position fallbacks. (305371@main) (163928932)
  • Fixed table column width distribution when a colspan spans mixed percentage and auto-width columns to properly respect percentage constraints. (305120@main) (165561401)
  • Fixed an issue where shape-outside did not update correctly after web fonts loaded. (305299@main) (166336491)
  • Fixed table height calculation to correctly account for captions with orthogonal writing-mode. (305110@main) (167220730)
  • Fixed an issue where grid-lanes items incorrectly used a grid area as their containing block in the stacking axis, ensuring proper sizing for cases like fit-content and percentage-based dimensions. (305319@main) (167221488)
  • Fixed counter-* properties serialization order. (305086@main) (167518994)
  • Fixed outline-width and outline-offset to follow updated computed style resolution rules. (305153@main) (167618367)
  • Fixed the computed style resolution for border-*-width properties. (305212@main) (167689519)
  • Fixed the computed style resolution for the column-rule-width property. (305240@main) (167725940)
  • Fixed border-*-width, outline-width, and column-rule-width so they now pixel snap correctly during CSS animations and transitions. (305272@main) (167763497)

Forms

Resolved Issues

  • Fixed an issue where input[type="search"] fields with appearance: none incorrectly reserved space for the datalist dropdown button. (305314@main) (166754216)
  • Fixed an incorrect fallback for the menu style for empty lists, improving readability and correctness. (305228@main) (167662316)

HTML

New Features

  • Added support for using the min(), max(), and clamp() math functions in the sizes attribute of <img> elements. (305226@main) (167526292)

Resolved Issues

  • Fixed an issue where nested about:blank frames were incorrectly treated as self-referencing, preventing them from loading. (305404@main) (148373033)

Images

Resolved Issues

  • Fixed image uploading to not transcode images when accept="image/*" is specified. (305283@main) (166124206)

Media

Resolved Issues

  • Fixed an issue where <video> poster images were incorrectly double-scaled when zoom was applied by using the cached intrinsic poster size without reapplying zoom. (305347@main) (150976146)
  • Fixed an issue where the macOS inline media controls timeline scrubber overlapped the right container buttons. (305177@main) (167634241)

Rendering

Resolved Issues

  • Fixed an issue where auto-positioned absolutely positioned descendants were not always marked for layout when their parent’s border box moved. (305229@main) (131806062)
  • Fixed an issue where color fonts could affect the color of other DOM elements. (305254@main) (166631312)
  • Fixed an issue by disabling CoreGraphics blur and drop-shadow filters due to system framework bugs and reverting the previous workaround. (305216@main) (166631624)
  • Fixed <col> elements with span > 1 not applying their width to all spanned columns during table layout, aligning behavior with other browsers. (305113@main) (167225435)
  • Fixed table layout min-width distribution for spanning cells with mixed percent, fixed, and auto columns. (305215@main) (167684748)

SVG

Resolved Issues

  • Fixed breaking SVG resource referencing when removing a resource which shares its id with other resources. (305197@main) (147015037)
  • Fixed <clipPath> to clip to its <use> child element based on the visibility of the <use> target element. (305374@main) (167491519)
  • Fixed displaying an SVG filter referencing an element with a huge stroke. (305136@main) (167516452)
  • Fixed hit testing for overlapping <text> and <tspan> elements in SVG. (305221@main) (167691166)

Web API

Resolved Issues

  • Fixed DeviceMotionEvent and DeviceOrientationEvent interfaces so that they only show up in secure contexts just like the corresponding events and made ondevicemotion and ondeviceorientation enumerable, aligning with the specification. (305266@main) (44804273)
  • Fixed handling of unknown DigitalCredential protocols by gracefully filtering them out and showing a console warning instead of throwing an error. (305257@main) (166673454)

WebRTC

Resolved Issues

  • Fixed RTCConfiguration.iceServers to be a non-optional sequence with an empty array as the default, improving spec compliance and ensuring RTCPeerConnection behaves correctly when iceServers is undefined. (305152@main) (167607478)

February 03, 2026 12:08 AM

February 02, 2026

Igalia WebKit Team: WebKit Igalia Periodical #55

Igalia WebKit

Update on what happened in WebKit in the week from January 26 to February 2.

A calm week for sure! The highlight this week is the fix for scrolling not starting when the main thread is blocked.

Cross-Port 🐱

Graphics 🖼️

Fixed the problem of wheel event async scrolling doesn't start while the main thread is blocked. This should make WebKit feel more responsive even on heavier websites.

That’s all for this week!

By Igalia WebKit Team at February 02, 2026 08:11 PM

January 26, 2026

Igalia WebKit Team: WebKit Igalia Periodical #54

Igalia WebKit

Update on what happened in WebKit in the week from January 19 to January 26.

The main event this week has been the creation of the branch for the upcoming stable series, accompanied by the first release candidate before 2.52.0. But there's more: the WPE port gains hyphenation support and the ability to notify of graphics buffer changes; both ports get graphics fixes and a couple of new Web features, and WPE-Android also gets a new stable release.

Cross-Port 🐱

Implemented support for the :open pseudo-class on dialog and details elements. This is currently behind the OpenPseudoClass feature flag.

Implemented the source property for ToggleEvent. This can be used to run code dependent on the triggering element in response to a popover or dialog toggle.

Graphics 🖼️

Fixed the rendering glitches with wheel event asynchronous scrolling, which occurred when the page was scrolled to areas not covered by tiles while the main thread was blocked.

WPE WebKit 📟

Support for hyphenation has been added to WPE. This requires libhyphen and can be disabled at build-time with the USE_LIBHYPHEN=OFF CMake option.

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

WPEPlatform gained support to notify changes in the configuration of graphics buffers allocated to render the contents of a web view, either by handling the WPEView::buffers-changed signal or by overriding the WPEViewClass.buffers_changed virtual function. This feature is mainly useful for platform implementations which may need to perform additional setup in advance, before updated web view contents are provided in the buffers configured by WebKit.

Releases 📦️

WPE-Android 0.3.0 has been released, and prebuilt packages are available at the Maven Central repository. The main change in this this version is the update to WPE WebKit 2.50.4, which is the most recent stable release.

A new branch has been created for the upcoming 2.52.x stable release series of the GTK and WPE WebKit ports. The first release candidates from this branch, WebKitGTK 2.51.90 and WPE WebKit 2.51.90 are now available. Testing and issue reports in Bugzilla are welcome to help with stabilization before the first stable release, which is planned for mid-March.

That’s all for this week!

By Igalia WebKit Team at January 26, 2026 09:00 PM

Enrique Ocaña: Igalia Multimedia contributions in 2025

Igalia WebKit

Now that 2025 is over, it’s time to look back and feel proud of the path we’ve walked. Last year has been really exciting in terms of contributions to GStreamer and WebKit for the Igalia Multimedia team.

With more than 459 contributions along the year, we’ve been one of the top contributors to the GStreamer project, in areas like Vulkan Video, GstValidate, VA, GStreamer Editing Services, WebRTC or H.266 support.

Pie chart of Igalia's contributions to different areas of the GStreamer project:
other (30%)
vulkan (24%)
validate (7%)
va (6%)
ges (4%)
webrtc (3%)
h266parse (3%)
python (3%)
dots-viewer (3%)
tests (2%)
docs (2%)
devtools (2%)
webrtcbin (1%)
tracers (1%)
qtdemux (1%)
gst (1%)
ci (1%)
y4menc (1%)
videorate (1%)
gl (1%)
alsa (1%)
Igalia’s contributions to the GStreamer project

In Vulkan Video we’ve worked on the VP9 video decoder, and cooperated with other contributors to push the AV1 decoder as well. There’s now an H.264 base class for video encoding that is designed to support general hardware-accelerated processing.

GStreaming Editing Services, the framework to build video editing applications, has gained time remapping support, which now allows to include fast/slow motion effects in the videos. Video transformations (scaling, cropping, rounded corners, etc) are now hardware-accelerated thanks to the addition of new Skia-based GStreamer elements and integration with OpenGL. Buffer pool tuning and pipeline improvements have helped to optimize memory usage and performance, enabling the edition of 4K video at 60 frames per second. Much of this work to improve and ensure quality in GStreamer Editing Services has also brought improvements in the GstValidate testing framework, which will be useful for other parts of GStreamer.

Regarding H.266 (VVC), full playback support (with decoders such as vvdec and avdec_h266, demuxers and muxers for Matroska, MP4 and TS, and parsers for the vvc1 and vvi1 formats) is now available in GStreamer 1.26 thanks to Igalia’s work. This allows user applications such as the WebKitGTK web browser to leverage the hardware accelerated decoding provided by VAAPI to play H.266 video using GStreamer.

Igalia has also been one of the top contributors to GStreamer Rust, with 43 contributions. Most of the commits there have been related to Vulkan Video.

Pie chart of Igalia's contributions to different areas of the GStreamer Rust project:
vulkan (28%)
other (26%)
gstreamer (12%)
ci (12%)
tracer (7%)
validate (5%)
ges (7%)
examples (5%)
Igalia’s contributions to the GStreamer Rust project

In addition to GStreamer, the team also has a strong presence in WebKit, where we leverage our GStreamer knowledge to implement many features of the web engine related to multimedia. From the 1739 contributions to the WebKit project done last year by Igalia, the Multimedia team has made 323 of them. Nearly one third of those have been related to generic multimedia playback, and the rest have been on areas such as WebRTC, MediaStream, MSE, WebAudio, a new Quirks system to provide adaptations for specific hardware multimedia platforms at runtime, WebCodecs or MediaRecorder.

Pie chart of Igalia's contributions to different areas of the WebKit project:
Generic Gstreamer work (33%)
WebRTC (20%)
Regression bugfixing (9%)
Other (7%)
MSE (6%)
BuildStream SDK (4%)
MediaStream (3%)
WPE platform (3%)
WebAudio (3%)
WebKitGTK platform (2%)
Quirks (2%)
MediaRecorder (2%)
EME (2%)
Glib (1%)
WTF (1%)
WebCodecs (1%)
GPUProcess (1%)
Streams (1%)
Igalia Multimedia Team’s contributions to different areas of the WebKit project

We’re happy about what we’ve achieved along the year and look forward to maintaining this success and bringing even more exciting features and contributions in 2026.

By eocanha at January 26, 2026 09:34 AM

January 19, 2026

Igalia WebKit Team: WebKit Igalia Periodical #53

Igalia WebKit

Update on what happened in WebKit in the week from December 26 to January 19.

We're back! The first periodical of 2026 brings you performance optimizations, improvements to the memory footprint calculation, new APIs, the removal of the legacy Qt5 WPE backend, and as always, progress on JSC's Temporal implementation.

Cross-Port 🐱

The memory footprint calculation mechanism has been unified across GTK, JSC, and WPE ports. Therefore, the expensive /proc/self/smaps is not used anymore and the WPE uses /proc/self/statm with extra cache now to prevent frequent file reading.

Added a new webkit_context_menu_get_position() function to the API that allows obtaining the pointer coordinates, relative to the web view origin, at the moment when a context menu was triggered.

Additionally, behaviour of context menus has been made more consistent between the GTK and WPE ports, and handling of GAction objects attached to menu items has been rewritten and improved with the goal of better supporting context menus in the WPE port.

JavaScriptCore 🐟

The built-in JavaScript/ECMAScript engine for WebKit, also known as JSC or SquirrelFish.

In JavaScriptCore's implementation of Temporal, fixed a bug in Temporal.PlainTime.from that read options in the wrong order, which caused a test262 test to fail.

In JavaScriptCore's implementation of Temporal, fixed several bugs in PlainYearMonth methods and enabled all PlainYearMonth tests that don't depend on the Intl object. This completes the implementation of Temporal PlainYearMonth objects in JSC.

Graphics 🖼️

In WebKit's Skia graphics backend, fixed GrDirectContext management for GPU resources. Operations on GPU-backed resources must use the context that created them, not the current thread's context. The fix stores GrDirectContext at creation time for NativeImage and uses surface->recordingContext()->asDirectContext() for SkSurface, correcting multiple call sites that previously used the shared display's context incorrectly.

Damage propagation has been added to the recently-added, non-composited mode in WPE.

In WebKit's Skia graphics backend for GTK/WPE, added canvas 2D operation recording for GPU-accelerated rendering. Instead of executing drawing commands immediately, operations are recorded into an SkPicture and replayed in batch when the canvas contents are needed, reducing GPU state change overhead for workloads with many small drawing operations, improving the MotionMark Canvas Lines performance on embedded devices with low-end tiled GPUs.

WPE WebKit 📟

Due to Qt5 not receiving maintenance since mid-2025, the WPE Qt5 binding that used the legacy libwpe API has been removed from the tree. The Qt6 binding remains part of the source tree, which is a better alternative that allows using supported Qt versions, and is built atop the new WPEPlatform API, making it a future-proof option. The WPE Qt API may be enabled when configuring the build with CMake, using the ENABLE_WPE_QT_API option.

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

The WPEScreenSyncObserver class has been improved to support multiple callbacks. Instead of a single callback set with wpe_screen_sync_observer_set_callback(), clients of the API can now use wpe_screen_sync_observer_add_callback() and wpe_screen_sync_observer_remove_callback(). The observer will be paused automatically when there are no callbacks attached to it.

That’s all for this week!

By Igalia WebKit Team at January 19, 2026 07:25 PM

December 25, 2025

Igalia WebKit Team: WebKit Igalia Periodical #52

Igalia WebKit

Update on what happened in WebKit in the week from December 16 to December 25.

Right during the holiday season 🎄, the last WIP installment of the year comes packed with new releases, a couple of functions added to the public API, cleanups, better timer handling, and improvements to MathML and WebXR support.

Cross-Port 🐱

Landed support for font-size: math. Now math-depth can automatically control the font size inside of <math> blocks, making scripts and nested content smaller to improve readability and presentation.

Two new functions have been added to the public API:

  • webkit_context_menu_item_get_gaction_target() to obtain the GVariant associated with a context menu item created from a GAction.

  • webkit_context_menu_item_get_title() may be used to obtain the title of a context menu item.

Improved timers, by making some of them use the timerfd API. This reduces timer “lateness”—the amount of time elapsed between the configured trigger time, and the effective one—, which in turn improves the perceived smoothness of animations thanks to steadier frame delivery timings. Systems where the timerfd_create and timerfd_settime functions are not available will continue working as before.

On the WebXR front, support was added for XR_TRACKABLE_TYPE_DEPTH_ANDROID through the XR_ANDROID_trackables extension, which allows reporting depth information for elements that take part in hit testing.

Graphics 🖼️

Landed a change that implements non-composited page rendering in the WPE port. This new mode is disabled by default, and may be activated by disabling the AcceleratedCompositing runtime preference. In such case, the frames are rendered using a simplified code path that does not involve the internal WebKit compositor. Therefore it may offer a better performance in some specific cases on constrained embedded devices.

Since version 2.10.2, the FreeType library can be built with direct support for loading fonts in the WOFF2 format. Until now, the WPE and GTK WebKit ports used libwoff2 in an intermediate step to convert those fonts on-the-fly before handing them to FreeType for rendering. The CMake build system will now detect when FreeType supports WOFF2 directly and skip the conversion step. This way, in systems which provide a suitable version of FreeType, libwoff2 will no longer be needed.

WPE WebKit 📟

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

The legacy libwpe-based API can now be disabled at build time, by toggling the ENABLE_WPE_LEGACY_API CMake option. This allows removal of uneeded code when an application is exclusively using the new WPEPlatform API.

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

AHardwareBuffer is now supported as backing for accelerated graphics surfaces that can be shared across processes. This is the last piece of the puzzle to use WPEPlatform on Android without involving expensive operations to copy rendered frames back-and-forth between GPU and system memory.

Releases 📦️

WebKitGTK 2.50.4 and WPE WebKit 2.50.4 have been released. These stable releases include a number of important patches for security issues, and we urge users and distributors to update to this release if they have not yet done it. An accompanying security advisory, WSA-2025-0010, has been published (GTK, WPE).

Development releases of WebKitGTK 2.51.4 and WPE WebKit 2.51.4 are available as well, and may be used to preview upcoming features. As usual, bug reports are welcome in Bugzilla.

Community & Events 🤝

Paweł Lampe has published a blog post that discusses various pre-rendering techniques useful in the context of using WPE on embedded devices.

That’s all for this week!

By Igalia WebKit Team at December 25, 2025 06:26 PM

December 19, 2025

Pawel Lampe: WPE performance considerations: pre-rendering

Igalia WebKit

This article is a continuation of the series on WPE performance considerations. While the previous article touched upon fairly low-level aspects of the DOM tree overhead, this one focuses on more high-level problems related to managing the application’s workload over time. Similarly to before, the considerations and conclusions made in this blog post are strongly related to web applications in the context of embedded devices, and hence the techniques presented should be used with extra care (and benchmarking) if one would like to apply those on desktop-class devices.

The workload #

Typical web applications on embedded devices have their workloads distributed over time in various ways. In practice, however, the workload distributions can usually be fitted into one of the following categories:

  1. Idle applications with occasional updates - the applications that present static content and are updated at very low intervals. As an example, one can think of some static dashboard that presents static content and switches the page every, say, 60 seconds - such as e.g. a static departures/arrivals dashboard on the airport.
  2. Idle applications with frequent updates - the applications that present static content yet are updated frequently (or are presenting some dynamic content, such as animations occasionally). In that case, one can imagine a similar airport departures/arrivals dashboard, yet with the animated page scrolling happening quite frequently.
  3. Active applications with occasional updates - the applications that present some dynamic content (animations, multimedia, etc.), yet with major updates happening very rarely. An example one can think of in this case is an application playing video along with presenting some metadata about it, and switching between other videos every few minutes.
  4. Active applications with frequent updates - the applications that present some dynamic content and change the surroundings quite often. In this case, one can think of a stock market dashboard continuously animating the charts and updating the presented real-time statistics very frequently.

Such workloads can be well demonstrated on charts plotting the browser’s CPU usage over time:

Typical web application workloads.

As long as the peak workload (due to updates) is small, no negative effects are perceived by the end user. However, when the peak workload is significant, some negative effects may start getting noticeable.

In case of applications from groups (1) and (2) mentioned above, a significant peak workload may not be a problem at all. As long as there are no continuous visual changes and no interaction is allowed during updates, the end-user is unable to notice that the browser was not responsive or missed some frames for some period of time. In such cases, the application designer does not need to worry much about the workload.

In other cases, especially the ones involving applications from groups (3) and (4) mentioned above, the significant peak workload may lead to visual stuttering, as any processing making the browser busy for longer than 16.6 milliseconds will lead to lost frames. In such cases, the workload has to be managed in a way that the peaks are reduced either by optimizing them or distributing them over time.

First step: optimization #

The first step to addressing the peak workload is usually optimization. Modern web platform gives a full variety of tools to optimize all the stages of web application processing done by the browser. The usual process of optimization is a 2-step cycle starting with measuring the bottlenecks and followed by fixing them. In the process, the usual improvements involve:

  • using CSS containment,
  • using shadow DOM,
  • promoting certain parts of the DOM to layers and manipulating them with transforms,
  • parallelizing the work with workers/worklets,
  • using the visibility CSS property to separate painting from layout,
  • optimizing the application itself (JavaScript code, the structure of the DOM, the architecture of the application),
  • etc.

Second step: pre-rendering #

Unfortunately, in practice, it’s not uncommon that even very well optimized applications still have too much of a peak workload for the constrained embedded devices they’re used on. In such cases, the last resort solution is pre-rendering. As long as it’s possible from the application business-logic perspective, having at least some web page content pre-rendered is very helpful in situations when workload has to be managed, as pre-rendering allows the web application designer to choose the precise moment when the content should actually be rendered and how it should be done. With that, it’s possible to establish a proper trade-off between reduction in peak workload and the amount of extra memory used for storing the pre-rendered contents.

Pre-rendering techniques #

Nowadays, the web platform provides at lest a few widely-adapted APIs that provide means for the application to perform various kinds of pre-rendering. Also, due to the ways the browsers are implemented, some APIs can be purposely misused to provide pre-rendering techniques not necessarily supported by the specification. However, in the pursuit of good trade-offs, all the possibilities should be taken into account.

Before jumping into particular pre-rendering techniques, it’s necessary to emphasize that the pre-rendering term used in this article refers to the actual rendering being done earlier than it’s visually presented. In that sense, the resource is rasterized to some intermediate form when desired and then just composited by the browser engine’s compositor later.

Pre-rendering offline #

The most basic (and limited at the same time) pre-rendering technique is one that involves rendering offline i.e. before the browser even starts. In that case, the first limitation is that the content to be rendered must be known beforehand. If that’s the case, the rendering can be done in any way, and the result may be captured as e.g. raster or vector image (depending on the desired trade-off). However, the other problem is that such a rendering is usually out of the given web application scope and thus requires extra effort. Moreover, depending on the situation, the amount of extra memory used, the longer web application startup (due to loading the pre-rendered resources), and the processing power required to composite a given resource, it may not always be trivial to obtain the desired gains.

Pre-rendering using canvas #

The first group of actual pre-rendering techniques happening during web application runtime is related to Canvas and OffscreenCavas. Those APIs are really useful as they offer great flexibility in terms of usage and are usually very performant. However, in this case, the natural downside is the lack of support for rendering the DOM inside the canvas. Moreover, canvas has a very limited support for painting text — unlike the DOM, where CSS has a significant amount of features related to it. Interestingly, there’s an ongoing proposal called HTML-in-Canvas that could resolve those limitations to some degree. In fact, Blink has a functioning prototype of it already. However, it may take a while before the spec is mature and widely adopted by other browser engines.

When it comes to actual usage of canvas APIs for pre-rendering, the possibilities are numerous, and there are even more of them when combined with processing using workers. The most popular ones are as follows:

  • rendering to an invisible canvas and showing it later,
  • rendering to a canvas detached from the DOM and attaching it later,
  • rendering to an invisible/detached canvas and producing an image out of it to be shown later,
  • rendering to an offscreen canvas and producing an image out of it to be shown later.

When combined with workers, some of the above techniques may be used in the worker threads with the rendered artifacts transferred to the main for presentation purposes. In that case, one must be careful with the transfer itself, as some objects may get serialized, which is very costly. To avoid that, it’s recommended to use transferable objects and always perform a proper benchmarking to make sure the transfer is not involving serialization in the particular case.

While the use of canvas APIs is usually very straightforward, one must be aware of two extra caveats.

First of all, in the case of many techniques mentioned above, there is no guarantee that the browser will perform actual rasterization at the given point in time. To ensure the rasterization is triggered, it’s usually necessary to enforce it using e.g. a dummy readback (getImageData()).

Finally, one should be aware that the usage of canvas comes with some overhead. Therefore, creating many canvases or creating them often, may lead to performance problems that could outweigh the gains from the pre-rendering itself.

Pre-rendering using eventually-invisible layers #

The second group of pre-rendering techniques happening during web application runtime is limited to the DOM rendering and comes out of a combination of purposeful spec misuse and tricking the browser engine into making it rasterizing on demand. As one can imagine, this group of techniques is very much browser-engine-specific. Therefore, it should always be backed by proper benchmarking of all the use cases on the target browsers and target hardware.

In principle, all the techniques of this kind consist of 3 parts:

  1. Enforcing the content to be pre-rendered being placed on a separate layer backed by an actual buffer internally in the browser,
  2. Tricking the browser’s compositor into thinking that the layer needs to be rasterized right away,
  3. Ensuring the layer won’t be composited eventually.

When all the elements are combined together, the browser engine will allocate an internal buffer (e.g. texture) to back the given DOM fragment, it will process that fragment (style recalc, layout), and rasterize it right away. It will do so as it will not have enough information to allow delaying the rasterization of the layer (as e.g. in case of display: none). Then, when the compositing time comes, the layer will turn out to be invisible in practice due to e.g. being occluded, clipped, etc. This way, the rasterization will happen right away, but the results will remain invisible until a later time when the layer is made visible.

In practice, the following approaches can be used to trigger the above behavior:

  • for (1), the CSS properties such as will-change: transform, z-index, position: fixed, overflow: hidden etc. can be used depending on the browser engine,
  • for (2) and (3), the CSS properties such as opacity: 0, overflow: hidden, contain: strict etc. can be utilized, again, depending on the browser engine.
The scrolling trick

While the above CSS properties allow for various combinations, in case of WPE WebKit in the context of embedded devices (tested on NXP i.MX8M Plus), the combination that has proven to yield the best performance benefits turns out to be a simple approach involving overflow: hidden and scrolling. The example of such an approach is explained below.

Suppose the goal of the application is to update a big table with numbers once every N frames — like in the following demo: random-numbers-bursting-in-table.html?cs=20&rs=20&if=59

Bursting numbers demo.

With the number of idle frames (if) set to 59, the idea is that the application does nothing significant for the 59 frames, and then every 60th frame it updates all the numbers in the table.

As one can imagine, on constrained embedded devices, such an approach leads to a very heavy workload during every 60th frame and hence to lost frames and unstable application’s FPS.

As long as the numbers are available earlier than every 60th frame, the above application is a perfect example where pre-rendering could be used to reduce the peak workload.

To simulate that, the 3 variants of the approach involving the scrolling trick were prepared for comparison with the above:

In the above demos, the idea is that each cell with a number becomes a scrollable container with 2 numbers actually — one above the other. In that case, because overflow: hidden is set, only one of the numbers is visible while the other is hidden — depending on the current scrolling:

Scrolling trick explained.

With such a setup, it’s possible to update the invisible numbers during idle frames without the user noticing. Due to how WPE WebKit accelerates the scrolling, changing the invisible numbers, in practice, triggers the layout and rendering right away. Moreover, the actual rasterization to the buffer backing the scrollable container happens immediately (depending on the tiling settings), and hence the high cost of layout and text rasterization can be distributed. When the time comes, and all the numbers need to be updated, the scrollable containers can be just scrolled, which in that case turns out to be ~2 times faster than updating all the numbers in place.

To better understand the above effect, it’s recommended to compare the mark views from sysprof traces of the random-numbers-bursting-in-table.html?cs=10&rs=10&if=11 and random-numbers-bursting-in-table-prerendered-1.html?cs=10&rs=10&if=11 demos:

Sysprof from basic demo.



Sysprof from pre-rendering demo.

While the first sysprof trace shows very little processing during 11 idle frames and a big chunk of processing (21 ms) every 12th frame, the second sysprof trace shows how the distribution of load looks. In that case, the amount of work during 11 idle frames is much bigger (yet manageable), but at the same time, the formerly big chunk of processing every 12th frame is reduced almost 2 times (to 11 ms). Therefore, the overall frame rate in the application is much better.

Results

Despite the above improvement speaking for itself, it’s worth summarizing the improvement with the benchmarking results of the above demos obtained from the NXP i.MX8M Plus and presenting the application’s average frames per second (FPS):

Benchmarking results.

Clearly, the positive impact of pre-rendering can be substantial depending on the conditions. In practice, when the rendered DOM fragment is more complex, the trick such as above can yield even better results. However, due to how tiling works, the effect can be minimized if the content to be pre-rendered spans multiple tiles. In that case, the browser may defer rasterization until the tiles are actually needed. Therefore, the above needs to be used with care and always with proper benchmarking.

Conclusions #

As demonstrated in the above sections, when it comes to pre-rendering the contents to distribute the web application workload over time, the web platform gives both the official APIs to do it, as well as unofficial means through purposeful misuse of APIs and exploitation of browser engine implementations. While this article hasn’t covered all the possibilities available, the above should serve as a good initial read with some easy-to-try solutions that may yield surprisingly good results. However, as some of the ideas mentioned above are very much browser-engine-specific, they should be used with extra care and with the limitations (lack of portability) in mind.

As the web platform constantly evolves, the pool of pre-rendering techniques and tricks should keep evolving as well. Also, as more and more web applications are used on embedded devices, more pressure should be put on the specification, which should yield more APIs targeting the low-end devices in the future. With that in mind, it’s recommended for the readers to stay up-to-date with the latest specification and perhaps even to get involved if some interesting use cases would be worth introducing new APIs.

December 19, 2025 12:00 AM

December 15, 2025

Igalia WebKit Team: WebKit Igalia Periodical #51

Igalia WebKit

Update on what happened in WebKit in the week from December 8 to December 15.

In this end-of-year special have a new GMallocString helper that makes management of malloc-based strings more efficient, development releases, and a handful of advancements on JSC's implementation of Temporal, in particular the PlainYearMonth class.

Cross-Port 🐱

Added GMallocString class to WTF to adopt UTF8 C strings and make them WebKit first class citizens efficiently (no copies). Applied in GStreamer code together with other improvements by using CStringView. Fixed other two bugs about string management.

JavaScriptCore 🐟

The built-in JavaScript/ECMAScript engine for WebKit, also known as JSC or SquirrelFish.

Releases 📦️

Development releases of WebKitGTK 2.51.3 and WPE WebKit 2.51.3 are now available. These include a number of API additions and new features, and are intended to allow interested parties to test those in advance, prior to the next stable release series. As usual, bug reports are welcome in Bugzilla.

That’s all for this week!

By Igalia WebKit Team at December 15, 2025 07:58 PM

December 08, 2025

Igalia WebKit Team: WebKit Igalia Periodical #50

Igalia WebKit

Update on what happened in WebKit in the week from December 1 to December 8.

In this edition of the periodical we have further advancements on the Temporal implementation, support for Vivante super-tiled format, and an adaptation of the DMA-BUF formats code to the Android port.

Cross-Port 🐱

JavaScriptCore 🐟

The built-in JavaScript/ECMAScript engine for WebKit, also known as JSC or SquirrelFish.

Implemented the toString, toJSON, and toLocaleString methods for PlainYearMonth objects in JavaScriptCore's implementation of Temporal.

Graphics 🖼️

BitmapTexture and TextureMapper were prepared to handle textures where the logical size (e.g. 100×100) differs from the allocated size (e.g. 128×128) due to alignment requirements. This allowed to add support for using memory-mapped GPU buffers in the Vivante super-tiled format available on i.MX platforms. Set WEBKIT_SKIA_USE_VIVANTE_SUPER_TILED_TILE_TEXTURES=1 to activate at runtime.

WPE WebKit 📟

WPE Platform API 🧩

New, modern platform API that supersedes usage of libwpe and WPE backends.

The WPEBufferDMABufFormats class has been renamed to WPEBufferFormats, as it can be used in situations where mechanisms other than DMA-BUF may be used for buffer sharing—on Android targets AHardwareBuffer is used instead, for example. The naming change involved also WPEBufferFormatsBuilder (renamed from WPEBufferDMABufFormatsBuilder), and methods and signals in other classes that use these types. Other than the renames, there is no change in functionality.

That’s all for this week!

By Igalia WebKit Team at December 08, 2025 08:26 PM

December 05, 2025

Enrique Ocaña: Meow: Process log text files as if you could make cat speak

Igalia WebKit

Some years ago I had mentioned some command line tools I used to analyze and find useful information on GStreamer logs. I’ve been using them consistently along all these years, but some weeks ago I thought about unifying them in a single tool that could provide more flexibility in the mid term, and also as an excuse to unrust my Rust knowledge a bit. That’s how I wrote Meow, a tool to make cat speak (that is, to provide meaningful information).

The idea is that you can cat a file through meow and apply the filters, like this:

cat /tmp/log.txt | meow appsinknewsample n:V0 n:video ht: \
ft:-0:00:21.466607596 's:#([A-za-z][A-Za-z]*/)*#'

which means “select those lines that contain appsinknewsample (with case insensitive matching), but don’t contain V0 nor video (that is, by exclusion, only that contain audio, probably because we’ve analyzed both and realized that we should focus on audio for our specific problem), highlight the different thread ids, only show those lines with timestamp lower than 21.46 sec, and change strings like Source/WebCore/platform/graphics/gstreamer/mse/AppendPipeline.cpp to become just AppendPipeline.cpp“, to get an output as shown in this terminal screenshot:

Screenshot of a terminal output showing multiple log lines. Some of them have the word

Cool, isn’t it? After all, I’m convinced that the answer to any GStreamer bug is always hidden in the logs (or will be, as soon as I add “just a couple of log lines more, bro🤭).

Currently, meow supports this set of manipulation commands:

  • Word filter and highlighting by regular expression (fc:REGEX, or just REGEX): Every expression will highlight its matched words in a different color.
  • Filtering without highlighting (fn:REGEX): Same as fc:, but without highlighting the matched string. This is useful for those times when you want to match lines that have two expressions (E1, E2) but the highlighting would pollute the line too much. In those case you can use a regex such as E1.*E2 and then highlight the subexpressions manually later with an h: rule.
  • Negative filter (n:REGEX): Selects only the lines that don’t match the regex filter. No highlighting.
  • Highlight with no filter (h:REGEX): Doesn’t discard any line, just highlights the specified regex.
  • Substitution (s:/REGEX/REPLACE): Replaces one pattern for another. Any other delimiter character can be used instead of /, it that’s more convenient to the user (for instance, using # when dealing with expressions to manipulate paths).
  • Time filter (ft:TIME-TIME): Assuming the lines start with a GStreamer log timestamp, this filter selects only the lines between the target start and end time. Any of the time arguments (or both) can be omitted, but the - delimiter must be present. Specifying multiple time filters will generate matches that fit on any of the time ranges, but overlapping ranges can trigger undefined behaviour.
  • Highlight threads (ht:): Assuming a GStreamer log, where the thread id appears as the third word in the line, highlights each thread in a different color.

The REGEX pattern is a regular expression. All the matches are case insensitive. When used for substitutions, capture groups can be defined as (?CAPTURE_NAMEREGEX).

The REPLACEment string is the text that the REGEX will be replaced by when doing substitutions. Text captured by a named capture group can be referred to by ${CAPTURE_NAME}.

The TIME pattern can be any sequence of numbers, : or . . Typically, it will be a GStreamer timestamp (eg: 0:01:10.881123150), but it can actually be any other numerical sequence. Times are compared lexicographically, so it’s important that all of them have the same string length.

The filtering algorithm has a custom set of priorities for operations, so that they get executed in an intuitive order. For instance, a sequence of filter matching expressions (fc:, fn:) will have the same priority (that is, any of them will let a text line pass if it matches, not forbidding any of the lines already allowed by sibling expressions), while a negative filter will only be applied on the results left by the sequence of filters before it. Substitutions will be applied at their specific position (not before or after), and will therefore modify the line in a way that can alter the matching of subsequent filters. In general, the user doesn’t have to worry about any of this, because the rules are designed to generate the result that you would expect.

Now some practical examples:

Example 1: Select lines with the word “one”, or the word “orange”, or a number, highlighting each pattern in a different color except the number, which will have no color:

$ cat file.txt | meow one fc:orange 'fn:[0-9][0-9]*'
000 one small orange
005 one big orange

Example 2: Assuming a pictures filename listing, select filenames not ending in “jpg” nor in “jpeg”, and rename the filename to “.bak”, preserving the extension at the end:

$ cat list.txt | meow 'n:jpe?g' \
   's:#^(?<f>[^.]*)(?<e>[.].*)$#${f}.bak${e}'
train.bak.png
sunset.bak.gif

Example 3: Only print the log lines with times between 0:00:24.787450146 and 0:00:24.790741865 or those at 0:00:30.492576587 or after, and highlight every thread in a different color:

$ cat log.txt | meow ft:0:00:24.787450146-0:00:24.790741865 \
 
  ft:0:00:30.492576587- ht:
0:00:24.787450146 739 0x1ee2320 DEBUG …
0:00:24.790382735 739 0x1f01598 INFO …
0:00:24.790741865 739 0x1ee2320 DEBUG …
0:00:30.492576587 739 0x1f01598 DEBUG …
0:00:31.938743646 739 0x1f01598 ERROR …

This is only the begining. I have great ideas for this new tool (as time allows), such as support for parenthesis (so the expressions can be grouped), or call stack indentation on logs generated by tracers, in a similar way to what Alicia’s gst-log-indent-tracers tool does. I might also predefine some common expressions to use in regular expressions, such as the ones to match paths (so that the user doesn’t have to think about them and reinvent the wheel every time). Anyway, these are only ideas. Only time and hyperfocus slots will tell…

By now, you can find the source code on my github. Meow!

By eocanha at December 05, 2025 11:16 AM