June 23, 2025

Igalia WebKit Team: WebKit Igalia Periodical #27

Igalia WebKit

Update on what happened in WebKit in the week from June 16 to June 23.

This week saw a variety of fixes on multimedia, including GStreamer, a fix for JSC, and the addition of analog gamepad buttons support for WPE.

Cross-Port đŸ±

Multimedia đŸŽ„

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

WebRTC DTMF support was recently implemented in our GstWebRTC backend.

The WebCodecs VideoFrame copyTo() function now correctly handles odd-sized frames.

Multiple MediaRecorder-related improvements landed in main recently (1, 2, 3, 4), and also in GStreamer.

JavaScriptCore 🐟

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

JSC saw some fixes in i31 reference types when using Wasm GC.

WPE WebKit 📟

WPE now has support for analog gamepad buttons when using libwpe. Since version 1.16.2 libwpe has the capability to handle analog gamepad button events, but the support on the WPE side was missing. It has now been added, and will be enabled when the appropriate versions of libwpe are used.

That’s all for this week!

By Igalia WebKit Team at June 23, 2025 07:58 PM

June 20, 2025

A guide to Scroll-driven Animations with just CSS

Surfin’ Safari

CSS animations have come a long way since Apple first introduced them to the web in 2007. What started as simple effects like animating from one color to another has turned into beautiful, complex images twisting and flying across the page.

But linking these animations to user behavior like scrolling has traditionally required third-party libraries and a fair bit of JavaScript, which adds some complexity to your code. But now, we can make those animations scroll-driven with nothing more than a few lines of CSS.

Scroll-driven animations have increased browser support and are available in Safari 26 beta, making it easier for you to create eye-catching effects on your page. Let me show you how.

First, let’s break down the components of a scroll-driven animation.

A scroll-driven animation has three parts:

three tiles reading 'target, keyframes, timeline'.
  • the target: the thing on the page that we’re animating
  • the keyframes: what happens to the element when the user scrolls
  • the timeline: what determines whether the animation proceeds

What’s great about these three parts is that two out of the three are probably already familiar to you.

The first, the target, can be whatever you want to move on your page, styled to your heart’s content.

The second, the keyframes, are the classic CSS animation that’s been around for years. Creating amazing scroll-driven animations largely depends on how great your animation is. If you’re new to CSS animations, check out MDN’s resource.

The third part, the timeline, might be less familiar, but it’s an important part of scroll-driven animation. Let’s explore it in more detail.

What are timelines?

Animations have a beginning, middle, and end, moving sequentially along a timeline. The default timeline on the web is called the document timeline and it’s time-based. That means that as time passes, the timeline progresses as well.

If I have an animation using the default timeline, it animates as time moves forward. If I start with a green circle and I want to animate a color change using the default timeline, then I might make it turn red in the first second, then blue a second later, then yellow on the third. The colors animate with time.

This is the way animations have worked for years. Then, the animation-timeline property was introduced as part of the CSS Animations Level 2 spec in June 2023. That allowed us to think of other things that might impact animation besides the passing of time, like a user scrolling up and down our webpage, and made scroll-driven animations possible.

With scroll-driven animations, we’re no longer using time. Instead, we have two new types of timelines to work with: scroll and view.

scroll() timeline

With scroll timelines, the animation doesn’t progress with time — it progresses based on the user’s scroll.

If the user starts scrolling, the movement begins. The moment the scroll stops, the movement stops too. It’s this new timeline that creates that link between scrolling and animation.

A common way to demonstrate how scroll-driven animations work is by creating a progress bar. In reality, since you already have scroll bars, you don’t need a progress bar like this, but it’s an easy-to-follow example, so let’s go with it.

The first thing to do is create my target, the element that we’re going to animate.

Let’s build that target as part of my website for a coding school, the A-School of Code. If we were to create a progress bar at the bottom of our page, we might add it as a pseudo-element of our footer. We want it to start at the bottom left and progress to the right.

The code might look like this:

footer::after {
  content: "";
  height: 1em;
  width: 100%;
  background: rgba(254, 178, 16, 1);
  left: 0;
  bottom: 0;
  position: fixed;
}

That’ll get us a narrow yellow bar going across my page (highlighted by the pink arrow):

Full progress bar on coding school landing page with pink arrow pointing to bar.

Next, we need our keyframes to create the actual animation.

We’re going to give our keyframe a custom name, let’s say “progress-expand,” like this:

@keyframes progress-expand {
  from { width: 0% }
  to { width: 100% }
}

Third, we need to use our new timeline — **** scroll(). This tells my browser that the animation should only take effect while my user is scrolling, making it a scroll-driven animation.

footer::after {
  ...
  animation-timeline: scroll();
}

And finally, we need to tie together our three components by adding our animation property to our progress bar, like this:

footer::after {
  ...
  animation: progress-expand;
  animation-timeline: scroll();
}

Note: Your animation-timeline property must be set after your animation property, otherwise, this will not work.

And just like that, you have your first scroll-driven animation.

Because we added motion to our page, there’s one thing we need to consider before we ship. Is the movement we just added going to cause any motion discomfort for our users? Is our page accessible?

The subtle, usually slow movement of a progress bar is unlikely to be a motion sensitivity trigger, partly because it’s not taking up much of the viewer’s field of vision. In comparison, larger, wider field-of-vision animations often simulate movement in three-dimensional space, using techniques like parallax, zoom, or other depth-of-field techniques like focal blur. Users with motion sensitivity are more likely to experience these larger animations as real movement in three-dimensional space, and are therefore more likely to experience other negative symptoms like discomfort or dizziness.

When in doubt, it’s a good idea to wrap your animation in a media query that checks for reduced motion preferences, like this:

@media not (prefers-reduced-motion) {
    /* animation here */
}

That way, your animation will only run if the user has not set reduced motion preferences. To learn more about when to use prefers-reduce-motion, read our article, Responsive Design for Motion (https://webkit.org/blog/7551/responsive-design-for-motion/). In this case, I think our animation is safe.

Here are our final results:

Here’s all the code in one place for easy viewing:

footer::after {
  content: "";
  height: 1em;
  width: 100%;
  background: rgba(254, 178, 16, 1);
  inset-inline-start: 0;
  bottom: 0;
  position: fixed;
  transform-origin: top left;
  animation: grow-progress linear;
  animation-timeline: scroll();
}

@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

View() timeline

The timeline we used in the example above, the scroll() timeline, becomes active as soon as we start scrolling, with no regard as to what’s visible to the user or when it shows up in the viewport.

That might be what you want, but more often than not, you want an animation to happen when your target element appears on the page.

Your element could be anything — a carousel, a menu, a gallery of images. But whatever it is, on most websites, the element you want to animate usually isn’t permanently visible on the page. Instead, it shows up as the user explores your site, poking its head into the viewport when you scroll far enough down the page.

So rather than activating the timeline when the user starts scrolling, you want the timeline to activate when the element appears in the viewport, and for that, we need a different timeline for our animation — the view() timeline.

To see how it works, let’s look at a simple example of an image sliding into place when it enters my viewport as we scroll.

I’ll start with a basic article using placeholder text and insert a few images at different parts of the page. Since the images are further down the article, they’re not in the viewport when the page first loads.

Let’s revisit the three things I need for my scroll-driven animation:

  • the target: the thing on the page that we’re animating
  • the keyframes: what happens to the element when the user scrolls
  • the timeline: what determines whether the animation proceeds

My targets are the images in the article, which I have in my HTML. Great! One down, two to go.

Next, I need to set my keyframes. That’s the animation part of scroll-driven animations.

I want two things to happen here — I want the image to fade in and I want it to slide in from the right. I can make that happen with the following code:

@keyframes slideIn {
  0% {
    transform: translateX(100%);
    opacity: 0;
  }
  100% {
    transform: translateX(0%);
    opacity: 1;
  }
}

And, lastly, I need to set my new animation-timeline in my image tag so that it activates when it’s in the viewport.

img {
  animation-timeline: view();
}

Now that I’ve got my three parts, I need to bring them all together by setting the animation property on my image.

Remember, it’s important to set the animation property first. Otherwise, this won’t work.

img {
  animation: slideIn;
  animation-timeline: view();
}

That gets us the nice sliding effect we’re looking for.

But there’s one more thing I want to do for this animation. You’ll notice that the picture doesn’t finish sliding into place until it’s almost out of the viewport. That means that our image is in motion the whole time it’s visible, and that’s not a great experience for our user.

What we really want is for it to slide into place and then stay there for awhile so the user can properly take it in without the distraction of all the movement. We can do that using another property called animation-range .

The animation-range tells our browser when to start and stop the animation along our timeline. The default range is 0% to 100%. The 0% represents the moment when the target element starts to enter our viewport. The 100% represents the moment when the target element completely exits our viewport.

Because we haven’t set our animation-range, we’re using the default values. That means that as soon as the first pixel of my image enters my viewport, my animation begins and it doesn’t end until the last pixel exits.

To make it easier for my users to actually see these images, I want the animation to stop when they’re about halfway through the viewport. At that point, I want the image to find its place and just stay there. To do that, I’m going to change my range to 0% and 50%, like this:

img {
  animation: slideIn;
  animation-timeline: view();
  animation-range: 0% 50%;
}

And this time, the animation stops when the image gets halfway up the page, just like I want.

Do you feel the difference of the change? Does it make it easier to view the images? Do you think a different range would be better? Asking ourselves these questions allow us to better understand what these changes mean for our users, so it’s good to take a moment to reflect.

Another thing to consider before we ship is the impact of this animation on our motion-sensitive users. Just like our previous example, since we’re introducing motion, we have to check if we’re triggering possible motion discomfort.

In this case, I have a bigger animation than my progress bar. The images on my page are pretty big, and if you’re not expecting them to move and you scroll too fast, they can zoom by. That can cause discomfort. Since I want to play it safe, I’m going to put this in a reduced motion query, so my final code will look like this:

img {
  @media not (prefers-reduced-motion) {
    animation: slideIn;
    animation-timeline: view();
    animation-range: 0% 50%;
  } 
}

Now, I can ship.

Next Steps

There’s more you can do with the animation timeline and the animation range. Since scroll() and view() are functions, you can pass in certain values to truly customize how your scroll driven animation works. You can change the default scroller element. That’s the element with scroll bars where the timeline is set. If you pass nothing in, the default value for this is nearest and it’ll find the nearest ancestor with scroll bars. But you can also set it to root and self. The second value you can change is the scrollbar axis. The default there is block but you can also set it to inline, x, and y. Try the different values yourself and see how they work.

There’s more you can do around the entry and exit of your animated elements as well, to get the exact effect you’re looking for. We’ll cover those in a future post. In the meantime, play around with scroll and view timelines in Safari 26 beta and see how you can elevate the user interaction on your website or web app.

And when you do, tell us your thoughts on scroll-driven animations. Send me, Saron Yitbarek, a message on BlueSky, or reach out to our other evangelists — Jen Simmons, on Bluesky / Mastodon, and Jon Davis, on Bluesky / Mastodon. You can also follow WebKit on LinkedIn. If you find a bug or problem, please file a WebKit bug report.

June 20, 2025 06:23 PM

June 16, 2025

Igalia WebKit Team: WebKit Igalia Periodical #26

Igalia WebKit

Update on what happened in WebKit in the week from May 27 to June 16.

After a short hiatus coinciding with this year's edition of the Web Engines Hackfest, this issue covers a mixed bag of new API features, releases, multimedia, and graphics work.

Cross-Port đŸ±

A new WebKitWebView::theme-color property has been added to the public API, along with a corresponding webkit_web_view_get_theme_color() getter. Its value follows that of the theme-color metadata attribute declared by pages loaded in the web view. Although applications may use the theme color in any way they see fit, the expectation is that it will be used to adapt their user interface (as in this example) to complement the Web content being displayed.

Multimedia đŸŽ„

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

The video capture pipeline has gained the ability to optionally rotate the input before encoding.

WebKitGTK đŸ–„ïž

Damage propagation has been toggled for the GTK port: for now only a single rectangle is passed to the UI process, which then is used to let GTK know which part of a WebKitWebView has received changes since the last repaint. This is a first step to get damage tracking code widely tested, with further improvements to be enabled later when considered appropriate.

WPE WebKit 📟

WPE Android ↗ đŸ€–

Adaptation of WPE WebKit targeting the Android operating system.

WPE-Android 0.2.0 has been released. The main change in this version is the update to WPE WebKit 2.48.3, which is the first that can be built for Android out of the box, without needing any additional patching. Thanks to this, we expect that the WPE WebKit version used will receive more frequent updates going forward. The prebuilt packages available at the Maven Central repository have been updated accordingly.

Releases đŸ“Šïž

WebKitGTK 2.49.2 and WPE WebKit 2.49.2 have been released. These are development snapshots and are intended to let those interested test out upcoming features and improvements, and as usual issue reports are welcome in Bugzilla.

Community & Events đŸ€

This year's Web Engines Hackfest had two WebKit-related sessions, and the slides are available already for the WPE-Android talk and the Multimedia in WebKit session. Video recordings will be available later on.

That’s all for this week!

By Igalia WebKit Team at June 16, 2025 09:44 PM

June 11, 2025

Release Notes for Safari Technology Preview 221

Surfin’ Safari

Safari Technology Preview Release 221 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: 295071@main…295660@main

Accessibility

Resolved Issues

  • Fixed aria-expanded attribute support on navigation links. (295356@main) (141163086)

Forms

Resolved Issues

  • Fixed updating scrollbar appearance correctly for the page and <textarea> elements. (295590@main) (151496190)
  • Fixed programmatically assigned File objects to display the correct filename in <input> elements, even without a file path. (295458@main) (152048377)

JavaScript

Resolved Issues

  • Fixed nested negated classes resulting in incorrect matches. (295277@main) (151000852)

Media

Resolved Issues

  • Fixed MP4 seeking with b-frames to prevent out-of-order frame display by suppressing frames with earlier presentation timestamps following the seek point. (295304@main) (140415210)

PDF

Resolved Issues

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

Rendering

Resolved Issues

  • Fixed an issue causing a <canvas> element to disappear for one frame if a view transition occurs. (295467@main) (149709642)

Web API

Resolved Issues

  • Fixed escaping < and > when serializing HTML attribute values. (295149@main) (150520333)

Web Inspector

New Features

  • Added support for recording Workers in the Timelines tab. (295485@main) (151721737)

Resolved Issues

  • Fixed pretty-printing CSS to avoid adding a space after the universal selector (*) when followed by a pseudo-class or pseudo-element, preventing unintended changes to CSS selector behavior. (295447@main) (71544976) (FB8906066)

WebRTC

New Features

  • Added support for exposing a default system speaker device. (295206@main) (151761469)

June 11, 2025 09:26 PM

June 09, 2025

News from WWDC25: WebKit in Safari 26 beta

Surfin’ Safari

Welcome to WWDC25! We’ve got lots of exciting announcements about web technology to share with you this week. Don’t miss our seven sessions, including What’s new in Safari and WebKit.

Today brings the beta of Safari 26, with 67 new features and 107 improvements. We’ll take a tour of them all in this article. But first — Safari 26? Where is Safari 19?

You might have seen today during the WWDC25 Keynote that macOS, iOS, iPadOS, visionOS, and watchOS have all been relabeled to now share the same version number — 26 for 2026. Safari is being renumbered as well.

It was four years ago that we unified the Safari version numbers across platforms, and started marking every update with a X.Y number — including, for example: Safari 16.0, Safari 17.2, and Safari 18.4. This change made all of our releases much more visible to web developers. Both Can I Use and MDN BCD started showing data about the new web technology in all seven releases each year.

While the version number is jumping from 18.x to 26.x, the manner in which point releases of Safari contain significant improvements to the underlying web technology will stay the same. The changes that you care about most as someone who make websites will continue to appear all year around.

So, let’s take a look at the web technology arriving today in Safari 26 beta.

SVG Icons

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

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

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

Data URL images are also now supported for icons as well, allowing you to embed small files in documents.

Every site can be a web app on iOS and iPadOS

iPhone users have been able to put a website’s icon on their Home Screen for quick access since Jan 2008, with “Add to Home Screen” shipping in iPhone OS 1.1.3. Tapping the icon opened the site in Safari.

Eight months later, with iPhone OS 2.1, web developers could start configuring their website with “the standalone mode to look more like a native application” by using the <meta name="apple-mobile-web-app-capable" content="yes"> tag. In 2013, the W3C began the standardization process of Web Application Manifest, making it possible to configure web app behavior with a JSON manifest file. Support for Web Application Manifest started landing in browsers in November 2014, and was added to Safari with iOS 11.4 in March 2018.

For the last 17 years, if the website had the correct meta tag or Web Application Manifest display value, and the user added it to their Home Screen in iOS or iPadOS, tapping the icon opened it as a web app. If the website was not configured as such, tapping the icon opened the site in the browser.

On Mac, we took a different approach when introducing Web Apps on Mac in Sep 2023. There, it doesn’t matter whether or not the website has a Web Application Manifest — it always opens as a web app. We don’t want our users to experience a mysterious difference in behavior because of the presence or absence of invisible technology. Users should have a consistent experience.

Now, we are bringing this new behavior to iOS and iPadOS. By default, every website added to the Home Screen opens as a web app. If the user prefers to add a bookmark that opens in their default browser, they can turn off “Open as Web App”, even if the site is configured to be a web app. It’s up to users to decide. And the UI is always the same.

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

This change, of course, is not removing any of WebKit’s current support for web app features! If the site you built has a Web Application Manifest, then all of the benefits it provides will be part of the user’s experience. If you define your icons in the manifest, they’re used! If you want to provide an offline experience by using a Service Worker, great!

We value the principles of progressive enhancement and separation of concerns. All of the same web technology is available to you as a developer, to build the experience you would like to build. Just now, nothing is required beyond the basics of an HTML file and a URL to provide a web app experience to users. As a developer, you get to layer on whatever you want with CSS, JS, Web API, and more. And users get to add any site to their Home Screen, and open it as a web app.

HDR Images

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

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

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

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

WebKit in SwiftUI

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

A slide from the WWDC25 session on WebKit in SwiftUI with a collection of concepts from the session — Observable properties, Handle custom schemes, Designed for Swift UI, Customized navigations, Back / forward / list, HTML on a webpage, WebView, Custom dialogs, JavaScript


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

WebView

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

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

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

WebPage

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

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

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

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

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

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

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

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

<model> on visionOS

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

Basic usage

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

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

Lighting

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

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

Animation and playback

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

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

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

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

Rotation and interaction

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

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

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

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

With this JavaScript:

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

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

Immersive video and audio on visionOS

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

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

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

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

WebGPU

WebKit for Safari 26 beta adds support for WebGPU.

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

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

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

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

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

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

CSS

Anchor Positioning

Anchor positioning is a new layout mechanism for anchoring one element to another on the web. It pairs well with the popover attribute (which shipped in Safari 17.0), making it easy to create responsive menus, tooltips and more. We are especially proud of the position-area syntax that makes using Anchor Positioning more intuitive. It’s an alternative to using syntax like this when the desired outcome is actually quite simple:

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

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

Above, we use the anchor() function in combination with top, left, bottom, right from absolute positioning. That combo can be powerful, and works well when your design calls for exact-to-the-pixel layout, anchoring to multiple anchors, or animated anchors. But often, you just want to tell the browser where to put an item, and have it figure out the details. Instead of the above, you can use code like:

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

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

The position-area syntax came from a proposal we put together, as we thought about how developers would use Anchor Positioning, and how overwhelming much of CSS layout can be.

Code using Anchor Positioning with the results. Watch our WWDC25 session for a walkthrough of this example.
Learn more about Anchor Positioning, and watch a full walkthrough of
this example in What’s new in Safari and WebKit at WWDC25.

You can also use position-try to make it responsive, giving it a new position to try (hence the name) if there’s not enough room to fully display the element.

Currently, this first beta of Safari 26 does not support position-visibility or the implicit anchor element. We’d love to hear from you as we continue to polish Anchor Positioning through this summer beta period, preparing for a fall release. You can file issues at bugs.webkit.org.

Scroll-driven Animations

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

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

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

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

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

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

Pretty text

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

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

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

Contrast Color

Safari 26 beta adds support for the contrast-color() function. It lets you define a color by referencing another color and asking the browser to choose either black or white — whichever one provides more contrast.

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

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

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

Progress function

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

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

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

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

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

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

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

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

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

And more CSS

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

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

Safari 26 beta adds support for overflow-block and overflow-inline. These are the logical versions of overflow-x and overflow-y, making it easier to write robust code that supports multiple languages.

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

Digital Credentials API

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

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

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

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

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

And then in JavaScript:

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

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

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

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

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

To learn more about this transformative technology watch Verify identity documents on the web at WWDC25. And keep an eye on Issue 268516 for more about support of the Digital Credentials API in WKWebView.

Web API

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

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

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

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

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

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

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

JavaScript

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

Several features from the Explicit Resource Management Proposal are also now supported:

  • the @@dispose and @@asyncDispose methods for performing an explicit resource cleanup on an object
  • the SuppressedError constructor
  • the DisposableStack constructor

Editing

For editing, Safari 26 beta adds support for rendering native selection UI inside scrolled content.

SVG

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

Media

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

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

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

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

WebRTC

WebKit brings multiple updates for WebRTC, adding support for:

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

Web Inspector

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

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

Screenshot of the Apps and Devices Inspection window

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

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

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

Screenshot of Web Inspector Elements tab.

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

Web Extensions

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

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

WebKit API

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

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

WebAssembly

As WebAssembly continues to grow in popularity, WebKit has been improving WebAssembly performance across the board. Now, WebAssembly is first evaluated by the in-place interpreter, which allows large WebAssembly code to run even faster.

Privacy

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

Networking

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

Lockdown Mode

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

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

Website Compatibility

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

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

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

Bug Fixes and more

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

Accessibility

  • Fixed aria-expanded attribute support on navigation links. (141163086)
  • Fixed presentational images with empty alt attributes to be ignored by assistive technology, even when additional labeling attributes are set. (146429365)
  • Fixed <figcaption> within a <figure> element to only contribute to the accessible name of an <img> element if the image lacks other labeling methods like alt, ARIA attributes, or the title attribute. (150597445)
  • Fixed invalid values for aria-setsize and aria-posinset are now handled according to the updated ARIA specification, to ensure correct accessibility API exposure and predictable behavior for these attributes. (151113693)

CSS

  • Fixed cursor: pointer not appearing on an <area> element used in conjunction with an <img usemap="..."> element. (74483873)
  • Fixed grid sizing with inline-size containment and auto-fit columns is incorrectly sized. (108897961)
  • Fixed content skipped with content-visibility: auto to be findable. (141237620)
  • Fixed an issue wrapping an SVG at the end of a line when using text-wrap: balance. (141532036)
  • Fixed @font-face font-family descriptor to not allow a list of values. (142009630)
  • Fixed the computed value of a float with absolute positioning to be none when there is no box. (144045558)
  • Fixed buttons to not have align-items: flex-start by default. (146615626)
  • Fixed @scope to create a style nested context. (148101373)
  • Fixed changing content-visibility from visible to hidden to repaint correctly. (148273903)
  • Fixed an issue where float boxes, selections, and carets were incorrectly painted inside skipped subtrees.(148741142)
  • Fixed incorrect getBoundingClientRect() inside skipped subtree on an out-of-flow positioned box. (148770252)
  • Fixed making <pre> and other elements use logical margins in the User-Agent stylesheet. (149212392)

Canvas

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

DOM

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

Editing

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

Forms

  • Fixed form associated ElementInternals always reporting a customError when using setValidity. (115681066)
  • Fixed setValidity of ElementInternals to handle missing optional anchor parameter. (123744294)
  • Fixed programmatically assigned File objects to display the correct filename in <input> elements, even without a file path. (152048377)
  • Fixed labels inside <select> elements to behave consistently with other browsers by using standard attribute matching instead of quirk mode handling. (152151133)

JavaScript

  • Fixed Array.prototype.pop to throw an exception when the array is frozen. (141805240)
  • Fixed performance of Math.hypot() that was significantly slower than Math.sqrt(). (141821484)
  • Fixed Array#indexOf and Array#includes to treat +0 and -0 as the same value. (148472519)
  • Fixed iterator helpers incorrectly closing iterators on early errors. (148774612)
  • Fixed Iterator.prototype.reduce failing with an undefined initial parameter. (149470140)
  • Fixed f() = 1 behavior with other engines when not using strict mode. (149831750)
  • Fixed nested negated classes resulting in incorrect matches. (151000852)

Media

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

Rendering

  • Fixed an issue to allow images in scroll containers to load when they are near the viewport rather than when they are intersecting the viewport. (118706766)
  • Fixed a disappearing stretched image in a vertical flexbox layout. (135897530)
  • Fixed CSS gradient interpolation into regions beyond the first or last color stop. (142738948)
  • Fixed integrating position-area and self-alignment properties for align-self and justify-self (145889235)
  • Fixed will-change: view-transition-name to create a stacking context and a backdrop root. (146281670)
  • Fixed will-change: offset-path to create a stacking context and a containing block. (146292698)
  • Fixed <datalist> dropdowns not displaying option labels. (146921617)
  • Fixed the text indicator sometimes getting clipped during a bounce animation. (147602900)
  • Fixed geometry values inside content-visibility: hidden subtrees. (148553259)
  • Fixed not marking content-visibility: hidden content for layout when targeting content-visibility: auto. (148663896)
  • Fixed incorrect ruby annotation positioning in sideways-lr. (148713073)
  • Fixed: Prevented hit testing content inside a skipped subtree. (148741508)
  • Fixed an issue where feMerge incorrectly positioned HTML elements when merging the same feMergeNode multiple times. (149431216)
  • Fixed an issue in determining when a flex item should be used for percentage resolution during intrinsic width computation. (149615295)
  • Fixed an issue causing a <canvas> element to disappear for one frame if a view transition occurs. (149709642)
  • Fixed invisible <audio> controls when transformed due to incorrect coordinate space calculations for clipped child elements. (150526971)
  • Fixed centering text for <input type=button> elements with display: flex. (151148821)
  • Fixed showing a resize cursor even when text overlaps the resize control. (151309503)

SVG

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

Safari View Controller

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

Scrolling

  • Fixed selection does not update during autoscroll when selecting with a gesture or a mouse. (144744443)
  • Fixed autoscrolling for smooth scrolling while selecting text. (144900491)

Service Workers

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

Tables

  • Fixed table layout to only be triggered when inline-size is not auto. (147636653)

Text

  • Fixed generating scroll to text fragments around text that contains newlines. (137109344)
  • Fixed generating text fragments when the selected text starts and ends in different blocks. (137761701)
  • Fixed bold synthesis to be less aggressive. (138047199)
  • Fixed Copy Link to Highlight not working when selecting text that is its own block and when that text exists higher up in the document. (144392379)
  • Fixed selections that start or end in white space not creating text fragments. (145614181)
  • Fixed <b> and <strong> to use font-weight: bolder to match the Web Specification. (146458131)

URLs

  • Fixed making URL host and hostname setters handle @ correctly. (146886347)

Web API

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

Web Animations

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

Web Extensions

  • Fixed a declarativeNetRequest bug that prevents redirects to extension resources. (145569361)
  • Fixed converting declarativeNetRequest rules so that higher numbers are treated as higher priority. (145570245)
  • Fixed an issue causing wasm-unsafe-eval to not get parsed as a valid CSP keyword. (147551225)
  • Fixed permissions.getAll() to return the correct origins if all urls and/or hosts match pattern(s) have been granted. (147872012)

Web Inspector

  • Fixed pretty-printing CSS to avoid adding a space after the universal selector () when followed by a pseudo-class or pseudo-element, preventing unintended changes to CSS selector behavior. (71544976) Fixed to show a separate overview for each target in the Timelines tab. (146356054)
  • Fixed a performance issue when blackboxing a large number of sourcemaps. (148116377)
  • Fixed the debugger to step over an await statement as though it is synchronous code. (149133320)
  • Fixed parsing sourcemaps asynchronously so that large sourcemaps do not block rendering. (151269154)
  • Fixed the Timelines tab to consistently display the target’s hierarchical path for JavaScript and Events to prevent confusion when working with multiple targets. (152357197)

WebRTC

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

Try out Safari 26 beta

You can test Safari 26 beta by installing the beta of macOS Tahoe 26, iOS 26, iPadOS 26 or visionOS 26. Or if you’d like, try out Safari 26 beta on macOS Sequoia or macOS Sonoma by downloading Safari 26 beta, once it’s available. (Sign in using a free Apple ID to download. Installing Safari 26 beta on macOS Sequoia or macOS Sonoma will replace your existing version of Safari with no way to revert to an earlier version.)

You can also help test many of these features in Safari Technology Preview on macOS.

Feedback

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

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

June 09, 2025 06:30 PM

Web technology videos at WWDC25

Surfin’ Safari

It’s time for WWDC25! Today brings announcements of the new web technology coming to Safari on iOS, iPadOS, macOS and in visionOS. Read our in-depth article, News from WWDC25: Web technology coming this fall in Safari for more.

A big part of WWDC is the sessions! Our videos about web technology include:

Saron Yitbarek presenting What's New in Safari and WebKit at WWDC25

What’s new in Safari and WebKit

Learn how the latest web technologies in Safari and WebKit can help you create incredible experiences. We’ll highlight different CSS features and how they work, including scroll driven animation, cross document view transitions, and anchor positioning. We’ll also explore new media support across audio, video, images, and icons.


Eddy Wong presenting What's new for the spatial web at WWDC25

What’s new for the spatial web

Discover the latest spatial features for the web on visionOS. We’ll cover how to display inline 3D models with the brand new HTML model element. And we’ll share powerful features, including model lighting, interactions, and animations. Learn how to embed newly supported immersive media on your web site, such as 360-degree video and Apple Immersive Video. And get a sneak peek at adding a custom environment to your web pages.


Brady Eidson presenting Learn more about Declarative Web Push at WWDC25

Learn more about Declarative Web Push

Learn how Declarative Web Push can help you deliver notifications more reliably. Find out how to build on existing standards to be more efficient and transparent by design while retaining backwards compatibility with original Web Push.


Hands holding an iPad with the image of a dog from the WWDC25 session What's new in Passkeys

What’s new in passkeys

Discover how iOS, iPadOS, macOS, and visionOS enhance passkeys. We’ll explore key updates including: the new account creation API for streamlined sign-up, keeping passkeys up-to-date, new ways to drive passkey upgrades through automatic passkey upgrades and passkey management endpoints, and the secure import/export of passkeys. Learn how these improvements enhance user experience and security, and how to implement these updates in your apps to provide a smoother, more secure authentication experience. To get the most out of this video, first watch Meet passkeys from WWDC22.


Marcos CĂĄceres & Erik Melone presenting Verify identity documents on the web at WWDC25

Verify identity documents on the web

Learn how Digital Credentials can enhance online identity verification flows. We’ll cover how websites can integrate the Digital Credentials API to enable requesting information from IDs in Wallet. We’ll also explore how apps can provide their own identity documents for online verification using the new IdentityDocumentServices framework.


WebGPU logo from Unlock GPU computing with WebGPU at WWDC25

Unlock GPU computing with WebGPU

Learn how the WebGPU API provides safe access to GPU devices for graphics and general-purpose computation. We’ll also explore the WGSL shading language to write GPU programs. And we’ll dive into best practices to achieve optimal performance while using minimal power across desktop and mobile devices.


Features of WebKit APIs for SwiftUI: back forward list, observable properties, WebPage, WebView, custom scheme handling, customized navigation handling, custom dialogs, and JavaScript support

Meet WebKit for SwiftUI

Discover how you can use WebKit to effortlessly integrate web content into your SwiftUI apps. Learn how to load and display web content, communicate with webpages, and more.

You can also watch all of these sessions, and much more about web technology, in the Apple Developer Safari & Web playlist on YouTube.

June 09, 2025 09:00 AM

June 02, 2025

Shop Talk Show episode 667

Surfin’ Safari

Check out this week’s episode of Shop Talk Show where we appeared to talk about Declarative Web Push, the future of form control styling, color contrast algorithms, accessibility standards, enhancements in color picker functionality, typography improvements and more.

Listen on the web: shoptalkshow.com/667

Watch on video:

Or of course, find it in Apple Podcasts or your podcast player of choice.

June 02, 2025 09:23 PM

May 30, 2025

Igalia WebKit Team: WebKit at the Web Engines Hackfest 2025

Igalia WebKit

The Web Engines Hackfest 2025 is kicking off next Monday in A Coruña and among all the interesting talks and sessions about different engines, there are a few that can be interesting to people involved one way or another with WebKitGTK and WPE:

All talks will be live streamed and a Jitsi Meet link will be available for those interested in participating remotely. You can find all the details at webengineshackfest.org.

By Igalia WebKit Team at May 30, 2025 04:21 PM

May 28, 2025

Release Notes for Safari Technology Preview 220

Surfin’ Safari

Safari Technology Preview Release 220 is now available for download for macOS Sequoia and macOS Sonoma. 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: 294488@main…295070@main

CSS

New Features

  • Added support for field-sizing to override the default sizing behavior of form elements and adjust size to fit their contents. (295370@main) (151949208)

Resolved Issues

  • Fixed grid sizing with inline-size containment and auto-fit columns is incorrectly sized. (295023@main) (108897961)
  • Fixed a bug where an anchor-positioned element does not properly render when it’s initially hidden from view, then shown. (294924@main) (148608785)

DOM

Resolved Issues

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

JavaScript

New Features

  • Added support for @@dispose and @@asyncDispose from the Explicit Resource Management Proposal behind runtime flag. (294592@main) (150816114)
  • Added support for SuppressedError from the Explicit Resource Management Proposal behind runtime flag. (294774@main) (151132111)
  • Added support for DisposableStack constructor from the Explicit Resource Management Proposal behind runtime flag. (294880@main) (151280591)

Media

Resolved Issues

  • Fixed MediaSession to handle SVG icons with subresources. (294499@main) (150665852)
  • Fixed MediaCapabilitiesDecodingInfo.configuration to be correctly populated even when .supported is false. (294957@main) (150680756)

Rendering

Resolved Issues

  • Fixed invisible <audio> controls when transformed due to incorrect coordinate space calculations for clipped child elements. (294694@main) (150526971)
  • Fixed centering text for <input type=button> elements with display: flex. (294785@main) (151148821)
  • Fixed showing a resize cursor even when text overlaps the resize control. (294907@main) (151309503)

Web Animations

Resolved Issues

  • Fixed CSS scroll-driven animations on pages using requestAnimationFrame to animate correctly after navigating away and back to the page. (294889@main) (141528296)

Web API

Resolved Issues

  • Fixed making the SpeechRecognition interface available only within a secure context. (294887@main) (151240414)

Web Inspector

Resolved Issues

  • Fixed parsing sourcemaps asynchronously so that large sourcemaps do not block rendering. (294875@main) (151269154)

WebRTC

Resolved Issues

  • Fixed the configurationchange event to fire when a microphone’s audio unit changes its echo cancellation mode, ensuring web pages are notified of such changes to update track settings accordingly. (294839@main) (150770940)

May 28, 2025 09:34 PM

May 26, 2025

Igalia WebKit Team: WebKit Igalia Periodical #25

Igalia WebKit

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

This week saw updates on the Android version of WPE, the introduction of a new mechanism to support memory-mappable buffers which can lead to better performance, a new gamepad API to WPE, and other improvements.

Cross-Port đŸ±

Implemented support for the new 'request-close' command for dialog elements.

JavaScriptCore 🐟

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

Added support for using the GDB JIT API when dynamically generating code in JSC.

Graphics đŸ–Œïž

Added support for memory-mappable GPU buffers. This mechanism allows to allocate linear textures that can be used from OpenGL, and memory-mapped into CPU-accessible memory. This allows to update the pixel data directly, bypassing the usual glCopyTexSubImage2D logic that may introduce implicit synchronization / perform staging copies / etc. (driver-dependant).

WPE WebKit 📟

WPE Platform API đŸ§©

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

Landed a patch to add a gamepads API to WPE Platform with an optional default implementation using libmanette.

WPE Android ↗ đŸ€–

Adaptation of WPE WebKit targeting the Android operating system.

WPE-Android has been updated to use WebKit 2.48.2. Updated packages will be available in the Central repository in the coming days.

The WPE-Android MiniBrowser no longer crashes when opening the “Settings” activity when the system-wide dark user interface mode is enabled.

That’s all for this week!

By Igalia WebKit Team at May 26, 2025 07:38 PM

May 22, 2025

WPE WebKit Blog: Success Story: Savant

Igalia WebKit

Savant logo WPE

Igalia worked with Savant Systems to bring a seamless, high-performance music experience to its smart home ecosystem. By enhancing WPE WebKit with critical backported patches, developing a custom Widevine CDM, and engineering a JavaScript D-Bus bridge, WPE WebKit was adapted to ensure robust and secure media playback directly within Savant’s platform.

Delivering a tightly integrated music experience in a smart home environment required overcoming significant technical challenges. To achieve this, WPE WebKit’s capabilities were streamlined to enable a fluid interface and reliable communication between the browser and the music process that powers a third-party music integration.

With deep expertise in browser technology and embedded systems, Igalia was able to help Savant implement a tailored WPE WebKit integration, optimizing performance while maintaining security and responsiveness. The result is a cutting-edge solution that enhances user experience and supports Savant’s commitment to innovation in smart home entertainment.

May 22, 2025 12:00 AM

May 21, 2025

Two lines of Cross-Document View Transitions code you can use on every website today

Surfin’ Safari

Arguably, the most profound thing about the web is the ability to link one page to another. Click or tap a link, and another webpage opens in the browser window. Click again, and there’s another web page. In 2025, this is so completely obvious, it feels like nothing. But back in the 1980s when the web was conceived, the idea of navigating from one page to another through a link was revolutionary.

If you surfed the early web, you probably remember how very long it would take for a page to load. You were on a webpage, then you’d click a navigation link, and wait
 wait
 wait
 The first page disappeared completely, leaving you starting at a blank white browser window (originally gray). Then slowly the next page started to load, from top to bottom. Bit by bit.

Over the years, internet connections sped up, and page loading got faster and faster. But for a long time you could still see a flash of white between pages. First page. Blank white flash. Next page. Even if a large portion of the second page was the same as the first — the same header, the same sidebar, same layout — it was clear that everything was being dumped and repainted.

Now days, the flash of white (or off-black in dark mode) is rarely seen. Sometimes headers and sidebars will repaint, but often, they do persist across the transition. Yet, earlier times still shape the way many people experience the web.

But now, you can add two lines of code to your website and fundamentally change the behavior of what happens when people navigate from one page to another on your site.

@view-transition {
  navigation: auto;
}

This activates cross-document View Transitions. It ensures any element that’s the same from one page to the next stays exactly in place. It keeps the background color of the site constant. And then anything that’s different — new elements appearing, old elements disappearing, page background changing to a different color — by default, all of these changes happen in a crossfade.

Yes, a crossfade, like a dissolve. The website fades the changes in content between the old page and the new page. Here’s what it looks like.

Notice how the header is present all of the time. It never blinks or moves. And notice how the image and other main content fades in and fades out as we switch from one web page to another. In some ways, it doesn’t feel like we’ve navigated to another web page at all.

Some people think of this as a way to make their website feel “more app like”. Others will simply marvel about how different the web in 2025 is from the web in 1995. And how just two lines of CSS fundamentally changes the behavior of what happens when people navigate from one page to another on your site. The crossfade replaces the cut to blankness and cut to new content that happens without it.

Anytime you are introducing animation to your web page, you should consider whether the animation could be problematic for users with motion sensitivity. Use a prefers-reduced-motion media query to determine whether to modify or stop motion triggers like parallax effects, dimensionality, or depth simulations like zooming/scaling.

But in this case, the dissolve animation doesn’t introduce motion. Simple crossfades are not known to cause adverse effects in those with motion sensitivity. Of course, you should make your own assessment about which motion-driven animations should be reduced — but simply removing 100% of animation on the web in the name of accessibility does not yield truly helpful results. Think through the type of motion you are introducing, and what the overall experience is like using our devices. It’s certain kinds of motion, or degrees of motion that start to cause problems for some users, not simply the presence of any animation. Learn much more about when to use prefers-reduce-motion by reading our article, Responsive Design for Motion.

What about browser support? Cross-document View Transitions are supported in Safari 18.2, Chrome 126, and Edge 126. Which means about 85% of users globally are using a browser with support. You can use View Transitions for this purpose with absolute confidence today, since the fallback behavior is to simply do nothing. Browsers without support act exactly the same as if you did not use this code. So why not use it? It will do something for the majority of your users, while the rest see no change at all.

You can do much more with View Transitions. It’s a very powerful and complex API. But doing more will take more than two lines of code. And more than 800 words to explain. So, for this coffee-break/snack-sized article, we’ll stop here.

Oh, do you want to know more? Well
 you can switch from a simple dissolve to something else, like a slide animation. Or you can use same-document View Transitions to easily animate something from one place on the page to another. Dive into the power of View Transitions reading MDN Web Docs.

May 21, 2025 02:00 PM

May 19, 2025

Igalia WebKit Team: WebKit Igalia Periodical #24

Igalia WebKit

Update on what happened in WebKit in the week from May 12 to May 19.

This week focused on infrastructure improvements, new releases that include security fixes, and featured external projects that use 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.

Fixed a reference cycle in the mediastreamsrc element, which prevented its disposal.

JavaScriptCore 🐟

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

Added an internal class that will be used to represent Temporal Duration objects in a way that allows for more precise calculations. This is not a user-visible change, but will enable future PRs to advance Temporal support in JSC towards completion.

WPE WebKit 📟

WPE Platform API đŸ§©

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

Added an initial demo application to the GTK4 WPEPlatform implementation.

Screenshot of a Web browser application using a WPEPlatform backend based on GTK4

Releases đŸ“Šïž

WebKitGTK 2.48.2 and WPE WebKit 2.48.2 have been released. These are paired with a security advisory (WSA-2025-0004: GTK, WPE), and therefore it is advised to update.

On top of security fixes, these release also include correctness fixes, and support for CSS Overscroll Behaviour is now enabled by default.

Community & Events đŸ€

GNOME Web has gained a preferences page that allows toggling WebKit features at run-time. Tech Preview builds of the browser will show the settings page by default, while in regular releases it is hidden and may be enabled with the following command:

gsettings set org.gnome.Epiphany.ui webkit-features-page true

This should allow frontend developers to test upcoming features more easily. Note that the settings for WebKit features are not persistent, and they will be reset to their default state on every launch.

Features page in the GNOME Web preferences dialog Features page in the GNOME Web preferences dialog

Infrastructure đŸ—ïž

Landed an improvement to error reporting in the script within WebKit that runs test262 JavaScript tests.

The WebKit Test Runner (WKTR) will no longer crash if invalid UTF-8 sequences are written to the standard error stream, (e.g. from 3rd party libraries' debugging options.

Experimentation is ongoing to un-inline String::find(), which saves ~50 KiB in the resulting binary size worth of repeated implementations of SIMD “find character in UTF-16” and “find character in UTF-32” algorithms. Notably, the algorithm for “find character in ASCII string” was not even part of the inlining.

Added the LLVM repository to the WebKit container SDK. Now it is possible to easily install Clang 20.x with wkdev-setup-default-clang --version=20.

Figured out that a performance bug related to jump threading optimization in Clang 18 resulted in a bottleneck adding up to five minutes of build time in the container SDK. This may be fixed by updating to Clang 20.x.

That’s all for this week!

By Igalia WebKit Team at May 19, 2025 09:10 PM

May 14, 2025

Release Notes for Safari Technology Preview 219

Surfin’ Safari

Safari Technology Preview Release 219 is now available for download for macOS Sequoia and macOS Sonoma. 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: 293899@main…294487@main.

Canvas

Resolved Issues

  • Fixed re-drawing a canvas with relative width when the parent element is resized. (294401@main) (121996660)

CSS

New Features

  • Added support for the margin-trim: block inline syntax for trimming in both directions. (294190@main) (141784069)

JavaScript

Resolved Issues

  • Fixed Array.prototype.pop to throw an exception when the array is frozen. (293907@main) (141805240)
  • Fixed f() = 1 behavior with other engines when not using strict mode. (293996@main) (149831750)

Media

Resolved Issues

  • Fixed SVG images with sub-resources to appear correctly when used in MediaSession. (294334@main) (148089535)

Rendering

Resolved Issues

  • Fixed an issue where feMerge incorrectly positioned HTML elements when merging the same feMergeNode multiple times. (294088@main) (149431216)
  • Fixed an issue in determining when a flex item should be used for percentage resolution during intrinsic width computation. (293901@main) (149615295)

Web API

Resolved Issues

  • Fixed an overly broad fullscreen exit trigger by restricting it to only text-entry elements gaining focus, preventing non-text input types from causing unexpected fullscreen exits. (294284@main) (136726993)

Web Extensions

Resolved Issues

  • Fixed 'allowAllRequests' for declarativeNetRequest. (294041@main) (72203692)
  • Fixed a non-fatal webRequest error for non-persistent background content. (294142@main) (150051544)

WebRTC

New Features

  • Added support for RTCEncodedAudioFrame and RTCEncodedVideoFrame constructors. (293951@main) (149541424)

May 14, 2025 09:37 PM

May 13, 2025

How to have the browser pick a contrasting color in CSS

Surfin’ Safari

Have you ever wished you could write simple CSS to declare a color, and then have the browser figure out whether black or white should be paired with that color? Well, now you can, with contrast-color(). Here’s how it works.

Imagine we’re building a website or a web app, and the design calls for a bunch of buttons with different background colors. We can create a variable named --button-color to handle the background color. And then assign that variable different values from our design system in different situations.

Sometimes the button background will be a dark color, and the button text should be white to provide contrast. Other times, the background will be a lighter color, and the text should be black. Like this:

Two buttons side by side. White text on dark purple for the first, black text on pink background for the second.

Now, of course, we could use a second variable for the text color and carefully define the values for --button-color and --button-text-color at the same time, in pairs, to ensure the choice for the text color is the right one. But, on a large project, with a large team, carefully managing such details can become a really hard task to get right. Suddenly a dark button has unreadable black text, and users can’t figure out what to do.

It’d be easier if we could just tell our CSS to make the text black/white, and have the browser pick which to use — whichever one provides more contrast with a specific color. Then we could just manage our many background colors, and not worry about the text color.

That’s exactly what the contrast-color() function will let us do.

contrast-color()

We can write this in our CSS:

color: contrast-color(purple);

And the browser will set color to either black or white, whichever choice provides better contrast with purple.

Let’s style our button. We’ll set the button background color to our variable. And we’ll define the text color to be the contrasting black/white choice that pairs with that variable.

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

Now we only need to define one color, and the other follows! When we change the button color, the browser will reconsider whether the text should be black or white, and choose fresh the option with more contrast.

For fun, let’s also define a hover color using Relative Color Syntax, and now one variable determines four colors — the default button color & the text to go with it, plus the hover color & the text to go with that.

:root {
  --button-color: purple;
  --hover-color: oklch(from var(--button-color) calc(l + .2) c h);
}
button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  text-box: cap alphabetic; /* vertically centers the text */
}
button:hover {
  background-color: var(--hover-color);
  color: contrast-color(var(--hover-color));
}

Here’s a demo of the result. Try it in Safari Technology Preview, where you can change the button color dynamically.

Accessibility considerations and contrast algorithms

Now, it might be tempting to believe that contrast-color() will magically solve all contrast accessibility concerns all by itself, and your team will never have to think about color contrast again. Nope, that’s not the case. At all.

Using the contrast-color() function does not guarantee that the resulting pair of colors will be accessible. It’s quite possible to pick a color (in this case a background color) that will not have enough contrast with either black or white. It’s still up to the humans involved — designers, developers, testers, and more — to ensure there’s enough contrast.

In fact, if you try out our demo in Safari Technology Preview now (as this article is published in May 2025), you’ll find many of the pairings with mid-tone background colors don’t result in enough contrast. It often seems like the wrong choice is being made. For example, this #317CFF blue returns a contrast-color of black.

Medium dark blue button with black text. The text is hard to see.

When white is clearly the better choice for perceptual contrast.

Same dark medium blue button, now with white text. Much easier to see what it says.

What is happening here? Why is the less-contrasting choice being made?

Well, the current implementation in Safari Technology Preview is using the contrast algorithm officially defined in WCAG 2 (Web Content Accessibility Guidelines version 2). If we put this color blue through a well-respected color contrast checker at WebAIM, it does clearly recommend using black for the text color, not white. WCAG 2 is the current authoritative standard for accessibility on the web, required by law in many places.

The WCAG 2 algorithm calculates black-on-#317CFF as having a contrast ratio of 5.45:1, while white-on-#317CFF has 3.84:1. The contrast-color() function is simply choosing the option with the bigger number — and 5.45 is bigger than 3.84.

Screenshots of the WCAG 2 color contrast checker, showing results of white on blue and black on blue. Black passes. White fails. But black is hard to read while white is easy to read.
Testing black versus white on a medium-dark blue in the WCAG 2 color contrast checker at Web AIM.

When machines run the WCAG 2 algorithm, the black text has higher contrast mathematically. But when humans look at these combinations, the black text has lower contrast perceptually. If you find this odd, well, you aren’t the only one. The WCAG 2 color contrast algorithm has long been a subject of criticism. In fact, one of the major driving forces for updating WCAG to level 3 is a desire to improve the contrast algorithm.

The Accessible Perceptual Contrast Algorithm (APCA) is one possible candidate for inclusion in WCAG 3. You can try out this algorithm today by using the APCA Contrast Calculator at apcacontrast.com. Let’s look at what it thinks about black vs white text on this particular shade of blue background.

Screenshot of APCA Contrast Calculator, showing the same tests of black on blue vs white on blue. White clearly wins.
Testing the same black versus white on a medium-dark blue in the APCA Contrast Calculator.

This contrast algorithm evaluates black-on-blue as having a score of Lc 38.7, while white-on-blue scores Lc -70.9. To know which has more contrast, ignore the negative sign for a moment, and compare 38.7 to 70.9. The bigger the number, the more contrast. The APCA test results say that white text is clearly better than black. Which feels exactly right.

(In the APCA scoring system, the negative number simply signifies that the text is lighter than the background. Think light mode = positive numbers, dark mode = negative numbers.)

Why is APCA giving such better results than WCAG 2? Because its algorithm calculates contrast perceptually instead of with simple mathematics. This takes into consideration the fact humans do not perceive contrast linearly across hue and lightness. If you’ve learned about LCH vs HSL color models, you’ve probably heard about how newer approaches to color mathematics do a better job of understanding our perception of lightness, and knowing which colors seem to be the same luminance or tone. The “Lc” marking the APCA score stands for “Lightness contrast”, as in “Lc 75”.

Luckily, the algorithm behind the contrast-color function can be swapped out. Support for this feature first shipped in March 2021, in Safari Technology Preview 122. (Also, at that time it was named color-contrast.) Back then, it was too early to choose a better algorithm.

The CSS standard still calls for browsers to use the older algorithm, but contains a note about the future: “Currently only WCAG 2.1 is supported, however this algorithm is known to have problems, particularly on dark backgrounds. Future revisions of this module will likely introduce additional contrast algorithms.” Debates over which algorithm is best for WCAG 3 are still ongoing, including discussion of licensing of the algorithms under consideration.

Meanwhile, your team should still take great care in choosing color palettes, keeping accessibility in mind. If you are choosing clearly-light or clearly-dark colors for the contrasting color, contrast-color() will work great even when backed by the WCAG 2 algorithm. It’s in evaluating contrast with mid-tones where the algorithms start to differ in their results.

Plus, the contrast-color() function alone will never guarantee accessibility, even when updated with a better algorithm. “This one has more contrast” is not the same thing as “this one has enough contrast”. There are plenty of colors that never have enough contrast with either black or white, especially at smaller text sizes or thinner font weights.

Providing enough contrast in the real world

While thinking about color contrast, we should remember another tool in our arsenal to ensure we provide good contrast for everyone — theprefers-contrast media query. It lets us offer alternative styling to those who want more contrast.

@media (prefers-contrast: more) {
  /* styling with more contrast */
}

Let’s think through how to use these tools in a real world situation. Imagine we are creating a website for a tree nursery whose main brand color is a particular shade of bright medium green. Our design team really wants to use #2DAD4E as the main button background.

To keep things simple, let’s also pretend we live in a future when the APCA algorithm has replaced the WCAG 2 algorithm in CSS. This change will mean contrast-color() will return white for our text color against this medium green, not black.

But looking up this color combination, we see there might not be enough contrast for some users, especially if the text is small. This is where good design is important.

Testing white on medium green in the APCA contrast calculator. The interface has lots of options for adjusting the colors. And it's got a panel across the bottom with six sections of examples of white text on this color green, in various sizes and weights of fonts.

When using this shade of green as the background for white text, the APCA score is Lc -60.4.

You might remember that WCAG 2 evaluates contrast with a ratio (like “2.9:1”). However, APCA scores are a single number, ranging from Lc -108 to 106. Whether or not Lc -60.4 has enough contrast depends on how big the text is — and, new in APCA, how thick the font weight is.

There’s information about what’s considered a good target for Bronze, Silver, and Gold level conformance in the APCA Readability Criterion. These recommendations can really help guide designers to select the size and weight of text to ensure enough contrast, while allowing a range of beautiful color combinations. In fact, the WCAG 3 itself is being designed to provide flexible guidance to help you understand how to support all users, rather than binary judgments the way WCAG 2 does. Good accessibility isn’t about simply meeting a magical metric to check off a box on a list. It’s about understanding what works for real people, and designing for them. And what people need is complex, not binary.

You’ll notice that this particular APCA Contrast Calculator not only provides a score, but also evaluates the success of dynamic examples showing combinations of font size and font weight. In our case, for “Usage” it says “fluent text okay”. (For the black on blue example above, it instead says “Usage: spot & non text only”.) The Calculator is showing that white text on #2DAD4E works at 24px text if the font weight is 400 or bolder. If we want to use a font-weight of 300, then the text should be at least 41px. Of course, this will depend on which font-face we use, and we aren’t using the same font as that Contrast Calculator does, but there’s far more nuance in this guidance than tools for the WCAG 2 algorithm. And it helps our team come up with a plan for a beautiful design.

Our tree nursery website supports both light and dark mode, and our designers determined that #2DAD4E works as a button color for both light and dark mode for many users, as long as they carefully designed our buttons considering how font size and weight impacts contrast. But even with those considerations, Lc -60.4 is not quite enough contrast for all users, so for anyone who has set their accessibility preferences to ask for more contrast, we’ll replace the button background color with two options — a darker #3B873E green for light mode (with white text, scoring Lc -76.1), and a lighter #77e077 green for dark mode (with black text, scoring Lc 75.2).

Here’s the color palette our fictional design team wants us to accomplish in CSS:

A diagram of our color palette, explaining when to use which color combination. (All information is also articulated in the text of this article.)

When we define colors in variables, it’s incredibly easy to swap out color values for these various conditions. And by using contrast-color(), we only need to worry about the background colors, not the text color pairings. We’ll make the browser do the work, and get the paired colors for free.

To accomplish all of these things at once, we can just write this code (because, remember, we are pretending to live in a future when a better algorithm has replaced the WCAG 2 algorithm in CSS):

--button-color: #2DAD4E;  /* brand green background */ 

@media (prefers-contrast: more) {
  @media (prefers-color-scheme: light) {
    --button-color: #419543;  /* darker green background */
  }
  @media (prefers-color-scheme: dark) {
    --button-color: #77CA8B;  /* lighter green background */
  }
}

button {
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
  font-size: 1.5rem;  /* 1.5 * 16 = 24px at normal zoom */
  font-weight: 500;
}

In reality, since the WCAG 2 algorithm is the one driving contrast-color(), we probably couldn’t use it on this website. But if we had another project where the brand color was a darker green, and the choice between white/black was the correct one, it could be quite helpful today.

Using contrast-color() is especially helpful when defining colors for multiple states or options like enabled/disabled, light/dark mode, prefers-contrast, and more.

Beyond black & white

You might be wondering, “but what if I want the browser to choose a color beyond just black/white?” If you read about or tried out our original implementation in Safari Technology Preview 122 four years ago, you might remember that the original feature did much more. The newer contrast-color() function is greatly simplified from the original color-contrast().

Because a decision on which color-contrast algorithm to use for WCAG 3 is still being debated, the CSS Working Group decided to move forward with a tool that simply chooses black or white to contrast with the first color. Keeping it simple makes it possible to swap out the algorithm later. By hardcoding the list of options to be black/white, websites are far less likely to break when the WCAG 2 algorithm is replaced, giving the CSSWG the flexibility it needs to keep making needed changes, even as contrast-color ships into the hands of users.

In the future, more complex tools will come along to support more powerful options. Perhaps you’ll be able to list a set of custom color options and have the browser pick from those, instead of picking from black/white. Perhaps you’ll list a set of options, plus specify a contrast level that you want the browser to aim for, instead of having it picking the choice that yields maximum contrast.

In the meantime, often a simple choice between black and white is all you need. We wanted to get the simple version into your hands sooner, rather than waiting for a process that will take years.

And while all of the examples above show black/white text on a color background, contrast-color can be used for much more. You can use a custom color for your text, and make the background be black/white. Or not involve text at all, and define colors for borders, background — anything. There’s a lot you can do.

Continue the conversation

You can learn more about the APCA (Accessible Perceptual Contrast Algorithm) by reading documentation from the folks creating it. Including:

We’d love to hear your thoughts about contrast-color(). Your feedback on this tool can help shape its future. You can find me, Jen Simmons, on Bluesky / Mastodon. Or follow our other web evangelists — Saron Yitbarek on BlueSky, and Jon Davis on Bluesky / Mastodon. You can also follow WebKit on LinkedIn.

May 13, 2025 05:00 PM

May 12, 2025

Igalia WebKit Team: WebKit Igalia Periodical #23

Igalia WebKit

Update on what happened in WebKit in the week from May 5 to May 12.

This week saw one more feature enabled by default, additional support to track memory allocations, continued work on multimedia and WebAssembly.

Cross-Port đŸ±

The Media Capabilities API is now enabled by default. It was previously available as a run-time option in the WPE/WebKitGTK API (WebKitSettings:enable-media-capabilities), so this is just a default tweak.

Landed a change that integrates malloc heap breakdown functionality with non-Apple ports. It works similarly to Apple's one yet in case of non-Apple ports the per-heap memory allocation statistics are printed to stdout periodically for now. In the future this functionality will be integrated with Sysprof.

Multimedia đŸŽ„

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

Support for WebRTC RTP header extensions was improved, a RTP header extension for video orientation metadata handling was introduced and several simulcast tests are now passing

Progress is ongoing on resumable player suspension, which will eventually allow us to handle websites with lots of simultaneous media elements better in the GStreamer ports, but this is a complex task.

JavaScriptCore 🐟

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

The in-place Wasm interpreter (IPInt) port to 32-bits has seen some more work.

Fixed a bug in OMG caused by divergence with the 64-bit version. Further syncing is underway.

Releases đŸ“Šïž

Michael Catanzaro has published a writeup on his blog about how the WebKitGTK API versions have changed over time.

Infrastructure đŸ—ïž

Landed some improvements in the WebKit container SDK for Linux, particularly in error handling.

That’s all for this week!

By Igalia WebKit Team at May 12, 2025 07:29 PM

WebKit Features in Safari 18.5

Surfin’ Safari

Safari 18.5 is here, with Declarative Web Push on macOS and more. After the massive set of new WebKit features and fixes in Safari 18.4, this release is far more modest. It includes bug fixes and follow-ups to the last release.

Declarative Web Push on macOS

Declarative Web Push is now available on macOS. This new approach to push notifications on the web doesn’t require Service Workers — which makes it far easier for you as a developer to implement. And saves battery life for your users.

Every notification uses a standardized JSON format:

{
    "web_push": 8030,
    "notification": {
        "title": "Webkit.org — Meet Declarative Web Push",
        "lang": "en-US",
        "dir": "ltr",
        "body": "Send push notifications without JavaScript or service worker!",
        "navigate": "https://webkit.org/blog/16535/meet-declarative-web-push/",
        "silent": false,
        "app_badge": "1"
    }
}

Because Declarative Web Push doesn’t have the potential for misuse that original Web Push does, there’s no need to enforce limitations and penalties for failing to display a notification. It’s more private and more energy efficient by design. And it can be used today with an elegant backwards compatibility path for browser engines that haven’t yet added support.

Learn all about it in Meet Declarative Web Push.

Bug Fixes and more

Editing

  • Fixed some errors in finding the caret position from a click or tap on iOS in vertical writing modes. (146512180)

JavaScript

  • Fixed processing of an alternation of strings. (147776512)

Lockdown Mode

  • Fixed a logic error present in iOS 18.4 and aligned releases that caused Lockdown Mode-exempted websites and apps to incorrectly restrict image formats. (149401615)

Networking

  • Fixed an issue where using WebSockets within a WebWorker could cause the entire worker to freeze by preventing the worker’s run loop from executing during the send operation. (149070944)

PDF

  • Fixed VoiceOver focus popping out of the text field or getting stuck. (148340058)

Rendering

  • Fixed an issue where text incorrectly overflows in width: max-content grids with min-content columns due to incorrect min-content sizing during track sizing, ensuring the grid properly accommodates the item’s max-content width. (149095793)

Sandboxing

  • Fixed an issue where the WebContent process would not respond to notifications due to a missing entitlement, ensuring notifications are properly forwarded by conditionalizing the sandbox rule. (148108994)

Service Workers

  • Fixed Service Worker downloads being prematurely interrupted. (143065672)
  • Fixed moving a download file to its final destination. (146326574)

Web Extensions

  • Fixed declarativeNetRequestWithHostAccess permission removing site access even though the extension already has permission to inject scripts. (145484265)
  • Fixed the Permissions API to return either <all_urls> or *://*/* match pattern depending on which was requested by the extension. (149003428)

Updating to Safari 18.5

Safari 18.5 is available on iOS 18.5, iPadOS 18.5, macOS Sequoia 15.5, macOS Sonoma, macOS Ventura, and in visionOS 2.5. To get the latest version of Safari on iPhone, iPad or Apple Vision Pro, go to Settings > General > Software Update, and tap to update.

If you are running macOS Sonoma or macOS Ventura, you can update Safari by itself, without updating macOS. Go to ïŁż > System Settings > General > Software Update and click “More info
” under Updates Available.

Feedback

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

Download the latest Safari Technology Preview on macOS to stay at the forefront of the web platform and to use the latest Web Inspector features.

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

May 12, 2025 05:15 PM

May 06, 2025

Igalia WebKit Team: WebKit Igalia Periodical #22

Igalia WebKit

Update on what happened in WebKit in the week from April 15 to May 6.

WPEPlatform continued closing the feature gap with libwpe-based WPE backends, WPE got improved clipboard support, and JSC gets assorted improvements.

Cross-Port đŸ±

Multimedia đŸŽ„

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

The GstWPE2 GStreamer plugin landed in GStreamer main, it makes use of the WPEPlatform API. It will ship in GStreamer 1.28. Compared to GstWPE1 it provides the same features, but improved support for NVIDIA GPUs. The main regression is lack of audio support, which is work-in-progress, both on the WPE and GStreamer sides.

JavaScriptCore 🐟

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

Work on enabling the in-place Wasm interpreter (IPInt) on 32-bits has progressed nicely

The JSC tests runner can now guard against a pathological failure mode.

In JavaScriptCore's implementation of Temporal, Tim Chevalier fixed the parsing of RFC 9557 annotations in date strings to work according to the standard. So now syntactically valid but unknown annotations [foo=bar] are correctly ignored, and the ! flag in an annotation is handled correctly. Philip Chimento expanded the test suite around this feature and fixed a couple of crashes in Temporal.

Math.hypot(x, y, z) received a fix for a corner case.

WPE WebKit 📟

WPE now uses the new pasteboard API, aligning it with the GTK port, and enabling features that were previously disabled. Note that the new features work only with WPEPlatform, because libwpe-based backends are limited to access clipboard text items.

WPE Platform API đŸ§©

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

A new interface for handling clipboard requests was added, with a generic implementation that only operated within WebKit.

Platform backends may add their own clipboard handling, with the Wayland one being the first one to, using wl_data_device_manager.

This continues the effort to close the feature gap between the “traditional” libwpe-based WPE backends and the new WPEPlatform ones.

Community & Events đŸ€

Carlos GarcĂ­a has published a blog post about the optimizations introduced in the WPE and GTK WebKit ports since the introduction of Skia replacing Cairo for 2D rendering. Plus, there are some hints about what is coming next.

That’s all for this week!

By Igalia WebKit Team at May 06, 2025 01:27 PM

April 21, 2025

Carlos GarcĂ­a Campos: Graphics improvements in WebKitGTK and WPEWebKit after the switch to Skia

Igalia WebKit

In my previous post, when I introduced the switch to Skia for 2D rendering, I explained that we replaced Cairo with Skia keeping mostly the same architecture. This alone was an important improvement in performance, but still the graphics implementation was designed for Cairo and CPU rendering. Once we considered the switch to Skia as stable, we started to work on changes to take more advantage of Skia and GPU rendering to improve the performance even more. In this post I’m going to present some of those improvements and other not directly related to Skia and GPU rendering.

Explicit fence support

This is related to the DMA-BUF renderer used by the GTK port and WPE when using the new API. The composited buffer is shared as a DMA-BUF between the web and UI processes. Once the web process finished the composition we created a fence and waited for it, to make sure that when the UI process was notified that the composition was done the buffer was actually ready. This approach was safe, but slow. In 281640@main we introduced support for explicit fencing to the WPE port. When possible, an exportable fence is created, so that instead of waiting for it immediately, we export it as a file descriptor that is sent to the UI process as part of the message that notifies that a new frame has been composited. This unblocks the web process as soon as composition is done. When supported by the platform, for example in WPE under Wayland when the zwp_linux_explicit_synchronization_v1 protocol is available, the fence file descriptor is passed to the platform implementation. Otherwise, the UI process asynchronously waits for the fence by polling the file descriptor before passing the buffer to the platform. This is what we always do in the GTK port since 281744@main. This change improved the score of all MotionMark tests, see for example multiply.

Enable MSAA when available

In 282223@main we enabled the support for MSAA when possible in the WPE port only, because this is more important for embedded devices where we use 4 samples providing good enough quality with a better performance. This change improved the Motion Mark tests that use 2D canvas like canvas arcs, paths and canvas lines. You can see here the change in paths when run in a RaspberryPi 4 with WPE 64 bits.

Avoid textures copies in accelerated 2D canvas

As I also explained in the previous post, when 2D canvas is accelerated we now use a dedicated layer that renders into a texture that is copied to be passed to the compositor. In 283460@main we changed the implementation to use a CoordinatedPlatformLayerBufferNativeImage to handle the canvas texture and avoid the copy, directly passing the texture to the compositor. This improved the MotionMark tests that use 2D canvas. See canvas arcs, for example.

Introduce threaded GPU painting mode

In the initial implementation of the GPU rendering mode, layers were painted in the main thread. In 287060@main we moved the rendering task to a dedicated thread when using the GPU, with the same threaded rendering architecture we have always used for CPU rendering, but limited to 1 worker thread. This improved the performance of several MotionMark tests like images, suits and multiply. See images.

Update default GPU thread settings

Parallelization is not so important for GPU rendering compared to CPU, but still we realized that we got better results by increasing a bit the amount of worker threads when doing GPU rendering. In 290781@main we increased the limit of GPU worker threads to 2 for systems with at least 4 CPU cores. This improved mainly images and suits in MotionMark. See suits.

Hybrid threaded CPU+GPU rendering mode

We had either GPU or CPU worker threads for layer rendering. In systems with 4 CPU cores or more we now have 2 GPU worker threads. When those 2 threads are busy rendering, why not using the CPU to render other pending tiles? And the same applies when doing CPU rendering, when all workers are busy, could we use the GPU to render other pending tasks? We tried and turned out to be a good idea, especially in embedded devices. In 291106@main we introduced the hybrid mode, giving priority to GPU or CPU workers depending on the default rendering mode, and also taking into account special cases like on HiDPI, where we are always scaling, and we always prefer the GPU. This improved multiply, images and suits. See images.

Use Skia API for display list implementation

When rendering with Cairo and threaded rendering enabled we use our own implementation of display lists specific to Cairo. When switching to Skia we thought it was a good idea to use the WebCore display list implementation instead, since it’s cross-platform implementation shared with other ports. But we realized this implementation is not yet ready to support multiple threads, because it holds references to WebCore objects that are not thread safe. Main thread might change those objects before they have been processed by painting threads. So, we decided to try to use the Skia API (SkPicture) that supports recording in the main thread and replaying from worker threads. In 292639@main we replaced the WebCore display list usage by SkPicture. This was expected to be a neutral change in terms of performance but it surprisingly improved several MotionMark tests like leaves, multiply and suits. See leaves.

Use Damage to track the dirty region of GraphicsLayer

Every time there’s a change in a GraphicsLayer and it needs to be repainted, it’s notified and the area that changed is included so that we only render the parts of the layer that changed. That’s what we call the layer dirty region. It can happen that when there are many small updates in a layer we end up with lots of dirty regions on every layer flush. We used to have a limit of 32 dirty regions per layer, so that when more than 32 are added we just united them into the first dirty area. This limit was removed because we always unite the dirty areas for the same tiles when processing the updates to prepare the rendering tasks. However, we also tried to avoid handling the same dirty region twice, so every time a new dirty region was added we iterated the existing regions to check if it was already present. Without the 32 regions limit that means we ended up iterating a potentially very long list on every dirty region addition. The damage propagation feature uses a Damage class to efficiently handle dirty regions, so we thought we could reuse it to track the layer dirty region, bringing back the limit but uniting in a more efficient way than using always the first dirty area of the list. It also allowed to remove check for duplicated area in the list. This change was added in 292747@main and improved the performance of MotionMark leaves and multiply tests. See leaves.

Record all dirty tiles of a layer once

After the switch to use SkPicture for the display list implementation, we realized that this API would also allow to record the graphics layer once, using the bounding box of the dirty region, and then replay multiple times on worker threads for every dirty tile. Recording can be a very heavy operation, specially when there are shadows or filters, and it was always done for every tile due to the limitations of the previous display list implementation. In 292929@main we introduced the change with improvements in MotionMark leaves and multiply tests. See multiply.

MotionMark results

I’ve shown here the improvements of these changes in some of the MotionMark tests. I have to say that some of those changes also introduced small regressions in other tests, but the global improvement is still noticeable. Here is a table with the scores of all tests before these improvements and current main branch run by WPE MiniBrowser in a RaspberryPi 4 (64bit).

TestScore July 2024Score April 2025
Multiply501.17684.23
Canvas arcs140.24828.05
Canvas lines1613.933086.60
Paths375.524255.65
Leaves319.31470.78
Images162.69267.78
Suits232.91445.80
Design33.7964.06

What’s next?

There’s still quite a lot of room for improvement, so we are already working on other features and exploring ideas to continue improving the performance. Some of those are:

  • Damage tracking: this feature is already present, but disabled by default because it’s still work in progress. We currently use the damage information to only paint the areas of every layer that changed. But then we always compose a whole frame inside WebKit that is passed to the UI process to be presented on screen. It’s possible to use the damage information to improve both, the composition inside WebKit and the presentation of the composited frame on the screen. For more details about this feature read Pawel’s awesome blog post about it.
  • Use DMA-BUF for tile textures to improve pixel transfer operations: We currently use DMA-BUF buffers to share the composited frame between the web and UI process. We are now exploring the idea of using DMA-BUF also for the textures used by the WebKit compositor to generate the frame. This would allow to improve the performance of pixel transfer operations, for example when doing CPU rendering we need to upload the dirty regions from main memory to a compositor texture on every composition. With DMA-BUF backed textures we can map the buffer into main memory and paint with the CPU directly into the mapped buffer.
  • Compositor synchronization: We plan to try to improve the synchronization of the WebKit compositor with the system vblank and the different sources of composition (painted layers, video layers, CSS animations, WebGL, etc.)

 

By carlos garcia campos at April 21, 2025 08:27 AM

April 16, 2025

Pawel Lampe: Introduction to damage propagation in WPE and GTK WebKit ports

Igalia WebKit

Damage propagation is an optional WPE/GTK WebKit feature that — when enabled — reduces browser’s GPU utilization at the expense of increased CPU and memory utilization. It’s very useful especially in the context of low- and mid-end embedded devices, where GPUs are most often not too powerful and thus become a performance bottleneck in many applications.

Basic definitions #

The only two terms that require explanation to understand the feature on a surface level are the damage and its propagation.

The damage #

In computer graphics, the damage term is usually used in the context of repeatable rendering and means essentially “the region of a rendered scene that changed and requires repainting”.

In the context of WebKit, the above definition may be specialized a bit as WebKit’s rendering engine is about rendering web content to frames (passed further to the platform) in response to changes within a web page. Thus the definition of WebKit’s damage refers, more specifically, to “the region of web page view that changed since previous frame and requires repainting”.

On the implementation level, the damage is almost always a collection of rectangles that cover the changed region. This is exactly the case for WPE and GTK WebKit ports.

To better understand what the above means, it’s recommended to carefully examine the below screenshot of GTK MiniBrowser as it depicts the rendering of the poster circle demo with the damage visualizer activated: GTK MiniBrowser screenshot showing the damage visualization. In the image above, one can see the following elements:

  • the web page view — marked with a rectangle stroked to magenta color,
  • the damage — marked with red rectangles,
  • the browser elements — everything that lays above the rectangle stroked to a magenta color.

What the above image depicts in practice, is that during that particular frame rendering, the area highlighted red (the damage) has changed and needs to be repainted. Thus — as expected — only the moving parts of the demo require repainting. It’s also worth emphasizing that in that case, it’s also easy to see how small fraction of the web page view requires repainting. Hence one can imagine the gains from the reduced amount of painting.

The propagation #

Normally, the job of the rendering engine is to paint the contents of a web page view to a frame (or buffer in more general terms) and provide such rendering result to the platform on every scene rendering iteration — which usually is 60 times per second. Without the damage propagation feature, the whole frame is marked as changed (the whole web page view) always. Therefore, the platform has to perform the full update of the pixels it has 60 times per second.

While in most of the use cases, the above approach is good enough, in the case of embedded devices with less powerful GPUs, this can be optimized. The basic idea is to produce the frame along with the damage information i.e. a hint for the platform on what changed within the produced frame. With the damage provided (usually as an array of rectangles), the platform can optimize a lot of its operations as — effectively — it can perform just a partial update of its internal memory. In practice, this usually means that fewer pixels require updating on the screen.

For the above optimization to work, the damage has to be calculated by the rendering engine for each frame and then propagated along with the produced frame up to its final destination. Thus the damage propagation can be summarized as continuous damage calculation and propagation throughout the web engine.

Damage propagation pipeline #

Once the general idea has been highlighted, it’s possible to examine the damage propagation in more detail. Before reading further, however, it’s highly recommended for the reader to go carefully through the famous “WPE Graphics architecture” article that gives a good overview of the WebKit graphics pipeline in general and which introduces the basic terminology used in that context.

Pipeline overview #

The information on the visual changes within the web page view has to travel a very long way before it reaches the final destination. As it traverses the thread and process boundaries in an orderly manner, it can be summarized as forming a pipeline within the broader graphics pipeline. The image below presents an overview of such damage propagation pipeline:

Damage propagation pipeline overview.

Pipeline details #

This pipeline starts with the changes to the web page view visual state (RenderTree) being triggered by one of many possible sources. Such sources may include:

  • User interactions — e.g. moving mouse cursor around (and hence hovering elements etc.), typing text using keyboard etc.
  • Web API usage — e.g. the web page changing DOM, CSS etc.
  • multimedia — e.g. the media player in a playing state,
  • and many others.

Once the changes are induced for certain RenderObjects, their visual impact is calculated and encoded as rectangles called dirty as they require re-painting within a GraphicsLayer the particular RenderObject maps to. At this point, the visual changes may simply be called layer damage as the dirty rectangles are stored in the layer coordinate space and as they describe what changed within that certain layer since the last frame was rendered.

The next step in the pipeline is passing the layer damage of each GraphicsLayer (GraphicsLayerCoordinated) to the WebKit’s compositor. This is done along with any other layer updates and is mostly covered by the CoordinatedPlatformLayer. The “coordinated” prefix of that name is not without meaning. As threaded accelerated compositing is usually used nowadays, passing the layer damage to the WebKit’s compositor must be coordinated between the main thread and the compositor thread.

When the layer damage of each layer is passed to the WebKit’s compositor, it’s stored in the TextureMapperLayer that corresponds to the given layer’s CoordinatedPlatformLayer. With that — and with all other layer-level updates — the WebKit’s compositor can start computing the frame damage i.e. damage that is the final damage to be passed to the very end of the pipeline.

The first step to building frame damage is to process the layer updates. Layer updates describe changes of various layer properties such as size, position, transform, opacity, background color, etc. Many of those updates have a visual impact on the final frame, therefore a portion of frame damage must be inferred from those changes. For example, a layer’s transform change that effectively changes the layer position means that the layer visually disappears from one place and appears in the other. Thus the frame damage has to account for both the layer’s old and new position.

Once the layer updates are processed, WebKit’s compositor has a full set of information to take the layer damage of each layer into account. Thus in the second step, WebKit’s compositor traverses the tree formed out of TextureMapperLayer objects and collects their layer damages. Once the layer damage of a certain layer is collected, it’s transformed from the layer coordinate space into a global coordinate space so that it can be added to the frame damage directly.

After those two steps, the frame damage is ready. At this point, it can be used for a couple of extra use cases:

Eventually — regardless of extra uses — the WebKit’s compositor composes the frame and sends it (a handle to it) to the UI Process along with frame damage using the IPC mechanism.

In the UI process, there are basically two options determining frame damage destiny — it can be either consumed or ignored — depending on the platform-facing implementation. At the moment of writing:

Once the frame damage is consumed, it means that it reached the platform and thus the pipeline ends for that frame.

Damage propagation pipeline details.

Current status of the implementation #

At the moment of writing, the damage propagation feature is run-time-disabled by default (PropagateDamagingInformation feature flag) and compile-time enabled by default for GTK and WPE (with new platform API) ports. Overall, the feature works pretty well in the majority of real-world scenarios. However, there are still some uncovered code paths that lead to visual glitches. Therefore it’s fair to say the feature is still a work in progress. The work, however, is pretty advanced. Moreover, the feature is set to a testable state and thus it’s active throughout all the layout test runs on CI. Not only the feature is tested by every layout test that tests any kind of rendering, but it also has quite a lot of dedicated layout tests. Not to mention the unit tests covering the Damage class.

In terms of functionalities, when the feature is enabled it:

  • activates the damage propagation pipeline and hence propagates the damage up to the platform,
  • activates additional WebKit-compositor-level optimizations.

Damage propagation #

When the feature is enabled, the main goal is to activate the damage propagation pipeline so that eventually the damage can be provided to the platform. However, in reality, a substantial part of the pipeline is always active regardless of the features being enabled or compiled. This part of the pipeline ends before the damage reaches CoordinatedPlatformLayer and is always active because it was used for layer-level optimizations for a long time. More specifically — this part of the pipeline existed long before the damage propagation feature and was using layer damage to optimize the layer painting to the intermediate surfaces.

Because of the above, when the feature is enabled, only the part of the pipeline that starts with CoordinatedPlatformLayer is activated. It is, however, still a significant portion of the pipeline and therefore it implies additional CPU/memory costs.

WebKit’s compositor optimizations #

When the feature is activated and the damage flows through the WebKit’s compositor, it creates a unique opportunity for the compositor to utilize that information and reduce the amount of painting/compositing it has to perform. At the moment of writing, the GTK/WPE WebKit’s compositor is using the damage to optimize the following:

  • to apply global glScissor to define the smallest possible clipping rect for all the painting it does — thus reducing the amount of painting,
  • to reduce the amount of painting when compositing the tiles of the layers using tiled backing stores.

Detailed descriptions of the above optimizations are well beyond the scope of this article and thus will be provided in one of the next articles on the subject of damage propagation.

Trying it out #

As mentioned in the above sections, the feature only works in the GTK and the new-platform-API-powered WPE ports. This means that:

  • In the case of GTK, one can use MiniBrowser or any up-to-date GTK-WebKit-derived browser to test the feature.
  • In the case of WPE with the new WPE platform API the cog browser cannot be used as it uses the old API. Therefore, one has to use MiniBrowser with the --use-wpe-platform-api argument to activate the new WPE platform API.

Moreover, as the feature is run-time-disabled by default, it’s necessary to activate it. In the case of MiniBrowser, the switch is --features=+PropagateDamagingInformation.

Building & running the GTK MiniBrowser #

For quick testing, it’s highly recommended to use the latest revision of WebKit@main with wkdev SDK container and with GTK port. Assuming one has set up the container, the commands to build and run GTK’s MiniBrowser are as follows:

# building:
./Tools/Scripts/build-webkit --gtk --release

# running with visualizer
WEBKIT_SHOW_DAMAGE=1 \
Tools/Scripts/run-minibrowser \
--gtk --release --features=+PropagateDamagingInformation \
'https://webkit.org/blog-files/3d-transforms/poster-circle.html'

# running without visualizer
Tools/Scripts/run-minibrowser \
--gtk --release --features=+PropagateDamagingInformation \
'https://webkit.org/blog-files/3d-transforms/poster-circle.html'

Building & running the WPE MiniBrowser #

Alternatively, a WPE port can be used. Assuming some Wayland display is available, the commands to build and run the MiniBrowser are the following:

# building:
./Tools/Scripts/build-webkit --wpe --release

# running with visualizer
WEBKIT_SHOW_DAMAGE=1 \
Tools/Scripts/run-minibrowser \
--wpe --release --use-wpe-platform-api --features=+PropagateDamagingInformation \
'https://webkit.org/blog-files/3d-transforms/poster-circle.html'

# running without visualizer
Tools/Scripts/run-minibrowser \
--wpe --release --use-wpe-platform-api --features=+PropagateDamagingInformation \
'https://webkit.org/blog-files/3d-transforms/poster-circle.html'

Trying various URLs #

While any URL can be used to test the feature, below is a short list of recommendations to check:

It’s also worth mentioning that WEBKIT_SHOW_DAMAGE=1 environment variable disables damage-driven GTK/WPE WebKit’s compositor optimizations and therefore some glitches that are seen without the envvar, may not be seen when it is set. The URL to this presentation is a great example to explore various glitches that are yet to be fixed. To trigger them, it’s enough to navigate around the presentation using top/right/down/left arrows.

Coming up next #

This article was meant to scratch the surface of the broad, damage propagation topic. While it focused mostly on introducing basic terminology and describing the damage propagation pipeline in more detail, it briefly mentioned or skipped completely the following aspects of the feature:

  • the problem of storing the damage information efficiently,
  • the damage-driven optimizations of the GTK/WPE WebKit’s compositor,
  • the most common use cases for the feature,
  • the benchmark results on desktop-class and embedded devices.

Therefore, in the next articles, the above topics will be examined to a larger extent.


References #

  1. The new WPE platform API is still not released and thus it’s not yet officially announced. Some information on it, however, is provided by this presentation prepared for a WebKit contributors meeting.
  2. The platform that the WebKit renders to depends on the WebKit port:
    • in case of GTK port, the platform is GTK so the rendering is done to GtkWidget,
    • in case of WPE port with new WPE platform API, the platform is one of the following:
      • wayland — in that case rendering is done to the system’s compositor,
      • DRM — in that case rendering is done directly to the screen,
      • headless — in that case rendering is usually done into memory buffer.

April 16, 2025 12:00 AM

April 14, 2025

Igalia WebKit Team: WebKit Igalia Periodical #21

Igalia WebKit

Update on what happened in WebKit in the week from April 7 to April 14.

Cross-Port đŸ±

It is now possible to customize the limit of the content resources kept for the network page of WebInspector.

Reduce the amount of data kept for network page of WebInspector after eviction in case the memory limit is exceeded.

Multimedia đŸŽ„

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

On the WebRTC front, basic support for Rapid Synchronization was added, along with a couple of spec coverage improvements (https://commits.webkit.org/293567@main, https://commits.webkit.org/293569@main).

Fixed some upstream regressions in OMG.

Dispatch a "canceled" error event for all queued utterances in case of SpeechSynthesis.

Support for the Camera desktop portal was added recently, it will benefit mostly Flatpak apps using WebKitGTK, such as GNOME Web, for access to capture devices, which is a requirement for WebRTC support.

JavaScriptCore 🐟

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

Fixed a corner case in BBQJIT.

Work continued on porting the in-place wasm interpreter (IPInt) to 32-bits.

We have been working on bringing the Temporal implementation in JSC up to the current spec, and a step towards that goal was implemented in WebKit PR #43849. This PR changes how calendar annotation key parsing works; it doesn't change anything observable, but sets the groundwork for parsing calendar critical flags and unknown annotations.

Releases đŸ“Šïž

The recent releases of WebKitGTK and WPE WebKit 2.48 introduced a number of improvements to performance, reduced resource usage, better support for web platform features and standards, multimedia, and more!

Read more about these updates in the freshly published articles for WebKitGTK, and WPE WebKit.

Community & Events đŸ€

Support for Display device permission requests was recently added in GNOME Web, this is a requirement for Screen Capture handling on the User-Agent side.

Pawel Lampe published a blog post on the damage propagation feature. This feature reduces browser's GPU utilization at the expense of increased CPU and memory utilization in the WPE and GTK WebKit ports.

Our efforts to bring GstWebRTC support to WebKitGTK and WPEWebKit also include direct contributions to GStreamer. We recently improved WebRTC spec compliance in webrtcbin, by making the SDP mid attribute optional in offers and answers.

That’s all for this week!

By Igalia WebKit Team at April 14, 2025 07:41 PM

April 11, 2025

WPE WebKit Blog: WPE WebKit 2.48 highlights

Igalia WebKit

The WPE WebKit team has been working hard during the past six months and has recently released version 2.48 of the WPE port of WebKit. As is now tradition, here is an overview of the most important changes in this new stable release series.

Graphics and Rendering

The work on the graphics pipeline of WebKit continues, and a lot of improvements, refactorings, bug fixes, and optimizations have happened under the hood. These changes bring both performance and rendering improvements, and are too many to list individually. Nonetheless, there are a number of interesting changes.

GPU Worker Threads

When GPU rendering with Skia is in use, tiles will be rendered in worker threads, which has a positive impact on performance. Threads were already used when using the CPU for rendering. Note that GPU usage for rendering is not yet the default in the WPE port, but may be enabled setting WEBKIT_SKIA_ENABLE_CPU_RENDERING=0 in the environment before running programs that use WPE WebKit.

Canvas Improvements

The CanvasRenderingContext2D putImageData() and getImageData() methods have been optimized by preventing unnecessary buffer copies, resulting in improved performance and reduced memory usage.

CSS 3D Transforms

There have been several improvements handling elements that use preserve-3d CSS transforms. On top of a modest performance improvement, the changes fixed rendering issues, making the implementation compliant with the specification.

Damage Tracking

This release gained experimental support for collecting “damage” information, which tracks which parts of Web content produce visual changes in the displayed output. This information is then taken into account to reuse existing graphics buffers and repaint only those parts that need to be modified. This results better performance and less resource usage.

Note that this feature is disabled by default and may be previewed toggling the PropagateDamagingInformation feature flag.

GPU Process Beginnings

A new “GPU process” is now always built, but its usage is disabled by default at runtime. This is an experimental feature that can be toggled via the UseGPUProcessForWebGL feature flag, and as the name implies at the moment this new auxiliary process only supports handling WebGL content.

The GPU process is a new addition to WebKit’s multiprocess model, in which isolated processes are responsible for different tasks: the GPU process will eventually be in charge of most tasks that make use of the graphics processing unit, in order to improve security by separating graphics handling from Web content and data access. At the same time, graphics-intensive work does not interfere with Web content handling, which may bring potential performance improvements in the future.

Multimedia

The MediaRecorder backend gained support for the WebM format and audio bitrate configuration. WebM usage requires GStreamer 1.24.9 or newer.

Video handling using the WebCodecs API no longer ignores the prefer-hardware option. It is used as a hint to attempt using hardware-accelerated GStreamer components. If that fails, software based codecs will be used as fallback.

The Web Speech API gained a new synthesis backend, using libspiel. The existing FLite-based backend is still chosen by default because the dependency is readily available in most distributions, but setting USE_SPIEL=ON at build time is recommended where libspiel may be available.

The GStreamer-GL sink can now handle DMA-BUF memory buffers, replacing the DMA-BUF sink in this way.

API Changes

The JavaScriptCore GLib API has a new jsc_value_new_promise() function to create Promise objects from native code.

WPE Platform library

The WPEPlatform library is a completely new API which changes how WPE embedding API works. The aim is to make both developing WPE backends and user applications more approachable and idiomatic using GLib and GObject conventions. Inspiration has been drawn from the Cog API. A preview version is shipped along in 2.48, and as such it needs to be explicitly enabled at build time with ENABLE_WPE_PLATFORM=ON. The API may still change and applications developed using WPEPlatform are likely to need changes with future WPE WebKit releases.

Web Platform

The list of Web Platform features that are newly available in 2.48 is considerably long, and includes the following highlights:

Some new functionality may disabled by default, but testable after toggling the corresponding feature flag.

  • FileSystemWritableFileStream is available behind the flag of the same name.
  • Support for CSS progress(), media-progress() and container-progress() functions—all part of the CSS, behind runtime flags CSSMediaProgressFunction and CSSContainerProgressFunction, respectively.

Packaging

The Web Inspector resources are no longer built into a shared library, but as a GResource bundle file. This avoids usage of the dynamic linker and allows mapping the file in memory, which brings improved resource usage.

Packagers and distributors might need some adaptation of their build infrastructure in cases where the Web Inspector resources have some ad-hoc handling. Typical cases are avoiding installation in production builds for embedded devices, or as an optional component for development builds.

For example, using the default build configuration, the file with the resources inside the installation prefix will be share/wpe-webkit-2.0/inspector.gresource, instead of the old lib/wpe-webkit-2.0/libWPEWebInspectorResources.so library.

Other Noteworthy Changes

  • The minimum required ICU version is now 70.1.
  • Reading of Remote Web Inspector protocol messages was optimized, resulting in a considerable speed-up for large messages.

The WPE WebKit team is already working on the 2.50 release, which is planned for September. In the meantime, you can expect stable updates for the 2.48 series through the usual channels.

April 11, 2025 12:00 AM

April 07, 2025

Igalia WebKit Team: WebKit Igalia Periodical #20

Igalia WebKit

Update on what happened in WebKit in the week from March 31 to April 7.

Cross-Port đŸ±

Graphics đŸ–Œïž

By default we divide layers that need to be painted into 512x512 tiles, and only paint the tiles that have changed. We record each layer/tile combination into a SkPicture and replay the painting commands in worker threads, either on the CPU or the GPU. A change was landed to improve the algorithm, by recording the changed area of each layer into a single SkPicture, and for each tile replay the same picture, but clipped to the tile dimensions and position.

WPE WebKit 📟

WPE Platform API đŸ§©

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

A WPE Platform-based implementation of Media Queries' Interaction Media Features, supporting pointer and hover-related queries, has landed in WPE WebKit.

When using the Wayland backend, this change exposes the current state of pointing devices (mouse and touchscreen), dynamically reacting to changes such as plugging or unplugging. When the new WPEPlatform API is not used, the previous behaviour, defined at build time, is still used.

WPE Android ↗ đŸ€–

Adaptation of WPE WebKit targeting the Android operating system.

A number of fixes have been merged to fix and improve building WPE WebKit for Android. This is part of an ongoing effort to make it possible to build WPE-Android using upstream WebKit without needing additional patches.

The example MiniBrowser included with WPE-Android has been fixed to handle edge-to-edge layouts on Android 15.

That’s all for this week!

By Igalia WebKit Team at April 07, 2025 09:49 PM

April 04, 2025

WPE WebKit Blog: Success Story: GLANCR

Igalia WebKit

GLANCR: the smart mirror for your digital home WPE

From SmartTVs to light bulbs and cameras, smart devices are not hard to find in homes these days. Glancr’s smart mirror is a device that seamlessly integrates digital functionality into everyday life. Powered by its custom operating system mirr.OS, Glancr puts the Web at the forefront, allowing users to see all the information that matters to them in a customizable interface, right on a mirror. Besides being useful in smart home scenarios, Glancr can also expand the possibilities in other use cases in the industry, such as digital signage, point-of-sale devices, or even the healthcare industry.

WPE WebKit empowers mirr.OS to efficiently run and render interactive widgets and applications, ensuring a fluid and responsive user experience at the same time that enables mirr.OS to leverage the power of the Web Platform. This allows developers to create rich, web-based interfaces tailored to the smart mirror’s unique requirements, in order to deliver a seamless and visually compelling interaction layer for users at the same time it bridges the gap between digital functionality and aesthetic appeal.

April 04, 2025 12:00 AM

March 31, 2025

Igalia WebKit Team: WebKit Igalia Periodical #19

Igalia WebKit

Update on what happened in WebKit in the week from March 24 to March 31.

Cross-Port đŸ±

Multimedia đŸŽ„

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

ManagedMediaSource was enabled in WPE WebKit and WebKitGTK.

JavaScriptCore 🐟

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

The JSC jitless Wasm tests now work again on ARMv7.

Doing a 32-bit build on ARM64 hardware now works with GCC 11.x as well.

Graphics đŸ–Œïž

Landed a change that improves the painting of tile fragments in the compositor if damage propagation is enabled and if the tiles sizes are bigger than 256x256. In those cases, less GPU is utilized when damage allows.

The GTK and WPE ports no longer use DisplayList to serialize the painting commands and replay them in worker threads, but SkPictureRecorder/SkPicture from Skia. Some parts of the WebCore painting system, especially font rendering, are not thread-safe yet, and our current cross-thread use of DisplayList makes it harder to improve the current architecture without breaking GTK and WPE ports. This motivated the search for an alternative implementation.

Community & Events đŸ€

Sysprof is now able to filter samples by marks. This allows for statistically relevant data on what's running when a specific mark is ongoing, and as a consequence, allows for better data analysis. You can read more here.

That’s all for this week!

By Igalia WebKit Team at March 31, 2025 07:58 PM

March 24, 2025

Igalia WebKit Team: WebKit Igalia Periodical #18

Igalia WebKit

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

Cross-Port đŸ±

Limited the amount data stored for certain elements of WebKitWebViewSessionState. This results in memory savings, and avoids oddly large objects which resulted in web view state being restored slowly.

Multimedia đŸŽ„

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

Reduced parsing overhead in incoming WebRTC video streams by reducing excessive tag events at startup and by avoiding the plugging of parser elements for already-parsed streams.

JavaScriptCore 🐟

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

Fixed an integer overflow when using wasm/gc on 32-bits.

Graphics đŸ–Œïž

Landed a change that fixes a few scenarios where the damage was not generated on layer property changes.

Releases đŸ“Šïž

WebKitGTK 2.48.0 and WPE WebKit 2.48.0 have been released. While they may not look as exciting as the 2.46 series, which introduced the use of Skia for painting, they nevertheless includes half a year of improvements. This development cycle focused on reworking internals, which brings modest performance improvements for all kinds of devices, but most importantly cleanups which will enable further improvements going forward.

For those who need longer to integrate newer releases, which we know can be a longer process for embedded device distrihytos, we have also published WPE WebKit 2.46.7 with a few stability and security fixes.

Accompanying these releases there is security advisory WSA-2025-0002 (GTK, WPE), which covers the solved security issues. Crucially, all three contain the fix for an issue known to be exploited in the wild, and therefore we strongly encourage updating.

As usual, bug reports are always welcome at the WebKit Bugzilla.

libsoup 3.6.5 has been released with bug fixes and security improvements.

That’s all for this week!

By Igalia WebKit Team at March 24, 2025 07:50 PM

March 18, 2025

Manuel Rego

Igalia WebKit

Two new nice additions by Igalia on the last Safari Technology Preview.

March 18, 2025 12:00 AM

March 17, 2025

Igalia WebKit Team: WebKit Igalia Periodical #17

Igalia WebKit

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

Cross-Port đŸ±

Web Platform 🌐

Updated button activation behaviour and type property reflection with command and commandfor. Also aligned popovertarget behaviour with latest specification.

Fixed reflection of command IDL property.

Implemented the trusted-types-eval keyword for the script-src CSP directive.

Implemented accessibility handling for command buttons.

Enabled command and commandfor in preview.

JavaScriptCore 🐟

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

Fixed an integer overflow in JSC (only happens on 32-bit systems with lots of RAM).

Graphics đŸ–Œïž

Fixed theming issues in WPE/WebKitGTK with vertical writing-modes.

That’s all for this week!

By Igalia WebKit Team at March 17, 2025 08:31 PM

March 10, 2025

Igalia WebKit Team: WebKit Igalia Periodical #16

Igalia WebKit

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

Cross-Port đŸ±

Web Platform 🌐

Forced styling to field-sizing: fixed when an input element is auto filled, and added support for changing field-sizing dynamically.

Fixed an issue where the imperative popover APIs didn't take into account the source parameter for focus behavior.

Multimedia đŸŽ„

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

Fixed YouTube breakage on videos with advertisements. The fix prevents scrolling to the comments section when the videos are fullscreened, but having working video playback was considered more important for now.

Graphics đŸ–Œïž

Fixed re-layout issues for form controls with the experimental field-sizing implementation.

Landed a change that improves the quality of damage rectangles and reduces the amount of painting done in the compositor in some simple scenarios.

Introduce a hybrid threaded rendering mode, scheduling tasks to both the CPU and GPU worker pools. By default we use CPU-affine rendering on WPE, and GPU-affine rendering on the GTK port, saturating the CPU/GPU worker pool first, before switching to the GPU/CPU.

Infrastructure đŸ—ïž

We have recently enabled automatic nightly runs of WPT tests with WPE for the Web Platform Tests (WPT) dashboard. If you click on the “Edit” button at the wpt.fyi dashboard now there is the option to select WPE.

For example, one may compare the results for WPE to other browsers or examine the differences between the WPE and GTK ports.

These nightly runs happen now daily on the TaskCluster CI sponsored by Mozilla (Thanks to James Graham!). If you want to run WPT tests with WPE WebKit locally, there are instructions at the WPT documentation.

That’s all for this week!

By Igalia WebKit Team at March 10, 2025 10:59 PM

March 03, 2025

Igalia WebKit Team: WebKit Igalia Periodical #15

Igalia WebKit

Update on what happened in WebKit in the week from February 19 to March 3.

Cross-Port đŸ±

Web Platform 🌐

Implemented support for setting returnValue for <dialog> with Invoker Commands.

After fixing an issue with Trusted Types when doing attribute mutation within the default callback, and implementing performance improvements for Trusted Types enforcement, the Trusted Types implementation is now considered stable and has been enabled by default.

Multimedia đŸŽ„

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

Landed one fix which, along with previous patches, solved the webKitMediaSrcStreamFlush() crash reported in bug #260455.

Unfortunately, in some pages where the crash previously occurred, now a different blank video bug has been revealed. The cause of this bug is known, but fixing it would cause performance regressions in pages with many video elements. Work is ongoing to find a better solution for both.

The initial support of MP4-muxed WebVTT in-band text tracks is about to be merged, which will bring this MSE feature to the ports using GStreamer. Text tracks for the macOS port of WebKit only landed two weeks ago and we expect there will be issues to iron out in WebKit ports, multiplatform code and even potentially in spec work—we are already aware of a few potential ones.

Note that out-of band text-tracks are well supported in MSE across browsers and commonly used. On the other hand, no browsers currently ship with in-band text track support in MSE at this point.

Support for MediaStreamTrack.configurationchange events was added, along with related improvements in the GStreamer PipeWire plugin. This will allow WebRTC applications to seamlessly handle default audio/video capture changes.

Graphics đŸ–Œïž

Continued improving the support for handling graphics damage:

  • Added support for validating damage rectangles in Layout Tests.

  • Landed a change that adds layout tests covering the damage propagation feature.

  • Landed a change that fixes damage rectangles on layer resize operations.

  • Landed a change that improves damage rectangles produced by scrolling so that they are clipped to the parent container.

The number of threads used for painting with the GPU has been slightly tweaked, which brings a measurable performance improvement in all kinds of devices with four or mores processor cores.

Releases đŸ“Šïž

The stable branch for the upcoming 2.48.x stable release series of the GTK and WPE ports has been created. The first preview releases from this branch are WebKitGTK 2.47.90 and WPE WebKit 2.47.90. People willing to report issues and help with stabilization are encouraged to test them and report issues in Bugzilla.

Community & Events đŸ€

Published a blog post that presents an opinionated approach to the work with textual logs obtained from WebKit and GStreamer.

That’s all for this week!

By Igalia WebKit Team at March 03, 2025 02:15 PM