December 25, 2025

Igalia WebKit Team: WebKit Igalia Periodical #52

Igalia WebKit

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

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

Cross-Port 🐱

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

Two new functions have been added to the public API:

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

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

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

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

Graphics 🖼️

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

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

WPE WebKit 📟

WPE Platform API 🧩

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

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

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

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

Releases 📦️

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

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

Community & Events 🤝

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

That’s all for this week!

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

December 19, 2025

Release Notes for Safari Technology Preview 234

Surfin’ Safari

Safari Technology Preview Release 234 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: 303092@main…304071@main.

Accessibility

Resolved Issues

  • Fixed missing aria-label on the button added by <img controls> to improve accessibility for screen readers. (304013@main) (164651880)

Animations

New Features

  • Added support for Threaded Scroll-driven Animations. With Threaded Scroll-driven Animations, eligible scroll-driven animations are updated as their associated timeline’s source element is scrolled instead of when the page rendering is updated, yielding smoother animations on devices with Pro Motion displays. Additionally, since scrolling is performed in a separate process, those animations update as scrolling occurs no matter the load on the main thread. Eligible animations are any scroll-driven animations animating the opacity, transform, translate, scale, rotate, filter, backdrop-filter or any of the Motion Path properties. (303997@main) (165924545)

CSS

New Features

  • Added support for CSS display: grid-lanes. (303114@main) (164860495)
  • Added support for an automatic initial value for grid-auto-flow in CSS Grid Level 3 to switch flow orientation based on grid-template-rows and grid-template-columns for grid lane layouts. (303634@main) (164791817)
  • Added support for item-tolerance in CSS Masonry layouts to improve flexible grid item placement. (303096@main) (164043151)

Resolved Issues

  • Fixed an issue where max-width was not correctly applied to tables with fixed widths. (303644@main) (96554687)
  • Fixed incorrect sizing and fragment URL handling for SVG images used in -webkit-cross-fade(). (303593@main) (106633417)
  • Fixed table layout so that fixed horizontal margins on <caption> elements now contribute to the table’s minimum preferred logical width, preventing captions from causing narrower than expected tables. (303642@main) (120990942)
  • Fixed incorrect width calculation for positioned elements using box-sizing: border-box with an aspect-ratio, ensuring borders and padding are not double-counted. (303834@main) (121500004)
  • Fixed the UA style sheet to use :focus-visible instead of :focus for outline properties. (303643@main) (123155364)
  • Fixed HighlightRegistry to remove its non-standard constructor and updated tests to use CSS.highlights while ensuring Map.prototype is properly restored after tampering. (303858@main) (125529396)
  • Fixed handling of @property registration so that the initial-value descriptor can be optional. (303757@main) (131288198)
  • Fixed devicePixelRatio so that page zoom now affects the main frame consistently with iframes, keeping their values synchronized. (303397@main) (163857955)
  • Fixed line-height to correctly scale font-relative units when text zoom is applied. (304012@main) (165073337)
  • Fixed element.clientWidth and element.clientHeight to correctly include padding for content-box tables. (303641@main) (165515755)
  • Fixed: Refactored the handling of block-level boxes inside inline boxes. (303985@main) (165523565)
  • Fixed an issue where text-decoration: underline appeared higher than expected when text-box-trim was applied to the root inline box. (303710@main) (165614136)
  • Fixed auto-placement cursor handling for spanning items in grid-lanes layouts to correctly wrap within the valid range. (303815@main) (165701659)
  • Fixed an issue with grid lanes masonry layout where items with negative margins were incorrectly positioned too low by always taking the maximum running position. (303813@main) (165718130)

HTML

Resolved Issues

  • Fixed shadowrootcustomelementregistry attribute serialization to correctly compare ShadowRoot and document registries. (303841@main) (165476421)

JavaScript

Resolved Issues

  • Fixed Date constructor overflow handling so that invalid day values now return NaN. (303582@main) (155776209)
  • Fixed Intl.Locale.prototype.getWeekInfo() to remove the minimalDays property for compliance with the specification. (303287@main) (165083619)
  • Fixed Intl.NumberFormat to properly apply minimumFractionDigits and maximumFractionDigits to ensure currency and compact notations behave correctly. (303943@main) (165875014)

MathML

Resolved Issues

  • Fixed default MathML rule thickness to use the font’s underlineThickness metric with a zero fallback. (303108@main). (164693673)
  • Fixed painting empty <msqrt> radical operator in MathML by removing an incorrect check. (303134@main) (164776629)
  • Fixed MathML <mpadded>, <mfrac>, <munderover>, <mover> and <mspace> elements not updating layout when attributes like width, height, depth, lspace, and voffset etc. changed. (303508@main) (164797996)
  • Fixed MathML boolean attributes so they are now compared ASCII case-insensitively. (303107@main) (164819048)

Media

Resolved Issues

  • Fixed an issue where Video Viewer UI elements overlapped or exited unexpectedly. (303256@main) (164051864)
  • Fixed an issue where WebM with VP9/Vorbis fallback would not play. (303100@main) (164053503)
  • Fixed an issue where empty <track> elements prevented media from advancing its readyState and blocked play() calls. (303314@main) (164125914)
  • Fixed MediaStreamTrackProcessor to respect track.enabled = false. (303389@main) (165199900)
  • Fixed an issue where the ended event for Media Source Extensions might never fire by ensuring buffered ranges update correctly and playback gaps are observed even when the video does not start at time zero. (303596@main) (165430052)
  • Fixed an issue where caption previews were not shown in the default media controls. (304070@main) (165931046)

SVG

Resolved Issues

  • Fixed SVG intrinsic sizing and preferredAspectRatio() to correctly transpose dimensions for vertical writing modes. (303578@main) (103262534)
  • Fixed SVGLength parsing to correctly return the initial value when encountering parser errors or invalid values. (303577@main) (136102554)
  • Fixed an issue where SVGImage did not respect system dark mode changes. (303920@main) (140661763)
  • Fixed SVGLength.prototype.valueAsString to throw a SyntaxError when assigned an empty string. (303594@main) (165429393)
  • Fixed an issue where lengths with leading or trailing whitespace failed to be parsed. (303951@main) (165501190)

Web API

New Features

  • Added support for ReadableStream.getIterator() and the [@@asyncIterator] methods to enable iteration over streams. (303970@main). (96318671)
  • Added support for the Keyboard Lock API. (303093@main) (161422221)
  • Added support for using readable byte streams as fetch request and response bodies and enabling synchronous start behavior. (303115@main) (162107262)
  • Added support for ReadableByteStream. (303239@main) (164877711)
  • Added support for upgrading elements in CustomElementRegistry.prototype.initialize. (303250@main) (165045530)
  • Added support for the customelementregistry content attribute and handling of null customElementRegistry values in document.createElement, document.createElementNS, and element.attachShadow. (303300@main) (165096267)
  • Expose MediaDeviceInfo interface in SecureContext only as per web specification. (303512@main) (165318702)

Resolved Issues

  • Fixed an issue where scroll-margin from IntersectionObserver incorrectly applied to scrollers inside cross-origin iframes. (303367@main) (164994009)
  • Fixed ReadableStream and WritableStream to correctly pass abort and cancel reasons and improving WebTransport stream handling. (303738@main) (165474756)

Web Authentication

New Features

  • Added support for the WebAuthn PRF extension that maps to the CTAP hmac-secret extension, enabling credential-bound cryptographic secrets for both credential creation and authentication flows. (303406@main) (113572812)

Resolved Issues

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

Web Inspector

New Features

  • Added support for starting and stopping <canvas> recordings from the console within a Worker using console.record() and console.recordEnd(). (303230@main) (98223237)
  • Added support in the Web Inspector Timelines Heap view to display the dominator object, if any, when viewing the shortest GC path. (303368@main) (165177746)
  • Added support for auto-completion of sideways-lr and sideways-rl values for the writing-mode CSS property. (303884@main) (165777054)
  • Added support for auto-completion of grid-lanes and inline-grid-lanes values for the display CSS property. (304001@main) (165873256)

Resolved Issues

  • Fixed incorrect breakpoint and search result positions in the Web Inspector after pretty-printing inline scripts containing multi-line template literals. (303680@main) (29417859)
  • Fixed an issue where the Console tab search bar in Web Inspector would disappear when the window was too narrow. (304057@main) (50922509)
  • Fixed an issue where breakpoints and search results in Web Inspector could point to the wrong location after a previously formatted source file was reopened in an unformatted state. (303327@main) (165059693)

WebRTC

New Features

  • Added support for capturing audio from multiple microphones on macOS with getUserMedia while managing echo cancellation and dynamically migrating existing captures to non-VPIO units. (303113@main) (163945062)

December 19, 2025 08:43 PM

Introducing CSS Grid Lanes

Surfin’ Safari

It’s here, the future of masonry layouts on the web! After the groundwork laid by Mozilla, years of effort by Apple’s WebKit team, and many rounds debate at the CSS Working Group with all the browsers, it’s now clear how it works.

Introducing CSS Grid Lanes.

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

Try it today in Safari Technology Preview 234.

How Grid Lanes work

Let’s break down exactly how to create this classic layout.

Classic masonry-style layout of photos of various aspect ratios, all the same width, aligned in six columns
You can try out this demo of photo gallery layouts today in Safari Technology Preview.

First, the HTML.

<main class="container">
  <figure><img src="photo-1.jpg"></figure>
  <figure><img src="photo-2.jpg"></figure>
  <figure><img src="photo-3.jpg"></figure>
  <!-- etc -->
</main>

Let’s start by applying display: grid-lanes to the main element to create a Grid container ready to make this kind of layout. Then we use grid-template-columns to create the “lanes” with the full power of CSS Grid.

In this case, we’ll use repeat(auto-fill, minmax(250px, 1fr)) to create flexible columns at least 250 pixels wide. The browser will decide how many columns to make, filling all available space.

And then, gap: 16px gives us 16 pixel gaps between the lanes, and 16 pixel gaps between items within the lanes.

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

That’s it! In three lines of CSS, with zero media queries or container queries, we created a flexible layout that works on all screen sizes.

Think of it like a highway of cars in bumper-to-bumper traffic.

Cartoon drawing of a highway from above. Nine cars fill four lanes of traffic, bumper to bumper. Each car has a number labeling it, showing the order these would be in HTML.

Just like the classic Masonry library, as the browser decides where to put each item, the next one is placed in whichever column gets it closest to the top of the window. Like traffic, each car “changes lanes” to end up in the lane that gets them “the furthest ahead”.

This layout makes it possible for users to tab across the lanes to all currently-visible content, (not down the first column below the fold to the very bottom, and then back to the top of the second column). It also makes it possible for you to build a site that keeps loading more content as the user scrolls, infinitely, without needing JavaScript to handle the layout.

The power of Grid

Varying lane sizes

Because Grid Lanes uses the full power of CSS Grid to define lanes using grid-template-*, it’s easy to create creative design variations.

For example, we can create a flexible layout with alternating narrow and wide columns — where both the first and last columns are always narrow, even as the number of columns changes with the viewport size. This is accomplished with grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr) minmax(16rem, 2fr)) minmax(8rem, 1fr).

Demo layout of photos, where the 1st, 3rd, 5th, and 7th column are narrow, while the 2nd, 4th and 6th columns are twice as wide.
Try out the demo of photo gallery layouts today in Safari Technology Preview.

There’s a whole world of possibilities using grid-template-* syntax.

Spanning items

Since we have the full power of Grid layout, we can also span lanes, of course.

A complex layout of titles with teaser text for over two dozen articles — telling people what they'll experience if they open the article. The first teaser has a very large headline with text, and spans four columns. Five more teasers are medium-sized, bowl and next to the hero. The rest of the space available is filled in with small teasers. None of the teasers have the same amount of content as the rest. The heights of each box are random, and the layout tucks each box up against the one above it.
Try out the demo of newspaper article layout today in Safari Technology Preview.
main {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(20ch, 1fr));
  gap: 2lh;
}
article { 
  grid-column: span 1; 
}
@media (1250px < width) {
  article:nth-child(1) { 
    grid-column: span 4;             
  }
  article:nth-child(2), article:nth-child(3), article:nth-child(4), article:nth-child(5), article:nth-child(6), article:nth-child(7), article:nth-child(8) { 
    grid-column: span 2; 
  }
}

All the article teasers are first set to span 1 column. Then the 1st item is specifically told to span 4 columns, while the 2nd – 8th to span 2 columns. This creates a far more dynamic graphic design than the typical symmetrical, everything the same-width, everything the same-height layout that’s dominated over the last decade.

Placing items

We can also explicitly place items while using Grid Lanes. Here, the header is always placed in the last column, no matter how many columns exist.

A layout of paintings — each has a bit of text below the painting: title, etc. The paintings are laid out in 8 columns. Over on the right, spanning across two columns is the header of the website.
Try out the demo of a museum website layout today in Safari Technology Preview.
main {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(24ch, 1fr));
}
header {
  grid-column: -3 / -1;
}

Changing directions

Yes, lanes can go either direction! All of the examples above happen to create a “waterfall” shape, where the content is laid out in columns. But Grid Lanes can be used to create a layout in the other direction, in a “brick” layout shape.

Contrasting cartoon drawings: on the left, waterfall layout with boxes lined up in columns, falling down the page. And "brick" layout, with boxes flowing left to right, stacked like bricks in rows.

The browser automatically creates a waterfall layout when you define columns with grid-template-columns, like this:

.container {
  display: grid-lanes;
  grid-template-columns: 1fr 1fr 1fr 1fr;
}

If you want a brick layout in the other direction, instead define the rows with grid-template-rows:

.container {
  display: grid-lanes;
  grid-template-rows: 1fr 1fr 1fr;
}

This works automatically thanks to a new default forgrid-auto-flow, the normal value. It figures out whether to create columns or rows based on whether you defined the lanes using grid-template-columns or grid-template-rows.

The CSS Working Group is still discussing which property will explicitly control the flow orientation, and what its syntax will be. The debate is over whether to reuse grid-auto-flow or create new properties like grid-lanes-direction. If you’re interested in reading about the options being considered or chime in with your thoughts, see this discussion.

However, since normal will be the initial value either way, you don’t have to wait for this decision to learn Grid Lanes. When you define only one direction — grid-template-rows or grid-template-columns — it will Just Work™. (If it doesn’t, check if grid-auto-flow is set to a conflicting value. You canunset it if needed.)

Placement sensitivity

“Tolerance” is a new concept created for Grid Lanes. It lets you adjust just how picky the layout algorithm is when deciding where to place items.

Look at the next drawing. Notice that Car 4 is a tiny bit shorter than Car 1. When the “tolerance” is zero, Car 6 ends up in the right-most lane, while Car 7 is on the left. Car 6 ends up behind Car 4 on the right because that gets it a tiny bit closer “down the road” (closer to the top of the Grid container). Car 7 then takes the next-closest-to-the-top slot, and ends up behind Car 1 on the left. The end result? The first horizontal grouping of content is ordered 1, 2, 3, 4, and the next is 7, 5, 6.

Same cartoon drawing of the highway of bumper to bumper traffic from above.

But the difference in length between Car 1 and Car 4 is tiny. Car 6 isn’t meaningfully closer to the top of the page. And having item 6 on the right, with item 7 on the left is likely an unexpected experience — especially for users who are tabbing through content, or when the content order is somehow labeled.

These tiny differences in size don’t matter in any practical sense. Instead, the browser should consider item sizes like Car 1 and Car 4 to be a tie. That’s why the default for item-tolerance is 1em — which means only differences in content length greater than 1 em will matter when figuring out where the next item goes.

If you’d like the layout of items to shuffle around less, you can set a higher value for item-tolerance. In the next digram, the tolerance is set to half-a-car, causing the cars to lay out basically from left to right and only moving to another lane to avoid the extra-long limo. Now, the horizontal groupings of content are 1, 2, 3, 4, and 5, 6, 7.

Now the highway has the cars ordered in a fashion that's less chaotic.

Think of tolerance as how chill you want the car drivers to be. Will they change lanes to get just a few inches ahead? Or will they only move if there’s a lot of space in the other lane? The amount of space you want them to care about is the amount you set in item-tolerance.

Remember that people tabbing through the page will see each item highlighted as it comes into focus, and may be experiencing the page through a screenreader. An item tolerance that’s set too high can create an awkward experience jumping up and down the layout. An item tolerance that’s too low can result in jumping back and forth across the layout more than necessary. Adjust item-tolerance to something appropriate for the sizes and size variations of your content.

Currently, this property is named item-tolerance in the specification and in Safari Technology Preview 234. However, there is still a chance this name will change, perhaps to something like flow-tolerance or pack-tolerance. If you have a preference, or ideas for a better name, you can chime in here. Keep an eye out for updates about the final name before using this property on production websites.

Try it out

Try out Grid Lanes in Safari Technology Preview 234! All of the demos at webkit.org/demos/grid3 have been updated with the new syntax, including other use cases for Grid Lanes. It’s not just for images! For example, a mega menu footer full of links suddenly becomes easy to layout.

A layout of 15 groups of links. Each has between two and nine links in the group — so they are all very different heights from each other. The layout has five columns of these groups, where each group just comes right after the group above it. Without any regard for rows.
Try out the mega menu demo today in Safari Technology Preview.
.container {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(max-content, 24ch));
  column-gap: 4lh;
}

What’s next?

There are a few last decisions for the CSS Working Group to make. But overall, the feature as described in this article is ready to go. It’s time to try it out. And it’s finally safe to commit the basic syntax to memory!

We’d love for you to make some demos! Demonstrate what new use cases you can imagine. And let us know about any bugs or possible improvements you discover. Ping Jen Simmons on Bluesky or Mastodon with links, comments and ideas.

Our team has been working on this since mid-2022, implementing in WebKit and writing the web standard. We can’t wait to see what you will do with it.

December 19, 2025 09:00 AM

Pawel Lampe: WPE performance considerations: pre-rendering

Igalia WebKit

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

The workload #

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

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

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

Typical web application workloads.

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

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

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

First step: optimization #

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

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

Second step: pre-rendering #

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

Pre-rendering techniques #

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

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

Pre-rendering offline #

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

Pre-rendering using canvas #

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

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

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

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

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

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

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

Pre-rendering using eventually-invisible layers #

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

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

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

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

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

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

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

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

Bursting numbers demo.

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

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

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

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

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

Scrolling trick explained.

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

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

Sysprof from basic demo.



Sysprof from pre-rendering demo.

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

Results

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

Benchmarking results.

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

Conclusions #

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

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

December 19, 2025 12:00 AM

December 15, 2025

Igalia WebKit Team: WebKit Igalia Periodical #51

Igalia WebKit

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

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

Cross-Port 🐱

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

JavaScriptCore 🐟

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

Releases 📦️

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

That’s all for this week!

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

December 12, 2025

WebKit Features for Safari 26.2

Surfin’ Safari

Safari 26.2 is a big release. Packed with 62 new features, this release aims to make your life as a web developer easier by replacing long-standing frustrations with elegant solutions. You’ll find simpler ways to create common UI patterns with just a few lines of HTML or CSS, and no JavaScript — like auto-growing text fields with CSS field-sizing, and buttons that open/close dialogs and popovers with command and commandfor HTML attributes. Navigation API makes building single-page apps more straightforward. Full width numbers are normalized, and text shaping across element boxes is supported for languages like Arabic. Anchor Positioning expands with the position-visibility property and much more. The cursor property works on pseudo-elements. Improvements to Initial Letter make it easier to create drop-caps. CHIPS brings back partitioned cookies, this time opt-in. WebXR on visionOS now supports WebGPU. And much, much more. Plus, this release resolves 165 bugs. It’s a release that makes the web more dependable, letting you spend less time working around browser inconsistencies and more time accomplishing your goals.

HTML

HTML is 35 years old, yet keeps evolving to make web development easier. Recent years have brought many small but powerful HTML innovations that simplify building dynamic user interfaces — letting you solve common use cases with HTML and CSS alone, “paving the cowpaths”, and reducing the need for JavaScript or third-party frameworks. Safari 26.2 delivers several more such features.

Button commands

Before, using a button on a web page to trigger a client-side action required JavaScript. Now, you can use command and commandfor attributes in HTML to control popovers and dialogs.

Use the commandfor attribute to tie the button to the id of the element it affects. And use the command attribute to tell the browser which action to take. There are six predefined values for the command attribute. These map to existing JavaScript methods. Fordialogelements: show-modal, close, and request-close. And for any HTML element with thepopover attribute: show-popover, hide-popover, and toggle-popover.

You can also observe custom commands in JavaScript through events. Custom commands are named using --foobar syntax, and used declaratively in HTML as command="--foobar".

Finding content

Safari 26.2 adds support for auto-expanding the details element. Now, anytime someone uses “Find” in Safari to search the page, if the search result is hidden inside a closed details element, the element will automatically expand to reveal the text. Also, if the user navigates to a URL that contains a Text Fragment and the targeted fragment is inside a closed details, it will automatically expand.

Similarly, Safari 26.2 adds support for the hidden=until-found attribute. You can apply this to any element in HTML, causing the content to not be rendered as part of the page, until a user searches for it. Then it appears.

<div hidden="until-found">
  <p>This content is hidden, but will be found by search!</p>
</div>

Thebeforematch event fires on an element with hidden=until-found just before it’s revealed by a find-in-page search. This allows you to run JavaScript to prepare the content, update analytics, or perform other actions before the hidden content becomes visible to the user.

Off-screen radio buttons now automatically scroll into view when focused. This is especially helpful when users navigate with keyboard shortcuts or screen readers, improving accessibility.

ARIA Index Text attributes

Safari 26.2 adds support for thearia-colindextext andaria-rowindextext attributes, which provide human-readable alternatives to aria-colindex and aria-rowindex.

The previously-supported options use numeric values to indicate a cell’s position within a table or grid. Now aria-colindextext and aria-rowindextext let you provide more meaningful labels. This is particularly valuable when columns use letters (like A, B, C in a spreadsheet) or when rows represent time periods, categories, or other non-numeric sequences. When both attributes are present, assistive technologies announce the human-readable text instead of the number, giving users better context as they navigate complex tables and grids.

Full width number normalization

Full-width characters are commonly used in Japanese, Chinese, and Korean text input. Safari 26.2 converts certain full-width characters into normalized equivalents for <input type="number"> fields. Now full-width digits (0123) become standard digits (0123); full-width minus signs (-) become standard minus signs (-); and full-width dots (.) become standard periods (.) This normalization lets users type numbers naturally in their language while ensuring the input works correctly as a number value. Also, disallowed characters are now immediately rejected.

And more

You might remember that HTML5 was originally intended to provide an outlining algorithm — where a website could use multiple h1 headlines on a page, and the browser would assign levels to each headline based on the overall DOM structure of elements. In practical terms, this idea didn’t work out and it was never widely implemented in browsers. Developers simply use h2, h3, h4, etc to convey levels of headline. To go with the intended original behavior in HTML5, the default UA styling for h1 elements was specified to visually change the headline when it was a child of article, aside, nav, or section — making the font-size and margin smaller and smaller each time it was nested. In Safari 26.2, following a recent HTML standard change, those UA styles are being removed.

When viewing web content in a Mac Catalyst app on macOS 26.2, elements with a title attribute will now display the tooltip when a user hovers.

CSS

Shrink wrapping form fields

Safari 26.2 adds support for the field-sizing property. Applying field-sizing: content causes the input and textarea form fields to shrink-wrap their content and grow as more text is entered. This eliminates the need for JavaScript to resize form fields dynamically. You can use min-height, min-width, max-height, and max-width to keep the field within the range you desire.

CSS Calc functions

In recent years, CSS has gained many powerful mathematical abilities, adding to its maturity as a programming language. Safari 26.2 brings three new functions — random(), sibling-index() and sibling-count().

The random() function lets you include a randomly generated value from a specified range in your calculations. For example, random(0, 100, 2) will choose an even number between 0 and 100, while random(0turn, 1turn) will be a fraction of a turn — basically, any decimal between 0 and 360 degrees. Learn all about what the CSS random function can do in our article, Rolling the dice with CSS random().

The sibling-count() function counts up the number of elements that are siblings of the current element, and returns a number, while sibling-index() will tell you which numbered item this one is — the 4th item for example. They make it incredibly easy to do things you’ve probably wished you could do for years.

For example, today, CSS Grid makes it trivial to proportion space in a layout with fr units — but in the days of float-based responsive layouts, this code would have been incredibly helpful (and still is today):

.gallery-item {
  width: calc(100% / sibling-count());
}

It calculates a width for an item by dividing the total width available by the total number of sibling items in the DOM. When there’s four items, the width of each is 25%. When there’s five items, each is 20%.

CSS has had nth-child() for a long time, letting you target a specific numbered item. The sibling-index() function is similar, telling you which number item a particular one is in the order.

This demo uses random() to generate the random rotation distance and sibling-index() to generate the angle for each of the wheel items.

Text shaping across inline boxes

Safari 26.2 adds support for text shaping across inline boxes. This is especially impactful for scripts like Arabic, N’Ko and many others where the letters join together and change appearance as text is rendered. The word “مرحبا” in Arabic (which means “hello” or “welcome”) is spelled using these letters: م ر ح ب ا — which then change their shapes and work together to create مرحبا. Before Safari 26.2, such letters would not combine if they were wrapped in separate elements. Of course, it’s not common to wrap an individual letter in the middle of a word with a span or other inline element, but there are good use cases for this — especially, perhaps, when wanting to change the color of the letters in teaching how the writing system works, like this:

There’s still more work to do in this space to support the full range of different behaviors of the many kinds of text shaping around the world.

Initial letter improvements

Initial letter was originally created by folks at Apple and implemented over a decade ago in Safari 9. It was created behind a -webkit- prefix, as was best practice at that time. Since then, the CSS web standard has matured to handle more complex use-cases. Safari 26.2 updates our implementation, adding support for decimal values in the initial-letter property. This can be especially helpful if you need to make a slight adjustment to better fit your font. Also, support for web fonts was added in March 2024 with Safari 18.4. The -webkit- prefix is still required, but these updates make it very usable in the meantime.

Anchor positioning improvements

Safari 26.2 adds several refinements to Anchor Positioning, which originally shipped in Safari 26.0. First, Safari 26.2 supports for flip-x and flip-y options in position-try-fallback. These keywords allow an anchored element to flip along the x- or y-axis when it would otherwise overflow its container.

The new position-visibility property lets you control when anchor-positioned elements are visible. For example, use no-overflow to hide an element when it overflows its container. Other values include always, anchors-visible, inherit, initial, revert, revert-layer, and unset.

The position-try property now works on pseudo-elements like ::before, ::after, and ::backdrop, which were previously unaffected. This means pseudo-elements can be repositioned using fallback strategies just like regular elements.

There are also many updates to the WebKit implementation of Anchor Positioning, listed in Bug Fixes below.

Color improvements

After being the first browser to ship new color spaces to CSS, color-mix(), relative color syntax, and contrast-color(), we’re excited to bring even more improvements in Safari 26.2.

The color-mix() function blends two color values and returns the result. Previously, developers were required to list a color space as the first argument of the function, like this: color-mix(in srgb, magenta, pink). Since many developers used srgb out of habit, the results were often sub-optimal. Starting with Safari 26.2, you can use color-mix() without listing a color space — and get oklab as the default. You can just say color-mix(magenta, pink)
and you’ll get the same result as if you’d written color-mix(in oklab, magenta, pink). The Oklab color space is an especially good choice when blending colors, since the mathematical model is designed to make adjustments based on how humans perceive color, rather than pure numbers. Learn more about wide-gamut P3 color on the web in this video from WWDC.

Safari 26.2 also adds support for thescrollbar-color property. It takes two values — one for the color of the scrollbar thumb (the part that moves) and the second for the scrollbar track (the background). Note in all browsers, the track background color only has an effect according to how that particular operating system works, and is dependent on how the user has their personal settings for scrollbars configured. Along with scrollbar-width and scrollbar-gutter, the scrollbar-color property completes the set of modern properties for styling scrollbars, making it a good time to stop using the original ::-webkit-scrollbar pseudo elements.

Safari 26.2 also adds display-p3-linear to the predefined set of color spaces available for use, alongside of display-p3, srgb and others.

P3 is still a better color space to use in most cases, but having support for P3 Linear provides an easy way to use linear color math while optimizing performance compared to other options like rec2100-linear.

accent-color legibility

The accent-color property allows for the customization of form controls with native appearance, but certain color choices could make text or indicators within the control hard to read. Safari 26.2 fixes these issues in both light and dark mode while maintaining a consistently-displayed color across all form controls. To achieve this, the following behaviors are performed:

For elements with a light color scheme, if the luminance of the accent color is greater than 0.5, the displayed accent color is clamped back down to 0.5 while preserving the hue.

For elements with a dark color scheme, if the luminance of the accent color is less than 0.5, the displayed accent color is clamped back down to 0.5 while preserving the hue. If the luminance of the accent color is greater than 0.5, then the following controls adapt in order to remain legible:

  • checkboxes display with a dark check
  • radio buttons display with a dark indicator
  • submit buttons display with dark text by default
  • switch controls display with an increased drop shadow for the thumb in the on-state

Text decoration improvements

In Safari 26.2, the text-decoration property is a proper shorthand for all four of the longhand properties: text-decoration-line, text-decoration-color, text-decoration-style and text-decoration-thickness. This means you can combine separate underline qualities for underlines, overlines and sidelines into one CSS rule like this: text-decoration: green wavy underline 3px. This turned out to be a large project, requiring significant refactoring of decades-old code to untangle the interaction between text-decoration and editing code.

The text-decoration-line property now supports the new spelling-error and grammar-error values, which let you emulate the respective native error markers. For example, this code will take the browser’s default styling for spelling errors (whatever that might be) and apply it to the span of text:

.span {
  text-decoration-line: spelling-error;
}

(If you want to override the browser’s default styling for spelling or grammar errors, you can target it with ::spelling-error or ::grammar-error and apply styling as desired — a feature that shipped in Safari 17.4 and is supported in Chromium browsers.)

Finally, Safari 26.2 provides bug fixes for text decoration. The text-decoration-thickness and text-underline-offset have been fixed to properly work in vertical writing modes. And the default computed value for text-emphasis-style: filled|open has been corrected to be filled|open circle instead of filled|open dot in horizontal typographic modes.

@scope

The @scope rule now correctly handles implicit scoping roots when used with constructed and adopted stylesheets in shadow DOM contexts. Previously, styles defined in constructed stylesheets might not have properly respected the shadow boundary as an implicit scope.

This improvement ensures that when you create a CSSStyleSheet programmatically and adopt it into a shadow root, any @scope rules without an explicit root will correctly treat the shadow root as the implicit scoping boundary:

const sheet = new CSSStyleSheet();
sheet.replaceSync(`
  @scope {    .item { padding: 1em; }  }`);
shadowRoot.adoptedStyleSheets = [sheet];

This is particularly important for developers building design systems and component libraries using the Constructable Stylesheets API, which is a common pattern in modern Web Components.

WebKit for Safari 26.2 supports using :host as the scoping root in @scope rules. This allows you to create scoped styles that target the shadow host element, making it easier to write encapsulated component styles.

@scope(:host) {
  /* Styles scoped to elements within the shadow host */
  .component {
    color: blue;
  }
}

This feature enhances the ability to write modular, component-based styles while maintaining proper encapsulation boundaries in Web Components.

Safari 26.2 adds support for using the :scope pseudo-class when the scoping root matches the :visited pseudo-class. This allows you to create sophisticated scoping patterns that take link visitation state into account.

@scope (a:visited) {
  :scope {
    color: green;
  }
}

MathML

A new value forfont-family is now available in Safari 26.2, named math. This moves a previously directly-coded stack of fonts intended for use with mathematical expressions from being defining in the UA style sheet for just the <math> element to a new rule that can be called anywhere with font-family: math. Math fonts include specialized data for equation layout — things like stylistic variants for stretching glyphs, proper handling of superscripts and subscripts, brackets that span multiple lines, and double-struck characters.

The newmath-shift CSS property gives you the ability to create a more tightly compacted rendering of formulas by using math-shift: compact to reduce the vertical shift of superscripts.

More CSS

Safari 26.2 adds support for cross-origin() and referrer-policy() CSS URL modifiers. These let you control CORS and referrer behavior directly in CSS when loading resources like images or fonts, without needing HTML attributes or server headers. You can now specify these policies inline with your url() functions for more granular control over resource fetching.

The cursor property now works on pseudo-elements like ::before and ::after. Previously, you couldn’t change the cursor when hovering over content generated by pseudo-elements — now you can style them just like regular elements, giving you more control over the user experience and visual feedback throughout your interface.

Positioned boxes in scrollable containing blocks can now overflow in the scrollable direction. Previously, positioned elements might be clipped or behave unexpectedly when they extended beyond their scrollable container. This fix ensures that positioned content can properly overflow in the direction the container scrolls, making it easier to create complex layouts with scrollable regions and absolutely or fixed positioned elements.

Web API

Navigation API

Safari 26.2 adds support for the Navigation API. A modern replacement for the History API, it’s designed to give you better control over browser navigation. For years, web developers have relied on the History API (pushState, replaceState, and the popstate event) to build single-page applications, but it had significant limitations — you couldn’t intercept link clicks or form submissions, the popstate event only fired for back-forward navigation, and managing navigation state was cumbersome. The Navigation API solves these problems with a cleaner, more powerful interface.

The key feature is the navigate event, which fires for all types of navigation — link clicks, form submissions, back-forward buttons, and programmatic changes. You can intercept these navigations and handle them client-side, making it much easier to build SPAs without routing libraries. The API is also promise-based, so you can easily coordinate async operations like data fetching with navigation changes, and it includes built-in state management for each navigation entry. Here’s a simple example of client-side routing:

navigation.addEventListener('navigate', (event) => {
  // Only intercept same-origin navigations
  if (!event.canIntercept) return;

  event.intercept({
    async handler() {
      // Fetch and display new content
      const response = await fetch(event.destination.url);
      const html = await response.text();
      document.querySelector('main').innerHTML = html;
    }
  });
});

With this code, all link clicks and navigation within your site are automatically intercepted and handled client-side, turning your multi-page site into a single-page application with just a few lines of code.

Scrollend

Safari 26.2 adds support for the scrollend event, which fires once when scrolling definitively completes. Whether triggered by user gestures, keyboard navigation, smooth scrolling, or JavaScript, this event gives you a reliable signal that scrolling has finished. Previously, developers had to debounce the scroll event with timers to detect when scrolling stopped, which was imprecise and required guessing at appropriate delay values.

The scrollend event is useful for tasks that should only happen after scrolling is done, like lazy-loading content, updating UI based on final scroll position, or saving scroll state. Instead of firing dozens of times during a scroll, you get one clean event when it’s actually over.

document.addEventListener('scrollend', () => {
  // Scrolling has completely finished
  const scrollPosition = window.scrollY;
  localStorage.setItem('scrollPosition', scrollPosition);

  // Or lazy-load images now in view
  loadVisibleImages();
});

This fires once when scrolling stops, giving you a clean, reliable way to respond to the final scroll position without any timer-based guesswork.

View Transitions

Safari 26.2 adds support for document.activeViewTransition, which exposes the currently active View Transition. This gives you direct access to the transition object while it’s running, allowing you to check its state, wait for specific phases to complete, or programmatically control the transition. You can use this to coordinate other animations or UI updates with your View Transitions, or to handle cases where you need to know if a transition is already in progress before starting a new one.

Text Interaction

WebKit for Safari 26.2 adds support for document.caretPositionFromPoint(). This method is useful whenever you want to convert screen coordinates (x, y) into a text position in the document, giving you character-level precision for sophisticated text interaction (like building text editors, annotation tools, or custom selection interfaces).

Pointer and Touch Events

Safari 26.2 adds support for fractional coordinates in PointerEvent and TouchEvent properties like clientX, clientY, pageX, pageY, offsetX, offsetY, and screenX/screenY. Previously, these values were rounded to whole numbers, but they now include sub-pixel precision as decimal values (like 150.7 instead of 151). This gives you more accurate position data for touch and pointer interactions, which is useful for precise drawing applications, gesture recognition, or smooth animation tracking. Note that MouseEvent coordinates remain whole numbers, so if you’re handling multiple input types, you may see different precision levels depending on the event type.

As part of Interop 2025, Safari 26.2 continues to improve the interoperability of Pointer and Mouse Events. Early implementations of these events predated any official web standard, leading to inconsistencies across browsers. In 2022, the Interop Project took on Pointer and Mouse Events as an investigation to sort out the complexities and chart a path forward. The official web standard for Pointer Events was first published in November 2024, and WebKit now further aligns with that maturing specification.

Service Workers

Safari 26.2 improves asynchronous URL error handling in Service Workers. Invalid or malformed URLs now properly throw catchable errors in async operations, allowing you to handle URL errors gracefully with try/catch blocks.

Cookie Store

Safari 26.2 adds support in the Cookie Store API for handling cookieStore.set() calls with an empty string path. Previously, passing an empty string for the path parameter could cause issues or unexpected behavior. Now you can explicitly set a cookie with an empty path string, and the API will handle it correctly, giving you more flexibility in how you manage cookie paths programmatically.

The CookieStore API originally shipped in Safari 18.4. Now, with Safari 26.2, it enforces stricter validation for special cookie name prefixes to prevent misuse. Cookies with certain prefixes have special security meanings: the __Host- prefix indicates an extra-secure cookie (must be secure, no domain, path must be /), and the __Secure- prefix signals it must be sent over HTTPS only. Cookies with invalid names like __Host-Http- and __Http- are now rejected.

And more

Safari 26.2 removes support for the overflowchanged event, which was never standardized. This event fired when an element’s overflow state changed, but modern alternatives like ResizeObserver offer more robust ways to detect layout changes.

Performance API

Safari 26.2 adds support for two tools that measure the performance of web applications, Event Timing API and Largest Contentful Paint.

The Event Timing API lets you measure how long it takes for your site to respond to user interactions. When someone clicks a button, types in a field, or taps on a link, the API tracks the full timeline — from the initial input through your event handlers and any DOM updates, all the way to when the browser paints the result on screen. This gives you insight into whether your site feels responsive or sluggish to users. The API reports performance entries for interactions that take longer than a certain threshold, so you can identify which specific events are causing delays. It makes measuring “Interaction to Next Paint” (INP) possible.

Largest Contentful Paint (LCP) measures how long it takes for the largest visible element to appear in the viewport during page load. This is typically your main image, a hero section, or a large block of text — whatever dominates the initial view. LCP gives you a clear signal about when your page feels loaded to users, even if other resources are still downloading in the background.

Web Inspector

Web Inspector now shows Largest Contentful Paint entries in the Timelines tab, both in the Layout & Rendering timeline and in the events list. This makes it easier to see exactly when your page’s largest visible element rendered, and to correlate that timing with other layout and rendering work happening on the page.

Web Animations API

Safari 26.2 adds support for theoverallProgress property on the Animation interface. It gives you a number between 0 and 1 that represents how far the animation has progressed toward completion. Unlike tracking individual iterations, this tells you the progress across the entire animation — so if you have an animation set to run three times, overallProgress tracks the journey from start to finish across all three iterations.

The Animation.commitStyles() method now works with completed animations, letting you persist their final state as inline styles. You can run an animation to completion, lock in the result, and remove the animation itself — keeping the visual effect while freeing up resources.

JavaScript

Safari 26.2 adds support for Math.sumPrecise(), which sums multiple numbers with better floating-point accuracy. Regular addition accumulates rounding errors with each operation — adding 12 numbers means 11 separate additions, each losing a bit of precision. Math.sumPrecise() performs the entire sum as a single operation, eliminating accumulated error.

Safari 26.2 adds support for Map.prototype.getOrInsert() and WeakMap.prototype.getOrInsert(), letting you retrieve a value or insert a default if the key doesn’t exist — all in one operation. This simplifies the common check-then-set pattern, making code that builds up data structures cleaner and more efficiently.

WebAssembly

WebKit for Safari 26.2 adds support for Wasm Memory resizable buffer APIs. They give you more control over how WebAssembly memory is allocated and managed, allowing code in both Wasm and JavaScript to directly access the same memory space for efficient communication.

JS String Builtins now let Wasm modules import string constants without substantial glue code, reducing memory usage for some Wasm applications. They also enable working directly with JavaScript strings without calling imported JavaScript functions, avoiding the overhead of Wasm-to-JavaScript calls.

Safari 26.2 also adds two resizable ArrayBuffer methods to WebAssembly to allow conversion between fixed-length and resizable memory buffers. The WebAssembly.Memory.prototype.toResizableBuffer() method converts a WebAssembly memory instance to use a resizable ArrayBuffer, whose maximum size is determined by the memory’s maximum property. The WebAssembly.Memory.prototype.toFixedLengthBuffer() method converts a WebAssembly memory instance to use a fixed-length ArrayBuffer, which is the traditional WebAssembly memory behavior.

Back in September 2025, Safari 26.0 added support for WebAssembly.JSTag to bridge exception handling between JavaScript and WebAssembly. When JavaScript throws an exception into Wasm, the exception is wrapped with a JSTag so WebAssembly can catch and handle it appropriately.

WebGPU

Support for WebXR first shipped in Safari 18.0 in visionOS 2 in September 2024. It relied on WebGL for rendering 3D graphics. In September 2025, Safari 26.0 added support for WebGPU for use cases outside of WebXR. WebGPU is similar to WebGL in its capabilities, but it’s designed to better match how modern GPUs work, resulting in better performance and more flexibility. Now with Safari 26.2 on visionOS, WebXR supports WebGPU. This means you can build experiences in WebXR with the full power of WebGPU.

Safari 26.2 also adds support for using GPUTexture objects as depth-stencil attachments in rendering operations and resolve attachments in WebGPU render passes. Depth-stencil attachments are special textures used during 3D render. Depth tracks how far away each pixel is from the camera, while stencil creates masks and templates that control which pixels get rendered. This change simply updates to the very latest specification now allowing texture in JavaScript anyplace you would have used texture.createView() in the past. Now, both are supported.

SVG

Safari 26.2 brings many improvements to SVG. First, the <a> element now supports the type attribute, which lets you specify the MIME type of the linked resource. For instance:

<svg>
  <a href="destination.svg" type="image/svg+xml">
    <text x="10" y="20">Click me</text>
  </a>
</svg>

The repeatEvent in SVG animations is an event that fires each time an SVG animation element repeats its iteration, now supported to better align with the SMIL specification and match other browsers. Safari 26.2 also supports the onbegin event handler property defined in the SVGAnimationElement IDL (Interface Definition Language) that fires when an SVG animation starts.

The async attribute in SVGScriptElement controls whether a script should be executed asynchronously, matching the behavior of HTMLScriptElement.

<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200">
  <script href="external-script.js" async="true"/>
  <!-- or -->
  <script href="another-script.js" async="async"/>
</svg>

Safari 26.2 supports the hreflang attribute on SVGAElement. It specifies the language of the linked resource, matching the functionality of HTMLAnchorElement to improve SVG link handling.

And see the list of bug fixes below for the full picture of the improvements to SVG.

WebRTC

Safari 26.2 adds support for the encrypted field to RTCRtpHeaderExtensionParameters. This gives you as a developer more fine-grained control over which metadata is visible to network intermediaries versus what remains private end-to-end. The RTCRtpHeaderExtensionParameters WebRTC API interface describes Real-time Transport Protocol header extensions used in media streams. Additional metadata alongside the already-encrypted media payload, might include audio levels, video orientation, timing information and custom application data.

Canvas

WebKit for Safari 26.2 removes the non-standard legacy drawImageFromRect. Created in the earliest days of Canvas API, it was originally intended to be an intuitive way to draw a rectangular portion of an image, but it never became part of the official Canvas specification. Instead the standard drawImage() method provides this functionality, supported since Safari 2.

Web Compatibility

Safari 26.2 ships support for CHIPS (Cookies Having Independent Partitioned State) once again. Originally shipped in Safari 18.4 in March 2025, CHIPS was temporarily removed in Safari 18.5 for further refinement.

Beginning with Intelligent Tracking Prevention 1.0 in 2017, we began unconditionally partitioning cookies. Over the next two year, WebKit’s implementation evolved based on feedback, until in 2019 we stopped partitioning cookies. Based on this experience, CHIPS was designed as a way to give site owners control over whether their cookies are partitioned.

Now, partitioned cookies let third-party content — like embedded chat widgets, payment processors, or SaaS tools in iframes — opt-in to using cookies on a specific site without enabling cross-site tracking. Safari blocks setting and accessing cross-site cookies when partitioning was not requested.

If siteB.example is embedded in an iframe on siteA.example and sets a partitioned cookie, that cookie is only accessible when siteB.example appears on siteA.example — not on other sites.

To create a partitioned cookie, add the Partitioned attribute along with SameSite=None and Secure:

Set-Cookie: TestCookie=12345; SameSite=None; Secure; Partitioned

Or in JavaScript:

document.cookie = "TestCookie=12345; SameSite=None; Secure; Partitioned";

Safari partitions cookies by site (not origin), matching other browsers. This means news.siteA.example and mail.siteA.example share the same partition since they’re subdomains of siteA.example.

If you need access to unpartitioned third-party cookies, continue using the Storage Access API with document.requestStorageAccess().

WebDriver

WebDriver in Safari 26.2 makes it easy to write automated tests that exercise different user scenarios involving the Storage Access API. Using the new Set Storage Access WebDriver command, a test can validate what happens when cross-site storage access is granted or denied (i.e., when calling document.requestStorageAccess()). The Set Permissions WebDriver command has been extended to support the storage-access key. This allows a test to validate website behavior when the user has configured permissions to always accept or reject such storage-access requests.

WebKit API

Now in iOS 26.2, iPadOS 26.2, macOS 26.2, watchOS 26.2 and visionOS 26.2, WebKit supports exporting more types of WebPage content using SwiftUI Transferable APIs — now including .png, .flatRTFD, .rtf, and .utf8PlainText.

Web Extensions

Safari 26.2 brings some much-requested APIs for developers of extensions. These enhancements make it easier than ever for you to help your users get started using a Safari Web Extension.

First, from your native iOS, iPadOS, and visionOS application, it’s now possible to determine if your Safari Web Extension has been turned on by the user. To do so, call SFSafariExtensionManager.stateOfExtension(withIdentifier identifier: String) async throws -> SFSafariExtensionState. The SFSafariExtensionState result has an isEnabled boolean property that lets your app know if the extension has been turned on.

Once you’ve determined the state of your extension, you may want to help your user understand where to turn it on. With one function call, you can deep link into Safari Extension Settings directly into the view for your extension. To do so, call
SFSafariSettings.openExtensionsSettings(forIdentifiers: [``String``]) async throws and pass in an array of the composed identifiers for your extension(s) as the extensionIdentifiers argument. If you pass a single identifier, the user will be deep linked directly into settings for that extension. If you pass multiple identifiers, the user will be deep linked into Safari Extension Settings and see the list of all installed extension, highlighting the rows of the extensions whose identifiers you passed in.

Finally, if you’re currently shipping a Safari App Extension, Safari 26.2 on macOS introduces improvements to the migration path for your users. Previously, it’s been possible to associate your web extension with an app extension and replace it during an upgrade. By making this association, it would automatically turn the web extension on and hide the app extension. In Safari 26.2 and later, if the Safari web extension replaces a single Safari app extension, Safari also migrates web permissions and the setting that indicates whether the user allows the extension in Private Browsing. Safari only migrates website permissions if the Safari web extension does not require more website access than the Safari app extension.

Also introduced browser.runtime.getVersion() as a simplified synchronous method to retrieve the installed extension version, replacing the common pattern of runtime.getManifest().version.

Bug fixes and more

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

Accessibility

  • Fixed Voice Control number and name overlays not labeling content inside iframe elements. (118252216)
  • Fixed incorrect accessibility bounds of SVG roots, ensuring assistive technologies have access to the correct geometry. (153782363)
  • Fixed an issue where <label> elements targeted by aria-labelledby stopped providing accessibility text to assistive technologies after dynamic page changes. (158906980)

Animations

  • Fixed inheritance of additional animation properties (animation-timing-function, animation-play-state, animation-iteration-count, and animation-direction) in ::view-transition pseudo-elements to improve style consistency. (156131284)
  • Fixed animation-name resolution to correctly find matching @keyframes within tree-scoped and shadow DOM contexts. (156484228)
  • Fixed a bug where extremely large animation-duration values could cause the page to become unresponsive. (158775366)
  • Fixed Animation.commitStyles() so that custom properties are properly committed to the target element. (158919736)
  • Fixed Animation.commitStyles() to correctly commit logical properties as their corresponding physical properties. (158920529)

Browser

  • 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 -webkit-user-select: none disabling find-in-page in Safari. (8081660)
  • Fixed incorrect z-ordering for position: sticky elements. (90572595)
  • Fixed getComputedStyle to correctly ignore ::first-letter and ::first-line styles on flex containers, to reflect used style. (94163778)
  • Fixed style invalidation so that :scope selectors always match even when the scoping root is unavailable. (135907710)
  • Fixed propagation of the body element’s writing-mode to the document element to match the CSS Writing Modes Level 4 specification. (149475070)
  • Fixed @position-try so that revert-layer correctly only reverts the position-try origin instead of affecting other cascade origins. (154355428)
  • Fixed positioned boxes in scrollable containing blocks to overflow in scrollable direction. (155625030)
  • Fixed anchor-center so that when an anchored element has no anchor in the same containing block, it correctly falls back to behaving like center as specified. (155768216)
  • Fixed anchor() positioning in CSS Grid to correctly account for grid-area. (155823420)
  • Fixed the default computed value for text-emphasis-style: filled|open to be filled|open circle not filled|open dot in horizontal typographic modes. (155911820)
  • Fixed the text-decoration shorthand to cover all four longhand properties. (156011594)
  • Fixed an issue where @namespace rules that failed insertion could still affect the namespace node. (156651404)
  • Fixed incorrect handling of auto inline margins on grid items during track sizing that caused excessive vertical spacing in subgrids. (157638931)
  • Fixed automatic min-size handling for flex and grid items to correctly treat overflow: clip as non-scrollable, aligning with the CSS specifications. (158215991)
  • Fixed incorrect baseline alignment for <button> elements when contain: layout is applied by using the content box as the baseline. (159007878)
  • Fixed out-of-flow box with no sibling ignoring align-content. (159097576)
  • Fixed CSS anchor positioning to remember the last successful position option at ResizeObserver delivery time, aligning with the spec. (159225250)
  • 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. (159613287)
  • Fixed an issue where collapsed table rows subtracted border-spacing twice. (160542118)
  • Fixed ::view-transition pseudo-element to use position: absolute instead of fixed to align with the updated specification. (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. (160696378)
  • Fixed handling of ::first-letter pseudo-elements to always force inline display unless floated.(160710650)
  • Fixed the behavior of the nesting selector & directly inside @scope to correctly act like :where(:scope) for proper specificity handling. (160769736)
  • Fixed position-try-fallback resolution by treating names as tree-scoped references to properly search shadow DOM host scopes. (161081231)
  • Fixed an issue where a <select> element with long <option> text caused horizontal scrolling when nested inside a flex item. (161563289)
  • Fixed getComputedStyle to return numeric values for orphans and widows instead of the internal auto value, ensuring the computed values correctly reflect the CSS specification. (161566631)
  • Fixed column-count: 1 so that it now correctly creates a multi-column container per the CSS Multi-column Layout specification. (161611444)
  • Fixed the calculation of anchor positions in vertical-rl multi-column layouts by correctly flipping coordinates in fragmented flows. (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. (161714637)
  • Fixed position-area handling to include the in-flow scrollable area of the initial containing block. (161741583)
  • Fixed position-visibility: no-overflow to respond correctly to scrolling. (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.(162214793)
  • Fixed auto margins by converting them to zero when position-area or anchor-center is applied in CSS Anchor Positioning. (162809291)
  • Fixed FontFace.family so that font family names with spaces are treated as plain strings without parsing or serializing aligning with other browsers. (163047573)
  • Fixed flex and grid layout to correctly handle padding and margins in all writing modes. (163048874)
  • Fixed an issue where underlines using text-underline-position: right appeared on top of the text in horizontal writing modes instead of under the line. (163506701)
  • Fixed an infinite style resolution loop when a position-try box was inside a display: none tree. (163691875)
  • Fixed position-area alignment so that when only one inset is set to auto, the element now aligns toward the opposite non-auto inset. (163691905)
  • Fixed text-decoration-thickness and text-underline-offset not working in vertical writing modes. (163727749)
  • Fixed an issue where scrollRectToVisible() did not scroll pages to bring fixed anchor-positioned boxes into view when navigating with the keyboard. (163764088)
  • Fixed an issue where anchor-positioned elements transitioning from display: block to display: none can not anchor themselves to the anchor. (163861918)
  • Fixed incorrect underline positioning for text-decoration when inline box sides are trimmed. (163909909)

DOM

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

Editing

  • Fixed jumbled text when copy/pasting bidirectional text starting with left-to-right. (152236717)
  • Fixed paste performance in textarea by skipping unnecessary layout calls and only removing unrendered text nodes in richly editable fields. (157813510)

Events

  • Fixed boundary pointer and mouse events not firing when the hit test target changed under a stationary pointer. (160147423)

Forms

  • Fixed an issue where some websites may sometimes fail to reveal the focused element when the keyboard appears. (50384887)
  • Fixed form controls to preserve legibility when using accent-color in both light and dark modes by adjusting luminance thresholds and updating submit button text handling. (99018889)
  • 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. (123125836)
  • Fixed <select> element with long <option> text causing horizontal scrolling in grid or flex containers. (141633685)
  • Fixed an issue on iOS where backing out of the “Take a photo/video” file upload flow caused the upload button to stop working. (157789623)
  • Fixed painting for <input type="range"> sliders in right-to-left vertical block writing modes. (158567821)
  • Fixed an issue where tainted scripts were blocked from reading values of form fields they created, now allowing access if the field was not modified by user input. (163299988)

HTML

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

Home Screen Web Apps

  • Fixed an issue where an audio element failed to play when re-opening a Home Screen Web App. (155336513)

Images

  • Fixed HDR images in CSS backgrounds, CSS borders and inside SVG images so they are now properly decoded and rendered in HDR mode. (158076668)

JavaScript

  • Fixed non-standard new Date(2024-12-3) yielding to an “Invalid Date” error. (141044926)
  • Fixed “text/json/json+json” to be considered an invalid JSON MIME type. (154912716)
  • Fixed compatibility issues with the timezone option in the Intl.DateTimeFormat constructor. (156148700)
  • Fixed Intl.Local#language to return "und" if the language subtag is "und". (156248659)
  • Fixed Intl to support non-continental timezones to align with the specification. (156424446)
  • Fixed exception check errors by adding a missing exception check for Array#flat. (157525399)
  • Fixed an issue where the module loader would incorrectly attempt to refetch a module after a failed fetch. (158084942)
  • Fixed Iterator.prototype.flatMap to properly handle iterators without a return method. (158783404)
  • Fixed poor error messages for destructing null or undefined values. (159340067)
  • Fixed TypeError messages to be clearer in for-of loops. (159814766)
  • Fixed TypeError messages when calling class or function constructors without new to include the constructor name. (161152354)

MathML

  • Fixed rendering of unknown MathML elements so they now behave like mrow as required by the MathML Core specification. (148593275)
  • Fixed mfenced elements to render like mrow. (161416576)

Media

  • Fixed western Arabic numbers being displayed in the video viewer instead of eastern Arabic numbers. (141281469)
  • Fixed WebVTT line-height to be normal by default, not 1. (156633220)
  • Fixed handling of null media accessibility caption profile. (159134245)
  • Fixed hiding and resuming a webm video that sometimes causes a decoding error. (159508950)
  • Fixed MediaRecorder to no longer fire erroneous error events when stopped immediately after track changes, aligning behavior with Chrome and closer to Firefox. (161124260)
  • 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. (162547969)

Networking

  • Fixed an issue where rel=preload link headers with a nonce could trigger erroneous Content-Security-Policy-Report-Only violations due to the nonce not being copied into the fetch options. (75060055)
  • Fixed an issue where apps that are mistakenly calling the WKWebView API loadRequest from a background thread may end up crashing. (162070925)

PDF

  • Fixed an issue where the active PDF annotation hover effect would remain visible after moving the pointer away. (162951528)

Rendering

  • Fixed incorrect clipping of position: fixed and position: sticky content during view transitions. (154886047)
  • Fixed computing static position to correctly size and locate an inset modified containing block. (155650719)
  • Fixed alignment candidate to consider both first and last baseline item position. (155806707)
  • Fixed the cross axis direction in flexbox to properly consider text directionality when the cross axis aligns with the inline axis to handle direction property and flex-wrap: wrap-reverse interactions. (156540996)
  • Fixed <button> elements to use the last line as their baseline instead of the first line to ensure consistent alignment with <br> in the text. (157955703)
  • Fixed orthogonal table cells so their writing-mode is no longer forced to match the table, allowing proper vertical layout. (158221827)
  • Fixed an issue where exiting fullscreen could scroll to unscrollable areas. (158351089)
  • Fixed an issue where the padding end incorrectly contributed to scrollable overflow when the inline direction was flipped. (158529814)
  • Fixed word breaking so that a hyphen followed by a Latin-1 Supplement character (U+00C0–U+00FF) correctly allows line breaks. (158942361)
  • Fixed large inset box shadows to render correctly. (159888287)
  • Fixed an issue where sticky elements at the edge of the viewport could disappear during rubber band scrolling. (160385933)
  • Fixed an issue where selecting table cells could cause overlapping selections in flex and grid layouts. (160805174)
  • Fixed flickering of elements with slow-painting content during view transitions. (160886647)
  • Fixed an issue where elements with both opacity and CSS filter effects could render incorrectly. (161130683)
  • Fixed an issue where elements with background images were not counted as contentful for Paint Timing. (161456094)
  • Fixed scroll-to-text fragment highlights to automatically choose a contrasting foreground and background color to keep text readable on dark pages, while respecting custom ::target-text styles. (163166782)
  • Fixed an issue where fullscreen dialog backdrops did not properly extend below the address bar by extending the backdrop’s background into obscured inset areas. (163535684)
  • Fixed an issue where slotted text nodes could become hidden when adjacent elements in a flex container changed their display property. (163571747)

SVG

  • 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. (98577657)
  • Fixed an issue where stop-color incorrectly accepted hashless hex color values like 1234 by treating them as invalid to follow the spec. (119166640)
  • Fixed SVGMarkerElement to correctly support the SVG_MARKER_ORIENT_AUTO_START_REVERSE value, aligning behavior with the spec and other browsers. (123453058)
  • Fixed absolutely positioned SVG elements to correctly account for the containing block’s padding. (127608838)
  • Fixed handling of word-spacing so that leading white space in SVG text correctly applies spacing at the start of a text box. (134941299)
  • Fixed an issue where SVGs with a 0px intrinsic width were ignored but now correctly respect degenerate aspect ratios and fall back to the viewBox aspect ratio. (156339128)
  • Fixed handling semicolons at end of a keySplines value. (156511711)
  • Fixed unnecessary rounding of viewportLocation in 'foreignObject' layout. (156740732)
  • Fixed <svg> elements to correctly calculate their intrinsic aspect ratio using currentViewBoxRect() when a <view> is referenced. (157445966)
  • Fixed SVGFETurbulenceElement to correctly fallback numOctaves to 1 for invalid or negative values, aligning with the specification and other browsers. (158988528)
  • Fixed an issue where SVG pattern tileImage could appear blurred or pixelated when zooming or printing. (159202567)
  • Fixed SVGStyleElement so that its type and media attributes now use pure attribute reflection, matching HTMLStyleElement. (159358585)
  • Fixed an issue where <view> element was not applied to the root element. (159705519)
  • 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. (160724516)

Security

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

Service Worker

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

Storage

  • Fixed WebSockets to correctly inherit storage access from the frame that created them. (147949918)
  • Fixed requestStorageAccess() should always grant access when called from a same-site iframe. (156545395)
  • Fixed Storage Access API to not be usable in insecure contexts. (157337423)
  • Fixed requestStorageAccess() to reject with a NotAllowedError. (157446015)
  • Fixed an issue where cross-origin navigations incorrectly preserved storage access. (158446697)
  • Fixed an issue where dedicated workers could inherit storage access from their parent document, preventing them from sending cross-site requests with cookies. (158814068)

Tables

  • Fixed collapsed table rows retaining nonzero height. (158276634)

Web API

  • Fixed an issue where the first pointerdown event was lost after triggering a context menu by right-clicking. (84787733)
  • 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. (117269418)
  • Fixed the ability to delete a cookie through Cookie Store API that was set through document.cookie. (142339417)
  • Fixed Trusted Types to only verify event handler attributes for elements in the XHTML, SVG, and MathML namespaces, preventing incorrect checks on other namespaces. (147763139)
  • Fixed reading the mutable field from the outer object instead of as a child of notification. (157475553)
  • Fixed location.protocol setter to be restricted to HTTP(S) schemes. (157607342)
  • Fixed scroll and scrollend events so they correctly fire on <input type="text"> elements instead of their inner elements. (157880733)
  • Fixed CookieStore methods to strip tabs and spaces from the names and values passed in. (157907393)
  • Fixed JSON modules to fetch with an application/json Accept header. (158176845)
  • Fixed an issue where click and auxclick event targeting does not follow pointer capture target override. (159477637)
  • 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. (160913756)
  • Fixed element.scrollTo and element.scrollBy so they correctly scroll text input fields by forwarding scroll operations to the inner text element. (160963921)
  • Fixed EventCounts interface was not maplike. Enables use of methods such as .forEach(), keys(), and entries(). (160968888)
  • Fixed an issue where mousemove events were still dispatched to removed mouseover targets instead of their parent element when the target was deleted. (161203639)
  • Fixed missing pointerenter and mouseenter events when a child element moved under the mouse. (161362257)
  • Fixed an issue where only one CSP violation report was sent for multiple enforced require-trusted-types-for directives. (161740298)
  • Fixed Trusted Types incorrectly treating null or undefined policy return values as null instead of empty strings during createHTML, createScript, and createScriptURL operations. (161837641)
  • Fixed attachShadow() to default to using the global custom element registry instead of the host’s registry when customElementRegistry is null. (161949419)
  • Fixed attachShadow() to use the global custom element registry by default when customElementRegistry is null, aligning with the specification. (161949493)

Web Extensions

  • Fixed an issue where onInstalled getting called after every launch of Safari when opening a profile window. (147491513)
  • Fixed sender.origin parameter to match window.location.origin. (155884667)
  • Fixed an issue where Safari extension popups could open scrolled down and some websites could flicker during scrolling. (155965298)
  • Fixed an issue that caused the web page to crash when navigating to certain URLs with an extension enabled. (158180410)

Web Inspector

  • Fixed syntax highlighting for JavaScript features like template literals, private class elements, optional chaining, and others. (107619553)
  • Fixed an issue where the Console truncated long string outputs. (124629101)
  • Fixed an issue where DOM elements represented in the Console could not be selected. (157015598)
  • Fixed an issue where newlines and indentation in HTML markup would show up in DOM node previews in the Console. (157225532)
  • Fixed an issue that prevented scrolling of the Media details sidebar from the Elements tab. (157768497)
  • Fixed an issue where accepting a completion suggestion for a shorthand property value would malform the combined value. (159107788)
  • Fixed an issue where navigating the DOM tree using the keyboard would get stuck in a loop within certain subtrees. (159841729)
  • 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. (160617913)
  • Fixed an issue where adding DOM attributes or node siblings did not work correctly when using the actions from the context menu. (161577627)

WebDriver

  • 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. (157031091)
  • Fixed an issue where element references nested inside Array or Object arguments were not properly extracted when executing scripts. (162571946)

WebRTC

  • Fixed camera indicator staying enabled even after ending a meeting or removing access to the camera. (152962650)

Feedback

We love hearing from you. To share your thoughts, find us online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn. If you run into any issues, we welcome your 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 or refresh the page and then select Report a Website Issue from Safari Page Menu to share feedback directly with Apple. Filing issues really does make a difference.

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

December 12, 2025 06:30 PM

December 08, 2025

Release Notes for Safari Technology Preview 233

Surfin’ Safari

Safari Technology Preview Release 233 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: 302450@main…303091@main.

Animations

Resolved Issues

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

HTML

New Features

  • Added support for enhanced HTML select parsing to allow <select> elements to include <optgroup>, <option>, and <hr> at deeper nesting levels. (302480@main) (163927485)

MathML

New Features

  • Added support for the MathML scriptlevel attribute, mapping it to CSS math-depth as a presentational hint. (302841@main) (164469677)

Resolved Issues

  • Fixed mpadded elements in RTL (dir="rtl") to respect lspace. (303070@main) (164740784)

Rendering

Resolved Issues

  • Fixed an issue where fixed positioned elements were not rendered correctly in right-to-left pages using the vertical-rl writing mode. (302542@main) (161712734)

Web API

New Features

  • Added support for reading Blob.stream() with a BYOB (getReader({mode:'byob'})) reader. ( 302787@main ) (164307723)

Resolved Issues

  • Fixed Trusted Types to correctly send CSP violation reports when a default policy returns an invalid javascript: URL or throws an exception. (302543@main) (160960418)
  • 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

New Features

  • Added support for capturing console.screenshot images within a Worker, including handling of ImageData, ImageBitmap, OffscreenCanvas, various CanvasRenderingContext types, and valid base64 data: URLs. (302778@main) (98223234)

December 08, 2025 10:21 PM

Igalia WebKit Team: WebKit Igalia Periodical #50

Igalia WebKit

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

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

Cross-Port 🐱

JavaScriptCore 🐟

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

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

Graphics 🖼️

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

WPE WebKit 📟

WPE Platform API 🧩

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

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

That’s all for this week!

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

December 05, 2025

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

Igalia WebKit

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

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

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

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

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

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

Currently, meow supports this set of manipulation commands:

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

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

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

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

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

Now some practical examples:

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

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

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

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

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

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

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

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

By eocanha at December 05, 2025 11:16 AM

December 04, 2025

::target-text: An easy way to style text fragments

Surfin’ Safari

You’re reading a great blog post. You want to share it with your friend but instead of getting them to read the whole thing, you really just want to highlight a few key sentences and have them go directly to that section of the page. That’s what text fragments are for.

As a user, you can highlight any section of text on a page and right click to make it a text fragment. In Safari, that means right clicking and selecting “Copy Link with Highlight” from the menu and getting a url that will highlight the text fragment when the page loads.

The default highlighting gives you a pale yellow highlight under the fragment text, like this:

Lorem ipsum blog post with pale yellow highlight for text fragment.

You can click on this link to see for yourself how it works.

That’s the user experience. But what about the developer experience? Is there something we developers can do to customize that experience for our users a bit more? Actually, there is! We’ll use the ::target-text pseudo-element to help us style our text fragment.

In your CSS file, use the ::target-text pseudo-element and style the text with whatever properties you wish, like this:

::target-text {
  background-color: blue;
  color: white;
}

That’ll get you this result:

Lorem ipsum blog post with white text on blue highlight for text fragment.

So if you want to decide how a text fragment looks to your users, take advantage of ::target-text and own the user’s text fragment experience. It’s fully supported in all browsers.

If you enjoyed this kind of bite-size content, let me know. You can reach 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.

December 04, 2025 07:21 PM

December 02, 2025

Igalia WebKit Team: WebKit Igalia Periodical #49

Igalia WebKit

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

The main highlights for this week are the completion of `PlainMonthDay` in Temporal, moving networking access for GstWebRTC to the WebProcess, and Xbox Cloud Gaming now working in the GTK and WPE ports.

Cross-Port 🐱

Multimedia 🎥

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

Xbox Cloud Gaming is now usable in WebKitGTK and WPE with the GstWebRTC backend, we had to fix non-spec compliant ICE candidates handling and add a WebRTC quirk forcing max-bundle in PeerConnections to make it work. Happy cloud gaming!

Support for remote inbound RTP statistics was improved in 303671@main, we now properly report framesPerSecond and totalDecodeTime metrics, those fields are used in the Xbox Cloud Gaming service to show live stats about the connection and video decoder performance in an overlay.

The GstWebRTC backend now relies on librice for its ICE. The Sans-IO architecture of librice allows us to keep the WebProcess sandboxed and to route WebRTC-related UDP and (eventually) TCP packets using the NetworkProcess. This work landed in 303623@main. The GNOME SDK should also soon ship librice.

Support for seeking in looping videos was fixed in 303539@main.

JavaScriptCore 🐟

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

Implemented the valueOf and toPlainDate for PlainMonthDay objects. This completes the implementation of Temporal PlainMonthDay objects in JSC!

WebKitGTK 🖥️

The GTK port has gained support for interpreting touch input as pointer events. This matches the behaviour of other browsers by following the corresponding specifications.

WPE WebKit 📟

Fixed an issue that prevented WPE from processing further input events after receiving a secondary mouse button press.

Fixed an issue that caused right mouse button clicks to prevent processing of further pointer events.

WPE Platform API 🧩

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

We landed a patch to add a new signal in WPEDisplay to notify when the connection to the native display has been lost.

Infrastructure 🏗️

Modernized the CMake modules used to find libtasn1, libsecret, libxkbcommon, libhyphen, and Enchant libraries.

Note that this work removed the support for building against Enchant 1.x, and only version 2 will be supported. The first stable release to require Enchant 2.x will be 2.52.0 due in March 2026. Major Linux and BSD distributions have included Enchant 2 packages for years, and therefore this change is not expected to cause any trouble. The Enchant library is used by the GTK port for spell checking.

Community & Events 🤝

We have published an article detailing our work making MathML interoperable across browser engines! It has live demonstrations and feature tables with our progress on WebKit support.

We have published new blogs post highlighting the most important changes in both WPE WebKit and WebKitGTK 2.50. Enjoy!

That’s all for this week!

By Igalia WebKit Team at December 02, 2025 02:15 PM

November 27, 2025

WPE WebKit Blog: Highlights of the WPE WebKit 2.50 release series

Igalia WebKit

This fall, the WPE WebKit team has released the 2.50 series of the Web engine after six months of hard work. Let’s have a deeper look at some of the most interesting changes in this release series!

Improved rendering performance

For this series, the threaded rendering implementation has been switched to use the Skia API. What has changed is the way we record the painting commands for each layer. Previously we used WebCore’s built-in mechanism (DisplayList) which is not thread-safe, and led to obscure rendering issues in release builds and/or sporadic assertions in debug builds when replaying the display lists in threads other than the main one. The DisplayList usage was replaced with SkPictureRecorder, Skia’s built-in facility, that provides similar functionality but in a thread-safe manner. Using the Skia API, we can leverage multithreading in a reliable way to replay recorded drawing commands in different worker threads, improving rendering performance.

An experimental hybrid rendering mode has also been added. In this mode, WPE WebKit will attempt to use GPU worker threads for rendering but, if these are busy, CPU worker threads will be used whenever possible. This rendering mode is still under investigation, as it is unclear whether the improvements are substantial enough to justify the extra complexity.

Damage propagation to the system compositor, which was added during the 2.48 cycle but remained disabled by default, has now been enabled. The system compositor may now leverage the damage information for further optimization.

Vertical writing-mode rendering has also received improvements for this release series.

Changes in Multimedia support

When available in the system, WebKit can now leverage the XDG desktop portal for accessing capture devices (like cameras) so that no specific sandbox exception is required. This provides secure access to capture devices in browser applications that use WPE WebKit.

Managed Media Source support has been enabled. This potentially improves multimedia playback, for example in mobile devices, by allowing the user agent to react to changes in memory and CPU availability.

Transcoding is now using the GStreamer built-in uritranscodebin element instead of GstTranscoder, which improves stability of the media recording that needs transcoding.

SVT-AV1 encoder support has been added to the media backend.

WebXR support

The WebXR implementation had been stagnating since it was first introduced, and had a number of shortcomings. This was removed in favor of a new implementation, also built using OpenXR, that better adapts to the multiprocess architecture of WebKit.

This feature is considered experimental in 2.50, and while it is complete enough to load and display a number of immersive experiences, a number of improvements and optional features continue to be actively developed. Therefore, WebXR support needs to be enabled at build time with the ENABLE_WEBXR=ON CMake option.

Android support

Support for Android targets has been greatly improved. It is now possible to build WPE WebKit without the need for additional patches when using the libwpe-based WPEBackend-android. This was achieved by incorporating changes that make WebKit use more appropriate defaults (like disabling MediaSession) or using platform-specific features (like ASharedMemory and AHardwareBuffer) when targeting Android.

The WebKit logging system has gained support to use the Android logd service. This is particularly useful for both WebKit and application developers, allowing to configure logging channels at runtime in any WPE WebKit build. For example, the following commands may be used before launching an application to debug WebGL setup and multimedia playback errors:

adb setprop log.tag.WPEWebKit VERBOSE   # Global logging filter
adb setprop debug.WPEWebKit.log 'WebGL,Media=error'  # Channels
adb logcat -s WPEWebKit                   # Follow log messages

There is an ongoing effort to enable the WPEPlatform API on Android, and while it builds now, rendering is not yet working.

Web Platform support

As usual, changes in this area are extensive as WebKit constantly adopts, improves, and supports new Web Platform features. However, some interesting additions in this release cycle include:

API changes

WPEPlatform

Work continues on the new WPEPlatform API, which is still shipped as a preview feature in the 2.50 and needs to be explicitly enabled at build time with the ENABLE_WPE_PLATFORM=ON CMake option. The API may still change and applications developed using WPEPlatform are likely to need changes with future WPE WebKit releases; but not for long: the current goal is to have it ready and enabled by default for the upcoming 2.52 series.

One of the main changes is that WPEPlatform now gets built into libWPEWebKit. The rationale for this change is avoiding shipping two copies of shared code from the Web Template Framework (WTF), which saves both disk and memory space usage. The wpe-platform-2.0 pkg-config module is still shipped, which allows application developers to know whether WPEPlatform support has been built into WPE WebKit.

The abstract base class WPEScreenSyncObserver has been introduced, and allows platform implementations to notify on display synchronization, allowing WebKit to better pace rendering.

WPEPlatform has gained support for controllers like gamepads and joysticks through the new WPEGamepadManager and WPEGamepad classes. When building with the ENABLE_MANETTE=ON CMake option a built-in implementation based on libmanette is used by default, if a custom one is not specified.

WPEPlatform now includes a new WPEBufferAndroid class, used to represent graphics buffers backed by AHardwareBuffer. These buffers support being imported into an EGLImage using wpe_buffer_import_to_egl_image().

As part of the work to improve Android support, the buffer rendering and release fences have been moved from WPEBufferDMABuf to the base class, WPEBuffer. This is leveraged by WPEBufferAndroid, and should be helpful if more buffer types are introduced in the future.

Other additions include clipboard support, Interaction Media Features, and an accessibility implementation using ATK.

What’s new for WebKit developers?

WebKit now supports sending tracing marks and counters to Sysprof. Marks indicate when certain events occur and their duration; while counters track variables over time. Together, these allow developers to find performance bottlenecks and monitor internal WebKit performance metrics like frame rates, memory usage, and more. This integration enables developers to analyze the performance of applications, including data for WebKit alongside system-level metrics, in a unified view. For more details see this article, which also details how Sysprof was improved to handle the massive amounts of data produced by WebKit.

Finally, GCC 12.2 is now the minimum required version to build WPE WebKit. Increasing the minimum compiler version allows us to remove obsolete code and focus on improving code quality, while taking advantage of new C++ and compiler features.

Looking forward to 2.52

The 2.52 release series will bring even more improvements, and we expect it to be released during the spring of 2026. Until then!

November 27, 2025 12:00 AM

November 24, 2025

Igalia WebKit Team: WebKit Igalia Periodical #48

Igalia WebKit

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

In this week's rendition, the WebView snapshot API was enabled on the WPE port, further progress on the Temporal and Trusted Types implementations, and the release of WebKitGTK and WPE WebKit 2.50.2.

Cross-Port 🐱

A WebKitImage-based implementation of WebView snapshot landed this week, enabling this feature on WPE when it was previously only available in GTK. This means you can now use webkit_web_view_get_snapshot (and webkit_web_view_get_snapshot_finish) to get a WebKitImage-representation of your screenshot.

WebKitImage implements the GLoadableIcon interface (as well as GIcon's), so you can get a PNG-encoded image using g_loadable_icon_load.

Remove incorrect early return in Trusted Types DOM attribute handling to align with spec changes.

JavaScriptCore 🐟

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

In JavaScriptCore's implementation of Temporal, implemented the with method for PlainMonthDay objects.

In JavaScriptCore's implementation of Temporal, implemented the from and equals methods for PlainMonthDay objects.

Releases 📦️

WebKitGTK 2.50.2 and WPE WebKit 2.50.2 have been released.

These stable releases include a number of patches for security issues, and as such a new security advisory, WSA-2025-0008, has been issued (GTK, WPE).

It is recommend to apply an additional patch that fixes building with the JavaScriptCore “CLoop” interpreter is enabled, which is typicall for architectures where JIT compilation is unsupported. Releases after 2.50.2 will include it and manual patching will no longer be needed.

That’s all for this week!

By Igalia WebKit Team at November 24, 2025 08:12 PM

November 21, 2025

Grid: how grid-template-areas offer a visual solution for your code

Surfin’ Safari

Using grid lines is a flexible and powerful way to place your elements on a grid, but, when looking at the code, it might be a bit hard to visualize. So Grid gives you another way to place your elements that might be easier to see called grid areas. There’s a bit more upfront work involved, but the pay off might be worth it.

The idea behind grid areas is that we name each element we want to place on our grid and then use a certain syntax to place them visually where we want them to go. To illustrate, let’s start with my product page.

Here’s the html I’m starting with:

<div class="card">
  <h1>Product 1</h1>
  <h2>$99</h2>
  <p>This is a description of the first option.</p>
  <ul><!-- product details here --></ul>
</div>

<div class="card">
  <h1>Product 2</h1>
  <h2>$99</h2>
  <p>This is a description of the first option.</p>
  <ul><!-- product details here --></ul>
</div>

<div class="card">
  <h1>Add-ons</h1>
  <h2>$149</h2>
  <p>This is another description.</p>
  <ul><!-- product details here --></ul>
</div>

<div class="card">
  <h1>Testimonial</h1>
  <h2>$299</h2>
  <p>This is a third description.</p>
  <ul><!-- product details here --></ul>
</div>

And here’s the CSS I’m starting with:

.pricing-options {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2em;
}
.card {
  border: 1px solid black;
  padding: 2em 5em;
  font-family: sans-serif;
  border-radius: 1em;
}

That will give me this result:

Four cards three on one row and the last on the second left spot.

We want our page to follow this design:

Black and white mockup of two products, add-ons on the right spanning two rows, and testimonial spanning two columns.

This means we want our Add-ons to span two rows and our Testimonial to span two columns. Here’s how we can accomplish that with grid-template-areas.

First, we need to label each element by assigning a value to the grid-area property. To do that, let’s give our elements classes to reference in our CSS.

<div class="card product-1">
  <h1>Product 1</h1>
  <!-- product details here -->
</div>

<div class="card product-2">
  <h1>Product 2</h1>
  <!-- product details here -->
</div>

<div class="card add-ons">
  <h1>Add-ons</h1>
  <!-- product details here -->
</div>

<div class="card testimonial">
  <h1>Testimonial</h1>
  <!-- product details here -->
</div>

Now let’s add those grid-area property values. You can name them anything you want, but since my class names are pretty descriptive, I’m going to name the grid-areas the same as my class names.

.product-1 {
  grid-area: product-1;
}
.product-2 {
  grid-area: product-2;
}
.add-ons {
  grid-area: add-ons;
}
.testimonial {
  grid-area: testimonial;
}

Now comes the visual part. In our CSS, we’re going to go back up to where our grid is defined and we’re going to assign a new property called grid-template-areas. I’m now going to visually assign each cell of my grid to an element in my html.

My grid is three columns across and an unspecified number of rows, which means it’ll be as many rows as needed. I’m going to assign product-1 to that first top cell and product-2 in my second top cell. Then my third top cell is going to be taken up by my add-ons element. And because my add-ons element is going to span two rows, the cell right beneath that is going to be assigned to add-ons too. And finally, we have the bottom left cell assigned to testimonial and because that element is taking up two columns across, the cell right next to it will be assigned to testimonial as well.

Here’s what the final code looks like:

.pricing-options {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2em;
  grid-template-areas:
    "product-1 product-2 add-ons"
    "testimonial testimonial add-ons";
}

The beauty of grid-template-areas is that all of the decisions about where to place what element happen in a single property. You still have to do the upfront work of naming your elements, but once you’ve done that, you can visually see where everything is in relation to each other in a single place. Changing it is simpler too — just move the element name to a different “cell” and you’re done.

Using grid-template-areas , we get our desired layout.

Final layout of site with two products, add-ons on the right spanning two rows and testimonial at the bottom spanning two columns.

If you have any other questions about Grid and would like to see more content on this topic, let us know. 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.

November 21, 2025 01:00 AM

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 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

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

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 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

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