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 07, 2025

Add wide gamut P3 and alpha transparency to your color picker in HTML

Surfin’ Safari

Since the beginning, the web was always interactive. HTML form controls let users create content, upload information and interact with other users and services. Several years ago, our team worked to add the switch control to the HTML web standard, and ship in Safari 17.4. Interoperable support for form controls in vertical writing modes was improved in all browsers as part of the Interop Project, which we also shipped in Safari 17.4.

The color picker was originally added to the web in HTML5. Jeremy Keith wrote about it 15 years ago in his book HTML5 for Web Designers:

Perhaps the most ambitious widget replacement in HTML5 is the color input type. This accepts values in the familiar Hexadecimal format: #000000 for black, #FFFFFF for white… The plan is for browsers to implement native color pickers like the ones in just about every other application on your computer. So far, no browsers have done this [UPDATE: now they have] but when they do, it will be, like, totally awesome.

Back in 2010, the sRGB color space was the only colorspace for the web. And hexadecimal colors were the dominant way to write a color. Since then, a lot has changed. First, CSS3 brought rgba() to the web, allowing colors with a translucent alpha channel for the first time.

Display technology continued to evolve by leaps and bounds. Apple started shipping P3 wide-gamut displays with the 2015 iMac Retina, the 2016 iPad Pro and iPhone 7. Also in 2016, WebKit shipped support for display-P3 in CSS using the color() function. Fast-forward to 2023… thanks to the Interop Project, all browsers finally supported multiple tools in CSS for defining wide gamut colors and alpha transparency, including lab(), oklab(), lch(), and oklch(), as well as color().

But what about the color picker? Can the user of a website or web app choose a color in P3 or a color with alpha transparency? Until recently, the answer was no. The web’s color picker has been stuck in the early 2010s.

The new color picker

We wanted to bring the color picker into the era of wide-gamut color and alpha transparency. So in 2024, we collaborated with the WHATWG to add a more powerful color picker to the HTML web standard. Now <input type="color"> has two new optional attributes — the alpha and colorspace. We shipped support for both in Safari 18.4.

Switching your color pickers to support the P3 color gamut and alpha transparency is easy.

<label>
  Select a color:
  <input type="color" colorspace="display-p3" alpha>
</label>

When you include the colorspace="display-p3" attribute, the browser now allows the user to choose from the full range of colors in the P3 color gamut. Without this attribute, HTML’s default kicks in, and the chosen color will be limited to 8-bits and sRGB. (If you want to explicitly constrain the user’s choice to the more limited set of colors, you can do so with colorspace="limited-srgb".)

iPhone showing a color picker. Three sliders provide options for red, green, blue. The Display P3 Hex Color number is in an editable box. And there's a slider for opacity.
Try out the new wide-gamut P3 color picker with support for selecting opacity in Safari 18.4

When you include the new alpha attribute, the color picker suddenly includes a slider (or other similar interface) to allow the user to choose from a range of alpha transparency. How the control looks and how it’s labeled depends on the operating system. In Safari on iOS, you can see a slider labeled “Opacity”.

Safari 18.4 is the first browser to add support for colorspace and for alpha to the color picker. You can start using these attributes today, even before many users have a browser with support. Older browsers will simply fallback to displaying the options they allowed previously — sRGB without any opacity. You can rely on the elegance of HTML’s built-in nature of progressive enhancement. It works for input type="color" colorspace="display-P3" alpha just the same as it does when you use input type="checkbox" switch to create a switch control on the web. WebKit is still the only browser engine with support for the switch control, but you can still use it on websites today, since it will fallback to a checkbox in browsers without support.

Ever since the color picker was added to HTML, you’ve been able to optionally include a value attribute for input type ="color". It changes the initially-selected color from the default of black to whatever you’d like. Previously, the value attribute only accepted hexadecimal colors. Now, with this change, you can use any of the many ways to define colors. Here are a few examples:

<input type="color" value="papayawhip">
<input type="color" value="oklab(59% 0.1 0.1 / 0.5)" colorspace="display-p3" alpha>
<input type="color" value="color(display-p3 0.3737 0.2103 0.5791)" colorspace="display-p3">

In browsers without support, defining the default value with a new option for declaring a color will simply fallback to black.

We’d love to hear from you

We have more ideas for further enhancing <input type=color> , but we’d love to know what you think to help guide which direction we should go. What else do you need from HTML’s color picker? What should we tackle next?

You can share your thoughts with us on social media, Anne van Kesteren on Bluesky or Jen Simmons on Bluesky / Mastodon. You can also follow Saron Yitbarek on BlueSky, and Jon Davis on Bluesky / Mastodon.

May 07, 2025 01:00 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

May 01, 2025

Easier layout with margin-trim

Surfin’ Safari

If you write a lot of CSS, you are familiar with those moments when you aren’t quite sure how to accomplish what you want to accomplish. Usually, you’ll turn to tutorials or documentation, and learn more about CSS to get your work done. But every once in a while, you realize there is no “proper” way to do what you want to do. So you come up with (or borrow) a solution that feels hacky. Maybe it requires a lot of complex selectors. Or maybe it works for the content you have at the moment, but you worry that someday, someone might throw different HTML at the site, and the solution you wrote will break.

CSS has matured a lot over the last decade. Many robust solutions filled in gaps that previously required fragile hacks. And now, there’s one more — margin-trim.

Margin trim

The margin-trim property lets you tell a container to trim the margins off its children — any margins that push up against the container. In one fell swoop, all of the margin space between the children and the container is eliminated.

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

This also works when the margins are on the grandchildren or great grand-children, or great great great great grand-children. If there is space created with margins on any of the content inside the container, and that space buts up against the container, it’s trimmed away when margin-trim is applied to the container.

Another diagram of how margin trim affects layout before & after — this time  with grandchildren that have margins

Let’s imagine a practical example. Let’s say we have multiple paragraphs inside an article element, and those paragraphs have margins. Also at the same time, the container has padding on it.

article {
  padding: 2lh;
  background: white;
  p {
    margin-block: 1lh;
  }
}

This is very typical code. The padding on the container is supposed to create an even amount of space all the way around the box, but instead there’s extra white space above and below the content. Like this:

Four paragraphs of text in a white box on a tan background. The white box has a lot more space above and below the text than it does on the sides of the text.

By using 1lh for the margins between the paragraphs, and 2lh for the padding on the article box, we’re attempting to create a beautiful typographic layout. Let’s turn on some guides to better see where the extra space is coming from. The padding on the article box and the margins on the paragraphs are each marked in separate colors.

The same example of text in a box with margins, now with one color marking the padding, and another color marking the margins.

The margins on the first and last paragraphs (1lh) are being added to the padding (2lh) to create a space in the block direction that measures 3lh.

It will be better for the design if we get rid of the margin above the first paragraph and the margin below the last paragraph. Before we had margin-trim, we would attempt to remove the margins from the first and last paragraphs, or lessen the padding in the block direction… but any approach we take will be dependent on the content inside. Perhaps another instance of this article will start with a headline that has a different amount for a top margin. Or start with an image that has no margin.

Without being 100% sure of what kind of content will be in the box, it’s hard to guarantee the spacing will come out as desired. Until now.

The new margin-trim property gives us an easy way to ask directly for what we want. We can tell the box to eliminate any margins that are butting up against that box.

For example:

article {
  margin-trim: block;
  padding: 2lh;
  background: white;
  p {
    margin-block: 1lh;
  }
}

Now the browser automatically chops off any margins that touch the edge of the article box in the block direction — in this case the top and bottom of the box.

The same example again, now with the margins above and below the text chopped off. The colored stripes marking margins no longer exist above and below the content.

Note that while the margins are defined on the <p> element, you declare margin-trim on the <article> element. You always apply margin-trim to the container, not the element that has the margin in the first place.

Here’s the end result.

The same demo, without any guides, now seeing the clean text, and seeing that the space above & below the text, and the space on the sides is the same amount.

Try it yourself

You can try out margin-trim in this live demo, in Safari 16.4 or greater.

Screenshot of the demo on the web where people can try it out for themselves.

Browser Support

Support formargin-trim shipped in Safari over two years ago. But so far, Safari is the only browser with support. So what should you do for browsers without support? For our demo, you could write fallback code inside of feature queries, like this:

article { 
  margin-trim: block;
  font-size: 1.2rem;
  line-height: 1.3;
  padding: 2lh;
  p {
    margin-block: 1lh;
  }
}
@support not (margin-trim: block) {
  article { 
    :first-child {
      margin-block-start: 0;
    }
    :last-child {
      margin-block-end: 0;
    }
  }
}

This helps to clarify the difference between margin-trim and the older techniques we’ve been using.

When using :first-child and :last-child any element that’s the first or last direct child of the container will have its margins trimmed. But any content that either isn’t wrapped in an element, or that is nested further down in the DOM structure will not.

Another diagram showing how the interaction of margins and margin trim works — this time with three drawings, to show margins on children and grandchildren with no margin trim, and older technique for solving this, and using margin trim.

For example, if the first element is a figure with a top margin, and the figure contains an image that also has a top margin, both of those margins will be trimmed by margin-trim, while only the figure margin will be trimmed by :first-child.

<article>
  <figure style="margin-top: 1em">
    <img  style="margin-top: 1em" src="photo.jxl" alt="[alt]">
    <figcaption>[caption]</figcaption>
  </figure>
</article>  

The margin-trim property makes trimming such margins easier and more robust than older techniques.

Even though Safari is the only browser with support at the moment, it makes sense to use it today. Put the hackier layout code in a feature query for the browsers without support ( like@support not (margin-trim: block) { }), while using margin-trim for the browsers that do have it. Hopefully the less robust code will work. It’s the code you are going to have to write anyway. But meanwhile, browsers with support get a more robust solution. And as more and more browsers add support, more and more users will be guaranteed to have a layout that never breaks, no matter what’s thrown at it.

Options for Margin Trim

The values for margin-trim are all logical values, referring to the block and inline directions.

  • margin-trim: none
  • margin-trim: block
  • margin-trim: inline
  • margin-trim: block-start
  • margin-trim: block-end
  • margin-trim: inline-start
  • margin-trim: inline-end

If you want to trim in both directions at the same time, you can do so by combining long-hand values. For example:

margin-trim: block-start block-end inline-start inline-end;

In December 2024, the CSSWG resolved to also allow the shorter block and inline keywords in combination, allowing for syntax like this:

margin-trim: block inline;

The work has been done in WebKit to support this last option. Look for it in Safari Technology Preview soon. Follow this issue for more updates.

Let us know

CSS has never been better. It’s my hope you learn about small improvements like this one, and use it to write more robust code. Let me know what you think on Bluesky or Mastodon. I’d love to hear your stories, feature requests, and questions.

May 01, 2025 02:00 PM

April 30, 2025

Release Notes for Safari Technology Preview 218

Surfin’ Safari

Safari Technology Preview Release 218 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: 293327@main…293898@main

CSS

New Features

  • Added support for crossorigin() and referrerpolicy() modifiers on CSS image loads. (293647@main) (149166339)

Resolved Issues

  • Fixed @font-face font-family descriptor to not allow a list of values. (293892@main) (142009630)
  • Fixed placeholder visibility to update on input type change. (293346@main) (148681639)
  • Fixed an issue where float boxes, selections, and carets were incorrectly painted inside skipped subtrees.(293358@main) (148741142)
  • Fixed incorrect getBoundingClientRect() inside skipped subtree on an out-of-flow positioned box. (293428@main) (148770252)
  • Fixed: Updated the CSS random() function implementation to match the latest spec changes. (293439@main) (148868731)
  • Fixed: Replace match-element with element-shared in the random() function to match the standard changes. (293474@main) (148920346)
  • Fixed making <pre> and other elements use logical margins in the User-Agent stylesheet. (293670@main) (149212392)

JavaScript

New Features

  • Added more support for Temporal. (293864@main) (75685318)

Resolved Issues

  • Fixed iterator helpers incorrectly closing iterators on early errors. (293842@main) (148774612)

  • Fixed Iterator.prototype.reduce failing with an undefined initial parameter. (293797@main) (149470140)

Lockdown Mode

New Features

  • Added support for more web fonts in Lockdown Mode by replacing a fragile hard-coded allowlist with a safe parser that handles a broader range of fonts without compromising security. (293728@main) (149314760)

Rendering

Resolved Issues

  • 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. (293552@main) (118706766)
  • Fixed incorrect ruby annotation positioning in sideways-lr. (293572@main) (148713073)
  • Fixed: Prevented hit testing content inside a skipped subtree. (293361@main) (148741508)

SVG

New Features

  • Added support for pointer-events="bounding-box" for SVG group containers. (293473@main) (148181592)

Web API

Resolved Issues

  • Fixed an issue where pending utterances do not receive an error event when speech synthesis is cancelled.(293536@main) (148731039)

Web Extensions

New Features

  • Added support for the request-method content blocker trigger field. (293746@main) (148474901)

Resolved Issues

  • Fixed converting declarativeNetRequest rules so that higher numbers are treated as higher priority. (293546@main) (145570245) (FB16607998)
  • Fixed an issue causingwasm-unsafe-eval to not get parsed as a valid CSP keyword. (293854@main) (147551225)
  • Fixed and issue that prevented loading a web accessible resource from a subframe at about:blank. (293839@main) (149494286)

Web Inspector

New Features

  • Added support in the Debugger tab for stepping over an await statement as though it is synchronous code. (293628@main) (149133320)

WebRTC

New Features

  • Added support for RTCRtpScriptTransformer.generateKeyFrame to take a rid parameter. (293633@main) (148592676)

April 30, 2025 10:07 PM

April 24, 2025

Polishing your typography with line height units

Surfin’ Safari

It’s been possible to create gorgeous layouts on the web for decades, but often refining the details to perfection can take a lot of effort. Arguably too much effort, and developers just don’t have the time. Happily, the last few years of CSS have seen a lot of new tools, sometimes very simple tools, that when used in a smart fashion suddenly make polishing graphic design details incredibly easy. One such tool is the lh unit.

Line height units

It might seem like no big deal that the web gained two new units, lh and rlh, to reference line heights. Support shipped in every browser in 2023 without much fanfare. But wow, are they powerful.

Basically, 1lh equals the height of one line of text, for the current font at the current line height. “LH” stands for Line Height. The accompanying 1rlh unit is the equivalent of one line height at the root, just like how rem is the em at the root. “RLH” stands for Root Line Height.

My favorite thing to do with the lh unit is to set margins on content. Let’s set a new universal margin on paragraphs with:

p { margin-block: 1lh; } 

You can see the results in the following screenshots. On the left, the margin in the block direction is set to 1em, the default in UA stylesheets since the 90s. On the right it’s changed to 1lh.

Two example articles typeset side-by-side. The one on the right has a bit more space between each paragraph.

Many people who have an eye for layout and spacing can immediately see the difference. You might agree that the version on the right just looks more polished. It looks refined. While the version on the left looks a bit clunky. It looks, well, like everything on the web has looked for decades. Slightly awkward.

Many other people will look at this comparison and think “I don’t see it” or “what’s the big deal”? Let’s draw a line grid over the text to make the difference more clear. Hopefully now it’s more obvious that the blank space between paragraphs is equivalent to a line of text when it’s defined in lh units.

The same side-by-side comparison of two typeset articles — only now there's a light blue line underneath each line of text, spaced exactly line height apart. On the left, the lines of text get messy. They aren't lining up correctly. On the right, every line of text falls on the line grid.

Line height units give us a direct way to tie any size in our layout to the vertical rhythm of the text. Margins are just one possibility — padding is another, gaps, width and height, or any other measurement in the layout.

Try it yourself

You can try out combinations of options to see the effects of using lh for paragraph margins in this demo.

A screenshot of the demo in a web browser. It shows the same sample text as the other images, this time with a control panel of options. You can switch the block margins from 1em to 1lh, adjust margin-trim, switch fonts, and turn on/off margin guides and line grid guides.

Browser support

Line height units are supported in over 94% of the browsers people use today. For that last 6%, you can use progressive enhancement to ensure they get a good experience. For example:

article {
  padding: 1em; /* fallback for browsers lh without support */
  padding: 1lh;
} 

Using this technique causes browsers without support to render 1em of padding, while the browsers with support render 1lh of padding.

Take your typography to the next level

It really is a great time for typography on the web. There’re over a dozen small features that shipped in the last few years that empower web designers & developers to polish typographic details to a far better result, especially with the kind of easy robustness that makes it practical to accomplish. It’s my hope you use them!

Let me know what you think on Bluesky or Mastodon. I’d love to hear your stories, plans and questions.

April 24, 2025 06:45 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

Release Notes for Safari Technology Preview 217

Surfin’ Safari

Safari Technology Preview Release 217 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: 292535@main…293326@main

CSS

Resolved Issues

  • Fixed @scope to create a style nested context. (292821@main) (148101373)
  • Fixed changing content-visibility from visible to hidden to repaint correctly. (292960@main) (148273903)
  • Fixed color to not be a high-priority property. (293082@main) (148361501)

Forms

Resolved Issues

  • Fixed setValidity of ElementInternals to handle missing optional anchor parameter. (292770@main) (123744294)

JavaScript

Resolved Issues

  • Fixed performance of Math.hypot() that was significantly slower than Math.sqrt(). (292549@main) (141821484)
  • Fixed Array#indexOf and Array#includes to treat +0 and -0 as the same value. (293134@main) (148472519)

Media

Resolved Issues

  • Fixed subtitle tracks with no srclang to be shown with the correct label. (292577@main) (147722563)

Rendering

Resolved Issues

  • Fixed the text indicator sometimes getting clipped during a bounce animation. (292612@main) (147602900)
  • Fixed geometry values inside content-visibility: hidden subtrees. (293267@main) (148553259)
  • Fixed not marking content-visibility: hidden content for layout when targeting content-visibility: auto. (293304@main) (148663896)

SVG

Resolved Issues

  • Fixed: Improved handling of SVG images with subresources. (293259@main) (148607855)

Tables

Resolved Issues

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

Web API

New Features

  • Added support for scrollMargin in IntersectionObserver. (293306@main) (117527880)
  • Added support for the alg parameter when importing or exporting Edward’s-curve based JSON Web Keys in WebCrypto. (293089@main) (147323269)

Web Extensions

Resolved Issues

  • Fixed "excludeMatches" array in scripting.registerContentScripts() API getting ignored in Safari web extensions. (293106@main) (145489255) (FB16590857)

Web Inspector

New Features

  • Added support for exporting and importing data from worker targets in the Timelines tab. (292991@main) (145330533)
  • Added a a badge for <slot> to quickly jump to the assigned node in the Elements tab. (292966@main) (148297936)

Resolved Issues

  • Fixed a performance issue when blackboxing a large number of sourcemaps. (292997@main) (148116377)

WebRTC

New Features

  • Added support for exposing CSRC information for RTCEncodedVideoStream. (293088@main) (76548862)
  • Added serialisation of RTCEncodedAudioFrame and RTCEncodedVideoFrame. (293185@main) (148244334)
  • Added support for ImageCapture.grabFrame. (293243@main) (148425176)

Resolved Issues

  • Fixed enumerateDevices returning devices as available when permissions are denied. (292921@main) (147313922)
  • Fixed getUserMedia not using the correct camera or microphone if devices change while user is answering the permission prompt. (292696@main) (147762070)
  • FixedenumerateDevices to not check for device permission. (293003@main) (148094614)
  • Fixed WebRTC encoded transform to transfer to the RTC encoded frame array buffer. (293232@main) (148343876)
  • Fixed RTC encoded frame timestamp should be persistent. (293238@main) (148580865)

April 16, 2025 09:33 PM

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 15, 2025

The CSS shape() function

Surfin’ Safari

Shapes are an important aspect of graphic design. The shape() function provides a new way to author shapes that can adapt and scale with mixed proportions based on the size of the element.

For many years now, CSS has given web developers the ability to use shapes for clipping and animation. The most common usage of shapes is in theclip-path property. This applies a clip after the element has been laid out, which allows you, as a web developer, to do things like trim the edge to a decorative wavy shape. This can give a similar effect to masking, but is more efficient to render than a mask. Another use case for shapes is in theoffset-path property, which allows you to animate something along a path. Finally,shape-outside provides a way to wrap text around a non-rectangular shape (but currently only supports a subset of the shape types).

CSS provides a set of shapes that can be used with each of these properties. (In the web standards, these are fully defined in Basic Shapes.) There are various ways to describe rectangles, as well as circle() and ellipse(), polygon() (a set of straight-line segments) and the path() function, which takes an SVG-style path.

For example, consider this simple arrow shape:

SVG arrow shape with right angle pointing right and slight right-set curve

The SVG that produces this shape is:

<svg viewBox="0 0 150 100" xmlns="http://www.w3.org/2000/svg">
  <path fill="black" d="M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z " />
</svg>

We’ll break down this path later. For now, let’s take that path and apply it to an HTML element in clip-path:

.clipped {
    width: 150px;
    height: 100px;
    box-sizing: border-box;
    background-color: blue;
    border: 10px solid orange;
    clip-path: path("M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z");
}

Which produces this:

Unique arrow shape with blue rectangle and orange border where the tip of the right angle shows a portion of the orange border

Note how I’ve added a border so that you can see how clip-path is clipping out parts of the element.

But what happens now if we change the dimensions of the element? For example, what if we wanted a longer arrow?

.clipped {
    width: 200px;
    ...
}

Alas, we get the same shape, only we no longer see the border at the tip of the arrow.

Unique arrow shape with blue rectangle and orange border where the tip of the right angle does not show the orange border

This means that using path() in clip-path can’t be responsive; you can’t write CSS rules so that the path adapts to the size of the element. This is where the new shape() function comes in.

shape()

The new shape() function addresses this responsiveness problem head-on by allowing you to specify the path in terms of CSS keywords and units that you’re already familiar with, including the full power of calc() and CSS variables, and it’s much more readable.

Let’s go back and break down that SVG path which looks like an incomprehensible list of letters and numbers: “M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z”. We can break this down into a sequence of “commands”, which describe each section of the path:

M 0 0 move to 0, 0
L 100 0 line to 100, 0
L 150 50 line to 150, 50
L 100 100 line to 100, 100
L 0 100 line to 0, 100
Q 50 50 0 0 quadratic curve with control point at 50, 50, ending at 0, 0
z close path

We can transcribe this path into a shape using the names specified in the CSS spec; note how the first “move” command becomes the “from” in the shape:

clip-path: shape(from top left,
    line to 100px top,
    line to 150px 50%,
    line to 100px bottom,
    line to bottom left,
    curve to top left with 50px 50%,
    close);

Note that we can use keywords like “top” and “bottom left” when the point is in absolute coordinates, and we can use percentages.

That gives us the same result as above, but we haven’t made it fully responsive yet. Our next step is to make the shape stretch horizontally with the element’s width, but keep the height fixed to 100px. Let’s use a bit of math to achieve that. To simplify things a little, lets put the desired height of the element into a variable:

.responsive-clip {
    --height: 100px;
    height: var(--height);
    ...
}

We’ll also define the half-height value to compute the arrow shape on the right side, and for the control point of the quadratic curve.

    --half-height: calc(var(--height) / 2);

The responsive parts of the shape can be written in terms of percentages. Percentages used in x coordinates are relative to the element’s width, while the ones used in y coordinates are relative to the element’s height:

clip-path: shape(from top left,
    line to calc(100% - var(--half-height)) 0%,
    line to 100% var(--half-height),
    line to calc(100% - var(--half-height)) 100%,
    line to left bottom,
    curve to left top with var(--half-height) var(--half-height),
    close);

Now we have a clip that can be as long as the element!

Unique right pointing arrow that stretches maintaining the 90 degree angle of the arrow point while still showing the orange border at the tip

Making this shape adapt to the height of the element is trickier, because we need to be able to refer to the height in values on the horizontal axis. This is something we can do using Container Queries. First, let’s create a container in the HTML markup:

<div class="responsive-clip-container">
  <div class="responsive-clip"></div>
</div>

Once we’ve defined the container and moved the element’s dimensions there, we can now replace var(--half-height) with 50cqh :

.responsive-clip-container {
  width: 100%;
  aspect-ratio: 5 / 1;
  container-type: size;
}

.responsive-clip {
  height: 100%;
  background: blue;
  border: 10px solid orange;
  box-sizing: border-box;
  clip-path: shape(from top left,
    line to calc(100% - 50cqh) 0%,
    line to 100% 50cqh,
    line to calc(100% - 50cqh) 100%,
    line to bottom left,
    curve to top left with 50cqh 50cqh,
    close);
}

Let’s add a resizer to the container so you can play around with the responsiveness!

.responsive-clip-container {
  resize: both;
  overflow: clip;
}

Try this demo in Safari 18.4 or another browser with support.

See the Pen
shape() – demo 3
by Jen Simmons (@jensimmons)
on CodePen.

Resizing the arrow helps to truly demonstrate what shape() can do. Notice how the curve and the 90 degree angle are preserved, which is not something you would get if you stretched an SVG.

There are more features of the shape() function that we haven’t touched on here, like relative instead of absolute commands (e.g. move by 10px, 2em), other commands like arc, and various ways of describing curve control points. Dive into the web standard to learn more about these. And, of course, you can animate between shapes, as long as they contain the same list of commands, which can make for some very cool effects.

See the Pen
Shape animation
by Simon Fraser (@smfr)
on CodePen.

Let us know what you think of shape(). We’d love to see what you make!

April 15, 2025 05:01 PM

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 08, 2025

Better typography with text-wrap pretty

Surfin’ Safari

Support for text-wrap: pretty just shipped in Safari Technology Preview, bringing an unprecedented level of polish to typography on the web. Let’s take a look at what the WebKit version of pretty does — it’s probably a lot more than you expect. Then, we’ll compare it to balance and the other text-wrap values to better understand when to use which one.

Ideas of what makes for “good” typography are deeply rooted in eras when type was set by hand using metal, wood, or ink. Typesetters took great care when deciding if a word should go on the end of one line, the beginning of the next, or be broken with a hyphen. Their efforts improved comprehension, reduced eye-strain, and simply made the reading experience more pleasant. While beauty can be subjective, with disagreements about what’s “better”, there are also strongly-held typographic traditions around the globe, representing various languages and scripts. These traditions carry people’s culture from one generation to the next, through the centuries.

In digital typography, a computer places all the words, not humans. Often, as a web designer or developer, you are creating a template to be filled with different versions of content. There is no “hand tweaking” typography on the web, especially when the layout is fluid, reflowing to fit different shapes and sizes of screens. So what can we do now to better express the expectations of quality from traditional typography, while still relying on the mechanization brought by today’s computers?

One solution is text-wrap:pretty. It’s intended to bring a new level of polish to type on the web by leveraging paragraph-based algorithms to solve long-standing problems.

Better typography

There are several things certain typographic traditions teach you to do:

  1. Avoid short last lines. You want to avoid leaving a single word by itself on the end of a paragraph. It can look quite strange, and make space between paragraphs look mistakenly large.
  2. Avoid “bad rag”. You can look at the ragged edge of the text (the inline-end side) and note whether the general length of lines is kind of consistent, or whether the rag is very jagged. When hand setting, typographers would move words around to minimize visual differences between adjacent lines — to avoid bag rag. “Good rag” increases readability of the text, and makes the entire block of text look more pleasing.
  3. Avoid poor hyphenation. For languages that can be hyphenated, hyphenation helps create good rag. It also breaks a word into pieces, and places those pieces as far apart as possible in the inline dimension. This adds to the cognitive load when reading. It’s best to minimize the use of hyphenation and to avoid hyphenating two lines in a row.
  4. Avoid typographic rivers. If you know to look for rivers, you might start to notice that sometimes the spaces between words randomly line up across lines, creating a path of whitespace through the paragraph. Rivers can be distracting.

You can see these four problems in the following example. The text is identical on both the left and the right.

A screenshot of paragraph text that demonstrates a short last line, bad rag, bad hyphenation, and a typographic river.

Designers and typographers often use hyphenation and/or justification to help improve rag, but on the web, neither provides satisfying results. Until recently, it’s been impossible to do much of anything about short lines, bad rag, or rivers on the web. We’ve just lived with them.

Web line layout since 1991

For over 30 years, the web had only one technique for determining where to wrap text.

The browser starts with the first line of text, and lays out each word or syllable, one after another until it runs out of room. As soon as it has no more space to fit another word/syllable, it wraps to the next line (if wrapping is allowed). Then it starts on the next line, fitting all the content it can… then when it runs out of room, it wraps… and starts working on the next line.

It’s always thinking about only one line at a time. It wraps whenever it needs, after it’s fit the maximum amount of content on the previous line. If hyphenation is turned on, it will hyphenate whatever word is last on the line, at whatever point leaves as much of the word on the previous line as possible. Nothing else is taken into consideration — which is why text on the web has bad rag, rivers, short last lines, and hyphenation that makes no sense.

This is not required by the fact that text is laid out by a computer. For decades, software like Adobe InDesign and LaTeX has evaluated multiple lines of text at a time as they decide where to end one line and begin the next. It’s just that the web didn’t use a multiline algorithm. Until now.

We are excited to bring this capability to the web for the first time, in Safari Technology Preview 216.

text-wrap: pretty

Now, the web has the ability to evaluate the whole passage of text when figuring out where best to wrap. You can ask browsers to do this by using text-wrap: pretty. WebKit is not the first browser engine to implement, but we are the first browser to use it to evaluate and adjust the entire paragraph. And we are the first browser to use it to improve rag. We chose to take a more comprehensive approach in our implementation because we want you to be able to use this CSS to make your text easier to read and softer on the eyes, to provide your users with better readability and accessibility. And simply, to make something beautiful.

Safari Technology Preview 216 prevents short lines, improves the rag, and reduces the need for hyphenation — across all of the text, no matter how long it is. We are not yet making adjustments to prevent rivers, although we’d love to in the future.

While support for pretty shipped in Chrome 117, Edge 177, and Opera 103 in Fall 2023, and Samsung Internet 24 in 2024, the Chromium version is more limited in what it accomplishes. According to an article by the Chrome team, Chromium only makes adjustments to the last four lines of a paragraph. It’s focused on preventing short last lines. It also adjusts hyphenation if consecutive hyphenated lines appear at the end of a paragraph.

The purpose of pretty, as designed by the CSS Working Group, is for each browser to do what it can to improve how text wraps. The CSS Text Level 4 specification currently defines it like this, (where “user agent” means the web browser, emphasis added):

The user agent may among other things attempt to avoid excessively short last lines… but it should also improve the layout in additional ways. The precise set of improvements is user agent dependent, and may include things such as: reducing the variation in length between lines; avoiding typographic rivers; prioritizing different classes of soft wrap opportunities, hyphenation opportunities, or justification opportunities; avoiding hyphenation on too many consecutive lines.

The use of the word “may” is very meaningful here. It’s a clear statement that each browser gets to decide for itself exactly what pretty should do. There is no mandate for every browser to make the same choices. In fact, a browser team might decide in 2025 to handle some aspects of improving these qualities, and then change what their implementation does in the future.

Because of the way Chrome’s implementation of pretty has been taught, a lot of web developers expect this value is only supposed to prevent short last lines. But that was never the intention. In fact, the CSS Working Group defined a different value for such a purpose. It was just renamed last week to text-wrap: avoid-short-last-lines.

Take a look

You can try out text-wrap: pretty today in Safari Technology Preview 216. Check out this demo where you can toggle pretty on and off to see its effects. You can also toggle hyphenation or justification to try out any combination. Show guides and show ghosts will help you understand what’s happening. Or try out text-wrap: balance to see the difference. The demo has content in English, Arabic, German, Chinese and Japanese so you can see the effects in different writing systems.

Screenshot of the demo showing a control panel of options that include: text-wrap: pretty, hyphenate, justify, show guides, show ghosts, text-wrap: balance. With a bunch of text on the page which will change as different options are applied.
Try this demo in Safari Technology Preview 216.

Here’s a sample of text in English, without applying text-wrap. This is the default wrapping algorithm the web has had for years. I’ve turned on “show guides” to mark the edges of the text box. The green line marks the inline-end edge of the box — where the line layout algorithm is aiming for each line to reach. The browser wraps when the text reaches this green line.

Three paragraphs of text, with a green vertical line marking the inline end edge of the text box.

And here’s how the same text looks instead with text-wrap: pretty applied. The green line has moved. Now, the browser instead aims to wrap each line sooner than the maximum limit of the text box. It wraps within the range, definitely after the magenta line, and definitely before the red line. This improves the rag.

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.

You can also “show ghosts” to view the unprettified version as a ghost in the background.

The same text, only now the well wrapped version is in normal color text, with the previous, badly wrapped version directly behind the good version, in a faint cyan color. It's a ghost of the before version, making it clear which lines have changed and how.

You can also toggle hyphenation and justification on and off to compare different combinations. Resize the browser window, and see what happens at different widths of text.

You might notice that since Safari Technology Preview applies pretty to every line of text, not just the last four lines, it has a more significant impact on the text. The block of text becomes more of a solid rectangle.

You really have to typeset body text with text-wrap: pretty to see just how much of a difference it makes. It’s subtle, but remarkable. Combine this with paragraph margins of 1lh, and the text starts looking fantastic.

So why doesn’t every browser do everything it can to make the text look better? Because of performance.

Performance

Many developers are understandably concerned about the performance of text-wrap: pretty. While better typography is visually pleasing — it cannot come at the expense of a slower page. Each browser engineering team must think about the hardware and chips their users have when deciding how to set limits.

We are thrilled with the work we’ve done to ensure Safari users don’t experience a negative performance impact, even when web developers apply text-wrap: pretty to a lot of content on the web page.

One thing to know as a developer, the performance of text-wrap is not affected by how many elements on the page it’s applied to. Perf concerns emerge as the pretty algorithm takes more and more lines into consideration as it calculates what to do. In WebKit-based browsers or apps, your text element would need to be many hundreds or thousands of lines long to see a performance hit — and that kind of content is unusual on the web. If your content is broken up into typical-length paragraphs, then you have no reason to worry. Use text-wrap: pretty as much as you want, and rely on our browser engineers to ensure your users will not experience any downsides.

We might decide to add a mechanism to take really long paragraphs and break them up into more reasonable chunks, where WebKit evaluates each chunk separately. If we do so, then even 1,000-line paragraphs won’t affect performance. That’s the approach Adobe InDesign takes. It improves the layout of all lines of text, but it doesn’t evaluate an infinite number of lines inside each paragraph all at once, in one pass. There also might be other ways the WebKit team discovers to balance beauty and speed, ensuring pretty does the most to improve all of your text without affecting the users experience of our incredibly fast browser.

Test out text-wrap: pretty in Safari Technology Preview 216 today, and let us know if you can trigger a performance impact. File an issue at bugs.webkit.org, so we can consider such feedback as we polish this feature before shipping in Safari itself.

When to use pretty vs balance?

Clearly, text-wrap: pretty is designed to make body text more beautifully typeset. But is that the only use case for it? What about text-wrap: balance? When should we use pretty or balance?

There are people who will give you an overly simple answer like “pretty is for paragraphs and balance is for headlines” — but that’s likely bad advice. Let’s look at what balance does in contrast to pretty and how to decide which one to use on headlines, captions, teasers, and other kinds of shorter, wrapped text.

text-wrap: balance

Basically, the text-wrap: balance rule tells the browser to wrap in such places to make every line be about the same length as the others. I think of it like folding a piece of paper into halves, or thirds, or quarters.

For example, here’s a headline with the default of text-wrap: auto. You can see that the word “enough” ends up as the first word of the second line, simply because there wasn’t enough space for it on the first line after “with”. Each line is laid out one-by-one, with no regard for the others. This causes this headline to end up with the word “itself” alone on the last line.

Here’s a similar headline with text-wrap: balance applied. There’s no longer a single word by itself on the last line. That’s great! But that’s not all. The last line is now the about same length as the other two. The first line is made significantly shorter so that its length is “balanced” with the length of the others. All three lines basically have the same length.

You’ll notice that as a result, the visual imprint of the headline / the chunk of “ink” on the page is now significantly narrower than the overall space available in its container (marked here with a cyan line).

A balanced headline, where all three lines are about the same length. All of them are about two-thirds as wide as the box they are in, leaving a lot of white space on the right. The first line is actually the shortest line.

This can be fantastic for your design. You can apply balance to headlines, captions, teasers, any shorter types of copy and it will have the same effect. It will stack the lines of text so they’re all about the same length — they are balanced. And once the text has been wrapped, it will likely not fill the box anymore. It will be narrower than the available space.

Sometimes, that’s not a desirable effect. Perhaps your headline sits under a teaser image, and the design calls for the text to be the same width as the image. You’ll notice that in this example, the first line ends up shorter than the rest. Balancing the text might come at too high a cost. Perhaps all you wanted to do is avoid a short last line.

You can instead use text-wrap: pretty on such headlines. This will avoid the short last line, while also still filling the container in the inline direction.

A similar headline, with three words on the last line. The first line does stretch all the way across the box, and the second line is a bit shorter so that some of its words can fill in the last line.

You can try out these examples yourself in Safari Technology Preview 126+ and Chrome/Edge 130+ to dive more into the effect of text-wrap on long, medium, and short headlines. Drag the corner of the boxes to see just how differently they handle wrapping.

What are the performance considerations for text-wrap: balance? Again, the CSS web standard leaves it to the browser engine to decide what kind of limits should be in place to ensure the users experience is not negatively impacted. Browsers do not have to make the same choices as each other.

The Chromium implementation limits the number of lines that are balanced to four to ensure Chrome, Edge and other Chromium browsers will still be fast. The WebKit implementation doesn’t need to limit the number of lines. Every line will be balanced with all of the others.

So if “pretty is for body text and balance is for headlines” is too simplistic a recommendation to be good advice, what might be a better way to think about the choice?

I think about it like this:

  • pretty can be applied to anything on the page — body text, headlines, captions, teasers, etc. Look to see what it does and if you like the effect. If you have incredibly long paragraphs (or better said: long body text without any paragraphs breaking it up, think in the hundreds or thousands of lines of text), test performance first. Also, if you are animating text in such a way that it rewraps as it animates, test to see if that’s a good idea.
  • balance should be used for anything where you want all the lines to be the same length — especially headlines and captions, etc. And where you do not mind if the overall group of lines is narrower than its container. Don’t use it on long passages of text; that doesn’t make sense.
  • auto is the default, which currently considers just one line at a time as layout is calculated, like the web has since 1991 (see below).
  • stable should be used for editable text and more (see below).

Unconvinced that text-wrap: balance won’t usually make sense on long passages of text? Well, you can try it out in this same demo.

The same demo, now with `text-wrap: balance` applied to the paragraphs. Each paragraph is now wildly different widths from the others. This is not useful for anything.

See how the overall width of each paragraph is adjusted so that all the lines of text are about the same length, with no regard for how wide they are overall compared to their container? That’s what balancing does. And in the above example, that means each paragraph is a radically different width than the others. Which is odd. Only use it when you want that effect. Otherwise, you probably want to use another option.

What do the other values for text-wrap do? Let’s go through them.

text-wrap: avoid-short-last-lines

The avoid-short-last-lines value is the newest one in the CSS Text Module Level 4 specification. It’s not yet been implemented in any browser. It will focus on just avoiding short last lines, leaving pretty to do so much more.

text-wrap: auto

The default value of text-wrap currently does what the web has done since 1991, where each line of text is laid out by itself, with no consideration for multiple lines. (This is often called a “first-fit” or “greedy” algorithm.)

This, however, could change in the future! There may come a day when browsers decide to change the default, and apply some kind of multiline line-layout algorithm to all existing content on the web. This would improve all content, even old websites, even websites where the developer didn’t know about text-wrap: pretty.

text-wrap: stable

If you’ve tried text-wrap: stable, you might think “it doesn’t do anything! What is this?” Basically, right now, stable does the same thing as auto. It uses the original first-fit wrapping algorithm, where each line is laid out to fully fill that line with content, and only wrap where necessary.

This is an especially good choice of wrapping algorithms when the content itself is editable. If your user is writing text, you don’t want words/syllables jumping around, changing the wrapping as they type. To ensure your content won’t shift due to edits on subsequent lines, or in any case where you want OG line wrapping, apply text-wrap: stable.

This is also a good choice if you are animating text in such a way that it keeps re-wrapping. It will ensure the fastest wrapping algorithm is used at all times — important if the calculations are going to be done over and over in rapid succession.

By explicitly choosing text-wrap: stable you are ensuring this content will continue to wrap using the original algorithm, even if browsers redefine what auto does.

The stable value is already well supported.

text-wrap-mode and text-wrap-style

The text-wrap property is actually a short-hand for two longhands. The text-wrap-style property is for choosing the wrapping algorithm that’s used, while text-wrap-mode lets you turn wrapping on and off.

text-wrap-style: auto | stable | balance | pretty | avoid-short-last-lines
text-wrap-mode: wrap | nowrap 

By having both the text-wrap-mode and text-wrap-style properties, you have the flexibility to change the style of wrapping independently from whether or not content wraps, and let these choices cascade independently.

This means you can also use the shorthand to simply turn off wrapping with text-wrap: nowrap. Or, use text-wrap: wrap to turn wrapping back on. Test out how it works in this demo.

Support for the text-wrap-mode and text-wrap-style longhands, along with the nowrap and wrap values, became “Baseline Newly Available” (aka, available in all major browsers) in October 2024, when Chromium added support in Chrome/Edge 130. To ensure full support for wrapping for people with older browsers, you can always provide a fallback to the older white-space: nowrap | normal. (Although when you do, take care to also check your white space collapsing behavior, since it’s affected by white-space.)

What do you think?

Try out text-wrap: pretty in Safari Technology Preview 216 today. We’d love to hear what you think. Find me, Jen Simmons, on Bluesky or Mastodon to share your feedback. If you find a bug or problem, please file a WebKit bug report.

April 08, 2025 02:00 PM

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

February 26, 2025

Pawel Lampe: Working with WebKit and GStreamer logs in Emacs.

Igalia WebKit

WebKit has grown into a massive codebase throughout the years. To make developers’ lives easier, it offers various subsystems and integrations. One such subsystem is a logging subsystem that offers the recording of textual logs describing an execution of the internal engine parts.

The logging subsystem in WebKit (as in any computer system), is usually used for both debugging and educational purposes. As WebKit is a widely-used piece of software that runs on everything ranging from desktop-class devices up to low-end embedded devices, it’s not uncommon that logging is sometimes the only way for debugging when various limiting factors come into play. Such limiting factors don’t have to be only technical - it may also be that the software runs on some restricted systems and direct debugging is not allowed.

Requirements for efficient work with textual logs #

Regardless of the reasons why logging is used, once the set of logs is produced, one can work with it according to the particular need. From my experience, efficient work with textual logs requires a tool with the following capabilities:

  1. Ability to search for a particular substring or regular expression.
  2. Ability to filter text lines according to the substring or regular expressions.
  3. Ability to highlight particular substrings.
  4. Ability to mark certain lines for separate examination (with extra notes if possible).
  5. Ability to save and restore the current state of work.

While all text editors should be able to provide requirement 1, requirements 2-5 are usually more tricky and text editors won’t support them out of the box. Fortunately, any modern extensible text editor should be able to support requirements 2-5 after some extra configuration.

Setting up Emacs to work with logs #

Throughout the following sections, I use Emacs, the classic “extensible, customizable, free/libre text editor”, to showcase how it can be set up and used to meet the above criteria and to make work with logs a gentle experience.

Emacs, just like any other text editor, provides the support for requirement 1 from the previous section out of the box.

loccur - the minor mode for text filtering #

To support requirement 2, it requires some extra mode. My recommendation for that is loccur - the minor mode that acts just like a classic grep *nix utility yet directly in the editor. The benefit of that mode (over e.g. occur) is that it works in-place. Therefore it’s very ergonomic and - as I’ll show later - it works well in conjunction with bookmarking mode.

Installation of loccur is very simple and can be done from within the built-in package manager:

M-x package-install RET loccur RET

With loccur installed, one can immediately start using it by calling M-x loccur RET <regex> RET. The figure below depicts the example of filtering: loccur - the minor mode for text filtering.

highlight-symbol - the package with utility functions for text highlighting #

To support requirement 3, Emacs also requires the installation of extra module. In that case my recommendation is highlight-symbol that is a simple set of functions that enables basic text fragment highlighting on the fly.

Installation of this module is also very simple and boils down to:

M-x package-install RET highlight-symbol RET

With the above, it’s very easy to get results like in the figure below: highlight-symbol - the package with utility functions for text highlighting. just by moving the cursor around and using C-c h to toggle the highlight of the text at the current cursor position.

bm - the package with utility functions for buffer lines bookmarking #

Finally, to support requirements 4 and 5, Emacs requires one last extra package. This time my recommendation is bm that is quite a powerful set of utilities for text bookmarking.

In this case, installation is also very simple and is all about:

M-x package-install RET bm RET

In a nutshell, the bm package brings some visual capabilities like in the figure below: bm - the package with utility functions for buffer lines bookmarking as well as non-visual capabilities that will be discussed in further sections.

The final configuration #

Once all the necessary modules are installed, it’s worth to spend some time on configuration. With just a few simple tweaks it’s possible to make the work with logs simple and easily reproducible.

To not influence other workflows, I recommend attaching as much configuration as possible to any major mode and setting that mode as a default for files with certain extensions. The configuration below uses a major mode called text-mode as the one for working with logs and associates all the files with a suffix .log with it. Moreover, the most critical commands of the modes installed in the previous sections are binded to the key shortcuts. The one last thing is to enable truncating the lines ((set-default 'truncate-lines t)) and highlighting the line that the cursor is currently at ((hl-line-mode 1)).

(add-to-list 'auto-mode-alist '("\\.log\\'" . text-mode))
(add-hook 'text-mode-hook
(lambda ()
(define-key text-mode-map (kbd "C-c t") 'bm-toggle)
(define-key text-mode-map (kbd "C-c n") 'bm-next)
(define-key text-mode-map (kbd "C-c p") 'bm-previous)
(define-key text-mode-map (kbd "C-c h") 'highlight-symbol)
(define-key text-mode-map (kbd "C-c C-c") 'highlight-symbol-next)
(set-default 'truncate-lines t)
(hl-line-mode 1)
))

WebKit logs case study #

To show what the workflow of Emacs is with the above configuration and modules, some logs are required first. It’s very easy to get some logs out of WebKit, so I’ll additionally get some GStreamer logs as well. For that, I’ll build a WebKit GTK port from the latest revision of WebKit repository. To make the build process easier, I’ll use the WebKit container SDK.

Here’s the build command:

./Tools/Scripts/build-webkit --gtk --debug --cmakeargs="-DENABLE_JOURNALD_LOG=OFF"

The above command disables the ENABLE_JOURNALD_LOG build option so that logs are printed to stderr. This will result in the WebKit and GStreamer logs being bundled together as intended.

Once the build is ready, one can run any URL to get the logs. I’ve chosen a YouTube conformance tests suite from 2021 and selected test case “39. PlaybackRateChange” to get some interesting entries from multimedia-related subsystems:

export GST_DEBUG_NO_COLOR=1
export GST_DEBUG=4,webkit*:7
export WEBKIT_DEBUG=Layout,Media=debug,Events=debug
export URL='https://ytlr-cert.appspot.com/2021/main.html'
./Tools/Scripts/run-minibrowser --gtk --debug --features=+LogsPageMessagesToSystemConsole "${URL}" &> log.log

The commands above reveal some interesting aspects of how to get certain logs. First of all, the commands above specify a few environment variables:

  • GST_DEBUG=4,webkit*:7 - to enable GStreamer logs of level INFO (for all categories) and of level TRACE for the webkit* categories
  • GST_DEBUG_NO_COLOR=1 - to disable coloring of GStreamer logs
  • WEBKIT_DEBUG=Layout,Media=debug,Events=debug - to enable WebKit logs for a few interesting channels.

Moreover, the runtime preference LogsPageMessagesToSystemConsole is enabled to log console output logged by JavaScript code.

The workflow #

Once the logs are collected, one can open them using Emacs and start making sense out of them by gradually exploring the flow of execution. In the below exercise, I intend to understand what happened from the multimedia perspective during the execution of the test case “39. PlaybackRateChange”.

The first step is usually to find the most critical lines that mark more/less the area in the file where the interesting things happen. In that case I propose using M-x loccur RET CONSOLE LOG RET to check what the console logs printed by the application itself are. Once some lines are filtered, one can use bm-toggle command (C-c t) to mark some lines for later examination (highlighted as orange): Effect of filtering and marking some console logs.

For practicing purposes I propose exiting the filtered view M-x loccur RET and trying again to see what events the browser was dispatching e.g. using M-x loccur RET on node node 0x7535d70700b0 VIDEO RET: Effect of filtering and marking some video node events.

In general, the combination of loccur and substring/regexp searches should be very convenient to quickly explore various types of logs along with marking them for later. In case of very important log lines, one can additionally use bm-bookmark-annotate command to add extra notes for later.

Once some interesting log lines are marked, the most basic thing to do is to jump between them using bm-previous (C-c n) and bm-next (C-c p). However, the true power of bm mode comes with the use of M-x bm-show RET to get the view containing only the lines marked with bm-toggle (originally highlighted orange): Effect of invoking bm-show.

This view is especially useful as it shows only the lines deliberately marked using bm-toggle and allows one to quickly jump to them in the original file. Moreover, the lines are displayed in the order they appear in the original file. Therefore it’s very easy to see the unified flow of the system and start making sense out of the data presented. What’s even more interesting, the view contains also the line numbers from the original file as well as manually added annotations if any. The line numbers are especially useful as they may be used for resuming the work after ending the Emacs session - which I’ll describe further in this section.

When the *bm-bookmarks* view is rendered, the only problem left is that the lines are hard to read as they are displayed using a single color. To overcome that problem one can use the macros from the highlight-symbol package using the C-c h shortcut defined in the configuration. The result of highlighting some strings is depicted in the figure below: Highlighting strings in bm-show.

With some colors added, it’s much easier to read the logs and focus on essential parts.

Saving and resuming the session #

On some rare occasions it may happen that it’s necessary to close the Emacs session yet the work with certain log file is not done and needs to be resumed later. For that, the simple trick is to open the current set of bookmarks with M-x bm-show RET and then save that buffer to the file. Personally, I just create a file with the same name as log file yet with .bm prefix - so for log.log it’s log.log.bm.

Once the session is resumed, it is enough to open both log.log and log.log.bm files side by side and create a simple ad-hoc macro to use line numbers from log.log.bm to mark them again in the log.log file: Resuming the session

As shown in the above gif, within a few seconds all the marks are applied in the buffer with log.log file and the work can resume from that point i.e. one can jump around using bm, add new marks etc.

Summary #

Although the above approach may not be ideal for everybody, I find it fairly ergonomic, smooth, and covering all the requirements I identified earlier. I’m certain that editors other than Emacs can be set up to allow the same or very similar flow, yet any particular configurations are left for the reader to explore.

February 26, 2025 12:00 AM

February 19, 2025

Manuel Rego: Announcing the Web Engines Hackfest 2025

Igalia WebKit

Igalia is arranging the twelfth annual Web Engines Hackfest, which will be held on Monday 2nd June through Wednesday 4th June. As usual, this is a hybrid event, at Palexco in A Coruña (Galicia, Spain) as well as remotely.

Registration is now open:

Picture of sunset in A Coruña from June 2024 at Riazor beach where the whole Orzán bay can be seen.
Sunset in A Coruña from Riazor beach (June 2024)

The Web Engines Hackfest is an event where folks working on various parts of the web platform gather for a few days to share knowledge and discuss a variety of topics. These topics include web standards, browser engines, JavaScript engines, and all the related technology around them. Last year, we had eight talks (watch them on YouTube) and 15 breakout sessions (read them on GitHub).

A wide range of experts with a diverse set of profiles and skills attend each year, so if you are working on the web platform, this event is a great opportunity to chat with people that are both developing the standards and working on the implementations. We’re really grateful for all the people that regularly join us for this event; you are the ones that make this event something useful and interesting for everyone! 🙏

Really enjoying Web Engines Hackfest by @igalia once again. Recommended for everyone interested in web technology.

— Anne van Kesteren (@annevk@hachyderm.io) June 05, 2024

The breakout sessions are probably the most interesting part of the event. Many different topics are discussed there, from high-level issues like how to build a new feature for the web platform, to lower-level efforts like the new WebKit SDK container. Together with the hallway discussions and impromptu meetings, the Web Engines Hackfest is an invaluable experience.

Talk by Stephanie Stimac about Sustainable Futures in the Web Engines Hackfest 2024

Big shout-out to Igalia for organising the Web Engines Hackfest every year since 2014, as well as the original WebKitGTK+ Hackfest starting in 2009. The event has grown and we’re now close to 100 onsite participants with representation from all major browser vendors. If your organization is interested in helping make this event possible, please contact us regarding our sponsorship options.

See you all in June!

February 19, 2025 07:55 AM

February 18, 2025

Igalia WebKit Team: WebKit Igalia Periodical #14

Igalia WebKit

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

Cross-Port 🐱

Multimedia 🎥

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

Several multimedia-related optimizations landed this week, memory allocations were reduced in the video rendering code path and in the MediaStreamAudioDestinationNode implementation.

On the WebRTC fronts, the GstWebRTC backend has gained support for stats rate limiting support and the send/receive buffer sizes were adjusted.

The GStreamer video frame converter, used to show video frames in the canvas or WebGL, has been fixed to use the right GL context and now supports DMA-BUF video frames, too.

Added support to the eotf additional MIME type parameter when checking for supported multimedia content types. This is required by some streaming sites like YouTube TV.

The GStreamer-based WebCodecs AudioData::copyTo() is now spec compliant, handling all possible format conversions defined in the WebCodecs specification.

Infrastructure 🏗️

The WebKit ports that use CMake as their build system—as is the case for the GTK and WPE ones—now enable C++ library assertions by default, when building against GNU libstdc++ or LLVM's libc++. This adds lightweight runtime checks to a number of C++ library facilities, mostly aimed at detecting out-of-bounds memory access, and does not have a measurable performance impact on typical browsing workloads.

A number of Linux distributions were already enabling these assertions as part of their security hardening efforts (e.g. Fedora or Gentoo) and they do help finding actual bugs. As a matter of fact, a number of issues were fixed before all the WebKit API and layout tests with the assertions enabled and the patch could be merged! Going forward, this will prevent accidentally introducing bugs new due to wrong usage of the C++ standard library.

That’s all for this week!

By Igalia WebKit Team at February 18, 2025 11:34 AM

February 11, 2025

Igalia WebKit Team: WebKit Igalia Periodical #13

Igalia WebKit

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

Cross-Port 🐱

Fixed an assertion crash in the remote Web Inspector when its resources contain an UTF-8 “non-breaking space” character.

Multimedia 🎥

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

Media playback now supports choosing the output audio device on a per element basis, using the setSinkId() API. This also added the support needed for enumerating audio outputs, which is needed by Web applications to obtain the identifiers of the available devices. Typical usage includes allowing the user to choose the audio output used in WebRTC-based conference calls.

For now feature flags ExposeSpeakers, ExposeSpeakersWithoutMicrophone, and PerElementSpeakerSelection need to be enabled for testing.

Set proper playbin flags which are needed to properly use OpenMax on the RaspberryPi.

Graphics 🖼️

Landed a change that adds a visualization for damage rectangles, controlled by the WEBKIT_SHOW_DAMAGE environment variable. This highlights areas damaged during rendering of every frame—as long as damage propagation is enabled.

Screenshot of a web page showing the “Poster Circle” CSS transforms demo, with red rectangles overlapping the areas that have been last re-rendered

Releases 📦️

Stable releases of WebKitGTK 2.46.6 and WPE WebKit 2.46.6 are now available. These come along with the first security advisory of the year (WSA-2025-0001: GTK, WPE): they contain mainly security fixes, and everybody is advised to update.

The unstable release train continues as well, with WebKitGTK 2.47.4 and WPE WebKit 2.47.4 available for testing. These are snapshots of the current development status, and while expected to work there may be rough edges—if you encounter any issue, reports at the WebKit Bugzilla are always welcome.

The recently released libwpe 1.16.1 accidentally introduced an ABI break, which has been corrected in libwpe 1.16.2. There are no other changes, and the latter should be preferred.

That’s all for this week!

By Igalia WebKit Team at February 11, 2025 10:43 AM

February 10, 2025

Manuel Rego: Solving Cross-root ARIA Issues in Shadow DOM

Igalia WebKit

This blog post is to announce that Igalia has gotten a grant from NLnet Foundation to work on solving cross-root ARIA issues in Shadow DOM. My colleague Alice Boxhall, which has been working on sorting out this issue since several years ago, together with support form other igalians is doing the work related to this grant.

The Problem #

Shadow DOM has some issues that prevent it to be used in some situations if you want to have an accessible application. This has been identified by the Web Components Working Group as one of the top priority issues that need to be sorted out.

Briefly speaking, there are mainly two different problems when you want to reference elements for ARIA attributes cross shadow root boundaries.

First issue is that you cannot reference things outside the Shadow DOM. Imagine you have a custom element (#customButton) which contains a native button in its Shadow DOM, and you want to associate the internal button with a label (#label) which is outside in the light DOM.

<label id="label">Label</label>
<custom-button id="customButton">
<template shadowrootmode="open">
<div>foo</div>
<button aria-labelledby="label">Button</button>
<div>bar</div>
</template>
</custom-button>

And the second problem is that you cannot reference things inside a Shadow DOM from the outside. Imagine the opposite situation where you have a custom label (#customLabel) with a native label in its Shadow DOM that you want to reference from a button (#button) in the light DOM.

<custom-label id="customLabel">
<template shadowrootmode="open">
<div>foo</div>
<label>Label</label>
<div>bar</div>
</template>
</custom-label>
<button id="button" aria-labelledby="customLabel">Button</button>

This is a huge issue for web components because they cannot use Shadow DOM, as they would like due to its encapsulation properties, if they want to provide an accessible experience to users. For that reason many of the web components libraries don’t use yet Shadow DOM and have to rely on workarounds or custom polyfills.

If you want to know more on the problem, Alice goes deep on the topic in her blog post How Shadow DOM and accessibility are in conflict.

Prior Art #

The Accessibility Object Model (AOM) effort was started several years ago aiming to solve several issues including the one described before, that had a wider scope and tried to solve many different things including the problems described in this blog post. At that time Alice was at Google and Alex Surkov at Mozilla, both were part of this effort. Coincidentally, they are now at Igalia, which together with Joanmarie Diggs and Valerie Young create a dream team of accessibility experts in our company.

Even when the full problem hasn’t been sorted out yet, there has been some progress with the Element Reflection feature which allows ARIA relationship attributes to be reflected as element references. Whit this users can specify them without the need to assign globally unique ID attributes to each element. This feature has been implemented in Chromium and WebKit by Igalia. So instead of doing something like:

<button id="button" aria-describedby="description">Button</button>
<div id="description">Description of the button.</div>

You could specify it like:

<button id="button">Button</button>
<div id="description">Description of the button.</div>
<script>
button.ariaDescribedByElements = [description];
</script>

Coming back to Shadow DOM, this feature also enables authors to specify ARIA relationships pointing to things outside the Shadow DOM (the first kind of problem described in the previous section), however it doesn’t allow to reference elements inside another Shadow DOM (the second problem). Anyway let’s see an example of how this will solve the first issue:

<label id="label">Label</label>
<custom-button id="customButton"></custom-button>
<script>
const shadowRoot = customButton.attachShadow({mode: "open"});

const foo = document.createElement("div");
foo.textContent = "foo";
shadowRoot.appendChild(foo);

const button = document.createElement("button");
button.textContent = "Button";
/* Here is where we reference the outer label from the button inside the Shadow DOM. */
button.ariaLabelledByElements = [label];
shadowRoot.appendChild(button);

const bar = document.createElement("div");
bar.textContent = "bar";
shadowRoot.appendChild(bar);
</script>

Apart from Element Reflection, which only solves part of the issues, there have been other ideas about how to solve these problems. Initially Cross-root ARIA Delegation proposal by Leo Balter at Salesforce. A different one called Cross-root ARIA Reflection by Westbrook Johnson at Adobe. And finally the Reference Target for Cross-root ARIA proposal by Ben Howell at Microsoft.

Again if you want to learn more about the different nuances of the previous proposals you can revisit Alice’s blog post.

The Proposed Solution: Reference Target #

At this point this is the most promising proposal is the Reference Target one. This proposal allows the web authors to use Shadow DOM and still don’t break the accessibility of their web applications. The proposal is still in flux and it’s currently being prototyped in Chromium and WebKit. Anyway as an example this is the kind of API shape that would solve the second problem described in the initial section, where we reference a label (#actualLabel) inside the Shadow DOM from a button (#button) in the light DOM.

<custom-label id="customLabel">
<template shadowrootmode="open"
shadowrootreferencetarget="actualLabel">

<div>foo</div>
<label id="actualLabel">Label</label>
<div>bar</div>
</template>
</custom-label>
<button id="button" aria-labelledby="customLabel">Button</button>

The Grant #

As part of this grant we’ll work on all the process to get the Reference Target proposal ready to be shipped in the web rendering engines. Some of the tasks that will be done during this project include work in different fronts:

  • Proposal: Help with the work on the proposal identifying issues, missing bits, design solutions, providing improvements, keeping it up to date as the project evolves.
  • Specification: Write the specification text, discuss and review it with the appropriate working groups, improved it based on gathered feedback and implementation experience, resolve issues identified in the standards bodies.
  • Implementation: Prototype implementation in WebKit to verify the proposal, upstream changes to WebKit, fix bugs on the implementation, adapt it to spec modifications.
  • Testing: Analyze current WPT tests, add new ones to increase coverage, validate implementations, keep them up-to-date as things evolve.
  • Outreach: Blog posts explaining the evolution of the project and participation in events with other standards folks to have the proper discussions to move the proposal and specification forward.

NLnet Foundation logo

We’re really grateful that NLnet has trusted us to this project, and we really hope this will allow to fix an outstanding accessibility issue in the web platform that has been around for too many time already. At the same point it’s a bit sad, that the European Union through the NGI funds is the one sponsoring this project, when it will have a very important impact for several big fishes that are part of the Web Components WG.

If you want to follow the evolution of this project, I’d suggest you to follow Alice’s blog where she’ll be updating us about the progress of the different tasks.

February 10, 2025 12:00 AM

February 03, 2025

Igalia WebKit Team: WebKit Igalia Periodical #12

Igalia WebKit

Update on what happened in WebKit in the week from January 27 to February 3.

Cross-Port 🐱

The documentation now has a section on how to use the Web Inspector remotely. This makes information on this topic easier to find, as it was previously scattered around a few different locations.

Jamie continues her Coding Experience work around bringing WebExtensions to the WebKitGTK port. A good part of this involves porting functionality from Objective-C, which only the Apple WebKit ports would use, into C++ code that all ports may use. The latest in this saga was WebExtensionStorageSQLiteStore.

Web Platform 🌐

The experimental support for Invoker Commands has been updated to match latest spec changes.

WPE and WebKitGTK now have support for the Cookie Store API.

Implemented experimental support for the CloseWatcher API.

Multimedia 🎥

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

The GStreamer WebRTC backend can now recycle inactive senders and support for inactive receivers was also improved. With these changes, support for screen sharing over WebRTC is now more reliable.

On the playback front, a bug on the silent video automatic pause optimization was fixed, the root case for certain VP9 videos appearing as empty sometimes was found out to be in GStreamer, and there is effort ongoing to solve racy crashes when flushing MSE streams.

WebKitGTK 🖥️

Support for WebDriver BiDi has been enabled in WebKitGTK as an experimental feature.

That’s all for this week!

By Igalia WebKit Team at February 03, 2025 10:33 PM

January 30, 2025

Vivienne Watermeier: Debugging GStreamer applications using Nushell

Igalia WebKit

Nushell is a new shell (get it?) in development since 2019. Where other shells like bash and zsh treat all data as raw text, nu instead provides a type system for all data flowing through its pipelines, with many commands inspired by functional languages to manipulate that data. The examples on their homepage and in the README.md demonstrate this well, and I recommend taking a quick look if you’re not familiar with the language.

I have been getting familiar with Nu for a few months now, and found it a lot more approachable and user-friendly than traditional shells, and particularly helpful for exploring logs.

Where to find documentation #

I won’t go over all the commands I use in detail, so if anything is ever unclear, have a look at the Command Reference. The most relevant categories for our use case are probably Strings and Filters. From inside nushell, you can also use help some_cmd or some_cmd --help, or help commands for a full table of commands that can be manipulated and searched like any other table in nu. And for debugging a pipeline, describe is a very useful command that describes the type of its input.

Set-up for analyzing GStreamer logs #

First of all, we need some custom commands to parse the raw logs into a nu table. Luckily, nushell provides a parse command for exacly this use case, and we can define this regex to use with it:

let gst_regex: ([
  '(?<time>[0-9.:]+) +'
  '(?<pid>\w+) +'
  '(?<thread>\w+) +'
  '(?<level>\w+) +'
  '(?<category>[\w-]+) +'
  '(?<file>[\w.-]+)?:'
  '(?<line>\w+):'
  '(?<function>[\w()~-]+)?:'
  '(?<object><[^>]*>)? +'
  '(?<msg>.*)$'
] | str join)

(I use a simple pipeline here to split the string over multiple lines for better readability, it just concatenates the list elements.)

Lets run a simple pipeline to get some logs to play around with:
GST_DEBUG=*:DEBUG GST_DEBUG_FILE=sample.log gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

For parsing the file, we need to be careful to remove any ansi escapes, and split the input into lines. On top of that, we will also store the result to a variable for ease of use:
let gst_log = open sample.log | ansi strip | lines | parse --regex $gst_regex

You can also define a custom command for this, which would look something like:

def "from gst logs" []: string -> table {
  $in | ansi strip | lines | parse --regex ([
    '(?<time>[0-9.:]+) +'
    '(?<pid>\w+) +'
    '(?<thread>\w+) +'
    '(?<level>\w+) +'
    '(?<category>[\w-]+) +'
    '(?<file>[\w.-]+)?:'
    '(?<line>\w+):'
    '(?<function>[\w()~-]+)?:'
    '(?<object><[^>]*>)? +'
    '(?<msg>.*)$'
  ] | str join)
}

Define it directly on the command line, or place it in your configuration files. Either way, use the command like this:
let gst_log = open sample.log | from gst logs

Some basic commands for working with the parsed data #

If you take a look at a few lines of the table, it should look something like this:
$gst_log | skip 10 | take 10

╭────┬────────────────────┬───────┬────────────┬────────┬─────────────────────┬────────────────┬───────┬──────────────────────────────┬─────────────┬───────────────────────────────────────────────╮
│  # │        time        │  pid  │   thread   │ level  │      category       │      file      │ line  │           function           │   object    │                      msg                      │
├────┼────────────────────┼───────┼────────────┼────────┼─────────────────────┼────────────────┼───────┼──────────────────────────────┼─────────────┼───────────────────────────────────────────────┤
│  0 │ 0:00:00.003607288  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_ELEMENT_PADS    │ gstelement.c   │ 315   │ gst_element_base_class_init  │             │ type GstBin : factory (nil)                   │
│  1 │ 0:00:00.003927025  │ 5161  │ 0x1ceba80  │ INFO   │ GST_INIT            │ gstcontext.c   │ 86    │ _priv_gst_context_initialize │             │ init contexts                                 │
│  2 │ 0:00:00.004117399  │ 5161  │ 0x1ceba80  │ INFO   │ GST_PLUGIN_LOADING  │ gstplugin.c    │ 328   │ _priv_gst_plugin_initialize  │             │ registering 0 static plugins                  │
│  3 │ 0:00:00.004164980  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_REGISTRY        │ gstregistry.c  │ 592   │ gst_registry_add_feature     │ <registry0> │ adding feature 0x1d08c70 (bin)                │
│  4 │ 0:00:00.004176720  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_REFCOUNTING     │ gstobject.c    │ 710   │ gst_object_set_parent        │ <bin>       │ set parent (ref and sink)                     │
│  5 │ 0:00:00.004197201  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_ELEMENT_PADS    │ gstelement.c   │ 315   │ gst_element_base_class_init  │             │ type GstPipeline : factory 0x1d09310          │
│  6 │ 0:00:00.004243022  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_REGISTRY        │ gstregistry.c  │ 592   │ gst_registry_add_feature     │ <registry0> │ adding feature 0x1d09310 (pipeline)           │
│  7 │ 0:00:00.004254252  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_REFCOUNTING     │ gstobject.c    │ 710   │ gst_object_set_parent        │ <pipeline>  │ set parent (ref and sink)                     │
│  8 │ 0:00:00.004265272  │ 5161  │ 0x1ceba80  │ INFO   │ GST_PLUGIN_LOADING  │ gstplugin.c    │ 236   │ gst_plugin_register_static   │             │ registered static plugin "staticelements"     │
│  9 │ 0:00:00.004276813  │ 5161  │ 0x1ceba80  │ DEBUG  │ GST_REGISTRY        │ gstregistry.c  │ 476   │ gst_registry_add_plugin      │ <registry0> │ adding plugin 0x1d084d0 for filename "(NULL)" │
╰────┴────────────────────┴───────┴────────────┴────────┴─────────────────────┴────────────────┴───────┴──────────────────────────────┴─────────────┴───────────────────────────────────────────────╯

skip and take do exactly what it says on the tin - removing the first N rows, and showing only the first N rows, respectively. I use them here to keep the examples short.


To ignore columns, use reject:
$gst_log | skip 10 | take 5 | reject time pid thread

╭───┬───────┬────────────────────┬───────────────┬──────┬──────────────────────────────┬─────────────┬────────────────────────────────╮
│ # │ level │      category      │     file      │ line │           function           │   object    │              msg               │
├───┼───────┼────────────────────┼───────────────┼──────┼──────────────────────────────┼─────────────┼────────────────────────────────┤
│ 0 │ DEBUG │ GST_ELEMENT_PADS   │ gstelement.c  │ 315  │ gst_element_base_class_init  │             │ type GstBin : factory (nil)    │
│ 1 │ INFO  │ GST_INIT           │ gstcontext.c  │ 86   │ _priv_gst_context_initialize │             │ init contexts                  │
│ 2 │ INFO  │ GST_PLUGIN_LOADING │ gstplugin.c   │ 328  │ _priv_gst_plugin_initialize  │             │ registering 0 static plugins   │
│ 3 │ DEBUG │ GST_REGISTRY       │ gstregistry.c │ 592  │ gst_registry_add_feature     │ <registry0> │ adding feature 0x1d08c70 (bin) │
│ 4 │ DEBUG │ GST_REFCOUNTING    │ gstobject.c   │ 710  │ gst_object_set_parent        │ <bin>       │ set parent (ref and sink)      │
╰───┴───────┴────────────────────┴───────────────┴──────┴──────────────────────────────┴─────────────┴────────────────────────────────╯

Or its counterpart, select, which is also useful for reordering columns:
$gst_log | skip 10 | take 5 | select msg category level

╭───┬────────────────────────────────┬────────────────────┬───────╮
│ # │              msg               │      category      │ level │
├───┼────────────────────────────────┼────────────────────┼───────┤
│ 0 │ type GstBin : factory (nil)    │ GST_ELEMENT_PADS   │ DEBUG │
│ 1 │ init contexts                  │ GST_INIT           │ INFO  │
│ 2 │ registering 0 static plugins   │ GST_PLUGIN_LOADING │ INFO  │
│ 3 │ adding feature 0x1d08c70 (bin) │ GST_REGISTRY       │ DEBUG │
│ 4 │ set parent (ref and sink)      │ GST_REFCOUNTING    │ DEBUG │
╰───┴────────────────────────────────┴────────────────────┴───────╯

Meanwhile, get returns a single column as a list, which can for example be used with uniq to get a list of all objects in the log:
$gst_log | get object | uniq | take 5

╭───┬──────────────╮
│ 0 │              │
│ 1 │ <registry0>  │
│ 2 │ <bin>        │
│ 3 │ <pipeline>   │
│ 4 │ <capsfilter> │
╰───┴──────────────╯

Filtering rows by different criteria works really well with where.
$gst_log | where thread in ['0x7f467c000b90' '0x232fefa0'] and category == GST_STATES | take 5

╭────┬────────────────────┬───────┬─────────────────┬────────┬─────────────┬──────────┬──────┬───────────────────────┬──────────────────┬───────────────────────────────────────────────────────────╮
│  # │        time        │  pid  │     thread      │ level  │  category   │   file   │ line │       function        │      object      │                            msg                            │
├────┼────────────────────┼───────┼─────────────────┼────────┼─────────────┼──────────┼──────┼───────────────────────┼──────────────────┼───────────────────────────────────────────────────────────┤
│  0 │ 0:00:01.318390245  │ 5158  │ 0x7f467c000b90  │ DEBUG  │ GST_STATES  │ gstbin.c │ 1957 │ bin_element_is_sink   │ <autovideosink0> │ child autovideosink0-actual-sink-xvimage is sink          │
│  1 │ 0:00:01.318523898  │ 5158  │ 0x7f467c000b90  │ DEBUG  │ GST_STATES  │ gstbin.c │ 1957 │ bin_element_is_sink   │ <pipeline0>      │ child autovideosink0 is sink                              │
│  2 │ 0:00:01.318558109  │ 5158  │ 0x7f467c000b90  │ DEBUG  │ GST_STATES  │ gstbin.c │ 1957 │ bin_element_is_sink   │ <pipeline0>      │ child videoconvert0 is not sink                           │
│  3 │ 0:00:01.318569169  │ 5158  │ 0x7f467c000b90  │ DEBUG  │ GST_STATES  │ gstbin.c │ 1957 │ bin_element_is_sink   │ <pipeline0>      │ child videotestsrc0 is not sink                           │
│  4 │ 0:00:01.338298058  │ 5158  │ 0x7f467c000b90  │ INFO   │ GST_STATES  │ gstbin.c │ 3408 │ bin_handle_async_done │ <autovideosink0> │ committing state from READY to PAUSED, old pending PAUSED │
╰────┴────────────────────┴───────┴─────────────────┴────────┴─────────────┴──────────┴──────┴───────────────────────┴──────────────────┴───────────────────────────────────────────────────────────╯

It provides special shorthands called row conditions - have a look at the reference for more examples.


Of course, get and where can also be combined:
$gst_log | get category | uniq | where $it starts-with GST | take 5

╭───┬────────────────────╮
│ 0 │ GST_REGISTRY       │
│ 1 │ GST_INIT           │
│ 2 │ GST_MEMORY         │
│ 3 │ GST_ELEMENT_PADS   │
│ 4 │ GST_PLUGIN_LOADING │
╰───┴────────────────────╯

And if you need to merge multiple logs, I recommend using sort-by time. This could look like
let gst_log = (open sample.log) + (open other.log) | from gst logs | sort-by time

Interactively exploring logs #

While there are many other useful commands, there is one more command I find incredbly useful: explore. It is essentially the nushell equivalent to less, and while it is still quite rough around the edges, I’ve been using it all the time, mostly for its interactive REPL.

First, just pipe the parsed log into explore:
$gst_log | explore

Now, using the :try command opens its REPL. Enter any pipeline at the top, and you will be able to explore its output below: Example of using the explore command and its REPL. The top of the window shows the current command, with the resulting data underneath as a table.

Switch between the command line and the pager using Tab, and while focused on the pager, search forwards or backwards using / and ?, or enter :help for explanations. Also have a look at the documentation on explore in the Nushell Book.

January 30, 2025 12:00 AM

January 27, 2025

Igalia WebKit Team: WebKit Igalia Periodical #11

Igalia WebKit

Update on what happened in WebKit in the week from January 20 to January 27.

Cross-Port 🐱

GLib 2.70 will be required starting with the upcoming 2.48 stable releases. This made it possible to remove some code that is no longer needed.

Fixed unlimited memory consumption in case of playing regular video playback and using web inspector.

Speed up reading of large messages sent by the web inspector.

Web Platform 🌐

Implemented support for dialog.requestClose().

Multimedia 🎥

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

Fixed the assertion error "pipeline and player states are not synchronized" related to muted video playback in the presence of scroll. Work is ongoing regarding other bugs reproduced with the same video, some of them related to scroll and some likely indepedent.

Fixed lost initial audio samples played using WebAudio on 32-bit Raspberry Pi devices, by preventing the OpenMAX subsystem to enter standby mode.

Graphics 🖼️

Landed a change that fixes damage propagation of 3D-transformed layers.

Fixed a regression visiting any web page making use of accelerated ImageBuffers (e.g. canvas) when CPU rendering is used. We were unconditionally creating OpenGL fences, even in CPU rendering mode, and tried to wait for completion in a worker thread, that had no OpenGL context (due to CPU rendering). This is an illegal operation in EGL and fired an assertion, crashing the WebProcess.

Releases 📦️

Despite the work on the WPE Platform API, we continue to maintain the “classic” stack based on libwpe. Thus, we have released libwpe 1.16.1 with the small—but important—addition of support for representing analog button inputs for devices capable of reporting varying amounts of pressure.

That’s all for this week!

By Igalia WebKit Team at January 27, 2025 05:56 PM

January 20, 2025

Igalia WebKit Team: WebKit Igalia Periodical #10

Igalia WebKit

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

Cross-Port 🐱

JavaScriptCore 🐟

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

The JavaScriptCore GLib API has gained support for creating Promise objects. This allows integrating asynchronous functionality more ergonomically when interfacing between native code and JavaScript.

Graphics 🖼️

Elements with outlines inside scrolling containers now render their outlines properly.

Landed a change that adds multiple fixes to the damage propagation functionality in scenarios such as:

  • Layers with custom transforms.

  • Pages with custom viewport scale.

  • Dynamic layer size changes.

  • Scrollbar layers.

Landed a change that improves damage propagation in terms of animations handling.

Landed a change that prevents any kind of damage propagation when the feature is disabled at runtime using its corresponding flag. Before that, even though the functionality was runtime-disabled some memory usage and unneeded calculations were being done.

WPE WebKit 📟

WPE Platform API 🧩

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

Drag gesture threshold, and key repeat delay/interval are now handled through the WPESettings API instead of using hardcoded values. While defaults typically work well, being able to tweak them for certain setups without rebuilding WPE is a welcome addition.

Sylvia has also improved the WPE Platform DRM/KMS backend to pick the default output device scaling factor using WPESettings.

Infrastructure 🏗️

libsoup has been added to Google's OSS-Fuzz program to help find security issues.

That’s all for this week!

By Igalia WebKit Team at January 20, 2025 10:32 PM