↫ Home

WebKit Igalia Periodical #70

13 July, 2026 - Categories: wip

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!