November 17, 2025

Igalia WebKit Team: WebKit Igalia Periodical #47

Igalia WebKit

Update on what happened in WebKit in the week from November 10 to November 17.

This week's update is composed of a new CStringView internal API, more MathML progress with the implementation of the "scriptlevel" attribute, the removal of the Flatpak-based SDK, and the maintanance update of WPEBackend-fdo.

Cross-Port 🐱

Implement the MathML scriptlevel attribute using math-depth.

Finished implementing CStringView, which is a wrapper around UTF8 C strings. It allows you to recover the string without making any copies and perform string operations safely by taking into account the encoding at compile time.

Releases 📦️

WPEBackend-fdo 1.16.1 has been released. This is a maintenance update which adds compatibility with newer Mesa versions.

Infrastructure 🏗️

Most of the Flatpak-based SDK was removed. Developers are warmly encouraged to use the new SDK for their contributions to the Linux ports, this SDK has been successfully deployed on EWS and post-commits bots.

That’s all for this week!

By Igalia WebKit Team at November 17, 2025 09:22 PM

November 12, 2025

Release Notes for Safari Technology Preview 232

Surfin’ Safari

Safari Technology Preview Release 232 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: 301766@main…302449@main.

CSS

New Features

  • Added support for allowing positioned boxes in scrollable containing blocks to overflow along scrollable directions. (302259@main) (162722820)
  • Added support for flip-x and flip-y options in position-try-fallback for CSS Anchor Positioning. (302057@main) (163282036)

Resolved Issues

  • Fixed handling of padding and margins for flex and grid layouts across all writing modes. (301814@main) (71046552)
  • Fixed getComputedStyle("top") to correctly resolve percentage values for absolutely positioned elements inside inline containers. (302090@main) (161390162)
  • Fixed an infinite style resolution loop when a position-try box was inside a display: none subtree. (302254@main) (161570947)
  • Fixed width, height, min-width, min-height, max-width and max-height to apply CSS zoom at used-value time. (302241@main) (161848512)
  • Fixed CSS zoom to scale <iframe> element contents. (302097@main) (162314059)
  • Fixed getBoundingClientRect and getClientRects to return scaled lengths according to CSS zoom instead of unscaled values, aligning with the CSS Viewport specification. (301806@main). (162325730)
  • Fixed scrolling behavior so that scrollRectToVisible() can bring fixed anchor-positioned boxes outside the viewport into view, improving keyboard navigation. (302368@main) (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. (301793@main) (162637501)
  • Fixed top, left, right, and bottom to apply CSS zoom at used-value time (302102@main) (162663056)
  • Fixed margin to apply CSS zoom at used-value time. (301965@main) (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. (301968@main) (163141549)
  • Fixed an issue where calc(em) values for unzoomed properties were incorrectly adjusted. (302041@main) (163267333)
  • Fixed position-area normal alignment to correctly align toward the non-auto inset when only one inset is auto. (302299@main) (163317238)
  • Fixed incorrect underline positioning for text-decoration when inline box sides are trimmed. (302435@main) (163858721)

JavaScript

Resolved Issues

  • Fixed Intl.DateTimeFormat to throw a RangeError for legacy non-IANA timezones, aligning behavior with TC39 standards. (302447@main) (156857252)

Media

Resolved Issues

  • Fixed: Aligned with other browsers by dispatching enter and exit events on TextTrackCue and VTTCue with no track. (301836@main) (160195643)
  • Fixed an issue where the mute button disappeared in macOS inline videos with adjustable sizes. (301896@main) (162897286)

Rendering

Resolved Issues

  • Fixed over-aggressive clipping of child layers in multicolumn layouts to prevent visual overflow issues with position: relative elements and transform:scale() text. (302249@main) (126413036)
  • Fixed unreadable Scroll-to-Text-Fragment highlights on dark pages. (301930@main) (126539910)
  • Fixed an issue where positioned, transformed, or opacity-altered <img> elements with HDR JPEG gainmaps would incorrectly render in SDR. (302200@main) (156858374)

SVG

Resolved Issues

  • Fixed animation of the stop-color attribute on <stop> elements.(302163@main) (109823555)

Storage

Resolved Issues

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

Web API

Resolved Issues

  • Fixed event ordering and committed promise timing for intercepted Navigation API traverse navigations. (302418@main) (161445256)
  • Fixed the processing order of Trusted Types for DOM attribute setting. (302272@main) (162143148)
  • Fixed NavigateEvent to correctly fire an AbortSignal when a navigation is aborted. (302591@main) (163957784)
  • Fixed NavigateEvent.sourceElement to correctly reference elements from different browsing contexts. (302504@main) (163962362)

Web Inspector

Resolved Issues

  • Fixed an issue where CSS properties added to new rules were not applied and were marked as invalid. (302301@main) (103548968)
  • Fixed an issue in the Console where the count of identical consecutive messages could be wrong. (301917@main) (162612099)

November 12, 2025 10:23 PM

November 10, 2025

Igalia WebKit Team: WebKit Igalia Periodical #46

Igalia WebKit

Update on what happened in WebKit in the week from November 3 to November 10.

This week brought a hodgepodge of fixes in Temporal and multimedia, a small addition to the public API in preparation for future work, plus advances in WebExtensions, WebXR, and Android support.

Cross-Port 🐱

The platform-independent part of the WebXR Hit Test Module has been implemented. The rest, including the FakeXRDevice mock implementation used for testing will be done later.

On the WebExtensions front, parts of the WebExtensionCallbackHandler code have been rewritten to use more C++ constructs and helper functions, in preparation to share more code among the different WebKit ports.

A new WebKitImage utility class landed this week. This image abstraction is one of the steps towards delivering a new improved API for page favicons, and it is also expected to be useful for the WebExtensions work, and to enable the webkit_web_view_get_snapshot() API for the WPE port.

Multimedia 🎥

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

Videos with BT2100-PQ colorspace are now tone-mapped to SDR in WebKit's compositor, ensuring colours do not appear washed out.

Lots of deadlock fixes this week, one among many in the MediaStream GStreamer source element.

Video frame rendering to WebGL was fixed. Another pending improvement is GPU-to-GPU texture copies, which might be coming soon.

JavaScriptCore 🐟

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

JavaScriptCore's implementation of Temporal received a number of improvements this week:

  • Fixed a bug that would cause wrong results when adding a duration with a very large microseconds or nanoseconds value to a PlainTime.

  • Fixed a rounding bug of Instant values.

  • Fixed a bug that resulted in incorrect printing of certain Instant values before the Epoch.

  • Fixed a bug that resulted in wrong results instead of exceptions when a date addition operation would result in an out-of-range date.

WPE WebKit 📟

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

One of the last pieces needed to have the WPEPlatform API working on Android has been merged: a custom platform EGL display implementation, and enabling the default display as fallback.

Community & Events 🤝

The dates for the next Web Engines Hackfest have been announced: it will take place from Monday, June 15th to Wednesday, June 17th. As it has been the case in the last years, it will be possible to attend both on-site, and remotely for those who cannot to travel to A Coruña.

The video recording for Adrian Pérez's “WPE Android 🤖 State of the Bot” talk from this year's edition of the WebKit Contributors' Meeting has been published. This was an update on what the Igalia WebKit team has been done during the last year to improve WPE WebKit on Android, and what is coming up next.

That’s all for this week!

By Igalia WebKit Team at November 10, 2025 11:04 PM

November 03, 2025

Igalia WebKit Team: WebKit Igalia Periodical #45

Igalia WebKit

Update on what happened in WebKit in the week from October 27 to November 3.

A calmer week this time! This week we have the GTK and WPE ports implementing the RunLoopObserver infrastructure, which enables more sophisticated scheduling in WebKit Linux ports, as well as more information in webkit://gpu. On the Trusted Types front, the timing of check was changed to align with spec changes.

Cross-Port 🐱

Implemented the RunLoopObserver infrastructure for GTK and WPE ports, a critical piece of technology previously exclusive to Apple ports that enables sophisticated scheduling features like OpportunisticTaskScheduler for optimal garbage collection timing.

The implementation refactored the GLib run loop to notify clients about activity-state transitions (BeforeWaiting, Entry, Exit, AfterWaiting), then moved from timer-based to observer-based layer flushing for more precise control over rendering updates. Finally support was added to support cross-thread scheduling of RunLoopObservers, allowing the ThreadedCompositor to use them, enabling deterministic composition notifications across thread boundaries.

Changed timing of Trusted Types checks within DOM attribute handling to align with spec changes.

Graphics 🖼️

The webkit://gpu page now shows more information like the list of preferred buffer formats, the list of supported buffer formats, threaded rendering information, number of MSAA samples, view size, and toplevel state.

It is also now possible to make the page autorefresh every the given amount of seconds by passing a ?refresh=<seconds> parameter in the URL.

That’s all for this week!

By Igalia WebKit Team at November 03, 2025 07:16 PM

WebKit Features for Safari 26.1

Surfin’ Safari

Today, Safari 26.1 is available with iOS 26.1, iPadOS 26.1, macOS Sequoia 26.1 and visionOS 26.1, as well as for macOS Sequoia and macOS Sonoma. It contains 2 new features and 36 improvements to existing features.

Relative units in SVG

As part of our most recent efforts on quality, WebKit for Safari 26.1 refactors the way the rendering engine handles CSS Units. This results in giving the SVG engine easier access to relative units, adding support for certain units inside SVG files for the first time. These units include rem; viewport units: vh, vw, vmin, vmax; typography-relative units: rlh, ic, cap, and container query units: cqw, cqi, cqmin, cqmax.

Anchor Positioning improvements

Safari 26.1 includes a dozen improvements to CSS Anchor Positioning. Now WebKit remembers the last successful position-try fallback in CSS anchor positioning to reduce layout jumps when styles change. See below for the full list of changes to Anchor Positioning.

Bug fixes and more

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

Accessibility

  • Fixed hit testing for scrolled iframe content by adjusting for the frame’s scroll position, ensuring accurate element detection across assistive technologies. (158233884)
  • Fixed an issue where VoiceOver reports the wrong radio count with a dynamically inserted radio option. (159937173)
  • Fixed exposing content within dynamically expanded details elements in the accessibility tree. (159937257)
  • Fixed the target of aria-labelledby not updating its accessibility label after dynamic innerHTML change in the label. (160691619)

CSS Anchor Positioning

  • Fixed anchor positioning to handle fragmented multicolumn flows. (156958568)
  • Fixed anchor positioning fallbacks to respond to scrolling. (158451016)
  • Fixed an issue where container queries doesn’t work with position-try element. (158880410)
  • Fixed anchor positioning to account for a left-hand scrollbar in right-to-left and vertical-rl containing blocks. (160723993)
  • Fixed handling inline containing blocks for CSS Anchor Positioning. (160892829)
  • Fixed an issue where anchor-positioned elements failed to update their position when the default anchor changed. (160892850)
  • Fixed an issue where transitioning an element to display: none with transition-behavior: allow-discrete and CSS Anchor Positioning would repeatedly restart the transition. (161421046)
  • Fixed position-area for the initial containing block to include the in-flow scrollable area, improving alignment for typical overflow cases. (161997622)
  • Fixed position-visibility: anchors-visible visibility heuristic when anchor is clipped by an ancestor container. (160060564)

CSS

  • Fixed @media print styles to work when used inside a nested rule. (158608834)
  • Fixed: Improved the performance of :has(> .changed) .subject selectors. (159257003)
  • Fixed pseudo-class invalidation performance by creating separate RuleSets for attribute selectors like :has([attr=value]) to avoid using universal invalidation. (159257022)
  • Fixed an issue where changing the ruby-overhang property did not trigger a layout update, ensuring proper rendering when overhang values change. (159573050)
  • Fixed offsetParent to return the fixed-position element’s containing block when it is not the viewport, such as a transformed element. (160892685)
  • Fixed <select> fallback styling by removing the outdated background and updating the dropdown arrow. (161104364)

Forms

  • Fixed native text inputs so that their background colors update when autofilled. (159014135)
  • Fixed checkboxes and radio buttons missing borders in the filled state when “Increased Contrast” is enabled on macOS. (159379948)

PDF

  • Fixed VoiceOver not recognizing the password form in encrypted documents. (159240531)

Rendering

  • Fixed a bottom gap appearing on layouts with viewport-sized fixed containers on iOS. (158055568)
  • Fixed an issue on iOS where Safari extension popups and some websites could scroll or flicker unexpectedly. (160216319)
  • Fixed list markers overlapping text in certain situations. (160892820)
  • Fixed an issue that caused cropped flexbox elements to render incorrectly. (161218029)
  • Fixed string search freezing when subject has large number (>1000). (161421015)

SVG

  • Fixed absolutely positioned SVGs so that their size correctly accounts for the padding of the containing block when the SVG root is out-of-flow. (160727702)

Security

  • Fixed Safari ignoring the style-src-elem Content Security Policy directive ensuring it is checked before falling back to style-src, in line with CSP3 specifications. (157298407)

Web API

  • Fixed an issue on iOS 26 where pressing the B button on a gamepad could make a page appear to lose gamepad focus by bypassing the system’s automatic navigation behavior. (159125095)

Web Inspector

  • Fixed issue where searching on certain text fails to find matches. (159897282)

WebGPU

  • Fixed an issue where GPUQueue.copyExternalImageToTexture could not handle SVG images. (158442476)
  • Fixed an issue where video playback using the WebGPU renderer in WebCodecs could display a black screen. (158442539)
  • Fixed an issue where WebGPU video textures failed to load in Three.js panoramas. (159918934)

WebKit API

  • Fixed a crash when an app uses WKWebView::loadRequest API on background threads. (162225842)

WebRTC

  • Fixed getUserMedia() on iOS incorrectly firing devicechange events when there was no actual change to available microphones or default devices. (157693528)

Feedback

We love hearing from you. To share your thoughts, find our web evangelists 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 feedback on Safari UI (learn more about filing Feedback), or your WebKit bug report about web technologies or Web Inspector. If you run into a website that isn’t working as expected, please file a report at webcompat.com. Filing issues really does make a difference.

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

November 03, 2025 09:00 AM

October 30, 2025

Release Notes for Safari Technology Preview 231

Surfin’ Safari

Safari Technology Preview Release 231 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: 300987@main…301765@main.

CSS

New Features

  • Added support for the safe keyword with anchor-center in CSS Anchor Positioning. (301301@main) (155767796)
  • Added support for flip-x and flip-y options in position-try-fallback for CSS Anchor Positioning. (302057@main) (163282036)

Resolved Issues

  • Fixed handling of padding and margins for flex and grid layouts across all writing modes. (301814@main) (71046552)
  • Fixed position-visibility: no-overflow to respond correctly to scrolling. (301211@main) (162173481)
  • Fixed: Renamed position-area keywords from x-self-start, x-self-end, y-self-start, and y-self-end to self-x-start, self-x-end, self-y-start, and self-y-end respectively to align with updated CSSWG specifications.(301226@main) (162214793)
  • Fixed <iframe> elements so their content correctly respects the page’s usedZoom(). (302097@main) (162314059)
  • Fixed auto margins by converting them to zero when position-area or anchor-center is applied in CSS Anchor Positioning. (301662@main) (162809291)

JavaScript

Resolved Issues

  • Fixed TypeError messages when calling class or function constructors without new to include the constructor name. (301023@main) (161152354)

Media

Resolved Issues

  • Fixed an issue where custom WebVTT caption text size settings did not propagate to cue child elements by moving the font-size definition into the cue’s shared <style> block. (301681@main) (162547969)

Rendering

New Features

  • Added support for text shaping across inline boxes. (301354@main) (162430932)

Resolved Issues

  • Fixed an issue where selecting table cells could cause overlapping selections in flex and grid layouts. (294464@main) (160805174)
  • Fixed a performance issue on layouts with long pre blocks and word-break: break-all by including whitespace in overflow width calculations. (301657@main) (162695099)
  • Fixed Largest Contentful Paint to optimize text paints by performing an early area comparison when an element has only one text box. (301895@main) (163067611)
  • Fixed Largest Contentful Paint to skip tracking loadTime for data URI images. (301988@main) (163213487)
  • Fixed how Largest Contentful Paint performs area checks for text nodes, optimizing calculations when all rects are collected and ancestor transforms are absent. (302072@main) (163285757)

Web API

Resolved Issues

  • Fixed Navigation API WPT tests failing due to a WebDriver error. (161199777)
  • Fixed PerformanceEventTiming so that keydown and pointerdown entries no longer wait for their corresponding keyup or pointerup events before assigning a duration, preventing durations from appearing too long. (302107@main) (161911473)
  • Fixed attachShadow() to use the global custom element registry by default when customElementRegistry is null, aligning with the WHATWG DOM specification (300996@main). (161949493)
  • Fixed navigate() with { history: "replace" } to correctly update the current History item instead of adding a new one during same-document navigations. (302130@main) (163323288)

Web Inspector

Resolved Issues

  • Fixed an issue where the Sources tab won’t show contents of a script that contains a for statement with optional chaining in the test condition. (301197@main) (160617913)

WebDriver

Resolved Issues

  • Fixed an issue where element references nested inside Array or Object arguments were not properly extracted when executing scripts. (301445@main) (162571946)

October 30, 2025 07:19 PM

October 28, 2025

Igalia WebKit Team: WebKit Igalia Periodical #44

Igalia WebKit

Update on what happened in WebKit in the week from October 21 to October 28.

This week has again seen a spike in activity related to WebXR and graphics performance improvements. Additionally, we got in some MathML additions, a fix for hue interpolation, a fix for WebDriver screenshots, development releases, and a blog post about memory profiling.

Cross-Port 🐱

Support for WebXR Layers has seen the very first changes needed to have them working on WebKit. This is expected to take time to complete, but should bring improvements in performance, rendering quality, latency, and power consumption down the road.

Work has started on the WebXR Hit Test Module, which will allow WebXR experiences to check for real world surfaces. The JavaScript API bindings were added, followed by an initial XRRay implementation. More work is needed to actually provide data from device sensors.

Now that the WebXR implementation used for the GTK and WPE ports is closer to the Cocoa ones, it was possible to unify the code used to handle opaque buffers.

Implemented the text-transform: math-auto CSS property, which replaces the legacy mathvariant system and is used to make identifiers italic in MathML Core.

Implemented the math-depth CSS extension from MathML Core.

Graphics 🖼️

The hue interpolation method for gradients has been fixed. This is expected to be part of the upcoming 2.50.2 stable release.

Usage of Multi-Sample Antialiasing (MSAA) has been enabled when using GPU rendering, and then further changed to use dynamic MSAA to improve performance.

Paths that contain a single arc, oval, or line have been changed to use a specialized code path, resulting in improved performance.

WebGL content rendering will be handled by a new isolated process (dubbed “GPU Process”) by default. This is the first step towards moving more graphics processing out of the process that handles processing Web content (the “Web Process”), which will result in increased resilience against buggy graphics drivers and certain kinds of malicious content.

The internal webkit://gpu page has been improved to also display information about the graphics configuration used in the rendering process.

WPE WebKit 📟

WPE Platform API 🧩

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

The new WPE Platform, when using Skia (the default), now takes WebDriver screenshots in the UI Process, using the final assembled frame that was sent to the system compositor. This fixes the issues of some operations like 3D CSS animations that were not correctly captured in screenshots.

Releases 📦️

The first development releases for the current development cycle have been published: WebKitGTK 2.51.1 and WPE WebKit 2.51.1. These are intended to let third parties test upcoming features and improvements and as such bug reports for those are particularly welcome in Bugzilla. We are particularly interested in reports related to WebGL, now that it is handled in an isolated process.

Community & Events 🤝

Paweł Lampe has published a blog post that discusses GTK/WPE WebKit memory profiling using industry-standard tools and a built-in "Malloc Heap Breakdown" WebKit feature.

That’s all for this week!

By Igalia WebKit Team at October 28, 2025 02:31 PM

October 24, 2025

Pawel Lampe: Tracking WebKit's memory allocations with Malloc Heap Breakdown

Igalia WebKit

One of the main constraints that embedded platforms impose on the browsers is a very limited memory. Combined with the fact that embedded web applications tend to run actively for days, weeks, or even longer, it’s not hard to imagine how important the proper memory management within the browser engine is in such use cases. In fact, WebKit and WPE in particular receive numerous memory-related fixes and improvements every year. Before making any changes, however, the areas to fix/improve need to be narrowed down first. Like any C++ application, WebKit memory can be profiled using a variety of industry-standard tools. Although such well-known tools are really useful in the majority of use cases, they have their limits that manifest themselves when applied on production-grade embedded systems in conjunction with long-running web applications. In such cases, a very useful tool is a debug-only feature of WebKit itself called malloc heap breakdown, which this article describes.

Industry-standard memory profilers #

When it comes to profiling memory of applications on linux systems, the 2 outstanding tools used usually are Massif (Valgrind) and Heaptrack.

Massif (Valgrind) #

Massif is a heap profiler that comes as part of the Valgrind suite. As its documentation states:

It measures how much heap memory your program uses. This includes both the useful space, and the extra bytes allocated for book-keeping and alignment purposes. It can also measure the size of your program’s stack(s), although it does not do so by default.

Using Massif with WebKit is very straightforward and boils down to a single command:

Malloc=1 valgrind --tool=massif --trace-children=yes WebKitBuild/GTK/Debug/bin/MiniBrowser '<URL>'
  • The Malloc=1 environment variable set above is necessary to instruct WebKit to enable debug heaps that use the system malloc allocator.

Given some results are generated, the memory usage over time can be visualized using massif-visualizer utility. An example of such a visualization is presented in the image below:

TODO.

While Massif has been widely adopted and used for many years now, from the very beginning, it suffered from a few significant downsides.

First of all, the way Massif instruments the profiled application introduces significant overhead that may slow down the application up to 2 orders of magnitude. In some cases, such overhead makes it simply unusable.

The other important problem is that Massif is snapshot-based, and hence, the level of detail is not ideal.

Heaptrack #

Heaptrack is a modern heap profiler developed as part of KDE. The below is its description from the git repository:

Heaptrack traces all memory allocations and annotates these events with stack traces. Dedicated analysis tools then allow you to interpret the heap memory profile to:

  • find hotspots that need to be optimized to reduce the memory footprint of your application
  • find memory leaks, i.e. locations that allocate memory which is never deallocated
  • find allocation hotspots, i.e. code locations that trigger a lot of memory allocation calls
  • find temporary allocations, which are allocations that are directly followed by their deallocation

At first glance, Heaptrack resembles Massif. However, a closer look at the architecture and features shows that it’s much more than the latter. While it’s fair to say it’s a bit similar, in fact, it is a significant progression.

Usage of Heaptrack to profile WebKit is also very simple. At the moment of writing, the most suitable way to use it is to attach to a certain running WebKit process using the following command:

heaptrack -p <PID>

while the WebKit needs to be run with system malloc, just like in Massif case:

WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1 Malloc=1 WebKitBuild/GTK/Debug/bin/MiniBrowser '<URL>'
  • If profiling of e.g. web content process startup is essential, it’s then recommended also to use WEBKIT2_PAUSE_WEB_PROCESS_ON_LAUNCH=1, which adds 30s delay to the process startup.

When the profiling session is done, the analysis of the recordings is done using:

heaptrack --analyze <RECORDING>

The utility opened with the above, shows various things, such as the memory consumption over time:

TODO.

flame graphs of memory allocations with respect to certain functions in the code:

TODO.

etc.

As Heaptrack records every allocation and deallocation, the data it gathers is very precise and full of details, especially when accompanied by stack traces arranged into flame graphs. Also, as Heaptrack does instrumentation differently than e.g. Massif, it’s usually much faster in the sense that it slows down the profiled application only up to 1 order of magnitude.

Shortcomings on embedded systems #

Although the memory profilers such as above are really great for everyday use, their limitations on embedded platforms are:

  • they significantly slow down the profiled application — especially on low-end devices,
  • they effectively cannot be run for a longer period of time such as days or weeks, due to memory consumption,
  • they are not always provided in the images — and hence require additional setup,
  • they may not be buildable out of the box on certain architectures — thus requiring extra patching.

While the above limitations are not always a problem, usually at least one of them is. What’s worse, usually at least one of the limitations turns into a blocking problem. For example, if the target device is very short on memory, it may be basically impossible to run anything extra beyond the browser. Another example could be a situation where the application slowdown due to the profiler usage, leads to different application behavior, such as a problem that originally reproduced 100% of the time, does not reproduce anymore etc.

Malloc heap breakdown in WebKit #

Profiling the memory of WebKit while addressing the above problems points towards a solution that does not involve any extra tools, i.e. instrumenting WebKit itself. Normally, adding such an instrumentation to the C++ application means a lot of work. Fortunately, in the case of WebKit, all that work is already done and can be easily enabled by using the Malloc heap breakdown.

In a nutshell, Malloc heap breakdown is a debug-only feature that enables memory allocation tracking within WebKit itself. Since it’s built into WebKit, it’s very lightweight and very easy to build, as it’s just about setting the ENABLE_MALLOC_HEAP_BREAKDOWN build option. Internally, when the feature is enabled, WebKit switches to using debug heaps that use system malloc along with the malloc zone API to mark objects of certain classes as belonging to different heap zones and thus allowing one to track the allocation sizes of such zones.

As the malloc zone API is specific to BSD-like OSes, the actual implementations (and usages) in WebKit have to be considered separately for Apple and non-Apple ports.

Malloc heap breakdown on Apple ports #

Malloc heap breakdown was originally designed only with Apple ports in mind, with the reason being twofold:

  1. The malloc zone API is provided virtually by all platforms that Apple ports integrate with.
  2. MacOS platforms provide a great utility called footprint that allows one to inspect per-zone memory statistics for a given process.

Given the above, usage of malloc heap breakdown with Apple ports is very smooth and as simple as building WebKit with the ENABLE_MALLOC_HEAP_BREAKDOWN build option and running on macOS while using the footprint utility:

Footprint is a macOS specific tool that allows the developer to check memory usage across regions.

For more details, one should refer to the official documentation page.

Malloc heap breakdown on non-Apple ports #

Since all of the non-Apple WebKit ports are mostly being built and run on non-BSD-like systems, it’s safe to assume the malloc zone API is not offered to such ports by the system itself. Because of the above, for many years, malloc heap breakdown was only available for Apple ports.

Fortunately, with the changes introduced in 2025, such as: 294667@main (+ fix 294848@main), 301702@main, and improvements such as: 294848@main, 299555@main, 301695@main, 301709@main, 301712@main, 301839@main, 301861@main, the malloc heap breakdown integrates also with non-Apple ports and is stable as of main@a235408c2b4eb12216d519e996f70828b9a45e19.

The idea behind the integration for non-Apple ports is to provide a simple WebKit-internal library that provides a fake <malloc/malloc.h> header along with simple implementation that provides malloc_zone_*() function implementations as proxy calls to malloc(), calloc(), realloc() etc. along with a tracking mechanism that keeps references to memory chunks. Such an approach gathers all the information needed to be reported later on.

At the moment of writing, the above allows 2 methods of reporting the memory usage statistics periodically:

  • printing to standard output,
  • reporting to sysprof as counters.
Periodic reporting to standard output

By default, when WebKit is built with ENABLE_MALLOC_HEAP_BREAKDOWN, the heap breakdown is printed to the standard output every few seconds for each process. That can be tweaked by setting WEBKIT_MALLOC_HEAP_BREAKDOWN_LOG_INTERVAL=<SECONDS> environment variable.

The results have a structure similar to the one below:

402339 MHB: | PID | "Zone name" | #chunks | #bytes | {
402339 "ExecutableMemoryHandle" 2 32
402339 "AssemblerData" 1 192
402339 "VectorBuffer" 37 16184
402339 "StringImpl" 103 5146
402339 "WeakPtrImplBase" 17 272
402339 "HashTable" 37 9408
402339 "Vector" 1 16
402339 "EmbeddedFixedVector" 1 32
402339 "BloomFilter" 2 65536
402339 "CStringBuffer" 3 86
402339 "Default Zone" 0 0
402339 } MHB: grand total bytes allocated: 9690

Given the allocation statistics per-zone, it’s easy to narrow down the unusual usage patterns manually. The example of a successful investigation is presented in the image below:

TODO.

Moreover, the data presented can be processed either manually or using scripts to create memory usage charts that span as long as the application lifetime so e.g. hours (20+ like below), days, or even longer:

TODO.
Periodic reporting to sysprof

The other reporting mechanism currently supported is reporting periodically to sysprof as counters. In short, sysprof is a modern system-wide profiling tool that already integrates with WebKit very well when it comes to non-Apple ports.

The condition for malloc heap breakdown reporting to sysprof is that the WebKit browser needs to be profiled e.g. using:

sysprof-cli -f -- <BROWSER_COMMAND>

and the sysprof has to be in the latest version possible.

With the above, the memory usage statistics can then be inspected using the sysprof utility and look like in the image below:

TODO.

In the case of sysprof, memory statistics in that case are just a minor addition to other powerful features that were well described in this blog post from Georges.

Caveats #

While malloc heap breakdown is very useful in some use cases — especially on embedded systems — there are a few problems with it.

First of all, compilation with -DENABLE_MALLOC_HEAP_BREAKDOWN=ON is not guarded by any continuous integration bots; therefore, the compilation issues are expected on the latest WebKit main. Fortunately, fixing the problems is usually straightforward. For a reference on what may be causing compilation problems usually, one should refer to 299555@main, which contains a full variety of fixes.

The second problem is that malloc heap breakdown uses WebKit’s debug heaps, and hence the memory usage patterns may be different just because system malloc is used.

The third, and final problem, is that malloc heap breakdown integration for non-Apple ports introduces some overhead as the allocations need to lock/unlock the mutex, and as statistics are stored in the memory as well.

Opportunities #

Although malloc heap breakdown can be considered fairly constrained, in the case of non-Apple ports, it gives some additional possibilities that are worth mentioning.

Because on non-Apple ports, the custom library is used to track allocations (as mentioned at the beginning of the Malloc heap breakdown on non-Apple ports section), it’s very easy to add more sophisticated tracking/debugging/reporting capabilities. The only file that requires changes in such a case is: Source/WTF/wtf/malloc_heap_breakdown/main.cpp.

Some examples of custom modifications include:

  • adding different reporting mechanisms — e.g. writing to a file, or to some other tool,
  • reporting memory usage with more details — e.g. reporting the per-memory-chunk statistics,
  • dumping raw memory bytes — e.g. when some allocations are suspicious.
  • altering memory in-place — e.g. to simulate memory corruption.

Summary #

While the presented malloc heap breakdown mechanism is a rather poor approximation of what industry standard tools offer, the main benefit of it is that it’s built into WebKit, and that in some rare use-cases (especially on embedded platforms), it’s the only way to perform any reasonable profiling.

In general, as a rule of thumb, it’s not recommended to use malloc heap breakdown unless all other methods have failed. In that sense, it should be considered a last resort approach. With that in mind, malloc heap breakdown can be seen as a nice mechanism complementing other tools in the toolbox.

October 24, 2025 12:00 AM

October 20, 2025

Igalia WebKit Team: WebKit Igalia Periodical #43

Igalia WebKit

Update on what happened in WebKit in the week from October 13 to October 20.

This week was calmer than previous week but we still had some meaningful updates. We had a Selenium update, improvements to how tile sizes are calculated, and a new Igalian in the list of WebKit committer!

Cross-Port 🐱

Selenium's relative locators are now supported after commit 301445@main. Before, finding elements with locate_with(By.TAG_NAME, "input").above({By.ID: "password"}) could lead to "Unsupported locator strategy" errors.

Graphics 🖼️

A patch landed to compute the layers tile size, using a different strategy depending on whether GPU rendering is enabled, which improved the performance for both GPU and CPU rendering modes.

Community & Events 🤝

Our coworker Philip Chimento gained WebKit committer status!

That’s all for this week!

By Igalia WebKit Team at October 20, 2025 08:35 PM

October 15, 2025

Release Notes for Safari Technology Preview 230

Surfin’ Safari

Safari Technology Preview Release 230 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: 300291@main…300986@main.

Animations

Resolved Issues

  • Fixed animation-name resolution to correctly find matching @keyframes within tree-scoped and shadow DOM contexts. (300706@main) (156484228)

CSS

Resolved Issues

  • Fixed incorrect handling of auto inline margins on grid items during track sizing that caused excessive vertical spacing in subgrids. (300422@main) (157638931)
  • Fixed CSS anchor positioning to remember the last successful position option at ResizeObserver delivery time, aligning with the spec. (300890@main) (159225250)
  • Fixed an issue where transitioning an element to display: none with transition-behavior: allow-discrete and CSS Anchor Positioning would repeatedly restart the transition. (300519@main) (160421419)
  • Fixed the acceptable anchor algorithm in CSS Anchor Positioning to correctly consider inline elements as containing blocks. (300614@main) (160917762)
  • Fixed CSS nesting to inline parent selectors when possible instead of always wrapping them in :is() to improve selector performance. (300297@main) (160927950)
  • Fixed position-try-fallback resolution by treating names as tree-scoped references to properly search shadow DOM host scopes. (300333@main) (161081231)
  • Fixed an issue where a <select> element with long <option> text caused horizontal scrolling when nested inside a flex item. (300684@main) (161563289)
  • Fixed getComputedStyle to return numeric values (2) for orphans and widows instead of the internal auto value, ensuring the computed values correctly reflect the CSS specification. (300690@main) (161566631)
  • Fixed column-count: 1 so that it now correctly creates a multi-column container per the CSS Multi-column Layout specification. (300787@main) (161611444)
  • Fixed the calculation of anchor positions in vertical-rl multi-column layouts by correctly flipping coordinates in fragmented flows. (300807@main) (161616545)
  • Fixed the order to try anchor position fallback options, such that the last successful position option is tried first, followed by the original style, and then the remaining options. (300909@main) (161714637)
  • Fixed position-area handling to include the in-flow scrollable area of the initial containing block. (300921@main) (161741583)
  • Fixed position-visibility: no-overflow to respond correctly to scrolling. (301211@main) (162173481)
  • Fixed: Renamed position-area keywords from x-self-start, x-self-end, y-self-start, and y-self-end to self-x-start, self-x-end, self-y-start, and self-y-end respectively to align with updated CSSWG specifications.(301226@main) (162214793)

HTML

Resolved Issues

  • Fixed an issue where navigating to :~:text fragments on dynamically generated pages did not highlight or scroll to the fragment. (300918@main) (150880542)

MathML

Resolved Issues

  • Fixed rendering of unknown MathML elements so they now behave like mrow as required by the MathML Core specification. (300580@main) (148593275)

Media

Resolved Issues

  • Fixed MediaRecorder to no longer fire erroneous error events when stopped immediately after track changes, aligning behavior with Chrome and closer to Firefox. (300682@main) (161124260)

Rendering

Resolved Issues

  • Fixed incorrect clipping of position:fixed/sticky content during view transitions. (300561@main) (154886047)
  • Fixed an issue that caused cropped flexbox elements to render incorrectly. (300433@main) (159638640)
  • Fixed an issue where sticky elements at the edge of the viewport could disappear during rubber band scrolling. (300544@main) (160385933)
  • Fixed flickering of elements with slow-painting content during view transitions. (300902@main) (160886647)
  • Fixed an issue where elements with both opacity and CSS filter effects could render incorrectly. (300549@main) (161130683)
  • Fixed an issue where elements with background images were not counted as contentful for Paint Timing. (300667@main) (161456094)

SVG

Resolved Issues

  • Fixed an issue where stop-color incorrectly accepted hashless hex color values like 1234 by treating them as invalid to follow the spec. (300296@main) (119166640)
  • Fixed an issue where SVG pattern tileImage could appear blurred or pixelated when zooming or printing. (300357@main) (159202567)
  • Fixed SVGAElement so that its rel and relList attributes now affect navigation behavior, including proper handling of noopener, noreferrer, and the new opener value, aligning SVG links with HTMLAnchorElement behavior. (300462@main) (160724516)

Security

Resolved Issues

  • Fixed parsing of require-trusted-types-for in CSP to ensure 'script' is only valid when followed by whitespace or end of buffer. (300770@main) (147760089)

Web API

New Features

  • Added support for Largest Contentful Paint. (300834@main) (161705604)

Resolved Issues

  • Fixed an issue where the first pointerdown event was lost after triggering a context menu by right-clicking. (300696@main) (84787733)
  • Fixed Trusted Types to only verify event handler attributes for elements in the XHTML, SVG, and MathML namespaces, preventing incorrect checks on other namespaces. (300783@main) (147763139)
  • Fixed an issue where navigate.back in the main frame would fail after a navigate.back in a child frame by properly clearing the provisional history item to allow correct back navigation. (301092@main) (158259024)
  • Fixed NavigateEvent.sourceElement to correctly reference the submitting HTMLFormElement instead of null when a form is submitted. (301252@main) (160391355)
  • Fixed EventCounts interface was not maplike. Enables use of methods such as .forEach(), keys(), and entries(). (300830@main) (160968888)
  • Fixed an issue where mousemove events were still dispatched to removed mouseover targets instead of their parent element when the target was deleted. (300522@main) (161203639)
  • Fixed missing pointerenter and mouseenter events when a child element moved under the mouse. (300564@main) (161362257)
  • Fixed an issue where only one CSP violation report was sent for multiple enforced require-trusted-types-for directives. (300832@main) (161740298)
  • Fixed Trusted Types incorrectly treating null or undefined policy return values as null instead of empty strings during createHTML, createScript, and createScriptURL operations. (300892@main) (161837641)

Web Extension

New Features

  • Added support for browser.runtime.getVersion() to retrieve the extension version from its manifest. (300972@main) (161742137)

Web Inspector

Resolved Issues

  • Fixed syntax highlighting for JavaScript features like template literals, private class elements, optional chaining, and others. (300332@main) (107619553)
  • Fixed an issue where navigating the DOM tree using the keyboard would get stuck in a loop within certain subtrees. (300471@main) (159841729)
  • Fixed an issue where adding DOM attributes or node siblings did not work correctly when using the actions from the context menu. (300752@main) (161577627)

October 15, 2025 09:15 PM

October 13, 2025

Igalia WebKit Team: WebKit Igalia Periodical #42

Igalia WebKit

Update on what happened in WebKit in the week from October 6 to October 13.

Another week with many updates in Temporal, the automated testing infrastructure is now running WebXR API tests; and WebKitGTK gets a fix for the janky Inspector resize while it drops support for libsoup 2. Last but not least, there are fresh releases of both the WPE and GTK ports including a security fix.

Cross-Port 🐱

Multimedia 🎥

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

When using libwebrtc, support has been added to register MDNS addresses of local networks as ICE candidates, to avoid exposing private addresses.

JavaScriptCore 🐟

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

JavaScriptCore's implementation of Temporal received a flurry of improvements:

  • Implemented the toString, toJSON, and toLocaleString methods for the PlainMonthDay type.

  • Brought the implementation of the round method on TemporalDuration objects up to spec. This is the last in the series of patches that refactor TemporalDuration methods to use the InternalDuration type, enabling mathematically precise computations on time durations.

  • Implemented basic support for the PlainMonthDay type, without most methods yet.

  • Brought the implementations of the since and until functions on Temporal PlainDate objects up to spec, improving the precision of computations.

WebKitGTK 🖥️

WebKitGTK will no longer support using libsoup 2 for networking starting with version 2.52.0, due in March 2026. An article in the website has more details and migrations tips for application developers.

Fixed the jittering bug of the docked Web Inspector window width and height while dragging the resizer.

Releases 📦️

WebKitGTK 2.50.1 and WPE WebKit 2.50.1 have been released. These include a number of small fixes, improved text rendering performance, and a fix for audio playback on Instagram.

A security advisory, WSA-2025-0007 (GTK, WPE), covers one security issue fixed in these releases. As usual, we recommend users and distributors to keep their WPE WebKit and WebKitGTK packages updated.

Infrastructure 🏗️

Updated the API test runner to run monado-service without standard input using XRT_NO_STDIN=TRUE, which allows the WPE and GTK bots to start validating the WebXR API.

Submitted a change that allows relaxing the DMA-BUF requirement when creating an OpenGL display in the OpenXRCoordinator, so that bots can run API tests in headless environments that don't have that extension.

That’s all for this week!

By Igalia WebKit Team at October 13, 2025 08:02 PM

October 10, 2025

CSS Grid: A helpful mental model and the power of grid lines

Surfin’ Safari

Grid is a powerful, flexible tool that brings complex layouts to life. While it’s not new, and has been around for eight years, there’s so much to learn that it can still feel confusing and overwhelming to work with.

What’s the right mental model for thinking about Grid? What do all the new terms mean? What’s with that “/” anyway? If you have these questions and struggle to wrap your head around Grid, you’re not alone. There’s a lot of stuff to learn. Let’s see if we can unpack and explore it together, and make grid a little more comfortable to use.

When we think of laying out content on a webpage, we might start by listing out all of our elements in HTML. Then we use CSS to move and adjust them into place, changing their flow on the page, tweaking the space between them, until they’re finally in their right positions.

But with Grid, I want to try something different.

Instead of starting with that list of elements and shifting them around, we’re going to use a mental model to first focus on the container holding our elements.

Let’s think of our grid like we’re creating a brand new spreadsheet.

When you start a new document, depending on the software, you might have to first declare that it’s going to be a spreadsheet. Similarly, to use Grid we need to declare that the display for our container will be grid, like this:

.products {
  display: grid;
}

Before we put our data into a spreadsheet, we first think about what that spreadsheet will look like. We’ll probably start with the columns — what columns do we need to hold our data? If we’re working with a list of products, will the columns contain prices, discount codes, descriptions, launch dates?

Then we’ll think about the rows — how many rows of data do we need to contain everything? Once we have a sense of these columns and rows, we can decide where to place our data.

Similarly, our grid comes with columns and rows that we determine upfront. If I’m working on a grid of products, I might think about how many products I have and how that translates to columns and rows. I have six products, so I’m going to have three columns and two rows. And I’m going to throw in a grid-gap so I have some cushion. That should look good.

Black and white mockups of six product cards organized into two rows and three columns

Here’s what that code might look like:

.products {
   display: grid;
   grid-template-columns: 1fr 1fr 1fr;
   grid-template-rows: 1fr 1fr;
   grid-gap: 2em;
}

Let’s dissect this code a bit further.

Here, I’m using fr for my values, which stands for fractional unit, and tells my CSS to divide the space up into fractions. While I don’t know exactly how wide I want each column to be, and that width should change anyway when the viewport width changes, I know that I want three equal columns. So, using my fr, I can divide up the width into three equal fractional units: 1fr 1fr 1fr.

Let’s now move on to something that might not be immediately intuitive. Why is it called grid-template-columns anyway? Why not just grid-columns? What does “template” mean in this context?

This was one of the new vocabulary of Grid that tripped me up when I first saw it. I constantly had to look up the syntax for setting columns and rows.

When it comes to columns and rows, there are actually several Grid properties that reference them.

The first, grid-template-columns and grid-template-rows reference the columns and rows on the container, the top-level element of our little grid world. The word “template” is meant to reference a sort of blueprint we’re creating — at the highest level, we are defining a template that the children within our container will adhere to. That template outlines the structure and creates the rules of our grid.

The second pair of properties is grid-row and grid-column. Unlike our first properties, these don’t refer to our container. Instead, we drop down to the children. These properties allow us to place individual items into certain rows and columns. I’ll show you how a bit later.

And the third set of properties are the auto properties: grid-auto-rows and grid-auto-columns. These allow you to define the size of any extra rows and columns for content you might not have planned for. So in my products example, I may start with six products, but since my team is always working on new ones, I might want to prepare for a future where I add a few more products. In that case, if I wanted them to take the same 1fr of space, I would set that property to 1fr , like this:

.products {
   display: grid;
   grid-template-columns: 1fr 1fr 1fr;
   grid-template-rows: 1fr 1fr;
   grid-gap: 2em;
   grid-auto-rows: 1fr;
}

Now let’s get back to my grid.

Just like I would think about my rows and columns in my spreadsheet, I’ve thought about and determined the rows and columns of my grid. Let’s explore other elements of our mental model.

In my spreadsheet, my rows and columns are separated by lines. In Grid, these are called my grid lines. Separating my columns are column grid lines, and separating my rows are row grid lines.

While we know that the cells moving from left to right (or right to left) are my rows and the cells going up and down are my columns, rows and columns are more generically known as “tracks”.

And if I have multiple cells grouped together spanning different rows and columns, that’s a “grid area.”

That’s some of the introductory vocabulary to give you a foundation of how to think about grid. Now that you know what things are called, let’s try actually laying out come products and see what happens.

I started with six products, but I don’t think I want to display all six right now. Instead, I’m going to feature my two best sellers, have a section for add-ons, and include a testimonial at the bottom. The design I have in mind might look something like this:

Black and white mockup of two products, an add-on, and testimonials.

Just like how we start our spreadsheet by thinking about the columns and rows, I need to create my grid and start with considering the columns and rows I’ll need, creating a template that my individual elements will use.

Looking at this mock up, I can see that I have three columns and two rows. But there’s some merging going on. Just like a spreadsheet can have merged cells, I see that one of my “cells,” the Add-ons, actually spans two rows. And my other “cell,” the Testimonial, spans two columns.

But before we implement this design, let’s see what Grid does all on its own. If we just designate our template rows and columns and do nothing else, where do our elements end up?

Here’s the code we’re starting with:

.products {
   display: grid;
   grid-template-columns: 1fr 1fr 1fr;
   grid-template-rows: 1fr 1fr;
   grid-gap: 2em;
}

That’ll get us the following output:

Grid layout of three cards across and one on the second row in the first column.

Not too bad. The default behavior actually got us pretty far. Without us explicitly telling Grid where to put each element, it automatically placed the elements in the next available “cell” in our Grid, and when it ran out of column space, it wrapped around to the second row. Pretty close! This is one of the things that makes Grid so powerful — it offers a lot out of the box without us needed to be explicit.

Now we need to add the code to create the spanning action we have in our design.

This next step takes a little bit of upfront planning. Before I start merging and spanning things, I need to first establish my grid lines. But I’m going to take things a step further: I’m also going to number them.

Black and white mockup of two products, add-ons, and testimonial with pink and green gridlines.

This is where our grid and the mental model of a spreadsheet start to differ. If I were to describe where Product 1 is located in a spreadsheet, I would describe it as being in the first column and the first row. In fact, in a spreadsheet, it’s these rows and columns that have labels. But in Grid, the most common way to position elements actually isn’t by using the rows and columns at all. Instead, we use the numbered grid lines to dictate where an element goes. We don’t say “the first column,” we say “between the first and second column grid lines.”

If we compare our mockup with our output, there are only two elements whose placement we need to update: Add-ons and Testimonials. Let’s look at Add-ons first.

Here, we want to keep the default behavior of the columns — it should stay between the 3 and 4 column grid lines. But we want the height to span both rows. Put in grid line lingo, we want the element to start at row grid line 1 and end at row grid line 3. Let’s write the code to make that happen.

.add-on {
   grid-row: 1/3;
}

Let’s see what that looks like.

Grid layout of four cards with Add-ons taking two rows and placed in first column.

Something interesting happened! We got the spanning across rows we wanted, but now my Add-on section has jumped to first place. That’s because Grid prioritized explicitly positioned elements over implicitly positioned ones. Since right now, Add-on is the only element with any kind of explicit positioning, it gets priority positioning in my grid. But I don’t want that. So I need to add an explicit column positioning to get it back where I want it. Let’s do that now.

Since we’re positioning based on grid lines, we’re going to explicitly put our Add-on between the 3 and 4 column grid lines, like this:

.add-on {
   grid-row: 1/3;
   grid-column: 3/4;
}

That’ll get us this output:

Four cards laid out in a grid with Add-ons on the right spanning two rows

Wonderful. Three elements placed, one more to go.

Here, we’re going to use grid lines once again. We’re going to have my Testimonial section start at row grid line 1 and go all the way to row grid line 3. Let’s code that now:

.testimonial {
   grid-column: 1/3;
}

And we get this output:

Four cards laid out as a grid with Add-ons spanning two rows on the right and Testimonial spanning two columns at the bottom

And just like that, we have our elements laid out just like our mockup.

We spent a lot of time in this article laying the foundation for Grid, establishing some important vocabulary that we later used to implement our own grid design. The biggest takeaway here, and the concept that tripped me up the most when I first learned Grid, is the importance of using those grid lines to place and span elements.

But that isn’t the only way to place items. You can also use something called grid areas that might feel a little more intuitive. We’ll cover that next.

If you found this helpful or have more feedback, you can share your feedback with me, Saron Yitbarek, on BlueSky, or reach out to our other evangelists — Jon Davis, on Bluesky / Mastodon, and Jen Simmons, on Bluesky / Mastodon. You can also follow WebKit on LinkedIn. If you find a bug or problem, please file a WebKit bug report.

October 10, 2025 01:13 AM

October 06, 2025

Igalia WebKit Team: WebKit Igalia Periodical #41

Igalia WebKit

Update on what happened in WebKit in the week from September 29 to October 6.

Another exciting weekful of updates, this time we have a number of fixes on MathML, content secutiry policy, and Aligned Trusted types, public API for WebKitWebExtension has finally been added, and fixed enumeration of speaker devices. In addition to that, there's ongoing work to improved compatibility for broken AAC audio streams in MSE, a performance improvement to text rendering with Skia was merged, and fixed multi-plane DMA-BUF handling in WPE. Last but not least, The 2026 edition of the Web Engines Hackfest has been announced! It will take place from June 15th to the 17th.

Cross-Port 🐱

Fixed rendering for unknown elements in MathML.

Fixed incorrect parsing of malformed require-trusted-types-for CSP directive.

Align reporting of Trusted Types violations with spec in case of multiple Content-Security-Policy headers.

Aligned Trusted Types event handler namespace checks with an update to the specification.

Fixed some incorrect handling of null or undefined policy values in Trusted Types.

On the WebExtensions front, the WebKitWebExtension API has finally been added, after porting some more code from Objective-C code to C++.

Improved alignment with MathML Core by making mfenced, semantics and maction render like an mrow, ignoring the subscriptshift/superscriptshift legacy attributes and cleaning the User-Agent stylesheet to more closely match the spec.

Multimedia 🎥

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

Speaker device enumeration has been fixed to properly enumerate ALSA PCM devices, while improving audio output device handling in general.

Improved compatibility for broken AAC audio streams in MSE is currently in review.

JavaScriptCore 🐟

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

In JavaScriptCore's implementation of Temporal, improved the precision of addition and subtraction on Durations.

In JavaScriptCore's implementation of Temporal, improved the precision of calculations with the total() function on Durations. This was joint work with Philip Chimento.

In JavaScriptCore's implementation of Temporal, continued refactoring addition for Durations to be closer to the spec.

Graphics 🖼️

Landed a patch to build a SkTextBlob when recording DrawGlyphs operations for the GlyphDisplayListCache, which shows a significant improvement in MotionMark “design” test when using GPU rendering.

WPE WebKit 📟

WPE Platform API 🧩

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

Improved wpe_buffer_import_to_pixels() to work correctly on non-linear and multi-plane DMA-BUF buffers by taking into account their modifiers when mapping the buffers.

Community & Events 🤝

The 2026 edition of the Web Engines Hackfest has been announced, and it will take place from June 15th to the 17th.

That’s all for this week!

By Igalia WebKit Team at October 06, 2025 08:20 PM

October 03, 2025

Online Identity Verification with the Digital Credentials API

Surfin’ Safari

The rise of e-commerce in the past decade changed the way customers interact with businesses online, leading to new innovations and improved user experiences. But while these interactions made leaps, one area that’s remained generally analog is online identity verification. A common form of online verification is for a website to ask users to take a photo of a government-issued ID for verification. This requires people to locate their driver’s license or passport, find a suitable background to capture a clear image of that ID and then upload it. Often, to help ensure that the person uploading the ID is the same person to whom the identity card belongs, people are asked to capture a selfie with it that leads to increased abandonment rates. Beyond that, businesses have to rely on expensive and complex systems to verify these uploaded photos which come with limited privacy protections and expose users to an increased risk of identity theft.

Spaceship Rentals website in Safari with Identity Verification prompt and a Verify Identity button

Mobile IDs are changing this landscape, making online identity verification easier, more secure, and more private for everyone.

What Are Mobile IDs?

Mobile identity documents are secure, electronic versions of government-issued or verified credentials (like driver’s licenses, passports, or ID cards) that can be stored and presented on a smartphone or other device. One important format for mobile identity documents are called mdocs.

Mobile documents, also known as mDocs are defined by the ISO/IEC 18013-5 and ISO/IEC 18013-7 standard, which allow for the secure presentment of digital identity documents in-person and online. The standard also provides for multiple security and privacy capabilities including:

  • Data minimization: Allows relying parties to request only the information that is needed to complete a transaction.
  • Identity data integrity and anti-forgery: Cryptographically signed to prevent tampering and reduce the risk of identity theft during presentment.
  • Device binding: use a device signature to protect against the cloning of an ID and the replay of an identity presentation.
  • User data confidentiality: Session encryption helps ensure that all personally identifiable information (PII) exchanged between the mdoc and the relying party is encrypted.

Introducing Web Support for the Digital Credentials API

Starting in Safari 26 on macOS 26, iOS 26, and iPadOS 26, we’re introducing support for the W3C’s Digital Credentials API. This means websites can now request mobile IDs from Apple Wallet and third party Wallets directly from Safari and other WebKit-based browsers across your iPhone, iPad, Mac, and other devices.

How It Works: A Real-World Example

An individual user would like to create a verified account with a car rental company to access preferred benefits such as a “skip-the-counter” service. As part of that verified account setup, the user is asked to verify their identity online by presenting their mobile ID.

On Safari on an iPhone:

  1. User taps the “Verify Identity” button on the car rental’s website, which invokes the W3C’s Digital Credentials API.
  2. A system UI appears allowing the user to select their preferred digital identity document (e.g., California ID in Apple Wallet).
  3. A consent sheet displays the requested information, the website behind the request and whether the website intends to retain the requested information.
  4. The requested information is sent to the website only after the user reviews and authorizes with their biometric or passcode to do so.

On Safari on a Mac or iPad:

The process is equally straightforward on macOS and iPadOS, and also relies on digital identity documents stored on your iPhone:

  1. User taps the “Verify Identity” button on the car rental’s website, which invokes the W3C’s Digital Credentials API.
  2. User is prompted to continue on their nearby iPhone that is signed into the same Apple account.
  3. A consent sheet displays the requested information, the website behind the request and whether the website intends to retain the requested information.
  4. The requested information is sent to the website only after the user reviews and authorizes with their biometric or passcode to do so.

On other platforms:

Users can also present their mobile IDs across other standards compliant non-Apple platforms:

  1. User taps the “Verify Identity” button on the car rental’s website, which invokes the W3C’s Digital Credentials API.
  2. The browser presents a QR code allowing the user to open the camera app on their iPhone and scan it to continue the identity verification process.
  3. On the user’s iPhone, a consent sheet displays the requested information, the website behind the request and whether the website intends to retain the requested information.
  4. The requested information is sent to the website only after the user authorizes with their biometric or passcode to do so.

How the Digital Credentials API works

Presenting your Digital Credential in Safari follows international standards, ensuring interoperability so that any browser or platform adopting the same standards can accept it:

Understanding the Technical Flow

Flow chart of the Digital Credentials API from user and browser to server as described in detail below
  1. User visits website: The request for a digital credential begins by some user action (e.g., pressing a “Verify Identity” button).
  2. Server builds the request: The website’s server creates and signs a document request.
  3. API call via JavaScript: The website calls the Digital Credentials API with the request.
  4. OS handles routing: The browser forwards the request to the operating system.
  5. Wallet selection UI: The OS determines available document sources (e.g., Apple Wallet or another app) and shows options to the user.
  6. User authorization: The selected app handles user consent and authorization.
  7. Encrypted response: An encrypted response (e.g., the request drivers license data) is sent back to the website.
  8. Decrypt and validate: The website’s server decrypts and checks the values are as expected in the response (see “Security: Trust but Verify” below).

For Website Developers: Getting Started

As a website developer looking to implement digital identity verification, there are three main steps to consider:

1. Building the Request

The website server needs to create a properly formatted request containing:

  • Document type: What kind of document the website is requesting (e.g., driver’s license in the mdoc format).
  • Required elements: Specific information the website needs (e.g., full name, age, etc.) and whether the website intends to retain this information.
  • Encryption parameters: Keys and nonces for encryption and signing the response.
  • Authentication structures: A signed ReaderAuth structure that contains information to help identify the website to document providers.

For more information about building and signing the request, see Requesting a mobile document on the web.

2. Making the API Call

Below is a reference to the Digital Credentials API that can be used as a starting point for making your own calls:

const requestData = {
  protocol: "org-iso-mdoc",
  data // Built on your server
};

try {
  // Gets back an encrypted credential
  const credential = await navigator.credentials.get({
    mediation: "required",
    digital: {
       requests: [ requestData ]
    }
  });
  // Send credential back to server for decryption
  await fetch("/verify", { method: "POST", body: credential };
} catch (error) {
  // Handle errors or fallback to alternative methods
}

Important: this API call must be triggered by a user gesture (like a button click) to prevent a website from requesting a user’s identity without user interaction. Like with most modern JavaScript APIs, this check is done automatically by the browser.

3. Processing the Response

When the server receives the response, it needs to:

  • Decrypt the response using its own private key.
  • Validate the issuer’s signature through the IssuerAuth structure as defined in ISO/IEC 18013-5.
  • Verify the authenticity of individual data elements by computing and comparing hash digests.
  • Confirm the document came from the device it was issued to by validating the DeviceAuth structure.

The precise procedure required to perform these steps for processing the response is defined in ISO/IEC 18013-5.

Security: Trust but Verify

Security is paramount when dealing with identity information. The mdoc format as defined in ISO/IEC 18013-5 includes multiple layers of protection to detect, deter, and mitigate security risks:

Request Authentication: Websites identify themselves using certificates, so users know exactly who’s asking for their information.

End-to-End Encryption: Responses are encrypted using keys generated by the requesting website, preventing unauthorized actors from accessing the data.

Issuer Authentication: Digital signatures prove the authenticity of the identity data and prevent tampering.

Device Binding: mdoc authentication use a device signature to protect against the cloning of an ID and the replay of an identity presentation.

For App Developers

If you’re building an app that provides digital identity documents like a government licensing authority app or digital identity documents provider, you can integrate your app with Identity Document Services. This framework enables the presentment of identity documents on device and web browser support for the Digital Credentials API. Once authorized, a person can select your app during a identity documents request, where they can authorize the presentment of identification through a UI you create.

If instead you are an app developer who wants to request a digital identity document from your app, see Get started with the Verify with Wallet API.

Learn More and Next Steps

For a deeper dive into Digital Credentials implementation, we recommend watching this WWDC session: Streamline identity verification with Digital Credentials. This session covers how Digital Credentials can enhance online identity verification flows, explores website integration with the Digital Credentials API for requesting information from IDs in Wallet, and demonstrates how apps can provide their own identity documents for online verification using the IdentityDocumentServices framework.

Next Steps

Ready to get started? Here’s what you need to do:

For Web Developers:

  • Register with Apple Business Connect to receive certificates for requesting IDs from Apple Wallet.
  • Check with other document providers about their specific onboarding requirements.
  • Review the ISO standards mentioned in this post for implementation details.
  • Follow the Requesting a mobile document on the web guide to get started with your implementation.

For App Developers wanting to integrate with Identity Document Services:

October 03, 2025 09:19 PM

October 02, 2025

Release Notes for Safari Technology Preview 229

Surfin’ Safari

Safari Technology Preview Release 229 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: 299669@main…300290@main.

Accessibility

Resolved Issues

  • Fixed an issue where <label> elements targeted by aria-labelledby lost their LabelFor relationships after content changes. (300029@main) (158906980)

CSS

New Features

  • Added support for text-decoration-line values spelling-error and grammar-error. (299919@main) (160494378)

Resolved Issues

  • Fixed offsetParent to correctly return the fixed-position element’s containing block when it is not the viewport, such as a transformed element. (300097@main) (63739636)
  • Fixed propagation of the body element’s writing-mode to the document element to match the CSS Writing Modes Level 4 specification. (300278@main) (149475070)
  • Fixed @position-try so that revert-layer correctly only reverts the position-try origin instead of affecting other cascade origins. (299900@main) (154355428)
  • Fixed the calculation of anchor() positions in right-to-left or vertical-rl containers to correctly account for left-side scrollbars. (300018@main) (155852237)
  • Fixed baseline alignment for grid items by adding correct first baseline and last baseline row axis handling and properly accounting for baseline offsets. (299933@main). (155967278)
  • Fixed issues with anchor() positioning during overscroll by clamping scroll positions, preventing unnecessary scroll recaptures, and ensuring anchor resolution stays in sync with layout changes. (300061@main) (159356009)
  • Fixed an issue where anchor-positioned elements failed to update their position when the default anchor changed. (300142@main) (159899182)
  • Fixed an issue where collapsed table rows subtracted border-spacing twice. (300023@main) (160542118)
  • Fixed ::view-transition pseudo-element to use position: absolute instead of fixed to align with the updated specification. (300008@main) (160622000)
  • Fixed container queries to allow container-name matching across the full flat tree, making container names tree-scoped in line with the CSS Conditional 5 specification. (300033@main) (160696378)
  • Fixed handling of ::first-letter pseudo-elements to always force inline display unless floated.(300073@main) (160710650)
  • Fixed the behavior of the nesting selector & directly inside @scope to correctly act like :where(:scope) for proper specificity handling. (300153@main) (160769736)

Events

Resolved Issues

  • Fixed boundary pointer and mouse events not firing when the hit test target changed under a stationary pointer. (300277@main) (160147423)
  • Fixed scroll event handling by consolidating scroll and scrollend into a single queue. (300239@main) (160936070)

Forms

Resolved Issues

  • Fixed input fields with field-sizing: content so that larger placeholder text now correctly expands the height of the field by including the placeholder’s computed height. (299672@main) (123125836)
  • Fixed painting for <input type="range"> sliders in right-to-left vertical block writing modes. (299690@main) (158567821)

JavaScript

Resolved Issues

  • Fixed TypeError messages to be clearer in for-of loops. (300154@main) (159814766)

Networking

Resolved Issues

  • Fixed the frozen iOS user agent string to report version 18_7 for iOS 26.1. (299818@main) (159902747)

SVG

New Features

  • Added support for the onbegin event in the SVGAnimationElement IDL interface to align with the SVG animations specification. (299724@main) (130609424)
  • Added support for the async attribute in SVGScriptElement to align behavior with HTMLScriptElement and other browsers. (299735@main) (151561361)
  • Added support for the hreflang IDL attribute on SVGAElement to improve SVG link handling. (299704@main) (160133102)
  • Added support for type attribute on SVG <a> element. (299770@main) (160222206)

Resolved Issues

  • Fixed absolutely positioned SVG elements to correctly account for the containing block’s padding. (300054@main) (127608838)

Storage

Resolved Issues

  • Fixed an issue where dedicated workers could inherit storage access from their parent document, preventing them from sending cross-site requests with cookies. (300120@main) (158814068)

Web API

New Features

  • Added preview support for Event Timing API (Interaction to Next Paint) (300205@main) (160880698)

Resolved Issues

  • Fixed window.opener being incorrectly set to null when a site-isolated iframe navigated to a new site, ensuring opener relationships persist across frame migrations. (299924@main) (117269418)
  • Fixed scroll and scrollend events so they correctly fire on <input type="text"> elements instead of their inner elements. (300260@main) (157880733)
  • Fixed the order of pointerup and boundary events so that pointerout and pointerover fire before pointerup when a child element is attached under the cursor. (https://commits.webkit.org/300273@main) (160913756)
  • Fixed element.scrollTo and element.scrollBy so they correctly scroll text input fields by forwarding scroll operations to the inner text element. (300274@main) (160963921)

Deprecations

  • Removed support for the non-standard “overflow” event. (281672@main) (71129110)

Web Inspector

Resolved Issues

  • Fixed an issue where the Console truncated long string outputs. (300077@main) (124629101)

WebDriver

New Features

  • Added support for new endpoints for setting storage access permission state and granting storage access to embedded frames for specific origins. (158263193)

Resolved Issues

  • Fixed the navigate endpoint in WebDriver to properly validate URLs against the current browsing context and set the default readiness state to Interactive to align with the specification. (299780@main) (157031091)

WebGPU

New Features

  • Added support for using GPUTexture objects as depth-stencil and resolve attachments in WebGPU render passes to match the specification. (299708@main) (159952306)

October 02, 2025 09:26 PM

September 29, 2025

Igalia WebKit Team: WebKit Igalia Periodical #40

Igalia WebKit

Update on what happened in WebKit in the week from September 22 to September 29.

Many news this week! We've got a performance improvement in the Vector implementation, a fix that makes a SVG attribute work similarly to HTML, and further advancements on WebExtension support. We also saw an update to WPE Android, the test infrastructure can now run WebXR tests, WebXR support in WPE Android, and a rather comprehensive blog post about the performance considerations of WPE WebKit with regards to the DOM tree.

Cross-Port 🐱

Vector copies performance was improved across the board, and specially for MSE use-cases

Fixed SVG <a> rel attribute to work the same as HTML  <a>'s.

Work on WebExtension support continues with more Objective-C converted to C++, which allows all WebKit ports to reuse the same utility code in all ports.

Added handling of the visibilityState value for inline WebXR sessions.

Graphics 🖼️

WPE now supports importing pixels from non-linear DMABuf formats since commit 300687@main. This will help the work to make WPE take screenshots from the UIProcess (WIP) instead of from the WebProcess, so they match better what's actually shown on the screen.

Added support for the WebXR passthroughFullyObscured rendering hint when using the OpenXR backend.

WPE WebKit 📟

WPE Platform API 🧩

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

The build system will now compile WPEPlatform with warning-as-errors in developer builds. This helps catch potential programming errors earlier.

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

WPE-Android is being updated to use WPE WebKit 2.50.0. As usual, the ready-to-use packages will arrive in a few days to the Maven Central repository.

Added support to run WebXR content on Android, by using AHarwareBuffer to share graphics buffers between the main process and the content rendering process. This required coordination to make the WPE-Android runtime glue expose the current JavaVM and Activity in a way that WebKit could then use to initialize the OpenXR platform bindings.

Community & Events 🤝

Paweł Lampe has published in his blog the first post in a series about different aspects of Web engines that affect performance, with a focus on WPE WebKit and interesting comparisons between desktop-class hardware and embedded devices. This first article analyzes how “idle” nodes in the DOM tree render measurable effects on performance (pun intended).

Infrastructure 🏗️

The test infrastructure can now run API tests that need WebXR support, by using a dummy OpenXR compositor provided by the Monado runtime, along with the first tests and an additional one that make use of this.

That’s all for this week!

By Igalia WebKit Team at September 29, 2025 08:34 PM

Position-area: Clear and explicit or short and sweet?

Surfin’ Safari

When I first learned anchor positioning, I built a demo to help me figure out how it all worked. I had a goal of what I wanted and I was trying to figure out what properties I needed to make it happen. I had a profile picture, and, when you clicked on it, I wanted a menu to appear below it, but left aligned to the profile picture, like this:

Nav menu with profile picture and drop down menu right below it

I did some searching, and found that, using logical direction, the code to accomplish this looked like this:

.profile-menu {
  position-anchor: --profile-button;
  position: absolute;
  position-area: block-end span-inline-end;
}

Most of this code is good and fine, but there’s one bit that I found surprising and a bit unclear: span-inline-end. This property conveys a few things. The span tells me that I’m crossing columns and including more than one. Makes sense. The inline-end tells me that one of those columns is the inline-end . Great.

But there’s something missing.

If I’m spanning multiple columns, what’s the other column? Do we start all the way on the left and spanning three columns, or are we starting in the center and spanning just the two? Looking at this property, there’s no way to tell.

I’m wondering if there should be a change to how CSS works, so that, instead, developers would write center-span-inline-end. That spells it out clearly. You start at the center and you span over to inline-end. Every piece of the puzzle is there.

So the code above would be rewritten as this:

.profile-menu {
  position-anchor: --profile-button;
  position: absolute;
  position-area: block-end center-span-inline-end;
}

But with this new property, you get an extra word, making it a bit more verbose. So is that extra word worth the extra clarity to you? Or would you prefer to fill in the blank yourself and keep things concise?

This change to the spec is currently being considered, and we want to know if it’s a change you’d support or if you’d prefer things as they are.

Find me on BlueSky and let me know which you like better.

September 29, 2025 06:42 PM

September 26, 2025

Pawel Lampe: WPE performance considerations: DOM tree

Igalia WebKit

Designing performant web applications is not trivial in general. Nowadays, as many companies decide to use web platform on embedded devices, the problem of designing performant web applications becomes even more complicated. Typical embedded devices are orders of magnitude slower than desktop-class ones. Moreover, the proportion between CPU and GPU power is commonly different as well. This usually results in unexpected performance bottlenecks when the web applications designed with desktop-class devices in mind are being executed on embedded environments.

In order to help web developers approach the difficulties that the usage of web platform on embedded devices may bring, this blog post initiates a series of articles covering various performance-related aspects in the context of WPE WebKit usage on embedded devices. The coverage in general will include:

  • introducing the demo web applications dedicated to showcasing use cases of a given aspect,
  • benchmarking and profiling the WPE WebKit performance using the above demos,
  • discussing the causes for the performance measured,
  • inferring some general pieces of advice and rules of thumb based on the results.

This article, in particular, discusses the overhead of nodes in the DOM tree when it comes to layouting. It does that primarily by investigating the impact of idle nodes that introduce the least overhead and hence may serve as a lower bound for any general considerations. With the data presented in this article, it should be clear how the DOM tree size/depth scales in the case of embedded devices.

DOM tree #

Historically, the DOM trees emerging from the usual web page designs were rather limited in size and fairly shallow. This was the case as there were no reasons for them to be excessively large unless the web page itself had a very complex UI. Nowadays, not only are the DOM trees much bigger and deeper, but they also tend to contain idle nodes that artificially increase the size/depth of the tree. The idle nodes are the nodes in the DOM that are active yet do not contribute to any visual effects. Such nodes are usually a side effect of using various frameworks and approaches that conceptualize components or services as nodes, which then participate in various kinds of processing utilizing JavaScript. Other than idle nodes, the DOM trees are usually bigger and deeper nowadays, as there are simply more possibilities that emerged with the introduction of modern APIs such as Shadow DOM, Anchor positioning, Popover, and the like.

In the context of web platform usage on embedded devices, the natural consequence of the above is that web designers require more knowledge on how the particular browser performance scales with the DOM tree size and shape. Before considering embedded devices, however, it’s worth to take a brief look at how various web engines scale on desktop with the DOM tree growing in depth.

Desktop considerations #

To measure the impact of the DOM tree depth on the performance, the random-number-changing-in-the-tree.html?vr=0&ms=1&dv=0&ns=0 demo can be used to perform a series of experiments with different parameters.

In short, the above demo measures the average duration of a benchmark function run, where the run does the following:

  • changes the text of a single DOM element to a random number,
  • forces a full tree layout.

Moreover, the demo allows one to set 0 or more parent idle nodes for the node holding text, so that the layout must consider those idle nodes as well.

The parameters used in the URL above mean the following:

  • vr=0 — the results are reported to the console. Alternatively (vr=1), at the end of benchmarking (~23 seconds), the result appears on the web page itself.
  • ms=1 — the results are reported in “milliseconds per run”. Alternatively (ms=0), “runs per second” are reported instead.
  • dv=0 — the idle nodes are using <span> tag. Alternatively, (dv=1) <div> tag is used instead.
  • ns=N — the N idle nodes are added.

The idea behind the experiment is to check how much overhead is added as the number of extra idle nodes (ns=N) in the DOM tree increases. Since the browsers used in the experiments are not fair to compare due to various reasons, instead of concrete numbers in milliseconds, the results are presented in relative terms for each browser separately. It means that the benchmarking result for ns=0 serves as a baseline, and other results show the relative duration increase to that baseline result, where, e.g. a 300% increase means 3 times the baseline duration.

The results for a few mainstream browsers/browser engines (WebKit GTK MiniBrowser [09.09.2025], Chromium 140.0.7339.127, and Firefox 142.0) and a few experimental ones (Servo [04.07.2024] and Ladybird [30.06.2024]) are presented in the image below:

Idle nodes overhead on mainstream browsers.

As the results show, trends among all the browsers are very close to linear. It means that the overhead is very easy to assess, as usually N times more idle nodes will result in N times the overhead. Moreover, up until 100-200 extra idle nodes in the tree, the overhead trends are very similar in all the browsers except for experimental Ladybird. That in turn means that even for big web applications, it’s safe to assume the overhead among the browsers will be very much the same. Finally, past the 200 extra idle nodes threshold, the overhead across browsers diverges. It’s very likely due to the fact that the browsers are not optimizing such cases as a result of a lack of real-world use cases.

All in all, the conclusion is that on desktop, only very large / specific web applications should be cautious about the overhead of nodes, as modern web browsers/engines are very well optimized for handling substantial amounts of nodes in the DOM.

Embedded device considerations #

When it comes to the embedded devices, the above conclusions are no longer applicable. To demonstrate that, a minimal browser utilizing WPE WebKit is used to run the demo from the previous section both on desktop and NXP i.MX8M Plus platforms. The latter is a popular choice for embedded applications as it has quite an interesting set of features while still having strong specifications, which may be compared to those of Raspberry Pi 5. The results are presented in the image below:

Idle nodes overhead compared between desktop and embedded devices.

This time, the Y axis presents the duration (in milliseconds) of a single benchmark run, and hence makes it very easy to reason about overhead. As the results show, in the case of the desktop, 100 extra idle nodes in the DOM introduce barely noticeable overhead. On the other hand, on an embedded platform, even without any extra idle nodes, the time to change and layout the text is already taking around 0.6 ms. With 10 extra idle nodes, this duration increases to 0.75 ms — thus yielding 0.15 ms overhead. With 100 extra idle nodes, such overhead grows to 1.3 ms.

One may argue if 1.3 ms is much, but considering an application that e.g. does 60 FPS rendering, the time at application disposal each frame is below 16.67 ms, and 1.3 ms is ~8% of that, thus being very considerable. Similarly, for the application to be perceived as responsive, the input-to-output latency should usually be under 20 ms. Again, 1.3 ms is a significant overhead for such a scenario.

Given the above, it’s safe to state that the 20 extra idle nodes should be considered the safe maximum for embedded devices in general. In case of low-end embedded devices i.e. ones comparable to Raspberry Pi 1 and 2, the maximum should be even lower, but a proper benchmarking is required to come up with concrete numbers.

Inline vs block #

While the previous subsection demonstrated that on embedded devices, adding extra idle nodes as parents must usually be done in a responsible way, it’s worth examining if there are nuances that need to be considered as well.

The first matter that one may wonder about is whether there’s any difference between the overhead of idle nodes being inlines (display: inline) or blocks (display: block). The intuition here may be that, as idle nodes have no visual impact on anything, the overhead should be similar.

To verify the above, the demo from Desktop considerations section can be used with dv parameter used to control whether extra idle nodes should be blocks (1, <div>) or inlines (0, <span>). The results from such experiments — again, executed on NXP i.MX8M Plus — are presented in the image below:

Comparison of overhead of idle nodes being inline or block elements.

While in the safe range of 0-20 extra idle nodes the results are very much similar, it’s evident that in general, the idle nodes of block type are actually introducing more overhead.

The reason for the above is that, for layout purposes, the handling of inline and block elements is very different. The inline elements sharing the same line can be thought of as being flattened within so called line box tree. The block elements, on the other hand, have to be represented in a tree.

To show the above visually, it’s interesting to compare sysprof flamegraphs of WPE WebProcess from the scenarios comprising 20 idle nodes and using either <span> or <div> for idle nodes:

idle <span> nodes:
Sysprof flamegraph of WPE WebProcess layouting inline elements.
idle <div> nodes:
Sysprof flamegraph of WPE WebProcess layouting block elements.

The first flamegraph proves that there’s no clear dependency between the call stack and the number of idle nodes. The second one, on the other hand, shows exactly the opposite — each of the extra idle nodes is visible as adding extra calls. Moreover, each of the extra idle block nodes adds some overhead thus making the flamegraph have a pyramidal shape.

Whitespaces #

Another nuance worth exploring is the overhead of text nodes created because of whitespaces.

When the DOM tree is created from the HTML, usually a lot of text nodes are created just because of whitespaces. It’s because the HTML usually looks like:

<span>
<span>
(...)
</span>
</span>

rather than:

<span><span>(...)</span></span>

which makes sense from the readability point of view. From the performance point of view, however, more text nodes naturally mean more overhead. When such redundant text nodes are combined with idle nodes, the net outcome may be that with each extra idle node, some overhead will be added.

To verify the above hypothesis, the demo similar to the above one can be used along with the above one to perform a series of experiments comparing the approach with and without redundant whitespaces: random-number-changing-in-the-tree-w-whitespaces.html?vr=0&ms=1&dv=0&ns=0. The only difference between the demos is that the w-whitespaces one creates the DOM tree with artificial whitespaces, simulating as-if it was written in the formatted document. The comparison results from the experiments run on NXP i.MX8M Plus are presented in the image below:

Overhead of redundant whitespace nodes.

As the numbers suggest, the overhead of redundant text nodes is rather small on a per-idle-node basis. However, as the number of idle nodes scales, so does the overhead. Around 100 extra idle nodes, the overhead is noticeable already. Therefore, a natural conclusion is that the redundant text nodes should rather be avoided — especially as the number of nodes in the tree becomes significant.

Parents vs siblings #

The last topic that deserves a closer look is whether adding idle nodes as siblings is better than adding them as parent nodes. In theory, having extra nodes added as siblings should be better as the layout engine will have to consider them, yet it won’t mark them with a dirty flag and hence it won’t have to layout them.

As in other cases, the above can be examined using a series of experiments run on NXP i.MX8M Plus using the demo from Desktop considerations section and comparing against either random-number-changing-before-siblings.html?vr=0&ms=1&dv=0&ns=0 or random-number-changing-after-siblings.html?vr=0&ms=1&dv=0&ns=0 demo. As both of those yield similar results, any of them can be used. The results of the comparison are depicted in the image below:

Overhead of idle nodes added as parents vs as siblings.

The experiment results corroborate the theoretical considerations made above — idle nodes added as siblings indeed introduce less layout overhead. The savings are not very large from a single idle node perspective, but once scaled enough, they are beneficial enough to justify DOM tree re-organization (if possible).

Conclusions #

The above experiments mostly emphasized the idle nodes, however, the results can be extrapolated to regular nodes in the DOM tree. With that in mind, the overall conclusion to the experiments done in the former sections is that DOM tree size and shape has a measurable impact on web application performance on embedded devices. Therefore, web developers should try to optimize it as early as possible and follow the general rules of thumb that can be derived from this article:

  1. Nodes are not free, so they should always be added with extra care.
  2. Idle nodes should be limited to ~20 on mid-end and ~10 on low-end embedded devices.
  3. Idle nodes should be inline elements, not block ones.
  4. Redundant whitespaces should be avoided — especially with idle nodes.
  5. Nodes (especially idle ones) should be added as siblings.

Although the above serves as great guidance, for better results, it’s recommended to do the proper browser benchmarking on a given target embedded device — as long as it’s feasible.

Also, the above set of rules is not recommended to follow on desktop-class devices, as in that case, it can be considered a premature optimization. Unless the particular web application yields an exceptionally large DOM tree, the gains won’t be worth the time spent optimizing.

September 26, 2025 12:00 AM

September 22, 2025

Igalia WebKit Team: WebKit Igalia Periodical #39

Igalia WebKit

Update on what happened in WebKit in the week from September 15 to September 22.

The first release in a new stable series is now out! And despite that, the work continues on WebXR, multimedia reliability, and WebExtensions support.

Cross-Port 🐱

Fixed running WebXR tests in the WebKit build infrastructure, and made a few more of them run. This both increases the amount of WebXR code covered during test runs, and helps prevent regressions in the future.

As part of the ongoing work to get WebExtensions support in the GTK and WPE WebKit ports, a number of classes have been converted from Objective-C to C++, in order to use share their functionality among all ports.

Multimedia 🎥

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

A number of multimedia-related memory leaks have been plugged. These have been found thanks to the GStreamer leak tracer.

Releases 📦️

WebKitGTK 2.50.0 and WPE WebKit 2.50.0 are now available. These are the first releases of a new stable series, and are the result of the last six months of work. This development cycle focused on rendering performance improvements, improved support for font features, and more. New public API has been added to obtain the theme color declared by Web pages.

For those longer to integrate newer releases, which we know can be a longer process when targeting embedded devices, we have also published WPE WebKit 2.48.7 with a few stability and security fixes.

Accompanying these releases there is security advisory WSA-2025-0006 (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.

That’s all for this week!

By Igalia WebKit Team at September 22, 2025 11:12 PM

September 17, 2025

Release Notes for Safari Technology Preview 228

Surfin’ Safari

Safari Technology Preview Release 228 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: 299100@main…299754@main.

Accessibility

New Features

  • Added support for auto-expanding details and hidden=”until-found” elements for text searches done via assistive technologies. (299649@main) (159913471)

Resolved Issues

  • Fixed an issue where VoiceOver reports the wrong radio count with a dynamically inserted radio option. (299585@main) (159221583)
  • Fixed exposing content within dynamically expanded details elements in the accessibility tree. (299601@main) (159291226)

CSS

New Features

  • Added support for :scope when the scoping root is :visited. (299560@main) (157588890)
  • Added support for using color-mix() without a color space, defaulting to oklab. (299440@main) (159039709)
  • Added support for display-p3-linear colors in CSS. (299381@main) (159579630)

Resolved Issues

  • Fixed -webkit-user-select: none disabling find-in-page in Safari. (299624@main) (8081660)
  • Fixed anchor position to only generate position options on base style. (299135@main) (158900076)
  • Fixed out-of-flow box with no sibling ignoring align-content. (299107@main) (159097576)
  • Fixed performance of :has(> .changed) .subject selectors. (299162@main) (159173631)
  • Fixed handling of the ::first-line pseudo-element when floats prevent the initial line from containing inline content, ensuring correct styling is applied to the actual first formatted line. (299402@main) (159613287)

DOM

Resolved Issues

  • Fixed an issue where command-clicking to open a link in a new tab navigates the current tab. (299537@main) (57216935)

Editing

Resolved Issues

  • Fixed jumbled text when copy/pasting bidirectional text starting with left-to-right. (299176@main) (152236717)

Forms

Resolved Issues

  • Fixed <select> element with long <option> text causing horizontal scrolling in grid or flex containers. (299631@main) (141633685)
  • Fixed checkboxes and radio buttons missing borders in the filled state when “Increased Contrast” is enabled on macOS. (299269@main) (159226308)
  • Fixed switch controls that may be sized incorrectly due to incorrect margins. (299543@main) (159729284)

JavaScript

New Features

  • Added support for Wasm Memory buffer APIs. (299236@main) (159305098)
  • Added support for Wasm JS String Builtins. (299455@main) (159679027)

Resolved Issues

  • Fixed non-standard new Date(2024-12-3) yielding to an “Invalid Date” error. (299182@main) (141044926)
  • Fixed poor error messages for destructing null or undefined values. (299244@main) (159340067)

Media

Resolved Issues

  • Fixed western Arabic numbers being displayed in the video viewer instead of eastern Arabic numbers. (299348@main) (141281469)
  • Fixed handling of null media accessibility caption profile. (299204@main) (159134245)
  • Fixed hiding and resuming a webm video that sometimes causes a decoding error. (299366@main) (159508950)

PDF

Resolved Issues

  • Fixed VoiceOver not recognizing the password form. (299180@main) (155907450)

Rendering

Resolved Issues

  • Fixed list markers overlapping text in certain situations. (299671@main) (157054277)
  • Fixed string search freezing when subject has large number (>1000). (292919@main) (159129919)
  • Fixed position-visibility: anchors-visible visibility heuristic in certain situations. (299554@main) (159790886)
  • Fixed buttons with box shadow being broken. (299603@main) (159888287)

SVG

Resolved Issues

  • Fixed an issue where a dynamic change in a CSS property of an SVG element does not get reflected in the instances of the SVGElement. (299112@main) (98577657)
  • Fixed an issue where <view> element was not applied to the root element. (299459@main) (159705519)

Service Worker

Resolved Issues

  • Fixed an issue where service worker downloads are not being saved to Downloads folder. (299564@main) (154501503)

Web API

Resolved Issues

  • Fixed preventing the javascript: protocol in navigation.navigate(). (299235@main) (158867866)
  • Fixed an issue where click and auxclick event targeting does not follow pointer capture target override. (299567@main) (159477637)

Web Extensions

Resolved Issues

  • Fixed an issue that caused the web page to crash when navigating to certain URLs with an extension enabled. (299173@main) (158180410)

Web Inspector

Resolved Issues

  • Fixed an issue where accepting a completion suggestion for a shorthand property value would malform the combined value. (299157@main) (159107788)
  • Fixed issue where searching on certain text fails to find matches. (299541@main) (159272725)

WebGPU

Resolved Issues

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

WebRTC

New Features

  • Added encrypted field to RTCRtpHeaderExtensionParameters. (299356@main) (159279401)

Resolved Issues

  • Fixed camera indicator staying enabled even after ending a meeting/access to camera. (299161@main) (152962650)

September 17, 2025 09:20 PM

September 15, 2025

Igalia WebKit Team: WebKit Igalia Periodical #38

Igalia WebKit

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

The JavaScriptCore implementation of Temporal continues to be polished, as does SVGAElement, and WPE and WebKitGTK accessibility tests can now run (but they are not passing yet).

Cross-Port 🐱

Add support for the hreflang attribute on SVGAElement, this helps to align it with HTMLAnchorElement.

An improvement in harnessing code for A11y tests allowed to unblock many tests marked as Timeout/Skip in WPEWebKit and WebKitGTK ports. These tests are not passing yet, but they are at least running now.

Add suport for the type attribute on SVGAElement.

JavaScriptCore 🐟

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

In the JavaScriptCore (JSC) implementation of Temporal, refactored the implementations of the difference operations (since and until) for the TemporalPlainTime type in order to match the spec. This enables further work on Temporal, which is being done incrementally.

That’s all for this week!

By Igalia WebKit Team at September 15, 2025 07:42 PM

WebKit Features in Safari 26.0

Surfin’ Safari

We’re happy to share with you what’s arriving in Safari 26.0! It includes big exciting new features, many important improvements, and lots of attention to detail. We can’t wait to see what you do with Anchor Positioning, Scroll-driven animations, High Dynamic Range images, the new HTML <model> element, the all-new Digital Credentials API, SVG icon support, WebGPU, WebKit in SwiftUI, and much, much more.

Now every site can be a web app on iOS and iPadOS. Safari in visionOS supports a wider range of immersive media, with spatial videos, Apple Immersive Video, and 180°, 360° & Wide FOV videos. Users can report issues they are having with websites directly from Safari. And there are new features for Web Inspector, Web Extensions, Content Blockers, Lockdown Mode, Device Management, WebKit API and more.

Safari 26.0 adds 75 new features, 3 deprecations, and 171 other improvements. That’s 12% more features and 59% more bug fixes than we announced in June at WWDC.

CSS

Anchor Positioning

Anchor positioning is a new layout mechanism for anchoring one element to another on the web. It pairs well with the popover attribute (which shipped in Safari 17.0), making it easy to create responsive menus, tooltips and more.

The easiest way to use anchor positioning is by using position-area, which lets you position elements (the “anchor-positioned”) in pre-defined areas relative to another element (the “anchor”). For example, to position an element on the top right corner of an anchor, it’s as simple as position-area: top right:

.thing-that-gets-anchored-to {
  anchor-name: --profile-button;
}

.item-that-pops-up {
  position: absolute;
  position-anchor: --profile-button;
  position-area: top right;
} 

For more advanced use cases, the anchor() CSS function calculates the inset value required to line up the edges of the anchor and anchor-positioned elements together. This example achieves the same effect as above, but using anchor() instead:

.thing-that-gets-anchored-to {
  anchor-name: --profile-button;
}

.item-that-pops-up {
  position: absolute;
  position-anchor: --profile-button;
  bottom: anchor(top);
  left: anchor(right);
}

Above, the anchor(top) in bottom: anchor(top) calculates to a value that lines up the bottom edge of the anchor-positioned to the top edge of the anchor. Similarly, left: anchor(right) lines up the right edge of the anchor-positioned to the left edge of the anchor.

As anchor() calculates to a value, it can be used in calc() for more advanced use cases: exact-to-the-pixel layout, anchoring to multiple anchors, or animated anchors. But for everything else, just stick to the pre-defined areas using position-area. The position-area syntax came from a proposal we put together, as we thought about how developers would use Anchor Positioning, and how overwhelming it’d be to manually line up edges together using anchor().

You can also use position-try to provide alternative positions when there’s not enough room to display element. For example, to place the element on the bottom right corner when there isn’t enough space on the top right corner, use position-try: bottom right.

.thing-that-gets-anchored-to {
  anchor-name: --profile-button;
}

.item-that-pops-up {
  position: absolute;
  position-anchor: --profile-button;
  position-area: top right;
  position-try: bottom right;
} 
Code using Anchor Positioning with the results. Watch our WWDC25 session for a walkthrough of this example.
Learn more about Anchor Positioning, and watch a full walkthrough of this example in What’s new in Safari and WebKit at WWDC25.

New since the first beta, Safari 26.0 supports implicit anchor elements for pseudo-elements with anchor functions.

To learn more about the anchor positioning, consult our gentle introduction to anchor positioning. You can also catch the full walkthrough of using anchor positioning in our WWDC25 session, What’s new in Safari and WebKit.

Scroll-driven animations

Scroll-driven animations lets you tie CSS animations to either the timeline of just how far the user has scrolled, or to how far particular content has moved through the viewport, in and out of view.

For example, let’s imagine you want to animate a group of items as they scroll into view.

Cards on a webpage, scrolled into the viewport, completing their animation (turning to land flat), as they reach 50%.

You can declare that you want the animation to be tied to whether or not they are in view with animation-timeline: view(), and specify that the animation should begin just as each item is 0% visible and end when they are 50% across the viewport with animation-range: 0% 50%.

.item {
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: 0% 50%;
  &:nth-child(3n + 1) { animation-name: in-from-left; }
  &:nth-child(3n + 2) { animation-name: in-from-middle; }
  &:nth-child(3n + 3) { animation-name: in-from-right; }
}

Watch What’s new in Safari and WebKit at WWDC25 to see the full walkthrough of this example, and learn more about what’s possible with Scroll-driven animations.

Pretty text

Safari 26.0 adds support for text-wrap: pretty. Our implementation of pretty adjusts how text wraps in an effort to even out the ragged edge, improve hyphenation, and prevent short last lines.

Demo of the same text, only now with better rag. The "guides" show three vertical lines about 50 pixels apart — the far right edge is marked with a red line. 45 pixels in from the left is a vertical green line. And another 45 pixels left of that line is a vertical magenta line.

In WebKit, all lines of text in an element are improved by pretty, not just a select group of lines at the end of the paragraph. To learn more, read Better typography with text-wrap pretty.

Contrast Color

Safari 26.0 adds support for the contrast-color() function. It gives you the chance to declare a color that’s either black or white, depending on which will provide more contrast with a second color.

For example, we can make a button with the background color of var(--button-color), and then ask the browser to set the text color to either black or white, whichever one provides more contrast against that background.

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

Now, when the --button-color variable is set, both the background and text colors are chosen. Try picking different colors in this demo to see it in action:

Learn much more about contrast-color(), including the accessibility implications, by reading How to have the browser pick a contrasting color in CSS.

Progress function

Safari 26.0 adds support for the CSS progress() function. It’s a math function that returns a number value representing how far along something is, how much progress it’s made between two other values.

progress(<progress-value>, <progress-start>, <progress-end>)

For example, let’s say you want to know how far along a width of a box is, compared to a specific start width and end width.

--percent-of-box-width: progress(100cqw, 300px, 600px);

Let’s imagine at a particular moment, the is container 450px wide. That’s half way in-between 300px and 600px. The progress() function will calculate this to be 50% using this formula:

(progress value - progress start value) / (progress end value - progress start value)

The result is always a number without any unit. Notice you can mix lengths with different units.

Be mindful that currently progress doesn’t clamp. So it won’t stop at 0% or 100%. It will just grow above 100%, or shrink down below 0%.

The progress() function is most powerful when used with other complex math. Combine with animations, gradients, or scroll timelines, and connect one set of conditions with another. There might be even more functions with which it could be combined coming to CSS in the future.

And more CSS

diagrams of how margin-trim affects layout — before and after.

Safari 26.0 now supports the margin-trim: block inline syntax for trimming in both directions. Learn all about margin-trim and what the block inline value does in Easier layout with margin-trim.

The overflow-block and overflow-inline properties are supported in Safari 26.0. They are the logical versions of overflow-x and overflow-y, making it even easier to write robust code that supports multiple languages.

Safari 26.0 supports the self-alignment properties align-self and justify-self in absolute positioning.

There are two features in CSS that are new since the Safari 26 beta announcements at WWDC25. Safari 26.0 now supports the animation-range, animation-range-start, animation-range-end, and animation-timeline properties for ::marker. It also adds support for allowing declarations directly inside @scope rule without a style rule ancestor.

Every site can be a web app on iOS and iPadOS

Since January 2008 with iPhone OS 1.1.3, users on iPhone could add website icons to their Home Screen for quick access. Tapping the icon opened the site in Safari. By August 2008 with iPhone OS 2.1, web developers could instead trigger their site to appear in an app-like “standalone mode” by adding the <meta name='apple-mobile-web-app-capable'> tag to the HTML head.

In 2013, the W3C began standardizing Web Application Manifests to make configuring web app behavior possible with a JSON manifest file. Browser support started in November 2014, and Safari adopted in March 2018 with iOS 11.4.

For the last 17 years, if the website had the specific meta tag or Web Application Manifest display value in it’s code, when a user added it to their Home Screen on iOS or iPadOS, tapping its icon opened it as a web app. If the website was not configured as such, tapping its icon opened the site in a browser. Users had no choice in the matter, nor visible way to understand why some sites behaved one way while others behaved another.

On Mac, we took a different approach. When introducing Web Apps on Mac in Sep 2023, we made the decision to always open websites added to the Dock as web apps. It doesn’t matter whether or not the website has a Web Application Manifest. Users get a consistent experience. Add to Dock creates a web app.

Now, we are revising the behavior on iOS 26 and iPadOS 26. By default, every website added to the Home Screen opens as a web app. If the user prefers to add a bookmark for their browser, they can disable “Open as Web App” when adding to Home Screen — even if the site is configured to be a web app. The UI is always consistent, no matter how the site’s code is configured. And the power to define the experience is in the hands of users.

iPhone in the process of adding a website to the Home Screen. The UI shows the site icon, editable name, and a toggle turned on to Open as Web App.

This change, of course, is not removing any of WebKit’s current support for web app features. If you include a Web Application Manifest with your site, the benefits it provides will be part of the user’s experience. If you define your icons in the manifest, they’re used.

We value the principles of progressive enhancement and separation of concerns. All of the same web technology is available to you as a developer, to build the experience you would like to build. Giving users a web app experience simply no longer requires a manifest file. It’s similar to how Home Screen web apps on iOS and iPadOS never required Service Workers (as PWAs do on other platforms), yet including Service Workers in your code can greatly enhance the user experience.

Simply put, there are now zero requirements for “installability” in Safari. Users can add any site to their Home Screen and open it as a web app on iOS26 and iPadOS26.

HDR images

The human eye can typically handle seeing things lit by bright light and sitting in dark shadows at the same time. The contrast your eyes see between brightness and darkness is called dynamic range, and it’s very challenging to reproduce.

As digital photography and videography improved by leaps and bounds over the years, the ability to digitally capture a dynamic range has greatly improved. The High Dynamic Range (HDR) format takes this even further, allowing you to capture both a wider dynamic range and increased color gamut, creating more vivid and realistic-looking images and video. Parallel breakthroughs in display technology have made it possible to present such images for others to view, with deep true blacks, pure bright whites and dramatic nuances in between.

WebKit shipped support for HDR video in 2020, in Safari 14.0. Now, in Safari 26.0 for iOS 26, iPadOS 26, macOS 26 and visionOS 26, WebKit adds support for HDR images on the web. You can embed images with high dynamic range into a webpage, just like other images — including images in WebGPU Canvas.

WebKit for Safari 26.0 also adds support for the new dynamic-range-limit property in CSS. This property lets you control what happens when presenting a mix of standard dynamic range (SDR) and HDR video or images together. Safari 26.0 supports the no-limit and standard values. Using no-limit tells the browser to let content be as is — HDR content is presented in HDR. Using standard converts all of the HDR content to SDR, and displays it within the limits of standard dynamic range. Doing so prevents HDR images and video from appearing overly bright or out of place next to SDR content, which can be especially helpful when users or third-parties provide content.

Immersive video and audio on visionOS

Safari in visionOS 26 now supports a wider range of immersive media, including spatial videos and Apple Immersive Video, and 180°, 360°, and Wide FOV (field of view) videos that conform to the new Apple Projected Media Profile (APMP). Embed your video on a webpage, and let users play it back immersively on a curved surface in 3D space.

A diagram of the five kinds of immersive media — Spatial, 180 degree, 360 degree, wide FOV (field of view), Apple Immersive

This support includes HTTP Live Streaming for all of these immersive media types. The existing HLS tools have been updated to support APMP segmentation, and the HLS specification has been updated with information on how to identify immersive media in an HLS manifest file.

Learn more about model and immersive video by watching What’s new for the spatial web at WWDC25.

<model> on visionOS

Now on visionOS, Safari supports the <model> element. It’s a brand new HTML element that’s similar to img or video — only now you can embed interactive 3D models into the webpage, and let users interact with them with a single attribute. And if they want to see your models in their own space at real size, they can drag the models off the page with a single gesture.

Basic usage

The syntax for showing a model is simple. Using the same USDZ files that work with AR Quick Look today, you can set the src attribute of the model element:

<model src="teapot.usdz">
  <img src="fallback/teapot.jpg" alt="a teapot">
</model>

Lighting

Lighting is an important part of making your 3D content look good, and the model element makes that straightforward too. You can apply an environment map as any image, including the high-dynamic range OpenEXR .exr and Radiance HDR .hdr formats by setting the environmentmap attribute:

<model src="teapot.usdz" environmentmap="night.hdr">
  <img src="fallback/teapot-night.jpg" alt="a teapot at night">
</model>

Animation and playback

You can work with models containing animated content too. Use the autoplay attribute to declaratively set a model’s animation to run as soon as it loads, keep the animation going using the loop attribute,

<model autoplay loop src="teapot-animated.usdz">
  <img src="fallback/teapot-animated.jpg" alt="a teapot with a stowaway!">
</model>

or use the JavaScript API for more fine-grained control:

const model = document.querySelector('model');
model.playbackRate = 0.5; //set 50% speed
model.currentTime = 6; //set the animation to 6 seconds in
model.play();

Rotation and interaction

To let users spin and tumble a model themselves, set the model’s stagemode attribute to orbit and everything will be handled for you.

<model stagemode="orbit" src="teapot.usdz">
  <img src="fallback/teapot-orbit.jpg" alt="a teapot for examining">
</model>

Or if you’re after programmatic control, models can be scaled, rotated and moved (translated) using their entityTransform property, which can takes a DOMMatrix value. You can compose these with functions like translate, rotate and scale3d to orient the model the way you want.

<model id="rotating-teapot" src="teapot.usdz">
  <img src="fallback/teapot-rotater.jpg" alt="a teapot for turning">
</model>

With this JavaScript:

const rotatingTeapot = document.getElementById("rotating-teapot");
await rotatingTeapot.ready;
function rotate() {
  rotatingTeapot.entityTransform = new DOMMatrix().rotate(0, performance.now()/10,0);
  requestAnimationFrame(rotate);
}
rotate();

There’s a lot more to discover about the model element and what you can do with it. Check out the Model element samples and the expected documentation for MDN.

Digital Credentials API

WebKit for Safari 26.0 adds support for the W3C’s Digital Credentials API. In jurisdictions that have issued such credentials, this API allows a website to securely request identity documents (like a driver’s license) from Apple Wallet or other iOS applications that have registered themselves as an Identity Document Provider.

The Digital Credential API is useful for situations where a high-trust credential is needed to access a service online (perhaps renting an automobile). It provides a much safer and user friendly alternative to, for example, a user uploading a photograph of their driver’s license.

The Digital Credentials API leverages the existing Credential Management API and introduces a “digital” member for requesting identity documents. Requesting an identity document relies on the ISO/IEC 18013-7 Annex C international standard, which is identified by the protocol string "org-iso-mdoc".

For example, to request an end-user’s driver’s license, you might do something like this. Create a button in HTML:

<button onclick="verifyIdentity">Verify Identity</button>

And then in JavaScript:

async function verifyIdentity() {
    try {
        // Server generated and cryptography signed request data.
        const response = await fetch("drivers/license/data");
        const data = await response.json();

        // Create the request.
        const request = {
            protocol: "org-iso-mdoc",
            // What is being rquested, e.g. person's driving privileges 
            data,
        };

        // Perform presentment request.
        // Must be done through a user gesture!
        const credential = await navigator.credentials.get({
            mediation: "required",
            digital: {
                requests: [request],
            },
        });

        // Send credential to server for decryption.
        const response = await fetch("/decrypt", {
            method: "POST",
            body: JSON.stringify(credential.data),
            headers: {
                'Content-Type': 'application/json'
            }
        });

        // Display it...
        const json = await response.json();
        presentDetails(json);
    } catch (err) {
        // Deal with any errors...
    }
}

New since WWDC, Digital Credentials API now includes support for the DigitalCredential.userAgentAllowsProtocol() static method. This method allows you check if a particular digital credential request protocol is allowed. For example:

   if (DigitalCredential.userAgentAllowsProtocol("org-iso-mdoc")) {
     // Create an mDoc request
   } else {
     // Fallback to some other credential request format
   }

To learn more about this transformative technology watch Verify identity documents on the web at WWDC25.

By the way, Digital Credentials is not yet supported in WKWebView. You can follow WebKit bug 268516 for updates. Also, Digital Credentials API currently has an known issue where mixed protocol requests containing both OpenID4VP and ISO 18013-7 (Annex C) protocols may cause an infinite loading spinner on iOS when scanning QR codes from Chrome on macOS during cross-device identity verification flows.

Web API

Web developers can use the Trusted Types API, now in Safari 26.0, to ensure that end user input does not lead to client-side cross-site scripting (XSS). The API guarantees that input can be sanitized using a developer-specified function before being passed to vulnerable APIs.

We’ve added support for the URL Pattern Standard, which provides an efficient and performant way for web developers to match URLs using regular expressions through the URLPattern object. For instance, if your blog posts follow the pattern of /blog/title-of-the-post you could match them as follows:

const pattern = new URLPattern({ pathname: "/blog/:title" });
pattern.test("https://example.org/blog/wwdc25"); // true
pattern.test("https://example.org/about"); // false

Coming to Safari 26.0 is the WebAuthn Signal API, which allows websites to report credential updates (like username changes or revocations) to credential providers, ensuring a more accurate and consistent user experience with passkeys. The new PublicKeyCredential.signal ** methods enable websites to communicate these changes, improving credential management and streamlining sign-in flows. This enhancement empowers websites to provide a more seamless and secure WebAuthn experience.

There’s also now support for the File System WritableStream API, enabling direct writing to files within the user’s file system. This API provides an efficient and streamlined way to save data, allowing developers to build applications with enhanced file handling capabilities, such as direct downloads and in-place file editing.

WebKit for Safari 26.0 adds support for the alg parameter when importing or exporting Edward’s-curve based JSON Web Keys in WebCrypto.

Support for scrollMargin in IntersectionObserver is here for more precise intersection detection. This allows you to define margins around the root element, similar to rootMargin, providing finer control over when intersection events are triggered.

New since Safari 26 beta 1, the <dialog> element now supports the toggle event which can be used to watch for whenever the dialog gets opened or closed. And now supports Scoped Custom Element Registry.

WebKit for Safari 26.0 also removed the getSVGDocument() method from HTMLFrameElement to align with the most recent specification.

JavaScript

WebKit for Safari 26.0 adds support for Pattern Modifiers in JavaScript’s RegExp objects. Pattern modifiers allow more fine-grained control over the behavior of regular expressions through adding and removing flags within a regular expression.

New since WWDC25, WebKit for Safari 26.0 adds support for the notation option for Intl.PluralRules and the Intl.Locale.prototype.variants getter.

SVG icons

Safari 26.0 now supports the SVG file format for icons everyplace there are icons in the interface, including favicons.

For years, favicons were just displayed in the browser window’s URL bar, or in a menu of favorites. Now, icons show up in a range of places across browsers, at wildly different sizes. That includes the Safari start page, where icons represent content in Reading List, iCloud Tabs, Suggestions and Favorites. For web apps, this same icon represents the website on the user’s Home Screen or in their Dock. And icons are, of course, used in Safari tabs and menus.

By using an SVG file for your icon, you leverage infinite vector scaling. You rely on Safari to do the work of creating rasterized icons at multiple sizes to be used in various locations. And an SVG file is also often a smaller download than the .png files commonly used for favicons.

Data URL images are also now supported for icons as well, allowing you to express small image files as code.

WebGPU

WebKit for Safari 26.0 adds support for WebGPU.

WebGPU, a JavaScript API for running programs on the GPU, is similar to WebGL in its capabilities for graphics and rendering. Additionally, it adds compute shaders, which allow general purpose computations on the GPU, something not previously possible with WebGL.

WebGPU supersedes WebGL on macOS, iOS, iPadOS, and visionOS and is preferred for new sites and web apps. It maps better to Metal, and the underlying hardware. Comparatively, WebGL required significant translation overhead due to being derived from OpenGL which was designed prior to modern GPUs.

GPU programs are provided by the website or web app using the WebGPU Shading Language, known as WGSL (pronounced wig-sill). It’s a new language that is verifiably safe for the web unlike some existing shading languages which allow for unchecked bounds accesses and pointer arithmetic.

WebGPU has been enabled in Safari Technology Preview for over a year, and is now shipping in Safari 26.0 for macOS, iOS, iPadOS, and visionOS. Given the level of hardware access provided by WebGPU, much consideration was taken to ensure WebGPU does not expose new security attack surfaces. Additionally, validation performed was streamlined recently to minimize overhead and maintain closer to native application performance.

As a developer you are most likely to leverage the power of WebGPU through a framework. Currently, Babylon.js, Three.js, Unity, PlayCanvas, Transformers.js, ONNX Runtime and others all work great in Safari 26.0.

Learn more by watching Unlock GPU computing with WebGPU at WWDC25.

Media

Safari 26.0 also adds support for ALAC and PCM audio in MediaRecorder.

const video = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(video, {
    mimeType: "audio/mp4; codecs=alac",
});

Safari 26.0 expands support for WebCodecs API by adding AudioEncoder and AudioDecoder. WebCodecs gives developers low-level access to the individual frames of a video stream and chunks of audio. These additions make it possible to encode AudioData objects and decode EncodedAudioChunk objects.

Safari 26.0 now includes several improvements for Media Source API (MSE). It adds support for detachable MediaSource objects to allow for seamless switching between objects attached to a media element. And it adds support for MediaSource prefers DecompressionSession.

And new since Safari 26 beta 1, WebKit now supports for in-band tracks in MSE.

WebRTC

WebKit brings multiple updates for WebRTC, adding support for:

  • Exposing CSRC information for RTCEncodedVideoStream
  • Speaker Selection API on iOS and iPadOS
  • Serialisation of RTCEncodedAudioFrame and RTCEncodedVideoFrame
  • ImageCapture.grabFrame
  • RTcRtpScriptTransformer.generateKeyFrame to take a rid parameter
  • RTCEncodedAudioFrame and RTCEncodedVideoFrame constructors

New since WWDC25, Webkit for Safari 26.0 now supports exposing a default system speaker device.

And Safari 26.0 removed the fec and rtx from WebRTC encoding parameters.

Editing

To further support users as they edit content on the web, Safari 26.0 adds rendering native selection UI inside scrolled content.

HTTP

Also new since our announcements at WWDC25, Safari 26.0 now adds support for WebSocket over HTTP/2 and HTTP/3.

SVG

For SVG group containers, Safari 26.0 adds support for pointer-events="bounding-box".

Website compatibility

Report a website issue

Now in Safari on macOS, iOS, and iPadOS, users can report an issue anytime they are having trouble with a webpage.

Safari on iPhone showing UI labeled "Report an issue" in the Page menu.

If you seem to have trouble that you don’t expect, first try reloading the page. If there’s still a problem, go to the Page menu, where you’ll find “Report a Website issue…” This brings up a quick set of multiple choice questions that provide the key information for us to spot patterns and better ensure a great experience in Safari.

Update to UA String

Also, now in Safari on iOS, iPadOS, and visionOS 26 the user agent string no longer lists the current version of the operating system. Safari 18.6 on iOS has a UA string of:

 Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Mobile/15E148 Safari/604.1

And Safari 26.0 on iOS has a UA string of:

 Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1

This matches the long-standing behavior on macOS, where the user agent string for Safari 26.0 is:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15` 

It was back in 2017 when Safari on Mac first started freezing the Mac OS string. Now the behavior on iOS, iPadOS, and visionOS does the same in order to minimize compatibility issues. The WebKit and Safari version number portions of the string will continue to change with each release.

Meanwhile, we highly recommend using feature detection instead of UA string detection when writing conditional code.

Web Inspector

Automatic inspection of Service Workers

To inspect a Service Worker you need to open a Web Inspector from Safari’s Develop menu. That’s because the execution context of a Service Worker is independent of the page that installed it. But the action handled by a Service Worker might have already occurred by the time you get to it via the Develop menu. This can happen, for example, with Web Push events where the Service Worker has already handled the incoming push.

To address this, Safari 26.0 introduces automatic inspection and pausing of Service Workers. This is similar to the existing feature for automatic inspection and pausing of JSContexts. To use it, open the Inspect Apps and Devices tool from the Develop menu. Identify the app or Home Screen Web App that uses a Service Worker you want to inspect and, from the three-dots menu, select the option labeled Automatically Inspect New Service Workers. The next time a Service Worker runs in that app, a Web Inspector window will open automatically for it. Use the Automatically Pause New Service Workers option to also pause JavaScript execution in the Service Worker as soon as it’s inspected. This allows you to set breakpoints and step through the code as actions are handled.

Screenshot of the Apps and Devices Inspection window

Recording Workers in the Timelines tab

Safari 26.0 makes it easier to debug Worker-related memory and performance issues using the Timelines tab in Web Inspector. Breakpoints, profiling data, events, call trees, and heap snapshots are now correctly attributed to each Worker and not its associated page. JavaScript code that runs in a Worker may also call debugger, console.profile, etc to supplement timeline data with application-specific milestones. Lastly, it is now possible to export and import data gathered from Workers in a Timeline recording.

Screenshot of Web Inspector Timelines tab, with all kinds of information about JavaScript events across time.

Slotted badge

The Elements node tree in Web Inspector now shows a badge labeled Slotted next to nodes that have been inserted into corresponding <slot> nodes within Custom Elements. Click the badge to expand the node tree into the Shadow DOM of the Custom Element and jump to the <slot> node. If there is a correspondence, the <slot> node has a badge labelled Assigned next to it. Click this badge to jump to the node from the light DOM that is slotted here.

Screenshot of Web Inspector Elements tab.

Improved async debugging experience

The Web Inspector debugger has been updated to provide a more intuitive debugging experience for asynchronous code. You can now step over an await statement as if it were synchronous, meaning the debugger will skip the underlying asynchronous mechanics and move to the next line of code in the function. This simplifies debugging because it allows you to focus on the intended logic of your code, rather than the potentially confusing execution path introduced by await.

New in Web Inspector since Safari 26 beta 1

Web Inspector adds support for two newer CSS features — @starting-style and @scope styles (in the Styles sidebar).

Safari 26.0 adds supports for the console to log both the URI and the time when entering a new navigation context. And adds supported for console.profile in Worker.

Web Inspector now supports exporting and importing data from worker targets in the Timelines tab.

WebKit in SwiftUI

WebKit has a brand-new API designed from the ground up to work with Swift and SwiftUI. This makes it easier than ever to integrate web content into apps built for Apple platforms.

The core parts of this new API are the new WebView and WebPage types.

WebView

To display your web content, simply use the new WebView type, a brand-new native SwiftUI View. All you need to do is give it a URL to display.

struct ContentView: View {
    var body: some View {
        WebView(
            url: URL(string: "https://www.webkit.org")
        )
    }
}

WebView also supports a powerful set of new and existing view modifiers, like webViewScrollPosition, webViewMagnificationGestures, findNavigator, and more. For more advanced customization, like being able to react to changes in the content, you’ll need to connect it to a WebPage.

WebPage

WebPage is a brand new Observable class that can be used to load, control, and communicate with web content. You can even use it completely on its own, in cases where you don’t need to display the page directly to your users. But when you do, combining it with WebView allows you to build rich experiences, and integrate the web into your app with ease. WebPage has a full set of observable properties and functions you can use to make reacting to changes incredibly simple, especially with SwiftUI.

The new URLSchemeHandler protocol makes it super easy to implement handling custom schemes so that local resources and files can be used in your app. It leverages the full capabilities of Swift and Swift Concurrency, and you just need to provide it with an AsyncSequence.

WebPage.NavigationDeciding is a new protocol that lets you customize how navigation policies should behave in your app across different stages of a navigation. In addition to WebPage.NavigationDeciding, there’s also WebPage.DialogPresenting to customize how dialogs presented from JS should be displayed.

struct ArticleView: View {
    @Environment(ArticleViewModel.self) private var model

    var body: some View {
        WebView(model.page)
            .navigationTitle(model.page.title)
    }
}

We look forward to seeing what Apple Developers do with the new WebPage and WebView types for Swift and SwiftUI. As a web developer, it’s now easier than ever for you to use the skills you have to create an app for iOS, iPadOS, macOS, and visionOS.

Screenshot of an iPad app, with a menu created in Swift, and content that is pulled from the web.

To learn more about the new SwiftUI WebKit API, watch Meet WebKit for SwiftUI at WWDC25, and explore the API in the developer documentation on developer.apple.com.

WebKit API

Several improvements to WebKit API are available now in iOS, iPadOS, macOS, and visionOS beta.

  • Screen Time support
  • Local storage and session storage restoration APIs for WKWebView
  • The ability to applying backdrop-filter to content behind a transparent webview

A new obscuredContentInsets property added to WKWebView allows developers to specify areas of the web view that are covered by browser UI elements like tab bars or toolbars. Set this property to automatically adjust the layout viewport so web content renders within the visible area without being obscured by overlapping interface elements.

WebKit also deprecated WKProcessPool and WKSelectionGranularity.

Web Extensions

The new web-based Safari Web Extension Packager allows developers to take their existing web extension resources and prepare them for testing in Safari through TestFlight and distribution through the App Store. The tool is available in App Store Connect and uses Xcode Cloud to package the extension resources you provide into a signed app + extension bundle that can be used in Safari on macOS, iOS, iPadOS, and visionOS. Learn more about using the tool in our documentation on developer.apple.com.

Web Extension commands are now shown in the menubar on macOS and iPadOS. On macOS, users can customize the keyboard shortcut associated with a command in Safari Settings.

Web Extensions can now be loaded in SafariDriver. This feature allows developers to test their extension in an automated setting. Using Selenium, you can register custom commands to utilize this new feature.

driver = webdriver.Safari()

driver.command_executor._commands["load_web_extension"] = (
    "POST", "/session/$sessionId/webextension"
)
driver.command_executor._commands["unload_web_extension"] = (
    "DELETE", "/session/$sessionId/webextension/$extension_id"
)

Learn more about these commands in the documentation on the WebExtensions Community Group GitHub.

New since WWDC25, Safari 26.0 adds support for Web Extension for dom.openOrClosedShadowRoot().

Content Blockers

Content blockers are a kind of extension that give Safari a set of rules to use to block content in the browser window. Blocking behaviors include hiding elements, blocking loads, and stripping cookies from Safari requests.

Safari 26.0 includes three new features for content blockers:

  • unless-frame-url
  • the request-method content blocker trigger field
  • isContentRuleListRedirect

WebAssembly

As WebAssembly continues to grow in popularity, WebKit has been improving WebAssembly performance across the board. Now, WebAssembly is first evaluated by our new in-place interpreter. This allows large WebAssembly modules to launch even faster and use less memory, while retaining the same top end throughput after warming up.

Networking

WebKit now supports <link rel=dns-prefetch> on iOS, iPadOS, and visionOS. It gives a hint to the browser to perform a DNS lookup in the background to improve performance. Supported on macOS since Safari 5, it now has improved privacy.

Privacy

In our continuing efforts to improve privacy and protect users, Safari 26.0 now prevents known fingerprinting scripts from reliably accessing web APIs that may reveal device characteristics, such as screen dimensions, hardware concurrency, the list of voices available through the SpeechSynthesis API,  Pay payment capabilities, web audio readback, 2D canvas and more. Safari additionally prevents these scripts from setting long-lived script-written storage such as cookies or LocalStorage. And lastly, Safari prevents known fingerprinting scripts from reading state that could be used for navigational tracking, such as query parameters and document.referrer.

Lockdown Mode

Available on iOS, iPadOS, watchOS, and macOS, Lockdown Mode is an optional, extreme protection that’s designed for the very few individuals who, because of who they are or what they do, might be personally targeted by some of the most sophisticated digital threats. This includes limiting some of what websites can do to ensure the highest level of protection.

Since its beginning, Lockdown Mode disallowed the use of most web fonts. Now instead, web fonts are evaluated by the new Safe Font Parser, and if they pass the evaluation, they are allowed. This means almost all content will be displayed using the specified web fonts in Lockdown Mode.

Device management

Device management lets an administrator securely and remotely configure devices. It’s often used when a fleet of devices is used by a lot of people at work or school, and the administrator responsible for those devices needs tooling for more easily taking care of them all.

Safari 26.0 adds two new features to further support device management. Now a managed device can have a folder of managed bookmarks. And a managed device can have managed new tab or new window page (home page, blank page, extension new tab page).

Security

Security improvements in Safari 26.0 include adding support enforcing the Integrity-Policy header on script destinations. And adding a new configuration to support Google Safe Browsing version 5 traffic to Safari and WebKit clients with the web browser entitlement.

Bug fixes and more

Along with all of these new features, WebKit for Safari 26.0 includes a plethora of fixes to existing features.

Accessibility

  • Fixed aria-expanded attribute support on navigation links. (141163086)
  • Fixed presentational images with empty alt attributes to be ignored by assistive technology, even when additional labeling attributes are set. (146429365)
  • Fixed <figcaption> within a <figure> element to only contribute to the accessible name of an <img> element if the image lacks other labeling methods like alt, ARIA attributes, or the title attribute. (150597445)
  • Fixed handling of invalid values for aria-setsize and aria-posinset according to the most-recent revision of the ARIA specification. (151113693)
  • Fixed VoiceOver reading “Processing page %infinity” when loading large pages. (152617082)
  • Fixed VoiceOver failing to output newlines in certain circumstances when using caret navigation. (154368379)
  • Fixed an issue where dynamic changes to iframe display properties could cause the iframe’s scroll view to incorrectly become the accessibility root, preventing assistive technologies from accessing content outside the iframe. (156440342)
  • Fixed CSS content alt text when used on an element to be announced by VoiceOver. (156666741)

Browser

  • Fixed keyboard typing to cancel voice dictation. (152597958)
  • Fixed: Safari now reports a frozen OS version in its user agent string on iOS 26 and iPadOS 26, showing the last version released before iOS 26. (156170132)

CSS

  • Fixed cursor: pointer not appearing on an <area> element used in conjunction with an <img usemap="..."> element. (74483873)
  • Fixed: Apply space from align-content when grid container and rows have definite sizes during column sizing (85252183)
  • Fixed <frame> and <frameset> to always be in-flow and non-floating. (102670652)
  • Fixed grid sizing with inline-size containment and auto-fit columns is incorrectly sized. (108897961)
  • Fixed “inherit” as a variable substitution fallback when setting custom property. (136463977)
  • Fixed content skipped with content-visibility: auto to be findable. (141237620)
  • Fixed an issue wrapping an SVG at the end of a line when using text-wrap: balance. (141532036)
  • Fixed @font-face font-family descriptor to not allow a list of values. (142009630)
  • Fixed the computed value of a float with absolute positioning to be none when there is no box. (144045558)
  • Fixed buttons to not have align-items: flex-start by default. (146615626)
  • Fixed style container query on :host CSS pseudo-class to be correctly applied to slotted elements. (147684247)
  • Fixed @scope to create a style rule with a nested context. (148101373)
  • Fixed changing content-visibility from visible to hidden to repaint correctly. (148273903)
  • Fixed an issue where float boxes, selections, and carets were incorrectly painted inside skipped subtrees. (148741142)
  • Fixed incorrect getBoundingClientRect() inside skipped subtree on an out-of-flow positioned box. (148770252)
  • Fixed making <pre> and other elements use logical margins in the User-Agent stylesheet. (149212392)
  • Fixed space-around and space-evenly to fallback to safe center for align-content. (153403381)
  • Fixed the serialization of <color> custom properties to provide the used value. (153675017)

Canvas

  • Fixed re-drawing a canvas with relative width when the parent element is resized. (121996660)
  • Fixed getContext('2d', { colorSpace: 'display-p3' }) in iOS Simulator. (151188818)

DOM

  • Fixed the serialization of CDATASection nodes in HTML. (150739105)

Editing

  • Fixed the selection UI to be clipped in overflow scrolling containers. (9906345)
  • Fixed selection issues caused by <br> elements between absolute positioned elements. (123637358)
  • Fixed selection failing to update during auto or keyboard scrolling. (144581646)

Forms

  • Fixed form associated ElementInternals always reporting a customError when using setValidity. (115681066)
  • Fixed setValidity of ElementInternals to handle missing optional anchor parameter. (123744294)
  • Fixed updating scrollbar appearance correctly for the page and <textarea> elements. (151496190)
  • Fixed programmatically assigned File objects to display the correct filename in <input> elements, even without a file path. (152048377)
  • Fixed labels inside <select> elements to behave consistently with other browsers by using standard attribute matching instead of quirk mode handling. (152151133)
  • Fixed allowing the custom element itself to be passed as validation anchor in the setValidity() API. (154303420)
  • Fixed the intrinsic size of number inputs when the spin button width is a percentage value. (154680747)

Images

  • Fixed zoomed <img> to not cause unwanted rounding of width and height. (150473104)

JavaScript

  • Fixed Array.prototype.pop to throw an exception when the array is frozen. (141805240)
  • Fixed performance of Math.hypot() that was significantly slower than Math.sqrt(). (141821484)
  • Fixed RegExp#[Symbol.search] to throw TypeError when lastIndex isn’t writable. (146488846)
  • Fixed Array#indexOf and Array#includes to treat +0 and -0 as the same value. (148472519)
  • Fixed iterator helpers incorrectly closing iterators on early errors. (148774612)
  • Fixed Iterator.prototype.reduce failing with an undefined initial parameter. (149470140)
  • Fixed: Aligned f() = 1 behavior with other engines when not using strict mode. (149831750)
  • Fixed nested negated classes resulting in incorrect matches. (151000852)
  • Fixed DateTime string parsing for ISO8601 inputs. (153679940)
  • Fixed toIntegerOrInfinity to truncate negative fractional values to +0.0. (153939418)
  • Fixed the order of function’s special properties returned by Object.keys and Object.entries. (155607661)

Media

  • Fixed picture-in-picture to exit when the video element is removed. (123869436)
  • Fixed MP4 seeking with b-frames to prevent out-of-order frame display by suppressing frames with earlier presentation timestamps following the seek point. (140415210)
  • Fixed media elements on iPadOS to support the volume being changed by web developers, similar to macOS and visionOS. The :volume-locked pseudo-class can continue to be used for feature detection. (141555604)
  • Fixed seeking or scrubbing not always seeking to the time requested. (142275903)
  • Fixed stale audio buffer data after seeking when playing sound through an AudioContext. (146057507)
  • Fixed subtitle tracks with no srclang to be shown with the correct label. (147722563)
  • Fixed MediaSession to handle SVG icons with subresources. (150665852)
  • Fixed MediaCapabilitiesDecodingInfo.configuration to be correctly populated even when .supported is false. (150680756)
  • Fixed video elements with WebM object URLs causing MediaError code 2. (151234095)

PDF

  • Fixed “Open with Preview” button to open a PDF in the Preview app. (148680145)

Rendering

  • Fixed overflow: hidden to not clip filter: drop-shadow(). (72205047)
  • Fixed a list-style-position: inside list item marker to be rendered as the first child of the list item. (79587134)
  • Fixed using setDragImage with a fixed-position element, so that the drag preview bitmap includes the correct content. (90120656)
  • Fixed an issue to allow images in scroll containers to load when they are near the viewport rather than when they are intersecting the viewport. (118706766)
  • Fixed CSS filters to establish a containing block like transform does. (119130847)
  • Fixed a disappearing stretched image in a vertical flexbox layout. (135897530)
  • Fixed CSS gradient interpolation for “longer hue” gradients when an end color stop is omitted. (142738948)
  • Fixed will-change: view-transition-name to create a stacking context and a backdrop root. (146281670)
  • Fixed will-change: offset-path to create a stacking context and a containing block. (146292698)
  • Fixed <datalist> dropdowns not displaying option labels. (146921617)
  • Fixed the text indicator sometimes getting clipped during a bounce animation. (147602900)
  • Fixed geometry values inside content-visibility: hidden subtrees. (148553259)
  • Fixed not marking content-visibility: hidden content for layout when targeting content-visibility: auto. (148663896)
  • Fixed incorrect ruby annotation positioning in sideways-lr. (148713073)
  • Fixed: Prevented hit testing content inside a skipped subtree. (148741508)
  • Fixed an issue where feMerge incorrectly positioned HTML elements when merging the same feMergeNode multiple times. (149431216)
  • Fixed box-shadow with spread on a border-radius box to scale the radii correctly. (149490613)
  • Fixed an issue in determining when a flex item should be used for percentage resolution during intrinsic width computation. (149615295)
  • Fixed an issue causing a <canvas> element to disappear for one frame if a view transition occurs. (149709642)
  • Fixed <div contenteditable> within an <iframe> not scrolling into the viewport on receiving focus for the second time. (150521759)
  • Fixed invisible <audio> controls when transformed due to incorrect coordinate space calculations for clipped child elements. (150526971)
  • Fixed centering text for <input type=button> elements with display: flex. (151148821)
  • Fixed showing a resize cursor even when text overlaps the resize control. (151309503)
  • Fixed SVG transform translate(X) not equal to translate(X,0). (151643419)
  • Fixed border-image repaint code is broken in some writing modes. (152396671)
  • Fixed rendering an image with a filter and mix-blend-mode only getting filtered but not mixed. (152460888)
  • Fixed box-shadow to repaint correctly in vertical-rl and horizontal-bt writing modes. (152803240)
  • Fixed border to no longer be adjusted in computed style for elements with native appearance (153152167)
  • Fixed margin-trim to not trim inline margins on block-level boxes, regardless of their position. (153240895)
  • Fixed text-wrap-style to not constrain single line content. (153755326)
  • Fixed inputs within inline-block containers shifting vertically when text is deleted and re-entered into an input. (154094432)
  • Fixed baseline alignment participation to expand to items with automatic logical width in the alignment axis. (154311395)
  • Fixed grid containers incorrectly processing first-letter pseudo-elements when they should not contribute a first formatted line according to the CSS Grid specification. (154504582)
  • Fixed grid items hit-testing order to align with painting order. (154990290)

SVG

  • Fixed SVG paint server fallback handling for a non-existent URL. (144493507)
  • Fixed respecting the CSS image-rendering property when drawing an SVG. (144507619)
  • Fixed handling url(...) none to match the SVG Paint Server specification. (146454258)
  • Fixed ancestor bounding box for “disabled” <foreignObject> and <image>. (147455573)
  • Fixed: Improved handling of SVG images with subresources. (148607855)
  • Fixed handling of auto for rx and ry on <ellipse>. (153274593)

Safari View Controller

  • Fixed lvh and vh viewport units getting incorrectly sized relative to the small viewport in SFSafariViewController. (108380836)

Scrolling

  • Fixed selection does not update during autoscroll when selecting with a gesture or a mouse. (144744443)
  • Fixed autoscrolling for smooth scrolling while selecting text. (144900491)
  • Fixed inconsistent decimal values from getBoundingClientRect for sticky elements. (147163986)
  • Fixed scroll compensation transform to be applied before any other transforms. (155992464)

Service Workers

  • Fixed the ReadableStream cancel method not getting reliably called in Service Worker. (144297119)
  • Fixed an issue where navigation preload responses incorrectly retained a redirection flag when served from disk cache, causing security check failures during loading. (144571433)
  • Fixed structureClone to preserve Error.cause. (152725880)

Spatial Web

  • Fixed various issues related to spatial audio not working in visionOS that could occur when repositioning Safari widows or moving a tab to a new window. (145661522)
  • Fixed the shape of gaze glow regions for elements with associated labels when the element has non-uniform border radii or if the element is styled with clip-path. (154258426)

Text

  • Fixed generating text fragments around text that contains newlines. (137109344)
  • Fixed generating text fragments when the selected text starts and ends in different blocks. (137761701)
  • Fixed bold synthesis to be less aggressive. (138047199)
  • Fixed Copy Link with Highlight not working when selecting text that is its own block and when that text exists higher up in the document. (144392379)
  • Fixed selections that start or end in white space not creating text fragments. (145614181)
  • Fixed <b> and <strong> to use font-weight: bolder to match the Web Specification. (146458131)
  • Fixed Korean counter styles to be aligned with manual Korean numbering in lists. (152969810)
  • Fixed content spacing for elements with text-align: justify and white-space: pre-wrap applied. (154211168)

URLs

  • Fixed percent-encoding ^ in non-opaque URL paths. (146233526)
  • Fixed ensuring that opaque URL paths always roundtrip. (146848690)
  • Fixed making URL host and hostname setters handle @ correctly. (146886347)
  • Fixed Windows drive letter after file:/// when parsing URLs. (147381130)

Web API

  • Fixed: URL’s protocol setter should forbid switching non-special to special schemes. (82549495)
  • Fixed event dispatching to be done by the fullscreen rendering update steps. (103209495)
  • Fixed the mousemove event to be fired when the mouse stays in the document but there is no element. (120551245)
  • Fixed an overly broad fullscreen exit trigger by restricting it to only text-entry elements gaining focus, preventing non-text input types from causing unexpected fullscreen exits. (136726993)
  • Fixed WKDownload.originatingFrame of downloads originated without a frame. (145328556)
  • Fixed fullscreen to use a single queue for event dispatching. (145372389)
  • Fixed the ProgressEvent members loaded and total to use the double type as per a recent specification change. (146356214)
  • Fixed Intrinsic Sizing of SVG embedded via <embed> to be invalidated on navigation. (147198632)
  • Fixed an issue where pending utterances do not receive an error event when speech synthesis is cancelled. (148731039)
  • Fixed escaping < and > when serializing HTML attribute values. (150520333)
  • Fixed making the SpeechRecognition interface available only within a secure context. (151240414)
  • Fixed the <option> element to not trim the label value and correctly handle an empty label. (151309514)
  • Fixed IntersectionObvserver to notify observers asynchronously. (152684301)
  • Fixed setting innerHTML to correctly use a scoped custom element registry associated with the context object. (154333132)
  • Fixed attachShadow throwing type error with a ShadowRoot document-fragment. (154658449)

Web Animations

  • Fixed CSS scroll-driven animations on pages using requestAnimationFrame to animate correctly after navigating away and back to the page. (141528296)
  • Fixed computing the time offset as needed when applying accelerated actions. (142604875)

Web Apps

  • Fixed the “Add to Home Screen” flow failing to load webpage data, preventing users from making new Home Screen web apps. (154655565)

Web Extensions

  • Fixed tabs.update to not remove history from the target tab. (134939755)
  • Fixed including the extension’s icon in the commands menu item and prevented customization using System Settings. (135360504)
  • Fixed a bug where the runtime.MessageSender origin parameter would be lowercased, differing from the result returned from runtime.getURL. (140291738)
  • Fixed high priority redirects to supercede low priority blocks for declarativeNetRequest. (145241581)
  • Fixed "excludeMatches" array in scripting.registerContentScripts() API getting ignored in Safari web extensions. (145489255)
  • Fixed a declarativeNetRequest bug that prevents redirects to extension resources. (145569361)
  • Fixed processing of declarativeNetRequest rules so that higher numbers are treated as higher priority. (145570245)
  • Fixed an issue causing wasm-unsafe-eval to not get parsed as a valid CSP keyword. (147551225)
  • Fixed permissions.getAll() to return the correct origins if all urls and/or hosts match pattern(s) have been granted. (147872012)
  • Fixed a non-fatal webRequest error for non-persistent background content. (150051544)
  • Fixed allowAllRequests declarativeNetRequest rules so that a higher priority correctly overrides a lower-priority block rule. (152746422)
  • Fixed CSS display: none matching everything still getting applied even after an ignore-following-rules action was matched. (152996225)
  • Fixed calling scripting.registerContentScripts() sometimes returning the error: “Error: Invalid call to scripting.registerContentScripts(). Failed to add content script.” (153001967)

Web Inspector

  • Fixed pretty-printing CSS to avoid adding a space after the universal selector () when followed by a pseudo-class or pseudo-element, preventing unintended changes to CSS selector behavior. (71544976) Fixed to show a separate overview for each target in the Timelines tab. (146356054)
  • Fixed a performance issue when blackboxing a large number of sourcemaps. (148116377)
  • Fixed the debugger to step over an await statement as though it is synchronous code. (149133320)
  • Fixed parsing sourcemaps asynchronously so that large sourcemaps do not block rendering. (151269154)
  • Fixed the Timelines tab to consistently display the target’s hierarchical path for JavaScript and Events to prevent confusion when working with multiple targets. (152357197)
  • Fixed clicking on the “+” button in the Sources tab sidebar doing nothing when Web Inspector is undocked. (153193833)
  • Fixed Quick Open dialog to show results when an Inspector Bootstrap script exists. (154947309)

WebKit API

  • Fixed a crash at launch in iOS Simulator for apps built for older deployment targets that bind to specific WebKit API. (152200884)

WebRTC

  • Fixed switching from speaker to receiver does not work the first time, but only the second time. (141685006)
  • Fixed enumerateDevices returning devices as available when permissions are denied. (147313922)
  • Fixed enumerateDevices to not check for device permission. (148094614)
  • Fixed WebRTC encoded transform to transfer to the RTC encoded frame array buffer. (148343876)
  • Fixed RTC encoded frame timestamp should be persistent. (148580865)
  • Fixed the configurationchange event to fire when a microphone’s audio unit changes its echo cancellation mode, ensuring web pages are notified of such changes to update track settings accordingly. (150770940)

Feedback

We love hearing from you. To share your thoughts, find our web evangelists online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn. If you run into any issues, we welcome your feedback on Safari UI (learn more about filing Feedback), or your WebKit bug report about web technologies or Web Inspector. If you run into a website that isn’t working as expected, please file a report at webcompat.com. Filing issues really does make a difference.

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

Updating to Safari 26.0

Safari 26.0 is available on iOS 26, iPadOS 26, macOS Tahoe, macOS Sequoia, macOS Sonoma, and in visionOS 26.

If you are running macOS Sequoia or macOS Sonoma, you can update Safari by itself, without updating macOS. Go to  > System Settings > General > Software Update. Under the heading “Also Available” you’ll find Safari listed.

To get the latest version of Safari on iPhone, iPad or Apple Vision Pro, go to Settings > General > Software Update, and tap to update.

September 15, 2025 05:00 PM

September 08, 2025

Igalia WebKit Team: WebKit Igalia Periodical #37

Igalia WebKit

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

In this week's installment of the periodical, we have better spec compliance of JavaScriptCore's implementation of Temporal, an improvement in how gamepad events are handled, WPE WebKit now implements a helper class which allows test baselines to be aligned with other ports, and finally, an update on recent work on Sysprof.

Cross-Port 🐱

Until now, unrecognized gamepads didn't emit button presses or axis move events if they didn't map to the standard mapping layout according to W3C (https://www.w3.org/TR/gamepad/#remapping). Now we ensure that unrecognized gamepads always map to the standard layout, so events are always emitted if a button is pressed or the axis is moved.

JavaScriptCore 🐟

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

In the JavaScriptCore (JSC) implementation of Temporal, the compare() method on Temporal durations was modified to follow the spec, which increases the precision with which comparisons are made. This is another step towards a full spec-compliant implementation of Temporal in JSC.

WPE WebKit 📟

Added a specific implementation for helper class ImageAdapter for WPE. This class allows to load image resources that until now were only shipped in WebKitGTK and other ports. This change has aligned many WPE specific test baselines with the rest of WebKit ports, which were now removed.

Community & Events 🤝

Sysprof has received a variety of new features, improvements, and bugfixes, as part of the integration with Webkit. We continued pushing this front in the past 6 months! A few highlights:

  • An important bug with counters was fixed, and further integration was added to WebKit
  • It is now possible to hide marks from the waterfall view
  • Further work on the remote inspector integration, wkrictl, was done

Read more here!

Screenshot of Sysprof showing Mesa and WebKit marks

That’s all for this week!

By Igalia WebKit Team at September 08, 2025 07:46 PM

Georges Stavracas: Marks and counters in Sysprof

Igalia WebKit

Last year the Webkit project started to integrate its tracing routines with Sysprof. Since then, the feedback I’ve received about it is that it was a pretty big improvement in the development of the engine! Yay.

People started using Sysprof to have insights about the internal states of Webkit, gather data on how long different operations took, and more. Eventually we started hitting some limitations in Sysprof, mostly in the UI itself, such as lack of correlational and visualization features.

Earlier this year a rather interesting enhancement in Sysprof was added: it is now possible to filter the callgraph based on marks. What it means in practice is, it’s now possible to get statistically relevant data about what’s being executed during specific operations of the app.

In parallel to WebKit, recently Mesa merged a patch that integrates Mesa’s tracing routines with Sysprof. This brought data from yet another layer of the stack, and it truly enriches the profiling we can do on apps. We now have marks from the DRM vblank event, the compositor, GTK rendering, WebKit, Mesa, back to GTK, back to the compositor, and finally the composited frame submitted to the kernel. A truly full stack view of everything.

Screenshot of Sysprof showing Mesa and Webkit marks

So, what’s the catch here? Well, if you’re an attentive reader, you may have noticed that the marks counter went from this last year:

Screenshot of the marks tab with 9122 marks

To this, in March 2025:

Screenshot of the marks tab with 35068 marks

And now, we’re at this number:

Screenshot of the marks tab with 3243352 marks

I do not jest when I say that this is a significant number! I mean, just look at this screenshot of a full view of marks:

Screenshot of the Sysprof window resized to show all marks. It's very tall.

Naturally, this is pushing Sysprof to its limits! The app is starting to struggle to handle such massive amounts of data. Having so much data also starts introducing noise in the marks – sometimes, for example, you don’t care about the Mesa marks, or the WebKit marks, of the GLib marks.

Hiding Marks

The most straightforward and impactful improvement that could be done, in light of what was explained above, was adding a way to hide certain marks and groups.

Sysprof heavily uses GListModels, as is trendy in GTK4 apps, so marks, catalogs, and groups are all considered lists containing lists containing items. So it felt natural to wrap these items in a new object with a visible property, and filter by this property, pretty straightforward.

Except it was not 🙂

Turns out, the filtering infrastructure in GTK4 did not support monitoring items for property changes. After talking to GTK developers, I learned that this was just a missing feature that nobody got to implementing. Sounded like a great opportunity to enhance the toolkit!

It took some wrestling, but it worked, the reviews were fantastic and now GtkFilterListModel has a new watch-items property. It only works when the the filter supports monitoring, so unfortunately GtkCustomFilter doesn’t work here. The implementation is not exactly perfect, so further enhancements are always appreciated.

So behold! Sysprof can now filter marks out of the waterfall view:

Counters

Another area where we have lots of potential is counters. Sysprof supports tracking variables over time. This is super useful when you want to monitor, for example, CPU usage, I/O, network, and more.

Naturally, WebKit has quite a number of internal counters that would be lovely to have in Sysprof to do proper integrated analysis. So between last year and this year, that’s what I’ve worked on as well! Have a look:

Image of Sysprof counters showing WebKit information

Unfortunately it took a long time to land some of these contributions, because Sysprof seemed to be behaving erratically with counters. After months fighting with it, I eventually figured out what was going on with the counters, and wrote the patch with probably my biggest commit message this year (beat only by few others, including a literal poem.)

Wkrictl

WebKit also has a remote inspector, which has stats on JavaScript objects and whatnot. It needs to be enabled at build time, but it’s super useful when testing on embedded devices.

I’ve started working on a way to extract this data from the remote inspector, and stuff this data into Sysprof as marks and counters. It’s called wkrict. Have a look:

This is far from finished, but I hope to be able to integrate this when it’s more concrete and well developed.

Future Improvements

Over the course of an year, the WebKit project went from nothing to deep integration with Sysprof, and more recently this evolved into actual tooling built around this integration. This is awesome, and has helped my colleagues and other contributors to contribute to the project in ways it simply wasn’t possible before.

There’s still *a lot* of work to do though, and it’s often the kind of work that will benefit everyone using Sysprof, not only WebKit. Here are a few examples:

  • Integrate JITDump symbol resolution, which allows profiling the JavaScript running on webpages. There’s ongoing work on this, but needs to be finished.
  • Per-PID marks and counters. Turns out, WebKit uses a multi-process architecture, so it would be better to redesign the marks and counters views to organize things by PID first, then groups, then catalogs.
  • A new timeline view. This is strictly speaking a condensed waterfall view, but it makes it more obvious the relationship between “inner” and “outer” marks.
  • Performance tuning in Sysprof and GTK. We’re dealing with orders of magnitude more data than we used to, and the app is starting to struggle to keep up with it.

Some of these tasks involve new user interfaces, so it would be absolutely lovely if Sysprof could get some design love from the design team. If anyone from the design team is reading this, we’d love to have your help 🙂

Finally, after all this Sysprof work, Christian kindly offered me to help co-maintain the project, which I accepted. I don’t know how much time and energy I’ll be able to dedicate, but I’ll try and help however I can!

I’d like to thank Christian Hergert, Benjamin Otte, and Matthias Clasen for all the code reviews, for all the discussions and patience during the influx of patches.

By Georges Stavracas at September 08, 2025 03:04 PM

September 05, 2025

Pawel Lampe: The problem of storing the damage

Igalia WebKit

This article is a continuation of the series on damage propagation. While the previous article laid some foundation on the subject, this one discusses the cost (increased CPU and memory utilization) that the feature incurs, as this is highly dependent on design decisions and the implementation of the data structure used for storing damage information.

From the perspective of this article, the two key things worth remembering from the previous one are:

  • The damage propagation is an optional WPE/GTK WebKit feature that — when enabled — reduces the browser’s GPU utilization at the expense of increased CPU and memory utilization.
  • On the implementation level, the damage is almost always a collection of rectangles that cover the changed region.

The damage information #

Before diving into the problem and its solutions, it’s essential to understand basic properties of the damage information.

The damage nature #

As mentioned in the section about damage of the previous article, the damage information describes a region that changed and requires repainting. It was also pointed out that such a description is usually done via a collection of rectangles. Although sometimes it’s better to describe a region in a different way, the rectangles are a natural choice due to the very nature of the damage in the web engines that originates from the box model.

A more detailed description of the damage nature can be inferred from the Pipeline details section of the previous article. The bottom line is, in the end, the visual changes to the render tree yield the damage information in the form of rectangles. For the sake of clarity, such original rectangles may be referred to as raw damage.

In practice, the above means that it doesn’t matter whether, e.g. the circle is drawn on a 2D canvas or the background color of some block element changes — ultimately, the rectangles (raw damage) are always produced in the process.

Approximating the damage #

As the raw damage is a collection of rectangles describing a damaged region, the geometrical consequence is that there may be more than one set of rectangles describing the same region. It means that raw damage could be stored by a different set of rectangles and still precisely describe the original damaged region — e.g. when raw damage contains more rectangles than necessary. The example of different approximations of a simple raw damage is depicted in the image below:

Raw damage approximated multiple ways.

Changing the set of rectangles that describes the damaged region may be very tempting — especially when the size of the set could be reduced. However, the following consequences must be taken into account:

  • The damaged region could shrink when some damaging information would be lost e.g. if too many rectangles would be removed.
  • The damaged region could expand when some damaging information would be added e.g. if too many or too big rectangles would be added.

The first consequence may lead to visual glitches when repainting. The second one, however, causes no visual issues but degrades performance since a larger area (i.e. more pixels) must be repainted — typically increasing GPU usage. This means the damage information can be approximated as long as the trade-off between the extra repainted area and the degree of simplification in the underlying set of rectangles is acceptable.

The approximation mentioned above means the situation where the approximated damaged region covers the original damaged region entirely i.e. not a single pixel of information is lost. In that sense, the approximation can only add extra information. Naturally, the lower the extra area added to the original damaged region, the better.

The approximation quality can be referred to as damage resolution, which is:

  • low — when the extra area added to the original damaged region is significant,
  • high — when the extra area added to the original damaged region is small.

The examples of low (left) and high (right) damage resolutions are presented in the image below:

Various damage resolutions.

The problem #

Given the description of the damage properties presented in the sections above, it’s evident there’s a certain degree of flexibility when it comes to processing damage information. Such a situation is very fortunate in the context of storing the damage, as it gives some freedom in designing a proper data structure. However, before jumping into the actual solutions, it’s necessary to understand the problem end-to-end.

The scale #

The Pipeline details section of the previous article introduced two basic types of damage in the damage propagation pipeline:

  • layer damage — the damage tracked separately for each layer,
  • frame damage — the damage that aggregates individual layer damages and consists of the final damage of a given frame.

Assuming there are L layers and there is some data structure called Damage that can store the damage information, it’s easy to notice that there may be L+1 instances of Damage present at the same time in the pipeline as the browser engine requires:

  • L Damage objects for storing layer damage,
  • 1 Damage object for storing frame damage.

As there may be a lot of layers in more complex web pages, the L+1 mentioned above may be a very big number.

The first consequence of the above is that the Damage data structure in general should store the damage information in a very compact way to avoid excessive memory usage when L+1 Damage objects are present at the same time.

The second consequence of the above is that the Damage data structure in general should be very performant as each of L+1 Damage objects may be involved into a considerable amount of processing when there are lots of updates across the web page (and hence huge numbers of damage rectangles).

To better understand the above consequences, it’s essential to examine the input and the output of such a hypothetical Damage data structure more thoroughly.

The input #

There are 2 kinds of Damage data structure input:

  • other Damage,
  • raw damage.

The Damage becomes an input of other Damage in some situations, happening in the middle of the damage propagation pipeline when the broader damage is being assembled from smaller chunks of damage. What it consists of depends purely on the Damage implementation.

The raw damage, on the other hand, becomes an input of the Damage always at the very beginning of the damage propagation pipeline. In practice, it consists of a set of rectangles that are potentially overlapping, duplicated, or empty. Moreover, such a set is always as big as the set of changes causing visual impact. Therefore, in the worst case scenario such as drawing on a 2D canvas, the number of rectangles may be enormous.

Given the above, it’s clear that the hypothetical Damage data structure should support 2 distinct input operations in the most performant way possible:

  • add(Damage),
  • add(Rectangle).

The output #

When it comes to the Damage data structure output, there are 2 possibilities either:

  • other Damage,
  • the platform API.

The Damage becomes the output of other Damage on each Damage-to-Damage append that was described in the subsection above.

The platform API, on the other hand, becomes the output of Damage at the very end of the pipeline e.g. when the platform API consumes the frame damage (as described in the pipeline details section of the previous article). In this situation, what’s expected on the output technically depends on the particular platform API. However, in practice, all platforms supporting damage propagation require a set of rectangles that describe the damaged region. Such a set of rectangles is fed into the platforms via APIs by simply iterating the rectangles describing the damaged region and transforming them to whatever data structure the particular API expects.

The natural consequence of the above is that the hypothetical Damage data structure should support the following output operation — also in the most performant way possible:

  • forEachRectangle(...).

The problem statement #

Given all the above perspectives, the problem of designing the Damage data structure can be summarized as storing the input damage information to be accessed (iterated) later in a way that:

  1. the performance of operations for adding and iterating rectangles is maximal (performance),
  2. the memory footprint of the data structure is minimal (memory footprint),
  3. the stored region covers the original region and has the area as close to it as possible (damage resolution).

With the problem formulated this way, it’s obvious that this is a multi-criteria optimization problem with 3 criteria:

  1. performance (maximize),
  2. memory footprint (minimize),
  3. damage resolution (maximize).

Damage data structure implementations #

Given the problem of storing damage defined as above, it’s possible to propose various ways of solving it by implementing a Damage data structure. Before diving into details, however, it’s important to emphasize that the weights of criteria may be different depending on the situation. Therefore, before deciding how to design the Damage data structure, one should consider the following questions:

  • What is the proportion between the power of GPU and CPU in the devices I’m targeting?
  • What are the memory constraints of the devices I’m targeting?
  • What are the cache sizes on the devices I’m targeting?
  • What is the balance between GPU and CPU usage in the applications I’m going to optimize for?
    • Are they more rendering-oriented (e.g. using WebGL, Canvas 2D, animations etc.)?
    • Are they more computing-oriented (frequent layouts, a lot of JavaScript processing etc.)?

Although answering the above usually points into the direction of specific implementation, usually the answers are unknown and hence the implementation should be as generic as possible. In practice, it means the implementation should not optimize with a strong focus on just one criterion. However, as there’s no silver bullet solution, it’s worth exploring multiple, quasi-generic solutions that have been researched as part of Igalia’s work on the damage propagation, and which are the following:

  • Damage storing all input rects,
  • Bounding box Damage,
  • Damage using WebKit’s Region,
  • R-Tree Damage,
  • Grid-based Damage.

All of the above implementations are being evaluated along the 3 criteria the following way:

  1. Performance
    • by specifying the time complexity of add(Rectangle) operation as add(Damage) can be transformed into the series of add(Rectangle) operations,
    • by specifying the time complexity of forEachRectangle(...) operation.
  2. Memory footprint
    • by specifying the space complexity of Damage data structure.
  3. Damage resolution
    • by subjectively specifying the damage resolution.

Damage storing all input rects #

The most natural — yet very naive — Damage implementation is one that wraps a simple collection (such as vector) of rectangles and hence stores the raw damage in the original form. In that case, the evaluation is as simple as evaluating the underlying data structure.

Assuming a vector data structure and O(1) amortized time complexity of insertion, the evaluation of such a Damage implementation is:

  1. Performance
    • insertion is O(1) ✅
    • iteration is O(N) ❌
  2. Memory footprint
    • O(N) ❌
  3. Damage resolution
    • perfect

Despite being trivial to implement, this approach is heavily skewed towards the damage resolution criterion. Essentially, the damage quality is the best possible, yet the expense is a very poor performance and substantial memory footprint. It’s because a number of input rects N can be a very big number, thus making the linear complexities unacceptable.

The other problem with this solution is that it performs no filtering and hence may store a lot of redundant rectangles. While the empty rectangles can be filtered out in O(1), filtering out duplicates and some of the overlaps (one rectangle completely containing the other) would make insertion O(N). Naturally, such a filtering would lead to a smaller memory footprint and faster iteration in practice, however, their complexities would not change.

Bounding box Damage #

The second simplest Damage implementation one can possibly imagine is the implementation that stores just a single rectangle, which is a minimum bounding rectangle (bounding box) of all the damage rectangles that were added into the data structure. The minimum bounding rectangle — as the name suggests — is a minimal rectangle that can fit all the input rectangles inside. This is well demonstrated in the picture below:

Bounding box.

As this implementation stores just a single rectangle, and as the operation of taking the bounding box of two rectangles is O(1), the evaluation is as follows:

  1. Performance
    • insertion is O(1) ✅
    • iteration is O(1) ✅
  2. Memory footprint
    • O(1) ✅
  3. Damage resolution
    • usually low ⚠️

Contrary to the Damage storing all input rects, this solution yields a perfect performance and memory footprint at the expense of low damage resolution. However, in practice, the damage resolution of this solution is not always low. More specifically:

  • in the optimistic cases (raw damage clustered), the area of the bounding box is close to the area of the raw damage inside,
  • in the average cases, the approximation of the damaged region suffers from covering significant areas that were not damaged,
  • in the worst cases (small damage rectangles on the other ends of a viewport diagonal), the approximation is very poor, and it may be as bad as covering the whole viewport.

As this solution requires a minimal overhead while still providing a relatively useful damage approximation, in practice, it is a baseline solution used in:

  • Chromium,
  • Firefox,
  • WPE and GTK WebKit when UnifyDamagedRegions runtime preference is enabled, which means it’s used in GTK WebKit by default.

Damage using WebKit’s Region #

When it comes to more sophisticated Damage implementations, the simplest approach in case of WebKit is to wrap data structure already implemented in WebCore called Region. Its purpose is just as the name suggests — to store a region. More specifically, it’s meant to store rectangles describing region in an efficient way both for storage and for access to take advantage of scanline coherence during rasterization. The key characteristic of the data structure is that it stores rectangles without overlaps. This is achieved by storing y-sorted lists of x-sorted, non-overlapping rectangles. Another important property is that due to the specific internal representation, the number of integers stored per rectangle is usually smaller than 4. Also, there are some other useful properties that are, however, not very useful in the context of storing the damage. More details on the data structure itself can be found in the J. E. Steinhart’s paper from 1991 titled SCANLINE COHERENT SHAPE ALGEBRA published as part of Graphics Gems II book.

The Damage implementation being a wrapper of the Region was actually used by GTK and WPE ports as a first version of more sophisticated Damage alternative for the bounding box Damage. Just as expected, it provided better damage resolution in some cases, however, it suffered from effectively degrading to a more expensive variant bounding box Damage in the majority of situations.

The above was inevitable as the implementation was falling back to bounding box Damage when the Region’s internal representation was getting too complex. In essence, it was addressing the Region’s biggest problem, which is that it can effectively store N2 rectangles in the worst case due to the way it splits rectangles for storing purposes. More specifically, as the Region stores ledges and spans, each insertion of a new rectangle may lead to splitting O(N) existing rectangles. Such a situation is depicted in the image below, where 3 rectangles are being split into 9:

WebKit's Region storing method.

Putting the above fallback mechanism aside, the evaluation of Damage being a simple wrapper on top of Region is the following:

  1. Performance
    • insertion is O(logN) ✅
    • iteration is O(N2) ❌
  2. Memory footprint
    • O(N2) ❌
  3. Damage resolution
    • perfect

Adding a fallback, the evaluation is technically the same as bounding box Damage for N above the fallback point, yet with extra overhead. At the same time, for smaller N, the above evaluation didn’t really matter much as in such case all the performance, memory footprint, and the damage resolution were very good.

Despite this solution (with a fallback) yielded very good results for some simple scenarios (when N was small enough), it was not sustainable in the long run, as it was not addressing the majority of use cases, where it was actually a bit slower than bounding box Damage while the results were similar.

R-Tree Damage #

In the pursuit of more sophisticated Damage implementations, one can think of wrapping/adapting data structures similar to quadtrees, KD-trees etc. However, in most of such cases, a lot of unnecessary overhead is added as the data structures partition the space so that, in the end, the input is stored without overlaps. As overlaps are not necessarily a problem for storing damage information, the list of candidate data structures can be narrowed down to the most performant data structures allowing overlaps. One of the most interesting of such options is the R-Tree.

In short, R-Tree (rectangle tree) is a tree data structure that allows storing multiple entries (rectangles) in a single node. While the leaf nodes of such a tree store the original rectangles inserted into the data structure, each of the intermediate nodes stores the bounding box (minimum bounding rectangle, MBR) of the children nodes. As the tree is balanced, the above means that with every next tree level from the top, the list of rectangles (either bounding boxes or original ones) gets bigger and more detailed. The example of the R-tree is depicted in the Figure 5 from the Object Trajectory Analysis in Video Indexing and Retrieval Applications paper:

TODO.

The above perfectly shows the differences between the rectangles on various levels and can also visually suggest some ideas when it comes to adapting such a data structure into Damage:

  1. The first possibility is to make Damage a simple wrapper of R-Tree that would just build the tree and allow the Damage consumer to pick the desired damage resolution on iteration attempt. Such an approach is possible as having the full R-Tree allows the iteration code to limit iteration to a certain level of the tree or to various levels from separate branches. The latter allows Damage to offer a particularly interesting API where the forEachRectangle(...) function could accept a parameter specifying how many rectangles (at most) are expected to be iterated.
  2. The other possibility is to make Damage an adaptation of R-Tree that conditionally prunes the tree while constructing it not to let it grow too much, yet to maintain a certain height and hence certain damage quality.

Regardless of the approach, the R-Tree construction also allows one to implement a simple filtering mechanism that eliminates input rectangles being duplicated or contained by existing rectangles on the fly. However, such a filtering is not very effective as it can only consider a limited set of rectangles i.e. the ones encountered during traversal required by insertion.

Damage as a simple R-Tree wrapper

Although this option may be considered very interesting, in practice, storing all the input rectangles in the R-Tree means storing N rectangles along with the overhead of a tree structure. In the worst case scenario (node size of 2), the number of nodes in the tree may be as big as O(N), thus adding a lot of overhead required to maintain the tree structure. This fact alone makes this solution have an unacceptable memory footprint. The other problem with this idea is that in practice, the damage resolution selection is usually done once — during browser startup. Therefore, the ability to select damage resolution during runtime brings no benefits while introduces unnecessary overhead.

The evaluation of the above is the following:

  1. Performance
    • insertion is O(logMN) where M is the node size ✅
    • iteration is O(K) where K is a parameter and 0≤K≤N ✅
  2. Memory footprint
    • O(N) ❌
  3. Damage resolution
    • low to high
Damage as an R-Tree adaptation with pruning

Considering the problems the previous idea has, the option with pruning seems to be addressing all the problems:

  • the memory footprint can be controlled by specifying at which level of the tree the pruning should happen,
  • the damage resolution (level of the tree where pruning happens) can be picked on the implementation level (compile time), thus allowing some extra implementation tricks if necessary.

While it’s true the above problems are not existing within this approach, the option with pruning — unfortunately — brings new problems that need to be considered. As a matter of fact, all the new problems it brings are originating from the fact that each pruning operation leads to the loss of information and hence to the tree deterioration over time.

Before actually introducing those new problems, it’s worth understanding more about how insertions work in the R-Tree.

When the rectangle is inserted to the R-Tree, the first step is to find a proper position for the new record (see ChooseLeaf algorithm from Guttman1984). When the target node is found, there are two possibilities:

  1. adding the new rectangle to the target node does not cause overflow,
  2. adding the new rectangle to the target node causes overflow.

If no overflow happens, the new rectangle is just added to the target node. However, if overflow happens i.e. the number of rectangles in the node exceeds the limit, the node splitting algorithm is invoked (see SplitNode algorithm from Guttman1984) and the changes are being propagated up the tree (see ChooseLeaf algorithm from Guttman1984).

The node splitting, along with adjusting the tree, are very important steps within insertion as those algorithms are the ones that are responsible for shaping and balancing the tree. For example, when all the nodes in the tree are full and the new rectangle is being added, the node splitting will effectively be executed for some leaf node and all its ancestors, including root. It means that the tree will grow and possibly, its structure will change significantly.

Due to the above mechanics of R-Tree, it can be reasonably asserted that the tree structure becomes better as a function of node splits. With that, the first problem of the tree pruning becomes obvious: tree pruning on insertion limits the amount of node splits (due to smaller node splits cascades) and hence limits the quality of the tree structure. The second problem — also related to node splits — is that with all the information lost due to pruning (as pruning is the same as removing a subtree and inserting its bounding box into the tree) each node split is less effective as the leaf rectangles themselves are getting bigger and bigger due to them becoming bounding boxes of bounding boxes (…) of the original rectangles.

The above problems become more visible in practice when the R-tree input rectangles tend to be sorted. In general, one of the R-Tree problems is that its structure tends to be biased when the input rectangles are sorted. Despite the further insertions usually fix the structure of the biased tree, it’s only done to some degree, as some tree nodes may not get split anymore. When the pruning happens and the input is sorted (or partially sorted) the fixing of the biased tree is much harder and sometimes even impossible. It can be well explained with the example where a lot of rectangles from the same area are inserted into the tree. With the number of such rectangles being big enough, a lot of pruning will happen and hence a lot of rectangles will be lost and replaced by larger bounding boxes. Then, if a series of new insertions will start inserting nodes from a different area which is partially close to the original one, the new rectangles may end up being siblings of those large bounding boxes instead of the original rectangles that could be clustered within nodes in a much more reasonable way.

Given the above problems, the evaluation of the whole idea of Damage being the adaptation of R-Tree with pruning is the following:

  1. Performance
    • insertion is O(logMK) where M is the node size, K is a parameter, and 0<K≤N ✅
    • iteration is O(K) ✅
  2. Memory footprint
    • O(K) ✅
  3. Damage resolution
    • low to medium ⚠️

Despite the above evaluation looks reasonable, in practice, it’s very hard to pick the proper pruning strategy. When the tree is allowed to be taller, the damage resolution is usually better, but the increased memory footprint, logarithmic insertions, and increased iteration time combined pose a significant problem. On the other hand, when the tree is shorter, the damage resolution tends to be low enough not to justify using R-Tree.

Grid-based Damage #

The last, more sophisticated Damage implementation, uses some ideas from R-Tree and forms a very strict, flat structure. In short, the idea is to take some rectangular part of a plane and divide it into cells, thus forming a grid with C columns and R rows. Given such a division, each cell of the grid is meant to store at most one rectangle that effectively is a bounding box of the rectangles matched to that cell. The overview of the approach is presented in the image below:

Grid-based Damage creation process.

As the above situation is very straightforward, one may wonder what would happen if the rectangle would span multiple cells i.e. how the matching algorithm would work in that case.

Before diving into the matching, it’s important to note that from the algorithmic perspective, the matching is very important as it accounts for the majority of operations during new rectangle insertion into the Damage data structure. It’s because when the matched cell is known, the remaining part of insertion is just about taking the bounding box of existing rectangle stored in the cell and the new rectangle, thus having O(1) time complexity.

As for the matching itself, it can be done in various ways:

  • it can be done using strategies known from R-Tree, such as matching a new rectangle into the cell where the bounding box enlargement would be the smallest etc.,
  • it can be done by maximizing the overlap between the new rectangle and the given cell,
  • it can be done by matching the new rectangle’s center (or corner) into the proper cell,
  • etc.

The above matching strategies fall into 2 categories:

  • O(CR) matching algorithms that compare a new rectangle against existing cells while looking for the best match,
  • O(1) matching algorithms that calculate the target cell using a single formula.

Due to the nature of matching, the O(CR) strategies eventually lead to smaller bounding boxes stored in the Damage and hence to better damage resolution as compared to the O(1) algorithms. However, as the practical experiments show, the difference in damage resolution is not big enough to justify O(CR) time complexity over O(1). More specifically, the difference in damage resolution is usually unnoticeable, while the difference between O(CR) and O(1) insertion complexity is major, as the insertion is the most critical operation of the Damage data structure.

Due to the above, the matching method that has proven to be the most practical is matching the new rectangle’s center into the proper cell. It has O(1) time complexity as it requires just a few arithmetic operations to calculate the center of the incoming rectangle and to match it to the proper cell (see the implementation in WebKit). The example of such matching is presented in the image below:

Matching rectangles to proper cells.

The overall evaluation of the grid-based Damage constructed the way described in the above paragraphs is as follows:

  1. performance
    • insertion is O(1) ✅
    • iteration is O(CR) ✅
  2. memory footprint
    • O(CR) ✅
  3. damage resolution
    • low to high (depending on the CR) ✅

Clearly, the fundamentals of the grid-based Damage are strong, but the data structure is heavily dependent on the CR. The good news is that, in practice, even a fairly small grid such as 8x4 (CR=32) yields a damage resolution that is high. It means that this Damage implementation is a great alternative to bounding box Damage as even with very small performance and memory footprint overhead, it yields much better damage resolution.

Moreover, the grid-based Damage implementation gives an opportunity for very handy optimizations that improve memory footprint, performance (iteration), and damage resolution further.

As the grid dimensions are given a-priori, one can imagine that intrinsically, the data structure needs to allocate a fixed-size array of rectangles with CR entries to store cell bounding boxes.

One possibility for improvement in such a situation (assuming small CR) is to use a vector along with bitset so that only non-empty cells are stored in the vector.

The other possibility (again, assuming small CR) is to not use a grid-based approach at all as long as the number of rectangles inserted so far does not exceed CR. In other words, the data structure can allocate an empty vector of rectangles upon initialization and then just append new rectangles to the vector as long as the insertion does not extend the vector beyond CR entries. In such a case, when CR is e.g. 32, up to 32 rectangles can be stored in the original form. If at some point the data structure detects that it would need to store 33 rectangles, it switches internally to a grid-based approach, thus always storing at most 32 rectangles for cells. Also, note that in such a case, the first improvement possibility (with bitset) can still be used.

Summarizing the above, both improvements can be combined and they allow the data structure to have a limited, small memory footprint, good performance, and perfect damage resolution as long as there are not too many damage rectangles. And if the number of input rectangles exceeds the limit, the data structure can still fall-back to a grid-based approach and maintain very good results. In practice, the situations where the input damage rectangles are not exceeding CR (e.g. 32) are very common, and hence the above improvements are very important.

Overall, the grid-based approach with the above improvements has proven to be the best solution for all the embedded devices tried so far, and therefore, such a Damage implementation is a baseline solution used in WPE and GTK WebKit when UnifyDamagedRegions runtime preference is not enabled — which means it works by default in WPE WebKit.

Conclusions #

The former sections demonstrated various approaches to implementing the Damage data structure meant to store damage information. The summary of the results is presented in the table below:

table { border-collapse: separate; border-spacing: 2px; } th { background-color: #666; color: #fff; } th, td { padding: 20px; } tr:nth-child(odd) { background-color: #fafafa; } tr:nth-child(even) { background-color: #f2f2f2; } .code { background-color: #e5e5e5; padding: .25rem; border-radius: 3px; }
Implementation Insertion Iteration Memory Overlaps Resolution
Bounding box O(1) ✅ O(1) ✅ O(1) ✅ No usually low ⚠️
Grid-based O(1) ✅ O(CR) ✅ O(CR) ✅ Yes low to high
(depending on the CR)
R-Tree (with pruning) O(logMK) ✅ O(K) ✅ O(K) ✅ Yes low to medium ⚠️
R-Tree (without pruning) O(logMN) ✅ O(K) ✅ O(N) ❌ Yes low to high
All rects O(1) ✅ O(N) ❌ O(N) ❌ Yes perfect
Region O(logN) ✅ O(N2) ❌ O(N2) ❌ No perfect

While all the solutions have various pros and cons, the Bounding box and Grid-based Damage implementations are the most lightweight and hence are most useful in generic use cases.

On typical embedded devices — where CPUs are quite powerful compared to GPUs — both above solutions are acceptable, so the final choice can be determined based on the actual use case. If the actual web application often yields clustered damage information, the Bounding box Damage implementation should be preferred. Otherwise (majority of use cases), the Grid-based Damage implementation will work better.

On the other hand, on desktop-class devices – where CPUs are far less powerful than GPUs – the only acceptable solution is Bounding box Damage as it has a minimal overhead while it sill provides some decent damage resolution.

The above are the reasons for the default Damage implementations used by desktop-oriented GTK WebKit port (Bounding box Damage) and embedded-device-oriented WPE WebKit (Grid-based Damage).

When it comes to non-generic situations such as unusual hardware, specific applications etc. it’s always recommended to do a proper evaluation to determine which solution is the best fit. Also, the Damage implementations other than the two mentioned above should not be ruled out, as in some exotic cases, they may give much better results.

September 05, 2025 12:00 AM

September 01, 2025

Igalia WebKit Team: WebKit Igalia Periodical #36

Igalia WebKit

Update on what happened in WebKit in the week from August 25 to September 1.

The rewrite of the WebXR support continues, as do improvements when building for Android, along with smaller fixes in multimedia and standards compliance.

Cross-Port 🐱

The WebXR implementation has gained input through OpenXR, including support for the hand interaction—useful for devices which only support hand-tracking—and the generic simple profile. This was soon followed by the addition of support for the Hand Input module.

Aligned the SVGStyleElement type and media attributes with HTMLStyleElement's.

Multimedia 🎥

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

Support for FFMpeg GStreamer audio decoders was re-introduced because the alternative decoders making use of FDK-AAC might not be available in some distributions and Flatpak runtimes.

Graphics 🖼️

Usage of fences has been introduced to control frame submission of rendered WebXR content when using OpenXR. This approach avoids blocking in the renderer process waiting for frames to be completed, resulting in slightly increased performance.

Loading a font from a collection will now iterate until finding the correct one. This solved a few font rendering issues.

WPE WebKit 📟

WPE Platform API 🧩

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

Changed WPEPlatform to be built as part of the libWPEWebKit library. This avoids duplicating some code in different libraries, brings in a small reduction in used space, and simplifies installation for packagers. Note that the wpe-platform-2.0 module is still provided, and applications that consume the WPEPlatform API must still check and use it.

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

Support for sharing AHardwareBuffer handles across processes is now available. This lays out the foundation to use graphics memory directly across different WebKit subsystems later on, making some code paths more efficient, and paves the way towards enabling the WPEPlatform API on Android.

The MediaSession API has been disabled when building for Android. The existing implementation would attempt to use the MPRIS D-Bus interface, which does not work on Android.

That’s all for this week!

By Igalia WebKit Team at September 01, 2025 09:02 PM

August 25, 2025

Igalia WebKit Team: WebKit Igalia Periodical #35

Igalia WebKit

Update on what happened in WebKit in the week from August 18 to August 25.

This week continue improvements in the WebXR front, more layout tests passing, support for CSS's generic font family for math, improvements in the graphics stack, and an Igalia Chat episode!

Cross-Port 🐱

Align the experimental CommandEvent with recent specification changes. This should finalise the implementation ready to enable by default.

The WebXR implementation has gained support to funnel usage permission requests through the public API for immersive sessions. Note that this is a basic implementation, and fine-grained control of requested session capabilities may be added at a later time.

The GTK MiniBrowser has been updated to handle WebKitXRPermissionRequest accordingly.

Implemented rawKeyDown and rawKeyUp in WKTestRunner for WPEWebKit and WebkitGTK, which made more than 300 layout tests pass.

Enable command and commandfor attributes in stable. These are part of the invoker commands API for buttons.

Graphics 🖼️

The CSS font-family: math generic font family is now supported in WebKit. This is part of the CSS Fonts Level 4 specification.

The WebXR implementation has gained to ability to use GBM graphics buffers as fallback, which allows usage with drivers that do not provide the EGL_MESA_image_dma_buf_export extension, yet use GBM for buffer allocation.

The WebXR render loop has been simplified by using a work queue and offloading the session handling to the render thread.

Community & Events 🤝

Early this month, a new episode of Igalia Chat titled "Get Down With the WebKit" was released, where Brian Kardell and Eric Meyer talk with Igalia's Alejandro (Alex) Garcia about the WebKit project and Igalia's WPE port.

That’s all for this week!

By Igalia WebKit Team at August 25, 2025 07:42 PM

August 18, 2025

Igalia WebKit Team: WebKit Igalia Periodical #34

Igalia WebKit

Update on what happened in WebKit in the week from August 11 to August 18.

This week we saw updates in WebXR support, better support for changing audio outputs, enabling of GLib API when building the JSCOnly port, improvements to damaging propagation, WPE platform enhancements, and more!

Cross-Port 🐱

Complementing our WebXR efforts, it is now possible to query whether a WebView is in immersive mode and request to leave immersive mode.

Multimedia 🎥

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

Changing audio outputs has been changed to use gst_device_reconfigure_element() instead of relying on knowledge about how different GStreamer sink elements handle the choice of output device. Note that audio output selection support is in development and disabled by default, the ExposeSpeakers, ExposeSpeakersWithoutMicrophone, and PerElementSpeakerSelection features flags may be toggled to test it.

While most systems use PipeWire or PulseAudio these days, some systems may need a fix for the corresponding ALSA and OSS elements, which has been already merged in GStreamer.

JavaScriptCore 🐟

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

Support for enabling the GLib API when building the JSCOnly port has been added.

Graphics 🖼️

With the #44192 PR landed, the damage propagation feature is now able to propagate damage from accelerated 2D canvas.

WPE WebKit 📟

Fixed minor bug in WPE's: pressing Esc key inside a dialog closes dialog. See PR #49265.

WPE Platform API 🧩

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

The WPEPlatform DRM backend can now report available input devices. This is mainly used to support Interaction Media Features queries, which allow web sites to better adapt to the available input devices.

That’s all for this week!

By Igalia WebKit Team at August 18, 2025 07:21 PM

Vivienne Watermeier: CEA-608 captions in Media Source Extensions with webkitgtk

Igalia WebKit

Recently, I have been working on webkitgtk support for in-band text tracks in Media Source Extensions, so far just for WebVTT in MP4. Eventually, I noticed a page that seemed to be using a CEA-608 track - most likely unintentionally, not expecting it to be handled - so I decided to take a look how that might work. Take a look at the resulting PR here: https://github.com/WebKit/WebKit/pull/47763

Now, if you’re not already familiar with subtitle and captioning formats, particularly CEA-608, you might assume they must be straightforward, compared to audio and video. After all, its just a bit of text and some timestamps, right?

However, even WebVTT as a text-based format already provides lots of un- or poorly supported features that don’t mesh well with MSE - for details on those open questions, take a look at Alicia’s session on the topic: https://github.com/w3c/breakouts-day-2025/issues/14

Quick introduction to CEA-608 #

CEA-608, also known as line 21 captions, is responsible for encoding captions as a fixed-bitrate stream of byte pairs in an analog NTSC broadcast. As the name suggests, they are transmitted during the vertical blanking period, on line 21 (and line 284, for the second field) - imagine this as the mostly blank area “above” the visible image. This provides space for up to 4 channels of captioning, plus some additional metadata about the programming, though due to the very limited bandwidth, these capabilities were rarely used to their full extent.

While digital broadcasts provide captioning defined by its successor standard CEA-708, this newer format still provides the option to embed 608 byte pairs. This is still quite common, and is enabled by later standards defining a digital encoding, known as Caption Distribution Packets.

These are also what enables CEA-608 tracks in MP4.

Current issues, and where to go in the future #

The main issue I’ve encountered in trying to make CEA-608 work in an MSE context lies in its origin as a fixed-bitrate stream - there is no concept of cues, no defined start or end, just one continuous stream.

As WebKit internally understands only WebVTT cues, we rely on GStreamer’s cea608tott element for the conversion to WebVTT. Essentially, this element needs to create cues with well-defined timestamps, which works well enough if we have the entire stream present on disk.

However, when 608 is present as a track in an MSE stream, how do we tell if the “current” cue is continued in the next SourceBuffer? Currently, cea608tott will just wait for more data, and emit another cue once it encounters a line break, or its current line buffer fills up, but this also means the final cue will be swallowed, because there will never be “more data” to allow for that decision.

The solution would be to always cut off cues at SourceBuffer boundaries, so cues might appear to be split awkwardly to the viewer. Overall, this conversion to VTT won’t reproduce the captions as they were intended to be viewed, at least not currently. In particular, roll-up mode can’t easily be emulated using WebVTT.

The other issue is that I’ve assumed for the current patch that CEA-608 captions will be present as a separate MP4 track, while in practice they’re usually injected into the video stream, which will be harder to handle well.

Finally, there is the risk of breaking existing websites, that might have unintentionally left CEA-608 captions in, and don’t handle a surprise duplicate text track well.

Takeaway #

While this patch only provides experimental support so far, I feel this has given me valuable insight into how inband text tracks can work with various formats aside from just WebVTT. Ironically, CEA-608 even avoids some of WebVTT’s issues - there are no gaps or overlapping cues to worry about, for example.

Either way, I’m looking forward to improving on WebVTT’s pain points, and maybe adding other formats eventually!

August 18, 2025 12:00 AM

August 11, 2025

Igalia WebKit Team: WebKit Igalia Periodical #33

Igalia WebKit

Update on what happened in WebKit in the week from July 29 to August 11.

This update covers two weeks, including a deluge of releases and graphics work.

Cross-Port 🐱

Graphics 🖼️

CSS animations with a cubic-bezier timing function are now correctly rendered

The rewrite of the WebXR support continued making steady progress, and is getting closer to being able to render content again.

WPE WebKit 📟

The WPE port gained basic undo support in text inputs.

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

WPE-Android has been updated to use WebKit 2.48.5. This update particular interest for development on Android is the support for using the system logd service, which can be configured using system properties. For example, the following will enable logging all warnings:

adb shell setprop debug.log.WPEWebKit all
adb shell setprop log.tag.WPEWebKit WARN

Updated prebuilt packages are also available in the Central repository.

Releases 📦️

Stable releases of WebKitGTK 2.48.5 and WPE WebKit 2.48.5 are now available. These include the fixes and improvements from the corresponding 2.48.4 ones, and additionally solve a number of security issues. Advisory WSA-2025-0005 (GTK, WPE) covers the included security patches.

WebKitGTK 2.49.3 and WPE WebKit 2.49.4 have been released, intended to test out upcoming features and improvements. As usual, issue reports are welcome in Bugzilla, and are particularly important now to stabilize the newly created branch for the upcoming 2.50.x series.

Ruby was re-added to the GNOME SDK, thanks to Michael Catanzaro and Jordan Petridis. So we're happy to report that the WebKitGTK nightly builds for GNOME Web Canary are now fixed and Canary updates were resumed.

That’s all for this week!

By Igalia WebKit Team at August 11, 2025 09:07 PM

July 28, 2025

Igalia WebKit Team: WebKit Igalia Periodical #32

Igalia WebKit

Update on what happened in WebKit in the week from July 21 to July 28.

This week the trickle of improvements to the graphics stack continues with more font handling improvements and tuning of damage information; plus the WPEPlatform Wayland backend gets server-side decorations with some compositors.

Cross-Port 🐱

Graphics 🖼️

The font-variant-emoji CSS property is now enabled by default in the GTK and WPE ports.

Font synthesis properties (synthetic bold/italic) are now correctly handled, so that fonts are rendered bold or italic even when the font itself does not provide these variants.

A few minor improvements to the damage propagation feature have landed.

The screen device scaling factor in use is now shown in the webkit://gpu internal information page.

WPE WebKit 📟

WPE Platform API 🧩

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

The Wayland backend included with WPEPlatform has been taught how to request server-side decorations using the XDG Decoration protocol. This means that compositors that support the protocol will provide window frames and title bars for WPEToplevel instances. While this is a welcome quality of life improvement in many cases, window decorations will not be shown on Weston and Mutter (used by GNOME Shell among others), as they do not support the protocol at the moment.

WPE MiniBrowser, showing server-side decorations with the Labwc compositor

That’s all for this week!

By Igalia WebKit Team at July 28, 2025 09:04 PM