September 17, 2026

WebKit Features for Safari 27.0

Surfin’ Safari

Our Safari release notes have never been as long as they are for this version. The number of features alone rose from 58 to 83 since the first beta in June.

Safari MCP makes working with coding agents dramatically easier. Customizable select turns the real <select> element into something you can fully restyle — now with new UA default styles that provide an even-better starting place. Scroll anchoring stops content from jumping when something loads in above. The <model> element comes to iOS, iPadOS, and macOS, giving 3D a powerful HTML element. Websites can now provide immersive environments on visionOS. And much more.

Safari MCP

Are you developing websites using coding agents? The Safari MCP server, now available in Safari 27.0 will make your workflow faster and more powerful. Give Claude Code, Codex, or the agent of your choice control over the browser window so it can see how your code renders. Safari MCP provides access to the DOM, network requests, screenshots, and console output. Your agent can do more on its own while you do less hopping between windows, less dropping screenshots in your terminal, and less typing prompts to describe what’s not working.

The Safari MCP server enables your agent to:

  • see how your code renders in Safari
  • verify user states in forms, checkout flows, selections & more
  • compare computed styles and layout to results in other browsers
  • test for accessibility issues like missing labels, improper ARIA attributes, and poor contrast
  • analyze performance with navigation timing and resource load times

And much more. The MCP server runs entirely on your local machine. It makes no network calls of its own. It does not have access to your personal information in Safari. And any captured data goes directly to the agent you’re running, not to Apple.

To give it a try, go to Safari > Settings > Developer > check “Allow remote automation and external agents.” (If the Developer pane is not available, first go to Advanced, and check “Show features for web developers”.)

If you’re using Claude:

claude mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp

If you’re using Codex:

codex mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp

For other agents, you put the following in your mcp.json or config.json file.

{
  "mcpServers": {
    "safari-mcp": {
      "command": "/usr/bin/safaridriver",
      "args": ["--mcp"]
    }
  }
}

Let us know what you think, especially if you have any feature requests. Learn more by reading Introducing the Safari MCP server for web developers, or by referencing documentation.

Quality

The biggest feature of Safari 27.0 isn’t a feature at all. It’s the tremendous effort that went into improving the quality of existing features. At WWDC, we were proud to announce 525 fixes. Then we added 60% more, reaching a total of 844. Plus the majority of feature work improves existing features.

Pie chart of breakdown of what's in Safari 27. Resolved issues is 86% of the pie. Improved features is 11%. Brand new issues is 2%. And deprecations is a sliver less than 1%.

When we look at the efforts we made to improve quality, the story can be seen in several themes.

Compatibility. Our team made many changes to help make specific websites work correctly for their users. For example, Hindi InScript typing in an online document editor, images vanishing from search results on a restaurant reservation site, and Pahawh Hmong text misrendering in an online encyclopedia.

Foundations. Sometimes the best way to improve quality is to start over. Safari 27.0 has an all-new ES module loader. We rebuilt CSS Zoom. And now inline layout places elements with subpixel precision.

Depth. We got deep into specific technologies. There are 66 fixes to SVG in this release alone, including an end-to-end review of the SMIL animation engine. HTML tables got a systematic pass, with absolutely positioned tables now handling percentage-sized children, min-height, and max-height correctly. Plus deep work on Media Source Extensions (MSE) and Encrypted Media Extensions (EME). And much more.

Alignment. Much of the work is to better match exactly what web standards prescribe. For example, we corrected the MathML Core operator dictionary and its spacing values across several fixes. Fixes to innerText bring Safari’s rendered-text output better in line with standards for display, visibility, white-space, and form controls. And we improved how HTTP cache obeys Cache-Control.

Integration. Sometimes two features each work perfectly alone, but combined, something starts to go wrong. We fixed a lot of these this year. For example, -webkit-line-clamp shipped in WebKit in 2010, while text-wrap: balance arrived in 2024. Before Safari 27.0, if you applied both to the same element, the balancing simply didn’t happen. Now that’s fixed.

We truly hope all of these efforts throughout the last year make your work as a web developer a little easier. Read through the resolved issues at the end of this article to see specifics. And learn more about what we are doing to raise the quality of WebKit by watching What’s new in WebKit for Safari 27.

Customizable Select

The <select> element has been part of the web since the very beginning of HTML. But until recently, there wasn’t a lot you could to do style it or fill it with custom content. Customizable Select changes that. Now in Safari 27.0, it lets you build a fully custom drop-down menu to match the look and feel of your website or web app, without reaching for JavaScript or a pile of <div>. You can even push far beyond a typical drop-down menu to a very different UI. Because it’s a real form control, you get automatic, reliable support for keyboard navigation, screen readers, form submission, validation, change events and more.

Start by applying appearance: base-select in your CSS. This immediately switches to the look and feel provided by new UA styles, and enables the new powers in HTML.

select, 
select::picker(select) { 
    appearance: base-select 
}

You might notice that the default UA styles in Safari 27.0 are different than they were for the first beta back in June. The summer gave us the opportunity to reflect on what it will be like for web developers to write custom styles on top of the new defaults. We realized after 30 years of web developers struggling with form control styling, we wanted to provide something even better.

screenshot of six select menus - three before & three after, showing the difference in default styling
Previous default UA styles for Customizable Select on the left, with the new design on the right.

These defaults set you up with all the basics. You won’t be left with homework to do to get the select into a usable state. You can simply switch to the new control with appearance: base-select, and apply as little or as much additional code as you’d like. Don’t like the new defaults? You are in luck, it’s very easy to override them. Feel fine keeping any of these pieces like the new drop shadow, 4px rounded corners, touch-friendly line height, cleaner hover states, user-ready chevron & checkmark, subtle opt group styling, etc? Great! It’s already done for you, with support for all the variations like light & dark modes, forced color mode, disabled states and more.

We brought this new design to the CSS Working Group, where it’s being further discussed and refined. Once other browsers update their implementations, we will together reach our shared commitment for all browsers to support an identically interoperable starting place.

New pseudo-elements like ::picker-icon and ::checkmark let you easily target parts of the control that were previously unstylable. Plus, you can now insert HTML elements inside each <option> to add more detail. The new <selectedcontent> element can be used to adjust what gets displayed as the currently-selected option’s content. Learn more watching Rediscover the HTML Select Element from WWDC26.

HTML

An iPad, desktop, and an iPhone each displaying a browser showing an image of a 3D hiking boot model.

Model

Originally shipped a year ago in visionOS, the HTML <model> element is now also available in Safari on iOS, iPadOS, and macOS. This new element is a lot like video, audio, and img — this time embedding a 3D model in the page.

<model src="mallet.usdz"></model>

Just like the other HTML elements for media, you can link to multiple source files, including a fallback.

<model>
    <source src="boot.usdz" type="model/vnd.usdz+zip">
    <source src="boot.glb" type="model/gltf-binary">
    <img src="boot.png" alt="workboot in light tan leather">
</model>

You can optionally include attributes like environmentmap to provide custom lighting for your model. Or stagemode, which sets the default interaction behavior. Target your model with JavaScript and open up a wide range of possibilities.

Learn all about it, including where to get a 3D model, how to optimize it for the web, and what can be done with JavaScript by watching Get started with the HTML Model Element from WWDC26. And check out these demos in Safari.

Safari 27.0 also adds support so the CSS dynamic-range-limit property can be applied to the <model> element, giving you control over HDR tone mapping and rendering range for 3D content on iOS and macOS.

Responsive images

Responsive image techniques get easier with the auto keyword for sizes.

<img src="photo.jpg"
     srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
     sizes="auto"
     loading="lazy"
     width="1200"
     height="800"
     alt="A mountain landscape">

Using sizes="auto" on an image with loading="lazy" tells the browser to automatically calculate the size based on the actual layout width once it’s known. This means you don’t have to predict the rendered layout width ahead of time.

Web Components

Safari 27.0 adds support for the shadowrootslotassignment attribute on declarative shadow roots. This lets you configure the slot assignment mode (named or manual) directly in HTML when defining a shadow root declaratively, matching the JavaScript attachShadow({ slotAssignment: "manual" }) option.

Spatial Web

Immersive environments

Environments in visionOS are an incredible part of the experience of Vision Pro. They let you transform your physical surroundings into a different place—like Yosemite, Mount Hood, or the Moon. It’s been possible for Apple developers creating immersive apps for visionOS to provide custom environments with their app. Now in Safari 27.0, environments can be provided as part of a website.

Illustration of woman wearing VisionPro standing in field facing screen.

You can provide an immersive environment with a simple <model> element and one JavaScript API call. The Immersive API on the model element works similarly to how the Fullscreen API does on video elements. Learn all about it in Explore immersive website environments in visionOS.

By the way, this new Immersive API replaces the developer preview originally called Spatial Backdrop. If you built anything using Spatial Backdrop, migrate it to the Immersive API on the <model> element.

Image controls

Now the <img> element has a controls attribute in HTML. It works just like the controls attribute on the video and audio elements. When present, the browser offers controls to allow the user to adjust or more fully experience the media.

<img controls src="panorama.jpg" alt="A panorama of the Dolomites" >  

In Safari 27.0 in visionOS when the controls attribute is present, Safari provides a user interface for interacting with spatial and panorama photos. This gives users an easy and consistent mechanism to view photos spatially or immersively, and eliminates the need for web developers to build their own UI.

Photo of valley surrounded by mountains, an open cloudy sky, and a lake towards the right center.

WebXR

Safari 27.0 adds support for texture array projection layers in WebXR Layers. When creating a projection layer with XRWebGLBinding.createProjectionLayer(), you can now request textureType: "texture-array" so each eye’s view renders into its own layer of a single texture array.

Scroll Anchoring

Many websites inject content into the page as the user is reading or viewing that content. The new content often appears above where the user is currently looking — like images, ads, or comments being injected into the page. In the past, this caused the content the user was reading to be suddenly pushed down, causing a disorienting jump to a random place on the page.

Now with support for Scroll Anchoring, Safari 27.0 instead adjusts the scroll position and keeps the content exactly where it was before the content insertion. As a web developer, you don’t have to do anything to enable this on your site. It just works.

Scroll anchoring is controlled by the overflow-anchor CSS property, which defaults to auto. If you have a specific need where you need to opt out of scroll anchoring, you can use overflow-anchor: none.

CSS

The stretch keyword for sizing

Safari 27.0 adds support for using stretch with the properties width, height, min-width, max-width, min-height, max-height, and flex-basis. The stretch keyword tells an element to fill the available space in the relevant axis.

.card {
    width: stretch;
}

It’s just like using width: 100% — but this time accounting for margins, which prevents overflow. If you’ve been using -webkit-fill-available to solve this need, now is a good time to switch.

Anchor positioning improvements

Safari 27.0 makes three updates to anchor positioning, as the web standard evolves and the tool becomes more powerful.

First, we added support for transform-aware anchor positioning. Now, when an anchor element has a CSS transform applied — scale, rotate, translate, or any combination — elements positioned relative to that anchor follow its transformed position instead of its pre-transform layout position. This works for transforms applied via the transform property as well as through the individual translate, rotate, and scale properties. If you use anchor positioning to attach a tooltip, popover, or annotation to a transformed element, it now tracks correctly, even with animated transforms.

Diagrams showing how the in-page element can be transformed without affecting the placement of the popover anchored to it.

Second, the default value for position-anchor changes from auto to normal, fixing a potential side effect where the positioning behavior of elements that don’t even use Anchor Positioning could be impacted. The new value none opts out entirely. The new default, normal, behaves the same as none unless position-area is also set, in which case it behaves like auto did, as originally intended.

And third, Safari 27.0 also adds support for anchor-valid and anchor-visible . Originally, position-visibility: anchors-valid hid an element if any of its required anchor references couldn’t be resolved. However, it wasn’t clear what constituted “required anchor references”. So the CSS Working Group changed the behavior to only look at the default anchor box. To match, some keywords were renamed to drop the plurality. The anchors-valid value is now anchor-valid , while anchors-visible is now anchor-visible. Safari 27.0 aligns with the new behavior, and temporarily supports the old keywords for compatibility.

Color improvements

The new alpha() relative color function is a shorthand for adjusting just the alpha channel of an existing color, without repeating the rest of its channels: alpha(from var(--mycolor) / 80%). It keeps the origin color in its own color space and only changes the alpha value — useful when you want a more transparent or more opaque version of a color you already have, without writing out the full relative color syntax.

The color-mix() function now accepts more than two colors, so you can blend several colors together at once, like color-mix(in oklab, teal 20%, olive 30%, blue 50%). If you leave out the percentages, each color contributes equally.

The image(<color>) function lets you use a solid color anywhere an <image> value is expected. Unlike background-color, which sits underneath all background layers, image(<color>) behaves like a real image layer — it can stack above other background images, get sized with background-size, and be positioned and clipped like any image.

Safari 27.0 also adds support for forwarding missing color components when interpolating between analogous color spaces. Previously, a color with an intentionally missing component (none), like an achromatic gray with no meaningful hue, could get incorrectly assigned a hard 0 when converted into an analogous space for interpolation, producing a subtly wrong blended color. Now the missing component is carried forward as missing instead, so interpolation behaves the way you’d expect.

And more CSS

The light-dark() function now accepts <image> values, not just colors, so you can specify different images for light and dark color schemes in a single declaration: background-image: light-dark(url(day.png), url(night.png)). Gradients work here too.

Safari 27.0 adds support for the :heading pseudo-class, which matches any heading element — <h1> through <h6>. Instead of writing h1, h2, h3, h4, h5, h6 in your selector list, you can just write :heading. Plus, :heading also has a functional form for targeting specific levels, for example, :heading(1, 2) matches only <h1> and <h2>.

The revert-rule keyword is now supported in Safari 27.0. Like revert and revert-layer, revert-rule rolls back the cascade — but specifically to the state as if the current style rule had not been present. It gives you a more precise tool for working with overrides, especially in component libraries and design systems where you want to selectively undo declarations within a rule without losing the rest.

The CSS progress() function now supports a no-clamp option in Safari 27.0. By default, progress() returns how far a value sits between two bounds as a ratio from 0 to 1, clamped to that range. Adding no-clamp removes the clamp, so the result can fall below 0 or above 1, which is useful when you want an effect to keep scaling past its defined bounds instead of flattening out at the edges.

Safari 27.0 adds support for contain: style applying to CSS quotes. This allows you to scope effects of quotes to a certain subtree.

Safari 18.4 added support for text-autospace to control spacing between Chinese/Japanese/Korean (CJK) and non-CJK characters. Safari 27.0 now adds the insert keyword, making text-autospace: ideograph-alpha ideograph-numeric and text-autospace: ideograph-alpha ideograph-numeric insert equivalent.

The Dutch IJ digraph is now supported in Safari 27.0. When the content language is Dutch (lang="nl"), text-transform: capitalize and ::first-letter now correctly titlecase “ij” to “IJ” at the start of words.

Safari 27.0 adds support for the case-sensitive s modifier in CSS attribute selectors. Adding s after the value forces a case-sensitive match — for example, a[href$=".PDF" s] matches only a literal uppercase .PDF. This is the counterpart to the i modifier you may already be using to force case-insensitive matching (a[href$=".pdf" i] matches .pdf, .PDF, .Pdf, and so on); s lets you go the other way when you need an exact-case match on an attribute HTML would otherwise treat as case-insensitive.

Safari 27.0 also adds support for the :host:has() compound selector, letting a shadow host style itself based on what’s inside its own shadow tree. Because :has() can compound onto any selector, :host:has(:checked) or :host:has(::slotted(img)) let a custom element’s host change its own appearance depending on the state of its shadow content — useful for web component authors who want the host to react to what’s inside it without reaching for JavaScript.

Animations

Safari 27.0 adds the animation property to the AnimationEvent and TransitionEvent interfaces, letting event handlers directly access the Animation object associated with the event.

SVG

Safari 27.0 adds quite a few improvements to SVG.

Now the lang and xml:lang attributes are supported inside SVG. Use it to specify the language of text content to ensure correctness of both text rendering and accessibility announcements.

Safari 27.0 adds support for <use> referencing an external SVG file without a # fragment identifier. Previously, in order to point <use href="…"> at another SVG document, you had to name a specific element inside it with a fragment but now <use> can reference the external file on its own. There’s also a fix so <a> elements in SVG are treated consistently with HTML <a> elements for origin/security checks.

Several non-standard and legacy SVG interfaces have been removed to better align with the SVG 2 specification:

  • SVGLocatable and SVGTransformable interfaces
  • nearestViewportElement and farthestViewportElement properties on SVGGraphicsElement
  • viewTarget property on SVGViewSpec
  • glyph-orientation-horizontal property

Plus, there are a huge number of SVG fixes shipping this year. See the list below for what’s improved in Safari 27.0.

WebAssembly

Safari 27.0 adds support for WebAssembly JavaScript Promise Integration (JSPI). JSPI lets synchronous-looking WebAssembly code suspend and wait for JavaScript Promises, making it much easier to port existing C, C++, Rust, and other language code to the web where that code expects synchronous I/O.

Before JSPI, porting code that called synchronous APIs to Wasm required rewriting everything on top of a callback or async state machine. With JSPI, the Wasm module can suspend at a call site and resume when the Promise resolves — the rest of the module sees straight-line synchronous code. This is a significant capability for the Wasm ecosystem.

JavaScript

Top-Level Await

Safari 27.0 includes a complete standards-compliant rewrite of the ECMAScript module (ESM) loader. The new loader is implemented in native C++ and conforms directly to the ECMAScript specification’s module loading algorithms, replacing an earlier implementation based on an abandoned 2016 WHATWG Loader proposal that predated top-level await entirely.

The rewrite fixes module execution ordering and initialization issues that could cause imports to access exports before they were fully evaluated. It was validated against test262, the Web Platform Tests, and additional test cases.

Top-level await is a foundational feature of modern JavaScript module authoring, and it’s been a real pain point in Safari for a while — a known source of cross-browser bugs that developers building module-based apps had to work around. This fix closes that gap. To learn more, read Fixing Top-Level Await in Safari.

BigInt Math

Safari 27.0 adds support for the TC39 BigInt Math proposal, which brings Math-equivalent operations to BigInt as static functions on the BigInt constructor: BigInt.abs, BigInt.sign, BigInt.sqrt, BigInt.cbrt, BigInt.pow, BigInt.min, and BigInt.max. Like the existing BigInt.asIntN(), they’re called directly on BigInt rather than as instance methods, since BigInts are primitives.

This fills a real gap: Math.sqrt() and friends only accept Number, and routing a BigInt through Number to use them loses precision above 2^53. BigInt.sqrt() and BigInt.cbrt() truncate toward zero, so BigInt.sqrt(16n) returns 4n and BigInt.sqrt(17n) also returns 4n — there’s no ceil, floor, or round variant, since there’s no fractional BigInt to round in the first place. The proposal is still at TC39 Stage 1, so treat the exact API as early and possibly still changing.

Web API

Safari 27.0 adds support for the Service Worker static routing API. This lets a service worker declare routing rules that the browser can use to bypass the service worker entirely for certain requests, reducing overhead for high-performance PWAs.

Safari 27.0 adds three improvements to ReadableStream. First, the async iteration with for await...of:

const response = await fetch("/data");
for await (const chunk of response.body) {
  process(chunk);
}

Second, the ReadableStream.from() static method for creating a stream from any async iterable or iterable:

const vegetables = ["Carrot", "Broccoli", "Tomato", "Spinach"];
const asyncIterator = (async function* () {
  yield 1;
  yield 2;
  yield 3;
})();
// Create ReadableStream from the array
const myReadableStream = ReadableStream.from(vegetables);
// Create ReadableStream from async iterator
const asyncReadableStream = ReadableStream.from(asyncIterator);

And third, the ability to transfer a ReadableStream , WritableStream and TransformStream across contexts via postMessage().

Web Inspector

Several Web Inspector updates in Safari 27.0 make common debugging tasks easier.

The Color Picker now shows color contrast information inline as you edit. No more switching tools mid-decision to check whether a color combination is accessible. This works when you’re editing both foreground and background colors at the same time.

Color picker in Web Inspector showing color contrast information

The Color Picker’s format and gamut controls are also now visible upfront instead of hidden. If you’ve ever gone hunting for those options, this will help.

Color picker in Web Inspector showing dropdown of formats.

In the Network tab, when a resource redirects, you can now see every request in the chain rather than just the final destination. It’s much easier to figure out what’s actually happening.

The network tab in Web Inspector showing three redirects.

The Elements tab adds Subgrid and Grid-Lanes badges that make it easy to identify subgrid and grid-lanes layout contexts as you explore a page.

Shows the subgrid lines in Web Inspector on top of Stripe's Dev page.

The Timeline tab now includes the layout root element in Layout event details, so you can see which element triggered a layout pass. The Timeline view also uses distinct colors for style events like “Style Invalidated” and “Style Recalculated”, making them easy to tell apart from layout events at a glance, and adds a separate column showing the node associated with each layout and rendering event.

Media

Safari 27.0 supports setting TextTrackCue.endTime to Infinity to represent an unbounded cue duration. It’s useful for captions or data cues of live streams.

Safari 27.0 adds support for synchronized video playback on macOS displays using genlock. Genlock synchronizes the timing signal across multiple displays or capture devices, which is important for broadcast, live-event, and multi-display installation setups. Even a few frames of misalignment between screens is visible. When genlock is available, video played in Safari stays in lockstep with the rest of the signal chain instead of drifting on its own clock.

Safari 27.0 improves how HDR images with gain maps are decoded and rendered, decoding them into accelerated backing stores in the GPU process. This is a rendering-pipeline improvement rather than a new API — HDR photos with gain maps (the format used by iPhone’s Adaptive HDR photos) should render more correctly and efficiently, building on gain-map rendering fixes from recent releases.

Safari 27.0 lets you override the color space a hardware VideoDecoder uses when decoding video with WebCodecs. This helps when a stream’s embedded color space metadata is missing or wrong. Now, you can tell the decoder which color space to interpret the frames in instead of being stuck with an incorrect result.

Safari 27.0 adds support for the nextslide and previousslide MediaSession actions, mapping them to the platform’s nexttrack and previoustrack commands, which are the hardware and remotes people already use to skip tracks. If a page hasn’t registered a nexttrack or previoustrack handler but has registered nextslide or previousslide, pressing next or previous invokes the slide handler instead. This lets presentation and slideshow web apps respond to the same physical controls as music and video apps.

Networking

Safari 27.0 adds support for Secure cookies on loopback hosts. For loopback hosts using plaintext HTTP, cookies marked Secure can now be set via JavaScript and Set-Cookie headers, matching the behavior of other browsers, simplifying local development and testing with Secure cookies.

WebGPU

Safari 27.0 now supports the clip_distances built-in value in WGSL shaders. Clip distances are a WebGPU feature that allows vertex shaders to define custom clipping planes, enabling you to discard geometry on one side of an arbitrary plane before rasterization occurs.

Canvas

The radii argument of CanvasPath.roundRect() is now optional in Safari 27.0. Calling roundRect(x, y, width, height) without radii draws a plain rectangle with square corners which is the same as calling rect() , matching the behavior of other browsers.

Rendering

Safari 27.0 adds srgb-linear and display-p3-linear to predefined color spaces, making these linear-light color spaces available in Canvas, WebGL, and other APIs.

MathML

Safari 27.0 updates its MathML operator dictionary to match the MathML Core specification, adding support for multi-character operators (like ++, :=, and /=). This improves spacing and layout for these operators in complex mathematical notation.

In addition, Safari 27.0 supports tabindex, focus(), blur(), and autofocus on MathML elements, improving MathML feature parity with HTML. This makes math content fully participate in keyboard navigation and focus management, which supports interactive educational content and accessibility.

Safari 27.0 also adds support for detecting embellished operators through <mrow> for underover layout. In MathML, an operator wrapped in a grouping element like <mrow> — for example, <mrow><mo>∑</mo></mrow> used as the base of a <munder> or <munderover> — still counts as that operator for layout purposes.

And the href attribute is now deprecated on all MathML elements except <a>, matching how HTML already restricts href to elements built for navigation rather than treating it as a global attribute.

WebRTC

Safari 27.0 adds support for the targetLatency attribute in WebRTC, for specifying a target latency on a receiver. It adds support for the RTCRtpCodec dictionary and related constructs, improving the ability to inspect and configure codecs. It adds support for RTCRtpReceiver.jitterBufferTarget, for tuning the jitter buffer. And it adds video source width and height to RTC stats.

Storage

Safari 27.0 now supports specifying maxAge when setting a cookie via the Cookie Store API.

await cookieStore.set({
  name: "session",
  value: "abc123",
  maxAge: 60 * 60 * 24 * 7, // one week, in seconds
});

Editing

Safari 27.0 now supports menu items that convert editable text between Simplified and Traditional Chinese characters. It’s available in the “Transformations” submenu of the context menu for relevant text selections.

WebDriver

Safari 27.0 adds WebDriver support for the Digital Credentials API, with commands that let automated tests simulate wallet payloads, wait indefinitely for a credential response, and simulate a user rejecting the request. This lets you write end-to-end tests for Digital Credentials flows without needing a real wallet or person to drive the interaction.

Web Extensions

The runtime.getDocumentId() Web Extension API now has support in Safari 27.0. It adds reporting of uncaught JavaScript exceptions and unhandled promise rejections in Web Extension scripts, making extensions easier to debug. It adds support for propagating user gestures through sendMessage(), connect(), postMessage(), and executeScript() — so extensions can reliably perform actions like media playback that require user activation. And it adds support for the tabId key in chrome.windows.create(), letting an extension move an existing tab into a newly created window instead of only creating new tabs from scratch.

WKWebView

Safari 27.0 helps native app developers do even more using the WKWebView public API for native app developers. Build advanced browser and web-hosting experiences on top of WebKit more easily with the following new features:

  • WKJSHandle — use JavaScript object references from native code.
  • WKContentWorldConfiguration — configure content world properties such as autofill scripting, shadow root access, and inspectability when creating a WKContentWorld.
  • alternateRequest and overrideReferrerForAllRequests on WKWebpagePreferences — modify the main resource request during navigation and apply custom referrer headers across all resource loads.
  • willSubmitForm callback on WKNavigationDelegate — receive notification of HTML form submissions via a new WKFormInfo object.
  • mainFrameNavigation on WKNavigationAction and mainFrameNavigation on WKNavigationResponse — correlate navigation actions and responses with each other and their originating loads.
  • WKWebView.load(_ url:) — load a URL directly without wrapping it in an NSURLRequest.
  • WKDOMNodeSnapshot — clone DOM nodes, including shadow roots, between different WKWebView instances.
  • WKHTTPCookieStore.cookies(for:) — retrieve cookies matching a specific URL without fetching the entire cookie store.
  • WKWebpagePreferences.globalPrivacyControlEnabled — let a native app enable or disable sending the Global Privacy Control (GPC) Sec-GPC HTTP header on outgoing requests for a given page load.

Resolved issues

Accessibility

  • Fixed an issue where calling speechSynthesis.cancel() removed utterances queued by subsequent speechSynthesis.speak() calls. (46151521)
  • Fixed an issue where SVG <use> elements referencing <symbol> elements inside an <img> were incorrectly included as unnamed images in VoiceOver’s Images rotor. (98999595)
  • Fixed an issue where changing the id attribute of an element targeted by aria-owns did not update the accessibility tree. (107644248)
  • Fixed slot elements referenced by aria-labelledby to correctly use their assigned slotted content for accessible names and ignore hidden slotted nodes. (114500560)
  • Fixed <meter> element to have consistent labels between aria-label and title attributes. (127460695)
  • Fixed elements with display: contents and content in a shadow root to have their content properly read when referenced by aria-labelledby. (129361833)
  • Fixed aria-labelledby to use the checkbox name instead of its value when the checkbox name comes from an associated <label> element. (141564913)
  • Fixed VoiceOver cursor positioning for elements focused via the drawFocusIfNeeded() canvas API. (146323788)
  • Fixed grid elements with child rows in a shadow root to properly work with VoiceOver. (153134654)
  • Fixed an issue where VoiceOver read text within images that have role="presentation". (159304061)
  • Fixed an issue where content within dynamically expanded <details> elements was not exposed in the accessibility tree. (159865815)
  • Fixed an issue where the contextmenu event was not fired for elements inside iframes when triggered by keyboard or assistive technology actions such as VoiceOver’s VO+Shift+M. (164128676)
  • Fixed an issue where changes to <input type="button"> elements inside live regions were not announced by assistive technologies. (168200460)
  • Fixed ::first-letter text not being exposed in the accessibility tree when no other text accompanies it. (168458291)
  • Fixed an issue where VoiceOver was unable to access aria-owned rows and their cells in grids and tables. (168770938)
  • Fixed an issue where VoiceOver could not find focusable splitter elements when navigating to the next or previous form control. (170187464)
  • Fixed an issue where color picker inputs could not be activated using VoiceOver’s press action. (172218114)
  • Fixed an issue where interactive elements containing an <svg> named by a child <title> element did not expose an accessible name. (172559238)
  • Fixed an issue where incorrect bounding boxes were computed for MathML table rows and cells. (172851295)
  • Fixed an issue where comboboxes did not forward focus to their aria-activedescendant, preventing assistive technologies from interacting with list items. (172931277)
  • Fixed VoiceOver on Safari unable to navigate to content revealed by disclosure widgets using hidden="until-found". (173228707)
  • Fixed an issue where aria-owns was not respected when computing the accessible name from element content. (173249317)
  • Fixed VoiceOver line-by-line reading skipping content in read-only documents. (174349841)
  • Fixed invalidation of aria-hidden=”true” when focus lands inside the aria-hidden subtree. (174449524)
  • Fixed VoiceOver’s “Skip redundant labels” setting not being respected on certain web pages. (176297111)
  • Fixed an issue preventing VoiceOver from following focus() calls for newly added elements. (177167634)
  • Fixed role computation for elements with prefixes. (178399441)
  • Fixed VoiceOver jumping to the top of the page when navigating to an element that immediately becomes accessibility-ignored via JavaScript. (179065364)
  • Fixed VoiceOver reporting the Recent Events table on parks.wa.gov as empty. (179156593)
  • Fixed an <a> element with a click handler but no href not being exposed as a link. (179398579)
  • Fixed VoiceOver not announcing the selected state of the day-of-month button when configuring a monthly notification. (180294912)
  • Fixed stale aria-labelledby when the referenced element dynamically changes its aria-label. (180319221)

Animations

  • Fixed an issue where animation-fill-mode did not correctly apply viewport-based units after the viewport was resized. (80075191)
  • Fixed an issue where !important declarations did not override CSS animation values when CSS transitions were also running on the same property. (174367827)
  • Fixed an issue where identity matrix decomposition generated invalid quaternions, resulting in incorrect transform animations. (174813328)

CSS

  • Fixed an issue where -webkit-text-fill-color incorrectly overrode text-decoration-color. (47010945)
  • Fixed shape-outside computing incorrect text wrapping in RTL writing modes. (56890238)
  • Fixed aspect-ratio intrinsic-size handling in flex layout to align with the specification. (83240099)
  • Fixed flex layout to use the used flex-basis instead of the specified value for definiteness evaluation. (85707621)
  • Fixed an issue where the outline offset was too large for outline: auto on macOS. (94116168)
  • Fixed an issue where element positioning was incorrect when the containing block was an anonymous block. (96548847)
  • Fixed an issue where box-shadow did not work on display: table-row elements. (96914376)
  • Fixed text-indent with calc() containing percentages to correctly treat percentage components as zero for intrinsic size contributions. (97025949)
  • Fixed an issue where out-of-flow content had an incorrect height when set to fit-content. (97492632)
  • Fixed an issue with percentage size resolution in flex items in quirks mode. (100183902)
  • Fixed an issue where clip-path: inset() border-radius values did not render correctly at certain element and clip-path sizes. (110847266)
  • Fixed text-decoration-thickness propagation to inner spans with non-inline style. (111015539)
  • Fixed -webkit-box flexbox emulation not sizing children correctly inside <fieldset> elements. (114094538)
  • Fixed: Improved performance on pages using :where and :is selectors. (114904007)
  • Fixed an issue where elements with display: table could have incorrect layout when borders were present. (116110440)
  • Fixed aspect-ratio not being respected on flex children when the flex container has position: absolute. (117807518)
  • Fixed aspect-ratio not working correctly on flex children that also have overflow set. (118926827)
  • Fixed font-family serialization to preserve quotes around family names that match CSS-wide keywords or generic families. (125334960)
  • Fixed an issue where elements with border, position: absolute, and aspect-ratio: 1 were not rendered as squares. (126292577)
  • Fixed an issue where perspective-origin failed to resolve var() references when used as the second value, preventing animations from being applied. (131288246)
  • Fixed :focus-visible incorrectly matching after a programmatic focus() call triggered by clicking a button with child elements. (134337357)
  • Fixed an issue where the bottom margin of a last child element collapsed out of a parent with min-height. (134356544)
  • Fixed a performance issue where pages with many DOM manipulations and complex :has() selectors could freeze. (138431700)
  • Fixed an issue where a font was downloaded despite no characters in the document falling within its unicode-range. (140674753)
  • Fixed an issue where @media (prefers-color-scheme: dark) inside an iframe did not match when the iframe’s color-scheme was set to dark. (142072593)
  • Fixed an issue where background-clip: text did not work on table header elements. (142812484)
  • Fixed an issue where width: 0 did not collapse a table cell to its minimum size. (142814603)
  • Fixed an issue where :has(:empty) continued to match after the targeted element’s content was dynamically changed to no longer be empty. (143864358)
  • Fixed an issue where floats and out-of-flow objects could be incorrectly adjacent to anonymous blocks. (144481961)
  • Fixed an issue where text gradually disappeared when toggling text-transform on elements with ::first-letter styling. (145550507)
  • Fixed an issue where height: max-content resolved to zero on absolutely positioned elements when a child had max-height: 100%. (147333178)
  • Fixed unnecessary text truncation that could occur with always-on scrollbars. (148428628)
  • Fixed an issue where tables with collapsed borders incorrectly calculated the first row width, causing excess border width to spill into the table’s margin area. (149675907)
  • Fixed an issue where an inline-flex container with flex-direction: column did not update its width to match the intrinsic size of a child image when the image was not cached. (150260401)
  • Fixed CSS zoom interacting incorrectly with font-weight, font-style, and font-variant on iPad. (152173269)
  • Fixed an issue where non-replaced elements with aspect-ratio enforced the automatic minimum size even when min-width was explicitly set to 0. (156837730)
  • Fixed image aspect-ratio not being preserved when width: 100% and height: 100% are set but no ancestor has a defined width. (162373271)
  • Fixed an issue where an element can’t anchor to its previous sibling. (162903640)
  • Fixed :has() style invalidation performance for selectors where :has() is in non-subject position. (163512170)
  • Fixed CSS Container Style Queries to work when the container has display: contents. (164414720)
  • Fixed CSS Style Container Query to recognize the default value of a custom property in multiline cases. (165627262)
  • Fixed an issue where RTL grid scrollable areas did not correctly account for grid layout and scrollbars. (167792896)
  • Fixed pixel snapping to be applied consistently for all border-width value types. (168240347)
  • Fixed rendering of linear gradients when all color stops are at the same position. (169063497)
  • Fixed an issue where inset box-shadow was incorrectly positioned on table cells with collapsed borders. (169254286)
  • Fixed position-try-order to interpret logical axis values using the containing block’s writing mode instead of the element’s own writing mode. (169501069)
  • Fixed CSS Anchor Positioning so that absolutely positioned elements no longer anchor to viewport-fixed-position elements unexpectedly. (170323196)
  • Fixed an issue where children with percentage heights inside absolutely positioned elements using intrinsic height values (fit-content, min-content, max-content) incorrectly resolved against the containing block’s height instead of being treated as auto. (171179193)
  • Fixed an issue where percent-height replaced elements computed stale preferred widths in shrink-to-fit containers. (171184282)
  • Fixed a regression where @scope styles did not apply to slotted elements in web components. (171383788)
  • Fixed an issue where the table cell nowrap minimum width calculation quirk was applied outside of quirks mode. (171410252)
  • Fixed an issue where display property transitions caused popovers and <dialog> elements to animate incorrectly when closing. (171454696)
  • Fixed a performance issue where contain: layout caused significantly slower forced layouts when all siblings created their own formatting context. (171545381)
  • Fixed an issue where dynamically inserting text before existing content did not update ::first-letter styling. (171649994)
  • Fixed an issue where underlines were split when a ruby base was expanded due to long ruby text. (171653095)
  • Fixed an issue where changing color-scheme did not repaint iframe background. (171658244)
  • Fixed an issue where nested children of a popover element failed to render when using position: absolute. (171735933)
  • Fixed an issue where color: initial resolved to the wrong color when the system is in dark mode. (172320282)
  • Fixed an issue where an element with display: contents did not establish an anchor scope when using anchor-scope. (172355302)
  • Fixed an issue where ordered list numbers with large starting values were clipped off-screen. (172515216)
  • Fixed <general-enclosed> in media queries to reject content with unmatched close brackets per the <any-value> grammar. (172575115)
  • Fixed an issue where the rlh unit was double-zoomed with evaluation-time CSS zoom. (172798163)
  • Fixed an issue where anchor-positioned elements anchored to children of sticky-positioned boxes did not stick correctly. (172884148)
  • Fixed an issue where pseudo-elements were not sorted correctly when sorting anchor elements by tree order. (173032203)
  • Fixed outline: auto to correctly respect zoom. (173068660)
  • Fixed transferred min/max block-size constraints not being applied for intrinsic keyword widths on replaced elements. (173128588)
  • Fixed outline-offset to work correctly with outline: auto on iOS. (173130230)
  • Fixed :active, :focus-within, and :hover pseudo-classes to correctly account for elements in the top layer. (173145294)
  • Fixed a regression where the ic length unit was incorrectly affected by page scaling. (173198587)
  • Fixed the shape() function to omit default control point anchors in computed value serialization per the CSS Shapes specification. (173233716)
  • Fixed: Updated SVG and MathML user agent style sheets to use :focus-visible instead of :focus. (173321368)
  • Fixed an issue where lh and rlh units resolved with double-zoom when line-height was a number value. (173448638)
  • Fixed outline-width to be ignored when outline-style is auto, matching the specification. (173567890)
  • Fixed :in-range and :out-of-range pseudo-classes for time inputs with reversed ranges. (173589851)
  • Fixed :placeholder-shown to correctly match input elements that have an empty placeholder attribute. (173604635)
  • Fixed an issue where ligatures caused a non-zero layout width for text with font-size: 0. (173840866)
  • Fixed computed value of auto insets or margins as returned by getComputedStyle() to be zero, if the element uses position-area or anchor-center. (173885561)
  • Fixed position-area not being able to anchor to an element positioned using anchor functions. (173964030)
  • Fixed :in-range and :out-of-range pseudo-classes to correctly update when the readonly attribute changes. (173978657)
  • Fixed scroll overcompensation of fixed, nested anchor-positioned elements, so they no longer lose their position when scrolling. (174010503)
  • Fixed an issue where view-timeline-inset serialization failed to coalesce identical values. (174096313)
  • Fixed CSS variable cycle detection to match the CSS Values Level 5 specification. (174105259)
  • Fixed url() token serialization in CSS custom properties. (174144616)
  • Fixed text-autospace to correctly handle supplementary Unicode characters. (174148315)
  • Fixed an issue where flex items with different order values caused incorrect baseline alignment. (174241817)
  • Fixed an issue where hovering over ::first-letter text showed a pointer cursor instead of the expected I-beam cursor. (174258447)
  • Fixed an issue where display: grid on a <fieldset> element added extra unnecessary space below its content. (174301311)
  • Fixed outline radii rendering for elements with a non-auto outline-style. (174328839)
  • Fixed an issue where aspect-ratio was not honored when the page was zoomed in. (174361289)
  • Fixed replaced elements to use the transferred size through intrinsic aspect ratio for min-content and max-content sizing. (174386310)
  • Fixed an issue where height: 100% on a child element altered the layout when the parent’s height was defined via aspect-ratio. (174448267)
  • Fixed margin collapse to be allowed when the preferred block size behaves as auto, per the CSS Sizing specification. (174547610)
  • Fixed an issue where document.styleSheets and shadowRoot.styleSheets incorrectly included adopted style sheets, which per the CSSOM specification should only appear in the final CSS style sheets list used for style resolution. (174583340)
  • Fixed the CSSOM preferred style sheet set name to be established at sheet creation time based on insertion order rather than tree order. (174586058)
  • Fixed -webkit-box-pack to account for -webkit-box-direction and to handle overflow repositioning correctly. (174588996)
  • Fixed highlight pseudo-elements such as ::selection and ::highlight to disallow vendor-prefixed properties, aligning with the CSS Pseudo-Elements specification. (174590593)
  • Fixed cycle detection and nested function call handling in CSS custom functions. (174609179)
  • Fixed FontFace.loaded to reject when a local() font source fails to load. (174631384)
  • Fixed an issue where word-break: break-all incorrectly allowed CJK close punctuation to appear at the start of a line. (174656971)
  • Fixed an issue where word-break: keep-all incorrectly suppressed line break opportunities at CJK punctuation characters. (174658701)
  • Fixed the FontFace constructor to reject with a SyntaxError instead of a NetworkError when a BufferSource fails to parse, per the CSS Font Loading specification. (174669738)
  • Fixed the FontFace family attribute to return the serialization of the parsed value. (174698351)
  • Fixed grid layout to correctly handle percentage and calc() values for the specified size suggestion. (174863227)
  • Fixed :has() sibling invalidation issues related to relation forwarding. (175006235)
  • Fixed an issue where min-width: auto was not correctly computed for flex items. (175157619)
  • Fixed an issue where percentage heights inside flex items did not resolve correctly in quirks mode. (175158571)
  • Fixed an issue where margin-trim: block-start did not apply to blocks nested inside inline boxes. (175162899)
  • Fixed an issue where dynamically changing display: contents on a <fieldset> legend caused incorrect rendering. (175163337)
  • Fixed: Improved :has() invalidation performance by including the full selector context in invalidation selectors. (175177078)
  • Fixed :hover state to repaint correctly on the customizable <select> element. (175273152)
  • Fixed the CSS preload scanner to resolve relative @import URLs against the <base> element URL. (175305190)
  • Fixed -webkit-box flex distribution for children with orthogonal writing modes. (175323734)
  • Fixed calc(infinity) as a flex-grow factor not stretching a flex item to 100% width. (175431146)
  • Fixed :has() sibling invalidation failing due to an internal bitfield overflow, causing stale styles when siblings are added or removed. (175433733)
  • Fixed :has() invalidation for sibling combinators when elements are inserted or removed from the DOM. (175441568)
  • Fixed transition-property not preserving the specified case of <custom-ident> values during serialization. (175467206)
  • Fixed the will-change property not serializing correctly when used with non-property identifiers or identifiers in a non-standard case. (175482352)
  • Fixed percentage top and bottom values on relatively positioned elements not resolving when the containing block has aspect-ratio. (175502356)
  • Fixed: Updated the enhanced <select> element to use self- keywords for anchor positioning. (175505107)
  • Fixed serialization of multi-word font family names that were always incorrectly quoted due to treating the full string as a single identifier. (175522811)
  • Fixed text-indent computation when tab stop positions are involved. (175529961)
  • Fixed calc() margin computations in flex layout. (175532405)
  • Fixed calc() margin computations for block, fieldset, and table caption layouts. (175548980)
  • Fixed handling of <li> value attributes in reversed ordered lists. (175558324)
  • Fixed CSS trigonometric functions to correctly convert degrees to radians. (175575617)
  • Fixed sibling-index() and sibling-count() inside calc() functions to be correctly simplified. (175590806)
  • Fixed sibling-index() and sibling-count()to correctly return 0 when used in cross-tree ::part() styling. (175592607)
  • Fixed the CSS resize handle not working on an element when the handle overlaps a child iframe. (175621855)
  • Fixed flex container baseline alignment being incorrectly computed for scroll containers by clamping to the border edge. (175631095)
  • Fixed an issue where inline-level boxes with calc() margins or padding lost the fixed component during intrinsic width computation. (175669222)
  • Fixed floats with margin-start incorrectly overlapping adjacent floats. (175669464)
  • Fixed aspect-ratio calculations for block-level elements with size constraints. (175669713)
  • Fixed aspect-ratio calculations for flex items with percentage cross-size constraints. (175669774)
  • Fixed aspect-ratio calculations for flex items with definite cross-size values. (175690028)
  • Fixed revert-layer computing incorrectly when there is a leading empty or space substitution value. (175729680)
  • Fixed :has() invalidation incorrectly resetting sibling relation bits, causing style invalidation failures for first-in-sibling-chain elements. (175738008)
  • Fixed text in nested CSS Subgrid with overflow: hidden clipping content on subsequent items. (175877530)
  • Fixed CSSStyleDeclaration.setProperty() failing to apply !important priority to an existing inline style property when the value was an integer of 255 or lower. (176099619)
  • Fixed an issue where flex items with explicit min-height: min-content were incorrectly treated as scrollable, zeroing out their minimum size. (176173688)
  • Fixed :has() invalidation performance when used inside nested :is() selectors. (176354723)
  • Fixed sibling-count() & sibling-index() used in @keyframes to re-resolve when siblings change. (176531901)
  • Fixed :has() style invalidation failing in complex nested cases involving :is(). (176719780)
  • Fixed -webkit-perspective not establishing a containing block for fixed-positioned descendants. (176729670)
  • Fixed nested multi-column layouts with three or more levels failing to paginate content across pages. (176741498)
  • Fixed :has() selector performance by using scope selectors to limit style invalidation traversal for class, attribute, and pseudo-class changes. (176771971)
  • Fixed non-replaced blocks with aspect-ratio and a percentage max-width collapsing to zero width during intrinsic sizing. (176873776)
  • Fixed percentage max-width on elements with aspect-ratio resolving against the wrong axis in perpendicular writing modes. (176879597)
  • Fixed z-index not applying to statically-positioned display: -webkit-box items to align with Firefox and Chrome behavior. (176886461)
  • Fixed an interaction between an img with max-width and surrounding elements that caused the parent’s layout to compute incorrectly. (176889859)
  • Fixed flex containers using box-sizing: border-box providing the wrong cross size to stretched flex items. (176989934)
  • Fixed flex containers with aspect-ratio-derived height not providing a definite cross size to their flex items. (177085129)
  • Fixed SVG images with no intrinsic dimensions collapsing to zero height inside column flex containers. (177086497)
  • Fixed incorrect margin offsets for inline <div> positioning to fix the broken layout of paragraph spacing on some sites. (177139092)
  • Fixed shrink-to-fit boxes to update their width when they gain or lose a scrollbar. (177172896)
  • Fixed CSS zoom to be animatable by the computed value. (177411607)
  • Fixed preferred width to trim trailing whitespace before a preserved newline. (177426037)
  • Fixed inline layout to apply margins of preceding block content at the line start eagerly when block-in-inline content is involved. (177438841)
  • Fixed CSS attr() to align with disallowing the <url> type. (177540489)
  • Fixed CSS attribute selector case-insensitivity handling for HTML attributes. (177547701)
  • Fixed ::first-letter to use the correct definition of punctuation. (177599506)
  • Fixed offset-path to respect <coord-box> when blending shape() and basic-shape paths. (177685457)
  • Fixed stretch-fit width with aspect-ratio providing a definite cross size to flex items when it should not. (177705930)
  • Fixed an aspect-ratio flex container resolving descendant percentage heights against a stale logical height. (177711905)
  • Fixed inline-block baseline to fall back to the bottom margin edge when the content has no in-flow line boxes. (177753094)
  • Fixed programmatic focus after keyboard interaction to match :focus-visible. (177850766)
  • Fixed @font-face font-style to serialize ‘oblique 0deg’ as ‘normal’. (178185291)
  • Fixed serialization of explicit font-variant longhands set after a system font. (178251443)
  • Fixed CSSFontFeatureValuesRule.fontFamily to be settable rather than readonly. (178323504)
  • Fixed font-style: oblique to be clamped against the font’s slant range rather than the @font-face weight range. (178324521)
  • Fixed font-style: oblique angle being applied to the variable font ‘slnt’ axis with the wrong sign. (178326843)
  • Fixed background and mask coordinated property list resolved values to match the specification. (178378309)
  • Fixed longer hue interpolation when one input is none. (178476769)
  • Fixed serialization of @font-face font-weight, font-width, and font-style oblique descriptor ranges with equal bounds to collapse to a single value, per CSSOM. (178517226)
  • Fixed line-through to render with the correct thickness over a descendant inline box. (178547557)
  • Fixed font-synthesis to avoid synthesizing styles outside of a font’s variable axis range. (178550149)
  • Fixed font-style: italic to slant a variable font whose @font-face uses an oblique angle. (178566326)
  • Fixed an issue where font-synthesis incorrectly applied synthetic oblique to variable fonts declared with @font-face. (178698772)
  • Fixed SVG intrinsic sizing so that height: max-content uses the used width rather than the default width. (178712792)
  • Fixed serialization of various CSS at-rules not escaping identifiers. (178750383)
  • Fixed color-mix() to allow percentages that sum to zero. (178758710)
  • Fixed color-mix() resolution for the new 0% rules. (178921722)
  • Fixed a flex item with min-width: min-content being clamped to a smaller max-width. (178777567)
  • Fixed :last-child and related selectors incorrectly gating on parser state outside of style resolution. (178879939)
  • Fixed synthetic bold not being applied for a variable font whose @font-face font-weight descriptor explicitly restricts it to normal. (179001275)
  • Fixed changing the color-scheme of an <iframe> not invalidating the appearance of the embedded document. (179177141)
  • Fixed an issue where CSS math functions produced an incorrect signed zero for subtraction, min(), max(), clamp(), and mod(). (179534440)
  • Fixed serialization of hsl() and hwb() colors that contain at least one none component value so they preserve their hsl()/hwb() function rather than converting to rgb(). (179854247)
  • Fixed CSS scroll snap re-snap to prefer the fragment-targeted (:target) snap area over other aligned snap targets. (180108825)
  • Fixed an issue where input[type=hidden] was not set to display: none !important in the user-agent stylesheet. (180137214)
  • Fixed the CSS preload scanner failing to preload @import rules that follow an @layer statement rule. (180170656)
  • Fixed serialization of CSSViewTransitionRule. (180170814)
  • Fixed MediaList.deleteMedium() to parse its argument as a media query and remove all matching queries. (180270019)
  • Fixed MediaList.appendMedium() to parse its argument as a single media query and suppress duplicates. (180291283)
  • Fixed grid items with stretch or fit-content preferred sizes computing incorrect minimum-content contributions when sizing tracks. (180748205)
  • Fixed inserting a CSS rule while a view transition is active causing the group animation to snap to its final state. (181100818)
  • Fixed CSS var() to only resolve its fallback when the first argument resolves to the guaranteed-invalid value. (181114298)
  • Fixed navigating away from a render-blocked document before its first rendering opportunity incorrectly firing pagereveal and starting an outbound cross-document view transition. (181191512)
  • Fixed anchor-center in vertical writing modes not being scroll-adjusted along the block axis. (181413103)

Canvas

  • Fixed an issue where a 2D canvas element unnecessarily forced a compositing layer. (172864747)
  • Fixed canvas 2D context to set the origin-clean flag when reset. (177858398)
  • Fixed canvas fillText with textAlign=center misplacing complex-shaped text. (178682402)

Editing

  • Fixed an issue where characters styled with ::first-letter were not selectable. (5688237)
  • Fixed an issue where explicitly setting a font size that matched a legacy <font> size would be incorrectly substituted with the legacy size, causing inconsistent rendering across different default font size configurations. (15292320)
  • Fixed drag images of DOM elements with CSS transforms not rendering correctly. (99614217)
  • Fixed an issue where the Font Picker style selection became unusable after changing fonts when editing multiple lines of text. (110651645)
  • Fixed an issue where adjusting text selection with touch handles was prevented by JavaScript touch event handling on some websites. (151851274)
  • Fixed an issue where execCommand('FormatBlock') did not preserve inline styles of replaced block elements, causing text formatting to be lost when pasting content. (157657531)
  • Fixed opaque DOM mutations coming from dictation on iOS. (163454428)
  • Fixed an issue where text-indent flickered or was ignored on contenteditable elements while typing. (170280101)
  • Fixed an issue where text selection would jump unexpectedly when selecting absolutely-positioned content inside an element with user-select: none. (170475401)
  • Fixed an issue where text selection was lost when focus transitioned from a contentEditable element to a non-editable target. (171221909)
  • Fixed an issue where composition events were dispatched in the wrong order during IME input, causing incorrect character rendering with Devanagari and other input methods. (174330850)
  • Fixed the Edit menu’s Copy item being incorrectly enabled when there was no selection in the web page. (176061974)
  • Fixed a regression where Vietnamese and Korean keyboard input methods incorrectly exited modeless composition mode, requiring a double spacebar press to complete each word. (176847897)
  • Fixed a recent regression that “Zhuyin – Traditional” input method stalling for multiple seconds when composing Chinese text. (177042301)
  • Fixed typing Hindi (InScript) input on Google Docs. (177643899)
  • Fixed iOS selection behavior so that selection is now possible while focus is inside editable content. (178846185)
  • Fixed deletion in an editable table leaving an empty trailing table row behind. (180877315)
  • Fixed vertical caret movement in editable content ignoring the requested editable-type parameter. (181000174)
  • Fixed the page scrolling on its own while adjusting a text selection inside a fixed-position element, such as a search field pinned to the top of the page. (182770935)

Encoding

  • Fixed an issue where CJK encoding state was not reset appropriately during text decoding. (174649963)

Fonts

  • Fixed an issue where synthetic bold incorrectly added advance width to zero-advance glyphs. (179418570)

Forms

  • Fixed an issue where keyboard commands such as paste did not work in form fields that restrict input to numbers. (4360235)
  • Fixed an issue where keyboard tabbing position was lost when a focused button became disabled, causing focus to jump to the beginning of the page. (120676409)
  • Fixed an issue where a positive margin-top on a <legend> element inside a <fieldset> did not shift the fieldset down. (141267953)
  • Fixed an issue where small range input slider thumbs were difficult to interact with on iPadOS and visionOS by expanding their touch hit area. (147428926)
  • Fixed <datalist> suggestions appearing with white text on a white background in dark mode after typing. (168676757)
  • Fixed an issue on iOS where typing into an <input> element associated with a <datalist> was intercepted by type-to-select behavior. (173346270)
  • Fixed: Made the <input type="checkbox" switch> control behave more like other controls with regards to native appearance CSS properties. (173487610)
  • Fixed identically sized buttons to render with consistent corner radius. (173786057)
  • Fixed an issue where <select multiple> did not always fire onchange when the mouse button was released far outside the element. (173882861)
  • Fixed an issue where <select> control rendering was broken in vertical writing mode. (174068353)
  • Fixed a performance issue where parsing <select> elements with thousands of <option> children via innerHTML caused O(n²) overhead due to repeated list recalculation. (174244946)
  • Fixed an issue where date and time input types without min or max attributes incorrectly matched the :in-range pseudo-class. (174829899)
  • Fixed an issue where cloned <input> and <textarea> elements did not preserve their user-modified state. (174892989)
  • Fixed <option> elements to correctly implement the HTML specification’s dirtiness concept for tracking user-modified selected state. (175306111)
  • Fixed the select picker appearing at an incorrect position when the <select> element is anchor positioned. (175454476)
  • Fixed the default display value for <optgroup> and <option> elements to block, matching the behavior of other browsers. (175473184)
  • Fixed field-sizing: content clipping the placeholder on number inputs that have no value. (175883299)
  • Fixed <option> and <optgroup> elements to match the :disabled pseudo-class when inside a disabled <select>. (176559708)
  • Fixed a box with percentage offset (e.g. top: 100%) being mispositioned when its containing block is out-of-flow with percentage height. (177181803)
  • Fixed the concentric inner-button corner radius on horizontal text form controls incorrectly ignoring the bottom inset. (180869927)
  • Fixed a number of issues with the default styles for customizable <select> with appearance: base-select, including spacing, borders, border-radius, overflow, cursors, optgroup styling, picker dialog shadow, and increased contrast colors. (183345556)

HTML

  • Fixed an issue where an HTML map element without a name attribute did not match its associated image using the id attribute. (12359382)
  • Fixed sequential focus navigation to skip elements that do not meet the specification’s focusability requirements. (103370883)
  • Fixed viewport <meta> parsing to correctly treat form feed as ASCII whitespace per the HTML specification. (108440799)
  • Fixed parsing of javascript: URLs to align with the specification. (147612682)
  • Fixed a severe performance regression causing dynamic insertion of <img> elements with a src attribute to be dramatically slower than other browsers. (166201075)
  • Fixed an issue where a third nested <iframe> using the srcdoc attribute did not render. (167917471)
  • Fixed incorrect parsing of pixel-length margin attributes on <body>, <iframe>, and <frame> elements. (171240848)
  • Fixed popover light dismiss to account for input buttons. (171352032)
  • Fixed popover light dismiss to account for disabled command buttons. (171358576)
  • Fixed an issue where replaceWith() stopped processing remaining nodes if a script in the replacement removed a sibling. (172753019)
  • Fixed an issue where HEIC images were incorrectly converted to JPEG when uploaded via drag-and-drop or file input. (173206598)
  • Fixed the HTML preload scanner to skip preloading stylesheets that have the disabled attribute. (173378582)
  • Fixed an issue where setting the rel attribute on an <a> element multiple times did not clear prior link relations. (173567839)
  • Fixed the HTML parser fast path to correctly process escaped attribute values longer than one character. (173673581)
  • Fixed the HTML parser fast path to correctly detect nested <li> elements. (173983892)
  • Fixed the HTML parser fast path to use the adjusted current node for MathML and SVG integration point checks. (174096305)
  • Fixed document named item collection to include all <object> elements, aligning with other browser engines. (174537345)
  • Fixed window.open() to correctly consume user activation when creating a new browsing context, aligning with the HTML specification. (174587258)
  • Fixed remaining issues with <img sizes="auto"> to fully align with the specification. (174684058)
  • Fixed nested calls to requestClose() incorrectly firing multiple cancel events and causing a stack overflow. (174850509)
  • Fixed requestClose() incorrectly removing the open attribute when called on a disconnected dialog element. (174855725)
  • Fixed an issue where dir=auto on <slot> elements did not update when slotted content changed. (174871706)
  • Fixed an issue where <option> elements rendered incorrectly when the label attribute was empty. (174979446)
  • Fixed an issue where the preload scanner incorrectly skipped <source> elements with an empty type attribute inside <picture>. (175094037)
  • Fixed innerText to emit a newline for empty <option> or <optgroup> inside <select>. (175245381)
  • Fixed HTML floating-point number parsing to correctly handle values with a leading + sign. (175300431)
  • Fixed innerText to no longer emit newlines for visibility: hidden block elements. (175569426)
  • Fixed innerText to correctly emit blank lines around <p> elements regardless of their CSS display value. (175729427)
  • Fixed the speculative preload scanner to no longer incorrectly preload scripts inside SVG elements. (175800116)
  • Fixed <a rel="ar"> elements wrapping <model> elements to correctly enter ARQL without extra steps and to display the AR badge. (176410897)
  • Fixed innerText on tables to no longer emit spurious trailing newlines and to preserve row-exit newlines after empty rows. (176635985)
  • Fixed the HTML preload scanner not preloading resources referenced by legacy <image> tags. (176712749)
  • Fixed a regression that broke pushState with custom application URL schemes. (177547157)
  • Fixed outerHTML setter to align with the HTML standard. (177788638)
  • Fixed fragment parsing of xmlns="" inheritance and annotation-xml encoding. (177808494)
  • Fixed createHTMLDocument() to no longer leave the body in a parsing state. (178440940)
  • Fixed text fragment matching so that a prefix is no longer matched outside of a word boundary near the start of the document. (178467104)
  • Fixed an issue where <link rel=preload as=json> incorrectly triggered a preload. (179843455)
  • Fixed an issue where the deprecated align="center" attribute was not treated as identical to align="middle" per spec. (180128710)

Images

  • Fixed an issue where inserting an image with a srcset attribute into a dynamically created iframe resulted in an invisible image. (66849050)
  • Fixed naturalWidth and naturalHeight returning incorrect values for SVG images without intrinsic dimensions. (141196049)
  • Fixed an issue where HDR images would flicker and lose their HDR appearance when overlapping layers animate. (163382580)
  • Fixed an issue where adopting a standalone img element did not update its image data. (172856773)
  • Fixed images appearing then quickly disappearing on OpenTable search results. (176275269)
  • Fixed rendering performance of HDR images that have gain-maps by using GPU-backed surfaces. (176605566)
  • Fixed <picture> <source> candidates being speculatively preloaded even when the inner <img> has loading=lazy. (177833110)
  • Fixed HTMLImageElement.decode() to no longer resolve spuriously after adoption, src change, or cached-image reuse. (178118012)
  • Fixed a regression where RGB gain map images were decoded to 8 bits per channel, causing a color shift and incorrect brightness. (179152566)
  • Fixed handling of the gain-map target pixel format when decoding HDR images to fall back safely when the format cannot be parsed. (181180757)

JavaScript

  • Fixed multiple top-level await correctness bugs with a rewrite of the ES module loader for standards compliance. (97370038)
  • Fixed regular expressions in Unicode mode to not count non-capturing groups and modifiers toward the number of available backreferences. (167746769)
  • Fixed %TypedArray%.prototype.subarray to calculate beginByteOffset correctly to align with ECMA-262. (168143600)
  • Fixed the trace behavior of RegExp.prototype[Symbol.split] to align with ECMA-262. (168288878)
  • Fixed Array.prototype.concat to correctly handle arrays with indexed accessors, preventing getter reentry from bypassing Symbol.isConcatSpreadable checks. (172237596)
  • Fixed an issue where a greedy or non-greedy non-BMP character class in a regular expression could advance the index past the end of input. (172978772)
  • Fixed an issue where class instance field initializers did not have the correct evaluation context when used inside arrow functions and nested scopes. (173296563)
  • Fixed TypedArray [[Set]] to check the receiver before writing to the typed array. (173386404)
  • Fixed %ArrayIteratorPrototype%.next() to return { done: true } instead of throwing a TypeError when the source TypedArray is detached and the iterator has already completed. (173759106)
  • Fixed an issue where a fixed-count mixed-width character class in a regular expression did not correctly restore the index on backtrack. (173972458)
  • Fixed an issue where regular expressions with non-BMP characters could skip valid match positions when alternating between patterns. (174200307)
  • Fixed an issue where regular expression captures were not properly cleared when backtracking out of fixed-count parenthesized groups and negative lookaheads. (174201284)
  • Fixed an issue where import { “``" as x } was incorrectly treated as a namespace import instead of a named import using the string “" as a ModuleExportName. (174314099)
  • Fixed an issue where RegExp.prototype.exec and RegExp.prototype.test could match against a stale pattern if lastIndex has a valueOf that calls RegExp.prototype.compile. (174461752)
  • Fixed an issue where async functions using module-scoped variables could fail when the DFG JIT optimized scope resolution. (174626957)
  • Fixed an issue where Intl.Segmenter with granularity: "word" incorrectly reported isWordLike: false for numeric segments. (175057894)
  • Fixed Object.defineProperties to call Proxy traps in the correct order. (175068687)
  • Fixed an issue where Intl.Locale did not canonicalize before overriding the language. (175092327)
  • Fixed time zone identifiers to return primary IANA time zone IDs instead of legacy ICU identifiers. (175098682)
  • Fixed the Array ToPrimitive fast path incorrectly ignoring overrides of Object.prototype.valueOf. (175122250)
  • Fixed input position corruption in regular expression backward matching when rewinding over a surrogate pair. (175122467)
  • Fixed Intl.DateTimeFormat to preserve the original legacy timezone identifier instead of replacing it with the primary IANA ID. (175206605)
  • Fixed Promise.prototype.finally to throw a TypeError when @@species is not a constructor, matching the behavior of other browsers. (175290627)
  • Fixed the regular expression engine to reject dangling hyphens in character class syntax when using the /v flag. (175559808)
  • Fixed a performance issue with module resolution by limiting cache population to star-resolution and indirect-resolution cases. (175826413)
  • Fixed a performance issue with TypedArray.prototype.lastIndexOf by adding SIMD-accelerated reverse search for numeric types. (175904377)
  • Fixed a performance issue where building a module namespace with many export statements was significantly slower than necessary. (175949532)
  • Fixed DataView constructor to match specification-defined argument validation order and error throwing behavior. (176110210)
  • Fixed an issue where Array.prototype.concat could produce incorrect results when combining arrays with incompatible indexing types. (176219964)
  • Fixed multiple TypedArray constructor edge cases involving buffer sequences to align with the specification. (176724918)
  • Fixed WebAssembly.Memory and WebAssembly.Module to align their cloning and transferring behavior with SharedArrayBuffer. (176792374)
  • Fixed Array.prototype.join to include prototype elements added during element toString invocation. (178055452)
  • Fixed an issue where Temporal.Instant operations were not aligned with the spec’s abstract operations. (179844859)

MathML

  • Fixed the MathML operator dictionary to correct the stretchy property for several operators, resolving Web Platform Test failures. (170901728)
  • Fixed an issue where symmetric non-stretchy large operators were not centered around the math axis. (170905663)
  • Fixed an issue where dynamic changes to <mo> element attributes did not trigger a relayout. (170907029)
  • Fixed an issue where minsize and maxsize defaults and percentages did not use the unstretched size as specified. (170908253)
  • Fixed positioning of the <mprescripts> element within <mmultiscripts> layout. (170909975)
  • Fixed an issue where the MathML fraction bar was not painted when its thickness was equal to its width. (170934351)
  • Fixed an issue where <none> and <mprescripts> elements were not laid out as <mrow> elements in MathML. (170940035)
  • Fixed an issue where MathML token elements ignored -webkit-text-fill-color when painting math variant glyphs. (172020318)
  • Fixed padding and border rendering on <msqrt> and <mroot> elements and corrected token sizing for mathvariant. (173081436)
  • Fixed absolute positioning of elements inside MathML by ensuring logical height is updated. (173088146)
  • Fixed tabIndex values not being set correctly for MathML elements. (174734133)
  • Fixed spacing values for prefix operators +, , ±, , , and infix operator in the MathML Core operator dictionary. (176652211)
  • Fixed the operator dictionary entry for the prefix operator to use the correct spacing values (3, 0) instead of (2, 1). (176693587)
  • Fixed nonce-hiding support for MathML elements to align with the HTML specification. (176875058)
  • Fixed MathML operators routed through MathOperator being invisible when their glyph only exists in a fallback font. (178096170)
  • Fixed MathML to use MathML Core fallback values for script layout constants. (179177178)

Media

  • Fixed <audio> and <video> controls rendering incorrectly when rotated via CSS transform. (37516619)
  • Fixed an issue where decoding WebM audio files with more than two channels would fail. (82160691)
  • Fixed an issue where preservesPitch and playbackRate were not correctly handled on an HTMLMediaElement connected to an AudioContext via createMediaElementSource. (93275149)
  • Fixed MediaCapabilities.decodingInfo() incorrectly reporting VP8 in WebM as not supported. (127339546)
  • Fixed WebVTT to not display cues that are larger than the viewport. (136809012)
  • Fixed an issue on iPad where exiting fullscreen on a media document incorrectly navigated back to the previous page instead of returning to the inline view. (137220651)
  • Fixed an issue where the WebCodecs VideoDecoder API output frames in an incorrect order for videos containing B-frames. (145093697)
  • Fixed an issue where the darkening overlay on inline video controls made accurate scrubbing difficult and displayed video content incorrectly on macOS. (161271114)
  • Fixed an issue where WebM with VP9/Vorbis fallback would not play. (164053503)
  • Fixed video playback failing when the declared MIME type in a <source> element does not match the actual content type served by the server. (166181001)
  • Fixed an issue where text selection was broken after pausing a video when the media player ran in the content process. (167727538)
  • Fixed HTMLMediaElement.currentTime to report smoothly progressing values instead of updating only at fixed intervals. (170115677)
  • Fixed an issue where MP4 files containing Opus audio tracks could not be decoded with decodeAudioData. (170196423)
  • Fixed an issue where the VideoFrame constructor did not handle the video color range correctly for NV12 (I420 BT601) video frames. (170299037)
  • Fixed an issue where Live Text selection was unavailable on paused fullscreen videos. (170817667)
  • Fixed an issue where FairPlay-protected VP9 content failed to play via MediaSource. (171210968)
  • Fixed media controls not appearing when tapping videos in the LinkedIn feed on iPad. (171231918)
  • Fixed an issue where autoplay would proceed before default text tracks finished loading. (171699293)
  • Fixed the currentTime getter to return defaultPlaybackStartPosition when no media player exists. (171722368)
  • Fixed HTMLMediaElement to fire a timeupdate event when resetting the playback position during media load as required by the specification. (171785463)
  • Fixed an issue where the media player preload attribute was not properly updated when the autoplay attribute was set. (171883159)
  • Fixed an issue where seeking in a WebM video did not work correctly while content was still loading. (172473039)
  • Fixed an issue where media playback could not move to the next item in a playlist when the tab was in the background. (172676372)
  • Fixed an issue where scrubbing a video in full-screen mode could cause it to exit full-screen. (172682230)
  • Fixed an issue where HDR video content appeared washed out due to colorspace information being lost during processing. (172721079)
  • Fixed Encrypted Media Extensions to check support for the full content type including codecs, rather than only the MIME type. (173852931)
  • Fixed an issue where setting HTMLMediaElement.volume had no effect when the element was connected to an AudioContext. (174278899)
  • Fixed ImageCapture to correctly queue takePhoto() and applyConstraints() requests to avoid concurrent capture session reconfiguration. (174950018)
  • Fixed a regression where videos would stop playing and lose audio after a few seconds on some websites. (174966899)
  • Fixed an issue where U+0000 (NULL) characters were not allowed in VTTCue text content. (175084171)
  • Fixed video content disappearing after switching to another tab and back. (175257980)
  • Fixed WebVTT cue settings line parsing failures. (175296476)
  • Fixed subtitles and closed captions not appearing in fullscreen video on iOS. (175298523)
  • Fixed <audio controls> to not show the “Subtitles” option when no subtitle track is present. (175357130)
  • Fixed ::cue() selectors to correctly match the WebVTT root object in addition to child nodes. (175550173)
  • Fixed currentTime on iOS to update more frequently during media playback. (175774587)
  • Fixed Media Source Extensions readyState not being updated immediately when playback stalls due to a gap in buffered data. (176330683)
  • Fixed timeupdate events being fired during seeking before the seek operation completes. (176861767)
  • Fixed the ended event not always firing when the MediaSource duration is changed to match the current playback position. (176863546)
  • Fixed a MediaSource issue where the decode-key cleanup in coded frame processing was incorrectly removing non-orphaned samples. (176971800)
  • Fixed currentTime() returning a stale value after the playback rate was changed from zero to a non-zero value. (177046564)
  • Fixed MSE SourceBuffer.remove() to no longer remove an extra sample, and fixed buffered ranges to cover the correct ranges. (177065364)
  • Fixed an HTMLMediaElement that doesn’t display in an infinite scrolling webpage to use a viewport listener that notifies the media player about the visibility of the element. (177081214)
  • Fixed EME to use a 10 second key wait timeout. (177936893)
  • Fixed EME OCDM to prevent a spurious keystatuses event when all keys have expired. (177939767)
  • Fixed getSupportedCapabilitiesForAudioVideoType (EME) to no longer include unsupported capabilities. (178142768)
  • Fixed MediaSession.setActionHandler to not throw an exception when called. (178167294)
  • Fixed AudioData.copyTo to throw RangeError when frameOffset equals numberOfFrames. (178609688)
  • Fixed MIDI and AVI MIME signature matching due to a typo in MIME sniffing. (178661530)
  • Fixed PannerNode to no longer produce non-finite samples for edge-case distance parameters. (178784571)
  • Fixed the VP codec parameters string to no longer zero-pad bitDepth based on transferCharacteristics. (179210193)
  • Fixed an issue where the MediaSource text-track removal loop always processed only the last track. (179508398)
  • Fixed an issue where isValidVideoFrameBufferInit() tested displayWidth and displayHeight presence against themselves instead of the correct properties. (179514279)
  • Fixed an issue where MediaMetadata artwork sizes parsing read the wrong substring for the height value. (179523057)
  • Fixed an issue where the pictureInPictureElement getter inverted the shadow-host connectivity check. (179675087)
  • Fixed ArrayBuffer-backed YUV VideoFrame with a visibleRect rendering with offset chroma channels. (180202939)
  • Fixed video playback of streams from certain sources such as security cameras not working. (180411019)
  • Fixed transient device rotation resulting in captured video frames having the wrong orientation. (180429147)
  • Fixed Media Source Extensions playback and seek by loosening the gap tolerance between buffered ranges. (180439090)
  • Fixed being unable to enter Picture-in-Picture again after navigating to another video. (182971786)
  • Fixed video showing a black screen while audio continued to play after the video decoder was invalidated. (184041554)

Model Element

  • Fixed setting the entityTransform on a <model> element while the model is unloaded or hidden. (179114750)
  • Fixed <model> elements losing gesture interactivity after the model player is reloaded (for example, when the model scrolls out of and back into the viewport). (179249565)

Navigation

  • Fixed a <meta http-equiv="refresh"> to a URL differing only in fragment identifier being incorrectly treated as a page reload. (176933795)

Networking

  • Fixed redirects to data: URLs to be blocked for subresources such as images and scripts, aligning with the Fetch specification. (74165956)
  • Fixed XMLHttpRequest incorrectly dropping the request body during redirects. (98459882)
  • Fixed X-Frame-Options to only strip tab or space characters, not vertical tabs. (126915315)
  • Fixed an issue where Safari’s address bar could display an internationalized domain name (IDN) homograph as a visually identical legitimate Latin domain, enabling potential phishing attacks. (166796168)
  • Fixed an issue where the preload scanner did not include integrity metadata in requests, causing incorrect Integrity-Policy violation reports. (168280745)
  • Fixed range request validation to properly handle HTTP 416 (Requested Range Not Satisfiable) responses. (168487440)
  • Fixed a regression where the referrer could be missing after a process-swap navigation. (169006635)
  • Fixed incorrect URL query percent-encoding when using non-UTF-8 character encodings such as iso-8859-2, windows-1250, and gbk. (169566553)
  • Fixed an issue where the multipart form data parser incorrectly required CRLF after the closing delimiter, causing some web applications to fail to render correctly. (174348783)
  • Fixed an issue where partitioned cookies could not be deleted via WKHTTPCookieStore. (174557252)
  • Fixed URL parsing for sendBeacon() and the Media Session API. (177330315)
  • Fixed an issue where WebKit refused to load valid ASCII domains starting with xn-- that did not pass strict IDNA 2008 validation, aligning behavior with the WHATWG URL Standard. xn-- is the prefix of a punycode-encoded non-ASCII domain. (177686282)
  • Fixed an issue where arbitrary Content-* headers from 304 responses were not used to update cached entries. (179864251)
  • Fixed an issue where the Cache-Control request directives max-age, min-fresh, and no-store were not honored. (179865576)
  • Fixed an issue where Cache-Control: public was not honored on responses with unknown status codes. (179870099)
  • Fixed an issue where the HTTP cache did not store responses with explicit freshness for all status codes. (179871690)
  • Fixed URL path separators being encoded as %2F following a percent-encoded Armenian path segment. (180067095)

PDF

  • Fixed an issue where panning a zoomed-in PDF on iOS would frequently rubber band back to the starting position. (156854435)
  • Fixed broken text underlines in PDFs created by WKWebView‘s PDF export API. (180631575)
  • Fixed only one PDF HUD responding to mouse clicks on pages with multiple embedded PDFs. (183273642)

Performance

  • Fixed an extremely slow page load on iPhone caused by blur filters with large radii (172480480)

Printing

  • Fixed an issue where animations were ignored during print, causing missing content on animated pages. (36901701)
  • Fixed an issue where printing light text on a dark background with backgrounds disabled could result in invisible text. (170070133)
  • Fixed a regression where printing a WebView embedded in an enclosing NSPrintOperation dropped all text. (174756900)

Rendering

  • Fixed an issue where table cells with rowspan values exceeding the actual number of rows were incorrectly computing heights. (3209126)
  • Fixed changes to a filter: drop-shadow() not repainting the area outside the element’s boundaries. (49387957)
  • Fixed an issue where ::first-letter styles caused Range.getClientRects() and Range.getBoundingClientRect() to return incorrect dimensions. (71546397)
  • Fixed incorrect distributed height in table rows when a <td> element has an explicit height set. (78549188)
  • Fixed an issue where U+2028 LINE SEPARATOR was not rendered as a forced line break. (88470339)
  • Fixed an issue where a block formatting context with margin-start could overlap an adjacent float. (93187697)
  • Fixed position: relative on table rows (<tr>) to correctly establish a containing block for absolutely positioned descendants. (94294819)
  • Fixed <marquee> elements causing incorrect table width calculations. (99826593)
  • Fixed Google search-suggestion font sizes increasing on rotation from portrait to landscape. (113801810)
  • Fixed boxes in the top layer to use the initial containing block as their static-position rectangle. (155495104)
  • Fixed an issue where a flex item containing a percentage-height image did not shrink correctly around the image. (156902823)
  • Fixed an issue where form controls with height: 100% in auto-height containers incorrectly resolved to zero height. (161699543)
  • Fixed incorrect box sizing on inline elements when they have no siblings and their padding-left plus margin-left equals zero. (162376969)
  • Fixed a regression where an element inside an iframe gaining its own compositing layer could cause an iframe’s semi-transparent background to appear darker.(163509267)
  • Fixed an issue where space-taking scrollbars did not trigger a proper re-layout when the box size depends on content size. (166836126)
  • Fixed misrendering of Pahawh Hmong text on Wikipedia. (167446860)
  • Fixed an issue where View Transition snapshots were incorrectly stored in sRGB, causing rendering issues with non-sRGB colors. (167634138)
  • Fixed an issue where font subpixel quantization was unnecessarily disabled in some cases, improving text rendering quality. (168088611)
  • Fixed table layout to properly handle visibility: collapse on columns. (168556786)
  • Fixed intrinsic sizing for absolutely positioned replaced elements. (168815514)
  • Fixed text being incorrectly truncated in RTL containers when combined with text-overflow: ellipsis and an inline-block pseudo-element. (168875614)
  • Fixed percentage padding in table cells to resolve against column widths. (168940907)
  • Fixed a regression where hovering over elements could leave repaint artifacts on the page. (169112402)
  • Fixed table height distribution to apply to tbody sections instead of only the first section. (169154677)
  • Fixed an issue where table sections with explicit heights did not properly constrain and distribute space among contained rows. (169235210)
  • Fixed an issue where images with min-width: fit-content rendered at an incorrect width. (169359566)
  • Fixed an issue where height: 100% was incorrectly calculated for replaced elements like images serving as grid items nested inside a flexbox. (169431440)
  • Fixed an issue where images were incorrectly stretched in certain page layouts. (170270187)
  • Fixed an issue where Find in Page scrolled to the wrong location when matching text inside elements with user-select: none. (170477571)
  • Fixed the baseline calculation for inline-block elements so that when overflow is not visible, the baseline is correctly set to the bottom margin edge. (170575015)
  • Fixed an issue where replaced elements did not correctly apply min-height and min-width constraints in certain configurations. (170765025)
  • Fixed an issue where overlay backgrounds would briefly dim incorrectly when de-compositing in a scrollable container. (171024685)
  • Fixed a regression where sticky-positioned elements inside overflow containers could appear in front of content that should overlap them. (171179878)
  • Fixed an issue where auto table layout did not honor max-width on table cells when distributing width between them. (171459245)
  • Fixed an issue where border-spacing incorrectly included collapsed columns in auto table layout calculations. (171468102)
  • Fixed an issue where percentage-height children of table cells with unresolvable percentage heights were not sized intrinsically. (171469500)
  • Fixed an issue where cell backgrounds in collapsed-border tables extended into adjacent cells’ border space at table edges. (172068907)
  • Fixed an issue where if a document in an iframe uses @prefers-color-scheme, it does not follow the color-scheme set by grandparents of the iframe. (172229372)
  • Fixed an issue where list item margins were computed incorrectly when the page was zoomed in or out. (172312498)
  • Fixed an issue where about:blank iframes did not always have a transparent background. (172400258)
  • Fixed an issue where a right-floated table could overlap another table. (172655655)
  • Fixed an issue where grid containers failed to avoid float boxes. (172655720)
  • Fixed an issue where an anonymous block created for list markers was not properly collapsed when block content prevented line-box parenting. (172686060)
  • Fixed text-wrap: balance not being applied to content with -webkit-line-clamp. (172715503)
  • Fixed an issue where checkboxes could overlap with adjacent text. (172741572)
  • Fixed an issue where checkbox outlines appeared misaligned. (172742551)
  • Fixed an issue where list markers rendered on the wrong line when list items started with empty inline elements. (172762578)
  • Fixed an issue where U+2029 PARAGRAPH SEPARATOR was not treated as a forced line break. (173106856)
  • Fixed a black region appearing on the right side of swift.org when the sidebar is open. (173191807)
  • Fixed an issue where tiles were missing after navigating back in history. (173288233)
  • Fixed an issue where view transition snapshots could capture stale transform values for accelerated CSS transform animations. (173323193)
  • Fixed an issue with outside list markers when blockification prevents line-box parenting. (173417560)
  • Fixed a regression where nested empty inline boxes accumulated an incorrect vertical offset, causing inline elements to stack as block-level elements. (173723162)
  • Fixed an issue where pseudo-elements were incorrectly included in outline rect collection. (174033087)
  • Fixed an inverted Y-axis comparison that could cause incorrect caret positioning. (174144220)
  • Fixed <legend> to mask the <fieldset>‘s border correctly when it has a negative left margin. (174185071)
  • Fixed an issue where <br> elements with line-height: 0 still created extra vertical space, failing to respect the declared line height. (174400946)
  • Fixed auto outlines to more closely follow the border radii of elements. (174466854)
  • Fixed how gradients are rendered to improve performance. (174880197)
  • Fixed image-orientation being ignored for background-image, border-image, and list-style-image. (174894122)
  • Fixed a white-space: pre-wrap layout issue with justified text. (174937310)
  • Fixed an image with min-height: min-content inside a column flex container not shrinking to preserve its aspect ratio. (174999995)
  • Fixed an issue with flex-wrap and flex factor computation for wrapping flex items. (175012395)
  • Fixed vertical writing-mode content incorrectly wrapping when the parent has auto height. (175123356)
  • Fixed a column-wrap flex container with flex-basis: 0 wrapping items into extra columns instead of stacking them when nested inside another column flex container. (175195518)
  • Fixed incorrect bounding box position for newline characters. (175243361)
  • Fixed an issue where a child element with filter: blur() ignored border-radius overflow clipping from its parent. (175519148)
  • Fixed an issue where absolutely positioned tables with content exceeding their declared width were incorrectly positioned. (175755871)
  • Fixed height calculations for absolutely positioned tables with percentage-sized children. (175762381)
  • Fixed an issue where containers with block-in-inline content did not expand when max-height was removed. (175799547)
  • Fixed an issue where absolutely positioned tables with explicit percentage or fixed heights did not resolve correctly against their containing block. (175852400)
  • Fixed always-on scrollbar thumbs not rendering on the root element of nested documents with display: flex. (175866046)
  • Fixed absolutely positioned tables ignoring min-height and shrinking below their content height. (175883577)
  • Fixed drop-shadow filters and transform: translate() incorrectly clipping nested elements after a regression. (175905543)
  • Fixed absolutely positioned tables ignoring max-height constraints. (175932457)
  • Fixed scrollbar-gutter placement on the root element in RTL layouts. (175939512)
  • Fixed an issue where a flex item with aspect-ratio and content-box padding computed the wrong height in a column flex container. (176033726)
  • Fixed a repaint issue where table rows did not repaint their previous position after a preceding row changed size, causing content to appear at both the old and new locations. (176172404)
  • Fixed an issue where block-level boxes nested within inline elements were not properly aligned when using align-content: center. (176173122)
  • Fixed a regression that caused incorrect layout in some content using stretch. (176398251)
  • Fixed intrinsic sizing for non-replaced elements with percentage dimensions. (176493856)
  • Fixed justification expansion to apply around CJK Unified Ideographs Extensions E, F, G, and H. (176759766)
  • Fixed RTL position-fixed elements losing their contents when the document is scrolled on iOS. (177454608)
  • Fixed the background of a composited <html> element not being repainted when the <body> background changed. (177975964)
  • Fixed an issue where text changes that did not modify the text element’s size in a flex layout on a new compositing layer did not trigger re-rendering. (179292409)
  • Fixed an issue where an underline was drawn twice on <sup> elements. (179537119)
  • Fixed an issue where an underline under a <sup> element was offset by one device pixel from the rest of the line on subpixel displays. (179586525)
  • Fixed an issue where the ex unit in text-box-edge misplaced the propagated underline, causing it to be painted twice. (179769451)
  • Fixed an issue where min-width was not honored over max-width when sizing a shrink-to-fit container around a replaced element. (179935558)
  • Fixed an issue where an <img> embedding an SVG with a near-integral intrinsic width rendered one device pixel narrower than expected. (180490343)
  • Fixed elements with filter: drop-shadow() not being fully repainted when a child is resized. (181284741)

SVG

  • Fixed SVG applying text-decoration to elements with display: contents. (85691104)
  • Fixed SVG <text> with textLength scaling each glyph separately when x or y is a list. (94161279)
  • Fixed an issue where backslash-escaped dot characters in SVG animation timing attribute ID references were not parsed correctly. (94260935)
  • Fixed an issue where SMIL animations of href or xlink:href on SVG <image> elements had no visual effect. (96316808)
  • Fixed the resolved value for the width and height properties on SVG <rect>, <image>, <svg>, and other elements. (96320059)
  • Fixed an animated GIF freezing when referenced by SVG <use> and the opacity was adjusted. (96837306)
  • Fixed an issue where SVG animation did not clear the animated CSS property when attributeName was dynamically changed. (97097883)
  • Fixed an issue where box-shadow was not drawn on fixed-positioned SVG elements. (97098951)
  • Fixed :visited link color to properly propagate to SVG through currentColor. (98776770)
  • Fixed an issue where a CSS filter referencing an SVG filter via url(#id) was not invalidated when the filter content changed. (101870430)
  • Fixed vector-effect to apply a transform to path geometry rather than to stroke geometry. (103573160)
  • Fixed negative stroke-dashoffset values rendering with incorrect offsets when stroke-dasharray has an odd number of values. (103596361)
  • Fixed <animateMotion> non-path animations to apply the rotate attribute. (110915794)
  • Fixed SVG2 systemLanguage attribute to improve parsing and compliance with the specification. (116427520)
  • Fixed removing an item from SVGTransformList to properly allow attribute removal. (117840533)
  • Fixed SVG SMIL length animations to reject invalid to, from, and by values such as those with leading whitespace. (118537155)
  • Fixed Unicode text with complex scripts not rendering correctly along a curved <textPath>. (120284006)
  • Fixed an issue where the XML document parser did not defer inline script execution until pending stylesheets had loaded. (122574381)
  • Fixed an issue where animated SVG images referenced via an <img> tag did not animate correctly due to repaint artifacts with object-fit. (141815698)
  • Fixed SVG elements with display: contents being visually hidden. (141825746)
  • Fixed an SVG <tspan> positioning bug with xml:space="preserve" that caused multi-line text to render incorrectly. (143722975)
  • Fixed an issue where SVG elements referencing non-existent filter IDs were not rendered. (164046592)
  • Fixed SVGPathElement.getTotalLength() and . SVGPathElement.getTotalLength.getPointAtLength() to respect the CSS d property. (167195297)
  • Fixed offsetX and offsetY for SVG elements to use the outermost SVG as the base for coordinate calculation. (168548585)
  • Fixed an issue where URL fragments were not percent-decoded before being used for SVG references. (169582378)
  • Fixed: Updated the default values of fx and fy attributes on SVGRadialGradientElement to 50% to align with the SVG2 specification. (169645572)
  • Fixed SVGAnimatedRect.baseVal to ignore invalid values set on the viewBox attribute, such as negative width or height, aligning with Firefox and Chrome. (170214971)
  • Fixed SVG length attributes to reset to their default values when removed, rather than retaining previously set values. (170360351)
  • Fixed an issue where getScreenCTM() did not include CSS transforms and zoom contributions in the legacy SVG rendering path. (171525696)
  • Fixed a rounding issue for SVG rect height with em and percentage values. (171587382)
  • Fixed SVGLength.convertToSpecifiedUnits() failing when converting from px to %, em, or ex. (172056830)
  • Fixed an issue where an SVG <image> element was not repainted when the href attribute was removed. (172530834)
  • Fixed an issue where an invalid attribute type in one SVG animation group prevented all subsequent animation groups from running. (172593109)
  • Fixed a regression where wheel events were not dispatched to an empty <svg> root element. (172909441)
  • Fixed an issue where SMIL parseClockValue did not reject out-of-range minutes and seconds values per the SMIL timing specification. (173577212)
  • Fixed the SMIL values attribute to preserve empty values and handle trailing semicolons. (173594455)
  • Fixed SMIL repeat(n) event conditions not triggering animations. (173599629)
  • Fixed SVG2 getStartPositionOfChar and getEndPositionOfChar to be more compliant with the specification. (174145885)
  • Fixed SVG intrinsic sizing so that max-content and min-content use the viewBox aspect ratio when intrinsic sizes are missing. (174568894)
  • Fixed glyph-orientation-vertical: auto to use UTR#50 Vertical Orientation properties for correct character orientation in vertical text. (175064567)
  • Fixed SVG intrinsic aspect ratio being incorrectly suppressed when preserveAspectRatio is set to none. (175173375)
  • Fixed SVG images without complete intrinsic dimensions incorrectly using ratio-based scaling for background-size. (175345107)
  • Fixed glyph-orientation-vertical: auto to decode surrogate pairs for UTR#50 lookup. (175570881)
  • Fixed the SMIL clock value parser to accept hours greater than 99 and reject malformed seconds values. (175593583)
  • Fixed stroke-dasharray interpolation to use least common multiple for list length matching and corrected composition behavior. (175598175)
  • Fixed SVG geometry presentation attributes like cx, cy, r, rx, ry, x, y, width, and height being incorrectly applied to elements such as <g> on which they are not permitted. (175672111)
  • Fixed handling of pathLength="0" and negative pathLength for stroke dashing. (175928827)
  • Fixed an issue where @prefers-color-scheme in an SVG image will sometimes not follow the system color appearance. (176413340)
  • Fixed getScreenCTM() returning an incorrect matrix when the document is scrolled under a CSS-transformed ancestor. (176814876)
  • Fixed an issue where an SVG filter applied via CSS to an element positioned below the viewport rendered a spurious black square at the viewport origin. (177482001)
  • Fixed hit-testing of clip-path with nested objectBoundingBox <clipPath> to use the correct reference box. (177605894)
  • Fixed SVG overflowing edges when offset by a fractional value. (177630386)
  • Fixed IntersectionObserver not computing intersections for SVG element roots. (177807041)
  • Fixed feGaussianBlur not applying when stdDeviation contains a 0 in the second component. (177906905)
  • Fixed SVG vertical <text> to honor the CSS text-orientation property instead of only the deprecated glyph-orientation-vertical presentation attribute. (178044217)
  • Fixed an issue where the per-character rotate attribute was discarded on a <textPath>, so it now composes with the path tangent angle. (178044478)
  • Fixed an issue where getRotationOfChar() returned approximately 360° instead of 0° for full-turn rotations after normalisation into the [0°, 360°) range. (178044934)
  • Fixed an issue where a non-BMP character before a <tspan> boundary shifted the x and y value lists by one position. (178360036)
  • Fixed interpolation of arc flags in the d property to treat them as non-zero booleans. (178950624)
  • Fixed pathFromEllipseElement to honor auto values for rx and ry, so APIs such as getTotalLength() return the correct length for ellipses. (178959205)
  • Fixed animated GIFs freezing when presentation attributes are changed on an SVG image referenced by a <use> element. (179414226)
  • Fixed an issue where getBoundingClientRect() on an SVG <tspan> element returned the bounds of the entire <text> element instead of the <tspan>‘s own area. (179626476)
  • Fixed an issue where a nested clip-path on a <clipPath> element ignored css zoom. (180162723)
  • Fixed several SVG styling spec-compliance failures. (181052042)
  • Fixed dynamic changes to orient and markerUnits on <marker> not repainting elements that reference it. (181106538)
  • Fixed SVG SMIL number, integer-optional-integer, number-optional-number, and path animations to not apply when their from, to, or by values fail to parse. (181308150)

Scrolling

  • Fixed an issue on iOS where calling scrollTo during a momentum scroll incorrectly interrupted the scroll, ensuring that momentum scrolling continues as expected and smooth scrolling behaves properly. (41949531)
  • Fixed scrolling of the “Add Contacts” drop-down on outlook.live.com by narrowing the scope of a quirk. (48008837)
  • Fixed an issue on iOS where programmatic smooth scrolling with scroll-snap-type: mandatory failed after the browser chrome was hidden. (100727098)
  • Fixed an issue where scrollIntoView with nearest alignment incorrectly aligned to the far edge for an oversized target positioned before the scrollport. (106356373)
  • Fixed an issue where interrupting scroll momentum caused the scrolling container to stop rendering and hit-testing to be misplaced. (116205365)
  • Fixed an issue on iOS where restored scroll position was incorrect after relaunching Safari. (127308062)
  • Fixed a passive: false wheel event listener combined with overscroll-behavior: contain preventing scrolling. (137757208)
  • Fixed an issue where tabbing in a scroll container with scroll-padding did not scroll the focused element into view. (147513379)
  • Fixed pan gestures incorrectly passing through scroll containers laid over vertical-rl body text. (160788907)
  • Fixed a regression on macOS 26 where horizontal rubber-banding interfered with vertical scrolling. (165449829)
  • Fixed an issue on macOS where custom CSS scrollbars could be cut off and the scrollbar corner rect was sized incorrectly. (168566468)
  • Fixed rubberbanding behaving incorrectly when a site triggers a smooth scroll to the top during a rubberband. (170705188)
  • Fixed an issue where pages could become blank and jump to the top after dynamically loading new content when scroll anchoring was enabled. (170889205)
  • Fixed an issue where scroll anchoring could cause pages to scroll to negative offsets. (171221075)
  • Fixed an issue where pages using the Navigation API could have offset hit test locations, making elements unclickable. (171752650)
  • Fixed CSS scroll snap points inside zero-sized elements not working correctly. (172863699)
  • Fixed an issue on iOS where composited layers would briefly flash blank when window.scrollTo() was called synchronously with a DOM layout change. (173197381)
  • Fixed occasional flashes of an incorrect scroll position when scroll anchoring adjusts content while scrolling. (173456210)
  • Fixed an issue where sticky-positioned elements could flicker rapidly after scrolling. (173680821)
  • Fixed an issue where scroll anchoring could cause a page to scroll to the top or bottom automatically. (173885027)
  • Fixed an issue where calling scrollIntoView() on a scrollable element incorrectly scrolled the element’s own contents. (174173683)
  • Fixed scroll anchoring interfering with rubberbanding on some websites. (175195943)
  • Fixed an issue on iOS where scroll position was not preserved correctly when rotating the device on right-to-left pages. (175910769)
  • Fixed top and bottom fixed-position elements flickering during rubber-band scrolling. (176226179)
  • Fixed the scroll anchoring behavior so that the comments panel on Quip is no longer blank when expanded. (178255628)
  • Fixed an issue where scroll snapping selected a snap point that overshot the destination instead of the closest one in the scroll direction. (179549119)
  • Fixed an issue where interrupting a smooth scroll with a new scrollTo() call to a different target fired the scrollend event at the wrong position. (179551854)
  • Fixed an issue where re-snapping after a layout change moved away from a valid scroll position when the snap area was larger than the snapport. (179553122)
  • Fixed CSS scroll snap re-snap to prefer a snap area that contains the focused or fragment-targeted element. (180707984)
  • Fixed the scroll position jumping when a page changes scroll-padding while scroll anchoring is active. (183145868)

Security

  • Fixed an issue where Content Security Policy 'self' did not match script sources in opaque-origin HTTP(S) documents. (178638597)
  • Fixed <object> elements that load images being incorrectly blocked by the img-src Content Security Policy directive. (178772677)
  • Fixed a regression where some websites failed to display and logged Content Security Policy errors in the console. (179684592)
  • Fixed an issue where Content Security Policy incorrectly applied script-src to JSON module imports instead of connect-src. (180006320)
  • Fixed same-page navigations being incorrectly checked against Content Security Policy. (180342503)
  • Fixed Content Security Policy frame-ancestors violations in report-only policies being ignored instead of reported. (180447621)
  • Fixed Content Security Policy parsing to reject trailing characters after the closing quote on nonce-source and hash-source values. (180903857)
  • Fixed Content Security Policy trusted-types expressions to reject trailing characters after keywords and the wildcard. (180973793)

Spatial Web

  • Fixed spatial and panoramic image controls to support RTL language layout and localization of type labels. (161690817)
  • Fixed <model> elements displaying at 100x the expected size for assets authored in tools that use centimeter units. (167805672)
  • Fixed an issue where WebXR viewports did not get an initial value until getViewport() was called. (168125694)
  • Fixed an issue where the <model> element stagemode orbit physics behaved differently between iOS and visionOS. (172189776)
  • Fixed an issue on visionOS where fullscreen video would sometimes jump when exiting fullscreen if the browser window was narrower than the video. (174454557)
  • Fixed an issue where removing all text from the URL bar on Safari in visionOS showed an empty completion list. (176499710)
  • Fixed XRProjectionLayer to return correct values for width, height, and layer count. (178444052)
  • Fixed an issue where transforming the camera instead of the model in a <model> element led to undesirable lighting effects. (179522538)

Storage

  • Fixed an issue where IndexedDB could incorrectly return a version 0 database after an abort during the initial onupgradeneeded event. (176195526)
  • Fixed IndexedDB connections in workers to recover after a network process crash. (177219395)
  • Fixed an issue where IndexedDB transactions could be blocked for an extended period before starting when another page’s transaction was suspended in the background. (178769599)

Tables

  • Fixed an issue with a collapsed border color mismatch when the table cell has a different writing-mode. (173655092)

Text

  • Fixed a line break appearing after a U+201D Right Double Quotation Mark. (177952069)

UI Events

  • Fixed boundary events not being dispatched when a hovered element is removed from the DOM, so the Full Screen button on bsky.app no longer remains stuck in a hover state. (176507648)

Web API

  • Fixed the parent window’s history.state being set to null when history.pushState is called from a child iframe. (50019069)
  • Fixed the Async Clipboard API to request paste access asynchronously. (75969974)
  • Fixed clicking on a scrollbar of an overflow container blurring the current activeElement. (92367314)
  • Fixed an issue where the change event was not fired on <input> and <textarea> elements when they lost focus while another application was in the foreground. (98526540)
  • Fixed Web IDL bindings to correctly reject SharedArrayBuffer where [AllowShared] is not specified. (107786134)
  • Fixed Content Security Policy to only recognize ASCII whitespace excluding vertical tabs to align with the specification. (108559413)
  • Fixed emoji input on Google Docs and similar web applications by suppressing keypress events for supplementary characters. (122678873)
  • Fixed an issue where MouseEvent.offsetX and MouseEvent.offsetY were not relative to the padding edge as specified. (125763807)
  • Fixed an issue on visionOS where the gamepadconnected event did not fire unless gamepad permission had already been granted. (141623162)
  • Fixed an issue where CSPViolationReportBody did not include the source line number in Content Security Policy violation reports. (152607402)
  • Fixed IntersectionObserver to no longer notify targets in detached documents. (162699098)
  • Fixed an issue where selecting credentials in the Digital Credentials API sometimes required a second click to trigger verification. (163295172)
  • Fixed window bar visibility properties (toolbar.visible, statusbar.visible, menubar.visible) to return static values per the HTML specification for privacy and interoperability. (166554327)
  • Fixed handling of unknown DigitalCredential protocols by gracefully filtering them out and showing a console warning instead of throwing an error. (166673454)
  • Fixed: Updated the Digital Credentials API to rename DigitalCredentialRequest to DigitalCredentialGetRequest per the latest specification. (167115220)
  • Fixed an issue where Service Worker routes were not matched when no fetch event handler was set. (167753466)
  • Fixed spec conformance issues in the Streams API piping and abort behavior. (167841090)
  • Fixed Service Worker static routing rules to enforce limitation checks as required by the specification. (167977145)
  • Fixed layerX and layerY to return correct values with CSS transforms. (168968832)
  • Fixed location.ancestorOrigins returning stale origins after an iframe is removed from the document. (169097730)
  • Fixed NavigateEvent.canIntercept to correctly return false when navigating to a URL with a different port, aligning with the Navigation API specification. (169845691)
  • Fixed NavigateEvent.navigationType to return "replace" when navigating to a URL that matches the active document’s URL. (169999046)
  • Fixed an issue where the dragend event had incorrect coordinates when dragging within a nested <iframe>. (170750013)
  • Fixed an issue where navigation.currentEntry.key did not change in private browsing windows after calling history.pushState(). (171147417)
  • Fixed an issue where touch event properties values were sometimes swapped with neighboring values. (171567543)
  • Fixed a performance issue where ResizeObserver callbacks became increasingly sluggish over time. (172718139)
  • Fixed a performance issue where IntersectionObserver became sluggish over time when observing many elements due to O(n²) iteration. (172727210)
  • Fixed an issue where navigation.currentEntry.id did not change in private browsing windows after calling history.replaceState(). (172897962)
  • Fixed an issue where document.open() incorrectly aliased the caller’s security origin. (173369038)
  • Fixed an issue where history.replaceState() on a traversed history entry incorrectly changed navigation.currentEntry.key to a new UUID instead of preserving the original key. (173388766)
  • Fixed an issue where Object.prototype could not be serialized by structuredClone(). (173728983)
  • Fixed an issue where backslashes were not handled correctly in non-special URLs. (173757759)
  • Fixed a URL parsing bug in the special relative or authority state. (173772241)
  • Fixed an issue where event listener once and passive flags were not preserved when copying listeners between elements. (173834642)
  • Fixed: Preserved existing listener options (such as passive defaulting) when overwriting event handler attributes. (173842822)
  • Fixed the Credential Management API to properly define which credential types are allowed in the same get() request. (173918198)
  • Fixed an issue where event.target was not set after dispatching an event in a shadow tree with no listeners. (174136382)
  • Fixed an issue where navigator.credentials.create() and navigator.credentials.get() discarded the AbortSignal reason and always rejected with a generic AbortError. (174220589)
  • Fixed Range.extractContents() to not extract out-of-bounds nodes when the end container is removed during extraction. (174307275)
  • Fixed Digital Credentials to surface OperationError for platform-cancellation and unknown errors instead of AbortError or UnknownError. (174308268)
  • Fixed document.createEvent() to throw an exception for "MutationEvents", "MutationEvent", "PopStateEvent", and "WheelEvent", aligning with other browser engines. (174339775)
  • Fixed ParentNode.append() to correctly de-duplicate nodes when the same node is passed multiple times. (174365465)
  • Fixed an issue where MutationObserver delivered childList records in the wrong order when script ran during node insertion. (174368989)
  • Fixed an issue where setting a URL object’s port property to whitespace behaved incorrectly. (174484035)
  • Fixed a missing return in the Navigation API’s performTraversal that caused incorrect behavior when traversing to an unknown key. (174513305)
  • Fixed Blob.slice() to correctly clamp fractional start and end parameters using round-half-to-even rounding per the File API specification, which may change how edge-case fractional values like 0.5 are rounded. (174555334)
  • Fixed postMessage() to validate transferable object states after serialization, aligning with the HTML specification. (174558047)
  • Fixed structuredClone() and window.postMessage() to correctly throw a DataCloneError when serializing a SharedArrayBuffer outside of cross-origin isolated contexts. (174562553)
  • Fixed an issue where calling Element.blur() on an <iframe> did not reset document.activeElement to <body>. (174591529)
  • Fixed document.styleSheets to be accessible on documents created by DOMParser. (174625774)
  • Fixed innerText getter to correctly handle trailing newlines and blank lines for <p> elements and headings. (174642704)
  • Fixed innerText whitespace handling at inline-block boundaries. (174713114)
  • Fixed XMLSerializer namespace handling to correctly serialize elements with namespace prefixes. (174726401)
  • Fixed the innerText getter to preserve newlines for elements with white-space: pre-line. (174727341)
  • Fixed service worker registrations to be unregistered when the main script is missing. (174755909)
  • Fixed innerText handling of replaced elements at block boundaries. (174816319)
  • Fixed EventSource to be closed when window.stop() is called. (174830925)
  • Fixed service worker registrations to be unregistered when failing to retrieve stored imported scripts. (174833692)
  • Fixed calling preventDefault() during a pointerdown event to correctly suppress mousedown and mouseup events on iOS. (174864309)
  • Fixed innerText to not fall back to textContent for elements with display: contents. (174883499)
  • Fixed Digital Credentials rejecting with the wrong error code and synchronously; rejections are now queued as a task with the correct error. (174895437)
  • Fixed innerText to preserve the contents of <option> elements inside <select>. (175006854)
  • Fixed Element.innerText to collect option text when called directly on a <select> element. (175156630)
  • Fixed worker scripts to always be decoded as UTF-8, as per the specification. (175327455)
  • Fixed an issue where an Event object’s target property could lose its JavaScript wrapper due to premature garbage collection. (175439759)
  • Fixed an issue where ancestors of TreeWalker.currentNode could be prematurely garbage collected. (175442228)
  • Fixed FileSystemDirectoryHandle.resolve() to return the correct path array for child entries. (175645387)
  • Fixed PerformanceNavigationTiming.domInteractive and domContentLoadedEventEnd incorrectly returning 0 instead of the correct timestamps. (175739835)
  • Fixed FileSystemDirectoryHandle.removeEntry() to correctly remove entries. (175745157)
  • Fixed CryptoKey to correctly remain associated with its secure context. (176157712)
  • Fixed: Improved cross-origin isolation enforcement for workers. (176175488)
  • Fixed SharedArrayBuffer cloning and agent cluster ID assignment. (176465817)
  • Fixed missing custom element callbacks for the role attribute. (176713992)
  • Fixed incorrect URL parser invocation on the Notification object. (176762955)
  • Fixed requestAnimationFrame() not providing sub-millisecond timestamp precision in cross-origin isolated contexts. (176967366)
  • Fixed IntersectionObserverEntry.boundingClientRect to honor CSS zoom aware getBoundingClientRect. (177250323)
  • Fixed IntersectionObserver to report correct bounds for SVG element targets. (177260411)
  • Fixed Web Locks API to remove the AbortSignal abort algorithm after a lock request settles. (178589067)
  • Fixed an issue where the URL Pattern tokenizer emitted a spurious zero-length Regexp token for empty regexp groups. (179452346)
  • Fixed KeyboardEvent.getModifierState("AltGraph") and MouseEvent.getModifierState("AltGraph") always returning false. (180597374)
  • Fixed Credential.type returning "digital-credential" instead of "digital" for digital credentials. (180618646)
  • Fixed aborting navigator.credentials.get() leaving the digital-credentials document picker stuck on screen. (180812397)
  • Fixed FileReader.readAsText() ignoring the charset parameter of the Blob‘s MIME type. (180890703)
  • Fixed DOMMatrix and IntersectionObserver correctly enforcing absolute-length unit requirements when parsing values. (181453666)

Web Audio

  • Fixed Web Audio PannerNode orientation-only changes not updating the directional cone gain. (181413407)

Web Extensions

  • Fixed cross-origin XMLHttpRequest from a Safari Web Extension to no longer trigger an additional permissions request. (154866064)
  • Fixed browser.i18n.getMessage() to correctly substitute named placeholders when they appear adjacent to non-space characters. (169146196)
  • Fixed browser.i18n.getMessage() to correctly substitute two adjacent named placeholders. (175315700)
  • Fixed an issue where web extension service worker registration database files accumulated on each Safari launch, causing performance degradation. (175484888)
  • Fixed loading Web Extensions breaking Cloudflare bot challenge pages. (176618014)

Web Inspector

  • Fixed hovering over a node in a preview for a collection to highlight the node in the inspected page. (20341722)
  • Fixed showing ES2022 class private fields, methods, and accessors when inspecting object instances in the Console. (88527162)
  • Fixed an issue where CSS properties added to new rules were not applied and were marked as invalid. (103548968)
  • Fixed an issue in the Network panel where the Request / Response menu did not remember the user’s previously selected value. (108231795)
  • Fixed a JavaScript breakpoint on a line containing only a semicolon not being triggered. (126707973)
  • Fixed the Console REPL to allow redefinition of variables declared with let and const. (143140659)
  • Fixed the Timeline exporting and importing the wrong timestamp for performance.mark() records. (145226764)
  • Fixed editing inline style attribute values in the Elements panel resulting in truncated or malformed content. (149523483)
  • Fixed local response overrides mapped to a file being interpreted as Latin-1 (ISO-8859-1) instead of their actual encoding. (149847746)
  • Fixed the Media Logging setting not persisting across page loads. (154766890)
  • Fixed symbolic breakpoints to work with native constructors such as Array, Date, EventTarget, and Worker. (157178256)
  • Fixed an issue where the input field for recording canvas frames in the Graphics tab was sometimes too small to type in and only allowed typing one character at a time. (157787230)
  • Fixed the Network tab filtering by resource type not working after clearing a filter that had no matches. (161570940)
  • Fixed an issue where CSS rules added via the “Add New Rule” button in the Styles panel were intermittently not applied or would vanish after entering a property. (164971557)
  • Fixed the Status column in the Network tab to be visible by default. (167348733)
  • Fixed missing stack traces for MIME type errors when importing modules. (169396940)
  • Fixed an issue where tree outlines in Web Inspector would intermittently show blank content while scrolling when a filter was active. (169502061)
  • Fixed an issue where an active recording in the Timelines tab would stop when navigating or reloading the current page even when the setting to stop recording once the page loads was turned off. (169732727)
  • Fixed an issue in the Timelines tab where rows containing object previews were sometimes not visible in the heap snapshot data grid. (170164522)
  • Fixed context menu items in the Elements tab to only display relevant options when multiple DOM nodes are selected. (170307979)
  • Fixed an issue where previewing resources in the Network tab displayed an error upon navigating away and Preserve Log was enabled. (171216835)
  • Fixed an issue where selected DOM node keys in a Map in the Scope Chain sidebar had unreadable white text on a light background. (171840122)
  • Fixed the Safari Develop menu and WebDriver to launch Device Hub instead of Simulator when available in Xcode. (174276041)
  • Fixed the Layers 3D view to correctly map textures to composited bounds and use proper selection highlighting instead of tinting textures. (174355052)
  • Fixed the Layers 3D view to re-snapshot preserved layers after a repaint instead of displaying stale textures. (174358757)
  • Fixed an issue where the WebAssembly debugger had no source bytes for modules compiled via WebAssembly.instantiateStreaming, preventing source-level debugging in LLDB. (174362152)
  • Fixed the WebAssembly debugger to generate human-readable module names from the WebAssembly name section and fetch URL, replacing bare address-based fallback names in LLDB’s image list. (174465437)
  • Fixed an issue where all folder tree elements were expanded after filtering for a resource in the Sources panel. (175009135)
  • Fixed an erroneous “There are unread messages that have been filtered” banner appearing in the Console when console.groupCollapsed() is used. (175279759)
  • Fixed an issue in the Storage tab where filtering by storage type did not reveal the popup with options. (175444192)
  • Fixed Timeline recordings showing unrelated events incorrectly nested inside longer events. (176309164)
  • Fixed clearing the Console tab search field to dismiss the Clear Filters button. (176388155)
  • Fixed Web Inspector toolbar buttons rendering at incorrect sizes. (176508343)
  • Fixed the Event Listeners sidebar to populate for cross-origin iframe nodes. (177011541)
  • Fixed properties added to an element’s Style Attribute sometimes disappearing momentarily. (178053421)
  • Fixed the Accessibility sidebar being empty for nodes inside cross-origin iframes. (178562336)
  • Fixed inline style invalidation to batch DOM.getAttributes commands per tick in cross-origin iframes instead of issuing one command per node. (178830496)
  • Fixed DOM Storage read and write commands to resolve against the frame’s own origin in cross-origin iframes. (179249711)
  • Fixed a moved breakpoint reverting to its original location after closing and reopening Web Inspector. (180083858)
  • Fixed showing the formatted parameters string for prototype objects such as Map.prototype. (180298712)
  • Fixed missing formatted parameter strings for object shorthand methods and arrow functions. (180466459)
  • Fixed an unnecessary colon appearing in front of non-class function properties. (180476445)
  • Fixed a self-canceling ternary that produced an incorrect cross-axis direction in the flex overlay. (181198803)
  • Fixed the color picker force-converting picked colors to Display P3. (181201503)
  • Fixed Page.searchInResources silently omitting cache-backed resources from search results. (181202027)
  • Fixed duplicate invalid CSS declarations both incorrectly displaying as Active in the Styles sidebar. (181203080)
  • Fixed adopted constructable stylesheets being misclassified as User Agent stylesheets in cross-origin iframes. (181204768)
  • Fixed an unsigned underflow that caused the DOM agent to spuriously report power-efficient playback. (181205602)
  • Fixed Network.setExtraHTTPHeaders to replace previously set headers instead of accumulating them. (181282814)
  • Fixed zero-width joiners and other hidden Unicode characters not being displayed in text nodes. (182968570)

WebAssembly

  • Fixed WebAssembly.Suspending and WebAssembly.SuspendError to be data properties instead of getter functions, aligning with other WebAssembly attributes like WebAssembly.Module. (170155726)
  • Fixed incorrect IntegerOverflow exceptions thrown by i32.rem_s, i64.rem_s, i32.div_u, i64.div_u, i32.rem_u, and i64.rem_u when both operands are constants. (175122462)
  • Fixed a regression where RegisterSet::normalizeWidths() lost vector-width information, causing v128 argument corruption in WebAssembly SIMD thunks. (176035764)

WebDriver

  • Fixed an issue where the WebDriver full page screenshot was clipped to the viewport dimensions instead of the full page. (179840186)

WebGL

  • Fixed a regression where a WebGL canvas was filled with opaque content that hid stacked canvases beneath it. (175352260)
  • Fixed compressedTexImage not validating whether the compressed texture format extension has been enabled. (175652171)
  • Fixed some texImage functions reporting errors with incorrect function names. (175652807)
  • Fixed some WebGL context state properties not being correctly reset on context loss. (176190808)

WebGPU

  • Fixed GPUDevice.onuncapturederror event handler attribute not working. (149577124)
  • Fixed: Restored maxStorageBuffersInFragmentStage and related WebGPU limits. (160800947)
  • Fixed rendering failing when using direct GPUTexture objects instead of GPUTextureView with multisampled resolve targets in render passes. (175452924)
  • Fixed a WGSL shader validation failure in binary arithmetic expressions. (176473479)
  • Fixed WGSL parser handling of identifiers per the latest specification. (177008615)
  • Fixed WGSL validation of binary expressions involving short-circuit and/or operators. (177012957)

WebRTC

  • Fixed an issue where I420 BT709 VideoFrame was encoded in an incorrect color space when encoding to VP9. (169425608)
  • Fixed an issue where RTCPeerConnection.addIceCandidate() did not reject when the connection was already closed. (170470988)
  • Fixed outgoing video feeds freezing when the Safari window is obscured by another window while a virtual background is active. (170720729)
  • Fixed an issue where RTCDataChannel did not check the SCTP buffered amount synchronously. (172386678)
  • Fixed an issue where MediaStreamTrack could have incorrect settings if the source settings changed while the track was being transferred. (172657570)
  • Fixed an issue where a remote WebRTC track was not unmuted when the first packet was received. (172904930)
  • Fixed validation of RTC send encodings to better align with the specification. (172997814)
  • Fixed an issue where RTCRtpSender.setParameters did not clear parameters that were unset by the web application. (173678165)
  • Fixed WebRTC VP9 encoders to correctly propagate frame colorspace information. (174008548)
  • Fixed WebRTC media capabilities to report power-efficient AV1 decoding when supported. (174686183)
  • Fixed a regression where RTCPeerConnection with iceTransportPolicy: "relay" failed to gather ICE candidates. (174794660)
  • Fixed RTCInboundRtpStreamStats.trackIdentifier to match MediaStreamTrack.id. (174938984)
  • Fixed audio issues when joining Cisco Meeting Server meetings using Safari. (175260977)
  • Fixed screen sharing via getDisplayMedia() starting at extremely low quality and taking up to 30 seconds to become legible for remote participants. (175425085)
  • Fixed the WebProcess AudioSession to remain active while microphone capture is live. (180505014)
  • Fixed OverconstrainedError to inherit from DOMException and expose a code attribute per the Media Capture spec. (180728516)
  • Fixed the configurationchange event being dropped when a source-side change occurred while a MediaStreamTrack was muted; the event is now deferred until unmute. (180728609)

Updating to Safari 27

Safari 27.0 comes automatically with macOS 27 Golden Gate, iOS 27, iPadOS27 and visionOS 27. Plus, you can update to Safari 27.0 on macOS 26 Tahoe and macOS 15 Sequoia, separate from macOS.

Feedback

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

September 17, 2026 11:30 AM

September 16, 2026

WPE WebKit Blog: WPE WebKit 2.54 highlights

Igalia WebKit

The WebKit team at Igalia is happy to announce a new release series of WPE WebKit. This release has two main highlights: the new WPEPlatform API, now stable and enabled by default, and a web process compositor built on the Skia graphics library, replacing the TextureMapper-based one. Read on for the details on both, along with a summary of the other most noteworthy changes from the latest release cycle.

WPEPlatform: the new WPE API

WPEPlatform, the API that has been in the works for the past several release cycles, takes center stage in 2.54: it is now enabled by default and its API is considered stable, so applications can build on it without expecting breaking changes in future releases. Consequently, the traditional libwpe-based API is officially deprecated: it remains available and maintained, but new code should target WPEPlatform, and existing embedders are encouraged to plan their migration. This also applies to Cog, which will not have stable releases beyond the 0.18.x series.

The best part of the new API is how much simpler it is. Under libwpe, an application had to create a view backend (usually through WPEBackend-fdo’s “exportable” backend) and drive rendering, buffer management, and input dispatch itself through its callbacks. WPEPlatform moves all of that into WebKit and the platform implementation, so migrating an application is mostly a matter of deleting code: in the common case, the application constructs its WebKitWebView without a backend, WebKit selects a suitable platform automatically, and everything built on top of the web view carries over unchanged. The platform API only surfaces when an application wants more than the defaults: pinning to a particular platform, handling raw input events, or driving the toplevel window. Each of those is a few lines against a small GObject API rather than a set of C callbacks to implement.

To help embedders make the move, WPEPlatform is now extensively documented. The reference documentation includes an overview of the platform API (covering its relationship to libwpe and what is intentionally not part of the new API), a guide on compiling against it, a tutorial on writing a browser, and a tutorial on writing a WPE platform implementation. A guide on migrating from libwpe, with a symbol-by-symbol mapping table, walks embedders through the process step by step.

Since WPEPlatform is now built by default, the wpe-platform-2.0 pkg-config module is available in regular builds. Conversely, the legacy API can be disabled at build time with ENABLE_WPE_LEGACY_API=OFF when it is not needed.

Additions and final adjustments

New platform APIs added this cycle include:

  • WPEProcessManager and WPEProcessLaunchOptions, which allow the embedder to control how the auxiliary WebKit processes are launched and terminated. This is particularly important on Android, where each process is a service that must be started with bindService(), and it removes the last reason a WPEPlatform-only build could not work there. Note that, unlike the rest of WPEPlatform, the process management API is only built when targeting Android and remains experimental: it is not yet generic enough to be enabled everywhere, and it may still change in future releases.
  • Gamepad rumble support, through wpe_gamepad_has_rumble() and wpe_gamepad_rumble(), with a built-in implementation based on libmanette. This enables the Gamepad API vibrationActuator for web content.
  • A new WPE_SETTING_OVERLAY_SCROLLBARS setting, enabled by default, which may be disabled by applications to opt into classic, always-visible scrollbars.
  • A new WPE_INPUT_PURPOSE_SEARCH input purpose, allowing input methods to detect when the values of an input field are expected to be search terms.

A few final adjustments were made to the API before declaring it stable, which may require updates to platform implementations and applications developed against the earlier previews:

  • The WPE_SETTING_DISABLE_ANIMATIONS setting has been replaced by WPE_SETTING_REDUCED_MOTION, matching the dedicated reduced-motion setting introduced in GNOME 50, and a new tri-state WPE_SETTING_INTERFACE_CONTRAST setting has been added. Together with the existing WPE_SETTING_DARK_MODE setting, these make the prefers-reduced-motion, prefers-contrast, and prefers-color-scheme media queries follow the platform settings.
  • wpe_gesture_controller_handle_event() now returns a boolean indicating whether the event was consumed.
  • The WPE_DMABUF_BUFFER_FORMAT environment variable has been renamed to WPE_BUFFER_FORMAT.

Beyond Linux: Android

A good measure of the new API’s maturity is wpe-android, which has been rebuilt this year as a WPEPlatform platform implementation living entirely outside the WebKit tree: Android’s display system now looks to the engine like any other WPE platform, and applications get a convenience Java API modeled after android.webkit.WebView. The new WPEProcessManager API removed the last dependency on libwpe there. You can read more about it in this post.

On the WebKit API side

The shared WebKit API has also seen additions this cycle:

Graphics improvements

A new Skia-based compositor

This cycle brings the largest overhaul of the rendering architecture since the adoption of Skia for 2D rendering: the web process compositor now uses the Skia API instead of the venerable TextureMapper. Layers are composed into the final frame using Skia, which allows sharing a single rendering infrastructure across the whole graphics stack and enables several optimizations:

  • Tile contents are recorded into deferred display lists and replayed on the compositor thread, so painting worker threads no longer need to touch the GPU at all.
  • Batched painting groups the drawing of many layers into a single Skia call, which improves performance on pages with many layers that can be painted in the same operation.
  • Unnecessary clip operations are avoided whenever possible, keeping the batched paths effective.

Beyond raw performance, expressing compositing as Skia draw calls made several features simpler and faster: filters and masks no longer require intermediate offscreen surfaces in most cases, and CSS blend modes, which TextureMapper never implemented, now work in composited layers. And since Skia can target both OpenGL and Vulkan, the compositor no longer stands in the way of Vulkan-based rendering in the future.

The new compositor also works when using the legacy libwpe-based API.

The consolidation on Skia goes beyond compositing: the option to use Cairo for 2D rendering has been removed, making Skia the only 2D rendering implementation. Rendering tiles in the main thread is no longer supported either, so threaded rendering is now the only tile painting path.

Damage-aware compositing

Damage tracking has seen substantial work this cycle. The damage is the region of the view that changed since the previous frame and therefore requires repainting; Paweł Lampe’s introduction to damage propagation covers the concept in depth. Compositing itself now uses this information: each draw the compositor issues is restricted to the damaged rectangles, in a way that preserves the batched painting described above, and damage propagation to the platform is now enabled by default. A new DamageRectangleThreshold preference allows embedders to tune the balance between damage precision and bookkeeping cost.

The performance impact

What drove the compositor rewrite was making it simpler and easier to maintain, as Carlos García Campos explained at the Web Engines Hackfest 2026; performance came later, since TextureMapper started out ahead after more than a decade of tuning. By now the optimization work has more than closed that gap. The public WPE performance dashboard, which continuously runs benchmarks on Raspberry Pi 4 devices, tells the story: comparing the last TextureMapper-based revisions against the current ones, and thus measuring the cumulative effect of the graphics work described in this section, the MotionMark score is up by around 36%. On the composition-focused variant of the benchmark, where the compositor dominates the workload, the score is up by around 45%.

Bar chart comparing MotionMark scores on a Raspberry Pi 4: MotionMark 1.3.1 at 30 FPS goes from 246 with TextureMapper to 334 with the Skia compositor (+36%), and the Composition variant from 131 to 189 (+45%)

The dashboard also records the GPU load during each run, and there the difference is even more telling. MotionMark increases scene complexity until the browser can no longer sustain the target frame rate, so a higher score already means more work per frame; the Skia compositor delivers that while keeping the GPU markedly less busy. On embedded devices, where the GPU is typically the scarcest resource, this headroom translates directly into smoother pages.

Bar chart comparing GPU load during the same benchmark runs: 56% with TextureMapper against 39% with the Skia compositor on MotionMark 1.3.1, and 47% against 26% on the Composition variant

One part of the work goes largely unmeasured here, though. MotionMark animates nearly the entire viewport, so there is hardly anything for damage tracking to save. Real-world content behaves differently: usually a small area of the page is changing while everything else stays still, and skipping all of that quiet area cuts the per-frame GPU work to a fraction.

Other rendering improvements

A GPU atlas is now used for batched raster image uploads, regardless of the compositor in use, and it is reused across frames when the image set does not change, avoiding needless texture allocation and pixel uploads.

Asynchronous scrolling is smoother: several synchronization issues between the main, scrolling, and compositing threads that caused glitches while scrolling have been fixed, and the scrolling thread no longer blocks the compositor to flush its state, removing input-latency stalls on pages with many layers.

Finally, more animations can now run on the compositing thread: CSS animations using the steps() and linear() timing functions no longer force main-thread animation.

Better multimedia on embedded hardware, and a WebRTC transition

Let’s start with the transition: the GStreamer-based WebRTC backend is being replaced with a LibWebRTC-based implementation, which is expected to be available in the next release cycle. As a consequence, WebRTC support, which in previous releases required building with experimental features enabled, is disabled in 2.54.

The rest of the multimedia work moved forward at full speed, with a strong focus on embedded hardware:

  • Hardware video decoding and encoding for platforms with a Qualcomm GPU has been added, leveraging the qtic2vdec and qtic2venc GStreamer elements.
  • Video decoding limits are now respected in MediaCapabilities queries, and can be overridden with the WEBKIT_GST_VIDEO_DECODING_LIMIT environment variable, so a single build can serve devices with different capabilities.
  • Resource usage on pages containing many videos has been improved, by stopping the pipelines of muted, invisible video elements.
  • A new feature flag, enabled by default, allows low-end devices to skip caching pages with multimedia content in the back-forward cache, so that a suspended pipeline cannot hold a scarce hardware decoder hostage.
  • Media capability reporting is more accurate: non-AAC mp4a codecs (MP3, AC-3, E-AC-3) are correctly reported as supported when decoders are present, xHE-AAC support is auto-detected, and Dolby AC-4 is advertised for MSE on systems that support it.
  • Experimental support for SourceBuffer.changeType() has been added to the MSE backend, along with a fix for playback stalling at ad transitions on Twitch.
  • Persistent licenses are now supported in the Thunder CDM for encrypted media.

On top of this, the GStreamer backend has received a substantial amount of memory-safety and lifetime-correctness work that translates into a more stable multimedia experience.

WebXR

The OpenXR-based WebXR implementation continues to progress. The main highlight is support for the WebXR Layers API: quad, cylinder, equirect, and cube layers are now implemented, in addition to the already supported projection layers. Layers can be backed by texture arrays, and XRSession.maxRenderLayers lets content query the compositor’s layer budget.

The backend has also been decoupled from OpenGL ES through an abstract graphics binding, paving the way for a future Vulkan-based binding.

WebXR support remains a build-time option, enabled with the ENABLE_WEBXR=ON CMake option; the Layers support additionally requires ENABLE_WEBXR_LAYERS=ON.

Other improvements to the WPE port

Several long-standing gaps in the WPE port have been closed this cycle:

  • A built-in popup menu is now used as the default implementation for <select> elements when the WebKitWebView::show-option-menu signal is left unhandled, so option menus work out of the box.
  • Initial drag-and-drop support has been added: web views now handle drags driven by the mouse events they already receive.
  • Spell checking is now supported using the Enchant library, enabled by default, and can be toggled at build time with the ENABLE_SPELLCHECKING CMake option.
  • The on-screen keyboard is no longer shown when an element is focused programmatically; it only appears as a result of user interaction.
  • Initial support for the Pointer Lock API can be enabled at build time with the ENABLE_POINTER_LOCK CMake option.
  • Saving files from the Web Inspector now works in WPE.

Web Platform support

As usual, this list is not exhaustive as WebKit continuously progresses in its support for new standards. Some of the highlights for this release are:

What’s new for WebKit developers?

WebKit can now use mimalloc as its memory allocator, as an alternative to its own bmalloc. For now it is the default only on some architectures (32-bit ARM, MIPS, RISC-V, and builds supporting 64 KB memory pages); everywhere else bmalloc remains the default, and mimalloc can be enabled with the USE_MIMALLOC build option.

Logging now falls back to the standard error output when journald is not reachable, which is common in minimal containers, and the new WEBKIT_DEBUG_OUTPUT environment variable allows choosing the log destination explicitly.

Profile-guided optimization is now supported in regular CMake builds with Clang, through the ENABLE_LLVM_PROFILE_GENERATION and USE_PGO_PROFILE options.

Finally, a note for packagers: building WPE WebKit now requires Ninja, as the CMake Makefile generator is no longer supported.

Looking forward to 2.56

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

September 16, 2026 12:00 AM

September 14, 2026

Igalia WebKit Team: WebKit Igalia Periodical #77

Igalia WebKit

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

In this week's edition, the PNG image decoder received a much needed cleanup, as well as another swipe of unassorted graphics improvements, most notably in the Layer-Based SVG Engine. Finally, we also have an exciting and informative article about how to use the WPE Platform API on Raspberry Pi.

Cross-Port 🐱

Graphics 🖼️

Cleaned up the PNG image decoder a little bit, by removing conditional code that was used to support libpng versions older than 1.5.0, and requiring that as the minimum version. Given that version 1.5.0 was released back in 2011, it is expected that every system where current WebKit works will have much newer versions of libpng anyway.

Fixed an assertion failure for an outermost <svg> with non-visible overflow in the Layer-Based SVG Engine (LBSE) by refreshing its scroll dimensions at the end of layout, a first step towards reworking <svg> scrolling, which is not yet spec compliant.

Fixed an assertion failure when computing filter outsets for <feGaussianBlur> and <feDropShadow> with a negative stdDeviation, which turns the primitive off but was still passed on to the outset calculation, affecting both SVG filters and CSS reference filters.

Stopped reporting damage every frame for composited layers that use CSS filter in the Skia based compositor, so a blurred or drop-shadowed element is now only repainted when its subtree, or the applied (possibly animated) filter value, actually changed.

Fixed the bug that composited layers with clipping or masking and fully covered by opaque child layers were treated as opaque layers.

Fixed two debug assertion failures in the Layer-Based SVG Engine (LBSE), one where an SVG transform change left ancestor layer repaint rects stale and one where repainting on a compositing change during layout tripped an over-strict check on the paint offset cache.

Fixed the white noised image issue on the combination of i915 driver and Intel Arc after 308458@main introduced image atlas uploading.

Community & Events 🤝

Published a blog post describing the steps to try WPE Platform API on Raspberry Pi with latest WebKit main branch.

That’s all for this week!

By Igalia WebKit Team at September 14, 2026 07:07 PM

September 13, 2026

Hironori Fujii: Asynchronous scrolling for touch events in WPE and WebKitGTK

Igalia WebKit

The WPE and GTK ports have supported touch events for a long time, but asynchronous scrolling only worked for wheel events. Scrolling driven by touch still depended on the web process main thread. This change puts touch events on the asynchronous scrolling path too.

Let’s start with the background: what asynchronous scrolling is and why it needs to be built differently here.

What is asynchronous scrolling? #

Why it is needed #

A naive implementation of scrolling looks like this:

  1. The UI process receives an input event (wheel or touch).
  2. It sends the event to the web process main thread.
  3. The main thread runs the page’s JavaScript event listeners.
  4. If nothing called preventDefault(), the scroll position is updated.
  5. The page is rendered at the new scroll position.

Step 3 is the problem. The main thread is easily blocked for hundreds of milliseconds by JavaScript execution or layout, and scrolling is frozen for that whole time. Your finger moves, the screen does not. That is what synchronous scrolling feels like.

Asynchronous scrolling moves the work off the main thread: the scrolling thread updates the scroll position, and the compositor thread composites and presents the frame. Neither needs the main thread, so scrolling keeps running at 60fps even when the main thread is busy.

The scrolling tree and event regions #

Two data structures make this possible.

The scrolling tree is a tree of the scrollable areas of a page. The main frame, overflow: scroll elements, position: fixed/sticky elements and so on each become a node holding its own scroll position and a reference to its layer. The scrolling thread updates scroll positions by looking only at this tree, and the compositor thread then draws the layers at their new positions — the main thread is not involved in either step.

But there are places where scrolling on its own would be wrong, because the page might call preventDefault() from an addEventListener("touchstart", ...) handler. That is what event regions are for.

An event region records, per layer and at rendering time, “this rectangle has a listener for this kind of event”. EventRegion keeps separate regions for touchstart, touchmove, pointerdown, mousedown and friends, and for any given point it yields a TrackingType:

enum class TrackingType : uint8_t {
NotTracking = 0, // No listener. The event does not even need to be delivered.
Asynchronous = 1, // Passive listeners only. Scroll now, notify the page later.
Synchronous = 2 // A non-passive listener may call preventDefault(). We must wait.
};

The passive distinction is what makes Asynchronous possible. preventDefault() cancels an event only if the listener was registered with passive: false; from a passive listener it does nothing. And on window, document and document.body, touchstart/touchmove (and wheel) default to passive: true — see MDN: Using passive listeners. So Asynchronous is the case “there are listeners, but none of them can cancel the scroll”: start scrolling now, deliver the event to the main thread afterwards.

Because this information travels to the scrolling thread along with the layer tree, an incoming input event can be classified without waking the main thread. That is the heart of asynchronous scrolling.

Why the iOS implementation could not be reused #

The iOS port already implements asynchronous scrolling for touch events, but it could not be reused, because the process layout is different.

iOS:
  UI process:  platform layer tree + scrolling tree + touch event input
  Web process: main thread (DOM, layout)

WPE / GTK (Coordinated Graphics):
  UI process:  touch event input only
  Web process: main thread (DOM, layout)
               + EventDispatcher thread / scrolling thread
               + platform layer tree + scrolling tree

On iOS both the platform layer tree and the scrolling tree live in the UI process — the very process that receives touch events — so the classification and the scroll both happen right there. On WPE and GTK we use Coordinated Graphics, and both trees live in the web process instead. The classification therefore has to happen after sending the event to the web process, but before touching the main thread.

Fortunately the same problem was already solved for wheel events. The web process has an EventDispatcher thread that receives wheel events from the UI process without going through the main thread and consults the scrolling tree directly. This change builds the same shape for touch events.

How a touch becomes a scroll in WPE #

One more piece of background: in WPE a touch does not scroll the page directly. Touch events are first offered to the page; only if the page does not consume them does the UI process turn the touch sequence into scrolling.

The decision point is PageClientImpl::doneWithTouchEvent(). If the page handled the event, gesture detection is cancelled with wpe_gesture_controller_cancel() so the engine does not also act on it. If it was not handled, the event is fed to the WPE platform gesture controller via ViewPlatform::handleGesture(), and a recognized WPE_GESTURE_DRAG is turned into a synthetic scroll event pushed back into the page as a wheel event:

GRefPtr<WPEEvent> simulatedScrollEvent = adoptGRef(wpe_event_scroll_new(
m_wpeView.get(), WPE_INPUT_SOURCE_TOUCHSCREEN, 0, static_cast<WPEModifiers>(0), dx, dy, TRUE, FALSE, x, y));
page().handleNativeWheelEvent(WebKit::NativeWebWheelEvent::create(simulatedScrollEvent.get(), phase));

That TRUE is precise_deltas: touch-driven scrolling in WPE reaches the engine as precise-delta wheel events, which becomes relevant later.

The important consequence is this: the UI process cannot start scrolling until it knows whether the page is going to consume the touch. That answer used to come from the web process main thread — so when the main thread was busy, scrolling did not start. That is the problem this change fixes.

The change #

Enabling touch event regions #

A new ENABLE(COORDINATED_TOUCH_EVENTS) is introduced in PlatformEnableGlib.h, and it turns on ENABLE(TOUCH_EVENT_REGIONS) whenever touch events are enabled on WPE/GTK. The AlwaysUseTouchEventRegions preference now defaults to true under that flag, so Document::shouldUseTouchEventRegions() returns true and touch regions are actually recorded on the layers during rendering.

UI process: send to the EventDispatcher instead of the main thread #

The old WebPageProxy::handleTouchEvent() consulted a touchEventTracking state kept in the UI process and sent Messages::WebPage::TouchEvent, i.e. straight to the web process main thread.

The new version delegates all classification to the web process and only queues events and delivers answers. One event is in flight at a time; the next is sent when the reply arrives. The flood of touchmove events produced while a finger moves is coalesced into the newest queued event when that is also a touchmove, and the coalesced events are flushed to doneWithTouchEvent() together with the reply.

The destination is now Messages::EventDispatcher::TouchEvent.

Web process: classification on the EventDispatcher thread #

EventDispatcher::touchEvent() runs on the EventDispatcher thread, where it looks up the page’s scrolling tree, asks it for a TrackingType, and splits three ways:

  • NotTracking — no listeners. Reply handled = false immediately, without bothering the main thread at all. The UI process can start scrolling right away.
  • Asynchronous — passive listeners only, so nothing can cancel the event. Reply handled = false first so scrolling starts, then deliver the event to the main thread.
  • Synchronous — a non-passive listener may call preventDefault(), so wait for the main thread result as before.

Replying without a main thread round trip is possible because the new TouchEvent message is declared AnyThread in EventDispatcher.messages.in. The iOS equivalent is MainThreadCallback, which always replies from the main thread.

If there is no scrolling tree for the page yet, the event goes to the main thread as before.

Classifying a touch in the scrolling tree #

The classification itself is ScrollingTreeCoordinated::eventTrackingTypeForTouchEvent(). It works in two stages.

First, for each newly pressed touch point: convert the point from view to contents coordinates, hit test the layer tree down from the root contents layer, take the frontmost layer whose event region contains the point, and query that region. It is queried for many event types, because a touch fires more than the DOM touch* events — pointer*, compatibility mouse events and gesture* too, and a non-passive listener for any of them forces synchronous handling. The results are folded into a small TouchEventTracking struct with four fields: start, move, end and force-change.

Second, the tracking type of the event as a whole is derived from the touch point states, merging the per-field values. Merging picks the stronger of two types (NotTracking < Asynchronous < Synchronous), so if any single point needs synchronous handling, the whole event is synchronous.

TouchEventTracking persists for the lifetime of a touch sequence and is reset once all points are released, so the hit test done at touchstart is reused for the following touchmove/touchend. That guarantees a sequence never flips from synchronous to asynchronous halfway through just because a finger moved off a listener’s area.

<input type=range> #

A slider handles touches internally even with no JavaScript listener, so looking at the event region alone would classify it as NotTracking. HTMLInputElement::updateTouchEventHandler() now sets the HasInternalTouchEventHandling flag on EventTarget for range inputs, and StyleAdjuster turns that flag into the full set of touch region types for the element.

Keeping the animation running on the scrolling thread #

The last piece is in ScrollingEffectsController::handleWheelEvent(). As shown above, WPE synthesizes wheel events from touch gestures with precise deltas. Precise-delta events only need immediateScrollBy() to move the scroll position — but then nothing drives screen updates while the main thread is busy.

The fix is that, while a scroll gesture is in progress, a scroll animation is also started — from one ULP short of the destination (std::nextafter()) to the destination. Visually it finishes instantly — the real scroll is still done by immediateScrollBy() — but a scroll animation is now running, which starts display link monitoring and keeps compositing driven regardless of the main thread.

The event flow, summarized #

Before:

The UI process sends the touch event to the web process main thread, which may
be blocked by JavaScript or layout. Only after the reply arrives does gesture
recognition synthesize a wheel event and scrolling
start.

After (no listeners, or passive listeners only):

The UI process sends the touch event to the EventDispatcher thread of the web
process, which asks the scrolling tree and replies immediately without the main
thread, so gesture recognition synthesizes a wheel event and scrolling starts
right away. If passive listeners exist, the event is also delivered to the main
thread afterwards.

Where a non-passive listener exists, we still wait for the main thread as before. The spec requires preventDefault() to be honoured, so that is unavoidable.

Layout test updates #

As a side effect, the tests under fast/events/touch/ had to be updated.

Event regions are computed during a rendering update and propagated to the scrolling tree via the platform layer tree. Which means a test like this:

target.addEventListener("touchstart", handler);
tapSoon(20, 20); // ← the region has not been updated yet!

taps immediately after registering the listener, while the scrolling tree still believes there is no listener and returns NotTracking. The event never reaches the main thread and the test fails.

A new UIHelper.renderingComplete() was added for this:

static async renderingComplete()
{
// Wait for the platform layer tree to be updated
await UIHelper.animationFrame();
await UIHelper.animationFrame();
}

Two animation frames are needed because the first one runs the rendering update that computes the regions, and a second is needed for the result to reach the layer tree.

Summary #

  • On WPE and GTK both the layer tree and the scrolling tree live in the web process (Coordinated Graphics), so the iOS touch asynchronous scrolling implementation could not be reused directly.
  • Instead, touch events were given the same shape that already works for wheel events: ask the scrolling tree from the EventDispatcher thread.
  • The keys were enabling touch event regions, and making the IPC reply AnyThread so it can be sent without waiting for the main thread.
  • Anywhere the page has no non-passive listener, scrolling now starts regardless of what the main thread is doing.

Acknowledgements #

Many thanks to Alejandro G. Castro and Carlos Garcia Campos for their insightful reviews of this work, and to Claude for writing this blog post.

September 13, 2026 12:00 AM

September 11, 2026

Release Notes for Safari Technology Preview 252

Surfin’ Safari

Safari Technology Preview Release 252 is now available for download for macOS Golden Gate and macOS Tahoe. 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: 319252@main…320112@main.

Accessibility

Resolved Issues

  • Fixed the accessible name of a display: contents element incorrectly including text from its siblings. (319287@main) (185108892)
  • Fixed list marker text being included in accessible names and values that requested text without list markers. (319615@main) (185422271)

Animations

Resolved Issues

  • Fixed an unresolved current time for a progress-based timeline being treated as a progress timeline boundary. (319442@main) (183902668)
  • Fixed a newly encountered animation frame rate being aligned with the lowest compatible frame rate instead of the highest. (319362@main) (185152473)
  • Fixed an animation of width or height being incorrectly accelerated when only some keyframes have a size-dependent transform. (319363@main) (185153675)
  • Fixed view timeline range boundaries discarding subpixel adjustments for sticky positioned elements. (319369@main) (185162728)
  • Fixed keyframes using revert-layer or revert-rule no longer being recomputed after a KeyframeEffect is copied. (319368@main) (185164643)
  • Fixed scroll-timeline-name and view-timeline-name to be loosely matched across shadow tree boundaries. (320040@main) (185206745)

CSS

New Features

  • Added support for the named-feature() function in @supports conditions. (319412@main) (183688843)
  • Added support for CSSConditionRule.supports. (319302@main) (184943650)
  • Added support for the unprefixed user-select CSS property. (319562@main) (184964807)
  • Added support for CSSMediaRule.matches. (319988@main) (185154835)
  • Added support for percentage values in text-decoration-inset. (320052@main) (185850399)

Resolved Issues

  • Fixed line-clamp not clamping content inside an inline block. (319848@main) (168534323)
  • Fixed an unexpected ellipsis appearing when line-clamp is applied to content with a block inside an inline box. (319392@main) (182123153)
  • Fixed the computed value of display for -webkit-box and -webkit-inline-box when combined with -webkit-box-orient: vertical and the continue property. (319370@main) (182288734)
  • Fixed random() and random-item() with an auto caching key in the same property value producing the same random value. (319463@main) (182846761)
  • Fixed margins trimmed by margin-trim being reflected in computed style. (319612@main) (184215336)
  • Fixed subgrid columns ignoring the parent grid’s content distribution from justify-content. (319353@main) (184542148)
  • Fixed the resolution of font-relative lengths when a minimum font size is in effect. (319340@main) (184727481)
  • Fixed an issue where grid item margins were not included in the intrinsic size contributions of a grid container. (319836@main) (184789473)
  • Fixed an issue where extra space was not distributed beyond growth limits when sizing grid tracks. (319837@main) (184933769)
  • Fixed ::selection failing to repaint when its style changes. (319662@main) (184994906)
  • Fixed invalidation and serialization of properties that use attr() with a number or percentage value. (319994@main) (185088893)
  • Fixed anchor(right) not resolving to an inline anchor’s right edge in vertical-rl writing mode. (319286@main) (185108663)
  • Fixed px values inside calc() expressions being serialized incorrectly in computed style when zoom is applied. (319320@main) (185156231)
  • Fixed text-overflow: ellipsis not being shown on content that is not clamped when using line-clamp. (319597@main) (185348784)
  • Fixed offset-path not respecting corner-shape when using a <coord-box> reference box. (319655@main) (185447602)
  • Fixed corner-shape drawing square and notch corners as round and scoop on outlines and box-shadow spread. (319606@main) (185452130)
  • Fixed font load status changes being reported with an incorrect previous state when script runs in response to a FontFace loaded promise. (319575@main) (185462848)
  • Fixed incorrect style sharing between elements when a computed value depends on the lh unit. (319596@main) (185508307)
  • Fixed interpolation of corner-shape for outset curves to match the updated specification algorithm. (319661@main) (185519894)
  • Fixed: Removed the inline, inline-start, and inline-end values from margin-trim, which now accepts none | block | [ block-start || block-end ]. (319622@main) (185527437)
  • Fixed: Changed calc() simplification so that clamp(none, a, b) becomes max(a, b) and clamp(a, b, none) becomes min(a, b). (319614@main) (185531467)
  • Fixed align-content moving a floated child twice in block containers. (319665@main) (185536955)
  • Fixed grid content not contributing to the scrollable overflow area. (319929@main) (185923265)
  • Fixed background-size: contain, background-size: cover, and automatic sizing ignoring SVG images that have one intrinsic dimension and no aspect ratio. (319972@main) (185970333)
  • Fixed arguments to registered custom functions so that they are evaluated in the calling context. (320022@main) (185975255)

CSS Grid Lanes

Resolved Issues

  • Fixed: Renamed the flow-tolerance property to fit-tolerance. (319795@main) (185125327)

Canvas

Resolved Issues

  • Fixed shadows rendering with a stale, incorrect transform in some cases. (319534@main) (185405205)

Editing

Resolved Issues

  • Fixed pasted text being cut off in some web-based code editors. (319350@main) (184653264)
  • Fixed typing a space in editable content inside a <summary> or <button> element also activating that element. (319628@main) (185479319)

Forms

Resolved Issues

  • Fixed: Updated the default styling for appearance: base-select to center-align items, use lh units for padding, and reset all font properties on ::picker-icon and ::checkmark. (319816@main) (184843832)
  • Fixed spin buttons for <input type="number"> not repainting when the window’s active state changes. (319683@main) (185625389)

HTML

Resolved Issues

  • Fixed the dialog focusing steps not moving focus after calling show() because the <dialog> was still treated as unrendered. (319387@main) (185156091)
  • Fixed dialog.close() restoring focus even when focus was not inside the dialog and the dialog was not modal. (319640@main) (185549474)
  • Fixed documentElement.focus() to move focus to the viewport instead of doing nothing. (319730@main) (185567540)
  • Fixed script elements parsed from XML by DOMParser executing after being cloned. (319922@main) (185943138)

Images

Resolved Issues

  • Fixed ImageDecoder not applying an image’s EXIF orientation to the decoded frames. (319527@main) (184850407)
  • Fixed ImageDecoder producing VideoFrame objects with a duration of zero. (319526@main) (185257570)

JavaScript

Resolved Issues

  • Fixed worker.terminate() and page reloads not terminating a worker that is blocked in Atomics.wait or a WebAssembly atomic.wait. (319508@main) (147482360)
  • Fixed Promise.try() to resolve its result with PromiseResolve, so a promise returned by the callback is no longer wrapped in an extra promise. (319276@main) (185064567)
  • Fixed regular expression backreferences not restoring the input position when reading a surrogate pair fails. (319560@main) (185065533)
  • Fixed Uint8Array.prototype.setFromBase64() so that nothing is read and no error is thrown when the target array has zero length. (319275@main) (185073630)
  • Fixed a regular expression using the s flag matching from the start of the string instead of from lastIndex. (320086@main) (185814135)
  • Fixed the Referer header sent for descendant and dynamically imported module script fetches. (319921@main) (185943042)
  • Fixed String.prototype.replace with a unicode regular expression splitting a surrogate pair after an empty match. (320099@main) (186143674)

Media

Resolved Issues

  • Fixed videos immediately exiting fullscreen back to inline after an earlier video had been played in fullscreen. (320084@main) (174807377)
  • Fixed an issue where interacting with the inline media controls could dismiss a site’s custom video presentation. (319741@main) (181244652)
  • Fixed video failing with MEDIA_ERR_ABORTED and restarting from the beginning after restoring a page from the back/forward cache. (319472@main) (183119720)
  • Fixed ManagedMediaSource not firing the startstreaming event when seeking outside the buffered range. (319330@main) (183790572)
  • Fixed an issue where a video could begin playing ahead of another video that requested playback earlier while audio session activation was still pending. (319455@main) (185043324)
  • Fixed an unnecessary ended event being fired when seeking to the end of an HTMLMediaElement backed by a MediaSource. (319847@main) (185145277)
  • Fixed an issue where audio could continue playing for several seconds after closing a tab. (319759@main, 319842@main) (185505597)

Networking

Resolved Issues

  • Fixed cookies being stored as session cookies when their Expires date uses the month-before-day format produced by Date.prototype.toString(). (320016@main) (185840799)

Performance

Resolved Issues

  • Fixed hidden page throttling heuristics not being applied to newly created pages. (319630@main) (185461584)

Rendering

Resolved Issues

  • Fixed characters overlapping adjacent text when a space glyph’s base advance is substituted. (319556@main) (180227130)
  • Fixed incorrect element dimensions being reported for content inside an iframe whose owner element generates no box. (319910@main) (181639379)
  • Fixed content overlapping when an element gains a scrollbar during layout. (320066@main) (183465635)
  • Fixed a list marker overlapping the list item content when the marker text is right-to-left. (319314@main) (185048387)
  • Fixed an outside list marker keeping a line of its own when a block is appended to an empty list item. (319289@main) (185109123)
  • Fixed a block-level box on a line being handed its own float as an intruding float. (319290@main) (185109160)
  • Fixed list markers not updating when the list item’s font changes dynamically. (319476@main) (185330733)
  • Fixed an issue where adjoining margins did not collapse when a container’s first child was a float or an out-of-flow box. (319696@main) (185591842)
  • Fixed an issue where margins did not collapse after a self-collapsing block-level box on a line. (319697@main) (185598333)
  • Fixed an issue where a list marker was painted on top of the list item’s content instead of behind it. (319818@main) (185741211)
  • Fixed an issue where a block-level box on a line was left behind when a column break moved the line. (319821@main) (185787568)
  • Fixed backdrop-filter being clipped to a rounded rectangle instead of following corner-shape. (320063@main) (185813432)
  • Fixed an issue where a max-width length was not honored on an out-of-flow replaced element with an intrinsic aspect ratio. (319917@main) (185831651)
  • Fixed a relatively positioned block-level box on a line reporting scrollable overflow as wide as the entire line. (319899@main) (185835450)
  • Fixed an issue where an orthogonal block-level box inside an inline box made its container as wide as the box instead of as wide as its content. (319897@main) (185890940)
  • Fixed the position of an orthogonal block-level box inside an inline box. (319895@main) (185895981)
  • Fixed background-size: cover painting nothing for an SVG image with an extreme natural aspect ratio. (320008@main) (186006463)

SVG

New Features

  • Added support for text-overflow as an SVG presentation attribute. (319563@main) (176146717)
  • Added support for SVG external resources, allowing url() references to SVG resources such as filters, masks, and gradients in external documents. (319411@main) (183940963)

Deprecations

  • Removed the use-script, no-change, and reset-size values from dominant-baseline to match SVG 2. (319256@main) (174635806)

Security

Resolved Issues

  • Fixed an issue where navigations to external URLs in subframes were not consistently blocked. (319798@main) (185801936)

Web API

New Features

  • Added support for a stack property on DOMException, matching other error objects. (319782@main) (185770735)

Resolved Issues

  • Fixed the Digital Credentials API settling the navigator.credentials.get() promise synchronously instead of queuing a global task as required by the specification. (319421@main) (182678875)
  • Fixed a streaming fetch() response body being withheld from the reader until the next network chunk arrived. (319981@main) (185883588)

Web Audio

Resolved Issues

  • Fixed AudioBufferSourceNode ignoring the offset passed to start() when using a negative playbackRate. (319674@main) (184484940)
  • Fixed AudioBufferSourceNode to clamp out-of-range loopStart and loopEnd values. (319755@main) (185752079)
  • Fixed an issue where AudioBufferSourceNode rendered only silence when start() was called with an out-of-bounds offset and a negative playbackRate. (319779@main) (185769043)

Web Inspector

New Features

  • Added logging of the site-specific quirks active on a page to the Web Inspector console. (319533@main) (185246801)

Resolved Issues

  • Fixed an exception being thrown when selecting a CDATA section in the DOM tree. (319252@main) (185027124)

WebAssembly

New Features

  • Added support for WebAssembly memory64 together with multiple memories. (319336@main) (185152671)

Resolved Issues

  • Fixed inconsistent CompileError messages when compiling a WebAssembly module with more than one invalid function by always reporting the error for the lowest function index. (319430@main) (140651602)
  • Fixed a re-exported imported WebAssembly.Tag not being the same object as the one that was imported. (319294@main) (185121644)
  • Fixed re-exporting an imported immutable WebAssembly.Global so that the exports object returns the same object instead of a new wrapper. (319351@main) (185184951)
  • Fixed WebAssembly.Module.imports() and WebAssembly.Module.exports() exposing the type field from the unfinished JS Type Reflection proposal. (319427@main) (185281658)

WebGL

Resolved Issues

  • Fixed the orientation and improved the performance of WebGL canvas contents copied into an image, such as when drawing a WebGL canvas into a 2D canvas or an ImageBitmap. (319576@main) (184913852)
  • Fixed getAttribLocation() and getUniformLocation() not reporting an INVALID_OPERATION error when querying an unlinked program with a reserved name prefix. (319903@main) (185565011)

WebGPU

New Features

  • Added support for the snorm10-10-10-2 value of GPUVertexFormat. (319603@main) (185379686)

Resolved Issues

  • Fixed a typo in WGSL diagnostics that named the sampler_comparison type incorrectly. (320037@main) (186031593)

WebRTC

Resolved Issues

  • Fixed an issue where certificate fingerprints were not validated against the previously generated offer or answer when applying a local description. (319822@main) (183719203)
  • Fixed TURN server URLs containing query-string parameters such as ?transport=tcp being rejected, which prevented relay ICE candidates from being gathered. (319365@main) (184556197)

September 11, 2026 05:02 PM

September 08, 2026

Pawel Lampe: Trying WPE Platform API on Raspberry Pi

Igalia WebKit

WPE Platform API (also known as “new API”) is a redesigned, GObject-based platform-integration layer for WPE WebKit that replaces the older libwpe backend model. A few months ago, Kate and Simon published two closely related blog posts about it. The first one focuses more on the API and browser implementation details, while the second one focuses more on writing and integrating the browser within Linux distribution.

This article builds on top of the above ones, and showcases how to build and try a minimal WPE browser using WPE Platform API on Raspberry Pi. Moreover, as the WPE Platform API still evolves to some degree, this article also explains how to use and stick to the latest WPE WebKit from main branch. This way one can play with all the latest features straight on embedded hardware.

Before going further, one should be aware that in case of a simple release build (instead of one using latest main branch) it’s better to follow official instructions instead of this article.

Setup #

This article focuses on a certain setup using Raspberry Pi 3B but it should be fairly easy to adapt the config to any other Raspberry Pi model.

As for the work environment: the Linux-based host with ability to run containers was used along with WebKit Container SDK. The SDK version was precisely 2.53-v6-d535e88 as it uses Ubuntu 24.04.4 LTS that works well with Yocto scarthgap.

The Yocto scarthgap has been used to increase the chances that the config and commands demonstrated in this article will remain buildable for many years to follow.

Preparing the image #

The preparation of the image starts with a series of commands that create a main directory and clone important Yocto repositories along with some meta layer repositories. At this point already, it’s important to have the working directory shared between host and SDK.

# host
mkdir wpe-upstream
cd wpe-upstream
git clone https://git.yoctoproject.org/git/poky -b scarthgap
git clone git@github.com:openembedded/meta-openembedded.git -b scarthgap
git clone https://git.yoctoproject.org/git/meta-raspberrypi -b scarthgap
git clone https://github.com/Igalia/meta-webkit -b scarthgap
source poky/oe-init-build-env build

Once the build directory is created, it’s necessary to configure the meta layers in the build/conf/bblayers.conf file the following way:

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}"
BSPDIR := "${@os.path.abspath(os.path.dirname(d.getVar('FILE', True)) + '/../..')}"

BBFILES ?= ""
BBLAYERS ?= " \
${BSPDIR}/poky/meta \
${BSPDIR}/poky/meta-poky \
${BSPDIR}/poky/meta-yocto-bsp \
${BSPDIR}/meta-openembedded/meta-oe \
${BSPDIR}/meta-openembedded/meta-multimedia \
${BSPDIR}/meta-openembedded/meta-python \
${BSPDIR}/meta-raspberrypi \
${BSPDIR}/meta-webkit \
"

With the above, the recipes from the meta layers cloned earlier will be considered by bitbake.

Next, the most important configuration step is appending the following to build/conf/local.conf:

MACHINE = "raspberrypi3-64" 
MACHINE_FEATURES:append = " vc4graphics"
GPU_MEM_256 = "128"
GPU_MEM_512 = "196"
GPU_MEM_1024 = "396"
DISTRO_FEATURES:append = " opengl egl wayland"
EXTRA_IMAGE_FEATURES = "debug-tweaks"
IMAGE_FEATURES:append = " ssh-server-dropbear hwcodecs"
IMAGE_INSTALL:append = " wpewebkit wpe-browser"
PREFERRED_VERSION_wpewebkit = "latest"
LICENSE_FLAGS_ACCEPTED = "synaptics-killswitch"

With that, wpewebkit latest will be preferred and installed in the image along with a dummy browser called wpe-browser.

To make the wpewebkit latest work, one needs to create meta-webkit/recipes-browser/wpewebkit/wpewebkit_latest.bb:

SUMMARY = "Lightweight WebKit port for embedded devices with OpenGL-ES acceleration"
DESCRIPTION = "WPE WebKit port pairs the WebKit engine with OpenGL-ES (OpenGL for Embedded Systems), \
allowing embedders to create simple and performant systems based on Web platform technologies. \
It is designed with hardware acceleration in mind, relying on EGL, and OpenGL ES."

HOMEPAGE = "https://wpewebkit.org/"
BUGTRACKER = "https://bugs.webkit.org/"
LICENSE = "BSD-2-Clause & LGPL-2.0-or-later"
LIC_FILES_CHKSUM = "file://Source/WebCore/LICENSE-LGPL-2.1;md5=a778a33ef338abbaf8b8a7c36b6eec80 "

REQUIRED_DISTRO_FEATURES = "opengl"

DEPENDS:append = " \
libsoup \
bison-native gperf-native harfbuzz-native libxml2-native ccache-native ninja-native ruby-native \
fontconfig freetype glib-2.0 harfbuzz icu jpeg pcre sqlite3 zlib libpng libtasn1 \
libwebp libxml2 libxslt virtual/egl virtual/libgles2 libepoxy libgcrypt \
unifdef-native \
"


inherit cmake features_check pkgconfig perlnative python3native

export WK_USE_CCACHE = "NO"

PACKAGECONFIG ??= "accessibility avif dfg-jit gbm gpu-process \
jit jpegxl libbacktrace \
mediasource mediastream \
remote-inspector \
sysprof \
${@' system-sysprof' \
if bb.utils.contains('BBFILE_COLLECTIONS', 'meta-gnome', True, False, d) \
else '' }
\
unified-builds video webaudio woff2 wpe-platform \
${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'journald', '' ,d)} \
"


PACKAGECONFIG[reduce-size] = "-DCMAKE_BUILD_TYPE=MinSizeRel,-DCMAKE_BUILD_TYPE=Release,,"
PACKAGECONFIG[release-with-debug-info] = "-DCMAKE_BUILD_TYPE=RelWithDebInfo,-DCMAKE_BUILD_TYPE=Release,,"

# WPE features
PACKAGECONFIG[accessibility] = "-DUSE_ATK=ON,-DUSE_ATK=OFF,atk at-spi2-atk"
PACKAGECONFIG[avif] = "-DUSE_AVIF=ON,-DUSE_AVIF=OFF,libavif"
PACKAGECONFIG[bubblewrap] = "-DENABLE_BUBBLEWRAP_SANDBOX=ON -DBWRAP_EXECUTABLE=${bindir}/bwrap -DDBUS_PROXY_EXECUTABLE=${bindir}/xdg-dbus-proxy,-DENABLE_BUBBLEWRAP_SANDBOX=OFF,bubblewrap xdg-dbus-proxy libseccomp"
PACKAGECONFIG[developer-mode] = "-DDEVELOPER_MODE=ON,-DDEVELOPER_MODE=OFF,wayland-native wayland-protocols wpebackend-fdo"
PACKAGECONFIG[deviceorientation] = "-DENABLE_DEVICE_ORIENTATION=ON,-DENABLE_DEVICE_ORIENTATION=OFF,"
PACKAGECONFIG[dfg-jit] = "-DENABLE_DFG_JIT=ON,-DENABLE_DFG_JIT=OFF,"
PACKAGECONFIG[documentation] = "-DENABLE_DOCUMENTATION=ON,-DENABLE_DOCUMENTATION=OFF, gi-docgen-native gi-docgen"
PACKAGECONFIG[encryptedmedia] = "-DENABLE_ENCRYPTED_MEDIA=ON,-DENABLE_ENCRYPTED_MEDIA=OFF,libgcrypt"
PACKAGECONFIG[experimental-features] = "-DENABLE_EXPERIMENTAL_FEATURES=ON,-DENABLE_EXPERIMENTAL_FEATURES=OFF,libavif libjxl"
PACKAGECONFIG[gamepad] = "-DENABLE_GAMEPAD=ON,-DENABLE_GAMEPAD=OFF,libmanette"
PACKAGECONFIG[gbm] = "-DUSE_GBM=ON,-DUSE_GBM=OFF,libdrm"
PACKAGECONFIG[geolocation] = "-DENABLE_GEOLOCATION=ON,-DENABLE_GEOLOCATION=OFF,geoclue"
PACKAGECONFIG[gpu-process] = "-DENABLE_GPU_PROCESS=ON,-DENABLE_GPU_PROCESS=OFF,"
PACKAGECONFIG[hyphen] = "-DUSE_LIBHYPHEN=ON,-DUSE_LIBHYPHEN=OFF,hyphen"
PACKAGECONFIG[introspection] = "-DENABLE_INTROSPECTION=ON,-DENABLE_INTROSPECTION=OFF, gobject-introspection-native"
PACKAGECONFIG[jit] = "-DENABLE_JIT=ON -DENABLE_C_LOOP=OFF,-DENABLE_JIT=OFF -DENABLE_C_LOOP=ON,"
PACKAGECONFIG[jpegxl] = "-DUSE_JPEGXL=ON,-DUSE_JPEGXL=OFF,libjxl"
PACKAGECONFIG[journald] = "-DENABLE_JOURNALD_LOG=ON,-DENABLE_JOURNALD_LOG=OFF,"
PACKAGECONFIG[lcms] = "-DUSE_LCMS=ON,-DUSE_LCMS=OFF,"
PACKAGECONFIG[spellcheck] = "-DENABLE_SPELLCHECK=ON,-DENABLE_SPELLCHECK=OFF,enchant"
PACKAGECONFIG[wpe-legacy-api] = "-DENABLE_WPE_LEGACY_API=ON,-DENABLE_WPE_LEGACY_API=OFF,libwpe virtual/wpebackend,"
PACKAGECONFIG[libbacktrace] = "-DUSE_LIBBACKTRACE=ON,-DUSE_LIBBACKTRACE=OFF,libbacktrace"
PACKAGECONFIG[minibrowser] = "-DENABLE_MINIBROWSER=ON,-DENABLE_MINIBROWSER=OFF,wayland-native wayland-protocols wpebackend-fdo"
PACKAGECONFIG[mediasource] = "-DENABLE_MEDIA_SOURCE=ON,-DENABLE_MEDIA_SOURCE=OFF,gstreamer1.0 gstreamer1.0-plugins-good"
PACKAGECONFIG[mediastream] = "-DENABLE_MEDIA_STREAM=ON,-DENABLE_MEDIA_STREAM=OFF,gstreamer1.0 gstreamer1.0-plugins-bad"
PACKAGECONFIG[pdfjs] = "-DENABLE_PDFJS=ON,-DENABLE_PDFJS=OFF,"

PACKAGECONFIG[speech-synthesis] = "-DENABLE_SPEECH_SYNTHESIS=ON,-DENABLE_SPEECH_SYNTHESIS=OFF,flite"

PACKAGECONFIG[sysprof] = "-DUSE_SYSPROF_CAPTURE=ON, -DUSE_SYSPROF_CAPTURE=OFF,"
PACKAGECONFIG[system-sysprof] = "-DUSE_SYSTEM_SYSPROF_CAPTURE=ON, -DUSE_SYSTEM_SYSPROF_CAPTURE=OFF, sysprof"
PACKAGECONFIG[video] = "-DENABLE_VIDEO=ON,-DENABLE_VIDEO=OFF,gstreamer1.0 gstreamer1.0-plugins-base"
PACKAGECONFIG[webaudio] = "-DENABLE_WEB_AUDIO=ON,-DENABLE_WEB_AUDIO=OFF,gstreamer1.0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good"
PACKAGECONFIG[woff2] = "-DUSE_WOFF2=ON,-DUSE_WOFF2=OFF,woff2"
PACKAGECONFIG[remote-inspector] = "-DENABLE_REMOTE_INSPECTOR=ON,-DENABLE_REMOTE_INSPECTOR=OFF,"
PACKAGECONFIG[webrtc] = "-DENABLE_WEB_RTC=ON,-DENABLE_WEB_RTC=OFF,libvpx libevent libopus openh264"
PACKAGECONFIG[qtwpe] = "-DENABLE_WPE_QT_API=ON ${CMAKE_QT_OECONF},-DENABLE_WPE_QT_API=OFF,qtbase-native qtbase qtdeclarative libepoxy wpebackend-fdo ${QT_BUILD_DEPS}"
PACKAGECONFIG[unified-builds] = "-DENABLE_UNIFIED_BUILDS=ON,-DENABLE_UNIFIED_BUILDS=OFF,"
PACKAGECONFIG[thunder] = "-DENABLE_THUNDER=ON,-DENABLE_THUNDER=OFF,virtual/open-cdm"
PACKAGECONFIG[webxr] = "-DENABLE_WEBXR=ON,-DENABLE_WEBXR=OFF,openxr"

# Build option for WPE API 1.1
PACKAGECONFIG[wpe-1-1-api] = "-DENABLE_WPE_1_1_API:BOOL=ON,-DENABLE_WPE_1_1_API:BOOL=OFF,"

# Build option for WPE platform API
PACKAGECONFIG[wpe-platform] = "-DENABLE_WPE_PLATFORM=ON,-DENABLE_WPE_PLATFORM=OFF,libinput libxkbcommon wayland-native"

EXTRA_OECMAKE = " -DPORT=WPE -G Ninja"

# TODO: documentation and introspection are disabled by default because the are
# causing cross-compiling build errors
# PACKAGECONFIG:append = " ${@bb.utils.contains('DISTRO_FEATURES', 'api-documentation', 'documentation', '' ,d)} introspection"

# If SSE code compiles, assume it runs successfully (it can't actually run
# because of cross compiling)
EXTRA_OECMAKE:append:x86 = " -DHAVE_SSE2_EXTENSIONS_EXITCODE=0"
# Javascript JIT is not supported on ppc/arm/RISCV32/mips64
PACKAGECONFIG:remove:powerpc = "jit"
PACKAGECONFIG:remove:powerpc64 = "jit"
PACKAGECONFIG:remove:powerpc64le = "jit"
PACKAGECONFIG:remove:armv4 = "jit"
PACKAGECONFIG:remove:armv5 = "jit"
PACKAGECONFIG:remove:armv6 = "jit"
PACKAGECONFIG:remove:armv7a = "jit"
PACKAGECONFIG:remove:armv7ve = "jit"
PACKAGECONFIG:remove:riscv32 = "jit"
PACKAGECONFIG:remove:riscv64 = "jit"
PACKAGECONFIG:remove:mipsarchn64 = "jit"
PACKAGECONFIG:remove:mipsarchn32 = "jit"
PACKAGECONFIG:remove:loongarch64 = "jit"

# Javascript JIT is not supported on x86
PACKAGECONFIG:remove:x86 = "jit"

LDFLAGS:append:riscv64 = " -pthread"

FULL_OPTIMIZATION:remove = "-g"

LEAD_SONAME = "libWPEWebKit.so"
PACKAGES =+ "${PN}-web-inspector-plugin ${PN}-qtwpe-qml-plugin"
FILES:${PN} += "${libdir}/wpe-webkit*/injected-bundle/libWPEInjectedBundle.so"
FILES:${PN}-web-inspector-plugin += "${datadir}/wpe-webkit-*/inspector.gresource"
# nooelint: oelint.vars.insaneskip - ignored for convenience. We need to recheck if problem persist
INSANE_SKIP:${PN}-web-inspector-plugin = "dev-so"

# nooelint: oelint.vars.insaneskip - ignored for convenience. We need to recheck if problem persist
INSANE_SKIP:${PN}-qtwpe-qml-plugin = "dev-so"

# JSC JIT on ARMv7 is better supported with Thumb2 instruction set.
ARM_INSTRUCTION_SET:armv7a = "thumb"
ARM_INSTRUCTION_SET:armv7r = "thumb"
ARM_INSTRUCTION_SET:armv7m = "thumb"
ARM_INSTRUCTION_SET:armv7ve = "thumb"

# Extra runtime depends
# nooelint: oelint.vars.dependsordered - ignored for convenience
RDEPENDS:${PN} += "\
${@bb.utils.contains('PACKAGECONFIG', 'remote-inspector', '${PN}-web-inspector-plugin', '', d)} \
${@bb.utils.contains('PACKAGECONFIG', 'gst_gl', 'gstreamer1.0-plugins-base-opengl', '', d)} \
${@bb.utils.contains('PACKAGECONFIG', 'mediasource', 'gstreamer1.0-plugins-good-isomp4', '', d)} \
${@bb.utils.contains('PACKAGECONFIG', 'webaudio', 'gstreamer1.0-plugins-good-wavparse', '', d)} \
${@bb.utils.contains('PACKAGECONFIG', 'video', 'gstreamer1.0-plugins-base-app \
gstreamer1.0-plugins-base-audioconvert \
gstreamer1.0-plugins-base-audioresample \
gstreamer1.0-plugins-base-gio \
gstreamer1.0-plugins-base-playback \
gstreamer1.0-plugins-base-typefindfunctions \
gstreamer1.0-plugins-base-videoconvertscale \
gstreamer1.0-plugins-base-volume \
gstreamer1.0-plugins-good-audiofx \
gstreamer1.0-plugins-good-audioparsers \
gstreamer1.0-plugins-good-autodetect \
gstreamer1.0-plugins-good-avi \
gstreamer1.0-plugins-good-deinterlace \
gstreamer1.0-plugins-good-interleave \
', '', d)}
\
libgles2 \
"


RDEPENDS:${PN}-web-inspector-plugin += "\
shared-mime-info \
"


# Extra runtime recommends
RRECOMMENDS:${PN} += "\
ca-certificates \
ttf-dejavu-sans \
ttf-dejavu-sans-mono \
ttf-dejavu-serif \
${PN}-qtwpe-qml-plugin \
${@bb.utils.contains('PACKAGECONFIG', 'video', 'gstreamer1.0-plugins-base-meta gstreamer1.0-plugins-good-meta gstreamer1.0-plugins-bad-meta', '', d)} \
"


DEFAULT_PREFERENCE = "-1"

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

# https://commits.webkit.org/319279@main
PR = "r319279"
SRCREV = "93472ec12ee947b30c7ec3c176ca0e2fb6ba6ace"
SRC_URI = "git://github.com/WebKit/WebKit.git;protocol=https;branch=main \
file://0001-libpas-Only-include-stdatomic.h-when-compiling-with.patch \
file://0002-WebDriver-Guard-LOG_CHANNEL-check-with-LOG_DISABLED.patch \
"

S = "${WORKDIR}/git"

This file is self-contained on purpose; the idea is not to rely on any includes so that unpredictable behavior doesn’t happen in the future.

While the above file contains a lot of interesting details, the most interesting practical part is the last few lines. The SRCREV is set to point to the latest commit from the WebKit main branch (at the time of writing). Along with that comes SRC_URI that specifies two patches required to make WPE WebKit compile.

The problem with the patches is that advancing SRCREV will likely make them unusable due to merge conflicts. Therefore, when advancing the revision, it’s recommended to remove patches from SRC_URI and face the compilation problems from scratch as it’s very likely there will be new compilation problems anyway. Fortunately, nowadays LLMs can be used to fix any compilation problems by preparing custom patches just like the below ones (created by LLM as well). For example, most of the modern models should manage to prepare proper patches just by pointing them to the above recipe and the temp directory with the latest logs from build commands.

Assuming one uses the wpewebkit_latest.bb above, the first patch needs to be created in: meta-webkit/recipes-browser/wpewebkit/wpewebkit/0001-libpas-Only-include-stdatomic.h-when-compiling-with.patch with the following content:

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pawel Lampe <plampe@igalia.com>
Date: Mon, 17 Aug 2026 00:00:00 +0000
Subject: [PATCH] [libpas] Only include stdatomic.h when compiling with Clang

Some libpas .c files (e.g. jit_heap.c) are compiled as C++ via
set_source_files_properties(... PROPERTIES LANGUAGE CXX) in
Source/bmalloc/CMakeLists.txt, for TZone heap support. pas_utils.h
unconditionally does `#include <stdatomic.h>`, but GCC's own
<stdatomic.h> relies on the C11-only `_Atomic` keyword and has no
support for being included from C++ translation units (this was only
addressed in much newer GCC releases). Compiling any of the
CXX-tagged libpas .c files with GCC 13 therefore fails with:

    error: '_Atomic' does not name a type

The header is only actually needed here for the Clang-specific
__c11_atomic_* intrinsics guarded by `#elif PAS_COMPILER(CLANG)`
further down in this file; the non-Clang (GCC) path uses the
__atomic_* builtins instead and does not need any of the types or
macros from <stdatomic.h>. Guard the include accordingly so GCC
builds (both plain C and the CXX-tagged libpas sources) are
unaffected by GCC's non-C++-aware <stdatomic.h>.

Upstream-Status: Pending
Signed-off-by: Pawel Lampe <plampe@igalia.com>
---
 Source/bmalloc/libpas/src/libpas/pas_utils.h | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Source/bmalloc/libpas/src/libpas/pas_utils.h b/Source/bmalloc/libpas/src/libpas/pas_utils.h
index 962634080930..9b98fda54f43 100644
--- a/Source/bmalloc/libpas/src/libpas/pas_utils.h
+++ b/Source/bmalloc/libpas/src/libpas/pas_utils.h
@@ -42,7 +42,15 @@
 #endif
 
 #include <limits.h>
+#if PAS_COMPILER(CLANG)
+/* GCC's <stdatomic.h> relies on the C-only _Atomic keyword and is not
+ * usable when this header is included from a translation unit compiled
+ * as C++ (some libpas .c files are compiled as C++, see bmalloc's
+ * CMakeLists.txt). It is only needed here for the Clang-specific
+ * __c11_atomic_* intrinsics below; the GCC path uses __atomic_* builtins
+ * instead. */
 #include <stdatomic.h>
+#endif
 #include <stdbool.h>
 #include <stdint.h>
 #include <string.h>
--
2.43.0


The second patch should be: meta-webkit/recipes-browser/wpewebkit/wpewebkit/0002-WebDriver-Guard-LOG_CHANNEL-check-with-LOG_DISABLED.patch with:

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pawel Lampe <plampe@igalia.com>
Date: Mon, 17 Aug 2026 00:00:00 +0000
Subject: [PATCH] [WebDriver] Guard LOG_CHANNEL check with
 LOG_DISABLED/RELEASE_LOG_DISABLED

WebDriverService::handleRequest() unconditionally checks
LOG_CHANNEL(WebDriverClassic).state to decide whether it is worth
building the request/response log strings. However,
Source/WebDriver/Logging.h only declares the WebDriverClassic (and
other WebDriver) log channels inside:

    #if !LOG_DISABLED || !RELEASE_LOG_DISABLED

On a release build (NDEBUG, so LOG_DISABLED is true) without journald
support and without OS_LOG/Android (so RELEASE_LOG_DISABLED is also
true) - the common configuration for an embedded Linux build without
the "journald" PACKAGECONFIG - that guard is false, so the channel is
never declared, and this direct, unguarded use of LOG_CHANNEL() fails
to compile:

    error: 'LOG_CHANNEL_PREFIXWebDriverClassic' was not declared in this scope

RELEASE_LOG_INFO() itself already collapses to a no-op in that
configuration (see wtf/Assertions.h), so guard this manual
LOG_CHANNEL() state check with the same condition used to declare the
channel in Logging.h.

Upstream-Status: Pending
Signed-off-by: Pawel Lampe <plampe@igalia.com>
---
 Source/WebDriver/WebDriverService.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Source/WebDriver/WebDriverService.cpp b/Source/WebDriver/WebDriverService.cpp
index 07ae385f33f4..2612d239a611 100644
--- a/Source/WebDriver/WebDriverService.cpp
+++ b/Source/WebDriver/WebDriverService.cpp
@@ -381,6 +381,7 @@ bool WebDriverService::findCommand(HTTPMethod method, const String& path, Comman
 void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
 {
     Function<void (HTTPRequestHandler::Response&&)> actualReplyHandler = WTF::move(replyHandler);
+#if !LOG_DISABLED || !RELEASE_LOG_DISABLED
     if (LOG_CHANNEL(WebDriverClassic).state != WTFLogChannelState::Off) {
         RELEASE_LOG_INFO(WebDriverClassic, "HTTP request %s %s (body=%zu bytes)", request.method.utf8().data(), request.path.utf8().data(), request.dataLength);
         actualReplyHandler = [startTime = MonotonicTime::now(), replyHandler = WTF::move(actualReplyHandler)](HTTPRequestHandler::Response&& response) mutable {
@@ -388,6 +389,7 @@ void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Func
             replyHandler(WTF::move(response));
         };
     }
+#endif
 
     auto method = toCommandHTTPMethod(request.method);
     if (!method) {
--
2.43.0

Once the patches are added, wpewebkit latest should build correctly. However, to make use of it one needs a browser.

To demonstrate how easy the browser for WPE WebKit with Platform API can be, a very minimalistic one will be prepared below.

The first step is to create a directory:

mkdir meta-webkit/recipes-browser/wpe-browser/

Then a file: meta-webkit/recipes-browser/wpe-browser/main.cpp that implements the whole browser:

#include <wpe/webkit.h>

int main(int argc, const char *argv[]) {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, false);
g_autoptr(WebKitWebView) view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
nullptr));
webkit_web_view_load_uri(view,
(argc > 1) ? argv[1] : "https://wpewebkit.org");
g_main_loop_run(loop);
return EXIT_SUCCESS;
}

Then a file: meta-webkit/recipes-browser/wpe-browser/CMakeLists.txt that describes how to build it:

cmake_minimum_required(VERSION 3.16)
project(wpe-browser CXX)

set(CMAKE_CXX_STANDARD 17)

include(GNUInstallDirs)

find_package(PkgConfig REQUIRED)

# The Wayland WPE Platform already depends on wpe-platform-2.0
pkg_check_modules(WebKitDeps REQUIRED
IMPORTED_TARGET
wpe-webkit-2.0
wpe-platform-wayland-2.0
)

add_executable(wpe-browser main.cpp)

target_link_libraries(wpe-browser
PRIVATE
PkgConfig::WebKitDeps
)

install(TARGETS wpe-browser RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

and finally a recipe file: meta-webkit/recipes-browser/wpe-browser/wpe-browser_1.0.bb that allows bitbake to build the browser and install it in the image:

SUMMARY = "Minimal WPE WebKit browser launcher"
DESCRIPTION = "A minimal launcher built on the WPE Platform API, displaying \
a URL given as its only argument (defaults to https://wpewebkit.org). \
Based on https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit/"

HOMEPAGE = "https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit/"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

FILESEXTRAPATHS:prepend := "${THISDIR}:"

SRC_URI = "file://main.cpp \
file://CMakeLists.txt \
"


S = "${WORKDIR}"

DEPENDS = "wpewebkit"
RDEPENDS:${PN} += "wpewebkit"

inherit cmake pkgconfig features_check

REQUIRED_DISTRO_FEATURES = "opengl wayland"

After all those steps, everything is ready to perform a build. This time the commands are executed in the SDK:

source poky/oe-init-build-env build
bitbake core-image-weston
cd tmp/deploy/images/raspberrypi3-64/
# flash to SD card based on preference

Trying the image #

Once the image is built and flashed to SD card, and the SD card has been used to boot the Raspberry Pi, one can SSH into it, basically by:

ssh root@<IP>

Then, the browser should work out of the box. The first command to try is the one that doesn’t need the network and therefore is the simplest:

XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 wpe-browser 'webkit://gpu/stdout'

If the network is available, it’s worth starting simple and loading some HTTP page:

XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 wpe-browser 'http://info.cern.ch'

If that works as well, one can try the HTTPS one:

XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 wpe-browser 'https://igalia.com'

If there’s an issue with certificates, it’s likely due to broken date/time, so the correct one needs to be set using a command like:

date -s '2026-09-02 22:34:56'

Conclusions #

Since the Platform API is the future of WPE, it’s worth using it already. As the above sections demonstrate, it hasn’t ever been easier to create a WPE-powered browser for embedded hardware. Moreover, nowadays as LLMs are at play, using latest sources from main branch and quickly patching on demand is a possibility worth utilizing. With that, a broad sea of experimenting possibilities becomes wide open. However, it must not be forgotten that while main branch is great for testing new web platform features implemented in WebKit, it’s not necessarily ideal for testing performance. In such case, it’s better to rely on releases and proper browser engine fine tuning for particular hardware one plays with.

September 08, 2026 12:00 AM

September 07, 2026

Igalia WebKit Team: WebKit Igalia Periodical #76

Igalia WebKit

Update on what happened in WebKit in the week from August 31 to September 7.

This was another week focused on ironing out graphics issues in preparation for the upcoming 2.54.x release series. Did we say release? Here we have another set of packaged release candidates as well!

Cross-Port 🐱

The “paint flushing” feature of the Web Inspector that shows the areas of the layers that were painted is now available. On the other hand, setting WEBKIT_SHOW_DAMAGE=1 in the environment will show the areas of the window that were rendered. For example, if the page is scrolled a little, the whole window is rendered, but no layer has to be repainted.

Graphics 🖼️

Fixed visible pixel snapping in the GTK and WPE ports' Skia compositor by choosing linear instead of nearest-neighbor sampling for backing store tiles whenever a layer's transform no longer maps tile pixels onto screen pixels 1:1, matching what the Texture Mapper backend already got from its always-linear OpenGL code path.

Fixed a clipping bug in the Skia-based compositor that caused layers with overflow: hidden set to not clip blur and box-shadow filter outsets.

Fixed a repaint bug where content changing behind an element with a software-rendered filter such as blur() could leave stale pixels on screen, as with the glow effect that YouTube draws around videos in dark/ambient mode. The fix restores proper tracking of which layers act as the repaint container for a pixel-moving filter, so the repaint area is expanded correctly again on WPE, GTK and macOS.

A few problems with Offscreen Canvas have been fixed.

Releases 📦️

Release candidates WebKitGTK 2.53.92 and WPE WebKit 2.53.92 have been published, and they include polishing and a number of fixes that ensure that there will not be noticeable regressions brought in by the new Skia-based compositor and the damage tracking support—two of the main features of the upcoming stable release series. As the stable release dates approaches, we encourage people who try these preview versions to report issues in Bugzilla.

That’s all for this week!

By Igalia WebKit Team at September 07, 2026 11:43 PM

September 03, 2026

Submit your ideas for Interop 2027

Surfin’ Safari

It’s that exciting time of year again when we start considering Interop proposals for next year! We’re thrilled to invite you to help shape the future of web interoperability.

Interop 2027 is just around the corner, and we want your ideas to make the web platform even more awesome. Whether you’re a seasoned pro or a newcomer with fresh perspectives, we want to hear from you.

Key Dates to Remember

  • Proposal Submission Opens: September 3, 2026
  • Submission Deadline: September 23, 2026

Don’t miss this opportunity to influence the direction of web standards and browser implementations.

Essential criteria

There are two essential criteria that all successful Focus Area proposals will have — testability and web standards. If an idea for a needed web technology doesn’t already have a mature web standard (from the W3C, TC39, etc), then it’s too early for it to be a Focus Area for Interop. Those standards are what make it possible to create automated Web Platform Tests, and the test results are what make the Interop Dashboard.

If you have an idea for cool new web technology that should exist, but doesn’t, then look into proposing it (or helping it along) in the appropriate web standards venue. For example, if you want a new CSS feature to exist, check out filing or commenting on an issue at the CSS Working Group. That is where new CSS gets invented, and the details of exactly how every piece should work get determined and written down.

If you want to propose technology that has insufficient tests or testing infrastructure — consider proposing it as an Interop Investigation. Interop Focus Areas are for web technology that can be tested and scored, while Interop Investigations are assignments the group behind the Interop Project gives themselves to encourage improvements for the future.

Writing a great proposal

The best submissions are typically:

  • Specific: Identify a specific interoperability issue. One feature (or narrow group of related features) per proposal. Think font-size-adjust, not “typography”.
  • Impactful: Provide a clear description of the interoperability problem that needs addressing. Use cases from your own experience are especially helpful.
  • Valuable: Explain how web developers and/or users will benefit. Why is this more valuable than other existing options? Is there evidence this is a common need (perhaps survey results)?
  • Stable: Link to the stable web standard that defines the technology you want improved. (Find it on the MDN web doc for that technology. Like here.)
  • Tested: Look up and link to WPT test coverage. If more tests are needed, how can they be created this fall?

Read the Interop Proposers Guide to learn more.

Take the time needed to fully make your case. Why is the focus area you are proposing important? If there are hundreds of things that could be considered, why does this one stand out? How does the current state of affairs inhibit web developers? How are users affected? Is this technology something a lot of sites use? How critical is fixing this sooner rather than later? And what evidence can you provide that a lot of people share your belief that this is important?

Don’t assume everyone reading the proposals has full knowledge of last year’s proposals, or has had the time to follow all the details of how developers are using (or not using) every web technology out there. Make the case in your proposal. Convince the Interop team!

How to submit

Submit your proposal by filing an issue on Github. Feel free to collaborate with other people. If someone else has already proposed your idea, use the comments in GitHub issues to help further make the case. It’s far better to support an existing issue than have two issues for the same or very similar ideas. You can read proposals submitted in previous years to better understand the process.

Join the Interop Party!

The Interop Project is all about collaboration. By participating, you’re joining a global community of web developers, browser makers, and standards bodies working together to make the web more consistent and reliable across platforms. Share your insights as we make the web even more amazing together. We can’t wait to see your proposals.

September 03, 2026 09:53 PM

September 02, 2026

Fixing Top-Level Await in Safari

Surfin’ Safari

WebKit for Safari 27 adds full spec compliance for top-level await. Before now, some of you may have run into unexpected “accessed before initialization” errors. We fixed it at the root by rewriting Safari’s module loader from the ground up so you can now confidently use await at the top level of your JavaScript modules. If top-level await wasn’t part of your toolkit before, this is a great time to give it another look.

What is top-level await?

Top-level await lets you use await at the top level of a module, enabling the same convenient use of Promises that async functions provide. Async functions provide support for await expressions to simplify complex Promise chains into linear sequences of code, and top-level await allows module authors to benefit from the same ease of use. Effectively, whenever an await expression is encountered, execution is paused until the awaited value is available, and in the meantime, control is returned to the caller of the async function. For ES modules, the analogy differs: when a module encounters a top-level await, any module that imports it is also suspended until the await resolves. However, sibling modules in the dependency graph that don’t depend on the awaiting module can still execute concurrently.

What this means for the web

You can try all of this today. Download Safari Technology Preview 251 or grab Safari 27 beta, and try integrating top-level await into your web apps. It just works! The improvements go beyond just top-level await: with the module loader rebuilt on the right foundation, ES modules as a whole are now something you can build on in Safari without a second thought. And once Safari 27 ships, you’ll be able to lean on top-level await and ES modules across your projects, in production, for everyone.

The problem

Top-level await is a complex feature implemented inside the module loader machinery. The ECMAScript specification’s section of modules leaves some parts up to the host—this includes the mechanism of fetching modules, which in practice will be done over the network by web browsers or from the local filesystem by JavaScript runtimes like Node.js and Bun. The WebKit team wrote Safari’s module loader long ago, back during the days of the WHATWG Loader proposal (last updated January 2016), and we relied on the proposal’s specification of the host-defined functionality. This worked during the early history of ES modules, when module execution was purely synchronous and top-level await didn’t exist. However, ECMAScript 2022 was later released, introducing top-level await as a feature. By this point, the WHATWG Loader proposal had effectively faded into obscurity after being superseded by the ECMAScript standard’s module section, but Safari’s module loader was still based on it. Top-level await was implemented in terms of a proposal that was abandoned before any support for async/await existed in ECMAScript, instead of in accordance with the ECMAScript standard’s algorithms for asynchronous module execution. This mismatch caused subtle bugs that we couldn’t fully resolve for years, despite multiple attempts. Instead of continuing to patch a foundation that couldn’t support the feature, we decided to rebuild it correctly.

An example

A major cause of Safari’s top-level await bugs was in how the module loader chose the order in which to load and evaluate modules. This code example demonstrates the issue:

// main.js

async function load(index) {
    try {
        print("Importing", index);
        const module = await import("./test-module.js");
        print("Imported", index);

        try {
            print(`Keys for ${index}:`, Object.keys(module));
        } catch (e) {
            print("Accessing", index, "failed:", e.message);
        }
    } catch (e) {
        print("Importing", index, "failed:", e.message);
    }
}

try {
    const imports = Array.from({ length: 3 }, (_, i) => {
        return load(i + 1);
    });

    await Promise.all(imports);
} catch (e) {
    print("Test failed:", e);
}
// test-module.js

await new Promise(resolve => setTimeout(resolve, 10));

export function someFunction() {
    return "Hello!";
}

export const someArray = [];

Its purpose is to dynamically load the same module three times in a row, printing the names of the module’s exports each time. It’s supposed to be ordered 1, 2, 3, but if the code is run with the old module loader, something unexpected occurs:

Importing 1
Importing 2
Importing 3
Imported 2
Accessing 2 failed: Cannot access 'someArray' before initialization.
Imported 3
Accessing 3 failed: Cannot access 'someArray' before initialization.
Imported 1
Keys for 1: someArray,someFunction

There are two things going wrong here. First, the order in which the imports complete is wrong. Instead of the expected 1, 2, 3, the order is 2, 3, 1. Second, there are strange errors about accessing a value before it’s initialized. Both problems are caused by the same bug.

When the module loader starts loading the module with top-level await the first time, it begins to execute it, and then pauses execution when it reaches the await. It then returns control to the main module, which begins the second import. The promise for the second import shouldn’t resolve until after the first import is done evaluating, but due to a bug in the old module loader, it resolves immediately. This leads to the second import finishing first, and because the evaluation of the imported module hasn’t completed yet, the code to print the module’s keys accesses uninitialized exports, causing an exception. The same happens with the third import. After those two fail, the first import finishes. This time, the evaluation has completed, so it’s able to successfully print the keys of the module’s exports.

With the new module loader, the output is what you’d expect:

Importing 1
Importing 2
Importing 3
Imported 1
Keys for 1: someArray,someFunction
Imported 2
Keys for 2: someArray,someFunction
Imported 3
Keys for 3: someArray,someFunction

Self-hosted JavaScript builtins vs. native C++

The old module loader was written in JavaScript as a self-hosted builtin. This had some advantages: unlike native code, JavaScript builtins can be inlined into the user code that invokes them, and it’s possible to avoid the performance penalty paid when crossing the boundary between JavaScript and C++. In addition, creation of objects is faster from JavaScript, as it’s sometimes possible to eliminate the heap allocation.

However, we later found multiple drawbacks with self-hosted JavaScript: it’s slower to start up because it has to be compiled at run time, unlike native code, which is fully compiled well in advance. In addition, builtins have inherently wide usage characteristics, which makes it harder for JavaScriptCore’s optimizing JIT compilers to exploit patterns in usage, and the module loader code isn’t a hot path, which further reduces the benefit of compiling at runtime. As a result, overall performance is less stable and predictable than it is for C++. When we rewrote the module loader, we chose to drop the self-hosted builtin approach in favor of fully native code.

The rewrite

In January 2026, we began rewriting the module loader. Because the consensus at that point had been that self-hosted JavaScript was not the ideal approach for the module loader, we started the process by deleting the entire JavaScript file that contained the old module loader.

After that, we began implementing the module loader operations one by one, translating the pseudocode defined in the ECMAScript specification into C++. As a guide through ordering which functions to implement first, given that the module loading machinery is a complex state machine, we read through the specification and took note of how the functions called each other and assembled a flow graph. This gave a starting point.


Leaf functions like ExecuteModule and ModuleRequestsEqual were ideal to implement early because they didn’t depend on any other functions. After a few weeks, the new module loader was able to handle the most common cases and a draft pull request was put up.

Testing the rewrite

Because the point of the rewrite was to improve the module loader’s reliability, thorough testing was essential. Engineers at Bun, whose runtime is built on JavaScriptCore and inherited the old module loader’s problems, graciously provided test cases they’d collected that demonstrated incorrect behavior. It was simple to adapt these to run with JavaScriptCore’s command line shell (jsc) and integrate them as tests.

To stress test the module loader’s performance and functionality, we wrote a fuzzer that produced complex graphs of modules (some with top-level await, others without) and import statements. The goal was to ensure that the observable effects of the module loading process were correct. We did this by generating large graphs and comparing the output of JavaScriptCore with the new module loader to the output of other JavaScript engines. If the text output matched byte-for-byte with other engines’ outputs, we could be sure that the new module loader was handling the test case correctly. And indeed we found in every tested example that the new module loader correctly handled the fuzzer output. Once all the module-related tests from test262 started passing and we fixed many previously failing module tests from WPT as well with no regressions, the new module loader was ready for merging. By this point, we had for a few weeks been daily driving a build of Safari with the new module loader integrated with no issues.

Give it a try

When we release Safari 27, you can start shipping web apps that take advantage of top-level await’s benefits. Until then, download the beta and try integrating top-level await into your projects. If you run into any issues, we’d love to hear your feedback. As always, bug reports can be submitted at bugs.webkit.org.

September 02, 2026 05:05 PM

August 31, 2026

Igalia WebKit Team: WebKit Igalia Periodical #75

Igalia WebKit

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

After such a packed edition last week, we can relax with an even more packed installment this week! The team has been moving full steam ahead with the Layer-Based SVG Engine, and graphics improvements in general, but we also had a handful of other updates, such as SDK updates, a new WebsitePolicies API, and more.

Cross-Port 🐱

Add new ChildChange types for moveBefore(). This addresses issues with the in-progress moveBefore() implementation where scripts would sometimes execute erroneously.

Added alpha channel support to color input choosers.

Fixed Service Worker static routes on non-Apple ports.

Added a WebsitePolicies:upgrade-to-https-policy property. When enabled this policy will automatically try using HTTPS even for HTTP websites (excluding localhost or IPs). The policy can be configured either to allow automatically falling back to HTTP if that fails, or to consider all HTTPS failures fatal for the best security.

Graphics 🖼️

Fixed SVG text being rasterized for the wrong resolution in zoomed standalone SVG documents in the Layer-Based SVG Engine (LBSE). vector-effect: non-scaling-stroke on <text> no longer comes out too thick, and the resulting metrics match the legacy SVG engine.

Fixed viewport clipping in the Layer-Based SVG Engine (LBSE), where a nested <svg> or a <marker> applied its viewport clip even when its content already fitted inside, and since that clip is not pixel-snapped its edge could fall between two device pixels and cut into whatever was drawn right at the viewport border. Painting now skips a clip that removes nothing, which eliminates a class of subtle pixel differences against the legacy SVG engine.

Fixed opacity animations not damaging descendant layers in the GTK and WPE ports, where a descendant that paints outside its parent's bounds kept its stale pixels on screen. The mask-specific damage handling was generalized into a single group-property path shared by opacity, filters, mask blend modes and replicas, which now damages the layer plus the overlap region of its whole subtree.

Added a cycle-analysis subcommand to webkit-sysprof, which draws every frame cycle of a capture as a bar of cells colored by the mark covering that moment on the main thread, showing whether a slow frame stalled on layout, a long timer or rasterization instead of only reporting that it was slow.

Removed redundant text updates for the text children of elements using display: contents, which previously got one on every style resolution regardless of whether their style actually changed. This removes dozens of useless updates per style recalculation in Web Component applications, where every <slot> uses display: contents.

Fixed tile image caching in the Skia compositor by regenerating the cached SkImage whenever a a tile's contents are updated and by adding a texture release callback that keeps the texture valid for as long as the image references it.

Switched video DMA-BUF buffers to Skia promise images in the Skia compositor, so the texture backing a video frame is only created at the point Skia actually draws it, which works for these buffers because the underlying DMABufBuffer can be kept alive until the promise image is released.

Fixed a hang in the Layer-Based SVG Engine (LBSE) when two SVG <pattern> elements reference each other through href, or one references itself: collecting the inherited pattern attributes now remembers which patterns it has already visited, the same cycle detection that gradients have always had. The walk also resolves every reference in the tree scope of the pattern it started from, so a pattern referenced from inside a shadow tree now inherits the right attributes.

Skipped anchor-positioning bookkeeping during style resolution when a document uses no anchor positioning at all, avoiding two hash lookups per styled element.

Cached the SVG viewport size used to resolve lengths in the Layer-Based SVG Engine (LBSE), instead of recomputing the nearest <svg> element's view box rectangle once per shape per frame. The viewport is invariant across a flush and identical for every shape under the same <svg>, so caching removes redundant work.

Removed the code path for GPU rendering without Deferred Display Lists (DDL) in the Skia compositor, so accelerated painting always records into a display list and no longer needs to create GL contexts on worker threads.

Fixed red and blue appearing swapped in non-accelerated video with the Skia compositor, by allocating the video frame's BitmapTexture with the BGRA layout flag and applying that flag when the buffer is turned into a Skia image.

Infrastructure 🏗️

Bumped the GTK and WPE developer SDK from v9 to v11, bringing GStreamer 1.28.5, sparkle-cdm 2026.2 and libsoup 3.7.2. Be sure to update your local wkdev-sdk container using wkdev-update to make sure your development environment matches what the CI is testing.

That’s all for this week!

By Igalia WebKit Team at August 31, 2026 08:12 PM

August 26, 2026

Release Notes for Safari Technology Preview 251

Surfin’ Safari

Safari Technology Preview Release 251 is now available for download for macOS Golden Gate and macOS Tahoe. 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: 317935@main…319386@main.

Accessibility

Resolved Issues

  • Fixed SVG anchors without an href being exposed as links. (318186@main) (181924326)

CSS

New Features

  • Added support for @supports at-rule(...), which tests if a CSS at-rule is supported. (318482@main) (87884897)
  • Added support for the object-view-box property. (318981@main) (132624217)
  • Added support for property-scoped and property-index-scoped caching keywords in random(). (318960@main) (176395332)
  • Added support for the random-item() function. (319180@main) (181816677)
  • Added support for a comma-separated list of conditions in @container queries. (318233@main) (182249814)
  • Added support for white-space-trim in the white-space shorthand. (318486@main) (182739052)
  • Added support for external and data: URL filter references on HTML elements. (317991@main) (183059589)
  • Added support for HighlightRegistry.highlightsFromPoint(). (318758@main) (184150480)
  • Added support for random() in custom properties. (319039@main) (184715508)
  • Added support for the ident() function. (319049@main) (184732904)
  • Added support for the inherit() function. (319062@main) (184738624)
  • Added support for the corner-shape property. (319205@main) (184950973)

Resolved Issues

  • Fixed text overflowing a max-width constrained box when the page is zoomed out, by scaling the minimum font size with the zoom factor. (318509@main) (66569768)
  • Fixed a background image not being repainted when only the URL fragment identifier changes. (318879@main) (105118256)
  • Fixed a subgrid with a large column-gap overflowing a parent constrained by max-width. (318009@main) (137422489)
  • Fixed nested @scope picking the wrong scoping roots when <scope-start> contains :has(:scope ...). (318711@main) (138495467)
  • Fixed outside list markers disappearing or being mispositioned around line breaks. (318561@main) (159277763)
  • Fixed the serialization of the size property in @page when both a page size and orientation are given. (318083@main) (162168233)
  • Fixed the computed value of outline-offset not being snapped as a line width. (319236@main) (180054053)
  • Fixed a flex item’s flex base size being clamped by its own min-width and max-width. (318264@main) (180064739)
  • Fixed :focus-visible being shown after calling focus() inside an already focused ancestor. (318775@main) (181570771)
  • Fixed multi-column height ignoring min-height when it exceeds max-height. (318676@main) (182381903)
  • Fixed scroll snap selecting among equally aligned snap targets in the wrong order. (317997@main) (182571341)
  • Fixed text-underline-offset being mispositioned for highlighted text in vertical writing modes. (318107@main) (182889143)
  • Fixed the serialization of an unrecognized media query to preserve the original text as closely as possible. (318627@main) (182958311)
  • Fixed the serialization of an unrecognized media query feature name to preserve the original casing. (318965@main) (183065105)
  • Fixed @container style queries evaluating root-relative lengths incorrectly. (318144@main) (183094796)
  • Fixed the originating element of ::part() in @container queries to be the element itself. (318620@main) (183154183)
  • Fixed an anti-aliased fringe on ::highlight() background fills in vertical writing modes. (318177@main) (183496114)
  • Fixed the first baseline of a wrap-reverse flex container coming from the wrong flex line. (318154@main) (183516222)
  • Fixed a flex line’s cross size mixing the baseline and last baseline alignment groups. (318165@main) (183536181)
  • Fixed the -webkit-border-image width flag being lost when a length edge precedes a non-length edge. (318214@main) (183584830)
  • Fixed rounding of central baselines for atomic inline elements. (318268@main) (183584888)
  • Fixed the block-ellipsis of a clamped block being styled from the wrong block formatting context. (318437@main) (183606151)
  • Fixed ::highlight() pseudo-elements not inheriting from the parent element’s highlight. (318779@main) (183702060)
  • Fixed clip on a child being ignored when determining whether an ancestor’s background is obscured. (318636@main) (183841775)
  • Fixed var() not behaving as an arbitrary substitution function. (318912@main) (183913427)
  • Fixed line-height units resolving incorrectly when the page is zoomed. (318558@main) (184032914)
  • Fixed the computed value of line-height to more closely match how it was specified. (318710@main) (184232888)
  • Fixed font-* properties resolving font-relative units against their own font instead of the parent element’s font metrics. (319052@main) (184336424)
  • Fixed the rangeStart and rangeEnd animation attributes accepting length values that are not computationally independent when set from script. (318815@main) (184363719)
  • Fixed -webkit-text-stroke not applying to highlighted text. (318976@main) (184375039)
  • Fixed the ViewTimelineOptions inset member accepting length values that are not computationally independent when set from script. (318883@main) (184504829)
  • Fixed ::highlight() not repainting after a CSS rule change. (319169@main) (184643272)
  • Fixed ::highlight() not repainting when a highlight’s range is changed with CSS.highlights.set(). (319154@main) (184672580)
  • Fixed a @function body not inheriting computed custom property values from the calling context. (319031@main) (184701762)
  • Fixed arguments that invoke a @function being treated as a cycle. (319034@main) (184711017)
  • Fixed the random() caching key not including the custom property name. (319096@main) (184722866)
  • Fixed an auto <random-key> not being element-scoped, so every element shared the same item. (319153@main) (184845561)

Deprecations

  • Removed the non-standard percentage value from -webkit-line-clamp. (318858@main) (159808285)
  • Removed margin-trim support from flex containers, following the CSSWG resolution limiting it to block and multi-column containers. (318654@main) (184151309)
  • Removed margin-trim support from grid containers, following the CSSWG resolution limiting it to block and multi-column containers. (318690@main) (184164505)

Canvas

Resolved Issues

  • Fixed ctx.font not re-resolving relative font sizes when set to the same string after the canvas element’s style changed. (318753@main) (184194378)
  • Fixed the canvas font keywords larger and smaller scaling by 1.02 instead of approximately 1.2. (318798@main) (184312611)

HTML

New Features

  • Added support for popover=hint. (318958@main) (184548373)

Resolved Issues

  • Fixed a color input with display: none not opening when its <label> is clicked. (318723@main) (100476630)
  • Fixed createContextualFragment() leaving nested <head> and <body> elements behind when stripping a stray <html> element. (317962@main) (183163523)
  • Fixed a <label> default action triggering when clicked interactive content removes itself. (318827@main) (184381262)
  • Fixed clicking an element nested inside a checkbox or radio input wrongly activating the ancestor input. (318853@main) (184441087)
  • Fixed focus not being restored to the correct element when a <dialog> is closed. (319173@main) (184751412)
  • Fixed confirming an IME composition removing its containing positioned element. (319167@main) (184918934)

JavaScript

New Features

  • Added support for Iterator.prototype.includes(). (318184@main) (173846280)
  • Added support for the RegExp Buffer Boundaries proposal. (318415@main) (178287060)
  • Added support for era and monthCode fields in Intl and Temporal calendars. (318428@main) (182753821)
  • Added support for the traceStack option on WebAssembly.Exception and for Exception.stack. (318397@main) (183342491)
  • Added support for WebAssembly Memory64. (318257@main) (183543404)
  • Added support for deferred module evaluation with import defer. (318481@main) (183892394)
  • Added support for Iterator.prototype.join(). (318502@main) (183892495)
  • Added support for iterator chunking, including Iterator.prototype.chunks() and Iterator.prototype.windows(). (318479@main) (183892687)
  • Added support for the joint iteration proposal, including Iterator.zip() and Iterator.zipKeyed(). (318477@main) (183892768)
  • Added support for importing text modules with an import attribute of type: text. (318950@main) (184443879)

Resolved Issues

  • Fixed a regular expression with ^ inside a group that can match empty failing to anchor the pattern. (317981@main) (183300022)
  • Fixed incorrect matching for sticky regular expressions wrapped in .*. (317983@main) (183300106)
  • Fixed a JSON.stringify() regression in how toJSON is checked. (318072@main) (183400665)
  • Fixed Iterator.zip to use null-prototype objects for its options object and synthetic underlying iterator. (318061@main) (183418070)
  • Fixed incorrect matching for regular expressions containing a negative lookahead. (318234@main) (183601444)
  • Fixed module resolution ignoring the import attribute type in a module request. (318292@main) (183700464)
  • Fixed the Array.from() fast path ignoring a Symbol.iterator override on set.keys() and set.values(). (318374@main) (183801651)
  • Fixed stale character widths produced by v mode class set operations in regular expressions. (318416@main) (183879621)
  • Fixed incorrect matching for regular expressions containing a lookbehind. (318418@main) (183881829)
  • Fixed Temporal.PlainDate construction with years outside the supported range. (318504@main) (183960408)
  • Fixed a TypeError regression when passing monthCode to Temporal calendar methods. (318566@main) (183980712)
  • Fixed the order in which Temporal constructors read newTarget.prototype. (318635@main) (184072358)
  • Fixed the String.prototype.localeCompare() collator cache going stale after a language change. (318616@main) (184096794)
  • Fixed Temporal skipping the epoch range check when resolving a time inside a daylight saving time gap. (318663@main) (184140083)
  • Fixed Temporal time zone identifier parsing to match the specification’s parse records. (318741@main) (184169471)
  • Fixed Array.prototype.sort() not being stable when called without a comparator. (318682@main) (184189734)
  • Fixed global object attribute getters and setters throwing when called as bare functions from global scope. (318748@main) (184273476)
  • Fixed Iterator.prototype.join() to match the latest specification. (318806@main) (184321382)
  • Fixed Iterator.prototype.chunks() and Iterator.prototype.windows() to match the latest specification. (318802@main) (184321801)
  • Fixed Intl.Locale collections not being sorted. (318808@main) (184322384)
  • Fixed formatToParts() omitting the era separator that format() inserts. (318847@main) (184382546)
  • Fixed the import defer implementation to match the updated proposal. (318875@main) (184423667)
  • Fixed \P{...} on the right side of a v mode && or -- operation being treated as a union. (318854@main) (184443379)
  • Fixed Temporal.Duration rounding decisions being made on doubles instead of exact integers. (318901@main) (184472920)
  • Fixed non-ISO calendar date field resolution at the edges of the supported range. (318940@main) (184478785)
  • Fixed Temporal returning plausible values instead of throwing when an underlying calendar operation fails. (318953@main) (184567556)
  • Fixed a regular expression string-list alternative containing a non-BMP character matching on only its leading code units. (318956@main) (184582034)
  • Fixed the WebAssembly.Global value setter not treating a missing argument as undefined. (318967@main) (184608074)
  • Fixed Unicode property escapes ignoring case-insensitive matching in u and v modes. (319064@main) (184762230)
  • Fixed the Before Hijra era year being computed from rendered text instead of the calendar. (319172@main) (184853584)

Media

New Features

  • Added support for HTMLMediaElement.removeTextTrack(). (318250@main) (183365541)

Resolved Issues

  • Fixed WebVTT cue overlap avoidance to move a cue the shortest possible distance. (318012@main) (183151333)
  • Fixed legacy EME generateKeyRequest() returning an incorrect destination URL. (317970@main) (183305929)
  • Fixed requestFullscreen() and requestPictureInPicture() not consuming user activation. (318673@main) (183621577)
  • Fixed selecting On in the media controls captions menu not turning on subtitles. (318468@main) (183776073)
  • Fixed MediaRecorder dropping pending video frames when recording is stopped. (318595@main) (183975207)
  • Fixed the auto position of a WebVTT cue with start or end text alignment to be 50%. (318610@main) (184039946)
  • Fixed MediaRecorder dropping a frame whenever the writer input reports that it is not ready. (318778@main) (184271782)

Navigation API

New Features

  • Added support for the to property on NavigationTransition. (319142@main) (184792753)
  • Added support for intercepting a navigation with a precommit handler. (319208@main) (184893603)

Resolved Issues

  • Fixed sourceElement reporting the deepest clicked element instead of the element responsible for the navigation. (319005@main) (184581613)
  • Fixed NavigationHistoryEntry returning null instead of the empty string for url, key, and id when the document is not fully active. (319007@main) (184590003)
  • Fixed navigateEvent.scroll() not scrolling to the beginning of the document when the fragment does not exist. (319068@main) (184595100)
  • Fixed navigation.activation.from not being null when navigating away from the initial about:blank. (319217@main) (184624752)
  • Fixed navigateerror not firing for a navigation started from a navigateerror handler. (319222@main) (184633618)

Networking

New Features

  • Added support for backpressure when uploading a stream request body with fetch(). (318236@main) (183400867)

Resolved Issues

  • Fixed a same-frame fragment navigation cancelling a download started by a download attribute click in the same task. (319105@main) (165498647)
  • Fixed a page reloading continually by tracking the latest navigation action when considering Enhanced Security state changes. (318304@main) (183480452)
  • Fixed XMLHttpRequest.getAllResponseHeaders() returning the previous response’s headers when the object is reused. (318217@main) (183561750)
  • Fixed XMLHttpRequest.responseURL still returning the aborted request’s URL after calling abort(). (318241@main) (183587043)
  • Fixed the CNAME-cloaking cookie expiry cap being applied to a top-level page’s own cookies instead of only subresources. (318332@main) (183664485)
  • Fixed XMLHttpRequest.send() setting a Content-Type request header on GET and HEAD requests when passed a URLSearchParams. (318424@main) (183801778)
  • Fixed imagesrcset not overriding href on <link rel="preload" as="image">. (318544@main) (183945535)
  • Fixed <link rel=preload as> keywords not being matched ASCII case-insensitively. (318584@main) (184013239)
  • Fixed PDF byte-range requests being blocked by the connect-src Content Security Policy directive instead of object-src. (318797@main) (184258237)
  • Fixed a Content Security Policy host-source of the form *:*/path not matching. (318612@main) (184902666)
  • Fixed a policy decision that outlives its navigation being applied to an unrelated load. (319378@main) (184974939)

Rendering

Resolved Issues

  • Fixed text selection highlighting extending incorrectly when a float precedes the text. (319128@main) (109610452)
  • Fixed text underlines rendering incorrectly for Urdu text. (319220@main) (161299983)
  • Fixed a regression where editing text in some inputs stopped responding to backspace. (318095@main) (183391399)
  • Fixed text-align: justify justifying the last line of a block. (318562@main) (183976364)
  • Fixed Arabic letters in adjacent inline boxes not being joined. (319209@main) (184071471)
  • Fixed a self-collapsing block nested in an inline box losing its margin when the root has a border. (318646@main) (184145672)
  • Fixed trailing whitespace not being collapsed before a block nested in an inline box. (318653@main) (184151239)
  • Fixed baseline alignment ignoring the offset of a block level box nested in an inline box. (318656@main) (184153324)
  • Fixed an empty orthogonal block nested in an inline box stretching the line. (318659@main) (184158817)
  • Fixed the margin after a block level box on a line being lost on relayout. (318660@main) (184159645)
  • Fixed an empty editable container with only an out-of-flow child losing its line. (318671@main) (184176269)
  • Fixed a float inside a block nested in an inline box intruding at the wrong position. (318678@main) (184184683)
  • Fixed the baseline of an inline-block whose last line box holds a block level box dropping that line’s offset. (318702@main) (184222186)
  • Fixed nested inline boxes around a block level box being stretched by their nesting depth. (318704@main) (184222677)
  • Fixed an inline box with cloned decorations generating an extra line between two block level boxes. (318718@main) (184241600)
  • Fixed a block whose content is a table being treated as self-collapsing. (318782@main) (184337051)
  • Fixed a block level box’s margin after being counted twice when only that box needs layout. (318792@main) (184344633)
  • Fixed an outside list marker making its list item’s children inline. (318891@main) (184380304)
  • Fixed an excluded list marker aligning with a block level box on a line instead of the first line of text. (318908@main) (184405037)
  • Fixed a block level box on a line reporting its scrollable overflow at its static position. (318885@main) (184505703)
  • Fixed a block level box on a line not starting a new paragraph for unicode-bidi: plaintext and text-indent: each-line. (318886@main) (184505835)
  • Fixed a highlight on the line after a block level box covering the box’s margin. (318887@main) (184506039)
  • Fixed a block level box on a line switching off text-wrap: balance for the whole container. (318893@main) (184508888)
  • Fixed inline boxes that are not relatively positioned offsetting their out-of-flow children by the position of the line. (318897@main) (184512485)
  • Fixed a block level box on a line not contributing its margin box to its container’s overflow. (318906@main) (184526625)
  • Fixed list-based hit testing reporting the container before a block level box on an earlier line. (318909@main) (184527135)
  • Fixed selection gaps not being filled around a block level box on a line. (318911@main) (184527621)
  • Fixed line-clamp not counting the lines of a block level box on a line. (318921@main) (184545926)
  • Fixed a forced page break after a block level box on a line not moving the following content to the next page. (319029@main) (184712866)
  • Fixed a float not being offered to the caret when its container has a block level box on a line. (319030@main) (184713698)
  • Fixed a <fieldset> on a line taking its legend’s overhang twice. (319033@main) (184714637)
  • Fixed a box that collapsed through with clearance on a line pushing the following content down by its margin. (319160@main) (184917509)
  • Fixed intrinsic width computation when a line holds a block level box. (319212@main) (184968113)

SVG

New Features

  • Added support for external and data: URL filter references on SVG elements. (317538@main) (114823248)
  • Added support for passing a DOMMatrix2DInit dictionary to SVGMatrix.multiply(). (318109@main) (180896205)
  • Added support for external SVG resource references, including clip-path, marker, and paint servers. (318543@main) (183941298)

Resolved Issues

  • Fixed auto sizing of <svg:image> to follow the CSS default sizing algorithm. (318548@main) (122586485)
  • Fixed SVGSVGElement.currentScale being a no-op on an outermost <svg> that is not the document root. (318762@main) (180889449)
  • Fixed SVGLength.value returning negative numbers for r, rx, ry, width, height, and textLength, which disagreed with getComputedStyle(). (317963@main) (181108927)
  • Fixed a clip-path basic shape being mispositioned on a <foreignObject> with a non-zero x or y. (318904@main) (182755161)
  • Fixed feDropShadow ignoring an update to the Y component of stdDeviation when X changes. (318281@main) (183678196)
  • Fixed <animateMotion> ignoring a numeric rotate value. (318565@main) (183832405)
  • Fixed SVG enumerated attributes not reverting to their initial value when removed or set to an invalid value. (319093@main) (184076505)
  • Fixed dynamic changes to x and y on <foreignObject> having no effect. (318669@main) (184172530)
  • Fixed a clip-path basic shape being mispositioned on a rotated or scaled SVG element. (318924@main) (184494108)
  • Fixed <animateMotion> ignoring a zero-length motion path. (318963@main) (184569273)

Deprecations

  • Removed the SVGPathSegList collection and the SVGPathSeg family of interfaces from SVGPathElement, matching SVG 2. (318760@main) (115039851)

Scrolling

Resolved Issues

  • Fixed re-snapping to prefer the block-axis box when the two scroll axes conflict. (318343@main) (183366125)
  • Fixed scroll snapping when a scroll overshoots past the end of the scroll container. (318081@main) (183375912)
  • Fixed element.scroll() and element.scrollTo() not scrolling inside a content-visibility: hidden subtree. (319219@main) (184935071)

Service Workers

New Features

  • Added support for service workers reading stream upload data from a fetch event request body. (317964@main) (183108477)
  • Added support for stream upload backpressure when a service worker reads the upload data. (318346@main) (183529369)

Resolved Issues

  • Fixed a missing Sec-Fetch-Dest header on service worker script requests. (318237@main) (181909962)
  • Fixed network fallback being allowed after a fetch event request body was already disturbed. (318042@main) (183125740)
  • Fixed cancellation of a fetch event request body not being propagated to the original ReadableStream. (318043@main) (183317580)

Web API

Resolved Issues

  • Fixed: Improved the InvalidStateError message thrown by selection methods to explain what is invalid. (318520@main) (134645414)
  • Fixed a missing resize event when an iframe transitions from display: none to visible. (318713@main) (174638256)
  • Fixed timeOrigin being skewed for Navigation Timing entries when the browsing context group changes. (318057@main) (179332119)
  • Fixed the Digital Credentials API to consume transient activation before serializing the request. (318122@main) (182412996)
  • Fixed the Cookie Store API not preserving the order of the cookie change subscription list. (318087@main) (183271849)
  • Fixed notifications after the first persistent one not being closed. (318007@main) (183362637)
  • Fixed getBoundingClientRect() returning a non-zero rect for SVG elements inside hidden containers. (318744@main) (183425103)
  • Fixed quota accounting over-charging positioned writes through a FileSystemWritableFileStream. (318130@main) (183489019)
  • Fixed an Apple Pay deferred payment request being dropped when the payment method changes. (318155@main) (183516996)
  • Fixed Cache Storage size accounting underflowing and persisting a corrupt size. (318204@main) (183578693)
  • Fixed FileReader.abort() being silently ignored on a reader reused after a completed read. (318288@main) (183695958)
  • Fixed MathML and SVG elements not resetting tabIndex when tabindex is changed to an invalid value. (318303@main) (183696552)
  • Fixed fullscreen state not being unwound when entering fullscreen fails. (319243@main) (184157871)
  • Fixed the SharedWorkerGlobalScope prototype not being an immutable prototype exotic object. (318684@main) (184190755)
  • Fixed the scrollend event not firing when wheel-scrolling a subframe. (318805@main) (184352772)

Web Audio

Resolved Issues

  • Fixed a loss of precision in exponentialRampToValueAtTime() caused by accumulating the ramp value. (317955@main) (124498013)
  • Fixed decodeAudioData() failing with an EncodingError for valid M4A files whose ftyp brand is not mp4. (318005@main) (183148186)
  • Fixed registerProcessor() error messages so they name the offending AudioParam instead of the processor. (317948@main) (183189781)
  • Fixed an OscillatorNode with a-rate automation starting out of phase by honoring sub-sample start times. (317953@main) (183194827)
  • Fixed the AudioBufferSourceNode output channel count to account for whether the node is actively processing. (317956@main) (183245229)
  • Fixed stale audio being played instead of silence when the audio destination ring buffer underruns. (317968@main) (183305574)
  • Fixed an AudioScheduledSourceNode not firing the ended event when it is not connected to the destination. (318515@main) (183986981)
  • Fixed a k-rate AudioParam input to AudioBufferSourceNode.detune diverging from the equivalent automation. (318516@main) (183987621)
  • Fixed AudioContext.close() not rejecting when called on an already-closed context. (318522@main) (183989479)
  • Fixed AudioWorkletGlobalScope.currentFrame reporting 0 for a worklet created while the context is suspended. (318601@main) (184081765)
  • Fixed an AudioWorkletProcessor being torn down too early after process() returns false. (318621@main) (184107806)
  • Fixed the AudioWorkletNode processorerror event missing filename, lineno, and colno. (318705@main) (184224032)
  • Fixed MediaStreamAudioSourceNode capturing the wrong track and continuing to play after the track ends. (318706@main) (184224142)
  • Fixed a zero-duration exponentialRampToValueAtTime() at a non-frame-aligned time rendering NaN. (318868@main) (184434164)
  • Fixed setting ConvolverNode.buffer to null not releasing the impulse response. (318870@main) (184434803)
  • Fixed reverse playback of buffers longer than 2^23 frames extrapolating outside the source samples. (318869@main) (184439324)

Web Extensions

New Features

  • Added support for wildcard domains and subdomains in externally_connectable.matches. (318361@main) (183642164)
  • Added support for setAccessLevel() on the local and sync storage areas. (318980@main) (183680404)
  • Added support for scripting.ExecutionWorld. (319009@main) (184284891)

Resolved Issues

  • Fixed several inconsistencies in the browser.alarms API. (318493@main) (183770530)
  • Fixed a significant delay loading web extensions when the browser is launched cold. (318756@main) (183827503)

Web Inspector

New Features

  • Added icons for recorded WebGL and WebGPU actions in the Canvas tab. (318517@main) (42777850)
  • Added support for recording WebGPU devices in the Canvas tab. (318407@main) (183538409)
  • Added support for instrumenting WebGPU devices in the Canvas tab. (318336@main) (183757645)
  • Added support for WebGPU content previews in the Canvas tab. (318363@main) (183785306)
  • Added support for instrumenting WebGPU pipelines in the Canvas tab. (318366@main) (183790749)
  • Added support for disabling WebGPU render pipelines in the Canvas tab. (318403@main) (183857257)
  • Added support for using the WebGPU label of devices and pipelines as their name in the Canvas tab. (318409@main) (183876768)
  • Added support for exposing client metadata for WebGPU devices. (318467@main) (183939079)
  • Added support for recording WebGL extension actions in the Canvas tab. (318474@main) (183945866)
  • Added support for mapping WebAssembly byte offsets to original sources with source maps. (318546@main) (184017160)
  • Added support for editing WebGPU shaders in the Canvas tab. (318633@main) (184128414)
  • Added support for highlighting WebGPU render pipelines in the Canvas tab. (318668@main) (184169419)
  • Improved the estimated memory use reported for WebGL and WebGPU canvases to include their graphics resources. (318915@main) (184535731)
  • Added support for recording object arguments and result objects in the Canvas tab. (319059@main) (184759172)
  • Added support for showing names for recorded WebGPU bitfields. (319174@main) (184928178)
  • Added support for snapshotting WebGPU element image copies. (319176@main) (184928370)
  • Added syntax highlighting to recording actions in the Canvas tab. (319189@main) (184936730)

Resolved Issues

  • Fixed canvas recordings retaining memory by pruning recordings that are no longer in use. (318613@main) (35893620)
  • Fixed searching resources returning duplicate results for the same resource. (319061@main) (42879586)
  • Fixed resource data for non-buffered resources persisting across navigations. (318035@main) (181210169)
  • Fixed WebAssembly module scripts not being associated with the resource that fetched them. (318207@main) (183583149)
  • Fixed stale frame navigation event timestamps being shown after initializing. (318213@main) (183584784)
  • Fixed a selected details sidebar panel not being restored when it becomes available again. (318353@main) (183778380)
  • Fixed detaching a child frame also detaching its parent frame. (318355@main) (183778531)
  • Fixed stale execution contexts being kept when their identifiers change. (318356@main) (183779166)
  • Fixed response end times being dropped for resources without request timing data. (318360@main) (183783292)
  • Fixed recorded WebGPU object arguments not being swizzled correctly. (318545@main) (184017109)
  • Fixed missing console.record() options being treated as if they were present. (318555@main) (184029716)
  • Fixed recorded object identifiers colliding by assigning them per type. (318567@main) (184040370)
  • Fixed configured WebGPU <canvas> elements not being distinguished from CSS canvas client nodes. (318794@main) (184346714)

WebCodecs

New Features

  • Added support for the WebCodecs ImageDecoder API. (319179@main) (184857272)

WebGPU

Resolved Issues

  • Fixed a WGSL miscompilation when swizzling a packed vec3 value. (318098@main) (183426431)
  • Fixed a large frame-rate drop when executeBundles() is called with bundles containing indirect draws. (318538@main) (183458069)
  • Fixed textureDimensions(t, level) clamping the mip level one past the last valid level. (318273@main) (183673818)
  • Fixed GPUQueue.copyExternalImageToTexture() uploading indexed PNG images incorrectly. (318952@main) (184529653)

WebRTC

New Features

  • Added WebRTC AV1 software fallback support. (317986@main) (182789617)

Resolved Issues

  • Fixed a valid current value being ignored when both min and max are given in a capture constraint range. (318046@main) (182968516)
  • Fixed setSinkId() reporting success without switching audio output for a single-track MediaStream. (318149@main) (183147942)
  • Fixed getUserMedia() interrupting the microphone system-wide when echoCancellation is disabled. (318164@main) (183517329)
  • Fixed ImageCapture.takePhoto() failing permanently after a single transient capture error. (318260@main) (183640839)

August 26, 2026 09:36 PM

August 24, 2026

Igalia WebKit Team: WebKit Igalia Periodical #74

Igalia WebKit

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

Another periodical packed with updates on the graphics, multimedia, and tooling fronts. Which sure has to do with preparing for the upcoming 2.54.x release series, that now has release candidates published. Also, do not miss a new stable release being published with fixes for security issues, and make sure to update.

Cross-Port 🐱

Fixed flakiness in the Web Inspector heap snapshot tests.

Extended the webkit-sysprof analyze tool cover the remaining marks emitted along the rendering pipeline, from RenderTreeBuild and CompositingUpdate through FinalizeRenderingUpdate, RenderLayerTree and WaitForCompositionCompletion down to the individual tile marks, pulling the tile count and the dirty region out of the mark messages as statistics next to the durations. The statistics tables also gained mean and median columns, so a captured trace now shows where the time in a frame actually goes.

Fixed test fast/canvas/canvas-composite-text-alpha.html, and improved the state of several other tests which relied on setTimeout().

Implemented moving steps for <option> elements.

Add support for asynchronous scrolling with touch events.

Touch events are now dispatched to the EventDispatcher thread in the Web Process, enabling smooth scrolling even when the main thread is busy. Additionally, this change fixed a bug where precise scrolling delta wheel events, dispatched by touchpads, failed to trigger asynchronous scrolling.

Multimedia 🎥

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

Ensure that multimedia on pages restored from the back-forward cache is correctly processed without errors or reloading.

The robustness level is now queried from the CDM (if supported) when using Encrypted Media Extensions (EME).

Added video rendering support for Qualcomm hardware-accelerated decoders when the Skia compositor is in use, complementing the earlier TextureMapper-only support that left such videos blank with the new compositor.

Video frame processing now relies on the driver's implicit YUV to RGB conversion, steered by the colour space and sample range hints taken from the frame colorimetry, with a hint-free import as fallback.

Graphics 🖼️

Fixed how <feImage> paints its referenced element in the Layer-Based SVG Engine (LBSE), which still used a helper written for the legacy SVG engine, where SVG content never had layers, so it used to paint renderers directly and skipped any child that owns a RenderLayer under LBSE, silently dropping its opacity, mask, filter or 3D transform. The referenced content is now painted through the layer tree, the way <mask>,<clipPath>, <pattern> and <marker> content already is.

Made SVG mask no longer force a RenderLayer in the Layer-Based SVG Engine (LBSE), mirroring the earlier change for clip-path. A mask is now applied during painting by SVGNonLayerClippingAndMaskingScope, which opens one transparency layer capturing the renderer's foreground and composites the mask over it afterwards, and which also absorbed the clips that cannot be expressed as a path. A container with a mask keeps its layer, since the mask covers its whole subtree, while a leaf stays layer-free. This further reduces the layer overhead that has been holding LBSE back against the legacy SVG engine.

Skipped scroll coordination for composited layers that have no scrolling role, where the per-layer update previously walked every branch to detach roles the layer had never registered for, only to hand back the parent node ID unchanged. Cutting that work out shortens every compositing update, which matters for composition-heavy workloads.

Fixed rendering of an outermost <svg> with an empty viewBox in the Layer-Based SVG Engine (LBSE), which per the SVG specification should disable painting when the width or height is zero, matching what the legacy engine already did.

Skipped scroll coordination work for layers that have no scrolling role during compositing updates, where updateScrollCoordinationForLayer previously walked every branch to detach roles the layer had never registered for, only to hand back the unchanged parent node ID.

Releases 📦️

WebKitGTK 2.52.6 and WPE WebKit 2.52.6 have been released, including a number of fixes for security issues covered in the accompanying security advisory WSA-2026-0005 (GTK, WPE). It is recommended for everybody to update to these stable releases.

Stabilization for the upcoming 2.54.x release series for both the GTK and WPE ports is ongoing, with the first stable release, 2.54.0, expected around mid-September 2026. In the meantime release candidates WebKitGTK 2.53.91 and WPE WebKit 2.53.91 have been released.

Those interested in previewing the work done by the team in the last half year, including the new Skia-based compositor that is expected to eventually replace the aging TextureMapper, may want to give them a try and report any issues found in Bugzilla.

That’s all for this week!

By Igalia WebKit Team at August 24, 2026 11:30 PM

August 17, 2026

Igalia WebKit Team: WebKit Igalia Periodical #73

Igalia WebKit

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

Following an extra packed periodical, this week we get back to a more regular pace with two nice bugfixes, and a new tool to analyze WebKit performance on Linux!

Cross-Port 🐱

The webkit-sysprof toolkit landed in main thus introducing a set of tools for processing Sysprof .syscap capture files recorded from WebKit (GTK/WPE ports). It extracts marks (timeline events) and counters (time-series metrics) from a capture and lets one dump, summarize, analyze, or plot delta-time histograms for them.

Graphics 🖼️

Fixed filters specified on the outermost <svg> element in the Layer-Based SVG Engine (LBSE), where a filter: url(...) reference on an SVG root was silently dropped because the layer code skipped it, as the legacy engine used to apply it by itself. The filter region is now resolved against the SVG root's border box in its container's coordinate system, since the outermost <svg> is a replaced element in the CSS box tree, not part of the SVG user space its children live in.

Avoided serializing gradient and pattern transforms just to answer a presence check in the Layer-Based SVG Engine (LBSE). Asking hasAttribute() whether gradientTransform or patternTransform was specified forced the transform list to be serialized into the attribute map whenever the base value was changed through the SVG DOM, even though that string is never read back, so the check is now answered directly from the typed accessor.

That’s all for this week!

By Igalia WebKit Team at August 17, 2026 07:08 PM

August 13, 2026

Release Notes for Safari Technology Preview 250

Surfin’ Safari

Safari Technology Preview Release 250 is now available for download for macOS Golden Gate and macOS Tahoe. 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: 317507@main…317934@main.

Accessibility

Resolved Issues

  • Fixed VoiceOver reading a stray newline and skipping lines when navigating a <textarea> whose value ends in a line break. (317784@main) (182912872)

CSS

New Features

  • Added support for the spaces value of the ruby-overhang property, which limits annotation overhang to adjacent spaces and punctuation, with none now an alias for spaces. (317569@main) (181808420)
  • Added support for the content property on the ::marker pseudo-element. (317570@main) (182750380)
  • Added support for the text-decoration-inset property. (317571@main) (182750933)
  • Added support for the white-space-trim property. (317573@main) (182751076)
  • Added support for the wrap-inside property. (317574@main) (182751559)

Resolved Issues

  • Fixed a list-style-position: inside marker glyph being painted one device pixel away from the equivalent inline text. (317789@main) (182447949)
  • Fixed text-decoration specified on ::selection never being painted. (317673@main) (182757292)
  • Fixed a text decoration on ::selection not being painted when the selected text had no decoration of its own. (317695@main) (182864748)
  • Fixed a max-content sized block collapsing to zero width when its child used an orthogonal writing mode. (317781@main) (183026896)

Deprecations

  • Removed support for the non-standard text color value; use canvastext instead. (317687@main) (100364547)

Editing

New Features

  • Added support for copying and pasting image/svg+xml data with the Clipboard API, sanitizing the markup on both read and write. (317528@main) (137553836)

JavaScript

New Features

  • Added support for the Explicit Resource Management proposal, including using and await using declarations, Symbol.dispose, DisposableStack, and AsyncDisposableStack. (317894@main) (103209363)
  • Added support for Iterator.zip() and Iterator.zipKeyed() from the Joint Iteration proposal. (317886@main) (180967133)
  • Added JavaScript and WebAssembly feature flags alongside other web feature settings. (317306@main (https://commits.webkit.org/317306@main)) (182210113)

Media

Resolved Issues

  • Fixed a spurious pause and ended event firing on a live HLS stream when playback reached the live edge. (317626@main) (100556746)
  • Fixed a media fragment with an out-of-range hours value being accepted as a valid seek instead of being rejected. (317869@main) (181820705)
  • Fixed a WebVTT cue incorrectly inheriting a preceding line as its identifier across a REGION or STYLE block. (317811@main) (181977285)

Networking

New Features

  • Added initial support for uploading a ReadableStream body with fetch(). (317724@main) (182510365)
  • Added support for the duplex option on Request, which is required when uploading a ReadableStream body. (317768@main) (182802330)

Scrolling

Resolved Issues

  • Fixed scrollIntoView() on an inline element sharing a line with a much taller element scrolling to the top of the line box instead of to the element. (317933@main) (178491868)
  • Fixed paged scrolling with Page Down or the spacebar skipping over intermediate scroll snap points. (317562@main) (182562852)
  • Fixed a crash that could occur while sending a scroll update during window tear-down. (317785@main) (182972245)
  • Fixed re-snapping after layout always preferring a focused element, instead of only when focus has changed. (317892@main) (183146768)

Storage

Resolved Issues

  • Fixed an IndexedDB put() that fails a unique index constraint deleting the record it would have overwritten, leaving the object store with neither the old nor the new record. (317632@main) (181513905)

Web API

Resolved Issues

  • Fixed Digital Credentials get() being rejected in an iframe inside a focused window. (317656@main) (109191868)

Web Inspector

New Features

  • Added support for showing WebAssembly modules in the Sources tab. (317891@main) (182605814)

Resolved Issues

  • Fixed disabling the resource cache overwriting a Cache-Control header already set by the page. (317814@main) (183072123)

WebAssembly

New Features

  • Added support for relaxed SIMD instructions. (317549@main) (180967232)
  • Added support for multiple memories in a single WebAssembly module. (317636@main) (182219080)

WebDriver

New Features

  • Added support for the “consume user activation” extension command, which consumes the current browsing context’s transient user activation and reports whether it was present. (317715@main) (102634281)

August 13, 2026 09:24 PM

August 10, 2026

Igalia WebKit Team: WebKit Igalia Periodical #72

Igalia WebKit

Update on what happened in WebKit in the week from July 28 to August 10.

Quite a packed pair of weeks this time! The range of updates is big, but some highlights are the handful of Layer-Based SVG Engine updates, performance improvements, and the new WPE APIs. Finally, the Web Engines Hackfest recordings are now published!

Cross-Port 🐱

Fixed webkit_website_data_get_size() looking up sizes for localStorage, indexedDB, and the DOM Cache.

Enabled support for the File System and Storage APIs.

Added WEBKIT_WEBSITE_DATA_FILE_SYSTEM API to enable fetching/clearing File System data sites use.

Multimedia 🎥

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

Landed a follow-up fix for the h264 edit list support to prevent regressions on YouTube MSE Conformance Tests 2019 when using older versions of GStreamer.

Avoided a spurious seek to zero when seeking to the end of an audio/video when playback starts.

Graphics 🖼️

Recovered a MotionMark compositing regression in the Skia backend, where respecting damage information during compositing cost about 20% on the composition suite and more than 50% on two of its tests. Restricting a draw to the damaged area means splitting it into source-rect-to-destination-rect pieces or drawing it under a device-space clip, and neither is needed when the damage already covers the whole draw, which is the common case in those tests because a composited layer is much smaller than a damage grid cell. Those draws are now issued exactly as they would be with damage turned off, avoiding a clip path that flushed the image set batch and left it an order of magnitude smaller, and with the regressions gone, using damage information for compositing was enabled again along with unifying damaged regions that are sent to the system compositor.

Skipped a per-frame visual overflow recomputation in the container paint cull of the Layer-Based SVG Engine (LBSE). The cull used a cached overflow rect that was recomputed by unioning all descendant bounds on a miss, which happened every frame for containers with an animated transform. It now only runs when the rect is already cached.

Skipped the outline paint pass for SVG renderers without an outline in the Layer-Based SVG Engine (LBSE). Every shape used to be painted twice per frame, the second pass being a no-op in the common outline-free case, so guarding it behind hasOutline() removes a redundant traversal from every frame.

Fixed masked SVG content being cut off at the edges in the Layer-Based SVG Engine (LBSE), where the mask image was sized over the enclosing integer rect of the mask content bounds in device space while the transparency layer clip was computed differently, losing the outermost pixels. An SVG renderer that is a box (<text>, <foreignObject>) also used the CSS mask clip rect derived from the border box, which leaves out SVG content spilling outside it, and now uses the visual overflow rect instead

Fixed most of the remaining <mask> issues in the Layer-Based SVG Engine (LBSE): masks were displaced on targets whose children carry transforms, the mask region given by x, y, width and height was ignored so content reaching past it was not cut off, and the cached mask image was never dropped on layout, leaving a resized viewport masking with an image rasterized for the old size.

Stopped rebuilding objectBoundingBox gradients on every layout size change in the Layer-Based SVG Engine (LBSE), which used to discard the cached gradient and re-collect its attributes (serializing every animated property back to a string, including gradientTransform) on the next paint. That work is wasted for objectBoundingBox units, whose coordinates resolve against the object bounding box with the userspace transform recomputed on every paint anyway, so only the clients are repainted now, while userSpaceOnUse gradients resolve against the viewport and are still invalidated as before.

Cached the SVG fill and stroke paint server directly on the renderer in the Layer-Based SVG Engine (LBSE), instead of in the shared referenced-resources table living in a renderer's rar data, where every fill and every stroke paid the cost of a hash map lookup just to reach the cache, which gives a small win on the MotionMark/Suits performance test. It also fixed a shape referencing a paint server that does not exist yet, which kept painting unfilled once an element finally took that id, because the shape registered itself as a pending resource under the full resolved URL while the lookups used the bare fragment identifier.

Sped up mapping the paint dirty rect through transforms in the Layer-Based SVG Engine (LBSE). Transformed SVG paints inverted the full 4x4 matrix on every paint, and now use the cheaper inverse of the 2x3 affine transform whenever the transform is affine, falling back to the 4x4 inverse only for 3D transforms.

Made SVG clip-path no longer force a RenderLayer in the Layer-Based SVG Engine (LBSE). A bare clip is now applied during painting through a shared ClipPathPaintScope, a scope object that sets up the clip in its constructor and tears it down afterwards, handling CSS basic-shape and box clips as well as SVG clipper resources so both regular CSS boxes and SVG content share one path. This is a further step in removing the intrinsic need for layers on SVG renderers, continuing the effort to close the performance gap between LBSE and the legacy SVG engine.

Fixed dynamic x and y updates on SVG <foreignObject> elements, which stopped taking effect after the viewport geometry started being derived from the resolved style. The x, y, width and height attributes are presentation attributes mapped to the CSS x, y, width and height properties, but only width and height marked the presentational hint style as dirty when they changed, so a style recalc never ran for x and y and layout kept reading stale values. All four geometry attributes now invalidate the presentational hint style, matching how <rect> handles its geometry, so setting x.baseVal.value from script repositions the <foreignObject> as expected.

Added support for external and data: URL references to clip-path, markers and paint servers (gradients and patterns) in the Layer-Based SVG Engine (LBSE). Until now the LBSE resource resolvers only looked for the referenced fragment inside the local document, so markup like url(file.svg#id) silently resolved to nothing, while filters already worked and the legacy SVG engine handled all of these since a few weeks.

Fixed SVG filters vanishing on elements with a very large bounding box in the Layer-Based SVG Engine (LBSE). The filter region was seeded with the element's object bounding box and then united with each referenced <filter> region, but a referenced <filter> brings its own region, and that region alone decides where the filter paints, so the union could grow far past the image buffer limits and get clamped down to scale that made the output disappear. When every function in the chain is a <filter> reference the bounding box is now dropped and only the referenced regions are kept, matching what the legacy SVG engine does, while objectBoundingBox filter units still resolve exactly as before and HTML/CSS filters are untouched.

WPE WebKit 📟

Add the WebView::run-color-chooser API to WPE to allow applications to show color choosers, similar to WebKitGTK's API.

The WPE port can now use libsecret for persistent credential storage, reusing the implementation from the WebKitGTK port. This is disabled by default and can be toggled passing -DUSE_LIBSECRET=ON to CMake when configuring the build.

Add the WebKitClipboardPermissionRequest API to WPE, allowing support for the clipboard permission similar to WebKItGTK.

Community & Events 🤝

The videos of the Web Engines Hackfest 2026 talks have been published, including the sessions from the new WPE WebKit track. This year the following WebKit-related talks have been recorded:

That’s all for this week!

By Igalia WebKit Team at August 10, 2026 06:51 PM

July 29, 2026

Release Notes for Safari Technology Preview 249

Surfin’ Safari

Safari Technology Preview Release 249 is now available for download for macOS Golden Gate and macOS Tahoe. 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: 316531@main…317820@main.

Accessibility

Resolved Issues

  • Fixed an issue where an aria-labelledby relation was not built when its target element was inserted via innerHTML. (316913@main) (179677418)
  • Fixed an issue where VoiceOver was silent during character navigation inside a local iframe. (317264@main) (182229891)
  • Fixed an issue where web content somtimes appeared empty to assistive technology after navigation. (317449@main) (182544199)

Animations

Resolved Issues

  • Fixed an issue where calling play() on a finite scroll-driven animation timeline did not reset its start time to the current time. (316745@main) (181712713)
  • Fixed an issue where animation-timeline did not use the last matching timeline when it matched multiple timelines. (317330@main) (182405676)
  • Fixed an issue where animation-timeline could match a timeline outside the nearest timeline-scope element with that name. (317376@main) (182417407)

CSS

New Features

  • Added support for the attr() function. (316672@main) (173992627)
  • Added support for outline-offset: inset. (316677@main) (180547604)
  • Added support for the @function custom functions rule. (316671@main) (181625123)
  • Added preview support for the wrap-inside property. (317182@main) (181751564)
  • Added support for the if() function. (316813@main) (181806268)
  • Added support for calc-mix(). (316847@main) (181839626)
  • Added support for justify-self on block-level boxes. (317212@main) (181926245)
  • Added preview support for text-decoration-inset. (317230@main) (182019037)
  • Added preview support for the content property on the ::marker pseudo-element. (317211@main) (182066124)

Resolved Issues

  • Fixed an issue where setting view-transition-name dynamically did not create a stacking context. (317310@main) (178316558)
  • Fixed an issue where CSS Grid items with a computed preferred size that behaves as auto did not correctly compute their minimum content contribution. (316697@main) (181146154)
  • Fixed an issue where visually overflowing content in a CSS Grid item was not painted. (316668@main) (181277780)
  • Fixed anchor-center in vertical writing modes not being scroll-adjusted along the block axis. (316537@main) (181413103)
  • Fixed an issue where an empty editable flex container did not reserve a line’s worth of block size in vertical writing modes. (317467@main) (181485387)
  • Fixed an issue where CSS Grid did not correctly increase track sizes to accommodate items spanning flexible tracks. (317426@main) (181635324)
  • Fixed an issue where stretch alignment did not trigger stretch sizing for CSS Grid items. (316949@main) (181750071)
  • Fixed an issue where CSS Grid items could retain stale block sizes after a subsequent layout. (317166@main) (182144347)
  • Fixed an issue where clip-path shapes could render with incorrect arcs. (317397@main) (182443333)
  • Fixed an issue where ::highlight() and ::target-text ignored text-underline-offset. (317440@main) (182466757)
  • Fixed an issue where ::highlight() decoration replaced the originating element’s decoration instead of layering over it. (317439@main) (182523249)

Editing

Resolved Issues

  • Fixed an issue that disallowed font names that were not a valid CSS identifiers. (316948@main) (51409819)
  • Fixed the Edit menu’s Copy item being incorrectly enabled when there was no selection in the web page. (316571@main) (176061974)

Forms

Resolved Issues

  • Fixed an issue where the datalist popup menu was presented in the wrong place on a screen to the left of the main screen. (316965@main) (181967418)

HTML

New Features

  • Added support for popover=hint. (317402@main) (129495028)

JavaScript

New Features

  • Added support for the Temporal object. (316742@main) (181723535)

MathML

Resolved Issues

  • Fixed an issue where U+2016 (DOUBLE VERTICAL LINE) was missing the symmetric property in the MathML operator dictionary. (316867@main) (179558196)

Media

Resolved Issues

  • Fixed an issue where finding text starting from a selection could resolve to the wrong caption cue instead of the one nearest the video’s playhead. (317168@main) (182001231)

Rendering

New Features

  • Added support for dark mode in the XML document viewer. (316832@main) (122234600)

Resolved Issues

  • Fixed an issue where box-shadow rendering broke when the page was zoomed. (317080@main) (169167365)

Scrolling

Resolved Issues

  • Fixed an issue where re-snapping did not select the snap area aligned in both axes when multiple targets were aligned. (317294@main) (182285397)
  • Fixed an issue where scroll-snap-align was not respected when scrolling to an anchor or calling scrollIntoView(). (317495@main) (182544180)

Spatial Web

Resolved Issues

  • Fixed an issue where navigating back to a page with a previously active web environment left the UI stuck showing the environment as active. (316891@main) (180966321)
  • Fixed an issue where the background color of HTMLModelElement did not match spec for non-opaque colors. (316788@main) (181641249)

Web API

New Features

  • Added support for subgroups in WebGPU. (317145@main) (154874391)
  • Added support for close watchers, including the closedby attribute for dialog elements. (316680@main) (180105698)

Resolved Issues

  • Fixed an issue where a user gesture authorization token was not properly verified for Digital Credentials requests. (317228@main) (174908839)
  • Fixed DOMMatrix and IntersectionObserver correctly enforcing absolute-length unit requirements when parsing values. (316532@main) (181453666)
  • Fixed an issue where Scroll To Text Fragment directives could run outside text/html and text/plain documents. (316976@main) (181763736)
  • Fixed an issue where replaceChildren() could not replace a document’s children. (316909@main) (181921026)

Web Audio

Resolved Issues

  • Fixed Web Audio PannerNode orientation-only changes not updating the directional cone gain. (316541@main) (181413407)

Web Inspector

New Features

  • Added support for the range mappings proposal in source maps. (316748@main) (178564308)
  • Added per-element Layout Invalidated events in Web Inspector to reflect the number of elements affected by a layout invalidation. (317302@main) (181243589)

Resolved Issues

  • Fixed an issue where every <input> element showed a Scroll badge in the Elements tab. (316773@main) (101661656)
  • Fixed an issue where search results did not select the corresponding source code line for fetched JSON. (317499@main) (118115749)
  • Fixed an issue where search in Web Inspector did not find results inside the shadow DOM. (317500@main) (126458322)
  • Fixed an issue where Web Inspector could crash when selecting a popover matching a nested @scope with a bare declaration. (316987@main) (181745000)
  • Fixed an issue where Web Inspector mapped the crimson named color to the wrong RGB value. (317047@main) (182090064)
  • Fixed an issue where the gradient editor in Web Inspector dropped explicit 0% color stops and misparsed leading radial color stops. (317192@main) (182172439)
  • Fixed an issue where autocompleting a vendor-prefixed value in Styles could duplicate the prefix, for example producing -apple--apple-system. (317167@main) (182178871)

WebAssembly

Resolved Issues

  • Fixed an issue where Error stack traces did not include names for modules instantiated with WebAssembly.instantiateStreaming. (316642@main) (181523735)

WebDriver

New Features

  • Added WebDriver support for the Digital Credentials API, including commands to simulate wallet payloads, indefinite waits, and user rejection. (316435@main) (168941907)

WebRTC

Resolved Issues

  • Fixed OverconstrainedError to inherit from DOMException and expose a code attribute per the Media Capture spec. (316578@main) (180728516)

July 29, 2026 09:46 PM

July 28, 2026

Igalia WebKit Team: WebKit Igalia Periodical #71

Igalia WebKit

Update on what happened in WebKit in the week from July 14 to July 27.

This two-week update includes plenty of changes to the Skia compositor, changes to multimedia support, three blog posts, and assorted improvements.

Cross-Port 🐱

The Web Inspector “Layout & Rendering” timeline now shows a Layout Invalidated event for every element that needs relayout, not just the layout root (with the old root-only event renamed to Layout Scheduled). This unveils why some layouts take much longer than others. No more guessing which of dozens of nodes is actually to blame!

The webkit://gpu page has gained a dark style, which will be used when the system settings indicate that dark mode is preferred by the user.

Multimedia 🎥

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

The experimental GstWebRTC backend was removed and libwebrtc usage was enabled in the main branch. We hope to enable WebRTC support by default in the 2.56 series, scheduled around March 2027.

MP4 edit lists support was enabled in the MSE backend, improving timestamp accuracy, specially when handling of B-frames.

Graphics 🖼️

Split the compositing walk in the Skia compositor into a damage pass and a paint pass, so the frame damage is known before the first draw. The damage pass walks the layer tree with a SkNoDrawCanvas in place of the real canvas, so every draw is discarded and only the damage is collected. Both passes run from a single paint() that applies animations and computes the transforms once, so the two see the same tree. Knowing the damage up front is what lets the compositor eventually paint only the parts of a frame that actually changed.

Wired up damage-driven compositing on the Skia compositor, so a frame re-composites only the region that actually changed instead of the whole surface, when the UseDamagingInformationForCompositing feature is enabled (not yet on by default). Each frame's damage is combined with what each swap-chain target still needs to redraw since it was last drawn into, and the clear and every draw are clipped to that region, which is a milestone towards no longer repainting untouched pixels every frame.

Made the root layer collect the frame damage itself in the Skia compositor, instead of having each layer report its own changes. Reporting leaves a gap whenever a layer is in no position to report, e.g. a destroyed one took its painted rectangle with it, so what it had drawn stayed on screen. The root now holds one rectangle per layer and compares it against what each frame's walk finds, so a layer that moved is repainted in both places, and a layer the walk never reaches is repainted where it used to be and dropped. Nothing has to notice anything for the pixels it left behind to be repainted, which is what makes it safe to restrict composition to the damaged region by default in future commits.

Limited every content draw to the target's repaint region in the Skia compositor, so a composited frame can redraw only the pixels that actually changed. Each content type restricts itself to the region's rectangles rather than clipping the canvas, since a multi-rectangle clip cannot be a hardware scissor and would make Skia build a mask and break batching. This is the groundwork for damage-driven compositing, which stays off by default behind the damage-tracking feature flag, as the compositor still passes no region and nothing is restricted yet.

Made each swap-chain target track its own damage since it was last current. Repainting only what changed is correct only when drawing into the target that holds the previous frame, but the swap chain hands back whichever target is free, and that one is a frame or more behind. Each frame's damage is now added to every target as it is recorded and cleared from a target when that target is presented, instead of being built as a side effect of reading it.

Taught the tile and image draws in the Skia compositor to split themselves by damage rectangle, so a frame only repaints the parts of a layer that actually changed. A new SkiaDamageRegion holds the frame's damage in device space and is built once per frame, and each draw is restricted to it: skipped when it touches no damage, split into one sub-draw per damage rectangle it overlaps, or drawn under a device-space clip when a rotated or skewed transform rules out working with rectangles. Nothing feeds a damage region in yet, so every draw still paints in full—this prepares for future patches enabling using damage information in the composition

Fixed missing repaints when compositor-applied layer state changes dynamically in the Coordinated Graphics backend. A layer recorded damage when its backing store re-rendered or a new contents buffer arrived, but the compositor also handles filters, masks, clip path changes, the contents rectangle, the contents tiling, the blend mode and contents visibility, and changing any of those alters the pixels it produces without dirtying a tile. Those setters now damage the whole layer, so a compositor that repaints only the damaged rectangles no longer leaves the previous frame's pixels on screen.

Community & Events 🤝

Nikolas Zimmermann has written a two-part blog series about the current the new Layer-Based SVG Engine (LBSE), with the first post covering the effort to reduce layer overhead using layers conditionally, and the second about how compositing is being implemented and the complications introduced due to paint ordering rules.

Loïc Le Page has published a blog post explaining how to use the new WPEPlatform API to implement a custom WPE integration. While presented example uses GLFW and EGL to show Web content on an X11 window, the concepts are useful for anyone looking into embedding WPE.

That’s all for this week!

By Igalia WebKit Team at July 28, 2026 01:04 AM

July 27, 2026

WebKit Features for Safari 26.6

Surfin’ Safari

Safari 26.6 is here. This release adds a small refinement to WebAssembly’s streaming compilation paths, and delivers eight bug fixes across CSS, service workers, networking, extensions, and WebRTC.

This release continues the ongoing focus of this entire year — polishing how features fit together, and resolving issues you might have run into as a web developer.

WebAssembly

WebKit for Safari 26.6 adds a compileOptions parameter to WebAssembly.compileStreaming() and WebAssembly.instantiateStreaming().

Safari 26.2 shipped support for Wasm JS String Builtins — a way for WebAssembly modules to import a set of standardized JavaScript string operations as builtins, which the engine can then optimize much more aggressively than a normal JS import. To turn them on, you pass an options object to WebAssembly.compile() or WebAssembly.instantiate().

The two streaming variants (compileStreaming() and instantiateStreaming()) didn’t yet accept that same options object. Now they do.

const module = await WebAssembly.compileStreaming(
  fetch("example.wasm"),
  { builtins: ["js-string"] }
);

Now you don’t have to fall back from streaming compilation to use JS String Builtins.

Bug fixes and more

In addition to the WebAssembly update, WebKit for Safari 26.6 includes eight bug fixes:

CSS

  • Fixed an issue where the ic length unit scaled incorrectly with page zoom, causing it to no longer equal 1em as expected by the CSS specification. (174857144)
  • Fixed an issue where fixed-positioned elements using position-area did not fall back properly when the body was scrollable. (175544079)
  • Fixed an issue where CSS zoom interacted incorrectly with font-size, font-weight, font-variant, and font-style on iPad when requesting the desktop website. (176647969)

Networking

  • Fixed an issue where partitioned cookies could not be deleted using WKHTTPCookieStore. (176097960)

Service Workers

  • Fixed an issue where service worker registrations with missing main scripts were not automatically unregistered, preventing pages from re-registering new service workers. (175522651)
  • Fixed an issue where service worker registrations with missing imported scripts were not automatically unregistered. (175522816)

Web Extensions

  • Fixed an issue where web extension service worker registration database files accumulated on each Safari launch, causing performance degradation. (175810627)

WebRTC

  • Fixed an issue where RTCPeerConnection configured with iceTransportPolicy: "relay" failed to gather any ICE candidates on macOS Sequoia. (175009190)

Updating to Safari 26.6

Safari 26.6 is available on iOS 26.6, iPadOS 26.6, visionOS 26.6, macOS Tahoe 26.6, macOS Sequoia, and macOS Sonoma. On iOS, iPadOS, and visionOS, you can update to Safari 26.6 as part of the OS update in Settings > General > Software Update. On macOS, Safari updates are delivered through System Settings > General > Software Update.

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.

July 27, 2026 06:00 PM

July 22, 2026

Release Notes for Safari Technology Preview 248

Surfin’ Safari

Safari Technology Preview Release 248 is now available for download for macOS Golden Gate and macOS Tahoe. 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: 315567@main…316817@main.

Accessibility

Resolved Issues

  • Fixed VoiceOver on Safari unable to navigate to content revealed by disclosure widgets using hidden="until-found". (315964@main) (173228707)
  • Fixed stale aria-labelledby when the referenced element dynamically changes its aria-label. (316316@main) (180319221)

CSS

New Features

  • Added support for forwarding missing color components when interpolating between analogous color spaces. (315569@main) (180239320)
  • Added support for the no-clamp option on the CSS progress() function. (315977@main) (180473472)

Resolved Issues

  • Fixed serialization of various CSS at-rules not escaping identifiers. (315795@main) (178750383)
  • Fixed :last-child and related selectors incorrectly gating on parser state outside of style resolution. (316495@main) (178879939)
  • Fixed changing the color-scheme of an <iframe> not invalidating the appearance of the embedded document. (316467@main) (179177141)
  • Fixed CSS scroll snap re-snap to prefer the fragment-targeted (:target) snap area over other aligned snap targets. (315880@main) (180108825)
  • Fixed the CSS preload scanner failing to preload @import rules that follow an @layer statement rule. (315576@main) (180170656)
  • Fixed serialization of CSSViewTransitionRule. (315614@main) (180170814)
  • Fixed MediaList.deleteMedium() to parse its argument as a media query and remove all matching queries. (315579@main) (180270019)
  • Fixed MediaList.appendMedium() to parse its argument as a single media query and suppress duplicates. (315594@main) (180291283)
  • Fixed grid items with stretch or fit-content preferred sizes computing incorrect minimum-content contributions when sizing tracks. (316296@main) (180748205)
  • Fixed inserting a CSS rule while a view transition is active causing the group animation to snap to its final state. (316436@main) (181100818)
  • Fixed CSS var() to only resolve its fallback when the first argument resolves to the guaranteed-invalid value. (316280@main) (181114298)
  • Fixed navigating away from a render-blocked document before its first rendering opportunity incorrectly firing pagereveal and starting an outbound cross-document view transition. (316446@main) (181191512)

Editing

Resolved Issues

  • Fixed drag images of DOM elements with CSS transforms not rendering correctly. (316077@main) (99614217)
  • Fixed opaque DOM mutations coming from dictation on iOS. (316049@main) (163454428)
  • Fixed deletion in an editable table leaving an empty trailing table row behind. (315990@main) (180877315)
  • Fixed vertical caret movement in editable content ignoring the requested editable-type parameter. (316241@main) (181000174)

Forms

Resolved Issues

  • Fixed the concentric inner-button corner radius on horizontal text form controls incorrectly ignoring the bottom inset. (316022@main) (180869927)

Images

Resolved Issues

  • Fixed a regression where RGB gain map images were decoded to 8 bits per channel, causing a color shift and incorrect brightness. (316069@main) (179152566)
  • Fixed handling of the gain-map target pixel format when decoding HDR images to fall back safely when the format cannot be parsed. (316405@main) (181180757)

JavaScript

New Features

  • Added support for the TC39 BigInt Math proposal, exposing Math-equivalent methods on BigInt such as BigInt.pow and BigInt.sqrt. (315974@main) (152472996)

Media

Resolved Issues

  • Fixed <audio> and <video> controls rendering incorrectly when rotated via CSS transform. (315718@main) (37516619)
  • Fixed subtitles and closed captions not appearing in fullscreen video on iPhone. (315789@main) (175298523)
  • Fixed ArrayBuffer-backed YUV VideoFrame with a visibleRect rendering with offset chroma channels. (315742@main) (180202939)
  • Fixed video playback of streams from certain sources such as security cameras not working. (315774@main) (180411019)
  • Fixed transient device rotation resulting in captured video frames having the wrong orientation. (315821@main) (180429147)
  • Fixed Media Source Extensions playback and seek by loosening the gap tolerance between buffered ranges. (316146@main) (180439090)

Navigation

Resolved Issues

  • Fixed a <meta http-equiv="refresh"> to a URL differing only in fragment identifier being incorrectly treated as a page reload. (316001@main) (176933795)

Networking

Resolved Issues

  • Fixed URL path separators being encoded as %2F following a percent-encoded Armenian path segment. (315627@main) (180067095)

Rendering

Resolved Issues

  • Fixed the background of a composited <html> element not being repainted when the <body> background changed. (316415@main) (177975964)
  • Fixed a regression where block-axis padding on a flex column container with overflow: auto was excluded from scrollHeight. (315813@main) (179376053)
  • Fixed an issue where an <img> embedding an SVG with a near-integral intrinsic width rendered one device pixel narrower than expected. (315807@main) (180490343)
  • Fixed elements with filter: drop-shadow() not being fully repainted when a child is resized. (316450@main) (181284741)

SVG

Resolved Issues

  • Fixed SVG SMIL length animations to reject invalid to, from, and by values such as those with leading whitespace. (315949@main) (118537155)
  • Fixed Unicode text with complex scripts not rendering correctly along a curved <textPath>. (316144@main) (120284006)
  • Fixed SVGLength.convertToSpecifiedUnits() failing when converting from px to %, em, or ex. (315953@main) (172056830)
  • Fixed SVG geometry presentation attributes like cx, cy, r, rx, ry, x, y, width, and height being incorrectly applied to elements such as <g> on which they are not permitted. (315946@main) (175672111)
  • Fixed an issue where the per-character rotate attribute was discarded on a <textPath>, so it now composes with the path tangent angle. (315786@main) (178044478)
  • Fixed several SVG styling spec-compliance failures. (316174@main) (181052042)
  • Fixed dynamic changes to orient and markerUnits on <marker> not repainting elements that reference it. (316350@main) (181106538)
  • Fixed SVG SMIL number, integer-optional-integer, number-optional-number, and path animations to not apply when their from, to, or by values fail to parse. (316468@main) (181308150)

Scrolling

Resolved Issues

  • Fixed CSS scroll snap points inside zero-sized elements not working correctly. (315948@main) (172863699)
  • Fixed CSS scroll snap re-snap to prefer a snap area that contains the focused or fragment-targeted element. (315927@main) (180707984)

Security

Resolved Issues

  • Fixed a regression where some websites failed to display and logged Content Security Policy errors in the console. (316290@main) (179684592)
  • Fixed same-page navigations being incorrectly checked against Content Security Policy. (315759@main) (180342503)
  • Fixed Content Security Policy frame-ancestors violations in report-only policies being ignored instead of reported. (315753@main) (180447621)
  • Fixed Content Security Policy parsing to reject trailing characters after the closing quote on nonce-source and hash-source values. (316008@main) (180903857)
  • Fixed Content Security Policy trusted-types expressions to reject trailing characters after keywords and the wildcard. (316089@main) (180973793)

Storage

Resolved Issues

  • Fixed an issue where IndexedDB transactions could be blocked for an extended period before starting when another page’s transaction was suspended in the background. (315609@main) (178769599)

Web API

Resolved Issues

  • Fixed the Async Clipboard API to request paste access asynchronously. (315997@main) (75969974)
  • Fixed Digital Credentials to surface OperationError for platform-cancellation and unknown errors instead of AbortError or UnknownError. (315973@main) (174308268)
  • Fixed Digital Credentials rejecting with the wrong error code and synchronously; rejections are now queued as a task with the correct error. (315895@main) (174895437)
  • Fixed KeyboardEvent.getModifierState("AltGraph") and MouseEvent.getModifierState("AltGraph") always returning false. (315804@main) (180597374)
  • Fixed Credential.type returning "digital-credential" instead of "digital" for digital credentials. (315891@main) (180618646)
  • Fixed aborting navigator.credentials.get() leaving the digital-credentials document picker stuck on screen. (316494@main) (180812397)
  • Fixed FileReader.readAsText() ignoring the charset parameter of the Blob‘s MIME type. (315996@main) (180890703)

Web Inspector

Resolved Issues

  • Fixed showing ES2022 class private fields, methods, and accessors when inspecting object instances in the Console. (316171@main) (88527162)
  • Fixed symbolic breakpoints in the debugger so they work with intrinsic functions. (315713@main) (99037335)
  • Fixed a JavaScript breakpoint on a line containing only a semicolon not being triggered. (316519@main) (126707973)
  • Fixed the Console REPL to allow redefinition of variables declared with let and const. (316523@main) (143140659)
  • Fixed the Timeline exporting and importing the wrong timestamp for performance.mark() records. (316073@main) (145226764)
  • Fixed local response overrides mapped to a file being interpreted as Latin-1 (ISO-8859-1) instead of their actual encoding. (316074@main) (149847746)
  • Fixed the Media Logging setting not persisting across page loads. (315758@main) (154766890)
  • Fixed symbolic breakpoints to work with native constructors such as Array, Date, EventTarget, and Worker. (316262@main) (157178256)
  • Fixed missing stack traces for MIME type errors when importing modules. (316529@main) (169396940)
  • Fixed the Accessibility sidebar being empty for nodes inside cross-origin iframes. (316399@main) (178562336)
  • Fixed inline style invalidation to batch DOM.getAttributes commands per tick in cross-origin iframes instead of issuing one command per node. (316487@main) (178830496)
  • Fixed DOM Storage read and write commands to resolve against the frame’s own origin in cross-origin iframes. (316501@main) (179249711)
  • Fixed a moved breakpoint reverting to its original location after closing and reopening Web Inspector. (315585@main) (180083858)
  • Fixed showing the formatted parameters string for prototype objects such as Map.prototype. (315599@main) (180298712)
  • Fixed missing formatted parameter strings for object shorthand methods and arrow functions. (315723@main) (180466459)
  • Fixed an unnecessary colon appearing in front of non-class function properties. (315731@main) (180476445)
  • Fixed a self-canceling ternary that produced an incorrect cross-axis direction in the flex overlay. (316429@main) (181198803)
  • Fixed the color picker force-converting picked colors to Display P3. (316419@main) (181201503)
  • Fixed Page.searchInResources silently omitting cache-backed resources from search results. (316421@main) (181202027)
  • Fixed duplicate invalid CSS declarations both incorrectly displaying as Active in the Styles sidebar. (316418@main) (181203080)
  • Fixed adopted constructable stylesheets being misclassified as User Agent stylesheets in cross-origin iframes. (316433@main) (181204768)
  • Fixed an unsigned underflow that caused the DOM agent to spuriously report power-efficient playback. (316443@main) (181205602)
  • Fixed Network.setExtraHTTPHeaders to replace previously set headers instead of accumulating them. (316444@main) (181282814)

WebDriver

New Features

  • Added WebDriver support for the Digital Credentials API, including commands to simulate wallet payloads, indefinite waits, and user rejection. (316435@main) (168941907)

WebRTC

Resolved Issues

  • Fixed the WebProcess AudioSession to remain active while microphone capture is live. (316394@main) (180505014)
  • Fixed the configurationchange event being dropped when a source-side change occurred while a MediaStreamTrack was muted; the event is now deferred until unmute. (316301@main) (180728609)

July 22, 2026 06:58 PM

Nikolas Zimmermann: Implementing compositing in LBSE

Igalia WebKit

Keeping paint order correct with paint order segments

July 22, 2026 12:00 AM

July 14, 2026

Nikolas Zimmermann: Reducing layer overhead in LBSE

Igalia WebKit

Conditional layer creation in the layer based SVG engine

July 14, 2026 12:00 AM

July 13, 2026

Igalia WebKit Team: WebKit Igalia Periodical #70

Igalia WebKit

Update on what happened in WebKit in the week from June 30 to July 13.

The summer continues with many updates to the new SVG engine (LBSE), improvements to the new Skia-based compositor, some small API additions, and ever-important stable releases with security fixes.

Cross-Port 🐱

Enabled the CloseWatcher API and dialog's closedby attribute in stable.

New API has been added which allows specifying per-navigation User-Agent string values using webkit_policy_decision_use_with_policies(). Applications now have more granularity to decide which User-Agent websites are presented with, complementing the existing global WebKitSettings:user-agent setting.

Graphics 🖼️

Roughly halved the cost of the Skia based compositor on WPE running on Vivante GPUs with the Etnaviv driver, by turning off Skia's mipmap sharpening option. That option is enabled by default and makes the Skia shader generator append a small negative level-of-detail (LOD) bias to every mipmap-capable texture sample. WPE does not use mipmapping at all, so the bias sharpened nothing, but it still turned each texture fetch into a LOD lookup, which is a slow path on the tiled GPUs found in the i.MX series. Disabling it restores usage of faster, plain fetch operations.

Fixed broken rendering with the Skia compositor on WPE when super-tiled textures are enabled on Vivante GPUs. Those tile buffers are allocated padded up to a multiple of 64 pixels, so the physical texture is larger than the logical tile, but the Skia backing failed to take this difference into account, leading to distorted tile images being rendered.

Stopped the Skia compositor from blending opaque layers on WPE. Every layer was drawn with the default source-over blend mode, which leaves GPU blending switched on even for fully opaque layers that do not need it, so the cost was paid on every composited frame.

Layers that are opaque, drawn at full opacity and using the default blend mode are now composited with a plain source blend mode instead, which lets Skia turn blending off and lowers GPU bandwidth usage, benefiting tiled GPUs the most.

Cached the concatenated SVG transform attribute matrix on graphics elements in the Layer-Based SVG Engine (LBSE).

Reading the transform attribute walked the whole transform list and multiplied every item together again, and that happened around three times per animation frame for each element, even though the result only changes when the transform list itself is mutated.

The concatenated matrix is now stored on the element and invalidated whenever a transform-related attribute changes, so the multiplication runs once per mutation instead of once per read. This cuts repeated matrix work out of the per-frame path for animated SVG content.

Moved the clip out of the SVG child-paint loop in the Layer-Based SVG Engine (LBSE).

Painting a container used to set up a clip rectangle for every child shape in turn, so each shape did its own graphics-context save, clip and restore even though the clip rectangle was identical for all of them. When there is a single region to clip to and no child paints into its own layer, that clip is now established once and shared by every child, transformed or not.

This removes a per-shape save and clip from the hot painting path of SVG documents with many children.

Cached the SVG transform origin on SVG renderers in the Layer-Based SVG Engine (LBSE).

Every transform flush recomputed the origin for each non-layered SVG shape, even though it only depends on the transform-origin style and the transform reference box, and sampling MotionMark's Suits test at fixed complexity showed that computation taking around 1% of the WebProcess main thread.

The origin is now cached and keyed on the reference box, with a style change to transform-origin or transform-box dropping the cache, and the fast path is limited to plain SVG transforms so viewport containers and CSS-transformed renderers keep computing it directly. This removes a repeated per-shape cost from animated SVG content, and the caching scope can be widened later.

Cached the SVG viewport size used to resolve the transform reference box in the Layer-Based SVG Engine (LBSE).

The default transform-box for SVG is view-box, so every transformed shape resolved the viewport from the SVG root's content box again on each query, both when updating its local transform and again during paint. The viewport is constant after layout, so it is now cached on the <svg> element and only recomputed when layout actually changes it, on resize, zoom or a viewBox update. This removes another repeated per-frame computation from the transform path for animated SVG content.

Coalesced the SVG transform flush into one minimal repaint per container in the Layer-Based SVG Engine (LBSE).

Once per rendering update WebKit processes every SVG renderer whose transform changed, whether from script or an animation, and that repaint pass was the dominant per-frame cost on MotionMark's Suits subtest. Instead of walking each moved renderer up to its repaint container, the flush now computes each child's rectangle in its parent's coordinate space, unions the children per parent, maps that single union up the chain once, and issues one repaintUsingContainer() call per repaint container rather than one per shape.

This also stops requesting outline bounds, which for SVG merely duplicated the visual overflow rectangle, and refreshes the bounding-box and visual-overflow caches that a layout would normally update, so getBBox() and paint or hit-test culling never read a stale rectangle. This collapses many backing-store invalidations into one while keeping the repainted region minimal, closing the performance gap to the legacy SVG engine.

Avoided re-resolving the SVG transform from style on every paint in the Layer-Based SVG Engine (LBSE).

Non-layer SVG renderers already cache their transform in m_localTransform, but the painting code path used to recompute it from scratch each time, concatenating the transform list, applying transform-origin and multiplying matrices, only because the cached value uses a different transform origin. The paint transform is now derived directly from the cached one by translating around the nominal origin, which removes that per-paint recomputation and cuts the cost of painting transformed SVG content.

Fixed a repaint bug in the Layer-Based SVG Engine (LBSE) where dynamically changing a marker's markerUnits or orient attribute left stale pixels behind. Such a change resizes every shape that references the marker, but a referencing shape without a layer gets no post-layout position update, so only its new bounds were repainted—a shrinking marker left its former area on screen.

The visual overflow rectangle, markers included, is now cached at the end of shape layout while the geometry is still current, so a marker change can repaint the old bounds before recomputing the new ones. The extra repaint is limited to markers, since gradients and patterns do not affect a client's bounds, and the resulting repaint rects are more accurate than the legacy SVG engine's.

WPE WebKit 📟

Added a new feature flag, BackForwardCacheWithMedia, which may be used to disable storing pages with media content in the back-forward cache. This should solve the problem with hardware decoders kept occupied on low-end devices in case of caching pages with media after navigation.

Releases 📦️

WebKitGTK 2.52.5 and WPE WebKit 2.52.5 have been released, including a number of fixes for security issues, and therefore it is recommended to update. An accompanying security advisory will be published in the coming days. Additionally, these releases include small improvements and Web compatibility improvements.

That’s all for this week!

By Igalia WebKit Team at July 13, 2026 10:59 PM

July 01, 2026

Introducing the Safari MCP server for web developers

Surfin’ Safari

Update: In Safari 27 beta and Safari Technology Preview 247, we’re introducing the Safari MCP server — a Model Context Protocol server for web developers that makes your web development and debugging workflow faster and more powerful. We know agents are increasingly integral to the coding process and the Safari MCP server gives your agent the ability to know how your code actually renders in the browser by connecting it to a Safari browser window.

Any MCP-compatible client can connect to the Safari MCP server. By connecting your agent to a Safari browser window, your agent can emulate what your users experience, giving it the information it needs to debug more autonomously, like access to the DOM, network requests, screenshots, and console output.

It speeds up your debugging process and lets you stay in the comfort of your terminal, which means fewer rounds of hopping windows and typing prompts to debug your code.

The use cases

If you build for the web, then you know about the debugging dance. It usually goes something like this:

You see something wrong with your site in the browser. You open the console to hunt it down. You click into the styles tab. You see what’s broken. You go back to your code to fix it. Or maybe you take a screenshot, detail the problem to your agent, and let it do the fixing for you. Hopefully it gets it right, the bug is fixed, and you can move on.

But when it isn’t fixed, you go through the workflow again — Browser. Prompt. Agent.

And again and again, until you finally squash the bug.

Regardless of the browser or tools you use, the debugging workflow is a lot of clicks, tools, and window hopping to make a single fix, but it doesn’t have to be that way. If you’re already using agents in your development workflow, the Safari MCP server makes your debugging faster and more efficient.

The Safari MCP server enables your agent to do more debugging and troubleshooting on its own. Here are just a few examples of what it can help with:

Web development in Safari. The next time you develop in Safari, you’ll benefit from an upgraded workflow. Your agent already helps you with your code, now it can do even more by checking out how your code actually renders in Safari.

Improve compatibility with Safari. Testing in just one browser means missing potential bugs in another, giving those users a subpar experience. With the Safari MCP server, your agent can open your site in Safari, inspect computed styles, check layout, and compare it against what you expect without switching windows.

Analyze performance. See what parts of your site are slowing things down. The Safari MCP server lets your agent evaluate JavaScript on the page to surface performance metrics, like navigation timing and resource load times, so it can pinpoint what’s slowing your site down and work on the right fix.

Check for accessibility. The Safari MCP server lets your agent check for common accessibility issues like missing labels, improper ARIA attributes, and poor contrast, so you can catch problems that impact your users.

Verify any user state. Know that the page is working and looking as it should. Your agent can check the state of the form, query an element using a selector, confirm specific interactions, show different states of a checkout flow, and more. Spend less time on these manual checks and empower the agent to do it for you.

These are just a few of the use cases. However you decide to implement it, the Safari MCP server helps your agent do more for you and reduce all the back and forth that web development often requires. An easier workflow means more bugs squashed, happier users, and a better product.

The tools

Here are the available tools and what they do:

Tool Description
browser_console_messages Return buffered console logs for the current or specified tab
browser_dialogs List and respond to browser dialogs (accept, dismiss, or input text for JS prompts)
close_tab Close a browser tab by its handle
create_tab Create a new browser tab, optionally loading a URL
evaluate_javascript Execute JavaScript code within the page and return the result
get_network_request Get full detail for a single recorded network request (headers, body, timing)
get_page_content Extract text content of a page in various formats (markdown, HTML, JSON, etc.)
list_network_requests List network request summaries (URL, method, status, timing) for the current tab
list_tabs List all open browser tabs with their handles and URLs
navigate_to_url Navigate to a URL and return the loaded page’s content
page_info Get info about the current page: URL, title, and loading state
page_interactions Perform DOM interactions in sequence: click, type, scroll, hover, keyPress, etc.
screenshot Capture a screenshot of the current page as a PNG
set_emulated_media Emulate a CSS media type (e.g. “print”) for responsive-design testing
set_viewport_size Set the browser viewport size in CSS pixels
switch_tab Switch to a different browser tab by its handle
wait_for_navigation Wait for the current page to finish loading; returns final URL and title

With the Safari MCP server, you no longer have to write the perfect prompt, carefully describing to your agent what you’re experiencing in the browser. You can give your agent the ability to find out for itself.

How to get started

Safari 27 beta

First, you’ll need to install Safari 27 beta. Once installed, make sure to enable web developer features and remote automation. To enable features for web developers choose Safari > Settings > Advanced > check the Show features for web developers checkbox. Then go to Safari > Settings > Developer > check “Allow remote automation and external agents.”

If you’re using Claude, you can use the following command in your terminal:

claude mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp

If you’re using Codex, you can use the following command in your terminal:

codex mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp

For other agents, you can put the following in your mcp.json or config.json file.

{
  "mcpServers": {
    "safari-mcp": {
      "command": "/usr/bin/safaridriver",
      "args": ["--mcp"]
    }
  }
}

Safari Technology Preview

First, you’ll need to install Safari Technology Preview. Once installed, make sure you enable Safari Settings > Advanced > Show features for web developers. Then go to Safari Settings > Developer > Enable remote automation and external agents.

If you’re using Claude, you can use the following command in your terminal:

claude mcp add safari-mcp-stp -- "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --mcp

If you’re using Codex, you can use the following command in your terminal:

codex mcp add safari-mcp-stp -- "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --mcp

For other agents, you can put the following in your mcp.json or config.json :

"safari-mcp-stp": {
  "command": "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver",
  "args": ["--mcp"] 
}

Note that while the above code names the server safari-mcp-stp, you can name it whatever you like, even just safari. Once you have the Safari MCP server installed, try one of the following prompts:

Find bugs on my site in Safari
How accessible is my site in Safari?
See how my website performs in Safari

While each agent works a little differently, it shouldn’t need to be told to use the Safari MCP server explicitly — it’ll figure it out on its own. Simple prompts like the ones above are enough to kickstart the MCP.

Here’s how a conversation with an agent using the Safari MCP server might go:

You: Got a bug report — something’s broken in Safari on the flight page. Can you figure out what’s going on?
Agent: Sure, let me take a look.
Agent: I found two distinct bugs on the flight page in Safari. Want me to fix them both?
You: Yes. Anything else here that might be causing problems for Safari users?
Agent: Yes, two more things worth flagging. The results reveal animation is silently broken and the API server startup log is wrong. Should I tackle both as well?

 

All you need is an initial request to get started, and with the help of the Safari MCP server, your agent can take it from there.

The Safari MCP server runs entirely on your local machine and makes no network calls of its own. It also does not have access to your personal information in Safari (e.g. AutoFill or other browser activity). When it captures page content, screenshots, or console logs, that data goes directly to the agent you’re running — not to Apple. What happens to that data from there depends on the agent and model you’re using. As with any agent you give access to your browser, only use ones you trust.

Why we built this

There are many ways to build for the web, both with and without AI. If AI is a part of your workflow, we think this tool will help make it even more productive. And if it isn’t, that’s OK too.

By creating this resource, we hope to make it easier than ever to test and debug in Safari by helping your agent understand how things look and work in the browser.

If you end up giving it a try or if this is your first time using an MCP server, let us know what you think.

Find us online: Saron Yitbarek on Bluesky, Jen Simmons on Bluesky / Mastodon, and Jon Davis on Bluesky / Mastodon. If you run into any issues, file a WebKit bug report. Filing issues really does make a difference.

July 01, 2026 09:28 PM

June 29, 2026

Igalia WebKit Team: WebKit Igalia Periodical #69

Igalia WebKit

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

After a small break after the Web Engines Hackgest, we're back with another round of updates, this time with a couple of exciting improvements to the SVG engine, a WebRTC fix, and support for WebP images with the toDataURL() API.

Cross-Port 🐱

Made RenderLayer creation conditional for SVG renderers in the new Layer-Based SVG Engine (LBSE), so a layer is now only created when one is actually needed for intrinsic reasons (3D transforms, opacity, etc.) instead of unconditionally for every renderer. Plain 2D transforms no longer force a layer and are applied directly during painting. This is the groundwork for follow-up patches that remove the intrinsic need for layers when applying clipping, masking and filters to SVG subtrees. It is an important milestone towards reducing the overhead that has been holding back LBSE performance compared to the legacy SVG engine.

Fixed the paint order of non-composited children around composited SVG siblings in the Layer-Based SVG Engine (LBSE). A layered container paints its children from a single flat list in DOM (and SVG paint) order, but some children are composited into their own GraphicsLayer for reasons like will-change, a 3D transform or certain opacity cases. The flat child list is now split into contiguous paint-order segments at those boundaries, with each run of plain children painted by its own overlay layer placed at the correct depth in the compositor's child list. This keeps every child in its DOM order without giving trailing siblings a RenderLayer or backing store of their own, and a container with no composited children produces no segments at all, so the common case costs nothing. This allows us to support composition within LBSE subtrees in a performant way, after dropping the requirement that every renderer creates a layer.

Multimedia 🎥

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

Fixed initial decoding issues on LibWebRTC on platforms that do video decoding on the final playback stage (for efficiency and performance), instead of on the LibWebRTC decoder component.

Graphics 🖼️

Added support for producing WebP images with canvas' .toDataURL(). Using 1.0 as the quality setting will produce lossless images, which matches the behaviour of Chromium and Firefox.

That’s all for this week!

By Igalia WebKit Team at June 29, 2026 09:00 PM

June 16, 2026

Igalia WebKit Team: WebKit Igalia Periodical #68

Igalia WebKit

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

The major highlight this week is the Web Engines Hackfest! Despite it, there are a variety of updates as well, such as various improvements to input handling in WPE WebKit and WebKitGTK, WPE menu rendering changes, and a plethora of other smaller improvements.

Cross-Port 🐱

Input methods may now know whether a field is intended to be used as search input, in which case the WebKitInputMethodContext:input-purpose property will have the value WEBKIT_INPUT_PURPOSE_SEARCH.

Due to GTK not providing an equivalent value for GtkInputPurpose, the default behaviour is to continue mapping search fields to GTK_INPUT_PURPOSE_FREE_FORM as before; but custom input methods may use the new value to detect search inputs. When using WPEPlatform, the value is mapped to WPE_INPUT_PURPOSE_SEARCH, which has been added as well.

Handle selections as part of moveBefore.

Corrected user activation propagation for close watchers.

Invalidate :lang() and :dir() selectors after moveBefore.

Enable Close Watchers in preview.

WPE WebKit 📟

WPE now renders its own popup menus for elements such as select. It supports all styling options the web provides such as colors and fonts. The internal menu can be overriden with the existing WebView::show-option-menu signal. Cog for example still renders its own (with a recent commit).

A colorful context menu

A context menu with a simpler style

A context menu inside an iFrame

A context menu inside a rotated container

Community & Events 🤝

The Web Engines Hackfest started! We had a fantastic first day of talks, and now are heading to breakout sessions. Make sure to check the schedule for sessions that may interest you!

That’s all for this week!

By Igalia WebKit Team at June 16, 2026 10:14 AM

June 08, 2026

Igalia WebKit Team: WebKit Igalia Periodical #67

Igalia WebKit

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

Another great week, this time we have a performance improvement implemented in the Skia-based compositor, an excellent writeup about how to investigate and isolate memory leaks in WPE WebKit, a couple of multimedia fixes, and a variety of improvements and fixes across WebKit ports.

Cross-Port 🐱

Implement dialog integration with close watcher.

Implement node iterator and live range pre-remove steps for in-progress moveBefore() implementation.

Fix an early return in CloseWatcher close to align with the spec.

The Web Inspector now shows DOM nodes associated with layout and rendering events in a separate column of layout timeline next to initiator, sizing, and timing information. Hovering over rows in the details table highlights the associated node, and clicking it reveals the node in the "Elements" tab. This makes it easier to match events with specific nodes and helps debugging changes to a web page.

Fix popover light dismiss to account for disabled command buttons.

Multimedia 🎥

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

Fix mediaTime provided with requestVideoFrameCallback in case of captureCanvas as source.

Graphics 🖼️

Batched painting support was implemented in the Skia-based compositor, improving the performance in several cases.

Community & Events 🤝

Pawel Lampe published a blog post where he's presenting and discussing a guide on structured approach to narrowing down and debugging memory leaks within WPE WebKit.

That’s all for this week!

By Igalia WebKit Team at June 08, 2026 08:57 PM

June 02, 2026

Igalia WebKit Team: WebKit Igalia Periodical #66

Igalia WebKit

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

The main feature of this week are new releases: stable ones with many security fixes, and development ones with the new Skia-based compositor enabled. Additionally, there was work on Web-facing features, optimizations, spell checking support for the WPE port, and more.

Cross-Port 🐱

WebKit now supports mirroring MathML stretchy operators using the OpenType rtlm feature.

Replaced the CloseWatcherManager's escapeKeyHandler, which will allow other types of close signals to be supported.

Implemented queuing mutation observer records in the work-in-progress moveBefore() implementation.

Implemented popover integration with close watcher.

Fixed popover light dismiss to account for popovertarget on input buttons.

Content filters now create temporary files in the compiled filters directory, which ensures that a file rename can always be used to place them at their final location. This avoids falling back to a regular file copy, which can be slower, when the temporary directory returned by g_get_tmp_dir() (typically /tmp) is in a different volume than the filters' storage path configured for WebKitUserContentFilterStore.

WPE WebKit 📟

Enabled spell checking support in WPE. The existing implementation for the WebKitGTK port, which uses the Enchant library as a backend, was generalized to provide spell checking support in WPE as well. The feature may be toggled at build time using the ENABLE_SPELLCHECK CMake option.

Releases 📦️

WebKitGTK 2.52.4 and WPE WebKit 2.52.4 have been released; they include a number of fixes for security issues, and it is a highly recommended update. The corresponding security advisory, WSA-2026-0003 (GTK, WPE is available as well. The release also includes a number of small improvements and Web compatibility fixes.

Additionally, development releases WebKitGTK 2.53.3 and WPE WebKit 2.53.3 are available since last week. These include a change to use a new Skia-based compositor by default, which is intended to replace TextureMapper once ready. Therefore, bug reports related to website rendering are particularly welcome when using this and subsequent development releases.

Infrastructure 🏗️

The deprecated and un-maintained Flatpak-based SDK was removed. Developers working on the WPE and GTK WebKit ports are encouraged to migrate to the new SDK.

That’s all for this week!

By Igalia WebKit Team at June 02, 2026 12:12 AM

Pawel Lampe: WPE memory leak investigation playbook

Igalia WebKit

Depending on the web application, the WPE WebKit memory usage trend can vary. When simple web applications are being processed, the memory consumption tends to be virtually stable (the same) no matter the period. However, when more complicated web applications are being executed, the memory usage usually grows over time while going back to normal from time to time e.g., when GC / memory pressure mechanism releases all kinds of caches and not-needed memory. Therefore, memory growth itself is not unusual. Nevertheless, as the memory leaks happen in WPE at times, the memory growth is worth investigating — especially if very rapid or unbounded.

This article presents a structured playbook for investigating such a memory growth and memory leaks in WPE. Rather than diving straight into debugging tools, it starts from first principles: confirming the problem is real, choosing the right environment to work in, and narrowing down the leaking area before any heavy tooling is involved. The goal is to reach actual debugging as fast as possible, regardless of whether the environment is an embedded device or a desktop machine, and regardless of how quickly the problem reproduces.

Playbook #

The high-level list of recommended steps to follow is presented below. In a nutshell, the steps 1, 2, and 3 are meant to choose and follow the fastest possible investigation path so that actual debugging of the problem (step 4) can be started as soon as possible.

  1. Confirming the problem
  2. Identifying the best setup for reproducing the problem
  3. Narrowing down
  4. Debugging

1. Confirming the problem #

The ultimate first step when working with alleged memory leak is to check whether the observed memory growth is actually abnormal. In the case of web browsers in general, the memory growth alone may not necessarily mean something is leaking. There may be many regular reasons why the browser’s memory usage is growing, but the usual suspects are:

  • JavaScript-level memory allocations — due to the very nature of JavaScript, the memory it allocates causes the overall web content process memory growth up until the garbage collector (GC) kicks in. Then (from the RSS perspective) some memory is usually freed. However, as it’s not easy to predict when the GC will be invoked (e.g., when the browser processes an application that performs heavy rendering), it’s possible that memory will grow but remain garbage-collectible.
  • JavaScript Just-in-Time (JIT) compilation — when not explicitly disabled or limited, the processing of any web application that has JavaScript code associated with it will cause the browser to continuously compile the JavaScript code in the background so that it executes such code faster in runtime at the expense of memory that is required for storing compiled artifacts.
  • Caches — as the WPE operates, it caches things such as web resources, style resolution artifacts, textures, glyph atlases, layer tiles, display lists, rasterization artifacts, and many others. Naturally, the cache sizes are limited, however, if many caches are growing at the same time, they may create an impression of a leak. The difference in that case is, the caches stop growing at some point.

Due to the above, to confirm the memory growth is abnormal, one should usually try the following first:

  1. Triggering memory pressure to force the browser to trigger GC and evict as many cache entries as possible,
  2. Rerunning the browser with JIT disabled to rule out the JIT-related memory growth — unless the application code is very small.

If the memory growth doesn’t stop with JIT disabled or its level does not go back to normal after triggering memory pressure, the growth can be assumed to be abnormal, and one can proceed to the next step.

2. Identifying the best setup for reproducing the problem #

When the memory growth is atypical, it needs to be narrowed down in a way that the final debugging is possible. For both narrowing down and the debugging, one should aim at the most flexible development environment along with the smallest possible web application that reproduces the problem quickly. What it means in practice is — desktop environment along with small demo web application that reproduces the problem. Whilst it’s not always possible to have such an environment, the 3 general rules are as follows:

  1. Desktop environment is usually better than embedded one in terms of working with memory leaks as it offers minimal overhead (e.g., in terms of compilation times) and huge flexibility in choosing the industry standard tools for profiling/debugging.
  2. Small web application is always better than a big one as long as it still reproduces the same problem in the same amount of time. In such case, a small application minimizes the amount of noise that usually stands in the way of profiling/debugging.
  3. A web application that reproduces the problem quickly is always better than the one that needs much more time for it. The worst thing that can happen in the case of narrowing down memory leaks, is when the memory growth is noticeable or starts after a very long time such as hours/days+.

Given the above, at this point one should go through the below steps:

  1. Check if the setup is trivial enough already — if the web application reproduces the problem quickly in a desktop environment and is simple enough, one should immediately jump to the Debugging section.
  2. Check if the problem can be reproduced on desktop assuming it originally reproduces on embedded.
  3. Check if the problem can be reproduced faster if it’s not reproducing fast enough.
  4. Check if the web application could be simplified.

Once the setup is simplified as much as possible, one should proceed to one of narrowing down sections depending on the setup. Also, if the setup is still not ideal, one should actively seek opportunities for simplifying the setup even during narrowing down as it’s likely that some new information will eventually open new possibilities in terms of simplifying setup.

3. Narrowing down #

When the problem has been confirmed but there are not enough clues to tell exactly which parts leak, the debugging cannot be started right away. In such case, it’s necessary to narrow down the problem to the browser/application area that can be easily debugged.

While in some cases narrowing down is not even necessary, quite often it takes orders of magnitude more time than actual debugging, and hence one should pay special attention to this step.

3a. Narrowing down on embedded when the problem takes a long time to reproduce #

This is the toughest situation one can find themselves in. When a problem takes a long time to reproduce (hours/days+), every iteration/test comes automatically with a significant cost. Moreover, when the environment is an embedded one, rebuilding WPE is usually more time-consuming and the amount of tooling is usually limited — or requires some work to bring it to the image at least.

Due to the above, narrowing down the problem in this setup requires a structured approach with extra care. In such case, the things to check should be approached in steps defined as follows:

  1. Things to check without rerunning the WebKit
    • in case of embedded devices, extra care is needed when attaching a memory profiler. On low-end devices, memory profilers tend to slow down the application hard enough to trigger otherwise non-existent problems.
  2. Things to check without rebuilding the WebKit
    • in case of embedded devices, one should prefer limiting JIT over disabling it as without it, the JS execution may be slow enough to trigger unexpected scenarios.
  3. Things to check if rebuilding WebKit

Ideally, while checking various things along the above steps, one should batch as many checks as possible within individual tests.

3b. Narrowing down on embedded when the problem reproduces quickly #

When the problem reproduces quickly, the limitations of embedded environment are not that relevant. In this scenario, one should prioritize getting debug symbols (RelWithDebInfo build) into the image and utilizing them by running the browser with whatever profilers are available. For the specific things to check, one should seek inspiration in the following groups:

  1. Things to check without rebuilding the WebKit.
  2. Things to check if rebuilding WebKit.

3c. Narrowing down on desktop when the problem takes a long time to reproduce #

This situation is similar to 3a and hence one should follow the things to check from the following groups:

  1. Things to check without rerunning the WebKit.
  2. Things to check without rebuilding the WebKit.
  3. Things to check if rebuilding WebKit.

However, this time, there are some extra opportunities around tooling:

  1. There should be many more tools available already in the system or available to be installed.
  2. Tools such as memory profilers that could slow down the application making it unusable on embedded, may turn out to be working well when the desktop-class processing power is available.

With the above in mind, it’s worth trying all the tools available with priority because if at least one tool works well, one can save hours of narrowing down.

3d. Narrowing down on desktop when the problem reproduces quickly #

This is technically the simplest possible scenario, so basically, all the possibilities are available. The most time-consuming activity in this case is very likely rebuilding WebKit itself — although it should still be relatively fast. In such case, just after a few quick checks with the Web Inspector, it’s recommended to get debug symbols (RelWithDebInfo build) and start with tools such as memory profilers.

Other than the above, one should go through the following groups on things to check:

  1. Things to check without rebuilding the WebKit.
  2. Things to check if rebuilding WebKit.

4. Debugging #

The WPE debugging is twofold and depends on whether the problem is within the engine (usually C/C++ code) or the web application (JavaScript code).

When problem lies in the engine #

Debugging WPE WebKit is the same as debugging any other C/C++ application on Linux (or Mac if the issue is cross-port and one prefers an Apple port to work with), and hence is outside the scope of this article. Some WebKit-specific information can be found in the WebKit Documentation article on building and debugging page and therefore is recommended as a first step.

When problem lies in web application #

When the problem lies in JavaScript code, the situation is usually fairly straightforward. The majority of bugs in this area should be reproducible across various browser engines and hence a full variety of tooling should be available. If the WebKit is preferred or if the problem reproduces only there, the tooling available is still very useful and helps debugging problems quickly. The ultimate tool in such case is the Web Inspector. On official WebKit’s web page there’s entire index of articles on Web Inspector. Among those, the most interesting read is about Timelines Tab where the most useful debugging can be done. Once the features of Timelines Tab are understood, the next important article is the memory debugging guide. It dives into the most important Timelines Tab subsections and showcases the work with heap snapshots which is a key. To supplement it, it’s very important to know the heap snapshot delta feature which is basically about button:

Web Inspector heap delta.

that allows one to inspect the delta-snapshot between 2 snapshots. It’s critical as it answers the question on what JS objects were added between the base snapshot and the later one. If some objects are piling up, it immediately shows which ones.

One important note on snapshots is that in some cases when using Web Inspector is not possible, one can generate the snapshots manually from the web engine’s C++ code by just calling GarbageCollectionController::singleton().dumpHeap(); at some appropriate moment. In this case, the dump will be written to standard output. It can be then turned into a file and imported from any Web Inspector using Import button.

As the Timelines Tab with its subsections should be able to answer on what happens, to understand why it actually happens, the last missing piece is the JS debugger within Web Inspector. It’s not very different to debuggers in other engines, but it’s worth checking a dedicated article on it just to understand the capabilities.

Appendix #

Things to check without rerunning the webkit #

Even if the WPE is running with default settings in release mode, there are plenty of useful things that can be checked while the browser is still running:

  1. Identifying which WebKit process allocates abnormally,
    • there are multiple ways to do this, but usually it’s as easy as using ps utility.
  2. Identifying how fast the process in question allocates the memory,
    • this is useful to know at least for comparison purposes, but it may hint some problems already if the numbers correlate with what web application does.
  3. Checking logs from stdout, stderr, and journal (using journalctl).
  4. Checking detailed process memory statistics.
  5. Triggering and checking the impact of memory pressure on given processes RSS,
    • in short, memory pressure triggers the cleanup of the majority of caches along with GC. Therefore, if this is able to bring memory back to normal level, then the problem is about caches, JS Heap / GC, or fragmentation.
  6. Attaching memory profilers if available,
    • even if the debug symbols are not present, this may be useful to see what data is being captured and how the web application behaves when slowed down by profiler.
  7. Attaching other tools if available,
    • even if the debug symbols are not present, various tools offer different perspectives on what the browser is doing. In some cases, such information may reveal some anomalies that may be related to the main issue.
  8. Cross-checking with other browsers,
    • if other browsers show a similar pattern of memory usage, it’s very likely the problem lies in web application itself. Otherwise, it strongly suggests a bug in the WPE.
  9. Cross-checking with other ports,
    • if any other WebKit port shows a similar pattern of memory usage, it allows one to narrow down the area in the code a bit based on what port it is:
      • if the same behavior is visible in any of Apple ports, the problem is most likely related to cross-platform code,
      • if the same behavior is visible only in GTK port, then the problem is most likely related to GLib-related part, coordinated graphics part, GStreamer-related part, or others that are shared.

Things to check without rebuilding the webkit #

  1. Tweaking and checking the logs from WPE,
    • while generic logs may hint some unusual behavior, more specific ones such as GC logs (JSC_logGC=1) may be used to check how the individual JS heap sizes evolve over time and how GC behaves. If it’s JavaScript leaking the memory, this log will quickly provide the evidence.
  2. Enabling Remote Web Inspector and checking:
    • both breakdown and trend of memory usage in the memory timeline after doing a bit of recording,
    • the effects of takeHeapSnapshot() invoked from JS console:
      • as this function usually triggers GC internally, it may be used to check how much RSS memory is reclaimed by GC in isolation (followed up by scavenger),
      • as this function takes a JS heap snapshot, it then can be used to explore manually if its contents point towards something interesting.
  3. Disabling JIT and checking the memory usage,
    • if the memory usage is stable with JIT disabled, one should proceed to the step below.
  4. Limiting JIT and checking the memory usage,
    • there are at least a few places (levels) where JIT compilation engine allocates memory. If limiting doesn’t resolve the issue completely, it’s likely the engine itself leaks some memory around temporary helper-heaps such as AssemblerData etc.
  5. Experimenting with environment variables and runtime preferences,
    • some environment variables and runtime preferences change the behavior of the web engine significantly. If changing one of them makes the problem go away, it usually helps to narrow down the problematic area quickly.
  6. Running WPE with system malloc (environment variable Malloc=1) and checking the memory usage,
    • when one suspects bmalloc/libpas issues with fragmentation or scavenger, it’s worth running a browser with system malloc to compare the memory evolution over time against the bmalloc/libpas.
  7. Limiting device memory and checking the memory usage,
    • if triggering memory pressure is not possible, an alternative solution is to limit the device memory so that the browser is under constant memory pressure.
  8. Running WPE with sysprof and checking:
    • stack traces — to see what parts of engine are particularly active as it may hint some problematic area,
    • WebKit marks — to see what the engine is doing as well as quantitative data in marks such as EventLoopRun etc. as in those cases the numeric value trends may reveal resource pile up.

Things to check if rebuilding webkit #

  1. Building WPE in release mode with debug symbols and re-trying memory profilers or other tools if the debug symbols were not present before,
    • if some desired tools such as heaptrack, valgrind, perf, or strace were not available before, it’s the right moment to get/build them as well,
    • once the debug symbols are in, one should try:
  2. Building and running with Google perftools,
    • as WPE allows switching to system malloc as an allocator, it’s possible to use custom malloc implementation with instrumentation such as gperftools. For that, the recommended read is this article from fellow Igalian, Pablo Saavedra.
  3. Building and running with sanitizers,
    • if the problem is about low-level leak, address/leak sanitizer should be able to help pointing out the problematic area.
  4. Building and running with memory sampler,
    • the data produced by memory sampler is roughly the same as inspector’s memory timeline, however, it’s much more convenient as it doesn’t need web inspector at all.
  5. Building and running with node statistics,
    • when memory growth seems to be related to DOM mutations, it’s worth enabling and reporting node statistics periodically — in some cases, it may directly suggest what the problem is about.
  6. Building and running with malloc heap breakdown,
    • when all other means fail, a very good last-resort approach for investigating memory usage statistics via a debug-only WebKit feature called Malloc Heap Breakdown. The details can be found in the dedicated article about it.
  7. Building and running with libpas statistics,
    • On very rare occasions such as memory fragmentation or allocation issues, it may be worth checking the libpas (low-level memory allocation and management library) statistics as WPE uses it by default on the vast majority of platforms.

Individual instructions #

Checking detailed process memory statistics #

As WPE WebKit uses multi-process architecture, there are multiple processes that can be checked, although the most interesting one is usually the Web Content Process. Once the PID of the given process is determined (e.g., using ps utility) the usual steps to check detailed memory statistics are:

  • cat /proc/<PID>/status or cat /proc/<PID>/statm for very basic statistics,
  • pmap -X <PID> - for detailed statistics (if available),
  • cat /proc/<PID>/smaps_rollup and cat /proc/<PID>/smaps for detailed statistics (requires CONFIG_PROC_PAGE_MONITOR kernel configuration option).

Triggering memory pressure from OS #

WPE uses a so-called Memory Pressure Monitor to observe the memory usage in the system and to react if there’s not much memory left. The default thresholds are specified in MemoryPressureMonitor.cpp and usually are 90% for non-critical and 95% for critical response. Depending on the response, WPE schedules GC and clears internal caches immediately.

As the above is usually on by default, one can leverage it to trigger GC (along with cache cleanups) by filling up the available memory in the OS to 95+%. There are many ways to allocate memory, yet the simplest is using stress:

  • e.g. stress --vm 1 --vm-bytes 1024M --vm-keep to allocate 1024 MB.

Attaching memory profilers #

When attaching any memory profiler, unless one wants to profile only native allocations (Skia, GStreamer, ICU, etc.), the key is to use Malloc=1 environment variable on WPE startup so that bmalloc uses system malloc instead of libpas. Also, if WebKit is using a sanboxed mode in given configuration, it’s usually necessary to use WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1 as well. Then the commands are as follows:

  • to attach heaptrack:
    • heaptrack -p <PID> so e.g. heaptrack -p $(pgrep WPEWebProcess) (see this article for details),
  • to run with valgrind’s massif (as attaching to running process is not possible):
    • valgrind --tool=massif --trace-children=yes <WPE-BROWSER-COMMAND> (see this article for details).

Attaching other tools #

If memory profilers are unusable or unavailable, it’s worth checking if other tools are present and experimenting a bit with them if so. In some cases, tools other than memory profilers may give some hints on further investigation or reveal a suspicious pattern within application execution. Some ideas for experiments with various tools are listed below:

  • strace:
    • strace -c -p $(pgrep WPEWebProcess)strace called with -c gives a nice summary of system calls executed by the traced application. It can be useful to check the overall syscall usage pattern to see if there are any anomalies.
    • strace -p $(pgrep WPEWebProcess) -e trace=mmap,munmap,mremap,madvise -ttstrace focused on mmap()-related system calls may be useful to debug libpas.
  • perf:
    • perf record -F 999 -ag -p $(pgrep WPEWebProcess) -- sleep 60 — regular recording with perf can be very useful, especially if symbols are available. With that, one can generate flamegraphs and investigate what’s going on in the browser. While it’s not about profiling memory, it may be helpful to narrow down at least a bit.
    • perf record -F 999 -e syscalls:sys_enter_mmap,syscalls:sys_enter_munmap,syscalls:sys_enter_mremap:sys_enter_madvise -ag -p $(pgrep WPEWebProcess) -- sleep 60perf focused on mmap()-related system calls is much more superior than e.g. strace as it also records stack traces. Therefore, if debug symbols are present, and if the memory growth is very rapid, it’s very likely the libpas mmap() stacktraces will lead to the growth origin statistically.
    • perf trace -e mmap,munmap,mremap,madvise -p $(pgrep WPEWebProcess) — this is very much similar to strace focused on mmap()-related system calls as it shows a live preview of what’s happening.
  • sysprof:
    • sysprof-cli -f — while running system-wide sysprof won’t make WPE push marks into it, the profiling trace may still be useful to some degree, especially if debug symbols are available.

Disabling JIT #

This can be done using an environment variable:

  • JSC_useJIT=false.

Limiting JIT #

Limiting JIT can be achieved via environment variables:

  • JSC_jitMemoryReservationSize=<BYTES> to limit JIT memory usage (the limit is semi-strict as some JIT compilation engine buffers are limited by this value indirectly),
  • JSC_useFTLJIT=false to disable FTL tier,
  • JSC_useDFGJIT=false to disable DFG and FTL tiers,
  • JSC_useBaselineJIT=false to disable Baseline, DFG, and FTL tiers.

Tweaking WPE logs #

WPE is a fairly complex piece of software and hence it offers various logging capabilities related to WebKit itself, as well as to related libraries. The vast majority of logging can be controlled via environment variables:

  • WEBKIT_DEBUG=all to enable all logging channels,
  • WEBKIT_DEBUG=Layout,Media=debug,Events=debug to enable selected logging channels,
  • JSC_logGC=2 to enable JS garbage collector logs,
  • GST_DEBUG=4 to enable gstreamer (multimedia-related) logs (see the documentation),
  • G_MESSAGES_DEBUG=all to enable GLib-level logs.

If MiniBrowser (or similar browser) is used, one can also set a runtime preference to enable JS console.log(...) logging to the standard output:

  • --features=+LogsPageMessagesToSystemConsole.

Enabling remote web inspector #

Enabling WPE’s remote web inspector is a twofold process:

  1. The first step is to run WPE with the proper environment variable so that it starts listening on IP:PORT using tcp socket:
  • WEBKIT_INSPECTOR_SERVER=IP:PORT is the most reasonable option as it uses inspector:// protocol that can be utilized by WebKit-native browsers such as GNOME Web (Epiphany) or Safari,
  • WEBKIT_INSPECTOR_HTTP_SERVER=IP:PORT is a less preferable alternative that uses HTTP protocol and technically works from any browser. However, no seamless integration is guaranteed in this case.
  1. The second step is to connect from a regular web browser to the WPE:
  • using inspector://IP:PORT/ if native inspector server was started,
  • using http://IP:PORT/ if HTTP inspector server was started,
  • forwarding the ports using socat tcp-l:PORT,fork,reuseaddr tcp:IP:PORT if the WPE is running in unreachable network.

Experimenting with environment variables and runtime preferences #

The most outstanding environment variables changing the behavior of WPE are the following:

  • WPE_DISPLAY — assuming the new WPE platform API is used, this environment variable allows one to switch the pre-defined platform implementation thus changing a platform-facing part of graphics pipeline. The valid options are:
    • WPE_DISPLAY=wpe-display-headless — for headless implementation,
    • WPE_DISPLAY=wpe-display-drm — for direct rendering manager integration,
    • WPE_DISPLAY=wpe-display-wayland — for wayland integration,
  • WEBKIT_SKIA_ENABLE_CPU_RENDERING — when set to 1, rendering the DOM contents to the layers is done using Skia CPU backend instead of GPU one.

The most outstanding runtime preferences changing the behavior of WPE are the following:

  • CanvasUsesAcceleratedDrawing — when disabled, 2D canvas will use Skia CPU backend instead of GPU one,
  • LayerBasedSVGEngine — when enabled, WPE uses a different SVG engine internally,
  • AcceleratedCompositing — when disabled, WPE uses experimental, non-composited mode that bypasses all of the compositor work.

Limiting device memory #

On the majority of embedded devices, the device memory can be limited by:

  1. Interrupting the boot sequence (usually holding some key such as z upon booting),
  2. Invoking the command to change the limit and booting, e.g.:
    > global linux.bootargs.console="console=ttymxc0,115200n8 mem=2G"
    > boot
    

Running WPE with sysprof #

Regardless of whether it’s done on desktop (using wkdev-sdk) or on embedded device, the command is always as simple as:

  • sysprof-cli -f -- <WPE-INVOCATION>.

See the documentation entry for more details.

Building WPE in release mode with debug symbols #

On desktop, the simplest way to get release with debug symbols is to utilize CMake’s build type by using -DCMAKE_BUILD_TYPE=RelWithDebInfo within WPE build command, so:

  • ./Tools/Scripts/build-webkit --wpe --release --cmakeargs="-DCMAKE_BUILD_TYPE=RelWithDebInfo".

On embedded, when Yocto is used, one should tweak settings such as:

IMAGE_GEN_DEBUGFS = "1"                                                         
IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
DEBUG_BUILD = "1"
EXTRA_IMAGE_FEATURES_append = " dbg-pkgs"

and potentially INHIBIT_PACKAGE_STRIP to control whether debug symbols should be kept with the binary or not. This may be necessary occasionally as some tools have problems reading .gnu_debuglink and therefore work only with symbols included in the binaries.

Building and running with sanitizers #

WebKit works pretty well with all kinds of sanitizers. To build with any of them a CMake-level helper called ENABLE_SANITIZERS can be used by specifying -DENABLE_SANITIZERS=address, -DENABLE_SANITIZERS=leak etc. With that, the command for building e.g. on desktop could look like:

  • ./Tools/Scripts/build-webkit --wpe --debug --cmakeargs=-DENABLE_SANITIZERS=address.

For more details, one can refer to this article from fellow Igalian, Fujii.

Building and running with memory sampler #

When WPE is built with -DENABLE_MEMORY_SAMPLER=ON, the simple memory sampler can be started along with the browser using environment variable:

  • WEBKIT_SAMPLE_MEMORY=1 (accompanied by WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1 if needed).

With that, the memory of various WPE processes is sampled every second, and saved to the files under /tmp directory continuously.

Building and running with node statistics #

Node statistics are a debug-only feature that can be enabled by:

  • changing 0 of #define DUMP_NODE_STATISTICS 0 to 1 in Source/WebCore/dom/Element.h,
  • adding dumpStatistics() call, to e.g. Node constructor in Source/WebCore/dom/Node.cpp.

Building and running with libpas statistics #

Libpas statistics are a debug-only feature that can be enabled by changing 0 of #define PAS_ENABLE_STATS 0 to 1 in Source/bmalloc/libpas/src/libpas/pas_config.h and then running WPE with environment variable PAS_STATS_ENABLE=1.

June 02, 2026 12:00 AM