February 12, 2026

Announcing Interop 2026

Surfin’ Safari

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

Introducing Interop 2026

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

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

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

Focus Areas for 2026

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

Anchor positioning

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

Advanced attr()

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

Container style queries

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

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

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

contrast-color()

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

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

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

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

Custom Highlights

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

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

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

Dialog and popover additions

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

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

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

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

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

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

dialog:open {
  animation: slideIn 0.3s;
}

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

Fetch uploads and ranges

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

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

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

Enhanced FormData support improves multipart uploads and responses.

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

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

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

getAllRecords() for IndexedDB

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

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

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

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

JSPI for Wasm

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

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

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

Media pseudo-classes

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

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

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

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

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

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

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

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

Navigation API

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

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

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

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

Scoped custom element registries

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

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

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

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

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

Scroll-driven Animations

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

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

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

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

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

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

Scroll Snap

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

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

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

shape()

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

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

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

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

View transitions

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

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

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

Web Compat

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

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

WebRTC

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

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

WebTransport

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

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

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

CSS Zoom

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

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

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

Investigation Efforts: A Look Ahead

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

Accessibility testing

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

JPEG XL

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

Mobile testing

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

WebVTT

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

A more interoperable web

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

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

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

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

February 12, 2026 05:00 PM

Release Notes for Safari Technology Preview 237

Surfin’ Safari

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

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

Accessibility

Resolved Issues

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

CSS

New Features

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

Resolved Issues

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

DOM

Resolved Issues

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

HTML

New Features

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

Networking

Resolved Issues

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

Rendering

Resolved Issues

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

SVG

New Features

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

Resolved Issues

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

Web API

New Features

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

Resolved Issues

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

Web Authentication

Resolved Issues

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

Web Extensions

New Features

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

Web Inspector

New Features

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

Resolved Issues

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

WebRTC

New Features

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

February 12, 2026 04:59 PM

February 11, 2026

WebKit features for Safari 26.3

Surfin’ Safari

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

Video in visionOS

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

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

Zstandard

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

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

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

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

Navigation API

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

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

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

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

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

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

Bug fixes and more

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

CSS

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

DOM

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

Media

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

Rendering

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

Safe Browsing

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

Feedback

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

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

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

February 11, 2026 06:00 PM

February 09, 2026

Igalia WebKit Team: WebKit Igalia Periodical #56

Igalia WebKit

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

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

Cross-Port 🐱

Graphics 🖼️

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

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

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

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

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

WebKitGTK 🖥️

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

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

WPE WebKit 📟

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

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

Releases 📦️

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

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

Community & Events 🤝

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

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

The videos for the talks are already available, too.

That’s all for this week!

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

February 06, 2026

Interop 2025: A year of convergence

Surfin’ Safari

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

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

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

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

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

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

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

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

February 06, 2026 05:45 PM

February 03, 2026

Release Notes for Safari Technology Preview 236

Surfin’ Safari

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

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

CSS

Resolved Issues

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

Forms

Resolved Issues

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

HTML

New Features

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

Resolved Issues

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

Images

Resolved Issues

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

Media

Resolved Issues

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

Rendering

Resolved Issues

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

SVG

Resolved Issues

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

Web API

Resolved Issues

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

WebRTC

Resolved Issues

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

February 03, 2026 12:08 AM

February 02, 2026

Igalia WebKit Team: WebKit Igalia Periodical #55

Igalia WebKit

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

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

Cross-Port 🐱

Graphics 🖼️

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

That’s all for this week!

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

January 26, 2026

Igalia WebKit Team: WebKit Igalia Periodical #54

Igalia WebKit

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

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

Cross-Port 🐱

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

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

Graphics 🖼️

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

WPE WebKit 📟

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

WPE Platform API 🧩

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

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

Releases 📦️

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

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

That’s all for this week!

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

Enrique Ocaña: Igalia Multimedia contributions in 2025

Igalia WebKit

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

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

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

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

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

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

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

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

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

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

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

By eocanha at January 26, 2026 09:34 AM

January 22, 2026

When will CSS Grid Lanes arrive? How long until we can use it?

Surfin’ Safari

Anytime an exciting new web technology starts to land in browsers, developers want to know “when in the world am I going to be able to use this?”

Currently, the finalized syntax for Grid Lanes is available in Safari Technology Preview. Edge, Chrome and Firefox have all made significant progress on their implementations, so it’s going to arrive sooner than you think.

Plus, you can start using it as soon as you want with progressive enhancement. This article will show you how.

Web page of content items — each item is a group with an image, headline and text inside a card with rounded corners. The page is in a browser window on the left, with a layout created with Grid Lanes. The items have different aspect ratios, and nestle together into columns, where all of the content for each item can be seen. On the right, is another browser window with the same content — this time laid out in columns again, but also rows. In order for the items to fit into the rows, the photos have all been cropped into squares, the headlines are truncated to fit onto one line, and the teaser descriptions are all chopped to only be three paragraphs long. All the rest of the content is simply missing, not shown in order to force each item to be the same shape and size as the others. And fit on the grid.
Deliver the layout on the left using Grid Lanes to browsers with support, while providing a fallback for other browsers.

(If you haven’t heard of Grid Lanes yet, it’s a new tool for layout that makes it easy to create masonry-style layouts in CSS alone. Read Introducing CSS Grid Lanes to learn all about it. And read New Safari developer tools provide insight into CSS Grid Lanes to learn about our new developer tooling that makes using Grid Lanes it even easier.)

Current status of implementations

Where are browsers in the process of getting ready to ship support for Grid Lanes? Let’s look at the progress that’s been made over the last seven years.

Firefox was first

It’s the team that was at Mozilla in 2019-2020 who wrote the original CSS Working Group Editor’s Draft for Grid level 3, proposing concrete ideas for how masonry-style layouts would work in CSS. The feature shipped in Firefox Nightly in very early 2020. Some of the syntax has since changed, but under the hood, the way this new layout feature relies on and expands CSS Grid is basically the same, which means much of the heavy lifting for implementing it in the Gecko layout engine is underway.

Firefox does need to update their implementation (including updating to the new syntax and adding the new flow-tolerance property, among other things) but if you want to try it out in Firefox today, you can enter about:config in the URL bar, search for “masonry” and set the flag to true — or use Firefox Nightly where it’s already on by default. (At the moment, remember to use the original grid-template-*: masonry syntax to trigger this layout, instead of display: grid-lanes.)

Safari picked up the pace

In 2022, Safari’s WebKit team picked up where Mozilla left off in 2020, and started implementing the same original proposal for CSS Grid Layout Level 3. We also restarted the discussion inside the CSS Working Group, hoping to advance the original Editor’s Draft to a point where it was mature enough that browsers could feel confident shipping.

The WebKit implementation was enabled on-by-default in Safari Technology Preview 163 in February 2023. It’s been updated continuously as the CSS specification has changed.

You can use Safari Technology Preview today to try out the official web standard, make demos using display: grid-lanes, and learn how it works. Keep an eye on the Safari Release notes to see when it ships in Safari beta.

Screenshot of webpage for CSS Grid Layout Module level 3 specification.
Grid Lanes is defined in CSS Grid Layout Module Level 3.

Chrome & Edge are on board, too

A variation for how masonry layouts could work in CSS landed in Chrome and Edge 140 behind a flag in July 2025. Rather than implementing the same syntax as Safari and Firefox, Chromium experimented with an alternative proposal. This drove debates in the CSSWG about how exactly this feature should work and what its syntax should be. With key syntax decisions now finalized, Chromium engineers at Edge are updating their implementation. Keep an eye on the Chrome Status issue for the latest news.

Bottom line — all the major browser engines are making progress. Now is a great time to learn how Grid Lanes works. And consider if, when and how you could start using it.

Yes, you can start using it kinda soon-ish

Great developers are always mindful of users whose browsers don’t have support. Not only does it take time for all browsers to ship new features, it takes time for all users to update. But this does not mean you have to wait multiple years before using new technologies. It just means you just have to be savvy.

You can progressively enhance your code to create masonry-style layouts, and support older browsers, both at the same time. How? As always, there are multiple options. Which choice is best for you depends on your use case, your team, and your code base. Let’s go through three different approaches you could use:

Option 1: Polyfill — use a JavaScript library as a backup for Grid Lanes

One common trick for using a new CSS feature when it’s still not available in all browsers is to use a polyfill — i.e.: use JavaScript to fill in the missing functionality.

Lucky for you, there are already tried and true JS libraries out in the world for creating masonry layouts. Masonry.js is a popular one. Perhaps you are using it now. You can keep using it by itself, and ignore Grid Lanes. Or you can switch to using the JS library as a polyfill.

The approach here is to go ahead and use CSS Grid Lanes to handle the layout in CSS alone — in browsers with support for Grid Lanes, even if that’s still only preview browsers. At the same time, architect your code to also work with a JavaScript library. Test in a browser without support for Grid Lanes to make sure the JS layout works.

The key is to structure your code with conditionals, so browsers with Grid Lanes support use CSS, while those without use JS. In your CSS, use Feature Queries to ensure the right CSS is used under the right conditions. In JavaScript, use if statements.

For example, you can structure your CSS like this:

/* Native Grid Lanes for supporting browsers */
@supports (display: grid-lanes) {
  .grid {
    display: grid-lanes;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1lh;
  }
}

/* Additional CSS needed only for browsers without Grid Lanes */
@supports not (display: grid-lanes) {
  .grid-item {
    margin: 1lh;
  }
  /* Perhaps also include a fallback layout in case JS doesn't run */
}

Then in JavaScript, you can check to see whether or not Grid Lanes is supported. If not, load the file. And then start using Masonry JS (or another library), according to its documentation.

// Check if CSS Grid Lanes is NOT supported
if (!CSS.supports('display', 'grid-lanes')) {

    // Dynamically load masonry.js
    const script = document.createElement('script');
    script.src = 'https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js';
    script.onload = function() {

    // Use Masonry.js after the script loads
    new Masonry('.grid', {
        itemSelector: '.grid-item',
        columnWidth: 200,
    });
    };
    document.head.appendChild(script);
}

It’s important to conditionally load the JS library only if the browser doesn’t support Grid Lanes. There’s no reason to have all users download and run the JS file when some percent don’t need it. That percentage might be small today (even zero), but over time it will grow to 100%.

Save future you the task of having to change your code later. Structure it today so in a future when all users have Grid Lanes, no one has to do anything. Users get the best experience, even if no one on your team ever cleans out the old code.

With this technique, browsers with Grid Lanes support use pure CSS, while older browsers load and use JavaScript. By switching to using the JavaScript library a polyfill, not as the primary layout mechanism, increasing numbers of users will get the benefit of a faster and more robust layout sooner.

Of course, maybe this won’t work for your project. Maybe it’s too complicated to architect your HTML and surrounding layout to work for both Grid Lanes and a masonry library at the same time. So what are the other options?

Option 2: Don’t use Grid Lanes — use another layout in CSS instead

Of course, you might be screaming “it’s too early to use Grid Lanes!” There is always the option of simply waiting to use a new technology. Perhaps another layout mode in CSS like Grid level 1, Flexbox or Multicolumn are good enough for your needs. And you can hold off using any tool for accomplishing a masonry-style layout until you feel more confident about Grid Lanes.

CSS Multicolumn is an interesting option that you might not be familiar with. It shipped in browsers decades ago (before Can I Use kept track). With origins that date back to the 1990s, Multicolumn suffered from the fate of most early CSS — the specification was not detailed enough, and that resulted in a lot of differences between browser implementations. This frustrated developers, resulting in Multicolumn falling out of favor.

In more recent years, Multicolumn level 1 has gotten a lot of love, and the specification now contains far more detail. This has helped browsers squash interop bugs. There’s even a Multicolumn level 2 specification bringing new features in the future. There’s still more work to do to create true interoperability, but it’s worth reconsidering Multicolumn to see if can solve your use case today.

Multicolumn and Grid Lanes can result in very similar-looking layouts. They are fundamentally different, however, in the way content flows. These differences impact the order of what comes into focus when tabbing through content, readability / scanability, and user experience. So consider carefully.

graphic comparing Grid Lanes layout vs Multicolumn. Two browser windows side by side. The layouts are basically the same visually, but Grid Lanes flows the content across the page, keeping 20 items above the bottom edge of the viewport. Multicolumn pours the items 1 to 5 down the first column, then starts the next column with the 16th item, showing 16-19... etc. Where is item 6-15? Not on screen.

Try out the demos we created to compare how Multicolumn and Grid Lanes work. Select different layouts from the dropdown menus, and turn on item numbers to emphasize the difference.

Option 3: Use Grid Lanes — along with a fallback layout in CSS

While “don’t use Grid Lanes” is always an option, perhaps the best approach is to write your code so that Grid Lanes is used when supported, and another layout mode in CSS is used as the fallback. This avoids using JavaScript for layout, while still delivering the newer layout to the users who do have support.

For example, let’s imagine we want to use Grid Lanes, and leverage CSS Grid (level 1) when Grid Lanes isn’t supported. To make the original Grid layout work, can use CSS to force all the items be the same aspect ratio by cropping images and truncating text.

To do this, we can apply layout to the container using the display property — twice. First we’ll declare display: grid, then we’ll immediately declare display: grid-lanes.

.grid-container {
  display: grid;
  display: grid-lanes; /* will override grid in browsers that support */
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1lh;
}

In browsers that support Grid Lanes, the second declaration will override the first. The display: grid rule will be ignored. And the layout will use Grid Lanes, resulting in a layout that packs content of different aspect ratios into a set of columns.

In browsers that do not support Grid Lanes, the browser will ignore the second declaration. It sees display: grid-lanes and goes “what? That’s not a thing. You must have misspelled something. Ignore!” This leaves grid as the layout that’s applied. The content will be laid out into clear rows as well as columns.

This is a tried and true technique that’s been used by developers for over two decades — relying on the fact that CSS just ignores anything it doesn’t understand. It does not throw an error message. It does not stop parsing. It just ignores that line of code and moves along.

We can also use a Feature Query to write code that only gets applied in browsers without support for Grid Lanes. Let’s use the aspect-ratio property to force all images into the same aspect ratio. And use object-fit: cover to crop those images to fit in the box, instead of letting them be squished.

/* Additional CSS for browsers without Grid Lanes support */
@supports not (display: grid-lanes) {
  .grid-item {
    img {
      aspect-ratio: 1; /* resize every image into a square */
      object-fit: cover; /* crop, don't squish the image */
    }
    h3 {
      white-space: nowrap; /* don't wrap the headline */
      overflow: hidden; /* crop the extra */
      text-overflow: ellipsis; /* add an ellipsis if it overflows */
    }
    p {
      display: -webkit-box; /* current practice in all browsers */
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 3; /* clamps to this many lines of text */
      overflow: hidden;
    }
  }
}

We can force our headline to not wrap with white-space: nowrap. Once the headline is on one line, we can hide whatever doesn’t fit with overflow: hidden. Then, text-overflow: ellipsis adds “…” to the end of what’s visible.

When we want to truncate multi-line text to a specific number of lines, we can use the -webkit-line-clamp technique. While originally invented by the WebKit team long ago when prefixes were the best practice for browsers rolling out new ideas, today -webkit-box, -webkit-box-orient and -webkit-line-clamp are supported by all browsers. (No browser has shipped a replacement yet because a complete web standard for defining such a tool is still under debate.)

This is the exact same image from the opening of the article. Web page of content items — each item is a group with an image, headline and text inside a card with rounded corners. The page is in a browser window on the left, with a layout created with Grid Lanes. The items have different aspect ratios, and nestle together into columns, where all of the content for each item can be seen. On the right, is another browser window with the same content — this time laid out in columns again, but also rows. In order for the items to fit into the rows, the photos have all been cropped into squares, the headlines are truncated to fit onto one line, and the teaser descriptions are all chopped to only be three paragraphs long. All the rest of the content is simply missing, not shown in order to force each item to be the same shape and size as the others. And fit on the grid.

This approach results in a masonry-style waterfall layout being delivered to the browsers that support Grid Lanes, while a more traditional layout of equal-sized boxes are delivered using CSS Grid level 1 to browsers that don’t yet support Grid Lanes.

It’s up to you

It’s totally up to you how you want to handle the fallback for a lack of support for Grid Lanes, but you definitely have options. This is one of the benefits of writing CSS, and not just using a 3rd-party utility framework that abstracts all the flexibility away. Progressive enhancement techniques bring the future into the present, and let you start using Grid Lanes far sooner!

Learn more about Grid Lanes

This is our third article in a series about Grid Lanes. The first introduces CSS Grid Lanes, explaining what it is and how to use it (and yes, it can be used “in the other direction”). The second article shows off our new developer tools and explains why they are particularly helpful for setting flow tolerance. Also, check out our demos in Safari Technology Preview. And be sure to come back to webkit.org soon for more articles about Grid Lanes.

  1. Introducing CSS Grid Lanes
  2. New Safari developer tools provide insight into CSS Grid Lanes
  3. Demos of Grid Lanes

January 22, 2026 10:00 AM

January 19, 2026

Igalia WebKit Team: WebKit Igalia Periodical #53

Igalia WebKit

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

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

Cross-Port 🐱

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

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

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

JavaScriptCore 🐟

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

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

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

Graphics 🖼️

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

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

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

WPE WebKit 📟

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

WPE Platform API 🧩

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

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

That’s all for this week!

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

January 14, 2026

New Safari developer tools provide insight into CSS Grid Lanes

Surfin’ Safari

You might have heard recently that Safari Technology Preview 234 landed the final plan for supporting masonry-style layouts in CSS. It’s called Grid Lanes.

web browser showing a 6-column layout of photos of various aspect ratios, packed vertically
Try out all our demos of CSS Grid Lanes today in Safari Technology Preview.

CSS Grid Lanes adds a whole new capability to CSS Grid. It lets you line up content in either columns or rows — and not both.

This layout pattern allows content of various aspect ratios to pack together. No longer do you need to truncate content artificially to make it fit. Plus, the content that’s earlier in the HTML gets grouped together towards the start of the container. If new items get lazy loaded, they appear at the end without reshuffling what’s already on screen.

It can be tricky to understand the content flow pattern as you are learning Grid Lanes. The content is not flowing down the first column to the very bottom of the container, and then back up to the top of the second column. (If you want that pattern, use CSS Multicolumn or Flexbox.)

With Grid Lanes, the content flows perpendicular to the layout shape you created. When you define columns, the content flows back and forth across those columns, just like to how it would if rows existed. If you define rows, the content will flow up and down through the rows — in the column direction, as if columns were there.

diagram showing how for waterfall layout there are columns, while content flows side to side. And for brick, the content is laid out in rows, while it the order flows up and down.

Having a way to see the order of items can make it easier to understand this content flow. Introducing the CSS Grid Lanes Inspector in Safari. It’s just the regular Grid Inspector, now with more features.

Grid Lanes photo demo in Safari, with Web Inspector open to the Layout panel, and all the tools for the Grid Inspector turned on. Grid lines are marked with dotted lines. Columns are labeled with numbers and sizes. And each photo is marked with a label like Item 1 — which makes it clear the order of content in the layout.

Safari’s Grid Inspector already reveals the grid lines for Grid Lanes, and labels track sizes, line numbers, line names, and area names. Now it has a new feature — “Order Numbers”.

By turning on the order numbers in the example above, we can clearly see how Item 1, 2, 3, and 4 flow across the columns, as if there were a row. Then Item 5 is in the middle right, followed by Item 6 on the far right, and so on.

You might be tempted to believe the content order doesn’t matter. With pages like this photo gallery — most users will have no idea how the photos are ordered in the HTML. But for many users, the content order has a big impact on their experience. You should always consider what it’s like to tab through content — watching one item after another sequentially come into focus. Consider what it’s like to listen to the site through a screenreader while navigating by touch or keyboard. With Grid Lanes, you can adjust flow-tolerance to reduce the jumping around and put items where people expect.

To know which value for flow tolerance to choose, it really helps to quickly see the order of items. That makes it immediately clear how your CSS impacts the result.

Order Numbers in the Grid Inspector is an extension of a feature Safari’s Flexbox Inspector has had since Safari 16.0 — marking the order of Flex items. Seeing content order is also helpful when using the order property in Flexbox.

Web browser showing photo layout — this time a Flexbox layout. The Web Inspector is open to the Layout tab, and the Flexbox Inspector is enabled. The lines of the layout are marked with dotted lines... and each item is labeled with its order.

Order Numbers in Safari’s Grid Inspector works for CSS Grid and Subgrid, as well as Grid Lanes.

Try out Safari’s layout tooling

The Grid and Flexbox layout inspectors might seem similar across browsers, but the team behind Safari’s Web Inspector has taken the time to finely polish the details. In both the Grid and Flexbox Inspectors, you can simultaneously activate as many overlays as you want. No limits. And no janky scrolling due to performance struggles.

Safari’s Flexbox Inspector visually distinguishes between excess free space and Flex gaps, since knowing which is which can solve confusion. It shows the boundaries of items, revealing how they are distributed both on the main axis and the cross axis of Flexbox containers. And it lists all the Flexbox containers, making it easier to understand what’s happening overall.

Our Grid Inspector has a simple and clear interface, making it easy to understand the options. It lists all of the Grid containers — and you can show the overlay for every single one at the same time. The overlays don’t disappear when you scroll. And of course, you can change the default colors of the overlays, to best contrast with your site content.

And Safari’s Grid and Flexbox Inspectors are the only browser devtools that label content order. We hope seeing the order of content in Grid Lanes helps you understand it more thoroughly and enjoy using this powerful new layout mechanism.

Try out Order Numbers

Order Numbers in Safari’s Grid Inspector shipped today in Safari Technology Preview 235. Let us know what you think. There’s still time to polish the details to make the most helpful tool possible. You can ping Jen Simmons on Bluesky or Mastodon with links, comments and ideas.

For more

Note: Learn more about Web Inspector from the Web Inspector Reference documentation.

January 14, 2026 10:45 PM

Release Notes for Safari Technology Preview 235

Surfin’ Safari

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

This release includes WebKit changes between: 304072@main…305083@main.

CSS

New Features

  • Added support for parsing and evaluating name-only @container queries that have no conditions, allowing named containers to match without explicit constraints. (304388@main) (164648718)
  • Added support for math-depth. (305010@main) (167332590)

Resolved Issues

  • Fixed an issue where display: list-item was incorrectly supported on fieldset. (304102@main) (95638460)
  • Fixed baseline handling for table cells when cell has no inflow children. (304477@main) (160774504)
  • Fixed -webkit-line-clamp so that it no longer propagates into inline-block children. (304956@main) (164488778)
  • Fixed an issue where nested identical CSS filter effects were not rendered. (304143@main) (165163823)
  • Fixed ::first-line pseudo-element to always use inline display to match the CSS Display specification. (304096@main) (166068698)
  • Fixed incorrect text selection ranges for truncated right-to-left content. (304821@main) (166944754)

Deprecations

  • Removed the FontFaceSet constructor from the CSS Font Loading API as it was deemed unnecessary, aligning with the CSSWG resolution. (304912@main) (132031306)

Canvas

Resolved Issues

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

Clipboard

Resolved Issues

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

Editing

Resolved Issues

  • Fixed incorrect text selection when dragging across pseudo elements. (304719@main) (142905243)
  • Fixed an issue where focusing a hidden editable element would incorrectly display the text cursor and selection at full opacity. (304160@main) (165489471)

Encoding

Resolved Issues

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

Forms

Resolved Issues

  • Fixed an issue where input fields did not display user input while typing . (304311@main) (163613957)

HTML

Resolved Issues

  • Fixed HTMLImageElement.currentSrc to return an empty string for <img src=""> instead of resolving to the document base URL. (304982@main) (167229274)

JavaScript

Resolved Issues

  • Fixed %TypedArray%.prototype.includes to correctly check that the index is less than the array length, aligning its behavior with ECMA-262 (304485@main). (166578170)
  • Fixed %TypedArray%.prototype.includes to correctly check that the index is less than the array length, aligning its behavior with ECMA-262. (304940@main) (167183441)

MathML

New Features

  • Added support for CSS size containment in MathML elements using contain-intrinsic-inline-size and contain-intrinsic-block-size. (304458@main) (166323213)
  • Added support for math-style and math-shift to animate as discrete values. (305017@main) (167369164)

Resolved Issues

  • Fixed incorrect positioning of mpadded content in right-to-left mode. (304087@main) (166045517)
  • Fixed an issue where RenderMathMLRoot did not reset its radical operator when msqrt or mroot children were dynamically added or removed.(304822@main) (166556627)
  • Fixed <mpadded> so that percentage values for width, height, and depth attributes are treated as absent and use content dimensions as defaults, matching the MathML Core specification. (305027@main) (167350169)

Media

Resolved Issues

  • Fixed an issue where changing an HTMLMediaElement volume from 0 to 0 did not activate the audio session or update the sleep disabler. (304297@main) (161691743)
  • Fixed playback of application/ogg blob media. (304530@main) (163119790)
  • Fixed an issue where HTMLMediaElement did not correctly detect new audio or video tracks causing Safari to pause video when leaving a tab. (304336@main) (164514685)
  • Fixed a crash in AudioData.copyTo() when copying the last channel of 3-channel audio. (304728@main) (164730320)
  • Fixed the caption menu’s On option to correctly enable the highest-scoring text track and mark the appropriate language as checked in the subtitle menu. (304462@main) (166158394)
  • Fixed parseSequenceHeaderOBU to return an AV1CodecConfigurationRecord, fully decode the Sequence Header OBU, and capture the complete color profile. (304474@main) (166439682)

Networking

Resolved Issues

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

Rendering

Resolved Issues

  • Fixed an issue where overconstrained sticky elements were not properly adjusting their insets when the sticky box rectangle was larger than the viewport. (304335@main) (163654023)
  • Fixed inconsistent text layout when using list-style-type by ensuring outside list markers do not affect intrinsic width calculations. (304947@main) (164650313)
  • Fixed incorrect min and max width calculations for block-level boxes inside inline content. (304170@main) (166157696)
  • Fixed an issue where getClientRects returned an incomplete list of rectangles for inline boxes containing block elements. (304949@main) (167209147)

SVG

New Features

  • Added support for the lighter operator in SVGFECompositeElement IDL to align with the Compositing and Blending specification. (304941@main) (166704079)

Resolved Issues

  • Fixed an issue where stroke-dasharray incorrectly propagated to SVG markers when explicitly marked as ‘0’. (305042@main) (46607685)
  • Fixed an issue where foreignObject elements in SVG incorrectly allowed margin collapsing. (304916@main) (97208795)
  • Fixed an issue where dynamically changing marker-start, marker-mid, or marker-end attributes on SVG elements did not trigger re-rendering. (304880@main) (130678384)
  • Fixed tiling gaps in CSS reference filters using <feDisplacementMap>. (304830@main) (135448018)
  • Fixed behavior to avoid incorrect pruning of SVG mask subtrees based on visibility. (305047@main) (157729389)
  • Fixed an issue where SVG animateTransform animations on hidden elements were triggering full-page rendering updates each frame. (304744@main) (159647563)
  • Fixed SVGLength percentage resolution for elements inside non-instanced <symbol> elements. (304197@main) (165431008)
  • Fixed an issue where SVGLength.value did not update for font-relative units (e.g., ch, em) after changes to writing-mode. (304657@main) (166190252)
  • Fixed missing gradient fills when using paint-order stroke fill. (288788@main) (166997630)
  • Fixed embedded <svg> elements in <img> without an explicit viewBox to synthesize preserveAspectRatio='none' so the SVG stretches to fill the container. (305043@main) (167121931)
  • Fixed the <stop> element offset attribute in SVG to reject invalid values with trailing characters and correctly fall back to 0. (305036@main) (167356988)
  • Fixed the transform order for clipPath elements so that the bounding box is scaled correctly before applying the local transform. (305078@main) (167417135)
  • Fixed incorrect rendering when combining markerUnits=strokeWidth with vector-effect=non-scaling-stroke. (305077@main) (167493417)

Web API

New Features

  • Added support for pointer event and mouse event button support for the back and forward mouse buttons when they are available. (304761@main) (137592588)

Resolved Issues

  • Fixed an issue where IntersectionObserver computed the root rectangle incorrectly when overflow clipping was present. (304296@main) (117143395)
  • Fixed DigitalCredential behavior to make user mediation implicitly required. (304676@main) (165597827)
  • Fixed an issue where the Big5 TextDecoder failed to recover and emit ASCII characters after encountering an invalid leading byte. (304591@main) (166672674)

Web Inspector

New Features

  • Added a context menu option to DOM nodes in the Elements Tab to copy pretty-printed HTML. (304698@main) (102125455)
  • Added support for toggling the visibility of User Agent stylesheet rules in the Elements tab, including hiding related “go-to” arrows in the Computed panel when the setting is disabled. (304627@main) (164265183)
  • Added support for showing actual composited layer snapshots in the 3D Layers view. (304758@main) (164977594)
  • Added support for Web Inspector to fetch CSS property keyword completions based on feature support. (304195@main) (165914089)
  • Added support for displaying Order Numbers in CSS Grid and CSS Grid Lanes overlays in Web Inspector, including new UI settings for toggling order number visibility. (304645@main) (166648769)
  • Added support for visualizing gaps between items in the masonry axis of grid-lanes layouts within the Web Inspector grid overlay. (304861@main) (166984079)

Resolved Issues

  • Fixed the Styles sidebar filter in Web Inspector to be case-insensitive. (304153@main) (36086981)
  • Fixed context menu items to rename Copy HTTP Request and Copy HTTP Response to Copy HTTP Request Headers and Copy HTTP Response Headers for clarity. (304768@main) (117708766)
  • Fixed an issue where the Sources navigation sidebar could be empty when reloading the page. (304221@main) (166141968)
  • Fixed timestamp formatting in the Web Inspector to remove unnecessary trailing .0 values for readability. (304851@main) (166500013)
  • Fixed item order labels in the Web Inspector grid and flex overlays to remove the # symbol, changing from Item #N to Item N. (304771@main) (166767949)

WebAssembly

Resolved Issues

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

WebRTC

Resolved Issues

  • Fixed RTCDataChannelInit to support [EnforceRange] on the maxPacketLifeTime and maxRetransmits fields to align with the WebRTC specification. (304499@main) (133630397)
  • Fixed an issue where RTCDataChannel close events did not fire when RTCPeerConnection was closed. (304859@main) (165617848)

January 14, 2026 10:28 PM

December 25, 2025

Igalia WebKit Team: WebKit Igalia Periodical #52

Igalia WebKit

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

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

Cross-Port 🐱

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

Two new functions have been added to the public API:

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

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

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

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

Graphics 🖼️

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

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

WPE WebKit 📟

WPE Platform API 🧩

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

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

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

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

Releases 📦️

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

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

Community & Events 🤝

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

That’s all for this week!

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

December 19, 2025

Release Notes for Safari Technology Preview 234

Surfin’ Safari

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

This release includes WebKit changes between: 303092@main…304071@main.

Accessibility

Resolved Issues

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

Animations

New Features

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

CSS

New Features

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

Resolved Issues

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

HTML

Resolved Issues

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

JavaScript

Resolved Issues

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

MathML

Resolved Issues

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

Media

Resolved Issues

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

SVG

Resolved Issues

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

Web API

New Features

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

Resolved Issues

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

Web Authentication

New Features

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

Resolved Issues

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

Web Inspector

New Features

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

Resolved Issues

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

WebRTC

New Features

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

December 19, 2025 08:43 PM

Introducing CSS Grid Lanes

Surfin’ Safari

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

Introducing CSS Grid Lanes.

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

Try it today in Safari Technology Preview 234.

How Grid Lanes work

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

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

First, the HTML.

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

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

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

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

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

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

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

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

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

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

The power of Grid

Varying lane sizes

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

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

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

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

Spanning items

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

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

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

Placing items

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

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

Changing directions

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

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

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

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

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

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

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

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

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

Flow Tolerance

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

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

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

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

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

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

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

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

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

[Note, when this article was first published, this property was named item-tolerance in the specification and in Safari Technology Preview 234. On January 7, 2026 the CSS Working Group resolved to change the name to flow-tolerance.]

Try it out

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

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

What’s next?

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

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

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

December 19, 2025 09:00 AM

Pawel Lampe: WPE performance considerations: pre-rendering

Igalia WebKit

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

The workload #

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

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

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

Typical web application workloads.

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

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

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

First step: optimization #

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

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

Second step: pre-rendering #

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

Pre-rendering techniques #

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

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

Pre-rendering offline #

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

Pre-rendering using canvas #

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

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

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

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

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

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

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

Pre-rendering using eventually-invisible layers #

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

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

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

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

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

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

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

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

Bursting numbers demo.

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

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

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

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

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

Scrolling trick explained.

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

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

Sysprof from basic demo.



Sysprof from pre-rendering demo.

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

Results

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

Benchmarking results.

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

Conclusions #

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

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

December 19, 2025 12:00 AM

December 15, 2025

Igalia WebKit Team: WebKit Igalia Periodical #51

Igalia WebKit

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

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

Cross-Port 🐱

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

JavaScriptCore 🐟

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

Releases 📦️

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

That’s all for this week!

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

December 08, 2025

Igalia WebKit Team: WebKit Igalia Periodical #50

Igalia WebKit

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

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

Cross-Port 🐱

JavaScriptCore 🐟

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

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

Graphics 🖼️

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

WPE WebKit 📟

WPE Platform API 🧩

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

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

That’s all for this week!

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

December 05, 2025

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

Igalia WebKit

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

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

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

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

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

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

Currently, meow supports this set of manipulation commands:

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

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

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

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

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

Now some practical examples:

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

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

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

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

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

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

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

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

By eocanha at December 05, 2025 11:16 AM

December 02, 2025

Igalia WebKit Team: WebKit Igalia Periodical #49

Igalia WebKit

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

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

Cross-Port 🐱

Multimedia 🎥

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

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

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

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

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

JavaScriptCore 🐟

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

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

WebKitGTK 🖥️

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

WPE WebKit 📟

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

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

WPE Platform API 🧩

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

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

Infrastructure 🏗️

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

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

Community & Events 🤝

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

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

That’s all for this week!

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

November 27, 2025

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

Igalia WebKit

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

Improved rendering performance

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

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

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

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

Changes in Multimedia support

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

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

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

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

WebXR support

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

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

Android support

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

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

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

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

Web Platform support

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

API changes

WPEPlatform

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

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

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

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

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

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

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

What’s new for WebKit developers?

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

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

Looking forward to 2.52

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

November 27, 2025 12:00 AM

November 24, 2025

Igalia WebKit Team: WebKit Igalia Periodical #48

Igalia WebKit

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

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

Cross-Port 🐱

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

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

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

JavaScriptCore 🐟

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

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

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

Releases 📦️

WebKitGTK 2.50.2 and WPE WebKit 2.50.2 have been released.

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

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

That’s all for this week!

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

November 17, 2025

Igalia WebKit Team: WebKit Igalia Periodical #47

Igalia WebKit

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

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

Cross-Port 🐱

Implement the MathML scriptlevel attribute using math-depth.

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

Releases 📦️

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

Infrastructure 🏗️

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

That’s all for this week!

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

November 10, 2025

Igalia WebKit Team: WebKit Igalia Periodical #46

Igalia WebKit

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

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

Cross-Port 🐱

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

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

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

Multimedia 🎥

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

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

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

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

JavaScriptCore 🐟

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

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

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

  • Fixed a rounding bug of Instant values.

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

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

WPE WebKit 📟

WPE Android 🤖

Adaptation of WPE WebKit targeting the Android operating system.

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

Community & Events 🤝

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

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

That’s all for this week!

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

November 03, 2025

Igalia WebKit Team: WebKit Igalia Periodical #45

Igalia WebKit

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

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

Cross-Port 🐱

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

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

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

Graphics 🖼️

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

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

That’s all for this week!

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

October 28, 2025

Igalia WebKit Team: WebKit Igalia Periodical #44

Igalia WebKit

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

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

Cross-Port 🐱

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

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

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

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

Implemented the math-depth CSS extension from MathML Core.

Graphics 🖼️

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

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

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

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

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

WPE WebKit 📟

WPE Platform API 🧩

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

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

Releases 📦️

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

Community & Events 🤝

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

That’s all for this week!

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

October 24, 2025

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

Igalia WebKit

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

Industry-standard memory profilers #

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

Massif (Valgrind) #

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

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

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

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

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

TODO.

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

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

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

Heaptrack #

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

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

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

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

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

heaptrack -p <PID>

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

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

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

heaptrack --analyze <RECORDING>

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

TODO.

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

TODO.

etc.

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

Shortcomings on embedded systems #

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

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

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

Malloc heap breakdown in WebKit #

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

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

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

Malloc heap breakdown on Apple ports #

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

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

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

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

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

Malloc heap breakdown on non-Apple ports #

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

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

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

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

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

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

The results have a structure similar to the one below:

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

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

TODO.

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

TODO.
Periodic reporting to sysprof

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

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

sysprof-cli -f -- <BROWSER_COMMAND>

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

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

TODO.

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

Caveats #

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

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

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

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

Opportunities #

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

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

Some examples of custom modifications include:

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

Summary #

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

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

October 24, 2025 12:00 AM

October 20, 2025

Igalia WebKit Team: WebKit Igalia Periodical #43

Igalia WebKit

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

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

Cross-Port 🐱

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

Graphics 🖼️

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

Community & Events 🤝

Our coworker Philip Chimento gained WebKit committer status!

That’s all for this week!

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