{"title":"Planet Igalia","comment":{},"link":[{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/planet.igalia.com\/"}},{"@attributes":{"href":"https:\/\/planet.igalia.com\/\/atom.xml","rel":"self","type":"application\/atom+xml"}}],"id":"https:\/\/planet.igalia.com\/\/atom.xml","updated":"2026-04-16T22:00:33+00:00","generator":"Planet\/2.0 +http:\/\/www.planetplanet.org","entry":[{"title":"Jos\u00e9 Dapena: Container Timing: moving to Origin Trial","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/"}},"id":"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/","updated":"2026-04-14T00:00:00+00:00","content":"\n                <img class=\"face\" src=\"\/images\/dape.png\" width=\"74\" height=\"100\" alt=\"\" align=\"right\" style=\"float: right\" \/>\n<p>It has been a busy few months for the Container Timing API. After <a href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">introducing the concept of measuring web components performance<\/a> and <a href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">detailing the native implementation in Blink<\/a>, I have an update to share.<\/p>\n<p>The API is moving to the next phase: a Chromium Origin Trial. I will also be presenting a year of work at the upcoming <a href=\"https:\/\/www.chromium.org\/events\/blinkon-21\/\">BlinkOn 21<\/a>.<\/p>\n<h2 id=\"the-origin-trial\" tabindex=\"-1\">The Origin Trial <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/\">#<\/a><\/h2>\n<p>After months of development and testing in Chromium, Container Timing is ready for real-world testing. We will run an Origin Trial from Chromium 148 to 153. You can <a href=\"https:\/\/developer.chrome.com\/origintrials\/#\/view_trial\/3312960475884421121\">register for the trial<\/a>.<\/p>\n<p>Until now, developers have had to manually enable the <code>ContainerTiming<\/code> feature flag in Chromium to test the new API. With the Origin Trial, early adopters can enable the API in production for a subset of their users by including the trial token. <a href=\"https:\/\/developer.chrome.com\/docs\/web-platform\/origin-trials\">More information on how to use origin trial tokens<\/a>.<\/p>\n<p>Why is this important? We have been internally testing and evolving the API, but now we need feedback from real-world users. The Origin Trial will allow web developers to use the new API in production and experiment with it.<\/p>\n<p>Please provide your feedback at the <a href=\"https:\/\/github.com\/WICG\/container-timing\/issues\">WICG Container Timing issue tracker<\/a>.<\/p>\n<ul>\n<li>Does the API design meet your needs?<\/li>\n<li>How could it be changed to be more useful?<\/li>\n<li>Any corner cases that are not working as expected?<\/li>\n<\/ul>\n<h2 id=\"a-year-in-review-at-blinkon-21\" tabindex=\"-1\">A year in review at BlinkOn 21 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/\">#<\/a><\/h2>\n<p>Next week, April 20th and 21st, the Chromium community will gather for BlinkOn 21. I\u2019ll be giving a lightning talk summarizing the updates over the last year.<\/p>\n<p>I will keep a close eye on the BlinkOn Slack channels during the event, so feel free to reach out to discuss the roadmap, implementation details, or any API feedback.<\/p>\n<h2 id=\"wrapping-up\" tabindex=\"-1\">Wrapping up <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/\">#<\/a><\/h2>\n<p>The Origin Trial is a key step toward finalizing the specification. Real-world feedback from the trial itself and from <a href=\"https:\/\/www.chromium.org\/events\/blinkon-21\/\">BlinkOn 21<\/a> discussions will feed into the standards working group discussions, and we expect the specification to evolve from there.<\/p>\n<p>If you build with Container Timing during the trial, please <a href=\"https:\/\/github.com\/WICG\/container-timing\/issues\">share what you find<\/a>: what works, what does not, and what is missing. That input will shape the final API.<\/p>\n<p>You can also test locally by enabling the <code>ContainerTiming<\/code> feature flag in Chromium, while you wait for your <a href=\"https:\/\/developer.chrome.com\/origintrials\/#\/view_trial\/3312960475884421121\">Origin Trial registration<\/a> to be approved.<\/p>\n<h2 id=\"thanks\" tabindex=\"-1\">Thanks <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/\">#<\/a><\/h2>\n<p>This work is part of the collaboration between <a href=\"https:\/\/techatbloomberg.com\">Bloomberg<\/a> and <a href=\"https:\/\/www.igalia.com\">Igalia<\/a>. Thanks!<\/p>\n<p><a href=\"https:\/\/www.igalia.com\">\n<source media=\"(prefers-color-scheme: dark)\">\n<img src=\"https:\/\/blogs.igalia.com\/dape\/img\/igalia_-_500px_-_RGB_-_Feb23-580x210.png\" alt=\"Igalia\" \/>\n<\/source><\/a> <a href=\"https:\/\/techatbloomberg.com\"><img src=\"https:\/\/blogs.igalia.com\/dape\/img\/Bloomberg-logo-580x117.png\" alt=\"Bloomberg\" class=\"dark-invert\" \/><\/a><\/p>\n<h2 id=\"references\" tabindex=\"-1\">References <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/04\/14\/container-timing-moving-to-origin-trial\/\">#<\/a><\/h2>\n<ul>\n<li><a href=\"https:\/\/github.com\/WICG\/container-timing\">Container Timing explainer<\/a>.<\/li>\n<li><a href=\"https:\/\/wicg.github.io\/container-timing\/\">Specification draft @ WICG<\/a>.<\/li>\n<li><a href=\"https:\/\/developer.chrome.com\/docs\/web-platform\/origin-trials\">Get started with origin trials<\/a>.<\/li>\n<li><a href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">Container Timing: measuring web components performance<\/a>.<\/li>\n<li><a href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">The implementation of Container Timing: aggregating paints in Blink<\/a>.<\/li>\n<li><a href=\"https:\/\/www.chromium.org\/events\/blinkon-21\/\">BlinkOn 21<\/a>.<\/li>\n<\/ul>                ","author":{"name":"Jos\u00e9 Dapena","uri":"https:\/\/blogs.igalia.com\/dape\/"}},{"title":"St\u00e9phane Cerveau: Introducing GstPrinceOfParser 0.4.3","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/"}},"id":"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/","updated":"2026-04-08T00:00:00+00:00","content":"\n<h1 id=\"gstprinceofparser-an-all-in-one-tool-to-play-with-gstreamer-on-any-platform\" tabindex=\"-1\">GstPrinceOfParser: An All-in-One Tool to Play With GStreamer on Any Platform <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h1>\n<p>Introducing <a href=\"https:\/\/github.com\/dabrain34\/gstpop\">gst-pop<\/a>, the GStreamer Prince of Parser \u2014 a tool to make interaction with GStreamer easier, global, and remotely accessible.<\/p>\n<h2 id=\"what-is-gstreamer\" tabindex=\"-1\">What is GStreamer? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p><a href=\"https:\/\/gstreamer.freedesktop.org\/\">GStreamer<\/a> is an open-source multimedia framework started in 1999. It lets you build pipelines of interconnected elements to stream, encode, decode, and manipulate media. The core idea is simple: a source element produces data, passes it through one or more transform elements, and delivers it to a sink. For example, here is a pipeline that decodes an MP3 audio file:<\/p>\n<pre class=\"language-mermaid\" tabindex=\"0\"><code class=\"language-mermaid\">    filesrc <span class=\"token arrow operator\">--><\/span> mp3dec <span class=\"token arrow operator\">--><\/span> audiosink<\/code><\/pre>\n<p>For more than 20 years, GStreamer has relied on its in-house toolbox to demonstrate the power\nof its pipelines. As this toolbox is used in thousands of projects and serves as a reference\nimplementation, modifications and enhancements are deliberately kept minimal to maintain stability.\ngst-pop was created to go beyond these limitations.<\/p>\n<h2 id=\"a-unified-interface-for-gstreamer\" tabindex=\"-1\">A Unified Interface for GStreamer <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p>Accessible over the network, via CLI arguments, or through D-Bus, gst-pop aims to provide\na multi-pipeline-capable command-line tool.<\/p>\n<p>With a simple invocation of <code>gst-pop<\/code> (or its alias <code>gst-popd<\/code>), you can run a daemon that accepts\nmultiple pipelines simultaneously, accessible through D-Bus or WebSocket via the pipeline\nID. You\u2019ll be able to control, query, and get information about each pipeline \u2014 all of that\nover a remote network, secured with API key authentication and origin validation to prevent unauthorized access.<\/p>\n<p>As demonstrated in the <a href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">blog post related to GstPipelineStudio<\/a>, it will be possible to connect to a remote pipeline or launch new pipelines through the GStreamer GUI. If a GUI is not available\non the platform, it will soon be possible to use a web interface to control GStreamer, offering\neverything GStreamer can provide and more, limited only by your imagination.<\/p>\n<h2 id=\"remote-element-inspection\" tabindex=\"-1\">Remote Element Inspection <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p>gst-pop (or its alias <code>gst-pop-inspect<\/code>) is also capable of listing the elements on a local or remote host, inspecting their capabilities, and providing a remote way to interact with your GStreamer installation.<\/p>\n<h2 id=\"media-discovery\" tabindex=\"-1\">Media Discovery <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p>It can also provide information on a media file using GStreamer\u2019s discovery interface using <code>gst-pop-discovery<\/code>, offering an easy and remote-capable media discovery system for your setup.<\/p>\n<h2 id=\"playback\" tabindex=\"-1\">Playback <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p>And of course, it can serve as an alternative to the <code>gst-play<\/code> tool, with <code>gst-pop-play<\/code>, allowing you to instantiate as many playback sessions as you need, with the ability to use any sink you want.<\/p>\n<p>The possibilities are vast: provide multimedia services such as transcoding, media analysis, or remote playback to your setup using the power of a remote machine, all controllable from your terminal or a GUI such as <a href=\"https:\/\/gps.mooday.dev\">GstPipelineStudio<\/a>.<\/p>\n<h2 id=\"cross-platform-and-language-support\" tabindex=\"-1\">Cross-Platform and Language Support <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<p>The tool is written in Rust for memory safety and reliability and provides client libraries in both Rust and C, offering all the flexibility needed for your existing applications. It is available on Linux (deb, rpm or docker), MacOS, and Windows, see the <a href=\"https:\/\/github.com\/dabrain34\/gstpop\/releases\/tag\/v0.4.3\">release page<\/a>.<\/p>\n<h2 id=\"examples\" tabindex=\"-1\">Examples <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstprinceofparser-0-4-3\/\">#<\/a><\/h2>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token comment\"># Start the daemon<\/span><br \/>gst-pop<br \/><br \/><span class=\"token comment\"># Launch a pipeline<\/span><br \/>gst-pop launch videotestsrc <span class=\"token operator\">!<\/span> autovideosink<br \/><br \/><span class=\"token comment\"># Inspect an element<\/span><br \/>gst-pop inspect videotestsrc<br \/><br \/><span class=\"token comment\"># Discover media info<\/span><br \/>gst-pop discover file:\/\/\/path\/to\/video.mp4<br \/><br \/><span class=\"token comment\"># Play a media file<\/span><br \/>gst-pop play file:\/\/\/path\/to\/video.mp4<br \/><br \/><span class=\"token comment\"># Create a pipeline with the client<\/span><br \/>gst-popctl create <span class=\"token string\">\"videotestsrc ! autovideosink\"<\/span><br \/><br \/><span class=\"token comment\"># List pipelines on a remote daemon<\/span><br \/>gst-popctl list<br \/><br \/><span class=\"token comment\"># Play the pipeline with ID 0<\/span><br \/>gst-popctl play <span class=\"token number\">0<\/span><br \/><br \/><span class=\"token comment\"># Stop the pipeline with ID 0<\/span><br \/>gst-popctl stop <span class=\"token number\">0<\/span><br \/><br \/><span class=\"token comment\"># Run via Docker<\/span><br \/><span class=\"token function\">docker<\/span> run <span class=\"token parameter variable\">-d<\/span> <span class=\"token parameter variable\">-p<\/span> <span class=\"token number\">9000<\/span>:9000 ghcr.io\/dabrain34\/gstpop:latest<\/code><\/pre>\n<p>Give it a try and let us know what ideas you might have \u2014 we have plenty coming, so stay tuned.<\/p>\n<p>As usual, if you would like to learn more about gst-pop, GStreamer, or any other open multimedia framework, please contact <a href=\"https:\/\/www.igalia.com\/\">us<\/a>!<\/p>                ","author":{"name":"St\u00e9phane Cerveau","uri":"https:\/\/blogs.igalia.com\/scerveau\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #62","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-62\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-62\/","updated":"2026-04-07T17:07:37+00:00","content":"\n<p>Update on what happened in WebKit in the week from March 31 to April 7.<\/p>\n<p>\nSupport for iOS dialog light dismiss, a new API to obtain page icons,\nWebKit nightly builds for Epiphany Canary produced by\nGNOME GitLab, and more conservative checks for MPEG-4 Audio object types\nare all part of this week's edition of the WebKit periodical.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310308@main\">A new API to obtain page icons<\/a> (a.k.a. \u201cfavicons\u201d) has been added to the GTK port. The new functionality reuses the recently added <code>WebKitImage<\/code> class and provides access to multiple page icons at once through the added <code>WebKitImageList<\/code> type, allowing applications to better choose an icon that suits their needs. Changes to the <code>WebKitWebView.page-icons<\/code> property are guaranteed to be done once per page load, when all icon images are available to be used. This new API has been also <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310697@main\">enabled for the WPE port<\/a>, and the plan is to deprecate the old page favicon functionality going forward.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310478@main\">Added iOS support for dialog light dismiss<\/a>, part of the experimental <code>closedby<\/code> attribute implementation.<\/p>\n  <\/div>\n<h3 id=\"multimedia-movie-camera\">Multimedia \ud83c\udfa5<\/h3>\n<div class=\"wip-description\">\n<p>GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310634@main\">canPlayType() is now more conservative regarding MPEG-4 Audio object types<\/a>. This primarily affects AAC extensions: In the past, as long as there was an AAC decoder installed, WebKit was accepting any codec string that started with <code>mp4a<\/code>. Now it only accepts codec strings that correspond to object types that have widespread support. This can prevent accidental playback of newer formats like xHE-AAC, which many decoders don't yet support \u2014 for example, as of writing, FFmpeg support for xHE-AAC is only very recent and still incomplete.<\/p>\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310381@main\">canPlayType() now also reports support for Dolby AC-4<\/a> in systems with a decoder capable of handling it.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>The GStreamer WebRTC backend <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310637@main\">now<\/a> rejects SDP including <code>rtpmap<\/code> attributes in the disallowed range of 64-95 payload types. Compliance with RFC 7587 was also improved.<\/p>\n  <\/div>\n<h2 id=\"infrastructure-construction-site\">Infrastructure \ud83c\udfd7\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p>The WebKitGTK nightly builds for Epiphany Canary are now handled entirely by the <a rel=\"external\" href=\"https:\/\/gitlab.gnome.org\">GNOME GitLab<\/a> infrastructure, many thanks to them! The previous approach was not optimal, producing release builds without debug symbols. With the new builds, it is now easier to get crash stack traces including more information.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"St\u00e9phane Cerveau: Introducing GstPipelineStudio 0.5.1","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/"}},"id":"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/","updated":"2026-04-07T00:00:00+00:00","content":"\n<h1 id=\"gstpipelinestudio-0-5-1\" tabindex=\"-1\">GstPipelineStudio 0.5.1 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h1>\n<h2 id=\"your-gstreamer-pipelines-at-a-glance\" tabindex=\"-1\">Your GStreamer Pipelines, at a Glance <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p>New version of GstPipelineStudio is out!<\/p>\n<p>After months of improvements and intermediate releases since October 2024, it\u2019s time for an official announcement for <a href=\"https:\/\/gps.mooday.dev\">0.5.1<\/a>.<\/p>\n<p>GstPipelineStudio provides a visual interface to GStreamer, the marvelous Swiss Army knife of multimedia pipelines. But what is GStreamer exactly?<\/p>\n<h2 id=\"what-is-gstreamer\" tabindex=\"-1\">What is GStreamer? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p><a href=\"https:\/\/gstreamer.freedesktop.org\/\">GStreamer<\/a> is an open-source multimedia framework started in 1999. It lets you build pipelines of interconnected elements to stream, encode, decode, and manipulate media. The core idea is simple: a source element produces data, passes it through one or more transform elements, and delivers it to a sink. For example, here is a pipeline that decodes an MP3 audio file:<\/p>\n<pre class=\"language-mermaid\" tabindex=\"0\"><code class=\"language-mermaid\">    filesrc <span class=\"token arrow operator\">--><\/span> mp3dec <span class=\"token arrow operator\">--><\/span> audiosink<\/code><\/pre>\n<p>GStreamer is written in C, with a growing ecosystem of plugins in Rust and bindings for languages such as Python and C++. It ships with many command-line tools to build and test pipelines, but validating ideas still requires writing C\/Rust\/Python code or using the command line. That\u2019s where GstPipelineStudio comes in \u2014 providing a visual interface to help newcomers discover and adopt GStreamer, and skilled developers debug their pipelines.<\/p>\n<h2 id=\"the-story-behind-gstpipelinestudio\" tabindex=\"-1\">The Story Behind GstPipelineStudio <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p>The GstPipelineStudio project started in 2021 with the idea to provide the same environment that brought me to multimedia: GraphEdit on Windows with DirectShow. Indeed, DirectShow and GStreamer share the same idea of plugins sharing data. As I started to implement a DVB decoder with DirectShow, the graphical interface made it easier to validate which filters to use. But DirectShow only works natively on Windows, unlike GStreamer which can run everywhere \u2014 Linux, macOS, Windows, iOS, Android, and even low-power devices such as a Raspberry Pi.<\/p>\n<p>GstPipelineStudio aims to work on all these platforms, easing GStreamer adoption where its use was not always obvious, such as on Windows.\nGStreamer is based on GLib, a cross-platform toolkit that abstracts system calls and provides a common base layer. For the GUI, since Rust was offering very good bindings, GTK was the natural choice to achieve cross-platform support.\nThere was an attempt to create a GUI using Qt, named <a href=\"https:\/\/github.com\/virinext\/pipeviz\">pipeviz<\/a>, which has been a great inspiration for GPS, but the Qt Rust bindings were not mature enough, unlike those for GTK.<\/p>\n<p>The first official release of GPS was 0.3.4, and you can read its <a href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-3-4\/\">official blog post<\/a> published in 2023. Since then, we have been devoted to providing new features\nto bring GPS to another level.<\/p>\n<p>A first revision, GPS 0.4.0, came out before Christmas 2024 with a refreshed interface \u2014 including zoom on the graph\nand contextual menus on any element or pad of the pipeline. The versions of GStreamer and GTK have also been updated to get the latest plugins and features from both frameworks.\nA new icon has also been introduced to let GPS dive into another dimension.<\/p>\n<h2 id=\"what-s-new-in-0-5-1\" tabindex=\"-1\">What\u2019s New in <a href=\"https:\/\/gitlab.freedesktop.org\/dabrain34\/GstPipelineStudio\/-\/releases\">0.5.1<\/a> <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p>0.5.1 is here, and it brings a game changer: the dot file reader.\nPreviously, it was possible to open a command-line pipeline or save\/open pipelines with an XML-based format, but now you can also open the generated dot files, the native format in GStreamer, to display a pipeline graphically. This is still a beta version as it can only display\nhigh-level pipelines such as those described with the command line. Nevertheless this is a great improvement and allows users to see their pipeline and manipulate it.<\/p>\n<p>Here is the list of other improvements you\u2019ll find in this release:<\/p>\n<ul>\n<li>Open Dot Folder menu entry for loading dot files from the common GStreamer folder<\/li>\n<li>Remote pipeline introspection using the GStreamer tracers<\/li>\n<li>App ID renamed to dev.mooday.GstPipelineStudio<\/li>\n<li>Improved look and feel of the interface<\/li>\n<li>Auto-connect on node click (node-link-request)<\/li>\n<li>File selector button for location property<\/li>\n<li>Logger copy to clipboard with multi-selection support<\/li>\n<li>Auto-arrange elements on screen<\/li>\n<li>GStreamer 1.28.0<\/li>\n<li>GTK 4.20<\/li>\n<li>RPM and AppImage artifacts<\/li>\n<\/ul>\n<h2 id=\"remote-pipeline-introspection\" tabindex=\"-1\">Remote Pipeline Introspection <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p>The remote pipeline introspection is a new way to connect to the WebSocket tracer available in GStreamer, pipeline-snapshot.\nIn addition to dot file loader, it allows you to visualize a pipeline directly in GPS from an external process running with the tracer.<\/p>\n<p>As you may know, GStreamer pipelines can be very complex, so one dream was to be able to visualize them live. There is already a mini tool in GStreamer named <code>gst-dots-viewer<\/code> which creates a web server to display pipelines in a browser from the <code>$XDG_CACHE_DIR<\/code> folder, see the <a href=\"https:\/\/blogs.gnome.org\/tsaunier\/2025\/05\/16\/gst-dots-viewer-a-new-tool-for-gstreamer-pipeline-visualization\/\">blog post<\/a> from Thibault about it.<\/p>\n<p>Now with GPS, you can directly create a WebSocket server and let the tracer connect to it and provide available dot files to be displayed.<\/p>\n<p>For example, to visualize a running pipeline in GPS:<\/p>\n<ol>\n<li>In GPS: <strong>Menu \u2192 Remote Pipeline \u2192 Listen\u2026<\/strong><\/li>\n<li>Enter the WebSocket address (e.g., <code>ws:\/\/localhost:8080<\/code>)<\/li>\n<li>Run your GStreamer pipeline with the <code>pipeline-snapshot<\/code> tracer:<\/li>\n<\/ol>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token assign-left variable\">GST_TRACERS<\/span><span class=\"token operator\">=<\/span><span class=\"token string\">\"pipeline-snapshot(dots-viewer-ws-url=ws:\/\/localhost:8080)\"<\/span> <span class=\"token punctuation\">\\<\/span><br \/>  gst-launch-1.0 videotestsrc <span class=\"token operator\">!<\/span> autovideosink<\/code><\/pre>\n<p>The pipeline graph will appear in GPS once the tracer connects.<\/p>\n<p>These dot files are converted to GPS pipelines, making it possible to modify them. That\u2019s a first step for real interaction with GStreamer pipelines \u2014 and there are more features coming in the <em>pipeline<\/em>.<\/p>\n<h2 id=\"coming-in-0-6-0\" tabindex=\"-1\">Coming in 0.6.0 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/scerveau\/introducing-gstpipelinestudio-0-5-1\/\">#<\/a><\/h2>\n<p>In parallel, a new tool named <a href=\"https:\/\/github.com\/dabrain34\/gstpop\">GstPrinceOfParser<\/a> (gst-pop) has also been implemented. This tool allows remote control of all pipelines instantiated locally or over the network.\nIt is a multi-pipeline daemon accessible through WebSocket or D-Bus, aiming to centralize all GStreamer options in one tool for <a href=\"https:\/\/gstreamer.freedesktop.org\/documentation\/tutorials\/basic\/gstreamer-tools.html#gstlaunch10\">launch<\/a>, <a href=\"https:\/\/gstreamer.freedesktop.org\/documentation\/tutorials\/basic\/gstreamer-tools.html#gstinspect10\">inspection<\/a>, and <a href=\"https:\/\/gstreamer.freedesktop.org\/documentation\/tutorials\/basic\/gstreamer-tools.html#gstdiscoverer10\">discovery<\/a>. GstPipelineStudio will be able to control this daemon, making gst-pop the backbone of the GStreamer GUI. A blog post will come soon, stay tuned\u2026<\/p>\n<p>A new tracer is under development: a WebSocket server that will allow you to inspect and interact with the current pipeline \u2014 modify the play state (pause, seek), fetch the logs, and of course see the current dot representation, all from the GstPipelineStudio interface.<\/p>\n<p>In addition, more features are on the way: a new look and feel based on libadwaita on Linux\/macOS\/Windows, better localization, an auto-plug feature, seek and step-by-step playback, and bug fixes on demand.<\/p>\n<p>We hope you\u2019ll enjoy this new version of the tool and please feel free to propose\nnew features with an RFC <a href=\"https:\/\/gitlab.freedesktop.org\/dabrain34\/GstPipelineStudio\/-\/issues\/new\">here<\/a> or merge requests <a href=\"https:\/\/gitlab.freedesktop.org\/dabrain34\/GstPipelineStudio\/-\/merge_requests\">here<\/a>.<\/p>\n<p>Stay tuned for the next <a href=\"https:\/\/discourse.gstreamer.org\/t\/gstreamer-spring-hackfest-2026-on-29-31-may-2026-in-nice-france\/5762\">GStreamer Spring hackfest 2026<\/a> coming soon (end of May) where new features and deeper interaction with GStreamer pipelines will be discussed.<\/p>\n<p>As usual, if you would like to learn more about GstPipelineStudio, GStreamer, or any other open multimedia framework, please contact <a href=\"https:\/\/www.igalia.com\/\">us<\/a>!<\/p>                ","author":{"name":"St\u00e9phane Cerveau","uri":"https:\/\/blogs.igalia.com\/scerveau\/"}},{"title":"Miyoung Shin: Extension Migration Progress Update \u2013 Part 1","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/mshin\/2026\/04\/02\/extension-migration-progress-update-part-1\/"}},"id":"https:\/\/blogs.igalia.com\/mshin\/?p=161","updated":"2026-04-02T11:32:34+00:00","content":"\n<h1 class=\"wp-block-heading\">Background<\/h1>\n\n\n\n<p>Following up on my previous post, I would like to share an update on the progress of the <strong>Extension migration work<\/strong> that has been underway over the past few months.<\/p>\n\n\n\n<p>To briefly recap the motivation behind this effort: Igalia&#8217;s long-term goal is to enable <strong>embedders<\/strong> to use the Extension system <strong>without depending on the <code>\/\/chrome<\/code> layer<\/strong>. In other words, we want to make it possible to support Extension functionality with minimal implementation effort using only <code>\/\/content + \/\/extensions<\/code>.<\/p>\n\n\n\n<p>Currently, some parts of the Extension system still rely on the <code>\/\/chrome<\/code> layer. Our objective is to remove those dependencies so that embedders can integrate Extension capabilities without needing to include the entire <code>\/\/chrome<\/code> layer.<\/p>\n\n\n\n<p>As a <strong>short-term milestone<\/strong>, we focused on migrating the <strong>Extension installation implementation<\/strong> from <code>\/\/chrome<\/code> to <code>\/\/extensions<\/code>. This phase of the work has now been completed, which is why I\u2019m sharing this progress update.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">Extension Installation Formats<\/h1>\n\n\n\n<p>Chromium supports several formats for installing Extensions. The most common ones are <strong>zip<\/strong>, <strong>unpacked<\/strong> and <strong>crx.<\/strong><\/p>\n\n\n\n<p>Each format serves a different purpose:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>zip<\/strong> \u2013 commonly used for internal distribution or packaged deployment<\/li>\n\n\n\n<li><strong>unpacked<\/strong> \u2013 primarily used during development and debugging<\/li>\n\n\n\n<li><strong>crx<\/strong> \u2013 the standard packaged format used by the Chrome Web Store<\/li>\n<\/ul>\n\n\n\n<p>During this migration effort, the code responsible for supporting all three installation formats has been successfully moved to the <code>\/\/extensions<\/code> layer.<\/p>\n\n\n\n<p>As a result, the Extension installation pipeline is now significantly less dependent on the <code>\/\/chrome<\/code> layer, bringing us closer to enabling Extension support directly on top of <code>\/\/content + \/\/extensions<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Patch and References<\/h2>\n\n\n\n<p>To support this migration, several patches were introduced to move installation-related components into the <code>\/\/extensions<\/code> layer and decouple them from <code>\/\/chrome<\/code>.<\/p>\n\n\n\n<p>For readers who are interested in the implementation details, you can find the related changes and discussions here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Chromium issue tracking this work:<br \/><a href=\"https:\/\/issues.chromium.org\/issues\/358567092\">https:\/\/issues.chromium.org\/issues\/358567092<\/a><\/li>\n\n\n\n<li>Related code reviews and patches:<br \/><a href=\"https:\/\/chromium-review.googlesource.com\/q\/myid.shin@igalia.com\">https:\/\/chromium-review.googlesource.com\/q\/myid.shin@igalia.com<\/a><\/li>\n<\/ul>\n\n\n\n<p>These links provide more insight into the design decisions, code changes, and ongoing discussions around the migration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">Demo<\/h1>\n\n\n\n<p>Below is a short demo showing the current setup in action.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls=\"controls\" src=\"https:\/\/blogs.igalia.com\/mshin\/files\/2026\/03\/demo-extension_installation.mp4\"><\/video><\/figure>\n\n\n\n<p>This demo was recorded using\u00a0<code>app_shell<\/code> on Linux, the minimal stripped-down browser container designed to run Chrome Apps and using only\u00a0<code>\/\/content<\/code>\u00a0and\u00a0<code>\/\/extensions<\/code>\/ layers.<\/p>\n\n\n\n<p>To have this executable launcher, we also extended <strong><code>app_shell<\/code> <\/strong>with the minimal functionality required for embedders to install the extension app.<\/p>\n\n\n\n<p>This allows Extensions to be installed and executed <strong>without relying on the full Chrome browser implementation<\/strong>, making it easier to experiment with and validate the migration work.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">Next Steps<\/h1>\n\n\n\n<p>The next short-term goal is to <strong>migrate the code required for installing Extensions via the Chrome Web Store<\/strong> into the <code>\/\/extensions<\/code> layer as well.<\/p>\n\n\n\n<p>At the moment, parts of the Web Store installation flow still depend on the <code>\/\/chrome<\/code> layer. The next phase of this project will focus on removing those dependencies so that Web Store-based installation can also function within the <code>\/\/extensions<\/code> layer.<\/p>\n\n\n\n<p>Once this work is completed, embedders will be able to install Extension apps from Chrome WebStore with a significantly simpler architecture (<code>\/\/content + \/\/extensions<\/code>). <\/p>\n\n\n\n<p>This will make the Extension platform <strong>more modular, reusable, and easier to integrate into custom Chromium-based products<\/strong>.<\/p>\n\n\n\n<p>I will continue to share updates as the migration progresses.<\/p>\n\n\n\n<p><\/p>                ","author":{"name":"mshin","uri":"https:\/\/blogs.igalia.com\/mshin"}},{"title":"Qiuyi Zhang (Joyee): Tinkering with Node.js Core on ARM64 Windows","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2026\/01\/31\/tinkering-with-nodejs-core-on-arm64-windows\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2026\/01\/31\/tinkering-with-nodejs-core-on-arm64-windows\/","updated":"2026-04-01T02:16:02+00:00","content":"\n<p>A while back, I wrote about <a href=\"https:\/\/joyeecheung.github.io\/blog\/2025\/02\/16\/building-nodejs-on-windows-with-clang-cl\/\" title=\"Building Node.js on Windows using the new ClangCL support\">Building Node.js on Windows using the new ClangCL support<\/a>, which was done on an actual x64 Windows machine<\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}},{"title":"Andy Wingo: wastrelly wabbits","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/wingolog.org\/archives\/2026\/03\/31\/wastrelly-wabbits"}},"id":"https:\/\/wingolog.org\/2026\/03\/31\/wastrelly-wabbits","updated":"2026-03-31T20:34:23+00:00","content":"\n<div><p>Good day!  Today (tonight), some notes on the last couple months of\n<a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/\">Wastrel<\/a>, my ahead-of-time\nWebAssembly compiler.<\/p><p>Back in the beginning of February, I showed <a href=\"https:\/\/wingolog.org\/archives\/2026\/02\/06\/ahead-of-time-wasm-gc-in-wastrel\">Wastrel running programs\nthat use garbage\ncollection<\/a>,\nusing an embedded copy of the <a href=\"https:\/\/github.com\/wingo\/whippet\">Whippet\ncollector<\/a>, specialized to the types\npresent in the Wasm program.  But, the two synthetic GC-using programs I\ntested on were just ported microbenchmarks, and didn\u2019t reflect the\noutput of any real toolchain.<\/p><p>In this cycle I worked on compiling the output from the <a href=\"https:\/\/spritely.institute\/hoot\/\">Hoot\nScheme-to-Wasm compiler<\/a>.  There were\nsome interesting challenges!<\/p><h3>bignums<\/h3><p>When I originally wrote the Hoot compiler, it targetted the browser,\nwhich already has a bignum implementation in the form of\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/BigInt#Browser_compatibility\">BigInt<\/a>,\nwhich <a href=\"https:\/\/wingolog.org\/archives\/2019\/05\/23\/bigint-shipping-in-firefox\">I worked on back in the\nday<\/a>.\nHoot-generated Wasm files use host bigints via <tt>externref<\/tt> (though\n<a href=\"https:\/\/wingolog.org\/archives\/2023\/03\/20\/a-world-to-win-webassembly-for-the-rest-of-us#:~:text=common%20struct%20supertype\">wrapped in structs to allow for hashing and\nidentity<\/a>).<\/p><p>In Wastrel, then, I implemented the imports that implement bignum\noperations: addition, multiplication, and so on.  I did so using\n<a href=\"https:\/\/gmplib.org\/repo\/gmp\/file\/tip\/mini-gmp\/README\">mini-gmp<\/a>, a\nstripped-down implementation of the workhorse GNU multi-precision\nlibrary.  At some point if bignums become important, this gives me the\noption to link to the full GMP instead.<\/p><p>Bignums were the first managed data type in Wastrel that wasn\u2019t defined\nas part of the Wasm module itself, instead hiding behind <tt>externref<\/tt>, so\nI had to add a facility to <a href=\"https:\/\/wingolog.org\/archives\/2026\/02\/18\/two-mechanisms-for-dynamic-type-checks\">allocate type\ncodes<\/a>\nto these \u201chost\u201d data types.  More types will come in time: weak maps,\nephemerons, and so on.<\/p><p>I think bignums would be a great proposal for the Wasm standard, similar\nto <a href=\"https:\/\/github.com\/WebAssembly\/stringref\">stringref<\/a> ideally\n(<a href=\"https:\/\/wingolog.org\/archives\/2023\/10\/19\/requiem-for-a-stringref\">sniff!<\/a>),\npossibly in an <a href=\"https:\/\/github.com\/WebAssembly\/js-string-builtins\/blob\/main\/proposals\/js-string-builtins\/Overview.md\">attenuated\nform<\/a>.<\/p><h3>exception handling<\/h3><p>Hoot used to emit a <a href=\"https:\/\/github.com\/WebAssembly\/exception-handling\/blob\/main\/proposals\/exception-handling\/legacy\/Exceptions.md\">pre-standardization form of exception\nhandling<\/a>,\nand hadn\u2019t gotten around to updating to the <a href=\"https:\/\/wingolog.org\/archives\/2026\/03\/10\/nominal-types-in-webassembly\">newer\nversion<\/a>\nthat was standardized last July.  I updated Hoot to emit the newer kind\nof exceptions, as it was easier to implement them in Wastrel that way.<\/p><p>Some of the problems <a href=\"https:\/\/cfallin.org\/blog\/2025\/11\/06\/exceptions\/\">Chris Fallin contended with in\nWasmtime<\/a> don\u2019t apply\nin the Wastrel case: since the set of instances is known at\ncompile-time, we can statically allocate type codes for exception tags.\nAlso, I didn\u2019t really have to do the back-end: I can just use <a href=\"https:\/\/man7.org\/linux\/man-pages\/man3\/longjmp.3.html\"><tt>setjmp<\/tt>\nand <tt>longjmp<\/tt><\/a>.<\/p><p>This whole paragraph was meant to be a bit of an aside in which I\nbriefly mentioned why just using <tt>setjmp<\/tt> was fine.  Indeed, because\nWastrel never re-uses a temporary, relying entirely on GCC to \u201cre-use\u201d\nthe register \/ stack slot on our behalf, I had thought that I didn\u2019t\nneed to worry about the \u201cvolatile problem\u201d.  From the C99 specification:<\/p><blockquote> [...] values of objects of automatic storage duration that\nare local to the function containing the invocation of the corresponding\nsetjmp macro that do not have volatile-qualified type and have been\nchanged between the setjmp invocation and longjmp call are\nindeterminate.  <\/blockquote><p>My thought was, though I might set a value between <tt>setjmp<\/tt> and\n<tt>longjmp<\/tt>, that would only be the case for values whose lifetime did\nnot reach the <tt>longjmp<\/tt> (i.e., whose last possible use was before the\njump).  Wastrel didn\u2019t introduce any such cases, so I was good.<\/p><p>However, I forgot about <tt>local.set<\/tt>: mutations of locals (ahem, <i>objects\nof automatic storage duration<\/i>) in the source Wasm file could run afoul\nof this rule.  So, because of writing this blog post, I went back and\ndid an analysis pass on each function to determine the set of locals\nwhich are mutated inside the body of a <tt>try_table<\/tt>.  Thank you, rubber duck readers!<\/p><h3>bugs<\/h3><p>Oh my goodness there were many bugs.  Lacunae, if we are being generous;\nthings not implemented quite right, which resulted in errors either when\ngenerating C or when compiling the C.  The <a href=\"https:\/\/wingolog.org\/archives\/2026\/02\/09\/six-thoughts-on-generating-c\">type-preserving translation\nstrategy<\/a>\ndoes seem to have borne fruit, in that I have spent very little time in\nGDB: once things compile, they work.<\/p><h3>coevolution<\/h3><p>Sometimes Hoot would use a browser facility where it was convenient, but\nfor which in a better world we would just do our own thing.  Such was the\ncase for the <tt>number-&gt;string<\/tt> operation on floating-point numbers: we\ndid something <a href=\"https:\/\/codeberg.org\/spritely\/hoot\/src\/branch\/main\/reflect-js\/reflect.js#L817-L828\">awful but\nexpedient<\/a>.<\/p><p>I didn\u2019t have this facility in Wastrel, so instead we moved to do\nfloat-to-string conversions <a href=\"https:\/\/codeberg.org\/spritely\/hoot\/src\/branch\/main\/lib\/hoot\/write.scm#L75-L208\">in\nScheme<\/a>.\nThis turns out to have been a good test for bignums too; the <a href=\"https:\/\/doi.org\/10.1145\/231379.231397\">algorithm\nwe use<\/a> is a bit dated and relies\non bignums to do its thing.  The move to Scheme also allows for printing\nfloating-point numbers in other radices.<\/p><p>There are a few more Hoot patches that were inspired by Wastrel, about\nwhich more later; it has been good for both to work on the two at the\nsame time.<\/p><h3>tail calls<\/h3><p>My plan for Wasm\u2019s\n<a href=\"https:\/\/webassembly.github.io\/spec\/core\/exec\/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-return-call-x\"><tt>return_call<\/tt><\/a>\nand friends was to use the new <tt>musttail<\/tt> annotation for calls, which\nhas been in clang for a while and was recently added to GCC.  I was\ncareful to <a href=\"https:\/\/wingolog.org\/archives\/2026\/02\/09\/six-thoughts-on-generating-c#:~:text=for%20ABI%20and%20tail%20calls%2C%20perform%20manual%20register%20allocation\">limit the number of function\nparameters<\/a>\nsuch that no call should require stack allocation, and therefore a\ncompiler should have no reason to reject any particular tail call.<\/p><p>However, there were bugs.  Funny ones, at first: <a href=\"https:\/\/gcc.gnu.org\/bugzilla\/show_bug.cgi?id=124533\">attributes applying to\na preceding label instead of the following\ncall<\/a>, or <a href=\"https:\/\/gcc.gnu.org\/bugzilla\/show_bug.cgi?id=124532\">the need\nto insert <tt>if (1)<\/tt> before the tail\ncall<\/a>.  More dire\nones, in which <a href=\"https:\/\/gcc.gnu.org\/bugzilla\/show_bug.cgi?id=124534\">tail callers inlined into <i>their<\/i> callees would cause\nthe tail calls to\nfail<\/a>, worked\naround with judicious application of <tt>noinline<\/tt>.  Thanks to GCC\u2019s Andrew\nPinski for help debugging these and other issues; with GCC things are\nfine now.<\/p><p>I did have to change the code I emitted to return \u201ctop types only\u201d: if\nyou have a function returning type T, you can tail-call a function\nreturning U if U is a subtype of T, but there is <a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/issues\/25\">no nice way to encode\nthis into the C type\nsystem<\/a>.  Instead, we\nreturn the top type of T (or U, it\u2019s the same), e.g. <tt>anyref<\/tt>, and\ninsert downcasts at call sites to recover the precise types.  Not so\nnice, but it\u2019s what we got.<\/p><p>Trying tail calls on clang, I ran into a funny restriction: clang not\nonly requires that return types match, but requires that tail caller and\ntail callee have the same parameters as well.  I can see why they did\nthis (it requires no stack shuffling and thus such a tail call is always\npossible, even with 500 arguments), but it\u2019s not the design point that I\nneed.  Fortunately there are discussions about <a href=\"https:\/\/discourse.llvm.org\/t\/experience-with-clang-musttail\/89085\/7\">moving to a different\nconstraint<\/a>.<\/p><h3>scale<\/h3><p>I spent way more time that I had planned to on improving the speed of\nWastrel itself.  My initial idea was to just emit one big C file, and\nthat would provide the maximum possibility for GCC to just go and do its\nthing: it can see everything, everything is static, there are loads of\n<tt>always_inline<\/tt> helpers that should compile away to single instructions,\nthat sort of thing.  But, this doesn\u2019t scale, in a few ways.<\/p><p>In the first obvious way, consider <a href=\"https:\/\/mastodon.social\/@wingo\/115378481224538009\">whitequark\u2019s\n<tt>llvm.wasm<\/tt><\/a>.  This\nis all of LLVM in one 70 megabyte Wasm file.  Wastrel made a huuuuuuge C\nfile, then GCC chugged on it forever; 80 minutes at <tt>-O1<\/tt>, and I wasn\u2019t\naiming for <tt>-O1<\/tt>.<\/p><p>I realized that in many ways, GCC wasn\u2019t designed to be a compiler\ntarget.  The shape of code that one might emit from a Wasm-to-C compiler\nlike Wastrel is different from that that one would write by hand.  I\neven ran into a <a href=\"https:\/\/gcc.gnu.org\/bugzilla\/show_bug.cgi?id=124651\">segfault compiling with\n-Wall<\/a>, because GCC\naccidentally recursed instead of iterated in the <tt>-Winfinite-recursion<\/tt>\npass.<\/p><p>So, I dealt with this in a few ways.  After many hours spent pleading\nand bargaining with different <tt>-O<\/tt> options, I bit the bullet and made\nWastrel emit multiple C files.  It will compute a DAG forest of all the\nfunctions in a module, where edges are direct calls, and go through that\nforest, <a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/src\/branch\/main\/module\/wastrel\/partitions.scm#L436\">greedily consuming (and possibly splitting) subtrees until we\nhave \u201cenough\u201d code to split out a partition<\/a>, as measured by number of\nWasm instructions.  They say that <tt>-flto<\/tt> makes this a fine approach,\nbut one never knows when a translation unit boundary will turn out to be\nimportant.  I compute needed symbol visibilities as much as I can so as\nto declare functions that don\u2019t escape their compilation unit as\n<tt>static<\/tt>; who knows if this is of value.  Anyway, this partitioning\nintroduced no performance regression in my limited tests so far, and\ncompiles are much much much faster.<\/p><h3>scale, bis<\/h3><p>A brief observation: Wastrel used to emit indented code, because it\ncould, and what does it matter, anyway.  However, consider Wasm\u2019s\n<a href=\"https:\/\/webassembly.github.io\/spec\/core\/valid\/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-br-table-l-ast-l-n\"><tt>br_table<\/tt><\/a>:\nit takes an array of <i>n<\/i> labels and an integer operand, and will branch\nto the <i>n<\/i>th label, or the last if the operand is out of range.  To set\nup a label in Wasm, you make a block, of which there are a handful of\nkinds; the label is visible in the block, and for <i>n<\/i> labels, the\n<tt>br_table<\/tt> will be the most nested expression in the <i>n<\/i> nested blocks.<\/p><p>Now consider that block indentation is proportional to <i>n<\/i>.  This means,\nthe file size of an indented C file is quadratic in the number of branch\ntargets of the <tt>br_table<\/tt>.<\/p><p>Yes, this actually bit me; there are <tt>br_table<\/tt> instances with tens of\nthousands of targets.  No, wastrel does not indent any more.<\/p><h3>scale, ter<\/h3><p>Right now, the long pole in Wastrel is the compile-to-C phase; the\nC-to-native phase parallelises very well and is less of an issue.  So,\none might think: OK, you have partitioned the functions in this Wasm\nmodule into a number of files, why not emit the files in parallel?<\/p><p>I gave this a go.  It did not speed up C generation.  From my cursory\ninvestigations, I think this is because the bottleneck is garbage\ncollection in Wastrel itself; Wastrel is written in Guile, and Guile\nstill uses the Boehm-Demers-Weiser collector, which does not parallelize\nwell for multiple mutators.  It\u2019s terrible but I ripped out\nparallelization and things are fine.  <a href=\"https:\/\/jorts.horse\/@migratory\/116279275172476524\">Someone on Mastodon suggested\n<tt>fork<\/tt><\/a>; they\u2019re not\nwrong, but also not Right either.  I\u2019ll just keep this as a nice test\ncase for the Guile-on-Whippet branch I want to poke later this year.<\/p><h3>scale, quator<\/h3><p>Finally, I had another realization: GCC was having trouble compiling the\nC that Wastrel emitted, because Hoot had emitted bad WebAssembly.  Not\nbad as in \u201cinvalid\u201d; rather, \u201cnot good\u201d.<\/p><p>There were two cases in which Hoot emitted ginormous (technical term)\nfunctions.  One, for an odd debugging feature: Hoot does a CPS transform\non its code, and allocates return continuations on a stack.  This is a\ngnarly technique but it gets us delimited continuations and all that\ngoodness even before stack switching has landed, so it\u2019s here for now.\nIt also gives us a reified return stack of <tt>funcref<\/tt> values, which lets\nus print Scheme-level backtraces.<\/p><p>Or it would, if we could associate data with a <tt>funcref<\/tt>.  Unfortunately\n<tt>func<\/tt> is not a subtype of <tt>eq<\/tt>, so we can\u2019t.  Unless... we pass the\nfuncref out to the embedder (e.g. JavaScript), and the embedder checks\nthe funcref for equality (e.g. using <tt>===<\/tt>); then we can map a funcref\nto an index, and use that index to map to other properties.<\/p><p>How to pass that <tt>funcref<\/tt>\/index map to the host?  When I initially\nwrote Hoot, I didn\u2019t want to just, you know, put the funcrefs of interet\ninto a table and let the index of a function\u2019s slot be the value in the\nkey-value mapping; that would be useless memory usage.  Instead, we\nemitted functions that took an integer, and which would return a\nfuncref.  Yes, these used <tt>br_table<\/tt>, and yes, there could be tens of\nthousands of cases, depending on what you were compiling.<\/p><p>Then to map the integer index to, say, a function name, likewise I\ndidn\u2019t want a table; that would force eager allocation of all strings.\nInstead I emitted a function with a <tt>br_table<\/tt> whose branches would\nreturn <tt>string.const<\/tt> values.<\/p><p>Except, of course, <a href=\"https:\/\/wingolog.org\/archives\/2023\/10\/19\/requiem-for-a-stringref\">stringref didn\u2019t become a\nthing<\/a>,\nand so instead we would end up lowering to allocate string constants as\nglobals.<\/p><p>Except, of course, Wasm\u2019s idea of what a \u201cconstant\u201d is is quite\nrestricted, so <a href=\"https:\/\/codeberg.org\/spritely\/hoot\/src\/branch\/main\/module\/wasm\/lower-globals.scm\">we have a pass that moves non-constant global\ninitializers to the \u201cstart\u201d\nfunction<\/a>.\nThis results in an enormous start function.  The straightforward\nsolution was to partition global initializations into separate\nfunctions, called by the start function.<\/p><p>For the <tt>funcref<\/tt> debugging, the solution was more intricate: firstly,\nwe represent the <tt>funcref<\/tt>-to-index mapping just as a table.  It\u2019s fine.\nThen for the side table mapping indices to function names and sources,\nwe emit DWARF, and attach a special attribute to each \u201cintrospectable\u201d\nfunction.  In this way, reading the DWARF sequentially, we reconstruct a\nmapping from index to DWARF entry, and thus to a byte range in the Wasm\ncode section, and thus to source information in the <tt>.debug_line<\/tt>\nsection.  It sounds gnarly but Guile already used DWARF as its own\ndebugging representation; switching to emit it in Hoot was not a huge\ndeal, and as we only need to consume the DWARF that we emit, we only\nneeded some <a href=\"https:\/\/codeberg.org\/spritely\/hoot\/src\/branch\/main\/reflect-js\/reflect.js#L3-L377\">400 lines of\nJS<\/a>\nfor the web\/node run-time support code.<\/p><p>This switch to data instead of code removed the last really long pole\nfrom the GCC part of Wastrel\u2019s pipeline.  What\u2019s more, Wastrel can now\nimplement the <tt>code_name<\/tt> and <tt>code_source<\/tt> imports for Hoot programs\nahead of time: it can parse the DWARF at compile-time, and generate\nfunctions that look up functions by address in a sorted array to return\ntheir names and source locations.  As of today, this works!<\/p><h3>fin<\/h3><p>There are still a few things that Hoot wants from a host that Wastrel\nhas stubbed out: weak refs and so on.  I\u2019ll get to this soon; my goal is\na proper Scheme REPL.  Today\u2019s note is a waypoint on the journey.  Until\nnext time, happy hacking!<\/p><\/div>                ","author":{"name":"Andy Wingo","uri":"https:\/\/wingolog.org\/"}},{"title":"Alex Bradbury: Minipost: Routing a Linux user's traffic through a WireGuard interface","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/muxup.com\/2026q1\/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface"}},"id":"https:\/\/muxup.com\/2026q1\/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface","updated":"2026-03-31T12:00:00+00:00","content":"\n<p>Simple goal: take advantage of my home router's WireGuard support and have one\nof my external servers connect using this, and pass all traffic from a certain\nuser through that interface.<\/p>\n<h2 id=\"create-wireguard-credentials\"><a href=\"https:\/\/muxup.com\/feed.xml#create-wireguard-credentials\" class=\"anchor\" tabindex=\"-1\"><\/a>Create WireGuard credentials<\/h2>\n<p>This part of the note won't be that useful to you unless you're using a\nFritzbox router. But if you're me or someone suspiciously like me you may want\nto know to:<\/p>\n<ul>\n<li>Navigate to <code>https:\/\/192.168.178.1\/#\/access\/wireguard<\/code><\/li>\n<li>Click \"Add WireGuard connection\" and ensure \"Connect a single device\" is\nselected on the modal that appears. Then click \"Next\".<\/li>\n<li>Enter a unique name for the connection (I typically use\n<code>$remote_host_name-wg<\/code>) and click Finish. Follow request to confirm by\npressing a button on the router.<\/li>\n<li>Click \"Download settings\" and a <code>wg_config.conf<\/code> will be downloaded.<\/li>\n<\/ul>\n<h2 id=\"add-user\"><a href=\"https:\/\/muxup.com\/feed.xml#add-user\" class=\"anchor\" tabindex=\"-1\"><\/a>Add user<\/h2>\n<div class=\"highlight\"><pre><span><\/span><code><span>VPN_USER=<\/span>asbvpn\nsudo useradd -m -g users -G wheel -s \/bin\/bash <span>&quot;<\/span><span>$VPN_USER<\/span><span>&quot;<\/span>\nsudo passwd <span>&quot;<\/span><span>$VPN_USER<\/span><span>&quot;<\/span>\n<\/code><\/pre><\/div>\n\n<h2 id=\"configure-systemd-networkd\"><a href=\"https:\/\/muxup.com\/feed.xml#configure-systemd-networkd\" class=\"anchor\" tabindex=\"-1\"><\/a>Configure systemd-networkd<\/h2>\n<p>First, extracting the relevant values from the <code>wg_config.conf<\/code>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span>WG_CONF=<\/span>wg_config.conf\n<span>PRIVATE_KEY=<\/span><span>&quot;<\/span><span>$(<\/span>sed -n <span>'s\/^PrivateKey = \/\/p'<\/span> <span>&quot;<\/span><span>$WG_CONF<\/span><span>&quot;<\/span><span>)<\/span><span>&quot;<\/span>\n<span>PUBLIC_KEY=<\/span><span>&quot;<\/span><span>$(<\/span>sed -n <span>'s\/^PublicKey = \/\/p'<\/span> <span>&quot;<\/span><span>$WG_CONF<\/span><span>&quot;<\/span><span>)<\/span><span>&quot;<\/span>\n<span>PRESHARED_KEY=<\/span><span>&quot;<\/span><span>$(<\/span>sed -n <span>'s\/^PresharedKey = \/\/p'<\/span> <span>&quot;<\/span><span>$WG_CONF<\/span><span>&quot;<\/span><span>)<\/span><span>&quot;<\/span>\n<span>ENDPOINT=<\/span><span>&quot;<\/span><span>$(<\/span>sed -n <span>'s\/^Endpoint = \/\/p'<\/span> <span>&quot;<\/span><span>$WG_CONF<\/span><span>&quot;<\/span><span>)<\/span><span>&quot;<\/span>\n\n<span>ADDRS=<\/span><span>&quot;<\/span><span>$(<\/span>sed -n <span>'s\/^Address = \/\/p'<\/span> <span>&quot;<\/span><span>$WG_CONF<\/span><span>&quot;<\/span><span>)<\/span><span>&quot;<\/span>\n<span>IPV4_ADDR=<\/span><span>&quot;<\/span><span>$(printf<\/span> <span>'%s\\n'<\/span> <span>&quot;<\/span><span>$ADDRS<\/span><span>&quot;<\/span> | cut -d, -f1<span>)<\/span><span>&quot;<\/span>\n<span>IPV6_ADDR=<\/span><span>&quot;<\/span><span>$(printf<\/span> <span>'%s\\n'<\/span> <span>&quot;<\/span><span>$ADDRS<\/span><span>&quot;<\/span> | cut -d, -f2<span>)<\/span><span>&quot;<\/span>\n<\/code><\/pre><\/div>\n\n<p>What we want to do is:<\/p>\n<ul>\n<li>Define the wireguard interface <code>wg0<\/code> and specify the necessary keys, IP\naddresses etc for it to be brought up successfully.<\/li>\n<li>Specify a routing policy so that all traffic from the given user account\ngoes via that interface.\n<ul>\n<li>As you can see below, we specify a RouteTable called \"vpn\", associate that\nwith the interface, and specify rules for that table.<\/li>\n<li>Ideally this would \"fail closed\" and no traffic from the user would be\nrouted if <code>wg0<\/code> is down. That appears to use additional rules managed\noutside of systemd-networkd. I haven't tried to implement this.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The above can be achieved with:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code>sudo mkdir -p \/etc\/systemd\/networkd.conf.d\nsudo tee \/etc\/systemd\/networkd.conf.d\/90-vpn-table.conf &gt;\/dev\/null <span>&lt;&lt;'EOF'<\/span>\n<span>[Network]<\/span>\n<span>RouteTable=vpn:100<\/span>\n<span>EOF<\/span>\n\nsudo tee \/etc\/systemd\/network\/50-wg0.netdev &gt;\/dev\/null <span>&lt;&lt;EOF<\/span>\n<span>[NetDev]<\/span>\n<span>Name=wg0<\/span>\n<span>Kind=wireguard<\/span>\n\n<span>[WireGuard]<\/span>\n<span>PrivateKey=$PRIVATE_KEY<\/span>\n<span>RouteTable=vpn<\/span>\n\n<span>[WireGuardPeer]<\/span>\n<span>PublicKey=$PUBLIC_KEY<\/span>\n<span>PresharedKey=$PRESHARED_KEY<\/span>\n<span>AllowedIPs=0.0.0.0\/0<\/span>\n<span>AllowedIPs=::\/0<\/span>\n<span>Endpoint=$ENDPOINT<\/span>\n<span>EOF<\/span>\n\nsudo tee \/etc\/systemd\/network\/50-wg0.network &gt;\/dev\/null <span>&lt;&lt;EOF<\/span>\n<span>[Match]<\/span>\n<span>Name=wg0<\/span>\n\n<span>[Network]<\/span>\n<span>Address=$IPV4_ADDR<\/span>\n<span>Address=$IPV6_ADDR<\/span>\n\n<span>[RoutingPolicyRule]<\/span>\n<span>User=$VPN_USER<\/span>\n<span>Table=vpn<\/span>\n<span>Priority=10000<\/span>\n<span>Family=both<\/span>\n<span>EOF<\/span>\n\nsudo chgrp systemd-network \/etc\/systemd\/network\/50-wg0.netdev\nsudo chmod <span>0640<\/span> \/etc\/systemd\/network\/50-wg0.netdev\nsudo systemctl restart systemd-networkd\n<\/code><\/pre><\/div>\n\n<p>Doing it this way, we've stored the secret keys in the 50-wg0.netdev file\nitself but restricted access to the file. It's possible to have the keys\nstored in a separate file, but for my setup it didn't seem worthwhile.<\/p>\n<p>Then check the status with e.g.:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code>sudo networkctl status wg0\nsudo ip rule show\nsudo ip route show table <span>100<\/span>\nsudo wg show wg0\nsudo -u <span>$VPN_USER<\/span> curl https:\/\/ifconfig.me\/all\n<\/code><\/pre><\/div>\n\n<p>IPv6 does not work in this setup (<code>curl -6 google.com<\/code> will fail),<\/p>\n<h2 id=\"copying-authorized_keys-to-new-user\"><a href=\"https:\/\/muxup.com\/feed.xml#copying-authorized_keys-to-new-user\" class=\"anchor\" tabindex=\"-1\"><\/a>Copying authorized_keys to new user<\/h2>\n<p>This is more a note to myself than anything else:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code>sudo install -d -m <span>700<\/span> -o <span>$VPN_USER<\/span> -g users \/home\/<span>$VPN_USER<\/span>\/.ssh\nsudo install -m <span>600<\/span> -o <span>$VPN_USER<\/span> -g users <span>&quot;<\/span><span>$HOME<\/span><span>\/.ssh\/authorized_keys&quot;<\/span> \/home\/<span>$VPN_USER<\/span>\/.ssh\/authorized_keys\n<\/code><\/pre><\/div>\n\n\n<hr \/><a href=\"https:\/\/muxup.com\/feed.xml#article-changelog\" class=\"anchor\" tabindex=\"-1\"><\/a>Article changelog\n<ul>\n<li>2026-03-31: Initial publication date.<\/li>\n<\/ul>                ","author":{"name":"Alex Bradbury","uri":"https:\/\/muxup.com"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #61","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-61\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-61\/","updated":"2026-03-30T21:46:57+00:00","content":"\n<p>Update on what happened in WebKit in the week from March 23 to March 30.<\/p>\n<p>\nThis week comes with a mixed bag of new features, incremental improvements,\nand a new release with the ever important security issue fixes. Also: more\nblog posts!\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/309558@main\">Implemented<\/a> initial support for\n<code>closedby=any<\/code> on dialog elements, which adds light dismiss behaviour. This is\nbehind the <code>ClosedbyAttributeEnabled<\/code> feature flag.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/309802@main\">Added<\/a> the remaining values for the\nexperimental <code>closedby<\/code> attribute implementation.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310070@main\">MiniBrowser now has<\/a> a\n<code>--profile-dir=DIR<\/code> command line option that can be used to specify a custom\ndirectory where website data and cache can be stored, to test, for example,\nbehavior in a clean session.<\/p>\n  <\/div>\n<h3 id=\"multimedia-movie-camera\">Multimedia \ud83c\udfa5<\/h3>\n<div class=\"wip-description\">\n<p>GStreamer-based multimedia support for WebKit, including (but not limited to)\nplayback, capture, WebAudio, WebCodecs, and WebRTC.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>Video decoding limits had been enforced on <code>HTMLMediaElement.canPlayType()<\/code> so\nfar, but <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=310192\">they are now also enforced in MediaCapabilities\nqueries<\/a>.<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310220@main\">Fixed<\/a> several OpenGL state\nrestoration bugs in <code>BitmapTexture<\/code> . These could cause a mismatch between the\nGL state assumed by Skia and the actual one, leading to rendering artifacts\nwith certain GPU drivers and configurations.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>The <code>SKIA_DEBUG<\/code> CMake option <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/310215@main\">has been\nenabled<\/a> for <code>Debug<\/code> builds, enabling\nSkia's internal assertions, debug logging, and consistency checks (e.g. bounds\nchecking, resource key diagnostics). It remains off by default for <code>Release<\/code>\nand <code>RelWithDebInfo<\/code> builds, and can still be explicitly configured via\n<code>-DSKIA_DEBUG=ON|OFF<\/code>.<\/p>\n  <\/div>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n<h3 id=\"wpe-platform-api-jigsaw\">WPE Platform API \ud83e\udde9<\/h3>\n<div class=\"wip-description\">\n<p>New, modern platform API that supersedes usage of libwpe and WPE backends.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>The new <code>WPE_SETTING_OVERLAY_SCROLLBARS<\/code> setting <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/309811@main\">is now\navailable<\/a>, and disabling it will use a\nmore traditional, always visible scrollbar style.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p>A new <code>USE_GSTREAMER<\/code> build option <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/309883@main\">may now be\nused<\/a> to toggle the features that\nrequire GStreamer at once. This can be used to effectively disable all\nmultimedia support, which previously needed toggling four CMake options.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/03\/27\/webkitgtk2.52.1-released.html\">WebKitGTK\n2.52.1<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.52.1.html\">WPE WebKit 2.52.1<\/a> have\nbeen released. On top of a small corrections typical of the first point\nreleases in a new stable series, this one includes a number of fixes for\nsecurity issues, and it is a recommended update. The corresponding security\nadvisory, <code>WSA-2026-0002<\/code>\n(<a rel=\"external\" href=\"https:\/\/webkitgtk.org\/security\/WSA-2026-0002.html\">GTK<\/a>,\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/security\/WSA-2026-0002.html\">WPE<\/a>) has been published as\nwell.<\/p>\n  <\/div>\n<h2 id=\"community-events-handshake\">Community &amp; Events \ud83e\udd1d<\/h2>\n  <div class=\"wip-item\">\n<p>Sim\u00f3n Pena wrote a blog post showing <a rel=\"external\" href=\"https:\/\/simonpena.com\/blog\/2026\/03\/20\/getting-started-with-wpe-webkit\/\">how to create a minimal WPE\nlauncher<\/a>,\nwhich uses a Fedora Podman container with pre-built WPE WebKit libraries and a\nlauncher with barely 10 lines of code to display a web view. This complements\nKate Lee's <a rel=\"external\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">custom HTML context menu blog\npost<\/a>\nfrom last week.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Ricardo Ca\u00f1uelo Navarro: Why don't we do a demo? Part 2: software development","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/index.html"}},"id":"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/index.html","updated":"2026-03-30T11:00:00+00:00","content":"\n<p>In <a href=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260317-why_dont_we_do_a_demo_part_1\/index.html\">part\n          1<\/a> of this series I talked about the beginning of this\n          story and laid out the plan. In this post we'll start the\n          actual work, beginning with the software part.<\/p>\n\n        <h3>Problem 5: base peripheral device<\/h3>\n\n        <p>I'll start with the most basic device: the peripheral. It\n          will provide a simple BLE service to allow toggling the board\n          LED remotely and displaying its current status.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>The <a href=\"https:\/\/github.com\/zephyrproject-rtos\/zephyr\/blob\/main\/samples\/bluetooth\/peripheral\/src\/main.c\">Zephyr samples<\/a> are a good starting point for the firmware\n          skeleton. The XIAO nRF54L15 is also well supported in Zephyr,\n          so defining a custom BLE service and operating the on-board\n          LED is not a challenge. A minimal sketch firmware with the\n          basic functionality can be done reasonably quickly starting\n          from scratch. To test the BLE service we can use a smartphone\n          and <a href=\"https:\/\/www.nordicsemi.com\/Products\/Development-tools\/nRF-Connect-for-mobile\">nRF\n          Connect for Mobile<\/a>.<\/p>\n\n        <p>I probably don't need to go all the trouble of doing a custom\n          BLE service and characteristic for this, but it's an exercise\n          I'll need to do at some point, and it has the added bonus of\n          giving us full freedom to define the functionalities we\n          want.<\/p>\n\n        <p>For the BLE services and characteristics, I picked up a\n          random\n          128-bit <div class=\"tooltip\">UUID<span class=\"tooltiptext\">Universally\n          Unique Identifier<\/span><\/div> generated\n          with <a href=\"https:\/\/www.uuidgenerator.net\/version4\">https:\/\/www.uuidgenerator.net\/version4<\/a>.<\/p>\n\n        The BLE-related boilerplate code for the basic functionality\n        uses the appropriate macros to define the GATT service and\n        characteristics:\n\n        <pre><code>\/* LED service UUID: 46239800-1bed5-4c51-a215-9251faaae809 *\/\n#define LED_SERVICE_UUID_VAL \\\n\tBT_UUID_128_ENCODE(0x46239800, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809)\n\nstatic struct bt_uuid_128 led_svc_uuid =\n\tBT_UUID_INIT_128(LED_SERVICE_UUID_VAL);\n\n\/* Characteristic UUID: 46239801-1bed5-4c51-a215-9251faaae809 *\/\nstatic struct bt_uuid_128 led_char_uuid = BT_UUID_INIT_128(\n\tBT_UUID_128_ENCODE(0x46239801, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809));\n\n\/* Characteristic UUID: 46239802-1bed5-4c51-a215-9251faaae809 *\/\nstatic struct bt_uuid_128 led_indication_char_uuid = BT_UUID_INIT_128(\n\tBT_UUID_128_ENCODE(0x46239802, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809));\n\n[...]\n\nBT_GATT_SERVICE_DEFINE(led_svc,\n\tBT_GATT_PRIMARY_SERVICE(&amp;led_svc_uuid),\n\tBT_GATT_CHARACTERISTIC(&amp;led_char_uuid.uuid,\n\t\t\tBT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,\n\t\t\tBT_GATT_PERM_READ | BT_GATT_PERM_WRITE,\n\t\t\tread_led_state, write_led_state, &amp;led_state),\n\tBT_GATT_CHARACTERISTIC(&amp;led_indication_char_uuid.uuid,\n\t\t\tBT_GATT_CHRC_INDICATE,\n\t\t\tBT_GATT_PERM_READ | BT_GATT_PERM_WRITE,\n\t\t\tNULL, NULL, NULL),\n\tBT_GATT_CCC(led_ccc_changed,\n\t\tBT_GATT_PERM_READ | BT_GATT_PERM_WRITE),\n);<\/code><\/pre>\n\n        Where the <code>read_led_state<\/code>, <code>write_led_state<\/code>\n        and <code>led_ccc_changed<\/code> callbacks look something like\n        this:\n\n        <pre><code>\/*\n * LED state characteristic read callback.\n *\/\nstatic ssize_t read_led_state(struct bt_conn *conn,\n                             const struct bt_gatt_attr *attr, void *buf,\n                             uint16_t len, uint16_t offset) {\n\tconst uint8_t *val = attr-&gt;user_data;\n\treturn bt_gatt_attr_read(conn, attr, buf, len, offset, val,\n\t\t\t\tsizeof(*val));\n}\n\n\/*\n * LED state characteristic write callback.\n * A write to this characteristic will trigger a LED toggle, the data\n * sent is irrelevant so we can just ignore it.\n *\/\nstatic ssize_t write_led_state(struct bt_conn *conn,\n                              const struct bt_gatt_attr *attr, const void *buf,\n                              uint16_t len, uint16_t offset, uint8_t flags) {\n\tARG_UNUSED(conn);\n\tARG_UNUSED(attr);\n\tARG_UNUSED(buf);\n\tARG_UNUSED(offset);\n\tARG_UNUSED(flags);\n\n\t\/*\n\t * Ignore received data (dummy): *((uint8_t *)buf)\n\t * and override (toggle) the led_state here as a side-effect.\n\t *\/\n\tLOG_DBG(\"LED toggle received: %d -&gt; %d\", led_state, led_state ? 0 : 1);\n\tled_state = led_state ? 0 : 1;\n\tgpio_pin_set_dt(&amp;led, led_state);\n\tgpio_pin_set_dt(&amp;led_board, led_state);\n\tif (led_indication_enabled)\n\t\tk_work_schedule(&amp;led_indicate_work, K_NO_WAIT);\n\n\treturn len;\n}\n\n\/*\n * LED indication Client Characteristic Configuration callback.\n *\/\nstatic void led_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)\n{\n\tARG_UNUSED(attr);\n\n\tled_indication_enabled = (value == BT_GATT_CCC_INDICATE);\n\tLOG_DBG(\"Indication %s\", led_indication_enabled ? \"enabled\" : \"disabled\");\n}<\/code><\/pre>\n\n        <p>This should be good enough for now, we'll surely need to\n        complicate it later.<\/p>\n\n\n        <h3>Problem 6: unexpected LED behavior<\/h3>\n\n        <p>The user LED in the XIAO nRF54L15 turns off\n          with <a href=\"https:\/\/docs.zephyrproject.org\/latest\/doxygen\/html\/group__gpio__interface.html#ga541064fb9e575c0c559c101754466fa8\"><code>gpio_pin_set_dt(&amp;led,\n          1)<\/code><\/a> and on with <code>gpio_pin_set_dt(&amp;led,\n          0)<\/code>. Not a problem if we only want to toggle it instead\n          of setting a specific value, but not ideal, since we also want\n          to keep track of its current state and report it.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>This one's easy. According to the\n          <a href=\"https:\/\/files.seeedstudio.com\/wiki\/XIAO_nRF54L15\/Getting_Start\/nRF54L15_Sense_Schematic.pdf\">schematic<\/a>,\n          this LED is active low, but\n          the <a href=\"https:\/\/github.com\/zephyrproject-rtos\/zephyr\/blob\/main\/boards\/seeed\/xiao_nrf54l15\/xiao_nrf54l15_common.dtsi\">device\n          tree for this SoC<\/a> defines it as active\n          high. <a href=\"https:\/\/github.com\/zephyrproject-rtos\/zephyr\/pull\/97808\/changes\">Fixed\n          and upstreamed<\/a>.<\/p>\n\n        <h3>Problem 7: modeling the behavior of the central device<\/h3>\n\n        <p>In the BLE central-peripheral architecture proposed, the\n          peripheral will work as an autonomous device that provides a\n          service but does no other action except when requested by the\n          user through a button press. Other than that, it'll sit there\n          waiting for requests from the central (the controller device\n          in our case), which will be the one governing the bulk of the\n          application and, more importantly, managing the connection and\n          doing the necessary actions to establish and monitor it.<\/p>\n\n        <p>Some of the tasks under the responsibility of the controller\n          are:<\/p>\n\n        <ul>\n          <li>\n            Scanning for peripherals.\n          <\/li>\n          <li>\n            Connecting to peripherals.\n          <\/li>\n          <li>\n            Service discovery.\n          <\/li>\n          <li>\n            Keep track of the connected devices.\n          <\/li>\n          <li>\n            Handle disconnection requests and lost connections.\n          <\/li>\n        <\/ul>\n\n        <p>We need a way to model this behavior into the controller so\n          we can integrate these tasks with the rest of the firmware\n          gracefully.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>I'll abstract the list of tasks above in a simple state\n          machine that will run in a separate thread taking care of\n          handling the connections, running the necessary actions as\n          response to specific events, interacting with the rest of the\n          firmware and reacting to the actions triggered by the user via\n          the board buttons or by external sources.<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/sm.png\" \/>\n        <\/div>\n\n        <p>That way, the main thread will set up the hardware and the\n          necessary software subsystems, and the state machine will keep\n          track of most of the BLE-related tasks and of the connected\n          devices.<\/p>\n\n        <p>So, when the initialization is done, the main thread will\n          start the state machine thread and then wait for events such\n          as button presses, managing and restarting common services,\n          while the state machine works on its own.<\/p>\n\n        <p>For our purposes we'll only need three states:<\/p>\n\n        <ul>\n          <li>\n            <b>Event listen<\/b>: the device waits for events from the\n            user or from external sources. In the most basic scenario,\n            it waits for a \"scan\" request, which will make the machine\n            move to the \"Scan\" state.\n          <\/li>\n          <li>\n            <b>Scan<\/b>: this state handles device scanning and\n            connection. Once connected to a suitable device, the state\n            machine will move to the \"Discover\" state. If no connection\n            is done after a period of time, the machine will go back to\n            the \"Event listen\" state.\n          <\/li>\n          <li>\n            <b>Discover<\/b>: here, the firmware will run the discovery\n            process for a connected peripheral, looking for a specific\n            set of services and characteristics. If the process is\n            successful, the controller will save the necessary data\n            about the peripheral for later use and move to the \"Event\n            listen\" state.<\/li>\n        <\/ul>\n\n        <p>I can reuse most of this architecture as the basis for the\n          console device as well, since it'll be a central device to the\n          controllers (remember the controllers are both central and\n          peripheral BLE devices at the same time), so I can start\n          sketching the console firmware as well as a generic central\n          device.<\/p>\n\n        <h3>Problem 8: designing the UX for the controller device<\/h3>\n\n        <p>We need a way for the controller to interact with the\n          connected peripherals, and in the controller boards (nRF54L15\n          DK) we have as user-facing devices four LEDs and four\n          buttons. The operations we'll need to perform are:<\/p>\n\n        <ul>\n          <li>\n            Scan for peripherals.\n          <\/li>\n          <li>\n            Disconnect from a connected peripheral.\n          <\/li>\n          <li>\n            Toggle the LED of a connected peripheral.\n          <\/li>\n          <li>\n            Check the status of the peripherals.\n          <\/li>\n        <\/ul>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/nrf54l15dk_leds_buttons.jpg\" \/>\n        <\/div>\n\n        <h3>Solution<\/h3>\n\n        <p>The most useful thing we could do with the board LEDs is to\n          replicate the status of the peripheral LEDs. That way we could\n          have a real-time overview of the state of the connected\n          peripherals at all times.<\/p>\n\n        <p>The downside of this is that the board only has four LEDs, so\n          if I want to show the status of the connected peripherals at a\n          glance, I'm limited to four of them. And it'd be good to keep\n          one LED to show the status of the controller itself, so lets\n          start by limiting the amount of simultaneously connected\n          peripherals to three.<\/p>\n\n        <p>Now, about the buttons, I'm going to need a way to perform at\n          least three actions: scanning, disconnecting and toggling, and\n          I'll probably need to make room for additional actions down\n          the road.<\/p>\n\n        <p>One option is to assign one button to each peripheral \"slot\",\n          so I could use button 0 to perform an action on slot 0, button\n          1 for slot 1, etc. In this case, I'd need to encode multiple\n          actions on the same button: scanning and toggling at least.<\/p>\n\n        <p>A different approach is to use one or two buttons to select\n          the active slot, and then the action buttons would operate on\n          the selected slot. I feel like this method could be easier to\n          adapt in case I need to add additional functionalities later,\n          so this is what I'll do:<\/p>\n\n        <ul>\n          <li>\n            Button 0: select the next slot as the \"active slot\".\n          <\/li>\n          <li>\n            Button 1: \"action button\", trigger an action on the\n            peripheral connected in the active slot. For now, the action\n            will be to toggle the LED.\n          <\/li>\n          <li>\n            Button 2: select the previous slot as the \"active slot\".\n          <\/li>\n          <li>\n            Button 3: disconnect the peripheral in the active slot, if\n            any, and start scanning on it.\n          <\/li>\n        <\/ul>\n\n        <p>I'll also need a way to tell which one is the selected\n          slot. Since I'm using the LEDs to represent the slots, an easy\n          way to do this is by briefly blink the LED of the currently\n          active slot when we use buttons 0 or 2 to cycle through the\n          slots. Additionally, I can use the same method to encode\n          whether the slot contains a connected peripheral or not, since\n          I'm using a static LED to show the status of the peripheral\n          LED (i.e. we can't tell from a LED that's off if the connected\n          peripheral has its LED off or of there's no peripheral\n          connected at all): when cycling through the slots selecting\n          the active one, the LED can do a short blink cycle to\n          represent a disconnected slot and a long blink cycle to\n          represent a connected one.<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <video width=\"320\" controls=\"controls\" height=\"240\">\n            <source src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/slot_cycle.webm\" type=\"video\/webm\"><\/source>\n          <\/video>\n        <\/div>\n\n        <h3>Problem 9: simulation and testing<\/h3>\n\n        <p>During development, it's very inconvenient to run all the\n          firmware changes we do on real hardware, even if these boards\n          can be flashed very fast. And for debugging and testing,\n          relying on the hardware is overkill most of the time, even if\n          we have direct access to a serial console and we have plenty\n          of tracing possibilities. I'd need a better way to test our\n          changes.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>Fortunately, Zephyr includes\n          a <a href=\"https:\/\/docs.zephyrproject.org\/latest\/boards\/native\/native_sim\/doc\/index.html\">native\n          simulator<\/a> that allows to build a firmware as a native\n          binary that I can run on the development machine using\n          emulated devices. For my purposes, the\n          <a href=\"https:\/\/docs.zephyrproject.org\/latest\/boards\/native\/doc\/bsim_boards_design.html#bsim-boards\">native\n          bsim boards<\/a> even let me simulate the specific SoC used in\n          the boards, including most of the SoC hardware, and run the\n          firmware natively in\n          <a href=\"https:\/\/babblesim.github.io\/\">BabbleSim<\/a> to\n          simulate real BLE usage.<\/p>\n\n        <p>This offers many advantages over testing on hardware:<\/p>\n\n        <ul>\n          <li>\n            Faster development cycles.\n          <\/li>\n          <li>\n            Easier debugging of runtime errors.\n          <\/li>\n          <li>\n            Triggering of specific corner cases programmatically.\n          <\/li>\n        <\/ul>\n\n        <p>Ideally, what I'd like is to configure the environment so\n          that I can selectively build and test the firmware on the\n          simulator, or build a release firmware for the real\n          hardware. A way to do this is to keep two separate project\n          config files, create the necessary device tree overlay files\n          for the different target boards (real and simulated) and\n          compile certain parts of the firmware conditionally, so that I\n          can enable test code and emulated devices only on the\n          simulator build and I can keep hardware-dependent code only\n          for the release build:<\/p>\n\n        <pre><code>\u251c\u2500\u2500 boards\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 nrf52_bsim.conf\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 nrf52_bsim.overlay\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 nrf54l15bsim_nrf54l15_cpuapp.conf\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 nrf54l15bsim_nrf54l15_cpuapp.overlay\n\u251c\u2500\u2500 build.sh\n\u251c\u2500\u2500 CMakeLists.txt\n\u251c\u2500\u2500 flash.sh\n\u251c\u2500\u2500 Kconfig\n\u251c\u2500\u2500 prj.conf\n\u251c\u2500\u2500 prj_sim.conf\n\u251c\u2500\u2500 sim_bin\n\u251c\u2500\u2500 sim_build.sh\n\u251c\u2500\u2500 sim_run.sh\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 common.h\n    \u251c\u2500\u2500 emul.c\n    \u251c\u2500\u2500 emul.h\n    \u251c\u2500\u2500 main.c\n    \u251c\u2500\u2500 peripheral_mgmt.c\n    \u251c\u2500\u2500 peripheral_mgmt.h\n    \u251c\u2500\u2500 sim_test.c\n    \u251c\u2500\u2500 sm.c\n    \u2514\u2500\u2500 sm.h<\/code><\/pre>\n\n        <p>Code compiled conditionally for the simulator looks like\n        this:<\/p>\n\n        <pre><code>[...]\nint main(void)\n{\n\tstatic struct gpio_callback button_cb_data;\n\tint log_sources = log_src_cnt_get(0);\n\tint ret;\n\tint i;\n\n#ifdef CONFIG_BOARD_NRF52_BSIM\n\t\/* Set all logging to INFO level by default *\/\n\tfor (i = 0; i &lt; log_sources; i++) {\n\t\tlog_filter_set(NULL, 0, i, LOG_LEVEL_INF);\n\t}\n\tint id = log_source_id_get(\"controller__main\");\n\tlog_filter_set(NULL, 0, id, LOG_LEVEL_DBG);\n#else\n\t\/* Disable all logging by default *\/\n\tfor (i = 0; i &lt; log_sources; i++) {\n\t\tlog_filter_set(NULL, 0, i, LOG_LEVEL_NONE);\n\t}\n#endif<\/code><\/pre>\n\n            \n        \n\n        \n        <p>From now on, I can do most of the development on the\n          simulator, and once things are the way I want I can test them\n          on the real hardware.<\/p>\n\n        <h3>Problem 10: battery-powered peripheral setup<\/h3>\n\n        <p>While the peripheral devices can be powered via USB, just the\n          same as the bigger boards, the demo would be both more\n          realistic and more diverse if we used batteries for them. The\n          XIAO nRF54L15 is prepared for that and\n          has <a href=\"https:\/\/wiki.seeedstudio.com\/xiao_nrf54l15_sense_getting_started\/#battery-powered-board\">battery\n          pads<\/a> and the necessary hardware to manage a LiPo\n          battery. I need to provide the batteries and add the\n          appropriate battery leads to the boards, though.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>Any suitable LiPo battery will do, but I'll search for\n          batteries with an appropriate dimensions and capacity for this\n          application.<\/p>\n\n        <p>I found this bundle containing five batteries and a charger,\n          which should be good enough for our purposes: we can have up\n          to 5 battery-powered peripherals and a convenient way to\n          recharge the batteries if they're easy to detach from the\n          devices.<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/batteries_charger.jpg\" \/>\n        <\/div>\n\n        <p>The battery connectors are Molex 51005, so I'll also need to\n          source a bunch of male and female leads. The pads are big\n          enough to solder the leads to them with a conventional pen\n          solder:<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260330-why_dont_we_do_a_demo_part_2\/peripheral_and_battery.jpg\" \/>\n        <\/div>\n\n        <h3>Problem 11: hardware unreliability<\/h3>\n\n        <p>The XIAO nRF54L15 seems very flaky. In particular, after\n          flashing it sometimes the device crashes and Zephyr reports a\n          bus data error in the serial console. It seems to be random,\n          it happens only after flashing some builds and it also seems\n          to depend on timing.<\/p>\n\n        <p>Even worse, when battery-powered, the board won't boot. When\n          powered via USB, though, it will boot, and then I can plug in\n          the battery, unplug the USB cable and the board will keep on\n          running.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>After some investigation and tests, it looks like the crashes\n          are related to the logging through the UART console. Why, I\n          don't know. The kind of crashes I'm seeing right during\n          booting are bus faults, and the first things I'd check for are\n          null pointer dereferences and stack overflows, but in this\n          case I'm not even getting a valid PC in the error\n          report. Besides, there are a few signs that this will be hard\n          to pinpoint:<\/p>\n\n        <ul>\n          <li>\n            Altering the logging does cause different results.\n          <\/li>\n          <li>\n            Different builds and flashings of the same firmware\n            sometimes crash and sometimes don't.\n          <\/li>\n          <li>\n            It doesn't seem related to the size of the logging stack.\n          <\/li>\n          <li>\n            Deferred vs immediate logging causes different results.\n          <\/li>\n          <li>\n            It doesn't fail on the simulator.\n          <\/li>\n          <li>\n            It seems related to timing.\n          <\/li>\n          <li>\n            There's a big randomness factor.\n          <\/li>\n          <li>\n            The same firmware on the same SoC but on a different board\n            design (nRF54L15 DK) works fine.\n          <\/li>\n        <\/ul>\n\n        <p>All of these hint that there's some flakiness involved in the\n          XIAO nRF54L15, particularly related to either power\n          management, flashing or the use of the builtin USB for UART\n          output.<\/p>\n\n        <p>Judging by some issues raised in\n          the <a href=\"https:\/\/forum.seeedstudio.com\/\">Seeed Studio\n          forums<\/a>, it looks like the USB-based SWD circuitry could be\n          the cause of these problems. Regarding the problems booting\n          when battery-powered, after asking about it in the forums, I\n          got a\n          <a href=\"https:\/\/forum.seeedstudio.com\/t\/xiao-nrf54l15-with-a-coin-battery\/294019\/33?u=rcn\">response<\/a>\n          explaining the reason: when logging is enabled, the TX line\n          back-feeds and powers up the USB-UART chip, causing a brownout\n          and a shutdown\/reboot.<\/p>\n\n        <p>The most reasonable fix or workaround for all of this is to\n          simply disable all logging and UART usage when the board is\n          battery-powered<a href=\"https:\/\/blogs.igalia.com\/rcn\/feed.xml#fn1\" id=\"ref1\"><sup>1<\/sup><\/a>. In\n          order to do this, I created another build type that will be\n          used for \"production\" releases. For the non-production builds\n          (the ones I'll use for development and debugging) I'll keep\n          logging disabled with the possibility of enabling it through\n          shell commands. That'll reduce the chances of crashing the\n          system at boot time.<\/p>\n\n        <h3>Problem 12: network connectivity in the console device<\/h3>\n\n        <p>We can take advantage of the builtin web server capabilities\n          provided by Zephyr for the console board. Since it'll be\n          governing the application and monitoring \/ controlling the\n          connected devices, we'll need a user interface to manage\n          it. Implementing it in the form of a web interface should be\n          easy enough, and it'd give us a lot of freedom to design the\n          interface. The idea would be to connect the console board to a\n          client (a laptop, for instance) using a point-to-point\n          Ethernet link and have the client access the web page served\n          by the console board.<\/p>\n\n        <p>The problem is that the board doesn't have an Ethernet\n        interface.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>Everything's not lost, though. The board doesn't have an\n          Ethernet interface but it has a general USB interface besides\n          the one used for flashing and debugging. And, fortunately, the\n          USB stack in Zephyr\n          supports <a href=\"https:\/\/github.com\/zephyrproject-rtos\/zephyr\/blob\/main\/subsys\/usb\/device_next\/class\/usbd_cdc_ncm.c\">USB\n          CDC NCM<\/a> (Ethernet-over-USB) and we even have an\n          <a href=\"https:\/\/docs.zephyrproject.org\/latest\/samples\/net\/sockets\/http_server\/README.html\">example<\/a>\n          of the web server running on the same board we're using for\n          the console device, so setting it up shouldn't be too much of\n          an issue.<\/p>\n\n        <p>I can run the sample code on the board and check that it\n          works, I can connect to it and see the web page published by\n          the web server. Integrating the basic code into our sketchy\n          console firmware is mostly painless, although I'm publishing\n          only a placeholder web page. For now, that's good enough. I'll\n          see what we can do with it later.<\/p>\n\n        <p>In the next post we'll continue through the rest of the\n          software development part of the project.<\/p>\n\n        <div class=\"footnotes\">\n          <p id=\"fn1\">1: This is now <a href=\"https:\/\/github.com\/Seeed-Studio\/wiki-documents\/commit\/e94991dad122407c8df5a7d6c045625e7b7d1a56\">documented<\/a> in the <a href=\"https:\/\/wiki.seeedstudio.com\/xiao_nrf54l15_sense_getting_started\/#battery-voltage-detection\">Seeed Studio wiki<\/a><a href=\"https:\/\/blogs.igalia.com\/rcn\/feed.xml#ref1\">\u21a9<\/a><\/p>\n        <\/div>                ","author":{"name":"rcn","uri":"https:\/\/blogs.igalia.com\/rcn"}},{"title":"Qiuyi Zhang (Joyee): Teaching gdb to Unwind V8 JIT Frames on x64","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2026\/03\/24\/teaching-gdb-to-unwind-v8-jit-frames-on-x64\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2026\/03\/24\/teaching-gdb-to-unwind-v8-jit-frames-on-x64\/","updated":"2026-03-26T16:20:20+00:00","content":"\n<p>Recently I landed a <a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/chromium-review.googlesource.com\/c\/v8\/v8\/+\/7543454\">custom Python gdb unwinder in V8<\/a> that allows gdb to unwind through V8\u2019s JIT-compiled frames on x64<\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}},{"title":"Jos\u00e9 Dapena: The implementation of Container Timing: aggregating paints in Blink","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/"}},"id":"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/","updated":"2026-03-26T00:00:00+00:00","content":"\n                <img class=\"face\" src=\"\/images\/dape.png\" width=\"74\" height=\"100\" alt=\"\" align=\"right\" style=\"float: right\" \/>\n<p>Measuring paint performance is a balancing act: you need precision, but the measurement itself can\u2019t slow things down.<\/p>\n<p>In my <a href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">previous post<\/a>, I introduced <strong>Container Timing<\/strong>, a new web API allowing developers to measure the rendering performance of DOM subtrees. Today, I will dive into the technical details of how I implemented this in <strong>Blink<\/strong>, the rendering engine used by Chromium.<\/p>\n<h2 id=\"the-architecture-hooking-into-paint\" tabindex=\"-1\">The Architecture: Hooking into Paint <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>In Blink, the rendering pipeline goes through several stages: Style, Layout, Paint, and Composite. The Container Timing implementation relies heavily on the <strong>Paint<\/strong> stage.<\/p>\n<p>The main idea was <strong>not reinventing the wheel<\/strong>. Blink already provides paint timing detection for the implementation of Large Contentful Paint (LCP) and Element Timing. However, this is targeted for <em>specific<\/em> nodes (an image, a text block). In Container Timing we care about <em>subtrees<\/em>.<\/p>\n<p>So, when a paint is detected, we need to quickly decide whether the paint  is relevant to Container Timing.<\/p>\n<h2 id=\"is-a-paint-interesting-for-container-timing\" tabindex=\"-1\">Is a paint interesting for Container Timing? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>As the DOM tree is built (on parsing, or because of a script), we check the value of the attribute <code>containertiming<\/code> for each <code>Element<\/code>. When found, we flag that element and all its descendants with the flag <code>SelfOrAncestorHasContainerTiming<\/code>.<\/p>\n<p>We also have the attribute <code>containertiming-ignore<\/code>. When found, we will stop the propagation.<\/p>\n<p>So, later, for any paint, we will immediately know if the paint should be tracked for Container Timing or not. This minimizes the impact when the element is not tracked.<\/p>\n<div class=\"markdown-alert markdown-alert-important\"><p class=\"markdown-alert-title\">What about DOM tree updates after parsing?<\/p><p>This is a pain point for performance. When a DOM element starts\/stops having the <code>containertiming<\/code> or <code>containertiming-ignore<\/code> attribute after the DOM tree is created, we need to traverse the tree to update the flag.<\/p>\n<\/div>\n<h2 id=\"collecting-paint-updates\" tabindex=\"-1\">Collecting Paint Updates <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>When a paint is detected, we just reuse the existing implementation in the <code>ImagePaintTimingDetector<\/code> and <code>TextPaintTimingDetector<\/code>, that are also used for LCP and Element Timing for the relevant elements.<\/p>\n<div class=\"markdown-alert markdown-alert-note\"><p class=\"markdown-alert-title\">Note<\/p><p>Only text and image paints are currently tracked. Video, canvas, and SVG are not yet supported.<\/p>\n<\/div>\n<p>We first determine if the paint should be recorded for Container Timing. And this is fast because of the <code>SelfOrAncestorHasContainerTiming<\/code> flag.<\/p>\n<p>The timing detectors give us the area of the visual rectangle, the bounding box on screen that was painted.<\/p>\n<p>For Container Timing, we added a mechanism to walk up the DOM tree from the painted node. If we encounter an ancestor that is marked with the <code>containertiming<\/code> attribute (a <strong>container timing root<\/strong>), we report that paint event to it.<\/p>\n<p>This \u201cbubbling up\u201d of paint events is illustrated in the diagram below.<\/p>\n<p><img src=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/images\/propagation-to-ancestor.png\" alt=\"Within the Blink rendering pipeline, paint events from individual text and image nodes are captured by the paint timing detectors and then \" \/><\/p>\n<div class=\"markdown-alert markdown-alert-tip\"><p class=\"markdown-alert-title\">Is this expensive?<\/p><p>It depends on the depth of the hierarchy from the node to the most remote ancestor. Further work will be needed to speed up or avoid these traversals.<\/p>\n<\/div>\n<h2 id=\"aggregating-regions\" tabindex=\"-1\">Aggregating Regions <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>One of the most interesting challenges was determining the <code>size<\/code> of the container. It is not just the size of the <em>container timing root<\/em>. It is the <strong>union of all painted content<\/strong>.<\/p>\n<p>Two reasons for this:<\/p>\n<ul>\n<li>Being able to incrementally determine the updated area, in a way that is inspired by Largest Contentful Paint.<\/li>\n<li>To reduce the amount of performance events generated, we <strong>discard<\/strong> the paints that do not increase the area.<\/li>\n<\/ul>\n<p>We maintain a <code>PaintedRegion<\/code> for each container. This is a non-overlapping union of the rectangles that cover the updated area:<\/p>\n<ol>\n<li><strong>Initial Paint:<\/strong> When the first child paints, we initialize the region with its visual rectangle.<\/li>\n<li><strong>Subsequent Paints:<\/strong> As more images load or text renders, we perform a union operation: <code>CurrentRegion = Union(CurrentRegion, NewPaintRect)<\/code>.<\/li>\n<\/ol>\n<p>So, as paints are detected, each container will aggregate the parts of the screen that have been painted by all their children.<\/p>\n<p>We use <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/HEAD\/cc\/base\/region.h\"><code>cc::Region<\/code><\/a>, based on <a href=\"https:\/\/api.skia.org\/classSkRegion.html\"><code>SkRegion<\/code><\/a> from the Skia graphics library to handle these unions efficiently.<\/p>\n<p>The following diagram shows this process in action over three frames.<\/p>\n<p><img src=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/images\/painted-regions.png\" alt=\"The  of a container is the union of the painted areas of its children. As new content paints, the region grows to encompass all visible parts of the container's subtree.\" class=\"dark-invert\" \/><\/p>\n<h2 id=\"buffering-and-reporting\" tabindex=\"-1\">Buffering and Reporting <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>Because a container paints over multiple frames (e.g., text renders first, then a background image, then a lazy-loaded icon), we cannot just emit one entry. We generate <strong>candidates<\/strong>.<\/p>\n<p>For each container, when a paint that increases the painted region is detected, we schedule a new event. Right at the end of the frame presentation, we package the current state into a new performance timeline entry: a <code>PerformanceContainerTiming<\/code> object.<\/p>\n<p>This object contains:<\/p>\n<ul>\n<li><code>startTime<\/code>: The presentation time of the paint. In the Chromium implementation, this is set to the moment the frame was presented to the user, and matches <code>presentationTime<\/code> from <code>PaintTimingMixin<\/code>.<\/li>\n<li><code>firstRenderTime<\/code>: the time of the first paint we detected in the container. Useful for getting a hint of how long a component has been showing updates to the user.<\/li>\n<li>The container element, in two ways. The <code>identifier<\/code> is the value of the <code>containertiming<\/code> attribute. <code>rootElement<\/code> is the actual element.<\/li>\n<li><code>size<\/code>: The total area of the aggregated <code>PaintedRegion<\/code>.<\/li>\n<li><code>lastPaintedElement<\/code>: the last element that triggered a paint \u2014 handy for debugging which child caused the latest candidate.<\/li>\n<\/ul>\n<div class=\"markdown-alert markdown-alert-note\"><p class=\"markdown-alert-title\">Note<\/p><p>We support the <a href=\"https:\/\/www.w3.org\/TR\/paint-timing\/#the-paint-timing-mixin\"><code>PaintTimingMixin<\/code><\/a>, which adds <code>paintTime<\/code> (when the paint was committed to the compositor) and <code>presentationTime<\/code> (when the frame was presented to the user). In Chromium, <code>startTime<\/code> is set to <code>presentationTime<\/code>.<\/p>\n<\/div>\n<p>This design means the observer might receive multiple entries for the same container. This is intentional: it lets developers pick the milestone that matters to them, typically the point where <code>size<\/code> stops growing.<\/p>\n<h2 id=\"handling-ignore\" tabindex=\"-1\">Handling \u201cIgnore\u201d <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>We also implemented the <code>containertiming-ignore<\/code> attribute. When a node has this attribute, it stops the <code>SelfOrAncestorHasContainerTiming<\/code> flag from propagating further down its subtree, so paints within it are not walked up to the container timing root, and never contribute to that container <code>PaintedRegion<\/code>.<\/p>\n<p><strong>Ignoring<\/strong> is useful for a number of things:<\/p>\n<ul>\n<li>Debug overlays and instrumentation widgets, which should not inflate the measured painted area.<\/li>\n<li>Visually independent nested components: child dialogs or overlays that paint independently from the container and would affect the size metric if included.<\/li>\n<\/ul>\n<div class=\"markdown-alert markdown-alert-tip\"><p class=\"markdown-alert-title\">Tip<\/p><p><code>containertiming-ignore<\/code> on large untracked subtrees also reduces traversal depth, helping with the cost mentioned above.<\/p>\n<\/div>\n<h2 id=\"how-to-test\" tabindex=\"-1\">How to test <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>With flag propagation, region aggregation, candidate buffering, and selective ignoring all in place, the implementation is complete.<\/p>\n<p><strong>Container Timing<\/strong> is <a href=\"https:\/\/groups.google.com\/a\/chromium.org\/g\/blink-dev\/c\/FnM3lweVssM\/m\/eVhhCtG5AQAJ\">ready for test<\/a> in Chromium. Just use the Blink feature flag <code>ContainerTiming<\/code>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">chrome --enable-blink-features<span class=\"token operator\">=<\/span>ContainerTiming<\/code><\/pre>\n<h2 id=\"what-s-next\" tabindex=\"-1\">What\u2019s next? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<ul>\n<li>We are preparing an Origin Trial in Chromium, a new step towards enabling Container Timing by default. Stay tuned!<\/li>\n<li>Optimizations in the traversal. We have some ideas for avoiding the traversal of the full tree when a paint is detected, to find the container timing root.<\/li>\n<li>Support for detecting paints in other parts of the tree. Shadow DOM is specially interesting here due to its importance in web components.<\/li>\n<\/ul>\n<h2 id=\"wrapping-up\" tabindex=\"-1\">Wrapping up <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>Building this native implementation was a great exercise in reusing Blink\u2019s existing performance infrastructure while extending it to support subtree-level aggregation.<\/p>\n<p>The key insight: subtree-level metrics didn\u2019t require a new paint tracking system. Only a way to aggregate and bubble up what Blink was already measuring.<\/p>\n<p>The result is a native, low-overhead API for measuring the rendering performance of entire components.<\/p>\n<h2 id=\"thanks\" tabindex=\"-1\">Thanks! <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<p>This  has been done as part of the collaboration between <a href=\"https:\/\/techatbloomberg.com\">Bloomberg<\/a> and <a href=\"https:\/\/www.igalia.com\">Igalia<\/a>. Thanks!<\/p>\n<p><a href=\"https:\/\/www.igalia.com\">\n<source media=\"(prefers-color-scheme: dark)\">\n<img src=\"https:\/\/blogs.igalia.com\/dape\/img\/igalia_-_500px_-_RGB_-_Feb23-580x210.png\" alt=\"Igalia\" \/>\n<\/source><\/a> <a href=\"https:\/\/techatbloomberg.com\"><img src=\"https:\/\/blogs.igalia.com\/dape\/img\/Bloomberg-logo-580x117.png\" alt=\"Bloomberg\" class=\"dark-invert\" \/><\/a><\/p>\n<h2 id=\"references\" tabindex=\"-1\">References <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/03\/26\/the-implementation-of-container-timing-aggregating-paints-in-blink\/\">#<\/a><\/h2>\n<ul>\n<li>Implementation of SelfOrAncestorHasContainerTiming: <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/ccebd1fb24b76dc2594e66b6fbad6c1192107405\/third_party\/blink\/renderer\/core\/dom\/node.h#1153\">1<\/a>, <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/ccebd1fb24b76dc2594e66b6fbad6c1192107405\/third_party\/blink\/renderer\/core\/html\/html_element.cc#3777\">2<\/a>.<\/li>\n<li><a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/ccebd1fb24b76dc2594e66b6fbad6c1192107405\/third_party\/blink\/renderer\/core\/paint\/timing\/container_timing.cc\">ContainerTiming aggregation at container_timing.cc<\/a>.<\/li>\n<li>Paint detection in <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/ccebd1fb24b76dc2594e66b6fbad6c1192107405\/third_party\/blink\/renderer\/core\/paint\/timing\/text_paint_timing_detector.cc\">text<\/a> and <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/ccebd1fb24b76dc2594e66b6fbad6c1192107405\/third_party\/blink\/renderer\/core\/paint\/timing\/image_paint_timing_detector.cc\">image<\/a> paint timing detectors.<\/li>\n<li><a href=\"https:\/\/wicg.github.io\/container-timing\/\">Specification draft @ WICG<\/a>.<\/li>\n<li>Related specifications: <a href=\"https:\/\/w3c.github.io\/element-timing\/\">Element Timing<\/a>, <a href=\"https:\/\/www.w3.org\/TR\/largest-contentful-paint\/\">Largest Contentful Paint<\/a>, <a href=\"https:\/\/www.w3.org\/TR\/paint-timing\/#the-paint-timing-mixin\">Paint Timing Mixin<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/WICG\/container-timing\">Explainer<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/WICG\/container-timing\/issues\/14\">Shadow DOM Handling discussion<\/a>.<\/li>\n<li><a href=\"https:\/\/issues.chromium.org\/489959278\">Optimizing hierarchy traversals<\/a>.<\/li>\n<li><a href=\"http:\/\/bit.ly\/lifeofapixel\">Life of a Pixel<\/a> presentation about the Blink rendering pipeline by Steve Kobes.<\/li>\n<\/ul>                ","author":{"name":"Jos\u00e9 Dapena","uri":"https:\/\/blogs.igalia.com\/dape\/"}},{"title":"Qiuyi Zhang (Joyee): Improving Single Executable Application Building for Node.js","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2026\/01\/26\/improving-single-executable-application-building-for-node-js\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2026\/01\/26\/improving-single-executable-application-building-for-node-js\/","updated":"2026-03-24T13:15:57+00:00","content":"\n<p>Recently, I <a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/github.com\/nodejs\/node\/pull\/61167\">landed a change that moves the Single Executable Application (SEA) build process directly into Node.js core<\/a> - a hobby project I\u2019d been tinkering with for some time<\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}},{"title":"Javier Fern\u00e1ndez: Protocol Handler Registration via Browser Extensions","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/jfernandez\/2026\/03\/24\/protocol-handler-registration-via-browser-extensions\/"}},"id":"https:\/\/blogs.igalia.com\/jfernandez\/?p=1961","updated":"2026-03-24T11:41:45+00:00","content":"\n<h2 class=\"wp-block-heading\">Motivation<\/h2>\n\n\n\n<p>Custom URL schemes have traditionally served as an integration bridge between the browser and external capabilities. Schemes such as mailto: and tel: allow navigation to trigger actions beyond ordinary HTTP resource retrieval. The HTML Standard formalizes this mechanism through the <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/system-state.html#custom-handlers\">Custom Scheme Handlers<\/a> API, which enables websites to register themselves as handlers for specific URL schemes.<\/p>\n\n\n\n<p>While the Web API is appropriate for origin-scoped integrations, its security model imposes several structural constraints:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Registration must be initiated from a visited website.<\/li>\n\n\n\n<li>It requires user activation.<\/li>\n\n\n\n<li>The handler URL must share the same origin as the registering site.<\/li>\n\n\n\n<li>Each registration is processed individually and requires explicit user approval.<\/li>\n<\/ul>\n\n\n\n<p>These constraints are deliberate and necessary to prevent cross-origin abuse. However, they also limit legitimate integration scenarios that are better expressed outside the web-origin layer.<\/p>\n\n\n\n<p>In collaboration with the <a href=\"https:\/\/openimpact.foundation\/\">Open Impact Foundation&#8217;s IPFS Implementations grants program<\/a>, <a href=\"http:\/\/igalia.com\">Igalia<\/a> has implemented support for declaring protocol handlers directly in the Web Extension Manifest for Chromium-based browsers &#8211; <strong>achieving interoperability with Firefox<\/strong>. The goal is to make protocol registration a first-class extension capability, while preserving the security invariants established by the HTML Standard.<\/p>\n\n\n\n<p>The proposal was <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/365\">discussed<\/a> by the Web Extensions WICG back in 2023, with the support of Firefox (already implemented) and Chrome. Safari initially supported but finally changed to opposed.<\/p>\n\n\n\n<p>This article introduces the motivation behind the feature, explains the design decisions that shaped it, and describes its internal security and lifecycle model.<\/p>\n\n\n\n<p>The feature has been shipped behind an experimental flag in <strong>Chrome 146<\/strong>. To test it, just launch Chrome from the command line with this option:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>--enable-features=ExtensionProtocolHandlers<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Case study: IPFS Companion<\/h2>\n\n\n\n<p>IPFS introduces schemes such as ipfs:\/\/, backed by a content-addressed data model rather than traditional origin-based addressing. In Chromium\u2019s previous extension model, <a href=\"https:\/\/chromewebstore.google.com\/detail\/ipfs-companion\/nibjojkomfdiaoajekhjakgkdhaomnch\">IPFS Companion<\/a> must request <em>declarativeNetRequest<\/em>, <em>webRequest<\/em>, <em>webNavigation<\/em>, and <em>&lt;all_urls&gt;<\/em> host permissions &#8212; not because it wants to monitor all browsing activity, but because intercepting an unrecognized protocol requires inspecting every navigation and network request. The browser shows users a warning like &#8220;Read and change all your data on all websites&#8221;, which is disproportionate to what the extension actually does with those protocols. Users must decide whether to trust that warning based on the extension&#8217;s reputation alone.<\/p>\n\n\n\n<p>These broad permissions also create <strong>friction with the Chrome Web Store review<\/strong> process. Extensions requesting <em>webRequest<\/em> and are flagged for in-depth review, adding days to every publish cycle and occasionally triggering outright rejections that require detailed justification of each permission.<\/p>\n\n\n\n<p>In the absence of a native mechanism, IPFS Companion resorts to detecting when the browser converts an unrecognized <em>ipfs:\/\/<\/em> URL into a search engine query, then intercepting and redirecting that query. This works, but it <strong>depends on browser-specific URL encoding<\/strong> behavior, breaks silently when search providers change their format, and may not work on all platforms due to security software interfering with such hijacking.<\/p>\n\n\n\n<p>With manifest-declared protocol handlers, the extension can register <em>IPFS<\/em> directly. Navigation dispatch becomes declarative rather than interceptive. The permission model narrows, the architecture simplifies, and the integration aligns with the browser\u2019s native routing mechanisms. These would be an example of the Extension Manifest:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  \"protocol_handlers\": &#91;\n    {\n      \"protocol\": \"ipns\",\n      \"name\": \"IPFS Companion: IPNS Protocol Handler\",\n      \"uriTemplate\": \"https:\/\/dweb.link\/ipns\/?uri=%s\"\n    },\n    {\n      \"protocol\": \"ipfs\",\n      \"name\": \"IPFS Companion: IPFS Protocol Handler\",\n      \"uriTemplate\": \"https:\/\/dweb.link\/ipfs\/?uri=%s\"\n    }\n  ]<\/code><\/pre>\n\n\n\n<p>This example illustrates the broader principle behind the feature: protocol handling should be expressed as a first-class navigation capability, not as a side effect of request rewriting.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Broader integration scenarios<\/h3>\n\n\n\n<p>Beyond decentralized networking use cases, manifest-declared protocol handlers enable enterprise and platform-level integrations. Organizations can define custom schemes that deep-link into internal systems, communication tools, authentication flows, or secure service endpoints. Extensions can manage these schemes centrally, update them through versioned deployments, and decouple protocol routing from web application modifications.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  \"protocol_handlers\": &#91;\n    {\n      \"protocol\": \"irc\",\n      \"name\": \"Corporate IRC client\",\n      \"uriTemplate\": \"https:\/\/mycompany.com\/irc\/?params=%s\"\n    },\n    {\n      \"protocol\": \"mailto\",\n      \"name\": \"Corporate Email client\",\n      \"uriTemplate\": \"https:\/\/mycompany.com\/webmail\/?params=%s\"\n    },\n    {\n      \"protocol\": \"webcal\",\n      \"name\": \"Corporate Calendar client\",\n      \"uriTemplate\": \"https:\/\/mycompany.com\/calendar\/?params=%s\"\n    },\n    {\n      \"protocol\": \"web+plan\",\n      \"name\": \"Corporate Planning client\",\n      \"uriTemplate\": \"https:\/\/mycompany.com\/planning\/?params=%s\"\n    },<\/code><\/pre>\n\n\n\n<p>This establishes a structured integration surface between browser navigation and external systems while maintaining explicit user control and security guarantees.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The limits of existing mechanisms<\/h2>\n\n\n\n<p>The HTML Standard\u2019s <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Navigator\/registerProtocolHandler\">navigator.registerProtocolHandler()<\/a> API abides by the same-origin security model. A website may only register handlers that resolve within its own origin, and registration requires explicit user activation. This model works well when a web application intends to claim responsibility for a scheme that maps naturally to its own domain. However, extensions operate under a fundamentally different trust and lifecycle model.<\/p>\n\n\n\n<p>Extensions are packaged artifacts installed by the user, subject to store review and explicit permission approval. Their integration surface extends beyond a single origin, and often spans navigation interception, network rewriting, operating system integration, and enterprise policy enforcement. Attempting to reuse the web-origin registration model for extension use cases introduces friction and architectural complexity.<\/p>\n\n\n\n<p>As a result, extensions in Chromium-based browsers have historically relied on indirect mechanisms. For example, extensions such as IPFS Companion intercept navigation requests, detect custom schemes, and rewrite them into gateway-based HTTP URLs using APIs like <em>declarativeNetRequest<\/em>. Although functional, this approach moves protocol handling into request interception layers, rather than treating it as a native navigation routing concern. It increases implementation complexity, expands the required permission surface, and introduces maintenance overhead.<\/p>\n\n\n\n<p>The absence of manifest-declared protocol handlers in Chromium created a gap between the capabilities of extensions and the needs of advanced integration scenarios.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A step forward: PWAs as \u201cURL handlers\u201d<\/h3>\n\n\n\n<p>Progressive Web Apps provided a partial evolution of the model by allowing protocol handlers to be declared <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Progressive_web_apps\/Manifest\/Reference\/protocol_handlers\">via the Web App Manifest<\/a>. This <a href=\"https:\/\/blogs.windows.com\/msedgedev\/2022\/01\/20\/getting-started-url-protocol-handlers-microsoft-edge\">improved declarative configuration<\/a>, but remained tightly coupled to the application\u2019s origin and lifecycle. It did not address scenarios where the integration logic belongs to an extension rather than a web application.<\/p>\n\n\n\n<p>Back in 2020, Chrome started prototyping a feature called <a href=\"https:\/\/developer.chrome.com\/docs\/capabilities\/pwa-url-handler\">PWAs as URL Handlers<\/a>, allowing apps to register themselves as handlers for URLs matching a certain pattern. This feature has been abandoned in favor of <a href=\"https:\/\/github.com\/WICG\/manifest-incubations\/blob\/gh-pages\/scope_extensions-explainer.md\">Scoped Extensions for Web App Manifest<\/a>, which precisely allows web apps to overcome some of the challenges that the same-origin policy imposes on this type of site architecture.<\/p>\n\n\n\n<p>These lines of work did not address scenarios where the integration logic belongs to an extension rather than a web application. However, these initiatives inspired the work to implement similar capabilities in Web Extensions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Manifest-declared protocol handlers<\/h2>\n\n\n\n<p>As a result of our work, Chromium now supports the <code>protocol_handlers<\/code> key directly in the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/manifest.json\/protocol_handlers\">Web Extension Manifest<\/a>. This feature aligns protocol registration with the extension lifecycle instead of the web-origin lifecycle.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"protocol_handlers\": &#91;\n  {\n    \"protocol\": \"ircs\",\n    \"name\": \"IRC Mozilla Extension\",\n    \"uriTemplate\": \"https:\/\/irccloud.mozilla.com\/#!\/%s\"\n  }\n]<\/code><\/pre>\n\n\n\n<p>Handlers declared in the manifest are parsed and validated during extension installation. Registration occurs at that time, but activation is deferred: the handlers remain inactive until they are invoked by a navigation request and explicitly approved by the user.<\/p>\n\n\n\n<p>This design introduces several important properties:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Registration is declarative and tied to the extension artifact.<\/li>\n\n\n\n<li>Validation enforces HTML Standard constraints at parse time.<\/li>\n\n\n\n<li>Activation requires runtime user consent.<\/li>\n\n\n\n<li>Disabling or uninstalling the extension automatically removes its handlers.<\/li>\n<\/ul>\n\n\n\n<p>By shifting protocol registration into the manifest, the browser gains a clearer separation between declaration, validation, and activation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Security Model and Validation<\/h2>\n\n\n\n<p>Because protocol handlers influence navigation routing, the feature inherits strict <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/system-state.html#normalize-protocol-handler-parameters\">validation rules<\/a> from the HTML Standard. During manifest parsing, the browser verifies that declared schemes belong to a predefined <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/system-state.html#safelisted-scheme\">safe list<\/a> and that handler URLs use HTTP or HTTPS.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> \"bitcoin\", \"cabal\",  \"dat\",    \"did\",  \"doi\",  \"dweb\", \"ethereum\",\n \"geo\",     \"hyper\",  \"im\",     \"ipfs\", \"ipns\", \"irc\",  \"ircs\",\n \"magnet\",  \"mailto\", \"matrix\", \"mms\",  \"news\", \"nntp\", \"openpgp4fpr\",\n \"sip\",     \"sms\",    \"smsto\",  \"ssb\",  \"ssh\",  \"tel\",  \"urn\",\n \"webcal\",  \"wtai\",   \"xmpp\"<\/code><\/pre>\n\n\n\n<p>Given that the same-origin requirement is relaxed in this model, we need to validate explicitly that the target handler operates in a <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/webappapis.html#secure-context\">secure context<\/a>. This ensures that the user doesn&#8217;t leave a <a href=\"https:\/\/w3c.github.io\/webappsec-secure-contexts\/#potentially-trustworthy-origin\">trustworthy origin<\/a> due to the redirection performed by the protocol handler.<\/p>\n\n\n\n<p>The Web API model imposes a requirement of a mandatory <a href=\"https:\/\/html.spec.whatwg.org\/multipage\/interaction.html#tracking-user-activation\">User Activation<\/a> to confirm the JavaScript registration request. The Extension API model, instead, proposes a declarative approach to perform the handler registration, so it happens silently without explicit user consent. However, this does not remove the user-gesture requirement from the security model; instead, it relocates it to the extension installation process.<\/p>\n\n\n\n<p>Extension installation is an explicit user action that requires them to review the requested permissions and give their consent. Registration of manifest-declared protocol handlers occurs as part of this installation transaction. In this sense, the User Activation requirement is satisfied at the lifecycle level rather than at the API invocation level.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/2ec5495e-2b57-4eec-b478-ffcdea00e5f4.png\"><img width=\"1024\" height=\"427\" src=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/2ec5495e-2b57-4eec-b478-ffcdea00e5f4-1024x427.png\" alt=\"\" class=\"wp-image-1966\" \/><\/a><\/figure>\n\n\n\n<p>In addition, activation of a registered handler is deferred. When a matching navigation occurs, the browser prompts the user before allowing the handler to resolve the request. This introduces a second layer of consent, ensuring that protocol usage cannot occur silently.<\/p>\n\n\n\n<p>The resulting model separates concerns:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Installation authorizes registration.<\/li>\n\n\n\n<li>Runtime approval authorizes use.<\/li>\n<\/ul>\n\n\n\n<p>This layered approach preserves the security intent of the HTML model while adapting it to the extension trust boundary.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Runtime permission flow<\/h3>\n\n\n\n<p>A key design decision was to avoid front-loading protocol permissions during installation. Modern WebExtensions APIs increasingly rely on runtime permission requests to reduce cognitive overload and improve user comprehension.<\/p>\n\n\n\n<p>Accordingly, protocol handlers declared in the manifest remain dormant until a matching navigation occurs. When such a navigation is triggered, the browser presents a permission dialog identifying both the extension requesting activation and the destination to which navigation will be redirected. The user may approve the request once or choose to persist the decision.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/85d4f18b-6f6b-4fd2-befd-a52bca18fe13.png\"><img width=\"1024\" height=\"666\" src=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/85d4f18b-6f6b-4fd2-befd-a52bca18fe13-1024x666.png\" alt=\"\" class=\"wp-image-1967\" \/><\/a><\/figure>\n\n\n\n<p>This runtime gating model ensures transparency while preserving a smooth installation experience. It also aligns protocol handling with contemporary permission paradigms used across browser APIs.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/fa531859-3d0f-4387-9a6a-418f47db1afc.png\"><img width=\"978\" height=\"516\" src=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/fa531859-3d0f-4387-9a6a-418f47db1afc.png\" alt=\"\" class=\"wp-image-1968\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Cross-origin considerations<\/h3>\n\n\n\n<p>The same-origin requirement in the HTML Standard\u2019s Custom Scheme Handlers API is not incidental; it is central to its threat model. When a website registers itself as a handler, the specification requires that the handler URL share the same origin as the registering site. This prevents a malicious origin from silently redirecting navigation events to an unrelated third-party origin. In the Web API model, the origin boundary is the primary trust primitive.<\/p>\n\n\n\n<p>The extension model operates under a different trust boundary. Extensions are not ephemeral web origins; they are packaged components, installed by the user, with declared permissions and a well-defined lifecycle. As a result, enforcing same-origin constraints in the extension context would artificially restrict legitimate scenarios, like the ones described in the previous sections, without materially improving security.<\/p>\n\n\n\n<p>For example, consider decentralized protocols such as IPFS. <a href=\"https:\/\/docs.ipfs.tech\/how-to\/ipfs-in-web-apps\/#addressing-data-by-cid\">Content addressing<\/a> in IPFS does not map cleanly to traditional origin semantics. A handler may need to resolve a scheme into HTTP resources, via gateway mechanism, or local node endpoints or simply connect to the network itself; these targets do not share a single origin in the conventional sense. Imposing a strict same-origin requirement in this context would block valid architectures without offering additional protection.<\/p>\n\n\n\n<p>Relaxing the same-origin requirement in the extension model does not eliminate safeguards. Instead, the security model shifts from origin isolation to layered controls managed by the user. These include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Extension <a href=\"https:\/\/developer.chrome.com\/docs\/webstore\/review-process\">store review<\/a> and distribution controls.<\/li>\n\n\n\n<li>Explicit consent during the installation.<\/li>\n\n\n\n<li>Manifest-declared capabilities.<\/li>\n\n\n\n<li>Runtime approval before handler activation.<\/li>\n<\/ul>\n\n\n\n<p>This layered approach ensures that a protocol handler cannot be silently introduced or activated. Even though a handler may redirect navigation to a different origin, that behavior is explicitly tied to an installed by the user from a trusted source, and subject to runtime confirmation.<\/p>\n\n\n\n<p>It is also important to distinguish between cross-origin navigation and cross-origin data access. Protocol handler resolution affects the destination of a navigation request; it does not grant the extension arbitrary access to the target origin\u2019s data. Standard web security boundaries\u2014such as the Same-Origin Policy and CORS\u2014remain fully enforced after navigation completes.<\/p>\n\n\n\n<p>In this way, the extension model preserves the security intent of the HTML specification while adapting it to a broader integration surface. The trust anchor shifts from \u201corigin that called the API\u201d to \u201cextension the user chose to install,\u201d but the system continues to require explicit consent by the user before navigation control is delegated.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/31849cd2-7376-4452-95df-81065967d63f.png\"><img width=\"1024\" height=\"899\" src=\"https:\/\/blogs.igalia.com\/jfernandez\/files\/2026\/03\/31849cd2-7376-4452-95df-81065967d63f-1024x899.png\" alt=\"\" class=\"wp-image-1970\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conflict resolution across registration mechanisms<\/h2>\n\n\n\n<p>With protocol handlers now registrable through multiple mechanisms\u2014the Web API, PWA manifests, and extension manifests\u2014conflict resolution becomes necessary. The implementation preserves backward compatibility by prioritizing Web API registrations. If a handler has been registered via <em>navigator.registerProtocolHandler()<\/em>, it becomes the default for the corresponding scheme. PWA and extension handlers are considered lower priority and remain available if higher-priority registrations are removed.<\/p>\n\n\n\n<p>This deterministic ordering ensures predictable behavior and avoids ambiguity when multiple registration surfaces coexist.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why this feature matters<\/h2>\n\n\n\n<p>Adding manifest-declared protocol handlers to Chromium closes a <strong>long-standing capability gap<\/strong> with Firefox, which has offered such capability since 2017. This allows extension authors to ship a single manifest that works across both browsers, eliminating the need to maintain separate interception codepaths per engine.<\/p>\n\n\n\n<p>Manifest-declared protocol_handlers replace all of this with a single, narrowly scoped declaration. The <strong>permissions surface shrink<\/strong> from \u201cread and change all your data on all websites&#8221; to a runtime prompt scoped to the specific protocol: &#8220;Allow this extension to open IPFS links through dweb.link&#8221;.<\/p>\n\n\n\n<p>The new API respects the validation rules of the HTML Standard while adapting them to the extension trust model. It aligns protocol handling with the extension lifecycle, integrates cleanly with <strong>modern runtime permission patterns<\/strong>, and provides deterministic conflict resolution across registration surfaces. Store reviewers can verify the declared intent directly in the manifest without auditing request interception logic.<\/p>\n\n\n\n<p>For <strong>browser engineers<\/strong>, the feature introduces a cleaner architectural boundary between navigation routing and network interception. For <strong>web authors<\/strong> building advanced integrations, it enables robust, declarative protocol handling without relying on brittle implementation techniques. For <strong>extension developers<\/strong>, it means protocol handling can finally be expressed as what it is (a navigation capability) rather than being disguised as request rewriting.<\/p>\n\n\n\n<p>With the Web Extensions CG moving toward <a href=\"https:\/\/w3c.github.io\/charter-drafts\/2025\/webextensions-wg.html\">WG status<\/a>, this is a good opportunity to advance the standardization of the protocol_handlers key by proposing its inclusion in the <a href=\"https:\/\/w3c.github.io\/webextensions\/specification\/index.html#manifest-keys\">Manifest Keys section<\/a> of the Draft Community Group Report.<\/p>                ","author":{"name":"jfernandez","uri":"https:\/\/blogs.igalia.com\/jfernandez"}},{"title":"Sim\u00f3n Pena: Getting started with WPE WebKit: a minimal launcher","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/simonpena.com\/blog\/2026\/03\/20\/getting-started-with-wpe-webkit\/"}},"id":"https:\/\/simonpena.com\/blog\/2026\/03\/20\/getting-started-with-wpe-webkit","updated":"2026-03-20T00:00:00+00:00","content":"\n<p>My colleague Kate recently <a href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">demonstrated on her blog<\/a> how simple it is to write a WPE Platform-based launcher, and did so by building it side-by-side with MiniBrowser, inside the WebKit tree.<\/p>\n\n<p>This entry takes one step back, and demonstrates the same concepts assuming you are not building WPE WebKit yourself, but rather getting it from your distribution. Many of the steps below would apply if you were using a <a href=\"https:\/\/www.yoctoproject.org\/\">Yocto\/OpenEmbedded-based image<\/a>, but that can be the focus of another post.<\/p>\n\n<h2 id=\"getting-wpe-webkit\">Getting WPE WebKit<\/h2>\n\n<p><a href=\"https:\/\/wpewebkit.org\/about\/get-wpe.html\">Get WPE<\/a> lists a number of options to get WPE from your preferred distribution. At the moment of writing, Fedora, Debian and ArchLinux are your best choices to get a recent version of WPE:<\/p>\n\n<ul>\n  <li>2.52 on Fedora<\/li>\n  <li>2.50 on Debian Forky, 2.52 on Debian Sid<\/li>\n  <li>2.50 on ArchLinux<\/li>\n<\/ul>\n\n<p>However, since WPE Platform hasn\u2019t officially been released, we need to use Fedora, where my colleague <a href=\"https:\/\/www.igalia.com\/team\/pnormand\">Philippe<\/a> maintains a Copr repository with it enabled.<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">sudo <\/span>dnf copr <span class=\"nb\">enable<\/span> <span class=\"nt\">-y<\/span> philn\/wpewebkit\n<span class=\"nb\">sudo <\/span>dnf <span class=\"nb\">install <\/span>wpewebkit-devel\n<\/code><\/pre><\/div><\/div>\n\n<p>Alternatively, you can use a container. Here is a <code class=\"language-plaintext highlighter-rouge\">Containerfile<\/code> based on Fedora 42:<\/p>\n\n<div class=\"language-dockerfile highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">FROM<\/span><span class=\"s\"> fedora:42<\/span>\n\n<span class=\"k\">RUN <\/span>dnf <span class=\"nb\">install<\/span> <span class=\"nt\">-y<\/span> <span class=\"se\">\\\n<\/span>    dnf-plugins-core <span class=\"se\">\\\n<\/span>    <span class=\"o\">&amp;&amp;<\/span> dnf copr <span class=\"nb\">enable<\/span> <span class=\"nt\">-y<\/span> philn\/wpewebkit <span class=\"se\">\\\n<\/span>    <span class=\"o\">&amp;&amp;<\/span> dnf <span class=\"nb\">install<\/span> <span class=\"nt\">-y<\/span> <span class=\"se\">\\\n<\/span>    gcc-c++ <span class=\"se\">\\\n<\/span>    cmake <span class=\"se\">\\\n<\/span>    pkg-config <span class=\"se\">\\\n<\/span>    wpewebkit-devel\n\n<span class=\"k\">WORKDIR<\/span><span class=\"s\"> \/src<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Build and run it with:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>podman build <span class=\"nt\">-t<\/span> wpe-dev <span class=\"nb\">.<\/span>\npodman run <span class=\"nt\">-it<\/span> <span class=\"nt\">-e<\/span> <span class=\"nv\">WAYLAND_DISPLAY<\/span><span class=\"o\">=<\/span><span class=\"nv\">$WAYLAND_DISPLAY<\/span> <span class=\"se\">\\<\/span>\n<span class=\"nt\">-e<\/span> <span class=\"nv\">XDG_RUNTIME_DIR<\/span><span class=\"o\">=<\/span>\/run\/user\/<span class=\"si\">$(<\/span><span class=\"nb\">id<\/span> <span class=\"nt\">-u<\/span><span class=\"si\">)<\/span> <span class=\"se\">\\<\/span>\n<span class=\"nt\">-v<\/span> <span class=\"nv\">$XDG_RUNTIME_DIR<\/span>\/<span class=\"nv\">$WAYLAND_DISPLAY<\/span>:\/run\/user\/<span class=\"si\">$(<\/span><span class=\"nb\">id<\/span> <span class=\"nt\">-u<\/span><span class=\"si\">)<\/span>\/<span class=\"nv\">$WAYLAND_DISPLAY<\/span> <span class=\"se\">\\<\/span>\n<span class=\"nt\">-v<\/span> \/dev\/dri:\/dev\/dri <span class=\"se\">\\<\/span>\nwpe-dev bash\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"the-build-system\">The build system<\/h2>\n\n<p>Kate\u2019s post builds the launcher as part of the WebKit tree using WebKit\u2019s own CMake infrastructure. For a standalone project, we need a self-contained <code class=\"language-plaintext highlighter-rouge\">CMakeLists.txt<\/code> that finds WPE WebKit through <code class=\"language-plaintext highlighter-rouge\">pkg-config<\/code>:<\/p>\n\n<div class=\"language-cmake highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">cmake_minimum_required<\/span><span class=\"p\">(<\/span>VERSION 3.16<span class=\"p\">)<\/span>\n<span class=\"nb\">project<\/span><span class=\"p\">(<\/span>wpe_sample CXX<span class=\"p\">)<\/span>\n\n<span class=\"nb\">set<\/span><span class=\"p\">(<\/span>CMAKE_CXX_STANDARD 17<span class=\"p\">)<\/span>\n\n<span class=\"nb\">find_package<\/span><span class=\"p\">(<\/span>PkgConfig REQUIRED<span class=\"p\">)<\/span>\n\n<span class=\"c1\"># The Wayland WPE Platform already depends on wpe-platform-2.0<\/span>\n<span class=\"nf\">pkg_check_modules<\/span><span class=\"p\">(<\/span>WebKitDeps REQUIRED\n    IMPORTED_TARGET\n    wpe-webkit-2.0\n    wpe-platform-wayland-2.0\n<span class=\"p\">)<\/span>\n\n<span class=\"nb\">add_executable<\/span><span class=\"p\">(<\/span>wpe_sample main.cpp<span class=\"p\">)<\/span>\n\n<span class=\"nb\">target_link_libraries<\/span><span class=\"p\">(<\/span>wpe_sample\n    PRIVATE\n        PkgConfig::WebKitDeps\n<span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"the-launcher\">The launcher<\/h2>\n\n<p>Here is a minimal launcher \u2014 the smallest amount of code needed to display a web page with WPE WebKit:<\/p>\n\n<div class=\"language-cpp highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">#include<\/span> <span class=\"cpf\">&lt;wpe\/webkit.h&gt;<\/span><span class=\"cp\">\n<\/span>\n<span class=\"kt\">int<\/span> <span class=\"nf\">main<\/span><span class=\"p\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">argc<\/span><span class=\"p\">,<\/span> <span class=\"k\">const<\/span> <span class=\"kt\">char<\/span> <span class=\"o\">*<\/span><span class=\"n\">argv<\/span><span class=\"p\">[])<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">g_autoptr<\/span><span class=\"p\">(<\/span><span class=\"n\">GMainLoop<\/span><span class=\"p\">)<\/span> <span class=\"n\">loop<\/span> <span class=\"o\">=<\/span> <span class=\"n\">g_main_loop_new<\/span><span class=\"p\">(<\/span><span class=\"nb\">nullptr<\/span><span class=\"p\">,<\/span> <span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n    <span class=\"n\">g_autoptr<\/span><span class=\"p\">(<\/span><span class=\"n\">WebKitWebView<\/span><span class=\"p\">)<\/span> <span class=\"n\">view<\/span> <span class=\"o\">=<\/span> <span class=\"n\">WEBKIT_WEB_VIEW<\/span><span class=\"p\">(<\/span><span class=\"n\">g_object_new<\/span><span class=\"p\">(<\/span><span class=\"n\">WEBKIT_TYPE_WEB_VIEW<\/span><span class=\"p\">,<\/span>\n        <span class=\"nb\">nullptr<\/span><span class=\"p\">));<\/span>\n    <span class=\"n\">webkit_web_view_load_uri<\/span><span class=\"p\">(<\/span><span class=\"n\">view<\/span><span class=\"p\">,<\/span>\n        <span class=\"p\">(<\/span><span class=\"n\">argc<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">1<\/span><span class=\"p\">)<\/span> <span class=\"o\">?<\/span> <span class=\"n\">argv<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">:<\/span> <span class=\"s\">\"https:\/\/wpewebkit.org\"<\/span><span class=\"p\">);<\/span>\n    <span class=\"n\">g_main_loop_run<\/span><span class=\"p\">(<\/span><span class=\"n\">loop<\/span><span class=\"p\">);<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">EXIT_SUCCESS<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This snippet relies heavily on default behaviours: it will create a default WPE view, with default top levels, with the default display selection behaviour (Wayland), default context, settings\u2026<\/p>\n\n<p>Again, <a href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">Kate\u2019s post<\/a> does a more realistic job at showing how the various pieces are created and connected together.<\/p>\n\n<h2 id=\"building-and-running\">Building and running<\/h2>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>cmake <span class=\"nt\">-B<\/span> build\ncmake <span class=\"nt\">--build<\/span> build\n.\/build\/wpe_sample https:\/\/wpewebkit.org\/\n<\/code><\/pre><\/div><\/div>\n\n<p><img src=\"https:\/\/simonpena.com\/assets\/images\/wpe-webkit-sample-screenshot.png\" alt=\"WPE WebKit minimal launcher\" \/><\/p>\n\n<h2 id=\"display-backends\">Display backends<\/h2>\n\n<p>WPE WebKit can render to different display backends depending on your environment, which you can select through environment variables:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># Wayland (e.g. desktop, Weston).<\/span>\n<span class=\"nv\">WPE_DISPLAY<\/span><span class=\"o\">=<\/span>wpe-display-wayland <span class=\"nv\">WAYLAND_DISPLAY<\/span><span class=\"o\">=<\/span>wayland-1 .\/build\/wpe_sample https:\/\/wpewebkit.org\/\n\n<span class=\"c\"># DRM\/KMS (e.g. embedded, no compositor)<\/span>\n<span class=\"nv\">WPE_DISPLAY<\/span><span class=\"o\">=<\/span>wpe-display-drm .\/build\/wpe_sample https:\/\/wpewebkit.org\/\n\n<span class=\"c\"># Headless (e.g. testing, CI)<\/span>\n<span class=\"nv\">WPE_DISPLAY<\/span><span class=\"o\">=<\/span>wpe-display-headless .\/build\/wpe_sample https:\/\/wpewebkit.org\/\n<\/code><\/pre><\/div><\/div>\n\n<p>You can take a look at <code class=\"language-plaintext highlighter-rouge\">wpe_display_get_default()<\/code> in <a href=\"https:\/\/github.com\/WebKit\/WebKit\/blob\/wpewebkit-2.52.0\/Source\/WebKit\/WPEPlatform\/wpe\/WPEDisplay.cpp\">WPEPlatform\/wpe\/WPEDisplay.cpp<\/a> to understand how the automatic selection takes place in the absence of an explicit <code class=\"language-plaintext highlighter-rouge\">WPE_DISPLAY<\/code> request.<\/p>\n\n<p>(In our example, we are only listing Wayland as a CMake dependency. If <code class=\"language-plaintext highlighter-rouge\">libwpewebkit<\/code> was compiled without DRM or headless support, the environment variable approach would not work.)<\/p>\n\n<h2 id=\"next-steps\">Next steps<\/h2>\n\n<p>This is all for now. The next entry in the series will cover classic kiosk features: preventing navigation to unwanted sites, controlling whether new windows can be opened, and intercepting requests through policy decisions.<\/p>\n\n<p>For a more complete example that includes a custom HTML context menu and JavaScript injection, see <a href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">Kate\u2019s post<\/a>.<\/p>                ","author":{"name":"Sim\u00f3n","uri":"https:\/\/simonpena.com\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #60","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-60\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-60\/","updated":"2026-03-18T19:46:56+00:00","content":"\n<p>Update on what happened in WebKit in the week from March 10 to March 18.<\/p>\n<p>\nThe big ticket item in this week's update are the 2.52.0 releases, which\ninclude the work from the last six-month development period, and come with\na security advisory. Meanwhile, WPE-Android also gets a release, and a number\nof featured blog posts.\n<\/p>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n  <div class=\"wip-item\">\n<p>Last week we <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/309047@main\">added support<\/a> to WPE\nMiniBrowser to load <a rel=\"external\" href=\"https:\/\/wpewebkit.org\/reference\/2.52.0\/wpe-webkit-2.0\/class.Settings.html\">settings<\/a> from a key file. This extended the existing\n<code>--config-file=FILE<\/code> feature, which previously only loaded WPEPlatform\nsettings under the <code>[wpe-platform]<\/code> group. Now the feature uses\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/reference\/2.52.0\/wpe-webkit-2.0\/method.Settings.apply_from_key_file.html\">webkit_settings_apply_from_key_file()<\/a>\nto load properties such as <code>user-agent<\/code> or <code>enable-developer-extras<\/code>\nfrom the <code>[websettings]<\/code> group as well.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/03\/18\/webkitgtk2.52.0-released.html\">WebKitGTK\n2.52.0<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.52.0.html\">WPE WebKit 2.52.0<\/a> are\nnow available. These include the results of the effort made by the team during\nthe last six months, including rendering improvements and performance\noptimizations, better security for WebRTC, a more complete WebXR\nimplementation, and a second preview of the WPEPlatform API for the WPE\nport\u2014among many other changes.<\/p>\n<p>More information about the changes and improvements brought by these major\nreleases can be found at the <a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/03\/18\/webkitgtk-2.52-highlights.html\">blog post about WebKitGTK\n2.52<\/a>, and the\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/blog\/2026-03-18-wpewebkit-2.52.html\">corresponding one for WPE WebKit\n2.52<\/a>.<\/p>\n<p>Accompanying these releases there is security advisory <code>WSA-2026-0001<\/code>\n(<a rel=\"external\" href=\"https:\/\/webkitgtk.org\/security\/WSA-2026-0001.html\">GTK<\/a>,\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/security\/WSA-2026-0001.html\">WPE<\/a>), with information\nabout solved security issues. As usual, we encourage everybody to use the most\nrecent versions where such issues are known to be fixed.<\/p>\n<p>Bug reports are always welcome <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/\">at the WebKit\nBugzilla<\/a>.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/github.com\/Igalia\/wpe-android\/releases\/tag\/v0.3.3\">WPE Android 0.3.3<\/a> has been released, and prebuilt packages are available <a rel=\"external\" href=\"https:\/\/central.sonatype.com\/artifact\/org.wpewebkit.wpeview\/wpeview\/\">at the Maven Central repository<\/a>. This is a maintenance release which updates the included WPE WebKit version to 2.50.6 and libsoup to 3.6.6, both of which include security fixes.<\/p>\n  <\/div>\n<h2 id=\"community-events-handshake\">Community &amp; Events \ud83e\udd1d<\/h2>\n  <div class=\"wip-item\">\n<p>Kate Lee wrote a <a rel=\"external\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">very interesting blog\npost<\/a>\nshowing how to create a small application using the WPEPlatform API to\ndemonstrate one of its newly available features: the Context Menu API. It is\nrendered entirely as an HTML overlay, enabling richer and more portable context\nmenu implementations.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>WebXR support for WebKitGTK and WPE has been reworked and aligned with the\nmodern multi-process architecture, using OpenXR to enable XR device integration\non Linux and Android. Sergio Villar <a rel=\"external\" href=\"https:\/\/blogs.igalia.com\/svillar\/post\/webkitgtk-wpe-webxr\/\">wrote a blog post that explains all the\nwork done<\/a> in the\nlast months around it.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Emmanuele Bassi: Let\u2019s talk about\u00a0Moonforge","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/www.bassi.io\/articles\/2026\/03\/17\/lets-talk-about-moonforge\/"}},"id":"tag:www.bassi.io,2026-03-17:\/articles\/2026\/03\/17\/lets-talk-about-moonforge\/","updated":"2026-03-17T17:44:31+00:00","content":"\n<p>Last week, Igalia finally <a href=\"https:\/\/www.igalia.com\/2026\/03\/09\/Introducing-Moonforge-A-Yocto-Based-Linux-OS.html\">announced Moonforge<\/a>, a project we&#8217;ve been working on for basically all of 2025. It&#8217;s been quite the rollercoaster, and the announcement hit various news outlets, so I guess now is as good a time as any to talk a bit about what Moonforge is, its goal, and its&nbsp;constraints.<\/p>\n<p>Of course, as soon as somebody announces a new Linux-based <span class=\"caps\">OS<\/span>, folks immediately think it&#8217;s a new general purpose Linux distribution, as that&#8217;s the <a href=\"https:\/\/www.youtube.com\/watch?v=cUbIkNUFs-4\">square shaped hole<\/a> where everything <span class=\"caps\">OS<\/span>-related ends up. So, first things first, let&#8217;s get a couple of things out of the way about <a href=\"https:\/\/moonforgelinux.org\">Moonforge<\/a>:<\/p>\n<ul>\n<li>Moonforge is <strong>not<\/strong> a general purpose Linux&nbsp;distribution<\/li>\n<li>Moonforge is <strong>not<\/strong> an embedded Linux&nbsp;distribution<\/li>\n<\/ul>\n<h3>What is&nbsp;Moonforge<\/h3>\n<p>Moonforge is a set of feature-based, well-maintained layers for <a href=\"https:\/\/yoctoproject.org\">Yocto<\/a>, that allows you to assemble your own <span class=\"caps\">OS<\/span> for embedded devices, or single-application environments, with specific emphasys on immutable, read-only root file system <span class=\"caps\">OS<\/span> images that are easy to deploy and update, through tight integration with <span class=\"caps\">CI<\/span>\/<span class=\"caps\">CD<\/span>&nbsp;pipelines.<\/p>\n<h3>Why?<\/h3>\n<p>Creating a whole new <span class=\"caps\">OS<\/span> image out of whole cloth is not as hard as it used to be; on the desktop (and devices where you control the hardware), you can reasonably <a href=\"https:\/\/store.steampowered.com\/steamos\/\">get away<\/a> with using existing Linux distributions, filing off the serial numbers, and removing any extant packaging mechanism; or you can rely on the <a href=\"https:\/\/universal-blue.org\/\">containerised tech stack<\/a>, and boot into&nbsp;it.<\/p>\n<p>When it comes to embedded platforms, on the other hand, you&#8217;re still very much working on bespoke, artisanal, locally sourced, organic operating systems. A good number of device manufacturers coalesced their <a href=\"https:\/\/en.wikipedia.org\/wiki\/Board_support_package\">BSPs<\/a> around the <a href=\"https:\/\/www.yoctoproject.org\/\">Yocto Project<\/a> and <a href=\"https:\/\/www.openembedded.org\/wiki\/Main_Page\">OpenEmbedded<\/a>, which simplifies adaptations, but you&#8217;re still supposed to build the thing mostly as a one&nbsp;off.<\/p>\n<p>While Yocto has improved leaps and bounds over the past 15 years, putting together an <span class=\"caps\">OS<\/span> image, especially when it comes to bundling features while keeping the overall size of the base image down, is still an exercise in artisanal&nbsp;knowledge.<\/p>\n<h3>A little detour:&nbsp;Poky<\/h3>\n<p>Twenty years ago, I moved to London to work for this little consultancy called OpenedHand. One of the projects that OpenedHand was working on was taking OpenEmbedded and providing a good set of defaults and layers, in order to create a &#8220;reference distribution&#8221; that would help people getting started with their own project. That reference was called <a href=\"https:\/\/web.archive.org\/web\/20070402231645\/http:\/\/projects.o-hand.com\/poky\">Poky<\/a>.<\/p>\n<p><figure>\n        <figcaption class=\"image-caption\">\n          <p>We had a beaver mascot before it was&nbsp;cool<\/p>\n        <\/figcaption>\n        <div><img src=\"https:\/\/www.bassi.io\/images\/poky-beaver.jpeg\" \/><\/div>\n      <\/figure><\/p>\n<p>These days, Poky exists as part of the Yocto Project, and it&#8217;s still the reference distribution for it, but since it&#8217;s part of Yocto, it has to abide to the basic constraint of the project: you still need to set up your <span class=\"caps\">OS<\/span> using shell scripts and copy-pasting layers and recipes inside your own repository. The Yocto project is working on <a href=\"https:\/\/github.com\/kanavin\/bitbake\/commits\/akanavin\/bitbake-setup\">a setup tool<\/a> to\nsimplify those steps, but there are&nbsp;alternatives\u2026<\/p>\n<h3>Another little detour:&nbsp;Kas<\/h3>\n<p>One alternative is <a href=\"https:\/\/kas.readthedocs.io\/en\/latest\/\">kas<\/a>, a tool that allows you to generate the <code>local.conf<\/code> configuration file used by bitbake through various <span class=\"caps\">YAML<\/span> fragments exported by each layer you&#8217;re interested in, as well as additional fragments that can be used to set up customised&nbsp;environments.<\/p>\n<p>Another feature of kas is that it can spin up the build environment inside a container, which simplifies enourmously its set up time. It avoids unadvertedly contaminating the build, and it makes it very easy to run the build on <span class=\"caps\">CI<\/span>\/<span class=\"caps\">CD<\/span> pipelines that already rely on&nbsp;containers.<\/p>\n<h3>What Moonforge&nbsp;provides<\/h3>\n<p>Moonforge lets you create a new <span class=\"caps\">OS<\/span> in minutes, selecting a series of features you care about from various <a href=\"https:\/\/moonforgelinux.org\/docs\/layers\/\">available layers<\/a>.<\/p>\n<p>Each layer provides a single feature,&nbsp;like:<\/p>\n<ul>\n<li>support for a specific architecture or device (<span class=\"caps\">QEMU<\/span> x86_64,&nbsp;RaspberryPi)<\/li>\n<li>containerisation (through Docker or&nbsp;Podman)<\/li>\n<li>A\/B updates (through <span class=\"caps\">RAUC<\/span>, systemd-sysupdate, and&nbsp;more)<\/li>\n<li>graphical session, using&nbsp;Weston<\/li>\n<li>a <a href=\"https:\/\/webkit.org\/wpe\/\"><span class=\"caps\">WPE<\/span><\/a>&nbsp;environment<\/li>\n<\/ul>\n<p>Every layer comes with its own kas fragment, which describes what the layer needs to add to the project configuration in order to&nbsp;function.<\/p>\n<p>Since every layer is isolated, we can reason about their dependencies and interactions, and we can combine them into a final, custom&nbsp;product.<\/p>\n<p>Through various tools, including kas, we can set up a Moonforge project that generates and validates <span class=\"caps\">OS<\/span> images as the result of a <span class=\"caps\">CI<\/span>\/<span class=\"caps\">CD<\/span> pipeline on platforms like GitLab, GitHub, and BitBucket; <span class=\"caps\">OS<\/span> updates are also generated as part of that pipeline, just as comprehensive <span class=\"caps\">CVE<\/span> reports and Software Bill of Materials (<span class=\"caps\">SBOM<\/span>) through custom Yocto&nbsp;recipes.<\/p>\n<p>More importantly, Moonforge can act both as a reference when it comes to hardware enablement and support for BSPs; and as a reference when building applications that need to interact with specific features coming from a&nbsp;board.<\/p>\n<p>While this is the beginning of the project, it&#8217;s already fairly usable; we are planning a lot more in this space, so keep an eye out on <a href=\"https:\/\/github.com\/moonforgelinux\">the repository<\/a>.<\/p>\n<h3>Trying Moonforge&nbsp;out<\/h3>\n<p>If you want to check out Moonforge, I will point you in the direction of its <a href=\"https:\/\/moonforgelinux.org\/docs\/tutorials\/\">tutorials<\/a>, as well as the <a href=\"https:\/\/github.com\/moonforgelinux\/meta-derivative\/\">meta-derivative<\/a> repository, which should give you a good overview on how Moonforge works, and how you can use&nbsp;it.<\/p>                ","author":{"name":"ebassi","uri":"https:\/\/www.bassi.io\/"}},{"title":"Sergio Villar: Implementing WebXR in WebKit for WPE","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/svillar\/post\/wpe-webxr\/"}},"id":"https:\/\/blogs.igalia.com\/svillar\/post\/wpe-webxr\/","updated":"2026-03-17T08:46:59+00:00","content":"\n                <img class=\"face\" src=\"\/images\/svillar.png\" width=\"103\" height=\"109\" alt=\"\" align=\"right\" style=\"float: right\" \/>\n<p>Since 2022, my main focus has been working on the <a href=\"https:\/\/wolvic.com\">Wolvic browser<\/a>, still the only open source WebXR-capable browser for Android\/AOSP devices (Meta, Pico, Huawei, Lenovo, Lynx, HTC&hellip;) out there. That&rsquo;s an effort that continues to this day (although to a <a href=\"https:\/\/wolvic.com\/blog\/next-steps\/\">much lesser extent nowadays<\/a>). In early 2025, as a consequence of all that work in XR on the web, an opportunity emerged to implement WebXR support in WebKit for the WPE port, and we decided to take it.<\/p>                ","author":{"name":"Sergio Villar","uri":"https:\/\/blogs.igalia.com\/svillar\/"}},{"title":"Ricardo Ca\u00f1uelo Navarro: Why don't we do a demo? Part 1: the plan","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/rcn\/posts\/20260317-why_dont_we_do_a_demo_part_1\/index.html"}},"id":"https:\/\/blogs.igalia.com\/rcn\/posts\/20260317-why_dont_we_do_a_demo_part_1\/index.html","updated":"2026-03-17T07:00:00+00:00","content":"\n<h3>Introduction<\/h3>\n\n        <p><a href=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20250807-first_steps_with_zephyr\/index.html\">Some\n        time ago<\/a>, I saw myself with some extra time in my hands and\n        I started experimenting with Zephyr as a way to reconnect with\n        my professional past and also to see how embedded software looks\n        like nowadays.\n        <\/p>\n\n        <p>Initially, I had no further intentions beyond playing around\n          a bit, gaining enough know-how to undertake typical embedded\n          software projects and doing the occasional upstream\n          contribution here and there, until\n          a <a href=\"https:\/\/blogs.igalia.com\/siglesias\/\">colleague<\/a>\n          told me \"Now that you've spent some time with Zephyr, what do\n          you think about doing a demo about it?\". Not a bad idea. The\n          goal is to have something to show at conferences and that\n          showcases Zephyr's possibilities using a simple\n          application.<\/p>\n\n        <p>At work, I'm not a specialist. What I do most of the time is\n          basically one thing, and it typically doesn't fit in a\n          specific field, area, or team: I solve problems <a href=\"https:\/\/blogs.igalia.com\/rcn\/feed.xml#fn1\" id=\"ref1\"><sup>1<\/sup><\/a>. So this is an example of how to\n          solve a single-sentence problem (\"Let's do a demo\") using\n          whatever means necessary, involving software, hardware,\n          planning, design, logistics, decision making and\n          improvisation. It's also a personal expression of the\n          importance, meaning and value of human work.<\/p>\n\n        <p>The following is a non-exhaustive list of the problems faced\n          along the way and the solutions found.<\/p>\n\n        <h3>Problem 1: the idea<\/h3>\n\n        <p>The starting point is just a phrase: \"Why don't we do a\n          demo?\", and a deadline. Nothing more. The amount of\n          possibilities alone can already be an obstacle if we can't\n          find a way to limit the solution space. Obviously, we'll find\n          limitations and constraints down the road that will shape the\n          final solution but, right now, everything is uncertainty.<\/p>\n\n        <p>What we want to show in the demo is the possibilities offered\n          by Zephyr for embedded development using 100% open source\n          software, how we can undertake complex application development\n          with Zephyr, and show a variety of development cases within\n          the same application.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>There are may approaches to a technical demo. However, having\n          been to conferences with demo booths, it's clear that the live\n          and interactive demos are the ones that gather the most\n          attention of the general public by a large margin. Regardless\n          of the technical merits displayed in the demo, people are\n          drawn to things they can touch, blinking lights, sounds, video\n          games.<\/p>\n\n        <p>So a hard requirement since the beginning was that the demo\n          should be interactive. Fortunately, the nature of the\n          technology behind it lends itself to that easily, although\n          I've seen many Zephyr-based demos that were rather static and\n          only for display. The intention here is to allow the public to\n          actually use it.<\/p>\n\n        <p>Another important thing to take into account is that\n          widespread or hot technologies and buzzwords will be more\n          attractive than obscure or niche terms. Fortunately, I'm\n          building the demo from scratch, so I get to decide what to\n          show. In this case, I picked up\n          <div class=\"tooltip\">BLE<span class=\"tooltiptext\">Bluetooth\n          Low Energy<\/span><\/div>\n          as a base technology. Not world-changing, but familiar enough to everyone.<\/p>\n\n        <p>The goal, then, is to develop a hardware\/software solution\n          using Zephyr and its BLE stack, allowing interaction from the\n          public and incorporating some way to display real-time\n          information about it. The initial idea is to have small\n          battery-powered devices in the demo booth and track their\n          position using trilateration based on any available\n          distance-measurement mechanism available in BLE devices, and\n          have a central device that displays the position of the\n          devices in real time.<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260317-why_dont_we_do_a_demo_part_1\/idea_sketch.jpg\" \/>\n        <\/div>\n\n        <h3>Problem 2: selecting the hardware<\/h3>\n\n        <p>Now that I settled on an initial idea, even if it's in a very\n          rough and sketchy form, with no further technical details, I\n          can start experimenting with the options. The first step is to\n          do some research about the hardware and software possibilities\n          to reach our goal, pick up some evaluation boards and start\n          sketching ideas to have a better understanding of the\n          feasibility of what I want to achieve and the limitations I\n          can find (time, software\/hardware constraints, skills,\n          etc.)<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>A good option for BLE-based applications is to use some of\n          the Nordic development kits. They're easy to source and\n          inexpensive. Besides, the recent nRF54L15 SoC\n          supports <a href=\"https:\/\/www.bluetooth.com\/learn-about-bluetooth\/feature-enhancements\/channel-sounding\/\">channel\n          sounding<\/a>, which promises precise distance estimations\n          between devices. Just what I'm looking for.<\/p>\n\n        <p>I'll need two types of devices for this: one of them needs to\n          be small (wearable size, if possible) and battery-powered. The\n          other type will be at a fixed location and can have a cabled\n          power supply. The idea is to have three devices at fixed\n          locations in the booth measuring the distance to a number of\n          battery-powered devices that will be moving. Then, a central\n          device will collect the distance information from the metering\n          devices and use it to calculate the position of each\n          battery-powered device.<br \/>\n\n          This central device will need to have some way to display the\n          position of the devices in some kind of graphical interface,\n          so I need to search for a device that can connect to the\n          metering devices, that is well supported in Zephyr and that\n          can support some kind of display out-of-the-box.<\/p>\n\n        <p>With all these requirements in mind I came up with this list\n        of devices:<\/p>\n\n        <ul>\n          <li>\n            For the battery-powered\n            devices: <a href=\"https:\/\/wiki.seeedstudio.com\/xiao_nrf54l15_sense_getting_started\/\">Seeed\n            Studio XIAO nRF54L15<\/a>, based on the nRF54L15 SoC.\n          <\/li>\n          <li>\n            For the metering\n            devices: <a href=\"https:\/\/www.nordicsemi.com\/Products\/Development-hardware\/nRF54L15-DK\">nRF54L15\n            DK<\/a> boards from Nordic, also based on the nRF54L15.\n          <\/li>\n          <li>\n            For the central\n            device: <a href=\"https:\/\/www.nordicsemi.com\/Products\/Development-hardware\/nRF52840-DK\">nRF52840\n            DK<\/a> board, which supports a serial SPI touchscreen like\n            <a href=\"https:\/\/www.buydisplay.com\/arduino-3-5-tft-lcd-touch-shield-serial-spi-example-for-mega-due\">this\n            one<\/a>.\n          <\/li>\n        <\/ul>\n\n        <p>All the hardware is already supported in Zephyr, so that\n          should eliminate a lot of the initial friction and save us\n          time.<\/p>\n\n        <h3>Problem 3: practical limitations, redefining the idea<\/h3>\n\n        <p>After some initial experiments with the hardware, running\n          sample BLE applications and getting familiar with the\n          ecosystem, I found out that, while the nRF54L15 hardware\n          supports Bluetooth channel sounding, the Zephyr BLE stack\n          still doesn't support it, so in order to use it I'd need to\n          use\n          Nordic's <a href=\"https:\/\/docs.nordicsemi.com\/bundle\/ncs-latest\/page\/nrfxlib\/softdevice_controller\/README.html\">SoftDevice\n          controller<\/a> instead of the upstream Zephyr controller,\n          together with the\n          <a href=\"https:\/\/www.nordicsemi.com\/Products\/Development-software\/nRF-Connect-SDK\">nRF Connect BLE<\/a> stack.<\/p>\n\n        <p>This is a problem because a key feature of this demo should\n          be that it's done using 100% open source code and, preferably,\n          upstream Zephyr code.<\/p>\n\n        <p>Another, even bigger obstacle, is that it's not clear that\n          collecting distance data from multiple sources simultaneously\n          for trilateration, and doing it for multiple peripherals at\n          the same time, is practically viable. I couldn't find any\n          examples or documentation on it, and I could be entering\n          uncharted territory. Considering that we have a deadline for\n          this, I'd rather find an alternative.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>The immediate solution is to find a less audacious idea to\n          develop using the same hardware that I already have, keeping\n          it interactive but simpler, and keeping the same goals.<\/p>\n\n        <p>The idea I finally settled on is an extension of the typical\n          BLE peripheral -- central application, where the peripheral\n          publishes some services and the central device connects to it\n          and issues\n          <a href=\"https:\/\/academy.nordicsemi.com\/courses\/bluetooth-low-energy-fundamentals\/lessons\/lesson-4-bluetooth-le-data-exchange\/topic\/gatt-operations\/\">GATT<\/a>\n          reads and writes to the peripheral characteristics, but adding\n          a multi-level network topology instead of a simple star\n          network, and adding real-time remote display and control of\n          the devices using a graphical interface. So we'd have three\n          device types: the battery-powered peripherals, which will\n          provide the basic services, then the controller devices, which\n          will connect to the peripherals to control them remotely, and\n          then a console device which will connect to the controllers\n          and can show and control the devices remotely using a\n          graphical interface.<\/p>\n\n        <div align=\"center\">\n          <br \/>\n          <img src=\"https:\/\/blogs.igalia.com\/rcn\/posts\/20260317-why_dont_we_do_a_demo_part_1\/demo_idea.png\" \/>\n        <\/div>\n\n        <p>Zephyr supports BLE Mesh already, but we'd lose part of the\n          challenge of implementing the networking routing ourselves, so\n          I'm keeping things more interesting by implementing a custom\n          tree topology that provides us with finer grained control, and\n          which can be tailored to a specific application use case.<\/p>\n\n        <p>This means that the controller device will need to act both\n          as a BLE central and peripheral device simultaneously, while\n          the peripheral devices will act only as peripherals and the\n          console will be only a central.<\/p>\n\n        <h3>Problem 4: initial planning<\/h3>\n\n        <p>With the development boards at hand, I can start designing\n          and developing the firmwares for the three board types,\n          including testing and documentation. The other certain thing I\n          have right now is a deadline: the conference where we want to\n          show the demo. Now I need to draw a rough plan with concrete\n          dates.<\/p>\n\n        <h3>Solution<\/h3>\n\n        <p>Considering that I'll surely find a few bad surprises down\n          the road and that there'll be uncertainty and problems that I\n          can't yet anticipate, since it's the first time we're doing a\n          demo with these characteristics, I set myself a personal hard\n          deadline: one month before the real hard deadline. Ideally,\n          the firmware should be all done and thoroughly tested one\n          month before that, so that'd leave two full months for\n          additional preparations and for sorting out whichever\n          last-minute obstacles I could find in the end.<\/p>\n\n        <p>Of course, all of this rough planning is based purely on\n          intuition. I could fall into the trap of wanting to plan\n          everything beforehand and write a well-specified roadmap of\n          everything that needs to be done in minute detail, but I'd be\n          setting myself up for failure from the start, since 90% of the\n          work ahead is a big question mark. I'm defining everything as\n          we go, and in cases like this it's much more reasonable to\n          plan and work based on different principles:<\/p>\n\n        <ul>\n          <li>\n            Define reasonable and achievable milestones and iterate\n            based on them.\n          <\/li>\n          <li>\n            Iterate fast and as many times as needed.\n          <\/li>\n          <li>\n            Re-draw the plan after an iteration if needed.\n          <\/li>\n          <li>\n            Be ready to improvise.\n          <\/li>\n          <li>\n            <a href=\"https:\/\/en.wikipedia.org\/wiki\/Kaizen\">Improve\n              incrementally<\/a> and have faith in the process. Don't\n              look at the top of the mountain, you know where it\n              is. Focus on the next meter of path in front.\n          <\/li>\n        <\/ul>\n\n        <p>Doing this as a one-person-army has both pros and cons. Fear\n          and uncertainty are something you have to shoulder on your\n          own, but you're also free to take whatever decision you need\n          whenever you need.<\/p>\n\n        <p>So, now we're ready to start developing. A rough milestones\n          sketch for the firmware development could be:<\/p>\n\n        <ul>\n          <li>\n            Base application for the peripheral: board setup and hardware\n            handling.\n          <\/li>\n          <li>\n            Base application for the controller device: board setup and\n            hardware handling.\n          <\/li>\n          <li>\n            Basic peripheral-central BLE application using the\n            peripheral and controller devices.\n          <\/li>\n          <li>\n            Base application for the console device: board setup and\n            hardware handling.\n          <\/li>\n          <li>\n            Make the controller device work as both a BLE peripheral and\n            central device.\n          <\/li>\n          <li>\n            Incorporate the console device to the peripheral +\n            controller application.\n          <\/li>\n          <li>\n            Graphical interface design and implementation.\n          <\/li>\n        <\/ul>\n\n        <p>Testing and simulation should be a part of every\n          milestone.<\/p>\n\n        <p>In the next post we'll go through the firmware development\n          part of the project.<\/p>\n\n        <div class=\"footnotes\">\n          <p id=\"fn1\">1: I like to think that's a specialty,\n          though. Maybe one day that'll be a role in the\n          company.<a href=\"https:\/\/blogs.igalia.com\/rcn\/feed.xml#ref1\">\u21a9<\/a><\/p>\n        <\/div>                ","author":{"name":"rcn","uri":"https:\/\/blogs.igalia.com\/rcn"}},{"title":"Hironori Fujii: Async Scrolling Improvements","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/fujii\/async-scrolling-improvements\/"}},"id":"https:\/\/blogs.igalia.com\/fujii\/async-scrolling-improvements\/","updated":"2026-03-16T00:00:00+00:00","content":"\n<p>WPE WebKit and WebKitGTK support <a href=\"https:\/\/docs.webkit.org\/Ports\/WebKitGTK%20and%20WPE%20WebKit\/Graphics.html#async-scrolling\">async scrolling<\/a> for wheel events.\nI landed several improvements for the upcoming 2.52 release.<\/p>\n<ul>\n<li><a href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=305451\">Bug 305451<\/a> \u2013 wheel event async scrolling doesn\u2019t start while the main thread is blocked<\/li>\n<li><a href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=305560\">Bug 305560<\/a> \u2013 rendering glitches for unpainted tiles<\/li>\n<li><a href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=305561\">Bug 305561<\/a> \u2013 Paint scrollbars in the scrolling thread for async scrolling<\/li>\n<\/ul>\n<p>Here are videos of before and after the changes.\nThis is <a href=\"https:\/\/bug-305441-attachments.webkit.org\/attachment.cgi?id=477989\">the test content<\/a>.<\/p>\n<p><video src=\"https:\/\/blogs.igalia.com\/fujii\/video\/async-scrolling-before.mp4\" controls=\"\"><\/video>\n<video src=\"https:\/\/blogs.igalia.com\/fujii\/video\/async-scrolling-after.mp4\" controls=\"\"><\/video><\/p>\n<p>There is still room for further improvement.<\/p>\n<ul>\n<li>The scrollbar hiding animation timer is still running in the main thread.\n<ul>\n<li>It can use CoordinatedPlatformLayer::setAnimations.<\/li>\n<li>Or CoordinatedPlatformLayer::setOpacity.<\/li>\n<\/ul>\n<\/li>\n<li>Add the showing animation and transition animations of mouse hover states like GTK Adwaita theme<\/li>\n<li>Support touch and gesture events async scrolling<\/li>\n<\/ul>                ","author":{"name":"Hironori Fujii","uri":"https:\/\/blogs.igalia.com\/fujii\/fujii"}},{"title":"Kate Lee: Building a Custom HTML Context Menu with the New WPEPlatform API","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/"}},"id":"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/","updated":"2026-03-16T00:00:00+00:00","content":"\n<p><a href=\"https:\/\/wpewebkit.org\/\">WPE WebKit<\/a> is a WebKit port optimized for embedded devices \u2014 think set-top boxes, digital signage, kiosk displays, and in-vehicle infotainment systems. It is developed by <a href=\"https:\/\/www.igalia.com\/\">Igalia<\/a> and powers web experiences on millions of devices worldwide, from set-top boxes to smart TVs and beyond.<\/p>\n<p>WPE WebKit has recently introduced a <strong>brand-new platform API called WPEPlatform<\/strong>, which replaces the legacy <code>libwpe<\/code> + <code>wpebackend-fdo<\/code> stack. In this post, I will walk you through building a minimal WPE browser launcher using <strong>only the new WPEPlatform API<\/strong>, and demonstrate one of its newly available features: the <strong>Context Menu API<\/strong> \u2014 rendered entirely as an HTML overlay.<\/p>\n<h2 id=\"why-a-new-api\" tabindex=\"-1\">Why a New API? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>The legacy stack (<code>libwpe<\/code> + <code>wpebackend-fdo<\/code> + Cog platform plugins) had several pain points: nested Wayland compositor complexity, dependency on Mesa\u2019s now-deprecated <code>EGL_WL_bind_wayland_display<\/code> extension, rigid C function-pointer tables, and platform code scattered across three libraries.<\/p>\n<p>The <strong>new WPEPlatform API<\/strong> replaces all of this with a single, clean <a href=\"https:\/\/docs.gtk.org\/gobject\/\">GObject<\/a>-based layer \u2014 providing automatic backend creation, DMA-BUF direct buffer sharing, unified window management (fullscreen, maximize, resize, title), and easy language bindings via GObject Introspection.<\/p>\n<blockquote>\n<p><strong>Timeline<\/strong>: The stable release of WPEPlatform is planned for <strong>September 2026<\/strong>. At that point, the legacy API will be officially deprecated. We strongly recommend new projects to adopt the WPEPlatform API from the start.<\/p>\n<\/blockquote>\n<h2 id=\"wpeplatform-launcher-a-minimal-browser-in-250-lines\" tabindex=\"-1\">WPEPlatform Launcher: A Minimal Browser in ~250 Lines <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>To demonstrate the new API, I built <strong>WPEPlatformLauncher<\/strong> \u2014 a minimal but functional WPE WebKit browser that uses only the WPEPlatform API. No legacy <code>libwpe<\/code>, no <code>wpebackend-fdo<\/code>, no Cog \u2014 just the new API.<\/p>\n<p>The full source code is available at:\n<strong><a href=\"https:\/\/github.com\/kate-k-lee\/WebKit\/commit\/aed6402b267475f79ae7a8d417d18239b53be651\">kate-k-lee\/WebKit@aed6402<\/a><\/strong><\/p>\n<h3 id=\"how-simple-is-it\" tabindex=\"-1\">How Simple Is It? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<p>Here is the core of the launcher \u2014 creating a WebView with the new API:<\/p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token comment\">\/* WPEPlatform backend is created automatically \u2014 no manual setup needed *\/<\/span><br \/><span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> webView <span class=\"token operator\">=<\/span> <span class=\"token function\">WEBKIT_WEB_VIEW<\/span><span class=\"token punctuation\">(<\/span><span class=\"token function\">g_object_new<\/span><span class=\"token punctuation\">(<\/span>WEBKIT_TYPE_WEB_VIEW<span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token string\">\"web-context\"<\/span><span class=\"token punctuation\">,<\/span> webContext<span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token string\">\"network-session\"<\/span><span class=\"token punctuation\">,<\/span> networkSession<span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token string\">\"settings\"<\/span><span class=\"token punctuation\">,<\/span> settings<span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token string\">\"user-content-manager\"<\/span><span class=\"token punctuation\">,<\/span> userContentManager<span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token keyword\">nullptr<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/><span class=\"token comment\">\/* Get the WPEPlatform view \u2014 this is where the new API shines *\/<\/span><br \/><span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> wpeView <span class=\"token operator\">=<\/span> <span class=\"token function\">webkit_web_view_get_wpe_view<\/span><span class=\"token punctuation\">(<\/span>webView<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> toplevel <span class=\"token operator\">=<\/span> <span class=\"token function\">wpe_view_get_toplevel<\/span><span class=\"token punctuation\">(<\/span>wpeView<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/><span class=\"token comment\">\/* Window management: fullscreen, resize, title \u2014 all built-in *\/<\/span><br \/><span class=\"token function\">wpe_toplevel_fullscreen<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token function\">wpe_toplevel_resize<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">,<\/span> <span class=\"token number\">1920<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">1080<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token function\">wpe_toplevel_set_title<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"WPEPlatform Launcher\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/><span class=\"token comment\">\/* Input events: just connect a GObject signal *\/<\/span><br \/><span class=\"token function\">g_signal_connect<\/span><span class=\"token punctuation\">(<\/span>wpeView<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"event\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token function\">G_CALLBACK<\/span><span class=\"token punctuation\">(<\/span>onViewEvent<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> webView<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>Compare this with the legacy API, which required:<\/p>\n<ol>\n<li>Manually creating a <code>WPEToolingBackends::ViewBackend<\/code><\/li>\n<li>Wrapping it in a <code>WebKitWebViewBackend<\/code> with a destroy callback<\/li>\n<li>Creating a C++ <code>InputClient<\/code> class and registering it<\/li>\n<li>Having no window management (no maximize, minimize, title, etc.)<\/li>\n<\/ol>\n<p>The new API handles backend creation, display detection, and input forwarding automatically.<\/p>\n<h3 id=\"keyboard-shortcuts\" tabindex=\"-1\">Keyboard Shortcuts <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<p>Handling keyboard events is straightforward with the WPEPlatform event system:<\/p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token keyword\">static<\/span> gboolean <span class=\"token function\">onViewEvent<\/span><span class=\"token punctuation\">(<\/span>WPEView<span class=\"token operator\">*<\/span> view<span class=\"token punctuation\">,<\/span> WPEEvent<span class=\"token operator\">*<\/span> event<span class=\"token punctuation\">,<\/span> WebKitWebView<span class=\"token operator\">*<\/span> webView<span class=\"token punctuation\">)<\/span><br \/><span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token function\">wpe_event_get_event_type<\/span><span class=\"token punctuation\">(<\/span>event<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">!=<\/span> WPE_EVENT_KEYBOARD_KEY_DOWN<span class=\"token punctuation\">)<\/span><br \/>        <span class=\"token keyword\">return<\/span> FALSE<span class=\"token punctuation\">;<\/span><br \/><br \/>    <span class=\"token keyword\">auto<\/span> modifiers <span class=\"token operator\">=<\/span> <span class=\"token function\">wpe_event_get_modifiers<\/span><span class=\"token punctuation\">(<\/span>event<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token keyword\">auto<\/span> keyval <span class=\"token operator\">=<\/span> <span class=\"token function\">wpe_event_keyboard_get_keyval<\/span><span class=\"token punctuation\">(<\/span>event<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>    <span class=\"token comment\">\/* Ctrl+Q: Quit *\/<\/span><br \/>    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">(<\/span>modifiers <span class=\"token operator\">&amp;<\/span> WPE_MODIFIER_KEYBOARD_CONTROL<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">&amp;&amp;<\/span> keyval <span class=\"token operator\">==<\/span> WPE_KEY_q<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>        <span class=\"token function\">g_application_quit<\/span><span class=\"token punctuation\">(<\/span><span class=\"token function\">g_application_get_default<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">return<\/span> TRUE<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token punctuation\">}<\/span><br \/><br \/>    <span class=\"token comment\">\/* F11: Toggle fullscreen via WPEToplevel *\/<\/span><br \/>    <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span>keyval <span class=\"token operator\">==<\/span> WPE_KEY_F11<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>        <span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> toplevel <span class=\"token operator\">=<\/span> <span class=\"token function\">wpe_view_get_toplevel<\/span><span class=\"token punctuation\">(<\/span>view<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token function\">wpe_toplevel_get_state<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">&amp;<\/span> WPE_TOPLEVEL_STATE_FULLSCREEN<span class=\"token punctuation\">)<\/span><br \/>            <span class=\"token function\">wpe_toplevel_unfullscreen<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">else<\/span><br \/>            <span class=\"token function\">wpe_toplevel_fullscreen<\/span><span class=\"token punctuation\">(<\/span>toplevel<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">return<\/span> TRUE<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token punctuation\">}<\/span><br \/><br \/>    <span class=\"token keyword\">return<\/span> FALSE<span class=\"token punctuation\">;<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h2 id=\"html-based-context-menu-solving-the-no-native-ui-challenge\" tabindex=\"-1\">HTML-Based Context Menu: Solving the \u201cNo Native UI\u201d Challenge <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>WPE WebKit is designed for embedded environments where there is <strong>no native UI toolkit<\/strong> \u2014 no GTK, no Qt. This means features like context menus (right-click menus) that desktop browsers take for granted need to be implemented by the application.<\/p>\n<p>The approach: <strong>intercept WebKit\u2019s <code>context-menu<\/code> signal, read the menu items, and render them as an HTML\/CSS overlay<\/strong> injected into the page DOM.<\/p>\n<h3 id=\"the-architecture\" tabindex=\"-1\">The Architecture <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<pre><code>User right-clicks\n  \u2192 WebKit emits &quot;context-menu&quot; signal\n  \u2192 onContextMenu() handler:\n      1. Reads menu items via webkit_context_menu_get_items()\n      2. Gets position via webkit_context_menu_get_position()\n      3. Builds JavaScript that creates DOM elements\n      4. Injects via webkit_web_view_evaluate_javascript()\n      5. Returns TRUE (suppresses default menu)\n\nUser clicks a menu item\n  \u2192 JS: window.webkit.messageHandlers.contextMenuAction.postMessage(actionId)\n  \u2192 C: onContextMenuAction() receives the action ID\n      \u2192 Executes: webkit_web_view_go_back(), execute_editing_command(&quot;Copy&quot;), etc.\n\nUser clicks outside the menu\n  \u2192 JS: overlay click handler removes the DOM elements\n<\/code><\/pre>\n<h3 id=\"reading-context-menu-items\" tabindex=\"-1\">Reading Context Menu Items <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<p>The Context Menu API provides everything we need:<\/p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token keyword\">static<\/span> gboolean <span class=\"token function\">onContextMenu<\/span><span class=\"token punctuation\">(<\/span>WebKitWebView<span class=\"token operator\">*<\/span> webView<span class=\"token punctuation\">,<\/span><br \/>    WebKitContextMenu<span class=\"token operator\">*<\/span> contextMenu<span class=\"token punctuation\">,<\/span> gpointer <span class=\"token comment\">\/* event *\/<\/span><span class=\"token punctuation\">,<\/span><br \/>    WebKitHitTestResult<span class=\"token operator\">*<\/span> hitTestResult<span class=\"token punctuation\">,<\/span> gpointer<span class=\"token punctuation\">)<\/span><br \/><span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token comment\">\/* Save hit test result for link-related actions *\/<\/span><br \/>    savedHitTestResult <span class=\"token operator\">=<\/span> <span class=\"token function\">WEBKIT_HIT_TEST_RESULT<\/span><span class=\"token punctuation\">(<\/span><span class=\"token function\">g_object_ref<\/span><span class=\"token punctuation\">(<\/span>hitTestResult<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>    <span class=\"token comment\">\/* Iterate through menu items *\/<\/span><br \/>    GList<span class=\"token operator\">*<\/span> items <span class=\"token operator\">=<\/span> <span class=\"token function\">webkit_context_menu_get_items<\/span><span class=\"token punctuation\">(<\/span>contextMenu<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token keyword\">for<\/span> <span class=\"token punctuation\">(<\/span>GList<span class=\"token operator\">*<\/span> l <span class=\"token operator\">=<\/span> items<span class=\"token punctuation\">;<\/span> l<span class=\"token punctuation\">;<\/span> l <span class=\"token operator\">=<\/span> l<span class=\"token operator\">-><\/span>next<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>        <span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> item <span class=\"token operator\">=<\/span> <span class=\"token function\">WEBKIT_CONTEXT_MENU_ITEM<\/span><span class=\"token punctuation\">(<\/span>l<span class=\"token operator\">-><\/span>data<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>        <span class=\"token keyword\">if<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token function\">webkit_context_menu_item_is_separator<\/span><span class=\"token punctuation\">(<\/span>item<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>            <span class=\"token comment\">\/* Render as a horizontal line *\/<\/span><br \/>            <span class=\"token keyword\">continue<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token punctuation\">}<\/span><br \/><br \/>        <span class=\"token keyword\">const<\/span> <span class=\"token keyword\">char<\/span><span class=\"token operator\">*<\/span> title <span class=\"token operator\">=<\/span> <span class=\"token function\">webkit_context_menu_item_get_title<\/span><span class=\"token punctuation\">(<\/span>item<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">auto<\/span> action <span class=\"token operator\">=<\/span> <span class=\"token function\">webkit_context_menu_item_get_stock_action<\/span><span class=\"token punctuation\">(<\/span>item<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token comment\">\/* Build HTML element with title and action ID *\/<\/span><br \/>    <span class=\"token punctuation\">}<\/span><br \/><br \/>    <span class=\"token comment\">\/* Get position for menu placement *\/<\/span><br \/>    gint posX <span class=\"token operator\">=<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">,<\/span> posY <span class=\"token operator\">=<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token function\">webkit_context_menu_get_position<\/span><span class=\"token punctuation\">(<\/span>contextMenu<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>posX<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>posY<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>    <span class=\"token keyword\">return<\/span> TRUE<span class=\"token punctuation\">;<\/span> <span class=\"token comment\">\/* Suppress default menu *\/<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h3 id=\"the-html-menu-dark-theme-for-embedded\" tabindex=\"-1\">The HTML Menu: Dark Theme for Embedded <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<p>The context menu is rendered with a dark theme CSS, designed for embedded\/kiosk displays:<\/p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">#__wpe_ctx_menu<\/span> <span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token property\">position<\/span><span class=\"token punctuation\">:<\/span> fixed<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">min-width<\/span><span class=\"token punctuation\">:<\/span> 180px<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">background<\/span><span class=\"token punctuation\">:<\/span> #2b2b2b<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">border<\/span><span class=\"token punctuation\">:<\/span> 1px solid #505050<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">border-radius<\/span><span class=\"token punctuation\">:<\/span> 6px<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">padding<\/span><span class=\"token punctuation\">:<\/span> 4px 0<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">box-shadow<\/span><span class=\"token punctuation\">:<\/span> 0 8px 24px <span class=\"token function\">rgba<\/span><span class=\"token punctuation\">(<\/span>0<span class=\"token punctuation\">,<\/span>0<span class=\"token punctuation\">,<\/span>0<span class=\"token punctuation\">,<\/span>0.4<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">font-family<\/span><span class=\"token punctuation\">:<\/span> system-ui<span class=\"token punctuation\">,<\/span> sans-serif<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">font-size<\/span><span class=\"token punctuation\">:<\/span> 13px<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">color<\/span><span class=\"token punctuation\">:<\/span> #e0e0e0<span class=\"token punctuation\">;<\/span><br \/><span class=\"token punctuation\">}<\/span><br \/><br \/><span class=\"token selector\">.__wpe_ctx_item:hover<\/span> <span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token property\">background<\/span><span class=\"token punctuation\">:<\/span> #0060df<span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token property\">color<\/span><span class=\"token punctuation\">:<\/span> #ffffff<span class=\"token punctuation\">;<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h3 id=\"handling-actions-via-script-message-handler\" tabindex=\"-1\">Handling Actions via Script Message Handler <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h3>\n<p>Communication between the HTML menu and the C application uses WebKit\u2019s script message handler mechanism:<\/p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token comment\">\/* Register message handler *\/<\/span><br \/><span class=\"token keyword\">auto<\/span><span class=\"token operator\">*<\/span> ucm <span class=\"token operator\">=<\/span> <span class=\"token function\">webkit_user_content_manager_new<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token function\">webkit_user_content_manager_register_script_message_handler<\/span><span class=\"token punctuation\">(<\/span><br \/>    ucm<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"contextMenuAction\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token keyword\">nullptr<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token function\">g_signal_connect<\/span><span class=\"token punctuation\">(<\/span>ucm<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"script-message-received::contextMenuAction\"<\/span><span class=\"token punctuation\">,<\/span><br \/>    <span class=\"token function\">G_CALLBACK<\/span><span class=\"token punctuation\">(<\/span>onContextMenuAction<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token keyword\">nullptr<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token comment\">\/\/ In the generated HTML menu item:<\/span><br \/>item<span class=\"token punctuation\">.<\/span><span class=\"token function\">addEventListener<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">'click'<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token keyword\">function<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>    window<span class=\"token punctuation\">.<\/span>webkit<span class=\"token punctuation\">.<\/span>messageHandlers<span class=\"token punctuation\">.<\/span>contextMenuAction<span class=\"token punctuation\">.<\/span><span class=\"token function\">postMessage<\/span><span class=\"token punctuation\">(<\/span>actionId<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token comment\">\/* Handle the action in C *\/<\/span><br \/><span class=\"token keyword\">static<\/span> <span class=\"token keyword\">void<\/span> <span class=\"token function\">onContextMenuAction<\/span><span class=\"token punctuation\">(<\/span>WebKitUserContentManager<span class=\"token operator\">*<\/span><span class=\"token punctuation\">,<\/span> JSCValue<span class=\"token operator\">*<\/span> value<span class=\"token punctuation\">,<\/span> gpointer<span class=\"token punctuation\">)<\/span><br \/><span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token keyword\">int<\/span> actionId <span class=\"token operator\">=<\/span> <span class=\"token function\">jsc_value_to_int32<\/span><span class=\"token punctuation\">(<\/span>value<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>    <span class=\"token keyword\">switch<\/span> <span class=\"token punctuation\">(<\/span>actionId<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br \/>    <span class=\"token keyword\">case<\/span> WEBKIT_CONTEXT_MENU_ACTION_RELOAD<span class=\"token operator\">:<\/span><br \/>        <span class=\"token function\">webkit_web_view_reload<\/span><span class=\"token punctuation\">(<\/span>webView<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">break<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token keyword\">case<\/span> WEBKIT_CONTEXT_MENU_ACTION_COPY<span class=\"token operator\">:<\/span><br \/>        <span class=\"token function\">webkit_web_view_execute_editing_command<\/span><span class=\"token punctuation\">(<\/span>webView<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"Copy\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">break<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token keyword\">case<\/span> WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK<span class=\"token operator\">:<\/span><br \/>        <span class=\"token function\">webkit_web_view_load_uri<\/span><span class=\"token punctuation\">(<\/span>webView<span class=\"token punctuation\">,<\/span><br \/>            <span class=\"token function\">webkit_hit_test_result_get_link_uri<\/span><span class=\"token punctuation\">(<\/span>savedHitTestResult<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>        <span class=\"token keyword\">break<\/span><span class=\"token punctuation\">;<\/span><br \/>    <span class=\"token comment\">\/* ... more actions ... *\/<\/span><br \/>    <span class=\"token punctuation\">}<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h2 id=\"demo\" tabindex=\"-1\">Demo <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>Here is the WPEPlatformLauncher in action, showing the HTML context menu with various actions:<\/p>\n<img src=\"https:\/\/blogs.igalia.com\/klee\/blog\/wpeplatform-launcher-context-menu\/context-menu-demo.gif\" alt=\"WPEPlatformLauncher context menu demo\" \/>\n<p><em>Right-clicking shows the HTML context menu. Clicking \u201cReload\u201d triggers an actual page reload.<\/em><\/p>\n<img src=\"https:\/\/blogs.igalia.com\/klee\/blog\/wpeplatform-launcher-context-menu\/context-menu-link-demo.gif\" alt=\"Context menu on a link\" \/>\n<p><em>Right-clicking a link shows link-specific actions like \u201cOpen Link\u201d and \u201cCopy Link Address\u201d.<\/em><\/p>\n<h2 id=\"building-and-running\" tabindex=\"-1\">Building and Running <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>I built and ran the WPEPlatformLauncher inside a container using the <a href=\"https:\/\/github.com\/Igalia\/webkit-container-sdk\">WebKit Container SDK<\/a>, which provides a pre-configured development environment with all the dependencies needed to build WPE WebKit.<\/p>\n<p>The WPEPlatformLauncher integrates into the WebKit build system:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token comment\"># Build WPE WebKit with the launcher<\/span><br \/>Tools\/Scripts\/build-webkit <span class=\"token parameter variable\">--wpe<\/span> <span class=\"token parameter variable\">--release<\/span><br \/><br \/><span class=\"token comment\"># Run<\/span><br \/>.\/WebKitBuild\/WPE\/Release\/bin\/WPEPlatformLauncher https:\/\/wpewebkit.org<br \/><br \/><span class=\"token comment\"># Run in fullscreen (kiosk mode)<\/span><br \/>.\/WebKitBuild\/WPE\/Release\/bin\/WPEPlatformLauncher <span class=\"token parameter variable\">--fullscreen<\/span> https:\/\/your-app.com<\/code><\/pre>\n<p>The full source is a single <code>main.cpp<\/code> file (~600 lines including the context menu), integrated into the WebKit tree alongside MiniBrowser:<\/p>\n<pre><code>WebKit\/Tools\/\n\u251c\u2500\u2500 MiniBrowser\/wpe\/          \u2190 Existing (supports both old + new API)\n\u251c\u2500\u2500 WPEPlatformLauncher\/      \u2190 New (WPEPlatform API only)\n\u2502   \u251c\u2500\u2500 main.cpp\n\u2502   \u2514\u2500\u2500 CMakeLists.txt\n\u2514\u2500\u2500 PlatformWPE.cmake         \u2190 Modified to add WPEPlatformLauncher\n<\/code><\/pre>\n<h2 id=\"summary\" tabindex=\"-1\">Summary <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<p>The new <strong>WPEPlatform API<\/strong> makes building WPE WebKit applications significantly simpler:<\/p>\n<ul>\n<li><strong>No manual backend setup<\/strong> \u2014 the platform is detected and configured automatically<\/li>\n<li><strong>GObject-based<\/strong> \u2014 signals, properties, and ref counting instead of C function pointers<\/li>\n<li><strong>DMA-BUF direct sharing<\/strong> \u2014 no dependency on Mesa\u2019s deprecated EGL extensions<\/li>\n<li><strong>Unified window management<\/strong> \u2014 fullscreen, maximize, minimize, resize, and title<\/li>\n<li><strong>Language binding friendly<\/strong> \u2014 works with Python, JavaScript, and more via GObject Introspection<\/li>\n<\/ul>\n<p>For embedded browser developers building kiosk UIs, set-top box interfaces, or digital signage with WPE WebKit \u2014 now is the time to adopt the new API. The stable release is coming in September 2026, and the legacy stack (<code>libwpe<\/code>, <code>wpebackend-fdo<\/code>, Cog) will be deprecated at that point.<\/p>\n<h2 id=\"resources\" tabindex=\"-1\">Resources <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/klee\/building-a-custom-html-context-menu-with-the-new-wpeplatform-api\/\">#<\/a><\/h2>\n<ul>\n<li><a href=\"https:\/\/wpewebkit.org\/\">WPE WebKit official site<\/a><\/li>\n<li><a href=\"https:\/\/wpewebkit.org\/reference\/2.51.92\/wpe-webkit-2.0\/index.html\">WPE WebKit API Reference (2.51.92)<\/a><\/li>\n<li><a href=\"https:\/\/wpewebkit.org\/reference\/2.51.92\/wpe-webkit-2.0\/class.ContextMenu.html\">WebKitContextMenu API Reference<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/kate-k-lee\/WebKit\/commit\/aed6402b267475f79ae7a8d417d18239b53be651\">WPEPlatformLauncher source code<\/a><\/li>\n<li><a href=\"https:\/\/www.igalia.com\/project\/wpe\">Igalia \u2014 WPE WebKit<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Igalia\/cog\">Cog \u2014 WPE launcher (legacy)<\/a><\/li>\n<\/ul>                ","author":{"name":"Kate Lee","uri":"https:\/\/blogs.igalia.com\/klee\/"}},{"title":"Hironori Fujii: Building WebKit and libsoup with AddressSanitizer (ASan)","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/fujii\/building-webkit-and-libsoup-with-addresssanitizer-asan\/"}},"id":"https:\/\/blogs.igalia.com\/fujii\/building-webkit-and-libsoup-with-addresssanitizer-asan\/","updated":"2026-03-11T00:00:00+00:00","content":"\n<p>I built libsoup and WebKit with ASan today.\nIt works almost out of the box.\nI used Clang.\nGCC also supports ASan, but WebKit has a problem with it.\n<a href=\"https:\/\/github.com\/Igalia\/webkit-container-sdk\">WebKit Container SDK<\/a> is based on Ubuntu 20.04 LTS at the moment.\nIt contains clang 18 by default.<\/p>\n<p>Installed required packages.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">sudo<\/span> <span class=\"token function\">apt<\/span> <span class=\"token function\">install<\/span> libclang-rt-18-dev llvm-18-dev<\/code><\/pre>\n<p>Set env vars.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token builtin class-name\">export<\/span> <span class=\"token assign-left variable\">CC<\/span><span class=\"token operator\">=<\/span>clang <span class=\"token assign-left variable\">CXX<\/span><span class=\"token operator\">=<\/span>clang++<\/code><\/pre>\n<p>Passed some flags to libsoup.<\/p>\n<pre class=\"language-diff\" tabindex=\"0\"><code class=\"language-diff\"><span class=\"token coord\">--- \/jhbuild\/webkit-sdk-deps.modules.orig<\/span><br \/><span class=\"token coord\">+++ \/jhbuild\/webkit-sdk-deps.modules<\/span><br \/><span class=\"token coord\">@@ -149,7 +149,7 @@<\/span><br \/><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">    &lt;\/dependencies><br \/><\/span><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">  &lt;\/meson><br \/><\/span><\/span><br \/><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-<\/span><span class=\"token line\">  &lt;meson id=\"libsoup\" mesonargs=\"-Dtests=false\"><br \/><\/span><\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">  &lt;meson id=\"libsoup\" mesonargs=\"-Dtests=false -Db_sanitize=address -Db_lundef=false\"><br \/><\/span><\/span><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">    &lt;branch repo=\"github.com\"<br \/><\/span><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">            checkoutdir=\"libsoup\"<br \/><\/span><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">            module=\"GNOME\/libsoup.git\" tag=\"3.6.6\"\/><\/span><\/span><\/code><\/pre>\n<p>Then, build and install libsoup.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">jhbuild buildone <span class=\"token parameter variable\">-f<\/span> libsoup<\/code><\/pre>\n<p>Then, build WebKit with ASan.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">.\/Tools\/Scripts\/build-webkit <span class=\"token parameter variable\">--gtk<\/span> <span class=\"token parameter variable\">--release<\/span> <span class=\"token parameter variable\">--cmakeargs<\/span><span class=\"token operator\">=<\/span>-DENABLE_SANITIZERS<span class=\"token operator\">=<\/span>address<\/code><\/pre>\n<p>WebKit has a lot of memory leaks by design. Don\u2019t detect leaks.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token builtin class-name\">export<\/span> <span class=\"token assign-left variable\">ASAN_OPTIONS<\/span><span class=\"token operator\">=<\/span>detect_leaks<span class=\"token operator\">=<\/span><span class=\"token number\">0<\/span><\/code><\/pre>\n<p>For run-webkit-tests, I had to modify a script a bit.<\/p>\n<pre class=\"language-diff\" tabindex=\"0\"><code class=\"language-diff\">diff --git a\/Tools\/Scripts\/webkitpy\/port\/driver.py b\/Tools\/Scripts\/webkitpy\/port\/driver.py<br \/>index eb12801a455b..c9f74eeab4e2 100644<br \/><span class=\"token coord\">--- a\/Tools\/Scripts\/webkitpy\/port\/driver.py<\/span><br \/><span class=\"token coord\">+++ b\/Tools\/Scripts\/webkitpy\/port\/driver.py<\/span><br \/>@@ -482,7 +482,7 @@ class Driver(object):<br \/><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">        else:<br \/><\/span><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">            environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)<br \/><\/span><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">        environment['LOCAL_RESOURCE_ROOT'] = str(self._port.layout_tests_dir())<br \/><\/span><\/span><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-<\/span><span class=\"token line\">        environment['ASAN_OPTIONS'] = \"allocator_may_return_null=1\"<br \/><\/span><\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">        environment['ASAN_OPTIONS'] = \"allocator_may_return_null=1:detect_leaks=0\"<br \/><\/span><\/span><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">        environment['__XPC_ASAN_OPTIONS'] = environment['ASAN_OPTIONS']<br \/><\/span><\/span><br \/><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">        # Disable vnode-guard related simulated crashes for WKTR \/ DRT (rdar:\/\/problem\/40674034).<\/span><\/span><\/code><\/pre>\n<p>That\u2019s it. Enjoy.<\/p>                ","author":{"name":"Hironori Fujii","uri":"https:\/\/blogs.igalia.com\/fujii\/fujii"}},{"title":"Andy Wingo: nominal types in webassembly","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/wingolog.org\/archives\/2026\/03\/10\/nominal-types-in-webassembly"}},"id":"https:\/\/wingolog.org\/2026\/03\/10\/nominal-types-in-webassembly","updated":"2026-03-10T08:19:34+00:00","content":"\n<div><p>Before the managed data types extension to WebAssembly was incorporated\nin the standard, there was a huge debate about type equality.  The end\nresult is that if you have two types in a Wasm module that look the\nsame, like this:<\/p><pre class=\"pre-wat\">(type $t (struct i32))\n(type $u (struct i32))\n<\/pre><p>Then they are for all intents and purposes equivalent.  When a Wasm\nimplementation loads up a module, it has to partition the module\u2019s types\ninto equivalence classes.  When the Wasm program references a given type\nby name, as in <tt>(struct.get $t 0)<\/tt> which would get the first field of\ntype <tt>$t<\/tt>, it maps <tt>$t<\/tt> to the equivalence class containing <tt>$t<\/tt> and\n<tt>$u<\/tt>.  See the <a href=\"https:\/\/webassembly.github.io\/spec\/core\/valid\/conventions.html#rolling-and-unrolling\">spec<\/a>, for more details.<\/p><p>This is a form of <i>structural type equality<\/i>.  Sometimes this is what you\nwant.  But not always!  Sometimes you want <i>nominal types<\/i>, in which no\ntype declaration is equivalent to any other.  WebAssembly doesn\u2019t have\nthat, but it has something close: <i>recursive type groups<\/i>.  In fact, the\ntype declarations above are equivalent to these:<\/p><pre class=\"pre-wat\">(rec (type $t (struct i32)))\n(rec (type $u (struct i32)))\n<\/pre><p>Which is to say, each type is in a group containing just itself.  One\nthing that this allows is self-recursion, as in:<\/p><pre class=\"pre-way\">(type $succ (struct (ref null $succ)))\n<\/pre><p>Here the struct\u2019s field is itself a reference to a <tt>$succ<\/tt> struct, or\nnull (because it\u2019s <tt>ref null<\/tt> and not just <tt>ref<\/tt>).<\/p><p>To allow for mutual recursion between types, you put them in the same <tt>rec<\/tt>\ngroup, instead of each having its own:<\/p><pre class=\"pre-wat\">(rec\n (type $t (struct i32))\n (type $u (struct i32)))\n<\/pre><p>Between <tt>$t<\/tt> and <tt>$u<\/tt> we don\u2019t have mutual recursion though, so why\nbother?  Well <tt>rec<\/tt> groups have another role, which is that they are the\nunit of structural type equivalence.  In this case, types <tt>$t<\/tt> and <tt>$u<\/tt>\nare not in the same equivalence class, because they are part of the same\n<tt>rec<\/tt> group.  Again, see <a href=\"https:\/\/webassembly.github.io\/spec\/core\/valid\/conventions.html#defined-types\">the spec<\/a>.<\/p><p>Within a Wasm module, <tt>rec<\/tt> gives you an approximation of nominal\ntyping.  But what about between modules?  Let\u2019s imagine that <tt>$t<\/tt>\ncarries important capabilities, and you don\u2019t want another module to be\nable to forge those capabilities.  In this case, <tt>rec<\/tt> is not enough:\nthe other module could define an equivalent <tt>rec<\/tt> group, construct a\n<tt>$t<\/tt>, and pass it to our module; because of isorecursive type equality,\nthis would work just fine.  What to do?<\/p><h3>curs\u00e8d nominal typing<\/h3><p>I said before that Wasm doesn\u2019t have nominal types.  That was true in\nthe past, but no more!  The <a href=\"https:\/\/github.com\/WebAssembly\/exception-handling\/blob\/main\/proposals\/exception-handling\/Exceptions.md\">nominal typing\nproposal<\/a>\nwas incorporated in the standard last July.  Its vocabulary is a bit\nodd, though.  You have to define your data types with the <a href=\"https:\/\/webassembly.github.io\/spec\/core\/syntax\/types.html#tag-types\"><tt>tag<\/tt> keyword<\/a>:<\/p><pre>(tag $v (param $secret i32))\n<\/pre><p>Syntactically, these data types are a bit odd: you have to declare\nfields using <tt>param<\/tt> instead of <tt>field<\/tt> and you don\u2019t have to wrap the\nfields in <tt>struct<\/tt>.<\/p><p>They also omit some features relative to isorecursive structs, namely\nsubtyping and mutability.  However, sometimes subtyping is not\nnecessary, and one can always assignment-convert mutable fields, wrapping them in mutable structs as needed.<\/p><p>To construct a nominally-typed value, the mechanics are somewhat\ninvolved; instead of <tt>(struct.new $t (i32.const 42))<\/tt>, you use <a href=\"https:\/\/webassembly.github.io\/spec\/core\/exec\/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-x\"><tt>throw<\/tt><\/a>:<\/p><pre>(block $b (result (ref exn))\n (try_table\n  (catch_all_ref $b)\n  (throw $v (i32.const 42)))\n (unreachable))\n<\/pre><p>Of course, as this is a new proposal, we don\u2019t yet have precise type\ninformation on the Wasm side; the new instance instead is returned as\nthe top type for nominally-typed values, <tt>exn<\/tt>.<\/p><p>To check if a value is a <tt>$v<\/tt>, you need to write a bit of code:<\/p><pre>(func $is-v? (param $x (ref exn)) (result i32)\n  (block $yep (result (ref exn))\n   (block $nope\n    (try_table\n     (catch_ref $v $yep)\n     (catch_all $nope)\n     (throw_ref (local.get $x))))\n   (return (i32.const 0)))\n  (return (i32.const 1)))\n<\/pre><p>Finally, field access is a bit odd; unlike structs which have\n<tt>struct.get<\/tt>, nominal types receive all their values via a <tt>catch<\/tt>\nhandler.<\/p><pre>(func $v-fields (param $x (ref exn)) (result i32)\n  (try_table\n   (catch $v 0)\n   (throw_ref (local.get $x)))\n  (unreachable))\n<\/pre><p>Here, the <tt>0<\/tt> in the <tt>(catch $v 0)<\/tt> refers to the function call itself:\nall fields of <tt>$v<\/tt> get returned from the function call.  In this case\nthere\u2019s only one, othewise a get-fields function would return multiple\nvalues.  Happily, this accessor preserves type safety: if <tt>$x<\/tt> is not\nactually <tt>$v<\/tt>, an exception will be thrown.<\/p><p>Now, sometimes you want to be quite strict about your nominal type\nidentities; in that case, just define your <tt>tag<\/tt> in a module and don\u2019t\nexport it.  But if you want to enable composition in a principled way,\nnot just subject to the randomness of whether another module happens to\nimplement a type structurally the same as your own, the nominal typing\nproposal also gives a preview of <a href=\"https:\/\/github.com\/WebAssembly\/proposal-type-imports\/blob\/main\/proposals\/type-imports\/Overview.md\">type\nimports<\/a>.\nThe facility is direct: you simply export your <tt>tag<\/tt> from your module,\nand allow other modules to import it.  Everything will work as expected!<\/p><h3>fin<\/h3><p>Friends, as I am sure is abundantly clear, this is a troll post :)  It\u2019s\nnot wrong, though!  All of the facilities for nominally-typed structs\nwithout subtyping or field mutability are present in the\nexception-handling proposal.<\/p><p>The context for this work was that I was updating\n<a href=\"https:\/\/spritely.institute\/hoot\/\">Hoot<\/a> to use the newer version of\nWasm exception handling, instead of the pre-standardization version.  It\nwas a nice change, but as it introduces the <tt>exnref<\/tt> type, it does open\nthe door to some funny shenanigans, and I find it hilarious that the\ncommittee has been hemming and hawwing about type imports for 7 years\nand then goes and ships it in this backward kind of way.<\/p><p>Next up, exception support in\n<a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\">Wastrel<\/a>, as soon as I can\nfigure out where to allocate type tags for this new nominal typing\nfacility.  Onwards and upwards!<\/p><\/div>                ","author":{"name":"Andy Wingo","uri":"https:\/\/wingolog.org\/"}},{"title":"Yeunjoo Choi: Smarter Chromium GN in Vim with gn-language-server","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/duswnchl.github.io\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/"}},"id":"https:\/\/duswnchl.github.io\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/","updated":"2026-03-10T03:06:00+00:00","content":"\n<p>GN Language Server for Chromium development was announced on <a href=\"https:\/\/groups.google.com\/a\/chromium.org\/g\/chromium-dev\/c\/uTa5mrlvbvw\/m\/vTVpKZPVDwAJ\">chromium-dev<\/a>.\nIt\u2019s very easy to install in VSCode, NeoVim or Emacs. But how can we configure\nit with classic Vim + <a href=\"https:\/\/github.com\/ycm-core\/YouCompleteMe\">YCM<\/a>?<\/p>\n\n<h2 id=\"setup\">Setup<\/h2>\n\n<p>First, install the language server with Cargo.<\/p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>cargo <span class=\"nb\">install<\/span> <span class=\"nt\">--locked<\/span> gn-language-server\n<\/code><\/pre><\/div><\/div>\n<p>Then, add this to your vimrc.<\/p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">let <\/span>g:ycm_language_server <span class=\"o\">=<\/span> <span class=\"o\">[<\/span>\n      <span class=\"se\">\\ <\/span><span class=\"o\">{<\/span>\n      <span class=\"se\">\\ <\/span>  <span class=\"s1\">'name'<\/span>: <span class=\"s1\">'gn'<\/span>,\n      <span class=\"se\">\\ <\/span>  <span class=\"s1\">'cmdline'<\/span>: <span class=\"o\">[<\/span> <span class=\"s1\">'gn-language-server'<\/span> <span class=\"o\">]<\/span>,\n      <span class=\"se\">\\ <\/span>  <span class=\"s1\">'filetypes'<\/span>: <span class=\"o\">[<\/span> <span class=\"s1\">'gn'<\/span> <span class=\"o\">]<\/span>,\n      <span class=\"se\">\\ <\/span><span class=\"o\">}<\/span>\n  <span class=\"se\">\\ <\/span><span class=\"o\">]<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>That easy, right?<\/p>\n\n<h2 id=\"whats-working\">What\u2019s Working<\/h2>\n\n<h3 id=\"hover-documentation\">Hover Documentation<\/h3>\n<p><img src=\"https:\/\/duswnchl.github.io\/assets\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/hover.gif\" alt=\"hover\" \/><\/p>\n<h3 id=\"go-to-imports\">Go To Imports<\/h3>\n<p><img src=\"https:\/\/duswnchl.github.io\/assets\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/jump_import.gif\" alt=\"jump_import\" \/><\/p>\n<h3 id=\"go-to-dependencies\">Go To Dependencies<\/h3>\n<p><img src=\"https:\/\/duswnchl.github.io\/assets\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/jump_deps.gif\" alt=\"jump_deps\" \/><\/p>\n\n<h2 id=\"current-limitations\">Current Limitations<\/h2>\n<p>The following features are not working yet. They may need more configuration or\nfurther work:<\/p>\n\n<h3 id=\"code-folding\">Code Folding<\/h3>\n<p>Classic Vim and YCM don\u2019t support LSP-based folding, and I\u2019m not a big fan of\nthat feature anyway. But you can configure another plugin that supports\nLSP-based folding, or simply rely on indent-based folding.<\/p>\n\n<h3 id=\"go-to-definition\">Go To Definition<\/h3>\n<p>When I try to go to the definition of <code class=\"language-plaintext highlighter-rouge\">template<\/code>, I get an error <code class=\"language-plaintext highlighter-rouge\">KeyError:\n'uri'<\/code>. I\u2019m not sure whether this is caused by my local configuration, but it\nneeds further investigation.\n<img src=\"https:\/\/duswnchl.github.io\/assets\/posts\/smarter-chromium-gn-in-vim-with-gn-language-server\/go_def_error.gif\" alt=\"go_def_error\" \/><\/p>                ","author":{"name":"Yeunjoo Choi","uri":"https:\/\/duswnchl.github.io\/tags\/igalia-planet\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #59","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-59\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-59\/","updated":"2026-03-09T20:02:33+00:00","content":"\n<p>Update on what happened in WebKit in the week from March 2 to March 9.<\/p>\n<p>\nAs part of this week's handful of news, WebKitGTK and WPE WebKit\nnow have support for Gamepad's \"VibationActuator\" property, the\nvideo decoding limit is now configurable at runtime in addition\nto build time, and an interesting fix that makes WebKit render\nfonts like other browsers by making it blend text incorrectly (!).\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p>Using <code>libmanette<\/code>'s <em>rumble<\/em> support, enabled <a rel=\"external\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Gamepad\/vibrationActuator\">Gamepad <em>VibrationActuator<\/em><\/a> for <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308799@main\">WebKitGTK<\/a> and <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308792@main\">WPE WebKit<\/a>.<\/p>\n<p>With these changes, <a rel=\"external\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/GamepadHapticActuator\/playEffect\">playEffect()<\/a> can be used to play <em>dual-rumble<\/em> vibration effects.<\/p>\n  <\/div>\n<h3 id=\"multimedia-movie-camera\">Multimedia \ud83c\udfa5<\/h3>\n<div class=\"wip-description\">\n<p>GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p><code>VIDEO_DECODING_LIMIT<\/code> is now <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=308969\">configurable at runtime<\/a>, in addition to build time. That will allow vendors that share a single binary build on different platforms to fine-tune their needs without a rebuild.<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p>Landed <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/pull\/59880\">a change<\/a> that tweaks the text rendering done with Skia. With this change, the text looks more natural now - just like in other browsers. However, this is done by blending text incorrectly as a compromise.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p>One more set of release candidates for the upcoming stable branch,\n<a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/03\/06\/webkitgtk2.51.93-released.html\">WebKitGTK 2.51.93<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.51.93.html\">WPE WebKit 2.51.93<\/a>,\nhave been published. For those interested in previewing the upcoming 2.52.x\nseries this release is expected to be quite stable. Reporting <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/\">issues in Bugzilla<\/a> are,\nas usual, more than welcome.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Tiago Vignatti: Accessibility and PDF documents","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/vignatti.com\/posts\/accessibility-and-pdfs\/"}},"id":"https:\/\/vignatti.com\/posts\/accessibility-and-pdfs\/","updated":"2026-03-04T13:00:00+00:00","content":"\n<div>\n \n<h2 class=\"relative group\">Accessibility\n <div id=\"accessibility\" class=\"anchor\"><\/div>\n \n <span class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none\">\n <a class=\"text-primary-300 dark:text-neutral-700 !no-underline\" href=\"https:\/\/vignatti.com\/posts\/index.xml#accessibility\">#<\/a>\n <\/span>\n \n<\/h2>\n<p>When we think of <strong>accessibility<\/strong>, we tend to picture it as something designed for a small minority. The reality is much broader: <strong>16% of the world&rsquo;s population \u2014 1.3 billion people \u2014 live with a significant disability<\/strong><a href=\"https:\/\/www.who.int\/news-room\/fact-sheets\/detail\/disability-and-health\" target=\"_blank\" rel=\"noreferrer\">\u00b9<\/a>. In <strong>Brazil<\/strong> alone, where I live, that means around <strong>14.4 million people report some form of disability<\/strong><a href=\"https:\/\/agenciadenoticias.ibge.gov.br\/en\/agencia-news\/2184-news-agency\/news\/43477-2022-census-brazil-has-14-4-million-persons-with-disabilities\" target=\"_blank\" rel=\"noreferrer\">\u00b2<\/a>. And those numbers capture only permanent disabilities.<\/p><\/div>                ","author":{"name":"Tiago Vignatti","uri":"https:\/\/vignatti.com\/posts\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #58","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-58\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-58\/","updated":"2026-03-02T20:11:00+00:00","content":"\n<p>Update on what happened in WebKit in the week from February 23 to March 2.<\/p>\n<p>\nThis installment of the periodical brings news about support\nfor Qualcomm qtivdec2 and qtivenc2 on GStreamer, GPU texture\natlas creation and replay substitution, enhancement of the scroll\ngesture in WPE, and two new releases: WebKitGTK 2.51.92 and WPE\nWebKit 2.51.92.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n<h3 id=\"multimedia-movie-camera\">Multimedia \ud83c\udfa5<\/h3>\n<div class=\"wip-description\">\n<p>GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>Work on adding support for the Qualcomm GStreamer qtivdec2 and qtivenc2 elements is on-going<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308458@main\">Implemented GPU texture atlas creation and replay substitution<\/a> in the Skia painting engine on GTK\/WPE. After recording, raster images are packed into GPU atlases via <code>BitmapTexture<\/code>, with two upload paths: an optimized DMA-buf path that memory-maps GPU buffers and dispatches uploading to a dedicated worker thread, and a synchronous GL fallback using <code>BitmapTexture::updateContents()<\/code>. Atlas uploads are synchronized across workers using a countdown-latch fence. During replay, <code>SkiaReplayCanvas<\/code> intercepts raster image draws and substitutes them with atlas texture draws, mapping source coordinates into atlas space.<\/p>\n  <\/div>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n<h3 id=\"wpe-platform-api-jigsaw\">WPE Platform API \ud83e\udde9<\/h3>\n<div class=\"wip-description\">\n<p>New, modern platform API that supersedes usage of libwpe and WPE backends.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>The recent WPE WebKit 2.51.92 release is the first one to have its <a rel=\"external\" href=\"https:\/\/wpewebkit.org\/reference\/2.51.92\/wpe-platform-2.0\/\">WPEPlatform documentation online<\/a>, but it was not included in the tarball. This issue <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308408@main\">has been corrected<\/a> and tarballs for future releases  will also include this documentation.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>Scrolling using touch input with WPEPlatform would result in scrolling faster when more than one touch point was in effect. The gesture detector <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308271@main\">has been fixed<\/a> to make scrolling have always a consistent speed.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p>The third \u2014and likely the last\u2014 release candidates for the upcoming stable branch, <a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/02\/27\/webkitgtk2.51.92-released.html\">WebKitGTK 2.51.92<\/a> and <a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.51.92.html\">WPE WebKit 2.51.92<\/a>, have been published. For those interested in previewing the upcoming 2.52.x series this release is expected to be quite stable; but there might be still some rough edges. Reporting <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/\">issues in Bugzilla<\/a> are, as usual, more than welcome.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Ziran Sun: A Day in \u201cState of the Browser 2026\u201d Conference","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/zsun\/2026\/03\/02\/a-day-in-state-of-the-browser-2026-conference\/"}},"id":"https:\/\/blogs.igalia.com\/zsun\/?p=837","updated":"2026-03-02T18:11:17+00:00","content":"\n<p>The &#8220;State of the Browser 2026&#8221; Conference was held on Saturday, the 28th of February in <a href=\"https:\/\/www.barbican.org.uk\/\" target=\"_blank\" rel=\"noreferrer noopener\">The Barbican Centre<\/a>, London. It is a yearly conference organised by <a href=\"https:\/\/londonwebstandards.org\/\">London Web Standards<\/a>. This is year is the 14th Edition.<\/p>\n\n\n\n<p>From Igalia, this year we had<a href=\"https:\/\/www.igalia.com\/team\/lwarlow\"> Luke Warlow<\/a> and myself attended in person,  <a href=\"https:\/\/www.igalia.com\/team\/jfernandez\">Javier Fern\u00e1ndez<\/a>  attended online. My colleague <a href=\"https:\/\/www.igalia.com\/team\/sstimac\">Stephanie Stimac<\/a> introduced this event to <a href=\"https:\/\/www.igalia.com\/\">Igalia<\/a> a couple of years ago. Now <a href=\"https:\/\/www.igalia.com\/\">Igalia<\/a> has become one of the sponsors for this great event. Luke had participated this event previously so it&#8217;s very helpful to understand more about this event from his note.<\/p>\n\n\n\n<p>The event is a one-day, single-track conference that is community focused. While queuing for the registrations, a couple of attendees commented that talks for this event had been very good in the past few years. I&#8217;d say, this year was not an exception. I thoroughly enjoyed the talks, and the whole experiences.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.youtube.com\/@londonwebstandards8403\">Talks<\/a> throughout the day covered a wide variety of topics including CSS features, accessibility, JS footprint, playing with gaming APIs and the art of connecting to people etc.. As someone who loves food, maybe I can describe it as a feast with content, taste, depth, variety&#8230;and a bit fun factor?<\/p>\n\n\n\n<p>The open talk was <a href=\"https:\/\/www.bram.us\/2026\/02\/28\/anchors-aweigh-sotb2026\/\">Anchor positioning by Bramus Van Damme<\/a>. The walk-through on the feature with examples were pretty cool, especially the case of a popover&#8230; with a little triangle (You&#8217;ll know what I mean if you look up the talk). <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1808823\">Igalia worked on popover for Firefox<\/a> in 2024, sponsored by Google. It&#8217;s really great to see that anchor positioning is in Firefox &#8211; popover has now found its place.<\/p>\n\n\n\n<p>It was nice to hear that Igalians&#8217; names were mentioned in the <a href=\"https:\/\/2026.stateofthebrowser.com\/speaker\/jason-williams\/\">Temporal talk by Jason Williams from Bloomberg<\/a>. A big shout out to <a href=\"https:\/\/www.igalia.com\/team\/pchimento\">Philip Chimento<\/a>, <a href=\"https:\/\/www.igalia.com\/team\/usharma\">Ujjwal Sharma<\/a> who have participated substantially in the discussions about standardizing Temporal over the years and my fellow Igalians who have been writing spec PRs and tests for the feature. Check on <a href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">Tim Chevalier&#8217;s blog on &#8220;Implementing the Temporal proposal in JavaScriptCore&#8221;<\/a> if you&#8217;d like to find out more.<\/p>\n\n\n\n<p>The atmosphere of the event was friendly, inclusive and energetic. I was very happy bumping into some ex-colleagues and making new friends.<\/p>\n\n\n\n<p>One final note &#8211; This event brings a range of attendees, many are web developers. There are representatives from companies and browser vendors etc.. For some web developers, &#8220;<a href=\"https:\/\/www.igalia.com\/\">Igalia<\/a>&#8221; is a new name. I had a question like &#8220;Oh, is it the company with rainbow colours in the sponsors?&#8221;. Yes, <em>Igalia<\/em> is <em>a private, worker-owned, employee-run cooperative model consultancy focused on open source software<\/em>[<a href=\"https:\/\/en.wikipedia.org\/wiki\/Igalia\">1<\/a>]. And Igalia has been a part of the Interop Project since its inception in 2021. Here is Igalia&#8217;s &#8220;rainbowy&#8221; logo :-).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img width=\"940\" height=\"426\" src=\"https:\/\/blogs.igalia.com\/zsun\/files\/2026\/03\/igalia_logo-940x426.png\" alt=\"\" class=\"wp-image-844\" \/><\/figure><\/div>\n\n\n<p><\/p>                ","author":{"name":"zsun","uri":"https:\/\/blogs.igalia.com\/zsun"}},{"title":"Fr\u00e9d\u00e9ric Wang: Stage d'impl\u00e9mentation des normes Web (session 2026)","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/frederic-wang.fr\/\/2026\/02\/25\/stage-implementation-des-normes-web\/"}},"id":"https:\/\/frederic-wang.fr\/\/2026\/02\/25\/stage-implementation-des-normes-web","updated":"2026-02-24T23:00:00+00:00","content":"\n<p>Les candidatures pour les <a href=\"https:\/\/www.igalia.com\/2026\/02\/27\/Igalia-2026-Coding-Experience-Open-for-Applications.html\">\u00ab\u00a0stages de programmation informatique\u00a0\u00bb<\/a> d\u2019<a href=\"https:\/\/www.igalia.com\/about\/\">Igalia<\/a> sont officiellement ouvertes jusqu\u2019\u00e0 d\u00e9but avril. Ils offrent aux \u00e9tudiant\u00b7e\u00b7s l\u2019occasion de participer au d\u00e9veloppement de logiciels libres tout en \u00e9tant r\u00e9mun\u00e9r\u00e9\u00b7e\u00b7s 7\u00a0000\u00a0\u20ac brut pour 450\u00a0heures, r\u00e9parties de juin \u00e0 d\u00e9cembre 2026.<\/p>\n\n<p>Comme chaque ann\u00e9e, j\u2019encadrerai un\u00b7e  \u00e9tudiant\u00b7e sur l\u2019\u00ab\u00a0Impl\u00e9mentation des normes Web\u00a0\u00bb (<em>Web Standards<\/em> en anglais). L\u2019objectif \u00e9tant de modifier les navigateurs (<a href=\"https:\/\/fr.wikipedia.org\/wiki\/Chromium\">Chromium<\/a>, <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Mozilla_Firefox\">Firefox<\/a> ou <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Safari_(navigateur_web)\">Safari<\/a>\u2026) afin d\u2019am\u00e9liorer le support de technologies Web (<a href=\"https:\/\/fr.wikipedia.org\/wiki\/Hypertext_Markup_Language\">HTML<\/a>, <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Feuilles_de_style_en_cascade\">CSS<\/a>, <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Document_Object_Model\">DOM<\/a>\u2026). Il faudra notamment \u00e9tudier les sp\u00e9cifications correspondantes et \u00e9crire des <a href=\"https:\/\/web-platform-tests.org\/\">tests de conformit\u00e9<\/a>. Notez bien que ce n\u2019est <em>pas<\/em> un stage de d\u00e9veloppement Web mais de d\u00e9veloppement <a href=\"https:\/\/fr.wikipedia.org\/wiki\/C%2B%2B\">C++<\/a>.<\/p>\n\n<p>Un des objectifs de ce programme \u00e9tant de lutter contre les discriminations professionnelles, tout le monde (y compris celles et ceux qui se sentent sous-repr\u00e9sent\u00e9\u00b7e\u00b7s dans le secteur informatique) sont invit\u00e9\u00b7e\u00b7s \u00e0 candidater. Depuis 2016, mon \u00e9quipe \u00ab\u00a0Web Platform\u00a0\u00bb a ainsi encadr\u00e9 13 \u00e9tudiant\u00b7e\u00b7s de diff\u00e9rents pays dans le monde (Espagne, Inde, Italie, <a href=\"https:\/\/www.azabani.com\/2020\/09\/27\/my-internship-with-igalia.html\">Australie<\/a>, Cameroun, Chine, Vietnam, Angleterre et \u00c9tats-Unis) dont 7 femmes. L\u2019ann\u00e9e derni\u00e8re, nous avions s\u00e9lectionn\u00e9 <a href=\"https:\/\/github.com\/Charlotte-McCleary\">Charlotte McCleary<\/a>, une Am\u00e9ricaine non-voyante qui a travaill\u00e9 sur l\u2019accessibilit\u00e9 dans Firefox au cours de son stage et a depuis rejoint  <a href=\"https:\/\/fizz.studio\/about.html\">Fizz Studio<\/a>. J\u2019aimerais encourager les \u00e9tudiant\u00b7e\u00b7s Sourd\u00b7e\u00b7s \u00e0 postuler et donne dans la vid\u00e9o ci-dessous une br\u00e8ve pr\u00e9sentation du programme en LSF (en esp\u00e9rant que ce soit compr\u00e9hensible et que vous serez indulgents avec mon pi\u00e8tre niveau en langue des signes \ud83d\ude05):<\/p>\n\n<div><\/div>\n\n\n<p>Si vous \u00eates int\u00e9r\u00e9ss\u00e9\u00b7e\u00b7s, <a href=\"https:\/\/www.igalia.com\/coding-experience\/\">remplissez ce formulaire<\/a> en cochant la case <em>Web Standards<\/em> et en pr\u00e9cisant \u00e9ventuellement que vous avez trouv\u00e9 cette offre via mon site Web. Enfin, si vous connaissez des \u00e9tudiant\u00b7e\u00b7s qui pourraient participer, n\u2019h\u00e9sitez pas \u00e0 partager l\u2019annonce !<\/p>                ","author":{"name":"Fr\u00e9d\u00e9ric Wang","uri":"https:\/\/frederic-wang.fr\/\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #57","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-57\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-57\/","updated":"2026-02-23T19:52:49+00:00","content":"\n<p>Update on what happened in WebKit in the week from February 9 to February 23.<\/p>\n<p>\nIn this week we have a nice fix for video streams timestamps, a fix\nfor a PDF rendering regression, support for rendering video buffers\nprovided by Qualcomm video decoders, and a fix for a font selection\nissue. Also notable we had a new WPE Android release, and the libsoup\n3.6.6 release.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p>Added a <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/307348@main\">new <code>webkit_feature_list_find()<\/code> convenience function<\/a> to the public API, which searches for a <a rel=\"external\" href=\"https:\/\/webkitgtk.org\/reference\/webkitgtk\/2.51.91\/struct.Feature.html\">WebKitFeature<\/a> given its identifier.<\/p>\n  <\/div>\n<h3 id=\"multimedia-movie-camera\">Multimedia \ud83c\udfa5<\/h3>\n<div class=\"wip-description\">\n<p>GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/307359@main\">Opportunistically fix decoding timestamps to prevent deletion of preexisting samples when PTS doesn't conflict<\/a>, fixing potential glitches when inserting videos (eg: ad insertion).<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/308033@main\">Fixed<\/a> a <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=306621\">PDF rendering regression<\/a> caused by the canvas 2D operation recording feature, where switching between the recording canvas and the GPU surface canvas failed to preserve the full save\/restore nesting, clip stack, and transparency layer state. Replaced the fragile state-copying approach with a state replay mechanism in GraphicsContextSkia that tracks the full sequence of save restore, clip, and transparency layer operations, then reconstructs the exact nesting on the target canvas when flushing a recording.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/307174@main\">Added support<\/a> for rendering video buffers provided by Qualcomm hardware-accelerated decoders, with aid from the <a rel=\"external\" href=\"https:\/\/registry.khronos.org\/OpenGL\/extensions\/EXT\/EXT_YUV_target.txt\">EXT_YUV_target<\/a> OpenGL extension.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/307565@main\">Fixed<\/a> the font selection issue that the system fallback font cache mixed up different font styles.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/github.com\/Igalia\/wpe-android\/releases\/tag\/v0.3.2\">WPE Android 0.3.2<\/a> has been released, and prebuilt packages are available <a rel=\"external\" href=\"https:\/\/central.sonatype.com\/artifact\/org.wpewebkit.wpeview\/wpeview\/\">at the Maven Central repository<\/a>. This is a stable maintenance release which updates WPE WebKit to 2.50.5, which is the most recent stable release.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/gitlab.gnome.org\/GNOME\/libsoup\/-\/releases\/3.6.6\">libsoup 3.6.6<\/a> has been released with numerous bug and security fixes.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Mauricio Faria de Oliveira: page_owner Part 2: optimizing output","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-2-optimizing-output\/"}},"id":"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-2-optimizing-output\/","updated":"2026-02-23T00:00:00+00:00","content":"\n<p>This blog post is <a href=\"https:\/\/mfo.dev.br\/tags\/page_owner\/\">part of a series<\/a> about the <code>page_owner<\/code> debug feature in the Linux memory management subsystem, related to the talk <em><a href=\"http:\/\/www.youtube.com\/watch?v=qFdjO3t5F9I\">Improving <code>page_owner<\/code> for profiling and monitoring memory usage per allocation stack trace<\/a><\/em> presented at <a href=\"https:\/\/lpc.events\/event\/19\/contributions\/2202\/\">Linux Plumbers Conference 2025<\/a>.<\/p>\n<ul>\n<li><a href=\"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-1-quick-introduction\/\">Part 1<\/a> is a quick introduction to <code>page_owner<\/code> and its debugfs files.<\/li>\n<li>Part 2 describes challenges with processing <code>page_owner<\/code> files over time and a solution with new debugfs files in Linux v6.19.<\/li>\n<\/ul>\n<h1 id=\"problem-stack-traces-over-time\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#problem-stack-traces-over-time\">\n Problem: stack traces over time\n <\/a>\n<\/h1>\n<p>As described in Part 1, <code>page_owner<\/code>&rsquo;s debugfs files contain stack traces for the most part:<\/p>\n<ul>\n<li><code>\/sys\/kernel\/debug\/page_owner<\/code> has one stack trace per allocated page, and<\/li>\n<li><code>\/sys\/kernel\/debug\/page_owner_stacks\/show_stacks<\/code> lists the stack traces that allocated pages.<\/li>\n<\/ul>\n<p>Reading and processing a significant amount of stack traces incurs a non-trivial computational cost in CPU and memory (copying to, and processing in, userspace) and storage usage, as the total size for such long strings might become large. This shouldn&rsquo;t be an issue if done only once, but it does pose a concern if done repeatedly.<\/p>\n<p>Take the processing of stack traces one step further and that concern materializes into a technical problem:<\/p>\n<blockquote>\n<p>How to store information (say, number of pages) <em>per-stack trace<\/em> and <em>over time<\/em>?<\/p><\/blockquote>\n<p>For that, the stack trace must become a <em>key<\/em> to be assigned <em>values<\/em> from multiple reads over time. However, keys are usually numbers or somewhat short identifiers, not such long strings as stack traces (although doable, that is computationally more expensive in CPU and memory usage).<\/p>\n<h1 id=\"workaround-stack-trace-hashing\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#workaround-stack-trace-hashing\">\n Workaround: stack trace hashing\n <\/a>\n<\/h1>\n<p>One possible solution to this problem is hashing the stack traces and using the resulting hash values as keys.<\/p>\n<p>However, this is inefficient with <code>page_owner<\/code> since there is significant duplication of stack traces on both debugfs files:<\/p>\n<ul>\n<li>In the <code>page_owner<\/code> file, even on a single read, some stack traces may have tens\/hundreds\/thousands of duplicates; and they compound on multiple reads over time.<\/li>\n<li>In the <code>show_stacks<\/code> file, there are no duplicates on a single read, but duplicates frequently happen on multiple reads over time.<\/li>\n<\/ul>\n<p>With a high ratio of duplication, the dominant component in computational cost is the hashing step, which is significantly more expensive than the remaining step that simply use the resulting keys for storing values.<\/p>\n<p>Additionally, the hashing step is usually repeated with the same data set (stack traces present in previous reads), which means that most of the calculations are discarded and done again on every read &ndash; wasting time and computational resources.<\/p>\n<p>For illustration purposes, compare the execution time of <a href=\"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-2-optimizing-output\/#script-1-page_owner-to-show_stackspy\">script <code>page_owner-to-show_stacks.py<\/code><\/a>, which parses the <code>page_owner<\/code> file hashing the stack traces (with the <a href=\"https:\/\/github.com\/Cyan4973\/xxHash?tab=readme-ov-file#benchmarks\">extremely fast<\/a> <code>XXH3_64<\/code>) and accumulating the number of pages per stack trace, reporting it at the end &ndash; basically mimicking <code>show_stacks<\/code> &ndash; with just reading the equivalent file.<\/p>\n<p>The single read with hashing is 38.55 times slower:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># time .\/page_owner-to-show_stacks.py &lt;\/sys\/kernel\/debug\/page_owner &gt;\/dev\/null<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>real 0m1.542s\n<\/span><\/span><span><span>user 0m1.486s\n<\/span><\/span><span><span>sys 0m0.057s\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span># time cat \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks &gt;\/dev\/null<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>real 0m0.040s\n<\/span><\/span><span><span>user 0m0.000s\n<\/span><\/span><span><span>sys 0m0.040s\n<\/span><\/span><\/code><\/pre><\/div><p>So, considering the single-read results with the <code>page_owner<\/code> file, it&rsquo;s not compelling to use it for multiple reads. However, multiple reads of the <code>show_stacks<\/code> file instead should perform better, though, as it contains unique stack traces and likely a lower ratio of duplication on multiple reads than in a single read of the former file.<\/p>\n<p>Check the execution time of <a href=\"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-2-optimizing-output\/#script-2-show_stacks-over-timepy\">script <code>show_stacks-over-time.py<\/code><\/a>, which parses copies of <code>show_stacks<\/code> (collected over time), similarly hashing the stack traces and storing the number of pages per stack trace over time (that is, per copy).<\/p>\n<p>For 100 copies, the execution time is almost 1 second:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># time .\/show_stacks-over-time.py show_stacks.{1..100} &gt;\/dev\/null<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>real 0m0.944s\n<\/span><\/span><span><span>user 0m0.900s\n<\/span><\/span><span><span>sys 0m0.044s\n<\/span><\/span><\/code><\/pre><\/div><p>That is a great improvement (comparing to processing a single read of the <code>page_owner<\/code> file), but this is just a particular case on a lightly stressed, small VM with 1 GiB RAM. There is still the computational cost of hashing, which might increase processing time in cases with more stack traces (that is, a greater number of different code paths for memory allocation were exercised in the kernel).<\/p>\n<h1 id=\"solution-stack-trace-handle-numbers\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#solution-stack-trace-handle-numbers\">\n Solution: stack trace handle numbers\n <\/a>\n<\/h1>\n<p>The hashing of stack traces is only required in order to obtain a <em>unique identifier<\/em> for each stack trace, so that it can be used as a <em>key<\/em>. However, if such an identifier were already available, the hashing step (and associated computational cost) could be avoided altogether.<\/p>\n<p>Fortunately, that is now the case with Linux 6.19! The stack trace storage used by <code>page_owner<\/code> ( <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/tree\/include\/linux\/stackdepot.h?h=v6.19\"><code>stackdepot<\/code><\/a>) provides a <em>handle number<\/em> to uniquely refer to stack traces &ndash; which meets the requirement.<\/p>\n<p>Linux 6.19 <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/commit\/Documentation\/mm\/page_owner.rst?h=v6.19&id=0de9a442eeba4a6435af74120822b10b12ab8449\">contains two new debugfs files<\/a> with <em>handle numbers<\/em> for optimized output:<\/p>\n<ul>\n<li><code>\/sys\/kernel\/debug\/page_owner_stacks\/show_handles<\/code>: this lists <code>nr_base_pages:<\/code> per <code>handle:<\/code> (instead of per stack trace as in <code>show_stacks<\/code>)<\/li>\n<li><code>\/sys\/kernel\/debug\/page_owner_stacks\/show_stacks_handles<\/code>: this lists <code>handle:<\/code> per stack trace (for resolving handle numbers to stack traces)<\/li>\n<\/ul>\n<p>For the example in the previous post, <code>show_stacks<\/code> contains:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># cat \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks<\/span>\n<\/span><\/span><span><span>...\n<\/span><\/span><span><span> get_page_from_freelist+0x1416\/0x1600\n<\/span><\/span><span><span> __alloc_frozen_pages_noprof+0x18c\/0x1000\n<\/span><\/span><span><span> alloc_pages_mpol+0x43\/0x100\n<\/span><\/span><span><span> folio_alloc_noprof+0x56\/0xa0\n<\/span><\/span><span><span> page_cache_ra_unbounded+0xd9\/0x230\n<\/span><\/span><span><span> filemap_fault+0x305\/0x1000\n<\/span><\/span><span><span> __do_fault+0x2c\/0xb0\n<\/span><\/span><span><span> __handle_mm_fault+0x6f4\/0xeb0\n<\/span><\/span><span><span> handle_mm_fault+0xd9\/0x210\n<\/span><\/span><span><span> do_user_addr_fault+0x205\/0x600\n<\/span><\/span><span><span> exc_page_fault+0x61\/0x130\n<\/span><\/span><span><span> asm_exc_page_fault+0x26\/0x30\n<\/span><\/span><span><span>nr_base_pages: <span>9643<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>...\n<\/span><\/span><\/code><\/pre><\/div><p>While, for the same snippet, <code>show_handles<\/code> contains:<\/p>\n<pre tabindex=\"0\"><code>...\nhandle: 27000838\nnr_base_pages: 9643\n\n...\n<\/code><\/pre><p>And the handle number can be resolved to a stack trace with <code>show_stacks_handles<\/code>:<\/p>\n<pre tabindex=\"0\"><code>...\n get_page_from_freelist+0x1416\/0x1600\n __alloc_frozen_pages_noprof+0x18c\/0x1000\n alloc_pages_mpol+0x43\/0x100\n folio_alloc_noprof+0x56\/0xa0\n page_cache_ra_unbounded+0xd9\/0x230\n filemap_fault+0x305\/0x1000\n __do_fault+0x2c\/0xb0\n __handle_mm_fault+0x6f4\/0xeb0\n handle_mm_fault+0xd9\/0x210\n do_user_addr_fault+0x205\/0x600\n exc_page_fault+0x61\/0x130\n asm_exc_page_fault+0x26\/0x30\nhandle: 27000838\n\n...\n<\/code><\/pre><h2 id=\"comparison-show_stacks-vs-show_handles\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#comparison-show_stacks-vs-show_handles\">\n Comparison: <code>show_stacks<\/code> vs. <code>show_handles<\/code>\n <\/a>\n<\/h2>\n<p>From the previous post, for <code>show_stacks<\/code>:<\/p>\n<pre tabindex=\"0\"><code># time cat \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks \\\n | wc --bytes | numfmt --to=iec\n402K\n\nreal 0m0.042s\nuser 0m0.004s\nsys 0m0.046s\n<\/code><\/pre><p>Now, for <code>show_handles<\/code>:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># time cat \/sys\/kernel\/debug\/page_owner_stacks\/show_handles \\<\/span>\n<\/span><\/span><span><span> | wc --bytes | numfmt --to<span>=<\/span>iec\n<\/span><\/span><span><span>31K\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>real 0m0.015s\n<\/span><\/span><span><span>user 0m0.004s\n<\/span><\/span><span><span>sys 0m0.019s\n<\/span><\/span><\/code><\/pre><\/div><p>That is only 7.7% of the size and 35.7% of the time! Nice improvements.<\/p>\n<p>Finally, compare the execution time of <a href=\"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-2-optimizing-output\/#script-3-show_handles-over-timepy\">script <code>show_handles-over-time.py<\/code><\/a> with the previous one; it uses handle numbers as keys for stack traces instead of hashing them.<\/p>\n<p>For 100 copies, the execution time is approximately 1\/3 of a second, roughly 3 times faster.<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># time .\/show_handles-over-time.py show_stacks_handles show_handles.ln.{1..100} &gt;\/dev\/nul<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>real 0m0.348s\n<\/span><\/span><span><span>user 0m0.319s\n<\/span><\/span><span><span>sys 0m0.030s\n<\/span><\/span><\/code><\/pre><\/div><h1 id=\"conclusion\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#conclusion\">\n Conclusion\n <\/a>\n<\/h1>\n<p>The original debugfs files provided by <code>page_owner<\/code> consist mainly of stack traces, which isn&rsquo;t an efficient format for reading and processing repeatedly.<\/p>\n<p>In order to store the number of pages used per stack trace over time, the stack traces must be converted to keys for storing values over time, for which hashing can be used. However, even efficient hashing algorithms incur a significant overhead.<\/p>\n<p>In order to address this issue, Linux 6.19 provides new debugfs files for <code>page_owner<\/code> with <em>handle numbers<\/em>, which are unique identifiers for stack traces and can be used as keys, instead of hashing.<\/p>\n<p>This optimizes the reading and processing of <code>page_owner<\/code> information, as it reduces the amount of data copied from kernel to userspace and allows storing the number of pages per stack trace over time without the overhead of hashing.<\/p>\n<h1 id=\"scripts\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#scripts\">\n Scripts\n <\/a>\n<\/h1>\n<h2 id=\"script-1-page_owner-to-show_stackspy\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#script-1-page_owner-to-show_stackspy\">\n Script 1: <code>page_owner-to-show_stacks.py<\/code>\n <\/a>\n<\/h2>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-python\"><span><span><span>#!\/usr\/bin\/env python3<\/span>\n<\/span><\/span><span><span><span># SPDX-License-Identifier: GPL-2.0<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Script to parse \/sys\/kernel\/debug\/page_owner, hashing the stack trace<\/span>\n<\/span><\/span><span><span><span># of each page and accumulating the number of pages per stack trace.<\/span>\n<\/span><\/span><span><span><span># At the end, print all stack traces and their number of pages in a format<\/span>\n<\/span><\/span><span><span><span># like \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks.<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Usage: page_owner-to-show_stacks.py &lt;\/sys\/kernel\/debug\/page_owner<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Author: Mauricio Faria de Oliveira &lt;mfo@igalia.com&gt;<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>import<\/span> re\n<\/span><\/span><span><span><span>import<\/span> sys\n<\/span><\/span><span><span><span>import<\/span> xxhash\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>re_page <span>=<\/span> re<span>.<\/span>compile(<span>'^Page allocated via order ([0-9]+)'<\/span>)\n<\/span><\/span><span><span>re_stack <span>=<\/span> re<span>.<\/span>compile(<span>'^ '<\/span>)\n<\/span><\/span><span><span>re_empty <span>=<\/span> re<span>.<\/span>compile(<span>'^$'<\/span>)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>pages <span>=<\/span> {} <span># key -&gt; number of pages<\/span>\n<\/span><\/span><span><span>stacks <span>=<\/span> {} <span># key -&gt; stack trace<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> line <span>in<\/span> sys<span>.<\/span>stdin:\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span># middle lines: try stack trace first as it occurs more often<\/span>\n<\/span><\/span><span><span> <span>if<\/span> re_stack<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span> stack <span>=<\/span> stack <span>+<\/span> line\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span> \n<\/span><\/span><span><span> <span># first line<\/span>\n<\/span><\/span><span><span> <span>match<\/span> <span>=<\/span> re_page<span>.<\/span><span>match<\/span>(line)\n<\/span><\/span><span><span> <span>if<\/span> <span>match<\/span>:\n<\/span><\/span><span><span> order <span>=<\/span> int(<span>match<\/span><span>.<\/span>group(<span>1<\/span>));\n<\/span><\/span><span><span> stack <span>=<\/span> <span>''<\/span>\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span># last line<\/span>\n<\/span><\/span><span><span> <span>if<\/span> re_empty<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span> key <span>=<\/span> xxhash<span>.<\/span>xxh3_64_hexdigest(stack)\n<\/span><\/span><span><span> nr_pages <span>=<\/span> <span>2<\/span> <span>**<\/span> order\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span>if<\/span> key <span>in<\/span> pages:\n<\/span><\/span><span><span> pages[key] <span>+=<\/span> nr_pages\n<\/span><\/span><span><span> <span>else<\/span>:\n<\/span><\/span><span><span> pages[key] <span>=<\/span> nr_pages\n<\/span><\/span><span><span> stacks[key] <span>=<\/span> stack\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> key <span>in<\/span> stacks<span>.<\/span>keys():\n<\/span><\/span><span><span> print(<span>\" \"<\/span> <span>+<\/span> stacks[key]<span>.<\/span>strip())\n<\/span><\/span><span><span> print(<span>\"nr_base_pages: \"<\/span> <span>+<\/span> str(pages[key]))\n<\/span><\/span><span><span> print()\n<\/span><\/span><\/code><\/pre><\/div><h2 id=\"script-2-show_stacks-over-timepy\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#script-2-show_stacks-over-timepy\">\n Script #2: <code>show_stacks-over-time.py<\/code>\n <\/a>\n<\/h2>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-python\"><span><span><span>#!\/usr\/bin\/env python3<\/span>\n<\/span><\/span><span><span><span># SPDX-License-Identifier: GPL-2.0<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Script to parse \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks in multiple<\/span>\n<\/span><\/span><span><span><span># reads, hashing each stack trace and recording the number of base pages per<\/span>\n<\/span><\/span><span><span><span># stack trace in each read.<\/span>\n<\/span><\/span><span><span><span># At the end, print all stack traces and their number of pages in each read.<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Usage: show_stacks-over-time.py &lt;read1&gt; &lt;read2&gt; &lt;read3&gt; ... &lt;read N&gt;<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Author: Mauricio Faria de Oliveira &lt;mfo@igalia.com&gt;<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>import<\/span> re\n<\/span><\/span><span><span><span>import<\/span> sys\n<\/span><\/span><span><span><span>import<\/span> xxhash\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>re_pages <span>=<\/span> re<span>.<\/span>compile(<span>'^nr_base_pages: ([0-9]+)'<\/span>)\n<\/span><\/span><span><span>re_stack <span>=<\/span> re<span>.<\/span>compile(<span>'^ '<\/span>)\n<\/span><\/span><span><span>re_empty <span>=<\/span> re<span>.<\/span>compile(<span>'^$'<\/span>)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>stacks <span>=<\/span> {}\t<span># key -&gt; stack trace (all reads)<\/span>\n<\/span><\/span><span><span>pages <span>=<\/span> {}\t<span># key -&gt; array of number of pages (per read)<\/span>\n<\/span><\/span><span><span>read <span>=<\/span> <span>0<\/span>\t<span># number of the current read<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>if<\/span> len(sys<span>.<\/span>argv) <span>&lt;<\/span> <span>2<\/span>:\n<\/span><\/span><span><span>\texit(<span>1<\/span>)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>files <span>=<\/span> sys<span>.<\/span>argv[<span>1<\/span>:]\n<\/span><\/span><span><span>nr_files <span>=<\/span> len(files)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> file <span>in<\/span> files:\n<\/span><\/span><span><span>\t<span>with<\/span> open(file, <span>'r'<\/span>) <span>as<\/span> fd:\n<\/span><\/span><span><span>\t\tstack <span>=<\/span> <span>''<\/span>\n<\/span><\/span><span><span>\t\t<span>for<\/span> line <span>in<\/span> fd:\n<\/span><\/span><span><span>\t\n<\/span><\/span><span><span>\t\t\t<span># first lines<\/span>\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> re_stack<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span>\t\t\t\tstack <span>=<\/span> stack <span>+<\/span> line\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\t\t\t\t\n<\/span><\/span><span><span>\t\t\t<span># next to last line<\/span>\n<\/span><\/span><span><span>\t\t\t<span>match<\/span> <span>=<\/span> re_pages<span>.<\/span><span>match<\/span>(line)\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> <span>match<\/span>:\n<\/span><\/span><span><span>\t\t\t\tnr_pages <span>=<\/span> int(<span>match<\/span><span>.<\/span>group(<span>1<\/span>));\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t<span># last line<\/span>\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> re_empty<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span>\t\t\t\tkey <span>=<\/span> xxhash<span>.<\/span>xxh3_64_hexdigest(stack)\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t\t<span>if<\/span> key <span>not<\/span> <span>in<\/span> stacks:\n<\/span><\/span><span><span>\t\t\t\t\tstacks[key] <span>=<\/span> stack;\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\t\t\t\t<span>if<\/span> key <span>not<\/span> <span>in<\/span> pages:\n<\/span><\/span><span><span>\t\t\t\t\tpages[key] <span>=<\/span> {}\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\t\t\t\tpages[key][read] <span>=<\/span> nr_pages\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t\tstack <span>=<\/span> <span>''<\/span>\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\t\tread <span>+=<\/span> <span>1<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> key <span>in<\/span> stacks<span>.<\/span>keys():\n<\/span><\/span><span><span>\tprint(<span>\" \"<\/span> <span>+<\/span> stacks[key]<span>.<\/span>strip())\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\tpages_per_read <span>=<\/span> []\n<\/span><\/span><span><span>\t<span>for<\/span> read <span>in<\/span> range(nr_files):\n<\/span><\/span><span><span>\t\tnr_pages <span>=<\/span> <span>0<\/span>\n<\/span><\/span><span><span>\t\t<span>if<\/span> read <span>in<\/span> pages[key]:\n<\/span><\/span><span><span>\t\t\tnr_pages <span>=<\/span> pages[key][read]\n<\/span><\/span><span><span>\t\tpages_per_read<span>.<\/span>append(str(nr_pages))\n<\/span><\/span><span><span>\t\n<\/span><\/span><span><span>\tprint(<span>' '<\/span><span>.<\/span>join(pages_per_read))\n<\/span><\/span><span><span>\tprint()\n<\/span><\/span><\/code><\/pre><\/div><h2 id=\"script-3-show_handles-over-timepy\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#script-3-show_handles-over-timepy\">\n Script #3: <code>show_handles-over-time.py<\/code>\n <\/a>\n<\/h2>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-python\"><span><span><span>#!\/usr\/bin\/env python3<\/span>\n<\/span><\/span><span><span><span># SPDX-License-Identifier: GPL-2.0<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Script to parse \/sys\/kernel\/debug\/page_owner_stacks\/show_handles in multiple<\/span>\n<\/span><\/span><span><span><span># reads, collecting handle numbers and recording the number of base pages per<\/span>\n<\/span><\/span><span><span><span># handle number in each read.<\/span>\n<\/span><\/span><span><span><span># At the end, print all stack traces and their number of pages in each read,<\/span>\n<\/span><\/span><span><span><span># resolving handle numbers with \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks_handles.<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Usage: show_handles-over-time.py &lt;show_stacks_handles&gt; &lt;read1&gt; &lt;read2&gt; &lt;read3&gt; ... &lt;read N&gt;<\/span>\n<\/span><\/span><span><span><span>#<\/span>\n<\/span><\/span><span><span><span># Author: Mauricio Faria de Oliveira &lt;mfo@igalia.com&gt;<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>import<\/span> re\n<\/span><\/span><span><span><span>import<\/span> sys\n<\/span><\/span><span><span><span>import<\/span> xxhash\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>re_pages <span>=<\/span> re<span>.<\/span>compile(<span>'^nr_base_pages: ([0-9]+)'<\/span>)\n<\/span><\/span><span><span>re_stack <span>=<\/span> re<span>.<\/span>compile(<span>'^ '<\/span>)\n<\/span><\/span><span><span>re_empty <span>=<\/span> re<span>.<\/span>compile(<span>'^$'<\/span>)\n<\/span><\/span><span><span>re_handle <span>=<\/span> re<span>.<\/span>compile(<span>'^handle: ([0-9]+)'<\/span>)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>stacks <span>=<\/span> {}\t<span># handle number -&gt; stack trace (all reads)<\/span>\n<\/span><\/span><span><span>pages <span>=<\/span> {}\t<span># handle number -&gt; array of number of pages (per read)<\/span>\n<\/span><\/span><span><span>read <span>=<\/span> <span>0<\/span>\t<span># number of the current read<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>if<\/span> len(sys<span>.<\/span>argv) <span>&lt;<\/span> <span>3<\/span>:\n<\/span><\/span><span><span>\texit(<span>1<\/span>)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>resolver <span>=<\/span> sys<span>.<\/span>argv[<span>1<\/span>]\n<\/span><\/span><span><span>files <span>=<\/span> sys<span>.<\/span>argv[<span>2<\/span>:]\n<\/span><\/span><span><span>nr_files <span>=<\/span> len(files)\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> file <span>in<\/span> files:\n<\/span><\/span><span><span>\t<span>with<\/span> open(file, <span>'r'<\/span>) <span>as<\/span> fd:\n<\/span><\/span><span><span>\t\t<span>for<\/span> line <span>in<\/span> fd:\n<\/span><\/span><span><span>\t\n<\/span><\/span><span><span>\t\t\t<span># first line<\/span>\n<\/span><\/span><span><span>\t\t\t<span>match<\/span> <span>=<\/span> re_handle<span>.<\/span><span>match<\/span>(line)\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> <span>match<\/span>:\n<\/span><\/span><span><span>\t\t\t\thandle <span>=<\/span> int(<span>match<\/span><span>.<\/span>group(<span>1<\/span>))\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\t\t\t\t\n<\/span><\/span><span><span>\t\t\t<span># next to last line<\/span>\n<\/span><\/span><span><span>\t\t\t<span>match<\/span> <span>=<\/span> re_pages<span>.<\/span><span>match<\/span>(line)\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> <span>match<\/span>:\n<\/span><\/span><span><span>\t\t\t\tnr_pages <span>=<\/span> int(<span>match<\/span><span>.<\/span>group(<span>1<\/span>));\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t<span># last line<\/span>\n<\/span><\/span><span><span>\t\t\t<span>if<\/span> re_empty<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span>\t\t\t\tkey <span>=<\/span> handle\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t\t<span>if<\/span> key <span>not<\/span> <span>in<\/span> pages:\n<\/span><\/span><span><span>\t\t\t\t\tpages[key] <span>=<\/span> {}\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\t\t\t\tpages[key][read] <span>=<\/span> nr_pages\n<\/span><\/span><span><span>\t\t\n<\/span><\/span><span><span>\t\t\t\t<span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\t\tread <span>+=<\/span> <span>1<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>with<\/span> open(resolver, <span>'r'<\/span>) <span>as<\/span> fd:\n<\/span><\/span><span><span> stack <span>=<\/span> <span>''<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span>for<\/span> line <span>in<\/span> fd:\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span># first line<\/span>\n<\/span><\/span><span><span> <span>if<\/span> re_stack<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span> stack <span>=<\/span> stack <span>+<\/span> line\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span> <span># next to last line<\/span>\n<\/span><\/span><span><span> <span>match<\/span> <span>=<\/span> re_handle<span>.<\/span><span>match<\/span>(line)\n<\/span><\/span><span><span> <span>if<\/span> <span>match<\/span>:\n<\/span><\/span><span><span> handle <span>=<\/span> int(<span>match<\/span><span>.<\/span>group(<span>1<\/span>))\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span> \n<\/span><\/span><span><span> <span># last line<\/span>\n<\/span><\/span><span><span> <span>if<\/span> re_empty<span>.<\/span><span>match<\/span>(line):\n<\/span><\/span><span><span> stacks[handle] <span>=<\/span> stack\n<\/span><\/span><span><span> stack <span>=<\/span> <span>''<\/span>\n<\/span><\/span><span><span> <span>continue<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span><span>for<\/span> key <span>in<\/span> pages<span>.<\/span>keys():\n<\/span><\/span><span><span>\tprint(<span>\" \"<\/span> <span>+<\/span> stacks[key]<span>.<\/span>strip())\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>\tpages_per_read <span>=<\/span> []\n<\/span><\/span><span><span>\t<span>for<\/span> read <span>in<\/span> range(nr_files):\n<\/span><\/span><span><span>\t\tnr_pages <span>=<\/span> <span>0<\/span>\n<\/span><\/span><span><span>\t\t<span>if<\/span> read <span>in<\/span> pages[key]:\n<\/span><\/span><span><span>\t\t\tnr_pages <span>=<\/span> pages[key][read]\n<\/span><\/span><span><span>\t\tpages_per_read<span>.<\/span>append(str(nr_pages))\n<\/span><\/span><span><span>\t\n<\/span><\/span><span><span>\tprint(<span>' '<\/span><span>.<\/span>join(pages_per_read))\n<\/span><\/span><span><span>\tprint()\n<\/span><\/span><\/code><\/pre><\/div>                ","author":{"name":"Mauricio Faria de Oliveira","uri":"https:\/\/mfo.dev.br\/tags\/igalia\/"}},{"title":"Mauricio Faria de Oliveira: page_owner Part 1: a quick introduction","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-1-quick-introduction\/"}},"id":"https:\/\/mfo.dev.br\/posts\/2026-02-23-page_owner-part-1-quick-introduction\/","updated":"2026-02-20T00:00:00+00:00","content":"\n<p>This blog post is <a href=\"https:\/\/mfo.dev.br\/tags\/page_owner\/\">part of a series<\/a> about the <code>page_owner<\/code> debug feature in the Linux memory management subsystem, related to the talk <em><a href=\"http:\/\/www.youtube.com\/watch?v=qFdjO3t5F9I\">Improving <code>page_owner<\/code> for profiling and monitoring memory usage per allocation stack trace<\/a><\/em> presented at <a href=\"https:\/\/lpc.events\/event\/19\/contributions\/2202\/\">Linux Plumbers Conference 2025<\/a>.<\/p>\n<h1 id=\"what-is-page_owner\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#what-is-page_owner\">\n What is <code>page_owner<\/code>?\n <\/a>\n<\/h1>\n<p>In the Linux kernel, <code>page_owner<\/code> is a debug feature that tracks the memory allocation (and release) of pages in the system &ndash; so as to tell the &lsquo;<em>owner of a page<\/em>&rsquo; ;-).<\/p>\n<p>For each memory allocation, <code>page_owner<\/code> stores its order, <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/tree\/include\/linux\/gfp_types.h?h=v6.19\">GFP flags<\/a>, stack trace, timestamp, command, process ID (PID) and thread-group ID (TGID), and more. It also stores some information when pages are freed (stack trace, timestamp, PID and TGID).<\/p>\n<p>With <code>page_owner<\/code>, one can find out &ldquo;<em>What allocated this page?<\/em>&rdquo; and &ldquo;<em>How many pages are allocated by this particular stack trace, PID, or comm<\/em>&rdquo;, for example.<\/p>\n<p>This is <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/tree\/mm\/page_owner.c?h=v6.19#n24\">struct page_owner<\/a> in Linux <code>v6.19<\/code>. It stores additional information per-page, as an extension of <code>struct page<\/code> with <code>CONFIG_PAGE_EXTENSION<\/code>.<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-c\"><span><span><span>struct<\/span> page_owner {\n<\/span><\/span><span><span> <span>unsigned<\/span> <span>short<\/span> order;\n<\/span><\/span><span><span> <span>short<\/span> last_migrate_reason;\n<\/span><\/span><span><span> <span>gfp_t<\/span> gfp_mask;\n<\/span><\/span><span><span> <span>depot_stack_handle_t<\/span> handle;\n<\/span><\/span><span><span> <span>depot_stack_handle_t<\/span> free_handle;\n<\/span><\/span><span><span> u64 ts_nsec;\n<\/span><\/span><span><span> u64 free_ts_nsec;\n<\/span><\/span><span><span> <span>char<\/span> comm[TASK_COMM_LEN];\n<\/span><\/span><span><span> <span>pid_t<\/span> pid;\n<\/span><\/span><span><span> <span>pid_t<\/span> tgid;\n<\/span><\/span><span><span> <span>pid_t<\/span> free_pid;\n<\/span><\/span><span><span> <span>pid_t<\/span> free_tgid;\n<\/span><\/span><span><span>};\n<\/span><\/span><\/code><\/pre><\/div><h1 id=\"usage\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#usage\">\n Usage\n <\/a>\n<\/h1>\n<p>In order to use <code>page_owner<\/code>, build the kernel with <code>CONFIG_PAGE_OWNER=y<\/code> (see <code>mm\/Kconfig.debug<\/code>) and boot the kernel with <code>page_owner=on<\/code>.<\/p>\n<p>The debugfs file <code>\/sys\/kernel\/debug\/page_owner<\/code> provides the information in <code>struct page_owner<\/code> for every page, listed per <code>PFN<\/code> (page frame number).<\/p>\n<p>This example shows the entry for a page (line continuation added for clarity) &ndash; it tells &ldquo;<em>What allocated this page?<\/em>&rdquo;:<\/p>\n<pre tabindex=\"0\"><code># cat \/sys\/kernel\/debug\/page_owner\n...\nPage allocated via order 0, \\\n mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), \\\n pid 5640, tgid 5640 (stress-ng-brk), ts 414987114269 ns\nPFN 0x114 type Unmovable Block 0 type Unmovable Flags 0x200(workingset|node=0|zone=0)\n get_page_from_freelist+0x1416\/0x1600\n __alloc_frozen_pages_noprof+0x18c\/0x1000\n alloc_pages_mpol+0x43\/0x100\n new_slab+0x349\/0x460\n ___slab_alloc+0x811\/0xd90\n __kmem_cache_alloc_bulk+0xb8\/0x1f0\n __prefill_sheaf_pfmemalloc+0x42\/0x90\n kmem_cache_prefill_sheaf+0xa9\/0x240\n mas_preallocate+0x32f\/0x420\n __split_vma+0xdc\/0x300\n vms_gather_munmap_vmas+0xa4\/0x240\n do_vmi_align_munmap+0xe9\/0x180\n do_vmi_munmap+0xcb\/0x160\n __vm_munmap+0xa7\/0x150\n __x64_sys_munmap+0x16\/0x20\n do_syscall_64+0xa4\/0x310\n\n...\n<\/code><\/pre><p>One can use <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/tree\/tools\/mm\/page_owner_sort.c?h=v6.19\">tools\/mm\/page_owner_sort<\/a> to process the information in the file, or come up with custom commands, scripts, or programs.<\/p>\n<p>For example: calculate the total size of pages allocated by <code>stress-ng-brk<\/code> with any order, in MiB:<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># COMM=stress-ng-brk<\/span>\n<\/span><\/span><span><span><span># cat \/sys\/kernel\/debug\/page_owner \\<\/span>\n<\/span><\/span><span><span> | awk -F <span>'[ ,]'<\/span> <span>\\\n<\/span><\/span><\/span><span><span><span><\/span> <span>'\/^Page allocated via order .* \\('<\/span><span>${<\/span>COMM<span>}<\/span><span>'\\)\/ { PAGES+=2^$5 } \n<\/span><\/span><\/span><span><span><span> END { print PAGES*4096\/2**20 \" MiB\" }'<\/span>\n<\/span><\/span><span><span>0.0429688 MiB\n<\/span><\/span><\/code><\/pre><\/div><p>More information about <code>page_owner<\/code> is available in <a href=\"https:\/\/git.kernel.org\/pub\/scm\/linux\/kernel\/git\/torvalds\/linux.git\/plain\/Documentation\/mm\/page_owner.rst?h=v6.19\">Documentation\/mm\/page_owner.rst<\/a>.<\/p>\n<h1 id=\"problem-output-size\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#problem-output-size\">\n Problem: output size\n <\/a>\n<\/h1>\n<p>In the <code>page_owner<\/code> file, note the significant amount of text that is produced <em>per-page<\/em>: 745 bytes, in the example above.<\/p>\n<p>Considering a system with 1 GiB of RAM and 4 kB pages, fully allocated, with similarly sized entries per page, the output size might reach approximately 186 MiB! (<code>745 [bytes\/page] * (2**30 [bytes of RAM] \/ 4096 [bytes\/page]) \/ 2**20 [bytes\/MiB]<\/code>)<\/p>\n<p>For validation, a test VM with 1 GiB of RAM after just a warm-up level of stress (<code>stress-ng --sequential --timeout 1<\/code>) produced 125 MiB, which was not quick to read even in idle state:<\/p>\n<pre tabindex=\"0\"><code># time cat \/sys\/kernel\/debug\/page_owner \\\n | wc --bytes | numfmt --to=iec\n125M\n\nreal 0m3.009s\nuser 0m0.512s\nsys 0m3.542s\n<\/code><\/pre><p>While this might not be a serious issue for reading and processing the file only once, it can likely impact a sequence of operations.<\/p>\n<h1 id=\"alternative-optimized-output\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#alternative-optimized-output\">\n Alternative: optimized output\n <\/a>\n<\/h1>\n<p>Fortunately, another debugfs file, <code>\/sys\/kernel\/debug\/page_owner_stacks\/show_stacks<\/code>, provides an optimized output for obtaining the memory usage per stack trace. Even though it doesn&rsquo;t address all needs as the generic output, it resembles the default operation of <code>page_owner_sort<\/code> (without <code>PFN<\/code> lines) and provides an often interesting information for kernel development or analysis.<\/p>\n<p>This example shows the entry for a stack trace &ndash; it tells &ldquo;<em>How many pages are allocated by this particular stack trace?<\/em>&rdquo;<\/p>\n<div class=\"highlight\"><pre tabindex=\"0\"><code class=\"language-shell\"><span><span><span># cat \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks<\/span>\n<\/span><\/span><span><span>...\n<\/span><\/span><span><span> get_page_from_freelist+0x1416\/0x1600\n<\/span><\/span><span><span> __alloc_frozen_pages_noprof+0x18c\/0x1000\n<\/span><\/span><span><span> alloc_pages_mpol+0x43\/0x100\n<\/span><\/span><span><span> folio_alloc_noprof+0x56\/0xa0\n<\/span><\/span><span><span> page_cache_ra_unbounded+0xd9\/0x230\n<\/span><\/span><span><span> filemap_fault+0x305\/0x1000\n<\/span><\/span><span><span> __do_fault+0x2c\/0xb0\n<\/span><\/span><span><span> __handle_mm_fault+0x6f4\/0xeb0\n<\/span><\/span><span><span> handle_mm_fault+0xd9\/0x210\n<\/span><\/span><span><span> do_user_addr_fault+0x205\/0x600\n<\/span><\/span><span><span> exc_page_fault+0x61\/0x130\n<\/span><\/span><span><span> asm_exc_page_fault+0x26\/0x30\n<\/span><\/span><span><span>nr_base_pages: <span>9643<\/span>\n<\/span><\/span><span><span>\n<\/span><\/span><span><span>...\n<\/span><\/span><\/code><\/pre><\/div><p>The <code>nr_base_pages<\/code> field tells the number of base pages (i.e., not huge pages) allocated by a stack trace. So, this particular stack trace for <em>readahead<\/em> (<code>page_cache_ra_unbounded()<\/code>) has allocated approximately 37 MiB (<code>9643 [pages] * 4096 [bytes\/page] \/ 2**20 [ bytes\/MiB]<\/code>).<\/p>\n<p>Note this file is more efficient for this particular purpose: just 402 KiB in less than 0.05 seconds. (That is 0.3% of the size and 1.7% of the time):<\/p>\n<pre tabindex=\"0\"><code># time cat \/sys\/kernel\/debug\/page_owner_stacks\/show_stacks \\\n | wc --bytes | numfmt --to=iec\n402K\n\nreal 0m0.042s\nuser 0m0.004s\nsys 0m0.046s\n<\/code><\/pre><h1 id=\"conclusion\">\n <a class=\"header-link\" href=\"https:\/\/mfo.dev.br\/tags\/igalia\/index.xml#conclusion\">\n Conclusion\n <\/a>\n<\/h1>\n<p>The <code>page_owner<\/code> debug feature (enabled with <code>CONFIG_PAGE_OWNER=y<\/code> and <code>page_owner=on<\/code>) provides information about the memory allocation of pages in the system in debugfs files <code>\/sys\/kernel\/debug\/page_owner<\/code> with a generic format (dense description per-page) and <code>\/sys\/kernel\/debug\/page_owner_stacks\/show_stacks<\/code> with an optimized format (number of base pages per stack trace).<\/p>                ","author":{"name":"Mauricio Faria de Oliveira","uri":"https:\/\/mfo.dev.br\/tags\/igalia\/"}},{"title":"Alex Bradbury: Minipost: Additional figures for per-query energy consumption of LLMs","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/muxup.com\/2026q1\/minipost-additional-figures-for-per-query-energy-consumption-of-LLMs"}},"id":"https:\/\/muxup.com\/2026q1\/minipost-additional-figures-for-per-query-energy-consumption-of-LLMs","updated":"2026-02-17T12:00:00+00:00","content":"\n<p>Last month I wrote up a fairly long piece on <a href=\"https:\/\/muxup.com\/2026q1\/per-query-energy-consumption-of-llms\">per-query energy consumption of\nLLMs using the data from\nInferenceMAX<\/a> (note:\nInferenceMAX has since been renamed to InferenceX). Much of the write-up was\ndedicated to exploring what you can actually conclude from these figures and\nhow that interacts with some of the implementation decisions in the benchmark,\nbut I feel the results still give a useful yardstick. Beyond concerns about\noverly-specialised serving engine configurations and whether the workload is\nrepresentative of real-world model serving in a paid API host, the other\nobvious limitation is that InferenceMAX is only testing GPT-OSS 120b and\nDeepSeek R1 0528 when there is a world of other models out there. I dutifully\nadded \"run my own tests using other models\" to the todo list and here we are.\nBy \"here we are\" I of course mean I made no progress towards that goal but\n<a href=\"https:\/\/muellerzr.github.io\/\">Zach Mueller<\/a> at <a href=\"https:\/\/lambda.ai\/\">Lambda<\/a>\nstarted publishing <a href=\"https:\/\/lambda.ai\/inference-models\">model cards with the needed\ndata<\/a> - thanks Zach!<\/p>\n<p>The setup for Lambda is simple - each model card lists the observed token\ngeneration throughput and total throughput (along with other stats) for an\ninput sequence length \/ output sequence length (ISL\/OSL) of 8192\/1024, as\nbenchmarked using <code>vllm bench serve<\/code>. The command used to serve the LLM (using\nsglang or vllm depending on the model) is also given. As a starting point this\nis no worse than the InferenceMAX data, and potentially somewhat better due to\nfigures being taken from a configuration that's not <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/359#issue-3750796719\">overly specialised to a\nparticular query\nlength<\/a>.<\/p>\n<p>The figures each Lambda model card gives us that are relevant for calculating\nthe energy per query are: the hardware used, token generation throughput and\ntotal token throughput (input+output tokens). Other statistics such as the\ntime to first token, inter-token latency, and parallel requests tested help\nconfirm whether this is a configuration someone would realistically use. Using\nan equivalent methodology to before, we get the Watt hours per query by:<\/p>\n<ul>\n<li>Determining the total Watts for the GPU cluster. We take the figures used by\nSemiAnalysis (2.17kW for a single B200) and multiply by the number of GPUs.<\/li>\n<li>Calculate the joules per token by dividing this total Watts figure by the\ntotal token throughput. This gives a weighted average of the joules per\ntoken for the measured workload, reflecting the ratio of isl:osl.<\/li>\n<li>Multiply this weighted average of joules per token by the tokens per query\n(isl+osl) to get the joules per query. Then divide by 3600 to get Wh.<\/li>\n<\/ul>\n<p>Collecting the data from the individual model cards we can generate the\nfollowing (as before, using minutes of PlayStation 5 gameplay as a point of\ncomparison):<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span>data<\/span> <span>=<\/span> {\n    <span>&quot;Qwen\/Qwen3.5-397B-A17B&quot;<\/span>: {\n        <span>&quot;num_b200&quot;<\/span>: <span>8<\/span>,\n        <span>&quot;total_throughput&quot;<\/span>: <span>11092<\/span>,\n    },\n    <span>&quot;MiniMaxAI\/MiniMax-M2.5&quot;<\/span>: {\n        <span>&quot;num_b200&quot;<\/span>: <span>2<\/span>,\n        <span>&quot;total_throughput&quot;<\/span>: <span>8062<\/span>,\n    },\n    <span>&quot;zai-org\/GLM-5-FP8&quot;<\/span>: {\n        <span>&quot;num_b200&quot;<\/span>: <span>8<\/span>,\n        <span>&quot;total_throughput&quot;<\/span>: <span>6300<\/span>,\n    },\n    <span>&quot;zai-org\/GLM-4.7-Flash&quot;<\/span>: {\n        <span>&quot;num_b200&quot;<\/span>: <span>1<\/span>,\n        <span>&quot;total_throughput&quot;<\/span>: <span>8125<\/span>,\n    },\n    <span>&quot;arcee-ai\/Trinity-Large-Preview&quot;<\/span>: {\n        <span>&quot;num_b200&quot;<\/span>: <span>8<\/span>,\n        <span>&quot;total_throughput&quot;<\/span>: <span>15611<\/span>,\n    },\n}\n\n<span># 8192 + 1024<\/span>\n<span>TOKENS_PER_QUERY<\/span> <span>=<\/span> <span>9216<\/span>\n\n<span># Taken from &lt;https:\/\/inferencex.semianalysis.com\/&gt;<\/span>\n<span>B200_KW<\/span> <span>=<\/span> <span>2.17<\/span>\n\n<span># Reference power draw for PS5 playing a game. Taken from<\/span>\n<span># &lt;https:\/\/www.playstation.com\/en-gb\/legal\/ecodesign\/&gt; (&quot;Active Power<\/span>\n<span># Consumption&quot;). Ranges from ~217W to ~197W depending on model.<\/span>\n<span>PS5_KW<\/span> <span>=<\/span> <span>0.2<\/span>\n\n\n<span>def<\/span> <span>wh_per_query<\/span>(<span>num_b200<\/span>, <span>total_throughput<\/span>, <span>tokens_per_query<\/span>):\n    <span>total_cluster_kw<\/span> <span>=<\/span> <span>num_b200<\/span> <span>*<\/span> <span>B200_KW<\/span>\n    <span>total_cluster_watts<\/span> <span>=<\/span> <span>total_cluster_kw<\/span> <span>*<\/span> <span>1000<\/span>\n    <span># joules_per_token is a weighted average for the measured mix of input<\/span>\n    <span># and output tokens.<\/span>\n    <span>joules_per_token<\/span> <span>=<\/span> <span>total_cluster_watts<\/span> <span>\/<\/span> <span>total_throughput<\/span>\n    <span>joules_per_query<\/span> <span>=<\/span> <span>joules_per_token<\/span> <span>*<\/span> <span>tokens_per_query<\/span>\n    <span># Convert joules to watt-hours<\/span>\n    <span>return<\/span> <span>joules_per_query<\/span> <span>\/<\/span> <span>3600.0<\/span>\n\n<span>def<\/span> <span>ps5_minutes<\/span>(<span>wh<\/span>):\n    <span>ps5_watts<\/span> <span>=<\/span> <span>PS5_KW<\/span> <span>*<\/span> <span>1000<\/span>\n    <span>return<\/span> (<span>wh<\/span> <span>\/<\/span> <span>ps5_watts<\/span>) <span>*<\/span> <span>60.0<\/span>\n\n<span>MODEL_WIDTH<\/span> <span>=<\/span> <span>31<\/span>\n<span>WH_WIDTH<\/span> <span>=<\/span> <span>8<\/span>\n<span>PS5_WIDTH<\/span> <span>=<\/span> <span>8<\/span>\n\n<span>header<\/span> <span>=<\/span> <span>f&quot;{'Model':&lt;{<\/span><span>MODEL_WIDTH<\/span><span>}} | {'Wh\/q':&lt;{<\/span><span>WH_WIDTH<\/span><span>}} | {'PS5 min':&lt;{<\/span><span>PS5_WIDTH<\/span><span>}}&quot;<\/span>\n<span>separator<\/span> <span>=<\/span> <span>f&quot;{'-'<\/span> <span>*<\/span> <span>MODEL_WIDTH<\/span><span>} | {'-'<\/span> <span>*<\/span> <span>WH_WIDTH<\/span><span>} | {'-'<\/span> <span>*<\/span> <span>PS5_WIDTH<\/span><span>}&quot;<\/span>\n\n<span>print<\/span>(<span>header<\/span>)\n<span>print<\/span>(<span>separator<\/span>)\n\n<span>for<\/span> <span>model<\/span>, <span>vals<\/span> <span>in<\/span> <span>data.items<\/span>():\n    <span>wh<\/span> <span>=<\/span> <span>wh_per_query<\/span>(<span>vals<\/span>[<span>&quot;num_b200&quot;<\/span>], <span>vals<\/span>[<span>&quot;total_throughput&quot;<\/span>], <span>TOKENS_PER_QUERY<\/span>)\n    <span>ps5_min<\/span> <span>=<\/span> <span>ps5_minutes<\/span>(<span>wh<\/span>)\n\n    <span>wh_str<\/span> <span>=<\/span> <span>f&quot;{<\/span><span>wh<\/span><span>:.2f}&quot;<\/span> <span>if<\/span> <span>wh<\/span> <span>&lt;<\/span> <span>10<\/span> <span>else<\/span> <span>f&quot;{<\/span><span>wh<\/span><span>:.1f}&quot;<\/span>\n    <span>print<\/span>(<span>f&quot;{<\/span><span>model.strip<\/span>()<span>:&lt;{<\/span><span>MODEL_WIDTH<\/span><span>}} | {<\/span><span>wh_str<\/span><span>:&lt;{<\/span><span>WH_WIDTH<\/span><span>}} | {<\/span><span>ps5_min<\/span><span>:.2f}&quot;<\/span>)\n<\/code><\/pre><\/div>\n\n<p>This gives the following figures (reordered to show Wh per query in ascending\norder, and added a column for interactivity (1\/TPOT)):<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Model<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">Wh\/q<\/th>\n<th align=\"left\">PS5 min.<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">zai-org\/GLM-4.7-Flash (bf16)<\/td>\n<td align=\"left\">34.0<\/td>\n<td align=\"left\">0.68<\/td>\n<td align=\"left\">0.21<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">MiniMaxAI\/MiniMax-M2.5 (fp8)<\/td>\n<td align=\"left\">30.3<\/td>\n<td align=\"left\">1.38<\/td>\n<td align=\"left\">0.41<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">arcee-ai\/Trinity-Large-Preview (bf16)<\/td>\n<td align=\"left\">58.8<\/td>\n<td align=\"left\">2.85<\/td>\n<td align=\"left\">0.85<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">Qwen\/Qwen3.5-397B-A17B (bf16)<\/td>\n<td align=\"left\">41.7<\/td>\n<td align=\"left\">4.01<\/td>\n<td align=\"left\">1.20<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">zai-org\/GLM-5-FP8 (fp8)<\/td>\n<td align=\"left\">23.3<\/td>\n<td align=\"left\">7.05<\/td>\n<td align=\"left\">2.12<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As a point of comparison, the most efficient 8 GPU deployment of fp8 DeepSeek\nR1 0528 from my figures in the <a href=\"https:\/\/muxup.com\/2026q1\/per-query-energy-consumption-of-llms\">previous\narticle<\/a> was 3.32 Wh\nper query.<\/p>\n<p>And that's all I really have for today. Some interesting datapoints with\nhopefully more to come as Lambda puts up more model cards in this format.\nThere's a range of interesting potential further experiments to do, but for\nnow, I just wanted to share this initial look.<\/p>\n\n<hr \/><a href=\"https:\/\/muxup.com\/feed.xml#article-changelog\" class=\"anchor\" tabindex=\"-1\"><\/a>Article changelog\n<ul>\n<li>2026-02-17: Initial publication date.<\/li>\n<\/ul>                ","author":{"name":"Alex Bradbury","uri":"https:\/\/muxup.com"}},{"title":"Alex Bradbury: shandbox","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/muxup.com\/shandbox"}},"id":"https:\/\/muxup.com\/shandbox","updated":"2026-02-11T12:00:00+00:00","content":"\n<p><a href=\"https:\/\/github.com\/muxup\/medley\/blob\/main\/shandbox\"><code>shandbox<\/code><\/a> is a simple\nLinux sandboxing script that serves my needs well. Perhaps it works for you\ntoo? No dependencies between a shell and util-linux (<code>unshare<\/code> and <code>nsenter<\/code>).<\/p>\n<p>In short, it aims to provide fairly good isolation for personal files (i.e.\nyour <code>$HOME<\/code>) while being very convenient for day to day use. It's designed to\nbe run as an unprivileged user - as long as you can make new namespaces you\nshould be good to go. By default <code>\/home\/youruser\/sandbox<\/code> shows up as\n<code>\/home\/sandbox<\/code> within the sandbox, and other than standard paths like <code>\/usr<\/code>,\n<code>\/etc<\/code>, <code>\/tmp<\/code>, and so on it's left for you to either copy things into the\nsandbox or expose them via a mount. There's a single shared sandbox (i.e.\nprocesses within the sandbox can see and interact with each other, and the\nexposed sandbox filesystem is shared as well), which trades off some ease of\nuse for the security you might get with a larger number of more targeted\nsandboxes. On the other hand, you only gain security from a sandbox if you\nactually use it and this is a setup that offers very low friction for me. The\nnetwork is not namespaced (although this is something you could change with a\nsimple edit).<\/p>\n<p>Usability is both subjective and highly dependent on your actual use case, so\nthe tradeoffs may or may not align with what is interesting for you!\n<a href=\"https:\/\/github.com\/containers\/bubblewrap\">Bubblewrap<\/a> is an example of a\nmature alternative unprivileged sandboxing\ntool that offers a lot of configurability as well as options with greater\ndegrees of sandboxing. Beyond that, look to\n<a href=\"https:\/\/firecracker-microvm.github.io\/\">Firecracker<\/a> based solutions or\n<a href=\"https:\/\/gvisor.dev\/\">gvisor<\/a>. <code>shandbox<\/code> obviously aims to provide a\nreasonable sandbox as much as Linux namespaces alone are able to offer, but if\nyou're looking for a security property stronger than \"makes it harder for\nsomething to edit or access unwanted files\" it's down to you to both carefully\nreview its implementation and consider alternatives.<\/p>\n<h2 id=\"usage-example\"><a href=\"https:\/\/muxup.com\/feed.xml#usage-example\" class=\"anchor\" tabindex=\"-1\"><\/a>Usage example<\/h2>\n<pre><code>$ shandbox run uvx pycowsay\nInstalled 1 package in 5ms\n\n  ------------\n&lt; Hello, world &gt;\n  ------------\n   \\   ^__^\n    \\  (oo)\\_______\n       (__)\\       )\\\/\\\n           ||----w |\n           ||     ||\n$ shandbox status\nrunning (pid 1589364)\n\nlog:\n  2026-02-11 13:02:51 stopped\n  2026-02-11 13:05:06 started (pid 1589289)\n$ shandbox add-mount ~\/repos\/llvm-project \/home\/sandbox\/llvm-project\nmounted \/home\/asb\/repos\/llvm-project -&gt; \/home\/sandbox\/llvm-project\n$ shandbox run touch \/home\/sandbox\/llvm-project\/write-attempt\ntouch: cannot touch '\/home\/sandbox\/llvm-project\/write-attempt': Read-only file system\n$ shandbox remove-mount \/home\/sandbox\/llvm-project\nunmounted \/home\/sandbox\/llvm-project\n$ shandbox add-mount --read-write ~\/repos\/llvm-project \/home\/sandbox\/llvm-project\nmounted \/home\/asb\/repos\/llvm-project -&gt; \/home\/sandbox\/llvm-project\n$ shandbox run touch \/home\/sandbox\/llvm-project\/write-attempt\n<\/code><\/pre>\n<p><code>shandbox enter<\/code> will open a shell within the sandbox for easy interactive\nusage. As a convenience, if the current working directory is in\n<code>$HOME\/sandbox<\/code> (e.g. <code>$HOME\/sandbox\/foo<\/code>) then the working directory within\nthe sandbox for <code>shandbox run<\/code> or <code>shandbox enter<\/code> will be set to the\nappropriate path within the sandbox (<code>\/home\/sandbox\/foo<\/code> in this case). i.e.,\nthe case where this mapping is trivial. Environment variables are not passed\nthrough.<\/p>\n<h2 id=\"functionality-overview\"><a href=\"https:\/\/muxup.com\/feed.xml#functionality-overview\" class=\"anchor\" tabindex=\"-1\"><\/a>Functionality overview<\/h2>\n<ul>\n<li><code>shandbox start<\/code>: Start the sandbox, creating the necessary namespaces and\nmount layout. Fails if the sandbox is already running.<\/li>\n<li><code>shandbox stop<\/code>: Stop the sandbox by killing the process holding the\nnamespaces. Fails if the sandbox is not running.<\/li>\n<li><code>shandbox restart<\/code>: Stop the sandbox and start it again.<\/li>\n<li><code>shandbox status<\/code>: Print whether the sandbox is running and if it is, the\npid. Also print the last 20 lines of the log.<\/li>\n<li><code>shandbox enter<\/code>: Open bash within the sandbox, starting the sandbox first\nif it's not already running.<\/li>\n<li><code>shandbox run &lt;command&gt; [args...]<\/code>: Run a command inside the sandbox. The\ncurrent working directory is translated to an in-sandbox path if it falls\nwithin the sandbox home directory. Starts the sandbox first if it isn't\nalready running.<\/li>\n<li><code>shandbox add-mount [--read-write] &lt;host-path&gt; &lt;sandbox-path&gt;<\/code>: Bind-mount a\nhost path into the running sandbox. Mounts are read-only by default; pass\n<code>--read-write<\/code> to allow writes. The sandbox must already be running.\nBoth directories and individual files are supported.<\/li>\n<li><code>shandbox remove-mount &lt;sandbox-path&gt;<\/code>: Remove a previously added bind mount\nfrom the running sandbox.<\/li>\n<\/ul>\n<h2 id=\"implementation-approach\"><a href=\"https:\/\/muxup.com\/feed.xml#implementation-approach\" class=\"anchor\" tabindex=\"-1\"><\/a>Implementation approach<\/h2>\n<p>The core sandboxing functionality is provided by the Linux namespaces\nfunctionality exposed by\n<a href=\"https:\/\/manpages.debian.org\/unstable\/util-linux\/unshare.1.en.html\"><code>unshare<\/code><\/a>\nand\n<a href=\"https:\/\/manpages.debian.org\/unstable\/util-linux\/nsenter.1.en.html\"><code>nsenter<\/code><\/a>.\nThe <a href=\"https:\/\/github.com\/muxup\/medley\/blob\/main\/shandbox\">script's\nimplementation<\/a> should be\nquite readable but I'll try to summarise some key points here.<\/p>\n<p>The goal is that:<\/p>\n<ul>\n<li>Within the sandbox, you appear as an unprivileged user, with uid and gid\nequal to your usual Linux user.<\/li>\n<li>It should be possible to expose additional files or directories to the\nsandbox once it's running.<\/li>\n<li>Applications running within the sandbox have no way (modulo bugs or\nvulnerabilities in the kernel or accessible applications) of reaching files\non the host filesystem that aren't explicitly exposed.\n<ul>\n<li>To underline: This is a goal, it is <em>not<\/em> a guarantee.<\/li>\n<\/ul>\n<\/li>\n<li>It's possible to launch multiple processes within the sandbox which can all\nsee each other, and have the same shared sandboxed filesystem.<\/li>\n<li>This is all doable as an unprivileged user.<\/li>\n<\/ul>\n<p>To implement that:<\/p>\n<ul>\n<li>Two sets of namespaces are used to provide this isolation: the outer\n'shandbox_root' has the user mapped to root within the namespace and retains\naccess to standard \/ (allowing us to mount additional paths into after the\nsandbox has started). The inner 'shandbox_user' represents a new user\nnamepsace mapping our uid\/gid to an unprivileged user, but other namespaces\nare shared with 'shandbox_root'. Sandboxed processes are launched within the\nnamespaces of 'shandbox_user'.<\/li>\n<li>The process IDs of the initial process within 'sandbox_root' and\n'sandbox_user' are saved and recalled so the script can use <code>nsenter<\/code> to\nenter the namespace.<\/li>\n<li>To help make it easier to tell when you're in the sandbox, a dummy\n<code>\/etc\/passwd<\/code> is bind-mounted naming the current user as <code>sandbox<\/code>.<\/li>\n<li>When <code>shandbox start<\/code> is executed, the necessary directories are bind\nmounted in a directory that will be used as root (<code>\/<\/code>) for the user sandbox\nin <code>.local\/share\/shandbox\/root<\/code>. This happens within the sandbox_root\nnamespace, which then uses <code>unshare<\/code> again to create a new user namespace\nwith an unprivileged user, executing within a chroot.<\/li>\n<li>'sandbox_root' retains access to the host filesystem, which is necessary to\nallow mounting additional paths after the fact. Without this requirement, we\ncould likely rewrite <code>shandbox start<\/code> to use <code>pivot_root<\/code>.<\/li>\n<\/ul>\n<h2 id=\"making-it-your-own\"><a href=\"https:\/\/muxup.com\/feed.xml#making-it-your-own\" class=\"anchor\" tabindex=\"-1\"><\/a>Making it your own<\/h2>\n<p>The script should be straight-forward enough to customise to your needs if\nthey're not too dissimilar to what is offered out of the box. Some variables\nat the top provide things you may be more likely to want to change, such as\nthe home directory location, and a list of files or directories in <code>$HOME<\/code> to\nalways bind-mount into the sandbox home:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span>SANDBOX_HOME_DIR=<\/span><span>&quot;<\/span><span>$HOME<\/span><span>\/sandbox&quot;<\/span>\n<span>HOME_FILES_TO_MAP=<\/span><span>&quot;.bashrc .vimrc&quot;<\/span>\n<span>HOME_DIRS_TO_MAP=<\/span><span>&quot;.vim bin&quot;<\/span>\n<span>SB_HOME=<\/span><span>&quot;\/home\/sandbox&quot;<\/span>\n<span>SB_PATH=<\/span><span>&quot;<\/span><span>$SB_HOME<\/span><span>\/bin:\/usr\/local\/bin:\/usr\/bin&quot;<\/span>\n<\/code><\/pre><\/div>\n\n\n<hr \/><a href=\"https:\/\/muxup.com\/feed.xml#article-changelog\" class=\"anchor\" tabindex=\"-1\"><\/a>Article changelog\n<ul>\n<li>2026-02-11: Initial publication date.<\/li>\n<\/ul>                ","author":{"name":"Alex Bradbury","uri":"https:\/\/muxup.com"}},{"title":"Jos\u00e9 Dapena: Container Timing: measuring web components performance","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/"}},"id":"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/","updated":"2026-02-10T00:00:00+00:00","content":"\n                <img class=\"face\" src=\"\/images\/dape.png\" width=\"74\" height=\"100\" alt=\"\" align=\"right\" style=\"float: right\" \/>\n<p>Over the last year, as part of the collaboration between <a href=\"https:\/\/www.igalia.com\">Igalia<\/a> and <a href=\"https:\/\/www.techatbloomberg.com\/\">Bloomberg<\/a> to improve web performance observability, I worked on a new web performance API: <strong>Container Timing<\/strong>. This standard aims to make component-level performance measurement as easy as page-level metrics like LCP and FCP.<\/p>\n<p>My focus has been writing the native implementation in Chromium, which is now available behind a feature flag.<\/p>\n<p>In this post, I will explain why this API is needed, how it works, and how you can experiment with it today. In a follow-up post, I will dive deep into the implementation details within the Blink rendering engine.<\/p>\n<h2 id=\"the-problem-measuring-component-performance\" tabindex=\"-1\">The problem: measuring component performance <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>We currently use <a href=\"https:\/\/web.dev\/articles\/lcp\">Largest Contentful Paint (LCP)<\/a> and <a href=\"https:\/\/web.dev\/articles\/fcp\">First Contentful Paint (FCP)<\/a> to measure web page loading performance. Both metrics are page-scoped, meaning they evaluate the user perceived load speed for full page.<\/p>\n<p>The <a href=\"https:\/\/w3c.github.io\/element-timing\/\">Element Timing API<\/a> shifts the focus to individual DOM elements. By targetting specific elements, like hero images or a headers, we can measure their specific rendering performance independent of the rest of the page.<\/p>\n<p>However, modern web development is component-based. Developers build complex widgets (as grids, charts, feeds or panels) that are made of many elements. It is not trivial to understand the performance of those components:<\/p>\n<ul>\n<li>LCP may not be useful as another large image painting could delay it.<\/li>\n<li>Measuring a web component with Element Timing may require instrumenting all the significant elements one by one.<\/li>\n<\/ul>\n<p><img src=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/images\/container_timing_problem.png\" alt=\"A representation of a news web page, where the scope of LCP is the full web page, and Element Timing is a specific element, but we want to measure the latest news feed widget.\" class=\"dark-invert\" \/><\/p>\n<h2 id=\"the-solution-container-timing\" tabindex=\"-1\">The solution: Container Timing <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>This is where <strong>Container Timing<\/strong> comes in! With the new specification, a web developer can mark subtrees of the DOM as \u201ccontainers\u201d. Then, it provides performance entries aggregating the painting time of that subtree.<\/p>\n<p><img src=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/images\/container_timing_solution.png\" alt=\"A representation of a news web page, where aggregating the paints of the children of the news feed widget allows to know when its painting has finished.\" class=\"dark-invert\" \/><\/p>\n<p>This way, we can answer: \u201cwhen did a specific component finish painting its content?\u201d.<\/p>\n<p>Some examples:<\/p>\n<ul>\n<li><strong>Breaking down the contributors to the initial page load<\/strong>: with <strong>Container Timing<\/strong> we can focus on the components that are more relevant to the user experience.<\/li>\n<li><strong>Single page application navigation<\/strong>: when a soft navigation shows a new component on the screen, we can obtain painting information for it.<\/li>\n<li><strong>Lazy-loaded components<\/strong>: Tracking when a widget that loads below the fold is fully visible.<\/li>\n<li><strong>Third-party content<\/strong>: Monitoring the performance of ads or embedded widgets.<\/li>\n<\/ul>\n<p>You just need to add, to the top element of the subtree, the new attribute <code>containertiming<\/code>. When you add it to an HTML element, the browser will track all the painting updates of that element and its descendants.<\/p>\n<p>What happens under the hood? The browser will start monitoring the rendering pipeline for paints that contribute to representing the subtree. When a new frame is painted, if that paints new areas for that subtree, it reports a performance entry showing the increase in painted area. It is similar to LCP, but for a specific subtree!<\/p>\n<h2 id=\"how-to-use-container-timing\" tabindex=\"-1\">How to use Container Timing? <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>Using the API is straightforward. First, mark the containers you want to track in HTML:<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>div<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>my-widget<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">containertiming<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>widget-load<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>img<\/span> <span class=\"token attr-name\">src<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>graph.png<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token punctuation\">\/><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>p<\/span><span class=\"token punctuation\">><\/span><\/span>Loading data...<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>p<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>div<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<p>Then, use a <code>PerformanceObserver<\/code> to listen for container entries:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const<\/span> observer <span class=\"token operator\">=<\/span> <span class=\"token keyword\">new<\/span> <span class=\"token class-name\">PerformanceObserver<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">list<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">=><\/span> <span class=\"token punctuation\">{<\/span><br \/>  list<span class=\"token punctuation\">.<\/span><span class=\"token function\">getEntries<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">forEach<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">entry<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">=><\/span> <span class=\"token punctuation\">{<\/span><br \/>    console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`<\/span><span class=\"token string\">Container '<\/span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${<\/span>entry<span class=\"token punctuation\">.<\/span>identifier<span class=\"token interpolation-punctuation punctuation\">}<\/span><\/span><span class=\"token string\">' painted.<\/span><span class=\"token template-punctuation string\">`<\/span><\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>    console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`<\/span><span class=\"token string\">Time: <\/span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${<\/span>entry<span class=\"token punctuation\">.<\/span>startTime<span class=\"token interpolation-punctuation punctuation\">}<\/span><\/span><span class=\"token template-punctuation string\">`<\/span><\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>    console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`<\/span><span class=\"token string\">Size: <\/span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${<\/span>entry<span class=\"token punctuation\">.<\/span>size<span class=\"token interpolation-punctuation punctuation\">}<\/span><\/span><span class=\"token template-punctuation string\">`<\/span><\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <span class=\"token comment\">\/\/ The area painted<\/span><br \/>  <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/>observer<span class=\"token punctuation\">.<\/span><span class=\"token function\">observe<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"container\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">buffered<\/span><span class=\"token operator\">:<\/span> <span class=\"token boolean\">true<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>When the web contents load, new Performance entries will be emitted with the container updates.<\/p>\n<p>Which entry will be interesting? The API lets you choose what best fits your needs! Some ideas:<\/p>\n<ul>\n<li>The most important entry could be the last one: the one that increased the painted area for the last time. Something similar to LCP.<\/li>\n<li>Or maybe the last one that contributed a significant size increase?<\/li>\n<li>Or the last one before a user interaction?<\/li>\n<\/ul>\n<h2 id=\"a-native-implementation-for-chromium\" tabindex=\"-1\">A native implementation for Chromium <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>In the initial steps of the specification, Jason Williams wrote a <a href=\"https:\/\/github.com\/bloomberg\/container-timing\/tree\/main\/polyfill\">polyfill<\/a> that worked on top of Element Timing. This was very useful to understand and polish the kind of information the specification could provide. However, this had its own performance impact.<\/p>\n<div class=\"markdown-alert markdown-alert-warning\"><p class=\"markdown-alert-title\">Deprecation Notice:<\/p><p>The polyfill is now deprecated and no longer maintained, as the native API cannot be fully replicated using Element Timing. Please use the native implementation for accurate results.<\/p>\n<\/div>\n<p>So I started a native implementation in Chromium. The main idea was working on top of the already existing implementation for Element Timing, and add the remaining bits.<\/p>\n<p>In my next blog post I will go through the implementation details. But, for this post, it is relevant to state that the goals of this native implementation were:<\/p>\n<ul>\n<li>Minimizing the overhead. It should be almost zero when elements are not interesting to <strong>Container Timing<\/strong>, and very fast and light when paints were relevant.<\/li>\n<li>It should reuse as much as possible of the already existing logic for Element Timing.<\/li>\n<\/ul>\n<p>The native implementation has landed and is available in Chromium144+, but still behind the <code>ContainerTiming<\/code> feature flag.<\/p>\n<p>You can experiment with this feature locally by passing the following flag to Chromium at startup:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">chrome --enable-blink-features<span class=\"token operator\">=<\/span>ContainerTiming<\/code><\/pre>\n<p>Or you can just enable the \u201cExperimental Web Platform features\u201d in <code>chrome:\/\/flags<\/code>.<\/p>\n<h2 id=\"upcoming-trials\" tabindex=\"-1\">Upcoming trials <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>So now, it is time to collect feedback from the actual web developers.<\/p>\n<p>We have already presented the specification in several conferences (as <a href=\"https:\/\/www.igalia.com\/downloads\/slides\/josedapenapaz-containertiming.pdf\">BlinkOn 20<\/a> or <a href=\"https:\/\/perfnow.nl\/2024\/\">Performance.now() 2024<\/a>). And discussions are ongoing in the <a href=\"https:\/\/www.w3.org\/webperf\/\">Web Performance Working Group<\/a>.<\/p>\n<p>We just <a href=\"https:\/\/groups.google.com\/a\/chromium.org\/g\/blink-dev\/c\/FnM3lweVssM\/m\/eVhhCtG5AQAJ\">announced the Dev Trial in the blink-dev mailing list<\/a>! The feature is now officially ready for testing.<\/p>\n<p>What\u2019s next? We are also preparing an Origin Trial, that will allow developers to test the specification in production for a subset of their users.<\/p>\n<p>If you want to provide feedback, we are collecting it in the explainer <a href=\"https:\/\/github.com\/bloomberg\/container-timing\/issues\">ticket tracker<\/a>.<\/p>\n<h2 id=\"wrapping-up\" tabindex=\"-1\">Wrapping up <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>With Container Timing, you will be able to measure paintings at the web component level, filling a significant gap in the web performance monitoring landscape.<\/p>\n<p>If you struggled with finding out the ready time of your widgets, just try it! It is available, under the feature flags <code>ContainerTiming<\/code>, in Chromium Stable today.<\/p>\n<p>And stay tuned! In a follow up post, I will go through the native implementation details in Chromium.<\/p>\n<h2 id=\"thanks\" tabindex=\"-1\">Thanks! <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<p>This  has been done as part of the collaboration between <a href=\"https:\/\/techatbloomberg.com\">Bloomberg<\/a> and <a href=\"https:\/\/www.igalia.com\">Igalia<\/a>. Thanks!<\/p>\n<p><a href=\"https:\/\/www.igalia.com\">\n<source media=\"(prefers-color-scheme: dark)\">\n<img src=\"https:\/\/blogs.igalia.com\/dape\/img\/igalia_-_500px_-_RGB_-_Feb23-580x210.png\" alt=\"Igalia\" \/>\n<\/source><\/a> <a href=\"https:\/\/techatbloomberg.com\"><img src=\"https:\/\/blogs.igalia.com\/dape\/img\/Bloomberg-logo-580x117.png\" alt=\"Bloomberg\" class=\"dark-invert\" \/><\/a><\/p>\n<h2 id=\"references\" tabindex=\"-1\">References <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/dape\/2026\/02\/10\/container-timing-measuring-web-components-performance\/\">#<\/a><\/h2>\n<ul>\n<li><a href=\"https:\/\/github.com\/bloomberg\/container-timing\">Container Timing explainer<\/a><\/li>\n<li><a href=\"https:\/\/bloomberg.github.io\/container-timing\/\">Container Timing specification draft<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/bloomberg\/container-timing\/issues\">Ticket tracker for specification discussion<\/a><\/li>\n<li><a href=\"https:\/\/chromestatus.com\/feature\/5110962817073152\">Chrome status feature: Container Timing<\/a><\/li>\n<li><a href=\"https:\/\/groups.google.com\/a\/chromium.org\/g\/blink-dev\/c\/FnM3lweVssM\/m\/eVhhCtG5AQAJ\">Container Timing ready for testing announcement in blink-dev<\/a><\/li>\n<li><a href=\"https:\/\/issues.chromium.org\/382422286\">Container Timing native implementation Chromium issue<\/a><\/li>\n<\/ul>                ","author":{"name":"Jos\u00e9 Dapena","uri":"https:\/\/blogs.igalia.com\/dape\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #56","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-56\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-56\/","updated":"2026-02-09T23:21:30+00:00","content":"\n<p>Update on what happened in WebKit in the week from February 2 to February 9.<\/p>\n<p>\nThe main event this week was FOSDEM (pun intended), which included\npresentations related to WebKit; but also we got a batch of stable\nand development releases, asynchronous scrolling work, OpenGL\nlogging, cleanups, and improving the inspector for the WPE work.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p>While asynchronous scrolling for mouse wheel events was already supported,\nscrollbar layers were still being painted on the main thread. This has been\n<a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306838@main\">changed<\/a> to paint scrollbars on the\nscrolling thread instead, which avoids scrollbars to \u201clag\u201d behind scrolled\ncontent.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306987@main\">Fixed<\/a> flickering caused by the\ncombination of damage tracking and asynchronous scrolling for mouse wheel\nevents.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>It is now possible to <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306778@main\">enable debug logging for OpenGL\ncontexts<\/a> using the new <code>GLContext<\/code> log\nchannel, which takes advantage of the message events produced by the\n<a rel=\"external\" href=\"https:\/\/wikis.khronos.org\/opengl\/Debug_Output\">widespread KHR_debug\nextension<\/a>.<\/p>\n<p>Figuring out the exact location inside WebKit that triggered an OpenGL issue\nmay still be challenging with this aid, and therefore <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306862@main\">a backtrace will be\nappended<\/a> in case of errors to help\npinpoint the source, when the log channel is enabled at the \u201cdebug\u201d level with\n<code>GLContext=debug<\/code>.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>Configuring the build with <code>USE_SKIA=OFF<\/code> to make WebKit use the\n<a rel=\"external\" href=\"https:\/\/cairographics.org\/\">Cairo<\/a> graphics library <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306343@main\">is no longer\nsupported<\/a>. Using\n<a rel=\"external\" href=\"https:\/\/skia.org\">Skia<\/a> has been the default <a rel=\"external\" href=\"https:\/\/blogs.igalia.com\/carlosgc\/2024\/09\/27\/graphics-improvements-in-webkitgtk-and-wpewebkit-2-46\/\">since  late\n2024<\/a>,\nand after two full years the 2.54.0 release (due in September 2026)\nwill be the first one where the choice is no longer possible.<\/p>\n  <\/div>\n<h2 id=\"webkitgtk-desktop\">WebKitGTK \ud83d\udda5\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p>The \u201con demand\u201d hardware acceleration policy has been rarely used lately, and\nthus support for it has been <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306855@main\">removed<\/a>.\nNote that this affects only the GTK port when built with GTK 3\u2014the option never\nexisted when using GTK 4.<\/p>\n<p>Existing GTK 3 applications that use\n<code>WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND<\/code> will continue to work and do\n<strong>not<\/strong> need rebuilding: they will be promoted to use the \u201calways enabled\u201d policy\nstarting with WebKitGTK 2.54.0 (due in September 2026).<\/p>\n  <\/div>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n  <div class=\"wip-item\">\n<p>The Web Inspector <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306914@main\">has received\nsupport<\/a> for saving data to local\nfiles, allowing things such as saving page resources or exporting the network\nsession to a <a rel=\"external\" href=\"https:\/\/en.wikipedia.org\/wiki\/HAR_(file_format)\">HAR archive<\/a>.<\/p>\n<p>Note that using the Web Inspector locally is supported when using the\nWPEPlatform API, and the keyboard shortcut <kbd title=\"Control + Shift + I\">Ctrl+Shift+I<\/kbd> may be used to bring it up.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/02\/09\/webkitgtk2.50.5-released.html\">WebKitGTK\n2.50.5<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.50.5.html\">WPE WebKit 2.50.5<\/a> have\nbeen released. These are stable maintenance releases that improves stability,\ncorrect bugs, and fixes small rendering issues.<\/p>\n<p>The second release candidates for the upcoming stable branch, <a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/02\/06\/webkitgtk2.51.91-released.html\">WebKitGTK\n2.51.91<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.51.91.html\">WPE WebKit 2.51.91<\/a>,\nhave been published as well. Those using those to preview the upcoming 2.52.x\nseries are encouraged to provide <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\/\">bug reports in\nBugzilla<\/a> for any issue they may experience.<\/p>\n  <\/div>\n<h2 id=\"community-events-handshake\">Community &amp; Events \ud83e\udd1d<\/h2>\n  <div class=\"wip-item\">\n<p>We have published a <a rel=\"external\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">blog\npost<\/a>\non our work implementing the\n<a rel=\"external\" href=\"https:\/\/tc39.es\/proposal-temporal\/docs\/\">Temporal<\/a> proposal in JavaScriptCore,\nWebKit's JavaScript engine.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>This year's edition of <a rel=\"external\" href=\"https:\/\/fosdem.org\/2026\/\">FOSDEM<\/a> took place in\nBrussels between January 31st and February 1st, and featured a number of\nsessions related to WebKitGTK and WPE:<\/p>\n<ul>\n<li><a rel=\"external\" href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/8ZL9BZ-web-platform-on-linux-devices-with-webkit\/\">The Web Platform on Linux devices with WebKit: where are we\nnow?<\/a>,\nby Mario S\u00e1nchez, is a good introduction-level talk about the GTK and WPE\nWebKit ports.<\/li>\n<li><a rel=\"external\" href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/KMMLGM-webrtc_support_in_webkitgtk_and_wpewebkit_with_gstreamer_current_status_and_plan\/\">WebRTC support in WebKitGTK and WPEWebKit with GStreamer: Current status and\nplans<\/a>\nby Philippe Normand. Exactly what it says on the tin.<\/li>\n<li><a rel=\"external\" href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/NJM3KB-mathml-core\/\">Interop and MathML\nCore<\/a> by Eri\nPazos, about the ongoing effort to improve how different Web engines handle\nMathML\u2014including WebKit!<\/li>\n<\/ul>\n<p>The videos for the talks are already available, too.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Andy Wingo: six thoughts on generating c","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/wingolog.org\/archives\/2026\/02\/09\/six-thoughts-on-generating-c"}},"id":"https:\/\/wingolog.org\/2026\/02\/09\/six-thoughts-on-generating-c","updated":"2026-02-09T13:47:44+00:00","content":"\n<div><p>So I work in compilers, which means that I write programs that translate\nprograms to programs.  Sometimes you will want to target a language at a\nhigher level than just, like, assembler, and oftentimes C is that\nlanguage.  Generating C is less fraught than writing C by hand, as the\ngenerator can often avoid the undefined-behavior pitfalls that one has\nto be so careful about when writing C by hand.  Still, I have found some\npatterns that help me get good results.<\/p><p>Today\u2019s note is a quick summary of things that work for me.  I won\u2019t be\nso vain as to call them \u201cbest practices\u201d, but they are my practices, and\nyou can have them too if you like.<\/p><h3>static inline functions enable data abstraction<\/h3><p>When I learned C, in the early days of\n<a href=\"https:\/\/gstreamer.freedesktop.org\/\">GStreamer<\/a> (oh bless its heart it\nstill has the same web page!), we used lots of preprocessor macros.\nMostly we got the message over time that <a href=\"https:\/\/gcc.gnu.org\/onlinedocs\/gcc\/Inline.html\">many macro uses should have\nbeen inline functions<\/a>;\nmacros are for token-pasting and generating names, not for data access\nor other implementation.<\/p><p>But what I did not appreciate until much later was that always-inline\nfunctions remove any possible performance penalty for data abstractions.\nFor example, in <a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\">Wastrel<\/a>, I can\ndescribe a bounded range of WebAssembly memory via a <tt>memory<\/tt> struct,\nand an access to that memory in another struct:<\/p><pre class=\"pre-c\">struct memory { uintptr_t base; uint64_t size; };\nstruct access { uint32_t addr; uint32_t len; };\n<\/pre><p>And then if I want a writable pointer to that memory, I can do so:<\/p><pre class=\"pre-c\">#define static_inline \\\n  static inline __attribute__((always_inline))\n\nstatic_inline void* write_ptr(struct memory m, struct access a) {\n  BOUNDS_CHECK(m, a);\n  char *base = __builtin_assume_aligned((char *) m.base_addr, 4096);\n  return (void *) (base + a.addr);\n}\n<\/pre><p>(Wastrel usually omits any code for <tt>BOUNDS_CHECK<\/tt>, and just relies on\nmemory being mapped into a <tt>PROT_NONE<\/tt> region of an appropriate size.\nWe use a macro there because if the bounds check fails and kills the\nprocess, it\u2019s nice to be able to use <tt>__FILE__<\/tt> and <tt>__LINE__<\/tt>.)<\/p><p>Regardless of whether explicit bounds checks are enabled, the\n<tt>static_inline<\/tt> attribute ensures that the abstraction cost is entirely\nburned away; and in the case where bounds checks are elided, we don\u2019t\nneed the <tt>size<\/tt> of the memory or the <tt>len<\/tt> of the access, so they won\u2019t\nbe allocated at all.<\/p><p>If <tt>write_ptr<\/tt> wasn\u2019t <tt>static_inline<\/tt>, I would be a little worried that\nsomewhere one of these <tt>struct<\/tt> values would get passed through memory.\nThis is mostly a concern with functions that return structs by value;\nwhereas in e.g. AArch64, returning a <tt>struct memory<\/tt> would use the same\nregisters that a call to <tt>void (*)(struct memory)<\/tt> would use for the\nargument, the SYS-V x64 ABI only allocates two general-purpose registers\nto be used for return values.  I would mostly prefer to not think about\nthis flavor of bottleneck, and that is what static inline functions do\nfor me.<\/p><h3>avoid implicit integer conversions<\/h3><p>C has an odd set of default integer conversions, for example promoting\n<tt>uint8_t<\/tt> to <tt>signed int<\/tt>, and also has weird boundary conditions for\nsigned integers.  When generating C, we should probably sidestep these\nrules and instead be explicit: define static inline <tt>u8_to_u32<\/tt>,\n<tt>s16_to_s32<\/tt>, etc conversion functions, and turn on <tt>-Wconversion<\/tt>.<\/p><p>Using static inline cast functions also allows the generated code to assert\nthat operands are of a particular type.  Ideally, you end up in a\nsituation where all casts are in your helper functions, and no cast is\nin generated code.<\/p><h3>wrap raw pointers and integers with intent<\/h3><p><a href=\"https:\/\/github.com\/wingo\/whippet\">Whippet<\/a> is a garbage collector\nwritten in C.  A garbage collector cuts across all data abstractions:\nobjects are sometimes viewed as absolute addresses, or ranges in a paged\nspace, or offsets from the beginning of an aligned region, and so on.\nIf you represent all of these concepts with <tt>size_t<\/tt> or <tt>uintptr_t<\/tt> or\nwhatever, you\u2019re going to have a bad time.  So Whippet has <a href=\"https:\/\/github.com\/wingo\/whippet\/blob\/main\/api\/gc-ref.h#L9-L11\"><tt>struct gc_ref<\/tt><\/a>,\n<a href=\"https:\/\/github.com\/wingo\/whippet\/blob\/main\/api\/gc-edge.h#L6-L8\"><tt>struct gc_edge<\/tt><\/a>,\nand the like: single-member structs whose purpose it is to avoid\nconfusion by partitioning sets of applicable operations.  A\n<tt>gc_edge_address<\/tt> call will never apply to a <tt>struct gc_ref<\/tt>, and so on\nfor other types and operations.<\/p><p>This is a great pattern for hand-written code, but it\u2019s particularly\npowerful for compilers: you will often end up compiling a term of a\nknown type or kind and you would like to avoid mistakes in the residualized\nC.<\/p><p>For example, when compiling WebAssembly, consider <a href=\"https:\/\/webassembly.github.io\/spec\/core\/exec\/instructions.html#xref-syntax-instructions-syntax-instr-struct-mathsf-struct-set-x-i\"><tt>struct.set<\/tt>\u2018s\noperational\nsemantics<\/a>:\nthe textual rendering states, \u201cAssert: Due to validation, <i>val<\/i> is some\n<tt>ref.struct structaddr<\/tt>.\u201d  Wouldn\u2019t it be nice if this assertion could\ntranslate to C?  Well in this case it can: with single-inheritance\nsubtyping (as WebAssembly has), you can make a forest of pointer\nsubtypes:<\/p><pre class=\"pre-c\">typedef struct anyref { uintptr_t value; } anyref;\ntypedef struct eqref { anyref p; } eqref;\ntypedef struct i31ref { eqref p; } i31ref;\ntypedef struct arrayref { eqref p; } arrayref;\ntypedef struct structref { eqref p; } structref;\n<\/pre><p>So for a <tt>(type $type_0 (struct (mut f64)))<\/tt>, I might generate:<\/p><pre class=\"pre-c\">typedef struct type_0ref { structref p; } type_0ref;\n<\/pre><p>Then if I generate a field setter for <tt>$type_0<\/tt>, I make it take a\n<tt>type_0ref<\/tt>:<\/p><pre class=\"pre-c\">static inline void\ntype_0_set_field_0(type_0ref obj, double val) {\n  ...\n}\n<\/pre><p>In this way the types carry through from source to target language.\nThere is a similar type forest for the actual object representations:<\/p><pre>typedef struct wasm_any { uintptr_t type_tag; } wasm_any;\ntypedef struct wasm_struct { wasm_any p; } wasm_struct;\ntypedef struct type_0 { wasm_struct p; double field_0; } type_0;\n...\n<\/pre><p>And we generate little cast routines to go back and forth between\n<tt>type_0ref<\/tt> and <tt>type_0*<\/tt> as needed.  There is no overhead because all\nroutines are static inline, and we get pointer subtyping for free: if a\n<tt>struct.set $type_0 0<\/tt> instruction is passed a subtype of <tt>$type_0<\/tt>, the\ncompiler can generate an upcast that type-checks.<\/p><h3>fear not <tt>memcpy<\/tt><\/h3><p>In WebAssembly, accesses to linear memory are not necessarily aligned,\nso we can\u2019t just cast an address to (say) <tt>int32_t*<\/tt> and dereference.\nInstead we <tt>memcpy(&amp;i32, addr, sizeof(int32_t))<\/tt>, and trust the compiler\nto just emit an unaligned load if it can (and it can).  No need for more\nwords here!<\/p><h3>for ABI and tail calls, perform manual register allocation<\/h3><p>So, <a href=\"https:\/\/gcc.gnu.org\/onlinedocs\/gcc\/Statement-Attributes.html#index-musttail-statement-attribute\">GCC finally has\n<tt>__attribute__((musttail))<\/tt><\/a>:\npraise be.  However, when compiling WebAssembly, it could be that you\nend up compiling a function with, like 30 arguments, or 30 return\nvalues; I don\u2019t trust a C compiler to reliably shuffle between different\nstack argument needs at tail calls to or from such a function.  It could\neven refuse to compile a file if it can\u2019t meet its <tt>musttail<\/tt>\nobligations; not a good characteristic for a target language.<\/p><p>Really you would like it if all function parameters were allocated to\nregisters.  You can ensure this is the case if, say, you only pass the\nfirst <i>n<\/i> values in registers, and then pass the rest in global\nvariables.  You don\u2019t need to pass them on a stack, because you can make\nthe callee load them back to locals as part of the prologue.<\/p><p>What\u2019s fun about this is that it also neatly enables multiple return\nvalues when compiling to C: simply go through the set of function types\nused in your program, allocate enough global variables of the right\ntypes to store all return values, and make a function epilogue store any\n\u201cexcess\u201d return values\u2014those beyond the first return value, if any\u2014in\nglobal variables, and have callers reload those values right after\ncalls.<\/p><h3>what\u2019s not to like<\/h3><p>Generating C is a local optimum: you get the industrial-strength\ninstruction selection and register allocation of GCC or Clang, you don\u2019t\nhave to implement many peephole-style optimizations, and you get to link\nto to possibly-inlinable C runtime routines.  It\u2019s hard to improve over\nthis design point in a marginal way.<\/p><p>There are drawbacks, of course.  As a Schemer, my largest source of\nannoyance is that I don\u2019t have control of the stack: I don\u2019t know how\nmuch stack a given function will need, nor can I extend the stack of my\nprogram in any reasonable way.  I can\u2019t iterate the stack to precisely\nenumerate embedded pointers (<a href=\"https:\/\/wingolog.org\/archives\/2024\/09\/07\/conservative-gc-can-be-faster-than-precise-gc\">but perhaps that\u2019s\nfine<\/a>).\nI certainly can\u2019t slice a stack to capture a delimited continuation.<\/p><p>The other major irritation is about side tables: one would like to be\nable to implement so-called <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220228-00\/?p=106296\">zero-cost\nexceptions<\/a>,\nbut without support from the compiler and toolchain, it\u2019s impossible.<\/p><p>And finally, source-level debugging is gnarly.  You would like to be\nable to embed DWARF information corresponding to the code you\nresidualize; I don\u2019t know how to do that when generating C.<\/p><p>(Why not Rust, you ask?  Of course you are asking that.  For what it is\nworth, I have found that lifetimes are a frontend issue; if I had a\nsource language with explicit lifetimes, I would consider producing\nRust, as I could machine-check that the output has the same guarantees\nas the input.  Likewise if I were using a Rust standard library.  But if\nyou are compiling <i>from<\/i> a language without fancy lifetimes, I don\u2019t\nknow what you would get from Rust: fewer implicit conversions, yes, but\nless mature tail call support, longer compile times... it\u2019s a wash, I\nthink.)<\/p><p>Oh well.  Nothing is perfect, and it\u2019s best to go into things with your\neyes wide open.  If you got down to here, I hope these notes help you in\nyour generations.  For me, once my generated C type-checked, it worked:\nvery little debugging has been necessary.  Hacking is not always like\nthis, but I\u2019ll take it when it comes.  Until next time, happy hacking!<\/p><\/div>                ","author":{"name":"Andy Wingo","uri":"https:\/\/wingolog.org\/"}},{"title":"Andy Wingo: ahead-of-time wasm gc in wastrel","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/wingolog.org\/archives\/2026\/02\/06\/ahead-of-time-wasm-gc-in-wastrel"}},"id":"https:\/\/wingolog.org\/2026\/02\/06\/ahead-of-time-wasm-gc-in-wastrel","updated":"2026-02-06T15:48:17+00:00","content":"\n<div><p>Hello friends!  Today, a quick note: the\n<a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/\">Wastrel<\/a> ahead-of-time\nWebAssembly compiler now supports managed memory via garbage collection!<\/p><h3>hello, world<\/h3><p>The quickest demo I have is that you should check out and build wastrel\nitself:<\/p><pre>git clone https:\/\/codeberg.org\/andywingo\/wastrel\ncd wastrel\nguix shell\n# alternately: sudo apt install guile-3.0 guile-3.0-dev \\\n#    pkg-config gcc automake autoconf make\nautoreconf -vif &amp;&amp; .\/configure\nmake -j\n<\/pre><p>Then run a quick check with <a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/src\/branch\/main\/examples\/simple-string.wat\">hello, world<\/a>:<\/p><pre>$ .\/pre-inst-env wastrel examples\/simple-string.wat\nHello, world!\n<\/pre><p>Now give a check to\n<a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/src\/branch\/main\/examples\/gcbench.wat\"><tt>gcbench<\/tt><\/a>,\na classic GC micro-benchmark:<\/p><pre>$ WASTREL_PRINT_STATS=1 .\/pre-inst-env wastrel examples\/gcbench.wat\nGarbage Collector Test\n Creating long-lived binary tree of depth 16\n Creating a long-lived array of 500000 doubles\nCreating 33824 trees of depth 4\n\tTop-down construction: 10.189 msec\n\tBottom-up construction: 8.629 msec\nCreating 8256 trees of depth 6\n\tTop-down construction: 8.075 msec\n\tBottom-up construction: 8.754 msec\nCreating 2052 trees of depth 8\n\tTop-down construction: 7.980 msec\n\tBottom-up construction: 8.030 msec\nCreating 512 trees of depth 10\n\tTop-down construction: 7.719 msec\n\tBottom-up construction: 9.631 msec\nCreating 128 trees of depth 12\n\tTop-down construction: 11.084 msec\n\tBottom-up construction: 9.315 msec\nCreating 32 trees of depth 14\n\tTop-down construction: 9.023 msec\n\tBottom-up construction: 20.670 msec\nCreating 8 trees of depth 16\n\tTop-down construction: 9.212 msec\n\tBottom-up construction: 9.002 msec\nCompleted 32 major collections (0 minor).\n138.673 ms total time (12.603 stopped); 209.372 ms CPU time (83.327 stopped).\n0.368 ms median pause time, 0.512 p95, 0.800 max.\nHeap size is 26.739 MB (max 26.739 MB); peak live data 5.548 MB.\n<\/pre><p>We set <tt>WASTREL_PRINT_STATS=1<\/tt> to get those last 4 lines.\nSo, this is a microbenchmark: it runs for only 138 ms, and the heap is\ntiny (26.7 MB).  It does collect 30 times, which is something.<\/p><h3>is it good?<\/h3><p>I know what you are thinking: OK, it\u2019s a microbenchmark, but can it tell us anything about how Wastrel compares to V8?  Well, probably so:<\/p><pre>$ guix shell node time -- \\\n   time node js-runtime\/run.js -- \\\n     js-runtime\/wtf8.wasm examples\/gcbench.wasm\nGarbage Collector Test\n[... some output elided ...]\ntotal_heap_size: 48082944\n[...]\n0.23user 0.03system 0:00.20elapsed 128%CPU (0avgtext+0avgdata 87844maxresident)k\n0inputs+0outputs (0major+13325minor)pagefaults 0swaps\n<\/pre><p>Which is to say, V8 takes more CPU time (230ms vs 209ms) and more\nwall-clock time (200ms vs 138ms).  Also it uses twice as much\nmanaged memory (48 MB vs 26.7 MB), and more than that for the total\nprocess (88 MB vs 34 MB, not shown).<\/p><h3>improving on v8, really?<\/h3><p>Let\u2019s try with\n<a href=\"https:\/\/codeberg.org\/andywingo\/wastrel\/src\/branch\/main\/examples\/quads.wat\"><tt>quads<\/tt><\/a>,\nwhich at least has a larger active heap size.  This time we\u2019ll compile a binary and then run it:<\/p><pre>$ .\/pre-inst-env wastrel compile -o quads examples\/quads.wat\n$ WASTREL_PRINT_STATS=1 guix shell time -- time .\/quads \nMaking quad tree of depth 10 (1398101 nodes).\n\tconstruction: 23.274 msec\nAllocating garbage tree of depth 9 (349525 nodes), 60 times, validating live tree each time.\n\tallocation loop: 826.310 msec\n\tquads test: 860.018 msec\nCompleted 26 major collections (0 minor).\n848.825 ms total time (85.533 stopped); 1349.199 ms CPU time (585.936 stopped).\n3.456 ms median pause time, 3.840 p95, 5.888 max.\nHeap size is 133.333 MB (max 133.333 MB); peak live data 82.416 MB.\n1.35user 0.01system 0:00.86elapsed 157%CPU (0avgtext+0avgdata 141496maxresident)k\n0inputs+0outputs (0major+231minor)pagefaults 0swaps\n<\/pre><p>Compare to V8 via node:<\/p><pre>$ guix shell node time -- time node js-runtime\/run.js -- js-runtime\/wtf8.wasm examples\/quads.wasm\nMaking quad tree of depth 10 (1398101 nodes).\n\tconstruction: 64.524 msec\nAllocating garbage tree of depth 9 (349525 nodes), 60 times, validating live tree each time.\n\tallocation loop: 2288.092 msec\n\tquads test: 2394.361 msec\ntotal_heap_size: 156798976\n[...]\n3.74user 0.24system 0:02.46elapsed 161%CPU (0avgtext+0avgdata 382992maxresident)k\n0inputs+0outputs (0major+87866minor)pagefaults 0swaps\n<\/pre><p>Which is to say, <i>wastrel is almost three times as fast, while using\nalmost three times less memory<\/i>: 2460ms (v8) vs 849ms (wastrel), and\n383MB vs 141 MB.<\/p><h3>zowee!<\/h3><p>So, yes, the V8 times include the time to compile the wasm module on the fly.  No idea what is going on with tiering, either, but I understand that tiering up is a thing these days; this is node v22.14, released about a year ago, for what that\u2019s worth.  Also, there is a V8-specific module to do some impedance-matching with regards to strings; in Wastrel they are WTF-8 byte arrays, whereas in Node they are JS strings.  But it\u2019s not a string benchmark, so I doubt that\u2019s a significant factor.<\/p><p>I think the performance edge comes in having the program ahead-of-time: you can statically allocate type checks, statically allocate object shapes, and the compiler can see through it all.  But I don\u2019t really know yet, as I just got everything working this week.<\/p><p>Wastrel with GC is demo-quality, thus far.  If you\u2019re interested in the back-story and the making-of, see <a href=\"https:\/\/wingolog.org\/archives\/2025\/10\/30\/wastrel-a-profligate-implementation-of-webassembly\">my intro to Wastrel<\/a> article from October, or the FOSDEM talk from last week:<\/p><video poster=\"https:\/\/wingolog.org\/pub\/fosdem-2026-wastrel-webassembly-without-the-runtime.jpg\" controls=\"controls\" width=\"100%\">\n  <source src=\"https:\/\/wingolog.org\/pub\/fosdem-2026-wastrel-webassembly-without-the-runtime.vp9.webm\" type=\"video\/webm; codecs=vp9,opus\"><\/source>\n  <source src=\"https:\/\/video.fosdem.org\/2026\/ub4136\/HT9HAG-wastrel-webassembly-without-the-runtime.mp4\" type=\"video\/mp4\"><\/source>\n<\/video><p>Slides <a href=\"https:\/\/wingolog.org\/pub\/fosdem-wastrel-2026-slides.pdf\">here<\/a>, if that\u2019s your thing.<\/p><p>More to share on this next week, but for now I just wanted to get the\nword out.  Happy hacking and have a nice weekend!<\/p><\/div>                ","author":{"name":"Andy Wingo","uri":"https:\/\/wingolog.org\/"}},{"title":"Igalia Compilers Team: Igalia\u2019s Compilers Team - A 2025 Retrospective","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/"}},"id":"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/","updated":"2026-02-06T00:00:00+00:00","content":"\n<p>Hey, hey, it\u2019s the beginning of a new year and before we sprint too far into 2026, let\u2019s take a quick breather, zoom out, and celebrate what Igalia\u2019s awesome compilers team got up to in 2025.\nOver the past year we\u2019ve been deeply involved in shaping <em>and<\/em> shipping key Web and JavaScript standards, which includes not just participating in committees but also chairing and actively moving the proposals forward.\nWe worked on major JavaScript runtimes and foundational ahead-of-time compilers including LLVM and Mesa, as well as JIT CPU emulation, and smaller language VMs.<\/p>\n<p>Some big highlights of this year included our work on FEX and Mesa that helped Valve with their upcomimg gaming devices - the Steam Frame and the Steam Machine (we talk more about this in a dedicated <a href=\"https:\/\/www.igalia.com\/2025\/11\/helpingvalve.html\">blog post<\/a>), our continued involvement in supporting RISC-V in contemporary compilers, and our key role in multiple WebAssembly implementations.<\/p>\n<h2 id=\"standards\" tabindex=\"-1\">Standards <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>In 2025, our standards work focused on parts of JavaScript developers touch every day like time, numbers, modules and more. Across TC39, WHATWG, WinterTC and internationalization ecosystems, we helped move proposals forward while turning specifications into running, interoperable code. So yep, let\u2019s talk about our most significant standards contributions from the year!<\/p>\n<h3 id=\"temporal\" tabindex=\"-1\">Temporal <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>It\u2019s been an exciting year for the <a href=\"https:\/\/github.com\/tc39\/proposal-temporal\/\">Temporal proposal<\/a>, which adds a modern date-and-time API to JavaScript. For starters, MDN published their <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Temporal\">API documentation for it<\/a>, which created a huge surge of interest.<\/p>\n<p>On the shipping front: Firefox shipped their implementation of the proposal and it\u2019s now available in Firefox 139. Chrome moved their implementation to beta in late 2025, and released it in early 2026. Meanwhile, we\u2019ve been steadily working on getting Temporal into Safari, with support for correct duration math and the <code>PlainMonthDay<\/code> and <code>PlainYearMonth<\/code> types added during 2025\/early 2026. You can read more about this in our recent <a href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">post on implementing Temporal<\/a>.<\/p>\n<p>Alongside that, we\u2019ve been working on the <a href=\"https:\/\/github.com\/tc39\/proposal-intl-era-monthcode\/\">Intl Era and Month Code proposal<\/a>, which has expanded in scope beyond era codes and month codes to cover other calendar-specific things that a JS engine with <code>Intl<\/code> must implement. This allows developers to make use of a number of commonly-used non-Gregorian calendars, including but not limited to the calendar used in Thailand, the Japanese Imperial calendar, and Islamic calendars.<\/p>\n<h3 id=\"decimal\" tabindex=\"-1\">Decimal <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>A lot of our recent work around the <a href=\"https:\/\/github.com\/tc39\/proposal-decimal\">Decimal<\/a> proposal has now migrated to a newer similarly number-focused effort called <a href=\"https:\/\/github.com\/tc39\/proposal-amount\">Amount<\/a> (formerly known as &quot;Measure&quot; and officially renamed in 2025). The proposal reached Stage 1 at the November 2024 TC39 plenary. We also launched a <a href=\"https:\/\/www.npmjs.com\/package\/proposal-amount\">polyfill<\/a>.\nSince then, we have iterated on the Amount API and data model a number of times in plenary. So while it started 2025 at <a href=\"https:\/\/tc39.es\/process-document\/\">stage 1<\/a> and remains at stage 1 heading into 2026, the design is noticeably sharper, thanks to a lot of TC39 discussions. We\u2019re lined up to keep it pushing forward next year.<\/p>\n<p>And because numerics work benefits a ton from regular iteration, in late 2024, we also kicked off a biweekly community call (&quot;JS Numerics&quot;) for those in TC39 interested in proposals related to numbers, such as Decimal, Amount, <a href=\"https:\/\/github.com\/tc39\/proposal-intl-keep-trailing-zeros\">intl-keep-trailing-zeros<\/a>, etc. We still host it, and it\u2019s turned out to be a genuinely productive place to hash things out without waiting for plenary.<\/p>\n<h3 id=\"source-maps\" tabindex=\"-1\">Source Maps <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>We implemented draft range mappings implementations on a number of systems: WebKit, <a href=\"https:\/\/github.com\/jridgewell\/sourcemaps\/tree\/main\/packages\/sourcemap-codec\">Justin Ridgewell\u2019s source map decoder<\/a>, a source map validator, and more.<\/p>\n<p>We also facilitated source map TG4 meetings and assisted with advancing proposals such as the scopes proposal.\nThroughout the year, we continued serving as editors for the ECMA-426 specification, landing a steady stream of improvements and clarifications.<\/p>\n<h3 id=\"modules\" tabindex=\"-1\">Modules <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>We pushed JavaScript\u2019s module system forward on multiple fronts, especially around reducing the impact of modules on application startup:<\/p>\n<ul>\n<li>we advanced the <a href=\"https:\/\/github.com\/tc39\/proposal-defer-import-eval\"><code>import defer<\/code><\/a> proposal, which allows modules to be be synchronously lazily evaluated, to Stage 3 in TC39. We are working on its implementations in V8 and WebKit, and we implemented it in Babel, webpack (together with other community members) and TypeScript.<\/li>\n<li>we presented <a href=\"https:\/\/github.com\/tc39\/proposal-deferred-reexports\"><code>export defer<\/code><\/a> and pushed it to Stage 2 in TC39: it allows more granular lazy evaluation, as well as built-in browser support for tree-shaking of re-exports.<\/li>\n<\/ul>\n<p>We are among the most active members of the &quot;Modules Harmony&quot; group, an unofficial group within TC39 that aims at improving the capabilities of ESM to improve native adoption, while making sure that all modules proposals are well-coordinated with each other.<\/p>\n<h3 id=\"asynccontext\" tabindex=\"-1\">AsyncContext <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>And over in the <a href=\"https:\/\/github.com\/tc39\/proposal-async-context\">AsyncContext proposal<\/a> world, we spent 2025 focusing on how the proposal should integrate with various <a href=\"https:\/\/github.com\/tc39\/proposal-async-context\/blob\/master\/WEB-INTEGRATION.md\">web APIs<\/a>. The way AsyncContext interacts with the web platform is unusually pervasive, and more challenging to figure out than the core TC39 proposal itself.<\/p>\n<p>In a first for a TC39 proposal, it is not also going through the <a href=\"https:\/\/whatwg.org\/stages\">WHATWG stages process<\/a>, where it has reached Stage 1. This gives us a clearer path to iterate with direct feedback from browser engines.<\/p>\n<h3 id=\"unicode-standards\" tabindex=\"-1\">Unicode standards <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>We have been working on <a href=\"https:\/\/messageformat.unicode.org\/\">Unicode MessageFormat<\/a>, which is a Unicode standard for localizable dynamic message strings, designed to make it simple to create natural sounding localized messages.<\/p>\n<p>In 2025, we helped the <a href=\"https:\/\/icu.unicode.org\/\">ICU4C<\/a> implementation of Unicode MessageFormat align with ongoing specification changes. We also carried out <a href=\"https:\/\/github.com\/unicode-org\/icu\/pull\/3536\">experimental work<\/a> on the custom function interface to support more extensible formatting formatting capabilities, which is currently under review.<\/p>\n<h3 id=\"wintertc\" tabindex=\"-1\">WinterTC <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h3>\n<p>In December 2024, <a href=\"https:\/\/wintertc.org\/\">WinterTC<\/a> was formed to replace WinterCG as an official ECMA <a href=\"https:\/\/ecma-international.org\/technical-committees\/\">Techincal committee<\/a> to achieve some level of API interoperability across server-side JavaScript runtimes, especially for APIs that are common with the web.<\/p>\n<p>We started chairing (together folks from Deno), and became involved in admin tasks.\nOver the course of the year, we:<\/p>\n<ul>\n<li>Identified a core set of Web APIs that should be shared across runtimes and standardized it as the <a href=\"https:\/\/min-common-api.proposal.wintertc.org\/\">Minimum Common Web API specification<\/a>, which was officially published at the ECMA General Assembly in December.<\/li>\n<li>Started identifying a subset of the WPT test suite that covers the Minimum Common Web API, and made some headway towards clarifying which parts of the Fetch specification server-side runtimes should support, and which they shouldn\u2019t.<\/li>\n<\/ul>\n<p>Additionally, if you\u2019re curious, we gave two talks about WinterTC: <a href=\"https:\/\/youtu.be\/elGNcCv57ZE\">one at the Web Engines Hackfest together with Deno folks, the other chair of WinterTC<\/a>; and <a href=\"https:\/\/youtu.be\/T9g3DtdTsGU\">one at JSConf.JP<\/a>.<\/p>\n<h2 id=\"node-js\" tabindex=\"-1\">Node.js <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>In Node.js, our work in 2025 spanned interoperability, proxy integration, and adding support for HTTP\/HTTPS proxy and shipping integration of System CA certificates across platforms.<\/p>\n<p>On the module side, we delivered interoperability features and bug fixes for <code>require(esm)<\/code> and helped stabilize it (read more about it in our colleague <a href=\"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-from-experiment-to-stability\/\">Joyee\u2019s blog<\/a>), <a href=\"https:\/\/github.com\/nodejs\/node\/pull\/55698\">shipped synchronous and universal loader hooks<\/a> (now promoted to release candidate), integrated TypeScript into the <a href=\"https:\/\/github.com\/nodejs\/node\/issues\/52696\">compile cache<\/a>, and improved the portability of the cache. Check out <a href=\"https:\/\/www.youtube.com\/watch?v=MYVn6TuZCEQ\">Joyee\u2019s talk at JSConf JP<\/a> if you are interested in learning more about these new module loader features.<\/p>\n<p>We also strengthened <a href=\"https:\/\/github.com\/nodejs\/node\/issues\/58990\">System CA certificate integration<\/a> along with JavaScript APIs for reading and configuring trusted CAs globally, <a href=\"https:\/\/github.com\/nodejs\/node\/issues\/57872\">adding built-in HTTP\/HTTPS proxy support<\/a>, and expanding <a href=\"https:\/\/nodejs.org\/en\/learn\/http\/enterprise-network-configuration\">documentation for using Node.js in enterprise environments<\/a>.<\/p>\n<p>Additionally, we started migration to the new V8 CppHeap model in Node.js and improved its V8 Platform integration.<\/p>\n<h2 id=\"v8\" tabindex=\"-1\">V8 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>On the V8 side of things, we worked on <code>HeapProfiler::QueryHolders<\/code>, a companion API to the <a href=\"https:\/\/chromium-review.googlesource.com\/c\/v8\/v8\/+\/5006373\">QueryObjects API<\/a>.<\/p>\n<p>We worked on extending the <a href=\"https:\/\/v8.github.io\/api\/head\/classv8_1_1HeapStatistics.html\">HeapStatistics API<\/a> to include a new field that tracks the total of bytes allocated in an Isolate since its creation. This counter excludes allocations that happen due to GC operations and it\u2019s intended to be used to create memory regression tests. Here\u2019s the <a href=\"https:\/\/chromium-review.googlesource.com\/c\/v8\/v8\/+\/6996467\">CL<\/a> highlighting these changes.<\/p>\n<p>We also started working on implementation of the <a href=\"https:\/\/github.com\/tc39\/proposal-defer-import-eval\">import defer proposal<\/a> on V8. This proposal extends the syntax of ESM imports to allow a mode where the evaluation of an imported module is deferred until its first access.\nFrom our work in Node.js, we upstreamed a few improvements and bug fixes in V8\u2019s embedder API and startup snapshot implementation. We also contributed to Node.js\u2019s V8 upgrade and upstreamed patches to address issues discovered in the upgrade.<\/p>\n<p>As part of our collaboration with Cloudflare we added <code>v8::IsolateGroup<\/code>: a new unit that owns an independent pointer-compression cage. We then also enabled multiple cages per process (\u201cmulti-cage\u201d), so thousands of isolates aren\u2019t forced into one &lt; 4 GiB region. Finally, we extended this to multiple sandboxes: one sandbox per isolate group instead of a single process-wide sandbox. In the end this work helped Cloudflare to enable the sandbox in Cloudflare workers.<\/p>\n<h2 id=\"babel\" tabindex=\"-1\">Babel <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>Our team also helps co-maintianing <a href=\"https:\/\/babeljs.io\">Babel<\/a>. The build tools area is very active nowdays, and we strongly believe that alongside the innovation happening in the ecosystem companies need to invest on ensuring that the older and widely used tools keep being actively maintained and improving over time.<\/p>\n<h2 id=\"llvm\" tabindex=\"-1\">LLVM <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>In LLVM, we helped extend auto-vectorization to take full advantage of the RISC-V vector extension\u2019s many innovative features.<\/p>\n<p>After four years of development by contributors from multiple organizations including Igalia, we finally enabled <a href=\"https:\/\/github.com\/llvm\/llvm-project\/pull\/151681\">EVL tail folding for RISC-V<\/a> as an LLVM default.<\/p>\n<p>This work took advantage of the new VPlan infrastructure, extending it and developing it iteratively in-tree when needed to give us the ability to model a relatively complex vectorization scheme.<\/p>\n<p>We also added <a href=\"https:\/\/github.com\/llvm\/llvm-project\/pull\/141865\">full scalable segmented access support<\/a> and <a href=\"https:\/\/github.com\/llvm\/llvm-project\/pull\/158690\">taught the loop vectorizer to make smarter cost model decisions<\/a>.<\/p>\n<p>Building on top of this, we achieved <a href=\"https:\/\/blogs.igalia.com\/compilers\/2025\/05\/28\/improvements-to-risc-v-vector-code-generation-in-llvm\/\">improvements in RISC-V vectorization<\/a>. In parallel, we also worked on LLVM scheduling models for the SpacemiT-x60 RISC-V processor, scoring a whopping <a href=\"https:\/\/blogs.igalia.com\/compilers\/2025\/11\/22\/unlocking-15-more-performance-a-case-study-in-llvm-optimization-for-risc-v\/\">16% performance improvement<\/a>.<\/p>\n<p>Regarding WebAssembly in LLVM we landed a number of commits that improve size and performance of generated code, and added support for a few ISD nodes that enable vectorization for otherwise sequential codegen.<\/p>\n<h2 id=\"mesa-ir3\" tabindex=\"-1\">Mesa\/IR3 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>We continued work on improving IR3, the Mesa compiler backend for Qualcomm Adreno GPUs. We <a href=\"https:\/\/gitlab.freedesktop.org\/mesa\/mesa\/-\/merge_requests\/31222\">implemented support for alias instructions<\/a> novel to the <em>a7xx<\/em> generation of GPUs, significantly improving register pressure for texture instructions. We also <a href=\"https:\/\/gitlab.freedesktop.org\/mesa\/mesa\/-\/merge_requests\/34108\">refactored the post-RA scheduler<\/a> to be able to reuse the legalization logic, significantly improving its accuracy when calculating instruction delays and, consequently, reducing latency.<\/p>\n<p>We also <a href=\"https:\/\/gitlab.freedesktop.org\/mesa\/mesa\/-\/merge_requests\/33602\">added debug tooling<\/a> to easily identify the shader that causes problems, among many other <a href=\"https:\/\/gitlab.freedesktop.org\/mesa\/mesa\/-\/merge_requests\/?sort=merged_at_desc&state=merged&author_username=jnoorman&first_page_size=20\">optimizations, implementations of new instructions, and bug fixes<\/a>.<\/p>\n<h2 id=\"guile-and-whippet\" tabindex=\"-1\">Guile and Whippet <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>This year we also made some interesting progress on <a href=\"https:\/\/github.com\/wingo\/whippet\">Whippet<\/a>, a no-dependencies embeddable garbage collector.  We were able to integrate Whippet into the <a href=\"https:\/\/gnu.org\/s\/guile\">Guile<\/a> Scheme implementation, replacing Guile\u2019s use of the venerable Boehm-Demers-Weiser library.  We hope to merge the <a href=\"https:\/\/codeberg.org\/guile\/guile\/src\/branch\/wip-whippet\">integration branch<\/a> upstream over the next months.  We also wrote up a <a href=\"https:\/\/arxiv.org\/abs\/2503.16971\">paper describing the innards of some of Whippet\u2019s algorithms<\/a>.<\/p>\n<p>We think Whippet is interesting whereever a programming language needs a garbage collector: it\u2019s customizable and easy to manage, as it is designed to be &quot;vendored&quot; directly into a user\u2019s source code repository. We are now in the phase of building out examples to allow for proper performance evaluation; after a <a href=\"https:\/\/github.com\/wingo\/whiffle\">bespoke Scheme implementation<\/a> and Guile itself, we also wrote a <a href=\"https:\/\/codeberg.org\/wingo\/wastrel\">fresh ahead-of-time compiler for WebAssembly<\/a>, which in the near future will gain support for the garbage collection WebAssembly extensions, thanks to Whippet.  For more info on our progress, check out <a href=\"https:\/\/wingolog.org\/tags\/whippet\">Andy Wingo\u2019s blog series<\/a>.<\/p>\n<h2 id=\"fex\" tabindex=\"-1\">FEX <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/06\/igalia-s-compilers-team-a-2025-retrospective\/\">#<\/a><\/h2>\n<p>For the FEX x86 JIT emulator for ARM64, we worked on X87 Floating-Point Emulation, <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4642\">implemented x87 invalid operation bit handling in F80 mode<\/a>, <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4811\">fixed IEEE 754 unordered comparison detection<\/a>, and <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/5009\">added f80 stack xchg optimization for fast path<\/a>.<\/p>\n<p>Besides <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4846\">further<\/a> <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/5062\">fixes<\/a> for instruction implementations, we also worked on memory and stability improvements, <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4540\">protecting the last page of CodeBuffer<\/a>, and <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/5035\">implementing gradual memory growth<\/a>. Finally, we also did some infrastructure work by <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4661\">upgrading the codebase to clang-format-19<\/a> and <a href=\"https:\/\/github.com\/FEX-Emu\/FEX\/pull\/4488\">adding UBSAN support<\/a>.<\/p>\n<p>This year\u2019s FEX work focused on x87 floating-point correctness and 32-bit compatibility\u2014both critical for Valve\u2019s Steam Frame, the ARM-powered VR headset they announced in November that uses FEX to run x86 games.<\/p>\n<p>The x87 improvements matter because many games and middleware still use legacy floating-point code. Subtle deviations from Intel\u2019s behavior\u2014wrong exception flags, incorrect comparison semantics\u2014cause crashes or weird behavior. Fixing invalid operation exceptions, IEEE 754 comparisons, and optimizing the x87 stack pass eliminated entire classes of compatibility bugs.<\/p>\n<p>The 32-bit fixes are just as important. A huge chunk of Steam\u2019s catalog is still 32-bit, and even 64-bit games often ship 32-bit launchers. Getting <code>fcntl<\/code> and addressing modes right means these games just work without users needing to do anything.<\/p>\n<p>In total, this work gave Valve confidence that the Steam Frame could ship with solid library coverage, letting them announce the device on schedule.<\/p>\n<hr \/>\n<p>Alright, that\u2019s a wrap on our 2025 retrospective! We hope you had as much fun reading it as we had writing it, and building all the things we talked about along the way. We\u2019ll see you next year with another roundup; until then, you can keep up with our latest work on the <a href=\"https:\/\/blogs.igalia.com\/compilers\/\">team blog<\/a>.<\/p>                ","author":{"name":"Igalia Compilers Team","uri":"https:\/\/blogs.igalia.com\/compilers\/"}},{"title":"Manuel Rego: FOSDEM 2026","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/"}},"id":"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/","updated":"2026-02-05T00:00:00+00:00","content":"\n<p>Last weekend I was in Brussels attending <a href=\"https:\/\/fosdem.org\/2026\/\"><strong>FOSDEM<\/strong><\/a>. A big event with lots of people and lots of things happening in parallel, where it\u2019s impossible to be everywhere.<\/p>\n<h2 id=\"browser-and-web-platform\" tabindex=\"-1\">Browser and web platform \ud83c\udf10 <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h2>\n<p>My main participation was in the <a href=\"https:\/\/fosdem.org\/2026\/schedule\/track\/browser-and-web-platform\/\"><strong>Browser and web platform<\/strong><\/a> devroom, a <em>new<\/em> devroom (it seems it already happened back in the days, but not in the recent years) where I had the chance to speak about <a href=\"https:\/\/servo.org\/\">Servo<\/a> with a talk titled <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/LXFKS9-servo-project-impact\/\"><strong>The Servo project and its impact on the web platform ecosystem<\/strong><\/a>. My colleagues from <a href=\"https:\/\/www.igalia.com\/\">Igalia<\/a> <a href=\"https:\/\/www.igalia.com\/team\/eri\">Eri Pazos<\/a> and <a href=\"https:\/\/www.igalia.com\/team\/msanchez\">Mario S\u00e1nchez Prada<\/a> were also speaking in that devroom about <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/NJM3KB-mathml-core\/\"><strong>Interop and MathML Core<\/strong><\/a> and <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/8ZL9BZ-web-platform-on-linux-devices-with-webkit\/\"><strong>The Web Platform on Linux devices with WebKit: where are we now?<\/strong><\/a> respectively. The room was fully packed a big part of the day, not unexpectedly many people are interested in the web platform.<\/p>\n<h3 id=\"mathml\" tabindex=\"-1\">MathML <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h3>\n<p><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/NJM3KB-mathml-core\/\"><strong>Eri was the first igalian talking in the room<\/strong><\/a>, they summarized the work Igalia has been doing for many years in MathML, on the standards side proposing the <a href=\"https:\/\/w3c.github.io\/mathml-core\/\">MathML Core spec<\/a>, and on the implementation side <a href=\"https:\/\/www.igalia.com\/2023\/01\/10\/Igalia-Brings-MathML-Back-to-Chromium.html\">bringing it back to Chromium<\/a> and improving it in Gecko and WebKit.<\/p>\n<p>In the talk, Eri went into deep detail about the last additions we have been adding around MathML: <code>math-depth<\/code>, <code>math-shift<\/code>, RTL mirroring, <code>font-family: math<\/code>, etc. This work is part of an agreement with the <a href=\"https:\/\/www.sovereign.tech\/\">Sovereign Tech Fund<\/a>, big thanks for your support.<\/p>\n<p>MathML is more ready than ever for production, someone from <a href=\"https:\/\/arxiv.org\/\">arXiv.org<\/a> in the audience mentioned that they are shipping it on millions of webpages today. Waiting for the day when <a href=\"https:\/\/www.wikipedia.org\/\">Wikipedia<\/a> switches to it by default, it will be a huge milestone.<\/p>\n<h3 id=\"webkit-on-linux\" tabindex=\"-1\">WebKit on Linux <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h3>\n<p><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/8ZL9BZ-web-platform-on-linux-devices-with-webkit\/\"><strong>Mario talked about the Linux ports of WebKit: WebKitGTK and WPE<\/strong><\/a>, both maintained by Igalia.<\/p>\n<p>He explained what they are, the differences between them, reviewed their history and highlighted the big progress on the recent years, with multiple improvements in several areas: WebPlatform API, WebKit Container SDK, switch from Cairo to Skia graphics library, etc.<\/p>\n<p>If you are curious about the status of things regarding them, you shouldn\u2019t miss his talk.<\/p>\n<h3 id=\"servo\" tabindex=\"-1\">Servo <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h3>\n<p><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/LXFKS9-servo-project-impact\/\"><strong>My talk<\/strong><\/a> started with an introduction to the Servo project and the current status of things. I showed a few demos about how Servo works and some of the things it can do already. After that introduction, I explained how Servo has been contributing to the wider web platform ecosystem.<\/p>\n<p>Like for the rest of talks, <a href=\"https:\/\/servo.org\/slides\/2026-02-fosdem-servo-web-platform\/\">slides<\/a> and <a href=\"https:\/\/mirrors.dotsrc.org\/fosdem\/2026\/h1309\/LXFKS9-servo-project-impact.av1.webm\">video<\/a> are already available if you want to know all the details. Kudos to the organization for being so quick.<\/p>\n<figure>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/02\/servo-talk.jpg\" alt=\"Picture of my talk with the slides about conclusions at the back\" \/><\/p>\n  <figcaption>Picture of my talk with the slides about conclusions at the back<\/figcaption>\n<\/figure>\n<p>As an anecdote, the night before the talk a <a href=\"https:\/\/tangled.org\/me.webbeef.org\/browser.html\/\">new project based on Servo was published<\/a>, a browser developed fully with web technologies using Servo underneath. I couldn\u2019t resist the urge to build it, play with it and add it to the presentation. It looks really cool what Servo can do these days.<\/p>\n<figure>\n  <video src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/02\/servo-demo-fosdem-2026-beaver.webm\" controls=\"\" title=\"Screencast of the new Beaver browser based on Servo\">\n    Screencast of the new Beaver browser based on Servo\n  <\/video>\n  <figcaption>Screencast of the new <a href=\"https:\/\/tangled.org\/me.webbeef.org\/browser.html\/\">Beaver browser<\/a> based on Servo<\/figcaption>\n<\/figure>\n<p>In addition, in the same devroom there was another Servo talk, this time by <a href=\"https:\/\/github.com\/Taym95\">Taym<\/a>, one of the Servo maintainers, <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/3J8GUD-servo-streams-reimplementation\/\"><strong>Implementing Streams Spec in Servo web engine<\/strong><\/a>, where he explained all the work behind adding Streams support to Servo.<\/p>\n<p>I am also very happy to met many Servo contributors at the event:\n<a href=\"https:\/\/github.com\/delan\">Delan Azabani (@delan)<\/a>,\n<a href=\"https:\/\/github.com\/eerii\">Eri Pazos (@eerii)<\/a>,\n<a href=\"https:\/\/github.com\/jschwe\">Jonathan Schwender (@jschwe)<\/a>,\n<a href=\"https:\/\/github.com\/mrobinson\">Martin Robinson (@mrobinson)<\/a>,\n<a href=\"https:\/\/github.com\/Taym95\">Taym Haddadi (@Taym95)<\/a>,\n<a href=\"https:\/\/github.com\/TimvdLippe\">Tim van der Lippe (@TimvdLippe)<\/a>.\nWe had the chance to have some informal conversations about the project, discussing some technical topics and ideas about things we can do in Servo.<\/p>\n<p>The feedback about Servo has been extremely positive, people are really happy with the evolution of the project and excited about the future.<\/p>\n<p>Apart from that, we also had the opportunity to talk to the nice folks from <a href=\"https:\/\/nlnet.nl\/\">NLnet<\/a> and the <a href=\"https:\/\/www.sovereign.tech\/\">Sovereign Tech Agency<\/a> who both have ongoings collaborations around Servo. The work these organizations do is really important for the open software development, and more should learn from them and join forces to try to fix the funding issues in <acronym title=\"Free\/Libre and Open-Source Software\">FLOSS<\/acronym> (more about this later, when talking about <a href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">Marga\u2019s keynote<\/a>).<\/p>\n<h2 id=\"igalia\" tabindex=\"-1\">Igalia <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h2>\n<p><a href=\"https:\/\/www.igalia.com\/\"><strong>Igalia<\/strong><\/a> presence in the open source community is very big, and not unexpectedly we have more talks at FOSDEM this year. This is the full list:<\/p>\n<ul>\n<li><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/KMMLGM-webrtc_support_in_webkitgtk_and_wpewebkit_with_gstreamer_current_status_and_plan\/\">WebRTC support in WebKitGTK and WPEWebKit with GStreamer: Current status and plans<\/a> by <a href=\"https:\/\/igalia.com\/team\/pnormand\">Philippe Normand<\/a><\/li>\n<li><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/BMFQSE-raspberry-pi-gpu-drivers-from-bookworm-to-trixie\/\">From Bookworm to Trixie: Upgrading the Raspberry Pi graphics stack<\/a> by <a href=\"https:\/\/igalia.com\/team\/chema\">Jos\u00e9 Mar\u00eda Casanova Crespo<\/a><\/li>\n<li><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/HT9HAG-wastrel-webassembly-without-the-runtime\/\">Wastrel: WebAssembly Without the Runtime<\/a> by <a href=\"https:\/\/igalia.com\/team\/awingo\">Andy Wingo<\/a><\/li>\n<li><a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/HX9XAY-mesa3d_the_heart_of_the_linux_graphics_stack\/\">Mesa3D: the heart of the linux graphics stack<\/a> by <a href=\"https:\/\/igalia.com\/team\/jasuarez\">Juan A. Suarez<\/a><\/li>\n<\/ul>\n<p>Also our work in different projects was mentioned in several talks and conversations, we\u2019re really happy regarding all the good feedback we got about Igalia contributions.<\/p>\n<h3 id=\"keynote\" tabindex=\"-1\">Keynote <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h3>\n<p><a href=\"https:\/\/www.igalia.com\/team\/marga\">Marga Manterola<\/a> was doing one of the keynotes talking about funding open source software: <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/L3BK7S-free-as-in-burned-out\/\"><strong>Free as in Burned Out: Who Really Pays for Open Source?<\/strong>\n<\/a>. First time Igalia was doing a keynote at FOSDEM, in the year we celebrate our 25th anniversary, and in <a href=\"https:\/\/bib.ulb.be\/fr\/documents\/digitheque\/institutionalia\/histoire-de-lulb\/historique\/histoire-de-lulb-la-crise-de-68\">a historical auditorium where May 68 started in Brussels<\/a>. \ud83c\udf89<\/p>\n<figure>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/02\/marga-talk.jpg\" alt=\"Picture of Marga Manterola's keynote at FOSDEM 2026\" \/><\/p>\n  <figcaption>Picture of Marga Manterola's keynote at FOSDEM 2026<\/figcaption>\n<\/figure>\n<p>The talk was great, Marga explained many of the issues with open source software sustainability and some potential ideas about how to improve the situation. This is a recurring topic in many conversations these days, we should find a way to get this fixed somehow.<\/p>\n<p>There I learnt about the <a href=\"https:\/\/opensourcepledge.com\/\">Open Source Pledge<\/a>, an interesting initiative to get companies donating 2,000 USD per developer to open source software maintainers. \ud83d\udcb0<\/p>\n<h2 id=\"wrap-up\" tabindex=\"-1\">Wrap up <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/fosdem-2026\/\">#<\/a><\/h2>\n<p>All in all, it was a nice but very busy weekend in Brussels, weather was ok (a bit cold but not rainy) and waffles were delicious as usual. \ud83e\uddc7\ud83d\ude0b<\/p>\n<p>Next big event on my calendar is the <a href=\"https:\/\/webengineshackfest.org\/\"><strong>Web Engines Hackfest<\/strong><\/a> in June, more than 50 people have already registered and a bunch of Servo folks will be there too. If you\u2019re interested in the web platform and willing to discuss about different topics, we would be very happy to host you there.<\/p>                ","author":{"name":"Manuel Rego","uri":"https:\/\/blogs.igalia.com\/mrego\/"}},{"title":"Brian Kardell: What if we just","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/bkardell.com\/blog\/WhatIfYouJustTellMe.html"}},"id":"https:\/\/bkardell.com\/blog\/WhatIfYouJustTellMe.html","updated":"2026-02-04T05:00:00+00:00","content":"\n<h1 class=\"contextual-heading\">What if we just<\/h1>\n<p class=\"segue\">What if a better answer to a question I've been struggling with for more than a decade is just... Way simpler?  Sharing a potentially half-baked idea for discussion.<\/p>\n\n<p>Back in 2013 I wrote <a href=\"https:\/\/bkardell.com\/blog\/Dropping-The-F-Bomb-On-Standards.html\">Dropping the F-Bomb on Web Standards<\/a>.  The core argument was simple: the web works best when developers can invent \u201cslang,\u201d and standards bodies behave more like dictionary editors \u2014 watching what people actually say, then paving the cow paths that clearly matter.<\/p>\n<p>It fed into the <a href=\"https:\/\/extensiblewebmanifesto.org\/\">Extensible Web Manifesto<\/a> (which followed)  and over the years I've continued to push for study of what people are really doing.  I have helped add features to the  HTTPArchive crawl and built tools to analyze this data.  <\/p>\n<p>But it's hard. It's <a href=\"https:\/\/bkardell.com\/blog\/SecretLifeOfCustomElements.html#:~:text=What's%20difficult%20about%20it...\">biased<\/a>. It's incomplete.  Even the best crawl misses huge swaths of the web \u2014 anything behind logins, paywalls, dashboards, internal tools, or private deployments.  And all of them have limits.  It requires a ton of follow-up analysis and raises almost as many questions as it answers.<\/p>\n<p>So lately I've been wondering (a bit like <a href=\"https:\/\/www.youtube.com\/watch?v=L2DqcXeGTyc\">Kramer<\/a>):<\/p>\n<blockquote>\n<p>What if we just... voluntarily shared this information?<\/p>\n<\/blockquote>\n<p>We don't need a formal standard or anyone's permission, we could just... share it, and build tools to share it easily in a well known format at a well known URL.<\/p>\n<p>It could give us insight into the use of custom elements behind logins and paywalls and so on too, and tell us where they come from (a git repo, for example)... <\/p>\n<p>Lots of things that are common happened through community effort and adoption. Normally you <em>get<\/em> something from it - <code>robots.txt<\/code> helped your site from being aggressively scraped in problematic ways, <code>ads.txt<\/code> helped say something about monetization, <code>feed.rss<\/code> helped syndicate, and so on.  What do you get out of sharing this kind of info? <\/p>\n<p>Individually, I'm not sure.  But, collectively the benefit is clear:  We'd finally have a real, ecosystem\u2011wide index of custom elements and how they're used. and hopefully a way to shape useful standards on them easily.<\/p>\n<p>As to what that would look like, I'm not sure.<\/p> \n<p>The community defined <a href=\"https:\/\/github.com\/webcomponents\/custom-elements-manifest\/\">Custom Element Manifest<\/a> already has a bit of uptake and tooling - we <em>could<\/em> just publish that to a well known URL.  It might be too much, or too little.. A simpler manifest of just element names and URLs of packages\/repositories that supply them would even be nice.<\/p>\n<p>Is it too much? Too little? Maybe.<\/p>\n<p>Is it worth trying?  I think so.<\/p>\n<p>What do you think? Is this worth trying somehow?<\/p>                ","author":{"name":"Brian Kardell","uri":"http:\/\/bkardell.com\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #55","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-55\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-55\/","updated":"2026-02-02T20:11:18+00:00","content":"\n<p>Update on what happened in WebKit in the week from January 26 to February 2.<\/p>\n<p>\nA calm week for sure! The highlight this week is the fix for scrolling not starting when the main thread is blocked.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306396@main\">Fixed<\/a> the problem of wheel event async scrolling doesn't start while the main thread is blocked. This should make WebKit feel more responsive even on heavier websites.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Brian Kardell: Maintaining the Bridges","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/bkardell.com\/blog\/Bridges.html"}},"id":"https:\/\/bkardell.com\/blog\/Bridges.html","updated":"2026-02-02T05:00:00+00:00","content":"\n<h1 class=\"contextual-heading\">Maintaining the Bridges<\/h1>\n<p class=\"segue\">Thoughts and analogies about infrastructure...<\/p>\n\n<p>I live in Pittsburgh, Pennsylvania \u2014 \u201cThe Steel City,\u201d once the beating heart of American steelmaking. In 1902, U.S. Steel\u2019s first full year of operation, it produced 67% of all steel in the United States. By 1943, the company employed more than 340,000 people. We burned so much coal that Pittsburgh earned the nickname \u201cHell with the lid off.\u201d Streetlights sometimes ran at noon because the sky was that dark. <\/p>\n\n<figure class=\"captioned-image\">\n    <img src=\"https:\/\/bkardell.com\/media\/2026\/pittsburgh-midday.jpg\" \/>\n    <figcaption>A photo of Pittsburgh dark with smoke at midday. (You can search more about this it if that interests you, here's <a href=\"https:\/\/www.treehugger.com\/think-air-quality-doesnt-matter-look-at-pittsburgh-in-the-s-4862509\">one nice piece with a few pictures<\/a>).<\/figcaption>\n<\/figure>\n\n\n<p>The city\u2019s geography didn\u2019t make things any easier. Pittsburgh is carved by mountains, valleys, and the three rivers \u2014 the Allegheny and Monongahela merging to form the Ohio. That topography, combined with the industrial boom, meant we built <em>a lot of bridges<\/em>.  It helps that when your city is literally manufacturing the materials, you get a hometown discount. <\/p>\n<figure class=\"captioned-image\">\n    <img src=\"https:\/\/bkardell.com\/media\/2026\/3-sisters.jpg\" alt=\"\" \/><p><\/p>\n    <figcaption>A view down river of Pittsburgh's <a href=\"https:\/\/en.wikipedia.org\/wiki\/Three_Sisters_(Pittsburgh)\">3 sisters\" bridges<\/a> and several others.<\/figcaption>\n<\/figure>\n<p>One of them, the Hot Metal Bridge \u2014 just a mile or two from my house \u2014 once carried ladle cars full of molten iron between the blast furnaces and mills of J&amp;L Steel. During World War II, 15% of America\u2019s steelmaking capacity crossed that bridge, up to 180 tons per hour.<\/p>\n<p>These bridges were originally built by private companies with a clear profit motive: to move coal, ore, steel, or workers. Others were toll bridges, run by private companies the way you\u2019d run a turnpike or ferry.<\/p>\n<p>But more bridges meant more industry, which meant more people, which meant more bridges. You can see where this goes.<\/p>\n<p>Even by the late 1800s we were beginning to publicly fund them.  By the 1920s\u20131930s Allegheny County\u2019s bridge program bought out many of the private bridges and replaced many of them.  By the time the New Deal and Interstate era arrived, the private\u2011toll era was basically over - and since then over 90% of Pittsburgh's public bridges were funded by federal programs (we still have some private industry use bridges).<\/p>\n\n<h2 class=\"contextual-heading\">So what does any of this have to do with software?<\/h2>\n<p>Aside from giving me an excuse to talk about my city (which I enjoy), Pittsburgh\u2019s bridges are a useful metaphor for the infrastructure we rely on in tech, in two important ways:<\/p>\n<ol>\n<li><p><em>Becoming<\/em> a public good\nPrivate investment built early bridges, just like private companies built much of what we've got now in terms of browser engines, search index, foundational libraries and so on - but eventually they stopped becoming optional.  I think we're only now starting to really understand that we need a lot of this to be a public good in the same kind of way.   These are the roads and bridges of the modern world.<\/p>\n<\/li>\n<li><p>Building something new is exciting. Maintaining it, not so much.\nA lot of my city's physical infrastructure is aging and some of it has been neglected.  It's somehow way easier to get people to build new things than to take care of the old stuff.  The public notices a new bridge!  The ribbon-cutting gets a photo op and celebration.  The maintenance budgets and crews struggle to even get funding.<\/p>\n<\/li>\n<\/ol>\n<p>In fact, even when things are fairly well funded, it doesn't mean they're kept up to date.  While researching to write this piece I realized that a lot of the Wikipedia data about Pittsburgh (and many topics!) is actually <em>really<\/em> out of date.  It's cool to write the article with these cool facts, but it's not so cool to do the work to keep it up... Or, maybe thats just not what you want to do anymore.  Or maybe you were incarcerated, or you died, or you went to Mars - idk. <\/p>\n<p>The point is that writing the thing in the first place is only half the battle. If most of your entry on a city was written two decades ago, a lot of what it details about the economics, population, jobs, and so on are probably not very accurate!<\/p>\n<p>It's no different with software.  It's cool and fun to build a new thing or add a new feature to an existing thing, but keeping them maintained is annoying.  New mechanisms arrive that you might need to adapt to.  Underlying code bit rots.  All of it needs release teams and Q&amp;A and reviews and fixes and updates at global scales, even if no new features were added.  But very few people actually want to do that, and almost nobody wants to pay for it.<\/p>\n\n\n<h2 class=\"contextual-heading\">More Public Funding<\/h2>\n<p>I'd really love for societies around the world to come to the realization that a lot of the online things we've built are, like roads and bridges, now <em>necessary<\/em> - and figure out how we can publicly fund enough of them that important things without an obvious and direct profit motive can get done. MathML and SVG are two easy examples of this, but there are plenty more. Maybe XSLT is another example.  Perhaps if we had good funding for those things, their ongoing survival wouldn't be questioned.<\/p>\n<p>I feel like there is a lot of room here for improvement from the status quo.  It doesn't even have to start with governments. Any ways that we expand the pool of funding avilable and diversifying, it helps.<\/p>                ","author":{"name":"Brian Kardell","uri":"http:\/\/bkardell.com\/"}},{"title":"Igalia Compilers Team: Implementing the Temporal proposal in JavaScriptCore","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/"}},"id":"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/","updated":"2026-02-02T00:00:00+00:00","content":"\n<p><em><a href=\"https:\/\/www.publicdomainpictures.net\/en\/view-image.php?image=10690&picture=prague-astronomical-clock-detail\">Image source<\/a><\/em><\/p>\n<p>For the past year, I've been working on implementing the <a href=\"https:\/\/tc39.es\/proposal-temporal\/docs\/\">Temporal<\/a> proposal for date and time handling in JavaScript, in JavaScriptCore (JSC). JavaScriptCore is the JavaScript engine that's part of the WebKit browser engine. When I started, Temporal was partially implemented, with support for the <code>Duration<\/code>, <code>PlainDate<\/code>, <code>PlainDateTime<\/code>, and <code>Instant<\/code> types. However, many <a href=\"https:\/\/github.com\/tc39\/test262\">test262<\/a> tests related to Temporal didn't pass, and there was no support for <code>PlainMonthDay<\/code>, <code>PlainYearMonth<\/code>, or <code>ZonedDateTime<\/code> objects. Further, there was no support for the <code>relativeTo<\/code> parameter, and only the &quot;iso8601&quot; calendar was supported.<\/p>\n<h2 id=\"duration-precision-landed\" tabindex=\"-1\">Duration precision (landed) <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>Conceptually, a duration is a 10-tuple of time components, or a record with the fields &quot;years&quot;, &quot;months&quot;, &quot;weeks&quot;, &quot;days&quot;, &quot;hours&quot;, &quot;seconds&quot;, &quot;milliseconds&quot;, &quot;microseconds&quot;, and &quot;nanoseconds&quot;.<\/p>\n<p>One way durations are used is to represent the difference between two dates. For example, to find the length of time from a given date until the end of 2027, I could write the following JS code:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token operator\">><\/span> <span class=\"token keyword\">const<\/span> duration <span class=\"token operator\">=<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token keyword\">new<\/span> <span class=\"token class-name\">Temporal<span class=\"token punctuation\">.<\/span>PlainDate<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">2026<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">26<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">until<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">new<\/span> <span class=\"token class-name\">Temporal<span class=\"token punctuation\">.<\/span>PlainDate<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">2027<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">12<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">31<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">largestUnit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"years\"<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token operator\">><\/span> duration<br \/>Temporal<span class=\"token punctuation\">.<\/span>Duration <span class=\"token operator\">&lt;<\/span><span class=\"token constant\">P1Y11M5D<\/span><span class=\"token operator\">><\/span> <span class=\"token punctuation\">{<\/span><br \/>  <span class=\"token literal-property property\">years<\/span><span class=\"token operator\">:<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span><br \/>  <span class=\"token literal-property property\">months<\/span><span class=\"token operator\">:<\/span> <span class=\"token number\">11<\/span><span class=\"token punctuation\">,<\/span><br \/>  <span class=\"token literal-property property\">days<\/span><span class=\"token operator\">:<\/span> <span class=\"token number\">5<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>The <code>until<\/code> method in this case returns a duration comprising one year, eleven months, and five days. Because durations can represent differences between dates, they can also be negative:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token operator\">><\/span> <span class=\"token keyword\">const<\/span> duration <span class=\"token operator\">=<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token keyword\">new<\/span> <span class=\"token class-name\">Temporal<span class=\"token punctuation\">.<\/span>PlainDate<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">2027<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">12<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">31<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">until<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">new<\/span> <span class=\"token class-name\">Temporal<span class=\"token punctuation\">.<\/span>PlainDate<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">2026<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">26<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">largestUnit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"years\"<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token operator\">><\/span> duration<br \/>Temporal<span class=\"token punctuation\">.<\/span>Duration <span class=\"token operator\">&lt;<\/span><span class=\"token constant\">P1Y11M5D<\/span><span class=\"token operator\">><\/span> <span class=\"token punctuation\">{<\/span><br \/>  <span class=\"token literal-property property\">years<\/span><span class=\"token operator\">:<\/span> <span class=\"token operator\">-<\/span><span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span><br \/>  <span class=\"token literal-property property\">months<\/span><span class=\"token operator\">:<\/span> <span class=\"token operator\">-<\/span><span class=\"token number\">11<\/span><span class=\"token punctuation\">,<\/span><br \/>  <span class=\"token literal-property property\">days<\/span><span class=\"token operator\">:<\/span> <span class=\"token operator\">-<\/span><span class=\"token number\">5<\/span><br \/><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>When converted to nanoseconds, the total of days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds for a duration may be a number whose absolute value is as large as 10<sup>9<\/sup> \u00d7 2<sup>53<\/sup>. This number is too large to represent either as a 32-bit integer or as a 64-bit double-precision value. (If you're wondering about the significance of the number 2<sup>53<\/sup>, see the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Number\/MAX_SAFE_INTEGER\">MDN documentation on JavaScript's <code>MAX_SAFE_INTEGER<\/code><\/a>.)<\/p>\n<p>To understand why we need to be able to work with such large numbers, consider totaling the number of nanoseconds in a duration.  Following on the previous example\u2019s definition of the variable <code>duration<\/code>:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token operator\">><\/span> duration<span class=\"token punctuation\">.<\/span><span class=\"token function\">total<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span><span class=\"token literal-property property\">unit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"nanoseconds\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">relativeTo<\/span><span class=\"token operator\">:<\/span> <span class=\"token keyword\">new<\/span> <span class=\"token class-name\">Temporal<span class=\"token punctuation\">.<\/span>PlainDate<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">2025<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">12<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">15<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token number\">60912000000000000<\/span><\/code><\/pre>\n<p>There are 60912000000000000 nanoseconds, or about 6.1e16, in a period of one year, eleven months, and five days. Since we want to allow this computation to be done with any valid start and end date, and valid years in Temporal range from -271821 to 275760, the result can get quite large. (By default, Temporal follows the <a href=\"https:\/\/en.wikipedia.org\/wiki\/ISO_8601\">ISO 8601 standard<\/a> for calendars, which entails using a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Proleptic_Gregorian_calendar\">proleptic Gregorian calendar<\/a>. Also note that this example uses a <code>PlainDate<\/code>, which has no time zone, so computations are not affected by daylight savings time; when computing with the Temporal <code>ZonedDateTime<\/code> type, the specification ensures that time zone math is done properly.)<\/p>\n<p>To make it easier for implementations to fulfill these requirements, the specification represents durations internally as <a href=\"https:\/\/tc39.es\/proposal-temporal\/#sec-temporal-internal-duration-records\">Internal Duration Records<\/a> and converts between JavaScript-level duration objects and Internal Duration Records (which I'll call &quot;internal durations&quot;) as needed. An internal duration pairs the date component of the duration (the years, months, weeks, and days fields) with a &quot;time duration&quot;, which is a single integer that falls within an accepted range, and can be as large as 2<sup>53<\/sup> \u00d7 10<sup>9<\/sup> - 1.<\/p>\n<p>Implementations don't <em>have<\/em> to use this representation, as long as the results are observably the same as what the specification dictates. However, the pre-existing implementation didn't suffice, so I re-implemented durations in a way that closely follows the approach in the specification.<\/p>\n<p>This work has been landed in JSC.<\/p>\n<h2 id=\"new-date-types\" tabindex=\"-1\">New date types <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>Temporal's date types include <code>PlainDate<\/code>, <code>PlainDateTime<\/code>, <code>Instant<\/code>, <code>ZonedDateTime<\/code>, <code>PlainMonthDay<\/code>, and <code>PlainYearMonth<\/code>. The latter two represent partial dates: either a pair of a month and a day within that month, or a pair of a year and month within that year. Partial dates are a better solution for representing dates where not all of the fields are known (or not all of the fields matter) than full dates with default values for the missing bits.<\/p>\n<p>Temporal's <code>ZonedDateTime<\/code> type represents a date along with a time zone, which can either be a numeric offset from UTC, or a named time zone.<\/p>\n<p>I implemented <code>PlainMonthDay<\/code> and <code>PlainYearMonth<\/code> with all their operations. <code>ZonedDateTime<\/code> is fully implemented and the first pull request in a series of PRs for it has been submitted.<\/p>\n<h2 id=\"the-relativeto-parameter\" tabindex=\"-1\">The relativeTo parameter <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>What if you want to convert a number of years to a number of days? Temporal can do that, but there's a catch. Converting years to days depends on what year it is, when using the ISO 8601 calendar (similar to the Gregorian calendar), because the calendar has leap years. Some calendars have leap months as well, so converting years to months would depend on what year it is as well. Likewise, converting months to days doesn't have a consistent answer, because months vary in length.<\/p>\n<p>For that reason, the following code will throw an exception, because there's not enough information to compute the result:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token operator\">><\/span> <span class=\"token keyword\">const<\/span> duration <span class=\"token operator\">=<\/span> Temporal<span class=\"token punctuation\">.<\/span>Duration<span class=\"token punctuation\">.<\/span><span class=\"token function\">from<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">years<\/span><span class=\"token operator\">:<\/span> <span class=\"token number\">1<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token operator\">><\/span> duration<span class=\"token punctuation\">.<\/span><span class=\"token function\">total<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">unit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"days\"<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/>Uncaught RangeError<span class=\"token operator\">:<\/span> a starting point is required <span class=\"token keyword\">for<\/span> years total<\/code><\/pre>\n<p>The above definition of <code>duration<\/code> can still be made to work if we pass in a starting point, which we can do using the <code>relativeTo<\/code> parameter:<\/p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token operator\">><\/span> duration<span class=\"token punctuation\">.<\/span><span class=\"token function\">total<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">unit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"days\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">relativeTo<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"2025-01-01\"<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token number\">365<\/span><br \/><span class=\"token operator\">><\/span> duration<span class=\"token punctuation\">.<\/span><span class=\"token function\">total<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">unit<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"days\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">relativeTo<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"2024-01-01\"<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><br \/><span class=\"token number\">366<\/span><\/code><\/pre>\n<p>The string passed in for the <code>relativeTo<\/code> parameter is automatically converted to either a <code>PlainDate<\/code> or a <code>ZonedDateTime<\/code>, depending on which format it conforms to.<\/p>\n<p>I implemented support for the <code>relativeTo<\/code> parameter on all the operations that have it; once the implementations for all the date types land, I'll be submitting this work as a series of pull requests.<\/p>\n<h2 id=\"calendars\" tabindex=\"-1\">Calendars <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>Representing dates with non-ISO8601 calendars is still very much a work in progress. The <a href=\"https:\/\/icu.unicode.org\/\">ICU library<\/a> can already do the basic date computations, but much glue code is necessary to internally represent dates with non-ISO8601 calendars and call the correct ICU functions to do the computations. This work is still underway. The Temporal specification does not require support for non-ISO8601 calendars, but a separate proposal, <a href=\"https:\/\/tc39.es\/proposal-intl-era-monthcode\/\">Intl Era Month Code<\/a>, proposes a set of calendars to be supported by conformant implementations.<\/p>\n<h2 id=\"testing-temporal\" tabindex=\"-1\">Testing Temporal <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>The JavaScript test suite is called <a href=\"https:\/\/github.com\/tc39\/test262\">test262<\/a> and every new proposal in JavaScript must be accompanied by test262 tests. Not all JS implementations are required to support internationalization, so Temporal tests that involve non-ISO calendars or named time zones (other than the UTC time zone) are organized in a separate <code>intl402<\/code> subdirectory in test262.<\/p>\n<p>The test262 suite includes 6,764 tests for Temporal, with 1,791 of these tests added in 2025. Igalia invested hundreds of hours on increasing test coverage over the past year.<\/p>\n<h2 id=\"status-of-work\" tabindex=\"-1\">Status of work <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/02\/02\/implementing-the-temporal-proposal-in-javascriptcore\/\">#<\/a><\/h2>\n<p>All of this work is behind a flag in JSC in Technology Preview, so to try it out, you'll have to pass the <code>--useTemporal=1<\/code> flag.<\/p>\n<p>All of the implementation work discussed above (except for non-ISO calendars) is complete, but I've been following an incremental approach to submitting the code for review by the JSC code owners. I've already landed about 40 pull requests over the course of 2025, and expect to be submitting at least 25 more to complete the work on <code>PlainYearMonth<\/code>, <code>ZonedDateTime<\/code>, and <code>relativeTo<\/code>.<\/p>\n<p>Based on all the code that I've implemented, 100% of the non-intl402 test262 tests for Temporal pass, while the current HEAD version of JSC passes less than half the tests.<\/p>\n<p>My colleagues at Igalia and I look forward to a future JavaScript standard that fully integrates Temporal, enabling JavaScript programs to handle dates more robustly and efficiently. Consistent implementation of the proposal across browsers is a key step towards this future. Step by step, we're getting closer to this goal.<\/p>\n<p>We thank <a href=\"https:\/\/www.bloomberg.com\/\">Bloomberg<\/a> for sponsoring this work.<\/p>                ","author":{"name":"Igalia Compilers Team","uri":"https:\/\/blogs.igalia.com\/compilers\/"}},{"title":"Manuel Rego","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/mrego\/blog\/2026-01-30\/"}},"id":"https:\/\/blogs.igalia.com\/mrego\/blog\/2026-01-30\/","updated":"2026-01-30T00:00:00+00:00","content":"\n<p>On my way to <a href=\"https:\/\/fosdem.org\/2026\/\">FOSDEM<\/a> where tomorrow I\u2019ll be <a href=\"https:\/\/fosdem.org\/2026\/schedule\/event\/LXFKS9-servo-project-impact\/\">talking about Servo<\/a> in the <em>Browser and web platform<\/em> devroom. See you there!<\/p>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/01\/fosdem-2026-servo-talk-banner.png\" alt=\"Banner of my talk at FOSDEM that reads: Igalia @ FOSDEM'26. The Servo project and its impact on the web platform ecosystem. Manuel Rego. Saturday, Jan. 31, 2:00pm\" \/><\/p>                ","author":{"name":"Manuel Rego","uri":"https:\/\/blogs.igalia.com\/mrego\/"}},{"title":"Alice Boxhall: Reference Target: having your encapsulation and eating it too","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/"}},"id":"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/","updated":"2026-01-30T00:00:00+00:00","content":"\n<p>Three years ago, I wrote a blog post about <a href=\"https:\/\/blogs.igalia.com\/alice\/how-shadow-dom-and-accessibility-are-in-conflict\/\">How Shadow DOM and accessibility are in conflict<\/a>.<\/p>\n<p>I explained how the encapsulation provided by shadow roots is a double-edged sword, particularly when it comes to accessibility. Being able to programmatically express relationships from one element to another is critical for creating user experiences which don\u2019t rely on visual cues - but elements inside a shadow root aren\u2019t available to be referenced from elements in the light DOM. This encapsulation, however, is what allows component authors to create accessible components which can be safely reused in any context, without necessarily requiring any particular dependencies or extra build steps.<\/p>\n<p>In the year or so following, even more heroic attempts were made to square this circle, and finally one seems likely to stick: <a href=\"https:\/\/github.com\/WICG\/webcomponents\/blob\/gh-pages\/proposals\/reference-target-explainer.md\">Reference Target<\/a>. In this post I\u2019ll explain how this feature works, why I like it, and what the situation is right now with the spec and implementation (thanks in part to <a href=\"https:\/\/blogs.igalia.com\/mrego\/solving-cross-root-aria-issues-in-shadow-dom\/\">Igalia\u2019s NLNet funding<\/a>).<\/p>\n<h2 id=\"a-quick-introduction\" tabindex=\"-1\">A quick introduction <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h2>\n<p><code>referenceTarget<\/code> is a new property on shadow root objects which lets you nominate an element in the shadow root\u2019s subtree which should be the <strong>target<\/strong> of any attribute-based <strong>reference<\/strong> to the shadow host.<\/p>\n<p>As an example, imagine that you have a <code>&lt;custom-input&gt;<\/code> component, which has an <code>&lt;input&gt;<\/code> tucked away in its shadow root.\nThis is a pattern which is ubiqutous in custom element libraries, as it allows the custom element to use <a href=\"https:\/\/en.wikipedia.org\/wiki\/Object_composition\">composition<\/a> to enhance the behaviour of a built-in element.<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>label<\/span> <span class=\"token attr-name\">for<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>track<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span>Track name:<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>label<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-input<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>track<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  #shadowRoot<br \/>  | <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>input<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>inner-input<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-input<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<p>We can set the shadow root\u2019s <code>referenceTarget<\/code> to allow the <code>&lt;label&gt;<\/code> to correctly label the inner <code>&lt;input&gt;<\/code>:<\/p>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\"><span class=\"token comment\">\/\/ in the constructor for the custom-input:<\/span><br \/><br \/><span class=\"token keyword\">const<\/span> shadowRoot <span class=\"token operator\">=<\/span> <span class=\"token keyword\">this<\/span><span class=\"token punctuation\">.<\/span><span class=\"token function\">attachShadow<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span><span class=\"token literal-property property\">mode<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'open'<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>shadowRoot<span class=\"token punctuation\">.<\/span>referenceTarget <span class=\"token operator\">=<\/span> <span class=\"token string\">'inner-input'<\/span><span class=\"token punctuation\">;<\/span><br \/>shadowRoot<span class=\"token punctuation\">.<\/span>innerHTML <span class=\"token operator\">=<\/span> '<span class=\"token operator\">&lt;<\/span>input id<span class=\"token operator\">=<\/span><span class=\"token string\">\"inner-input\"<\/span><span class=\"token operator\">><\/span>`<span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>This lets the label refer to the <code>&lt;custom-input&gt;<\/code> just like it would refer to an <code>&lt;input&gt;<\/code>; the <code>&lt;custom-input&gt;<\/code> transparently proxies the reference through to the encapsulated <code>&lt;input&gt;<\/code>.<\/p>\n<!--\nbk: would it be more accurate to say \"This makes the `<custom-input>` act as a transparent proxy for its internal input for the purposes of the `<label>`\"?  It's a little easier for me to grok because the above easily misreads to me as \"behave just like\", but not necessarily \"behave as if it were\" - if that makes any sense\n\nab: is this better?\n-->\n<p>In this example, we\u2019ve set the <code>referenceTarget<\/code> property directly on the <code>ShadowRoot<\/code> object, but it can also be set declaratively when using the <code>&lt;template&gt;<\/code> element to create the shadow root:<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>label<\/span> <span class=\"token attr-name\">for<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>track<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span>Track name:<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>label<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-input<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>track<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>template<\/span> <span class=\"token attr-name\">shadowRootMode<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>open<span class=\"token punctuation\">\"<\/span><\/span><br \/>            <span class=\"token attr-name\">shadowRootReferenceTarget<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>inner-input<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>input<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>inner-input<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>template<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-input<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<p>This works equally well for <em>any<\/em> attribute which refers to other elements like this - even if you set it via a reflected property like <code>commandForElement<\/code>:<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>button<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>settings-trigger<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span>Site settings<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>button<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-dialog<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>settings-dialog<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  #shadowRoot referenceTarget=\"inner-dialog\"<br \/>  | <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>dialog<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>inner-dialog<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  |   <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>button<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>close<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">aria-label<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>close<span class=\"token punctuation\">\"<\/span><\/span><br \/>  <span class=\"token attr-name\">|<\/span>           <span class=\"token attr-name\">commandFor<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>inner-dialog<span class=\"token punctuation\">\"<\/span><\/span><br \/>  <span class=\"token attr-name\">|<\/span>           <span class=\"token attr-name\">command<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>request-close<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>button<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  |   <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>slot<\/span><span class=\"token punctuation\">><\/span><\/span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>slot<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  | <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>dialog<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>fieldset<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>legend<\/span><span class=\"token punctuation\">><\/span><\/span>Colour scheme:<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>legend<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>label<\/span> <span class=\"token attr-name\">for<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>dark<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>input<\/span> <span class=\"token attr-name\">type<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>radio<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>dark<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">name<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>appearance<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">value<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>dark<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">checked<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>      Dark<br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>label<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token comment\">&lt;!-- TODO: more colour schemes --><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>fieldset<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-dialog<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\"><span class=\"token comment\">\/\/ Someone probably has a good reason why they'd do it this way, right?<\/span><br \/><br \/><span class=\"token keyword\">const<\/span> settingsButton <span class=\"token operator\">=<\/span> document<span class=\"token punctuation\">.<\/span><span class=\"token function\">getElementById<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">'settings-trigger'<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>settingsButton<span class=\"token punctuation\">.<\/span>command <span class=\"token operator\">=<\/span> <span class=\"token string\">'show-modal'<\/span><span class=\"token punctuation\">;<\/span><br \/>settingsButton<span class=\"token punctuation\">.<\/span>commandForElement <span class=\"token operator\">=<\/span> document<span class=\"token punctuation\">.<\/span><span class=\"token function\">getElementById<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">'settings-dialog'<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>This lets the <code>&lt;custom-dialog&gt;<\/code> behave exactly like a <code>&lt;dialog&gt;<\/code> for the purposes of the <code>command<\/code> and <code>commandForElement<\/code> properties.<\/p>\n<h2 id=\"why-i-like-it\" tabindex=\"-1\">Why I like it <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h2>\n<p>In my <a href=\"https:\/\/blogs.igalia.com\/alice\/how-shadow-dom-and-accessibility-are-in-conflict\/\">earlier blog post<\/a> I explained that I was concerned that the Cross-root ARIA delegation and reflection proposals introduced a <a href=\"https:\/\/blogs.igalia.com\/alice\/how-shadow-dom-and-accessibility-are-in-conflict\/#limitations-of-these-apis\">bottleneck problem<\/a>. This problem arose because it was only possible to refer to one element per attribute, rather than allowing <em>arbitrary<\/em> cross-shadow root references.<\/p>\n<p>This proposal <em>absolutely doesn\u2019t<\/em> solve that problem, but it reframes the overall problem such that I don\u2019t think it matters any more.<\/p>\n<p>The key difference between reference target and the earlier proposals is that reference target is a catch-all for references to the shadow host, rather than requiring each attribute to be forwarded separately. This solves a specific problem, which I alluded to above: how can custom element authors encapsulate the behaviour of a given built-in HTML element while also allowing other elements to refer to the custom element as if it <em>was<\/em> the built-in element?<\/p>\n<p>I believe this more narrow problem definition accounts for a significant proportion - not all, but many - cases where references need to be able to cross into shadow roots. And it makes the API make much more sense to me - if you\u2019re using the <code>for<\/code> attribute to refer to a <code>&lt;custom-input&gt;<\/code>, you\u2019re not meant to need to know that you\u2019re actually referring to an enclosed <code>&lt;input&gt;<\/code>, you just want the <code>&lt;custom-input&gt;<\/code> to be labelled. This API makes the enclosed <code>&lt;input&gt;<\/code> an implementation detail. And since a shadow root can only have one host, it makes sense that it can only have one reference target.<\/p>\n<h2 id=\"adjacent-as-yet-unsolved-problems\" tabindex=\"-1\">Adjacent, as-yet unsolved problems <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h2>\n<h3 id=\"arbitrary-cross-shadow-root-references\" tabindex=\"-1\">Arbitrary cross-shadow root references <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h3>\n<p>As mentioned above, one adjacent problem is the problem of element references which do need to refer to specific elements within a shadow root, rather than a stand-in for the shadow host.<\/p>\n<p>The explainer gives two examples of this: <a href=\"https:\/\/github.com\/WICG\/webcomponents\/blob\/gh-pages\/proposals\/reference-target-explainer.md#aria-activedescendant-and-comboboxes\"><code>aria-activedescendant<\/code><\/a> on a combobox element which needs to refer to an option inside of a shadow root, and ARIA attributes like <code>aria-labelledby<\/code>, <code>aria-describedby<\/code> and <code>aria-errormessage<\/code> which may need <a href=\"https:\/\/github.com\/WICG\/webcomponents\/blob\/gh-pages\/proposals\/reference-target-explainer.md#fine-grained-aria-labelledby-and-aria-describedby\">a computed name for the component which excludes some parts<\/a>.<\/p>\n<p>I think we need to be careful about generalising this problem, though. As I describe later in the explainer, I think we might be able to <a href=\"https:\/\/github.com\/WICG\/webcomponents\/blob\/gh-pages\/proposals\/reference-target-explainer.md#addressing-individual-use-cases-separately\">get better solutions by solving more specific problems<\/a> - as we have with reference target.<\/p>\n<p>If you have another example of where you need to refer to specific elements within a shadow root, you can leave a comment on <a href=\"https:\/\/github.com\/WICG\/webcomponents\/issues\/1111\">this issue collecting use cases<\/a>.<\/p>\n<h3 id=\"attribute-forwarding\" tabindex=\"-1\">Attribute forwarding <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h3>\n<p>While reference target allows other elements to refer to the encapsulated element, custom element authors may also want to allow developers using their component to use standard HTML and ARIA attributes on the host element and have those apply to the encapsulated element.<\/p>\n<p>For example, you might like to support <code>popoverTarget<\/code> on your <code>&lt;custom-button&gt;<\/code> element:<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-button<\/span> <span class=\"token attr-name\">popoverTarget<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>languages<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span>Language<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-button<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-menu<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>languages<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">popover<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span>Nederlands<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span>Frysk<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span>Vlaams<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-menuitem<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-menu<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<p>There is an <a href=\"https:\/\/github.com\/WICG\/webcomponents\/issues\/1068\">issue for the attribute forwarding idea<\/a>; leave a comment there if this is an idea you\u2019d like to see pursued.<\/p>\n<h3 id=\"form-association\" tabindex=\"-1\">Form association <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h3>\n<p>Custom elements can be specified as <a href=\"https:\/\/html.spec.whatwg.org\/dev\/custom-elements.html#custom-elements-face-example\">form-associated<\/a>, but there\u2019s no way to associate an encapsulated form-associated built-in element (such as <code>&lt;input&gt;<\/code>) with an enclosing <code>&lt;form&gt;<\/code>.<\/p>\n<p>For example, the <code>&lt;custom-input&gt;<\/code> above could be nested in a <code>&lt;form&gt;<\/code> element, but the enclosed <code>&lt;input&gt;<\/code> wouldn\u2019t be associated with the <code>&lt;form&gt;<\/code> - instead, you\u2019d have to use <code>setFormValue()<\/code> on the custom element and copy the value of the <code>&lt;input&gt;<\/code>.<\/p>\n<h2 id=\"spec-and-implementation-status\" tabindex=\"-1\">Spec and implementation status <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h2>\n<p>In brief: the spec changes seem to be in good shape, Chromium has the most feature-complete implementation and there are significantly less-baked implementations in WebKit and Firefox.<\/p>\n<h3 id=\"spec-changes\" tabindex=\"-1\">Spec changes <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h3>\n<p>There are open pull requests on the <a href=\"https:\/\/github.com\/whatwg\/html\/pull\/10995\">HTML<\/a> and <a href=\"https:\/\/github.com\/whatwg\/dom\/pull\/1353\">DOM<\/a> specs. Since these PRs are still being reviewed, the concepts and terminology below might change, but this is what we have right now. These changes have already had a few rounds of reviews, thanks to Anne van Kesteren, Olli Pettay and Keith Cirkel.<\/p>\n<p>The <a href=\"https:\/\/whatpr.org\/dom\/1353\/388779b...64676d6.html\">DOM<\/a> change:<\/p>\n<ul>\n<li>adds the concept of a <a href=\"https:\/\/whatpr.org\/dom\/1353\/388779b...64676d6.html#shadowroot-reference-target\">reference target<\/a><\/li>\n<li>adds the <a href=\"https:\/\/whatpr.org\/dom\/1353\/388779b...64676d6.html#interface-shadowroot\"><code>referenceTarget<\/code><\/a> property to the <code>ShadowRoot<\/code> object.<\/li>\n<\/ul>\n<p>The <a href=\"https:\/\/github.com\/whatwg\/html\/pull\/10995\">HTML<\/a> change is where the actual effect of the reference target is defined.<\/p>\n<h4 id=\"element-reference-attribute-type\" tabindex=\"-1\">Element reference attribute type <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>One key change in the HTML spec is the addition of an attribute type for <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#element-reference-attributes\">\u201celement reference\u201d attributes<\/a>. This formalises in HTML what has previously been referred to as an <a href=\"https:\/\/www.w3.org\/TR\/wai-aria\/#valuetype_idref\">ID reference<\/a> or <a href=\"https:\/\/www.w3.org\/TR\/xmlschema11-2\/#IDREF\">IDREF<\/a>. This term isn\u2019t currently used in HTML, and since the addition of <a href=\"https:\/\/html.spec.whatwg.org\/#reflecting-content-attributes-in-idl-attributes:element\">reflected IDL Element attributes<\/a>, IDs aren\u2019t strictly necessary, either.<\/p>\n<p>Before this change, whenever an attribute in the HTML spec was required to match another element based on its ID, this was written out explicitly where the attribute was defined. For example, the <a href=\"https:\/\/html.spec.whatwg.org\/#attr-label-for\">definition of the <code>&lt;label&gt;<\/code> element\u2019s <code>for<\/code> attribute<\/a>\ncurrently reads:<\/p>\n<blockquote>\n<p>The <code>for<\/code> attribute may be specified to indicate a form control with which the caption is to be associated. If the attribute is specified, the attribute\u2019s value must be the ID of a labelable element in the same tree as the <code>label<\/code> element. If the attribute is specified and there is an element in the tree whose ID is equal to the value of the for attribute, and the first such element in tree order is a labelable element, then that element is the <code>label<\/code> element\u2019s labeled control.<\/p>\n<\/blockquote>\n<p>Since reference target affects how this type of reference works, and is intended to apply for every attribute which refers to another element, it was simpler to have one central definition.<\/p>\n<h4 id=\"reference-target-resolution\" tabindex=\"-1\">Reference target resolution <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>For a reference target to actually do something, we need to define what effect it has. This is defined, quite straightforwardly, in the <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#resolve-the-reference-target\">steps to resolve the reference target<\/a>:<\/p>\n<blockquote>\n<ol>\n<li>If <em>element<\/em> is not a shadow host, or <em>element<\/em>\u2019s shadow root\u2019s reference target is null, then return <em>element<\/em>.<\/li>\n<li>Let <em>referenceTargetValue<\/em> be the value of <em>element<\/em>\u2019s shadow root\u2019s reference target.<\/li>\n<li>Let <em>candidate<\/em> be the first element in <em>element<\/em>\u2019s shadow root whose ID matches <em>referenceTargetValue<\/em>.<\/li>\n<li>If no such element exists, return null.<\/li>\n<li>Return the result of resolving the reference target on <em>candidate<\/em>.<\/li>\n<\/ol>\n<\/blockquote>\n<p>These steps are recursive: if a shadow root\u2019s reference target has its own shadow root, and that shadow root has a reference target, we keep descending into the nested shadow root.<\/p>\n<p>One slightly subtle design choice here is that if a shadow root has a reference target which doesn\u2019t refer to <em>any<\/em> element - for example, an empty string, or a value which doesn\u2019t match the ID of any element in its subtree - the resolved reference target is null, <strong>not<\/strong> the shadow host.<\/p>\n<p>For example, if you tried to use <code>popoverTarget<\/code> to refer to a shadow host which had a <code>popover<\/code> attribute, but had an invalid reference target on its shadow root, the <code>popoverTarget<\/code> attribute won\u2019t actually target anything:<\/p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>button<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>more-actions<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">popoverTarget<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>actions-popover<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">aria-label<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>more actions<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span>\u2026<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>button<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><br \/><span class=\"token comment\">&lt;!-- Even though this has a popover attribute, the button won't toggle it! --><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>custom-popover<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>actions-popover<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">popover<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>template<\/span> <span class=\"token attr-name\">shadowRootMode<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>open<span class=\"token punctuation\">\"<\/span><\/span><br \/>            <span class=\"token attr-name\">shadowRootReferenceTarget<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>0xDEADBEEF<span class=\"token punctuation\">\"<\/span><\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>div<\/span> <span class=\"token attr-name\">id<\/span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=<\/span><span class=\"token punctuation\">\"<\/span>help-im-trapped-in-a-shadow-root<span class=\"token punctuation\">\"<\/span><\/span> <span class=\"token attr-name\">popover<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;<\/span>slot<\/span><span class=\"token punctuation\">><\/span><\/span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>slot<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>div<\/span><span class=\"token punctuation\">><\/span><\/span><br \/>  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>template<\/span><span class=\"token punctuation\">><\/span><\/span><br \/><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;\/<\/span>custom-dialog<\/span><span class=\"token punctuation\">><\/span><\/span><\/code><\/pre>\n<h4 id=\"resolved-and-unresolved-attr-target-elements\" tabindex=\"-1\">Resolved and unresolved <em>attr<\/em> target elements <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>Like many spec concepts, this one is a real mouthful.<\/p>\n<p>This lets us be very clear about whether reference target resolution has happened when we\u2019re talking about what element an attribute refers to.<\/p>\n<p>If we\u2019re <a href=\"https:\/\/whatpr.org\/html\/10995\/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:element\">reflecting an attribute to its IDL counterpart<\/a>, we now use the <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#get-the-unresolved-attr-target-element\">unresolved <em>attr<\/em> target element<\/a>. For example, if we had the DOM defined in the previous example, and we wanted to get the <code>popoverTargetElement<\/code> for the <code>&quot;settings-trigger&quot;<\/code> button:<\/p>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\"><span class=\"token keyword\">const<\/span> moreActions <span class=\"token operator\">=<\/span> document<span class=\"token punctuation\">.<\/span><span class=\"token function\">getElementById<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"more-actions\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/><span class=\"token comment\">\/\/ This will log the &lt;custom-popover> element (!)<\/span><br \/>console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span>moreActions<span class=\"token punctuation\">.<\/span>popoverTargetElement<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>(In spec terms: the <code>&lt;custom-popover&gt;<\/code> element is the <strong>unresolved <code>popoverTarget<\/code> target element<\/strong> for the <code>&lt;button&gt;<\/code>.)<\/p>\n<p>This might also be a bit surprising; we spent quite a bit of time going back and forth on this, since we thought developers might want to know that the <code>popoverTarget<\/code> isn\u2019t actually targeting anything. However, using the unresolved target lets us have a very close parallel between setting and getting the <code>popoverTargetElement<\/code>, as well as preserving the shadow root\u2019s encapsulation.<\/p>\n<p>The <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#get-the-resolved-attr-target-element\">resolved <em>attr<\/em> target element<\/a>, meanwhile, is what will be used when actually doing something with the attribute - such as triggering a popover, or computing a label\u2019s labeled control, or determining an element\u2019s <a href=\"https:\/\/w3c.github.io\/accname\/#mapping_additional_nd_description\">accessible description<\/a>.<\/p>\n<p>In the above example, the <strong>resolved <code>popoverTarget<\/code> target element<\/strong> for the button is null. And, going back to the examples we\u2019ve seen earlier:<\/p>\n<ul>\n<li>the <strong>resolved <code>commandFor<\/code> target element<\/strong> for the Settings button is the inner <code>&lt;dialog&gt;<\/code> - clicking the button will open the <code>&lt;dialog&gt;<\/code>.<\/li>\n<li>the <strong>resolved <code>for<\/code> target element<\/strong> for the <code>&lt;label&gt;<\/code> is the inner <code>&lt;input&gt;<\/code> - clicking the label will focus the input, and the input\u2019s computed <a href=\"https:\/\/w3c.github.io\/accname\/#mapping_additional_nd_name\">accessible name<\/a> will be \u201cTrack name\u201d.<\/li>\n<\/ul>\n<h4 id=\"referring-to\" tabindex=\"-1\">\u201cReferring to\u201d <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>For convenience, we define the concept of an attribute <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#single-element-reference-refers-to\">\u201creferring to\u201d<\/a> an element:<\/p>\n<blockquote>\n<p>A single-element reference attribute <em>attr<\/em> on an element <em>X<\/em> <strong>refers to<\/strong> an element <em>Y<\/em> as its target if <em>Y<\/em> is the resolved <em>attr<\/em> target element for <em>X<\/em>.<\/p>\n<\/blockquote>\n<p>So, for example, the <code>commandFor<\/code> attribute on the Settings button <strong>refers to<\/strong> the inner <code>&lt;dialog&gt;<\/code> element.<\/p>\n<h4 id=\"sets-of-element-references\" tabindex=\"-1\">Sets of element references <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>All of the above used single element references as examples, but there are attributes which can refer to more than one element. For example, almost all of the ARIA attributes which refer to other elements refer to multiple elements in an ordered list - one such is <a href=\"https:\/\/w3c.github.io\/aria\/#aria-errormessage\"><code>aria-errormessage<\/code><\/a>, which can refer to one or more elements which should be exposed as specifically as an error message for an element which is marked as invalid.<\/p>\n<p>We define a <a href=\"https:\/\/whatpr.org\/html\/10995\/common-microsyntaxes.html#set-of-element-references\">set of element references<\/a> attribute type, as well as a couple of subtypes which impose constraints such as ordering or uniqueness, as well as what it means for one of these attributes to <strong>refer to<\/strong> another element, and how to get the resolved and unresolved <em>attr<\/em> target elements for these attributes.<\/p>\n<p>While these are slightly more complex than the single element versions, they follow the same basic logic. The only marginally significant difference is that since they produce lists of elements, if a shadow root\u2019s reference target is invalid, no element is added to the list for that unresolved <em>attr<\/em> target, instead of returning null.<\/p>\n<h4 id=\"using-these-concepts-in-the-rest-of-the-spec\" tabindex=\"-1\">Using these concepts in the rest of the spec <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h4>\n<p>Now that we\u2019ve defined these spec concepts, we have to update each place in the spec where we previously used the \u201cwhose ID is equal to the value of the blahblah attribute\u201d wording.<\/p>\n<p>Returning to our good friend <code>popoverTarget<\/code>, we can see a relatively straightforward example.<\/p>\n<p>The definition of the <code>popoverTarget<\/code> attribute now reads:<\/p>\n<blockquote>\n<p>If specified, the <code>popovertarget<\/code> attribute value must be a valid single-element reference attribute referring to an element with the <code>popover<\/code> attribute.<\/p>\n<\/blockquote>\n<p>And now, <a href=\"https:\/\/whatpr.org\/html\/10995\/59931bd...99765dc\/popover.html#popover-target-element\">get the <strong>popover target element<\/strong><\/a> determines <em>popoverElement<\/em> like this:<\/p>\n<blockquote>\n<ol start=\"4\">\n<li>Let <em>popoverElement<\/em> be <em>node<\/em>\u2019s resolved <code>popovertarget<\/code> target element.<\/li>\n<\/ol>\n<\/blockquote>\n<p><a href=\"https:\/\/whatpr.org\/html\/10995\/forms.html#labeled-control\"><code>&lt;label&gt;<\/code> association<\/a> is a bit more complex, since we wanted descendants of the <code>&lt;label&gt;<\/code> to be correctly labelled when using reference target:<\/p>\n<blockquote>\n<p>To determine a <code>label<\/code> element <em>label<\/em>\u2019s <strong>labeled control<\/strong>, run these steps:<\/p>\n<ol>\n<li>If <em>label<\/em>\u2019s <code>for<\/code> attribute is specified, then:\n<ol>\n<li>If the resolved <code>for<\/code> target element is not null, and the resolved <code>for<\/code> target element is a labelable element, return that element.<\/li>\n<li>Otherwise, return null.<\/li>\n<\/ol>\n<\/li>\n<li>For each descendant <em>descendant<\/em> of <em>label<\/em> in tree order:\n<ol>\n<li>Let <em>candidate<\/em> be the result of resolving the reference target on descendant.<\/li>\n<li>If <em>candidate<\/em> is a labelable element, return <em>candidate<\/em>.<\/li>\n<\/ol>\n<\/li>\n<li>Return null.<\/li>\n<\/ol>\n<\/blockquote>\n<p>There is also a <a href=\"https:\/\/github.com\/w3c\/aria\/pull\/2474\">PR open on the ARIA spec<\/a> to introduce this terminology there.<\/p>\n<h3 id=\"implementation-status\" tabindex=\"-1\">Implementation status <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/alice\/reference-target-having-your-encapsulation-and-eating-it-too\/\">#<\/a><\/h3>\n<p>Chromium has the <a href=\"https:\/\/wpt.fyi\/results\/shadow-dom\/reference-target\/tentative?label=master&label=experimental&product=chrome&product=edge&aligned\">most complete implementation<\/a>, though it may not quite be up to date with the latest spec changes. Any developers wanting to try it out should get the latest build of Chrome or Edge and flip on the Experimental Web Platform Features flag. If you do try it out, I\u2019d love to hear any <a href=\"https:\/\/github.com\/WICG\/webcomponents\/issues\/1120\">feedback<\/a> you might have!<\/p>\n<p>WebKit and Firefox <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1952585\">(tracking bug)<\/a> each have a prototype implementation, available behind respective feature flags (<code>ShadowRootReferenceTargetEnabled<\/code> for WebKit and <code>dom.shadowdom.referenceTarget.enabled<\/code> for Firefox), which should pass at least most of the existing WPT tests - however, the WPT tests are insufficient to test <em>all<\/em> of the functionality, and the functionality which couldn\u2019t be tested via WPTs hasn\u2019t been implemented yet in these engines. The Chromium implementation included adding many Chromium-specific tests for the behaviour which can\u2019t be tested via WPTs, as well as implementing that behaviour.<\/p>\n<p>Currently, WPT tests can only test the computed accessible name and computed accessible role for an element, as well as testing DOM methods and user actions like clicking. However, reference target impacts the accessibility tree in many ways - not only via ARIA attributes, but via <a href=\"https:\/\/www.w3.org\/TR\/html-aam-1.0\/#att-popovertarget\">attributes like <code>popoverTarget<\/code> being exposed in the accessibility tree<\/a> as an accessible relation.<\/p>\n<p>And, importantly, <em>changes<\/em> to the accessibility tree can require certain notifications to be fired to assistive technology APIs - and reference target introduces several new ways to change the accessibility tree. Adding, changing, or removing a shadow root\u2019s <code>referenceTarget<\/code> may cause changes in the resolved target elements for attributes, causing accessibility tree changes and potentially requiring notifications. Likewise, inserting an element with an ID which matches a shadow root\u2019s <code>referenceTarget<\/code> could also cause a shadow host\u2019s resolved reference target to change, also potentially causing the accessibility tree to change.<\/p>\n<p>There are two complementary projects currently underway which will allow us to write much richer tests for accessibility tree functionality in browsers:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/web-platform-tests\/wpt\/pull\/53733\">support for writing WPT tests which directly test what browsers expose to accessibility APIs<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/web-platform-tests\/wpt\/pull\/55784\">support for an \u201caccessible properties\u201d API<\/a>.<\/li>\n<\/ul>\n<p>Once we can write WPT tests which actually test the full spectrum of expected behaviour for reference target, we\u2019ll be able to actually make it an <a href=\"https:\/\/github.com\/web-platform-tests\/interop\/issues\/1011\">official interop focus area<\/a>.<\/p>\n<p>The prototype implementation work in WebKit and Firefox, as well as the spec work done by Igalia, was generously funded by a <a href=\"https:\/\/blogs.igalia.com\/mrego\/solving-cross-root-aria-issues-in-shadow-dom\/\">grant from NLNet Foundation<\/a>, while the implementation work in Chromium and much of the remainder of the spec work was done by Microsoft engineers on the Edge team.<\/p>                ","author":{"name":"Alice Boxhall","uri":"https:\/\/blogs.igalia.com\/alice\/alice\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #54","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-54\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-54\/","updated":"2026-01-26T21:00:55+00:00","content":"\n<p>Update on what happened in WebKit in the week from January 19 to January 26.<\/p>\n<p>\nThe main event this week has been the creation of the branch for the upcoming stable series, accompanied by the first release candidate before 2.52.0. But there's more: the WPE port gains hyphenation support and the ability to notify of graphics buffer changes; both ports get graphics fixes and a couple of new Web features, and WPE-Android also gets a new stable release.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305917@main\">Implemented<\/a> support for the <code>:open<\/code>\npseudo-class on dialog and details elements. This is currently behind the\n<code>OpenPseudoClass<\/code> feature flag.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306152@main\">Implemented<\/a> the <code>source<\/code> property for\n<code>ToggleEvent<\/code>. This can be used to run code dependent on the triggering element\nin response to a popover or dialog toggle.<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306119@main\">Fixed<\/a> the rendering glitches with\nwheel event asynchronous scrolling, which occurred when the page was scrolled\nto areas not covered by tiles while the main thread was blocked.<\/p>\n  <\/div>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n  <div class=\"wip-item\">\n<p>Support for\n<a rel=\"external\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Reference\/Properties\/hyphens\">hyphenation<\/a>\nhas been <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305816@main\">added to WPE<\/a>. This requires\n<code>libhyphen<\/code> and can be disabled at build-time with the <code>USE_LIBHYPHEN=OFF<\/code>\nCMake option.<\/p>\n  <\/div>\n<h3 id=\"wpe-platform-api-jigsaw\">WPE Platform API \ud83e\udde9<\/h3>\n<div class=\"wip-description\">\n<p>New, modern platform API that supersedes usage of libwpe and WPE backends.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>WPEPlatform <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/306008@main\">gained support<\/a> to notify\nchanges in the configuration of graphics buffers allocated to render the\ncontents of a web view, either by handling the <code>WPEView::buffers-changed<\/code>\nsignal or by overriding the <code>WPEViewClass.buffers_changed<\/code> virtual function.\nThis feature is mainly useful for platform implementations which may need to\nperform additional setup in advance, before updated web view contents are\nprovided in the buffers configured by WebKit.<\/p>\n  <\/div>\n<h2 id=\"releases-package\">Releases \ud83d\udce6\ufe0f<\/h2>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/github.com\/Igalia\/wpe-android\/releases\/tag\/v0.3.1\">WPE-Android 0.3.0<\/a>\nhas been released, and prebuilt packages are available <a rel=\"external\" href=\"https:\/\/central.sonatype.com\/artifact\/org.wpewebkit.wpeview\/wpeview\/\">at the Maven Central\nrepository<\/a>.\nThe main change in this this version is the update to WPE WebKit 2.50.4, which\nis the most recent stable release.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/commits\/webkitglib\/2.52\">A new branch has been\ncreated<\/a> for the\nupcoming 2.52.x stable release series of the GTK and WPE WebKit ports. The\nfirst release candidates from this branch, <a rel=\"external\" href=\"https:\/\/webkitgtk.org\/2026\/01\/23\/webkitgtk2.51.90-released.html\">WebKitGTK\n2.51.90<\/a> and\n<a rel=\"external\" href=\"https:\/\/wpewebkit.org\/release\/wpewebkit-2.51.90.html\">WPE WebKit 2.51.90<\/a> are\nnow available. Testing and <a rel=\"external\" href=\"https:\/\/bugs.webkit.org\">issue reports in Bugzilla<\/a>\nare welcome to help with stabilization before the first stable release, which\nis planned for mid-March.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Enrique Oca\u00f1a: Igalia Multimedia contributions in 2025","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/eocanha.org\/blog\/2026\/01\/26\/igalia-multimedia-contributions-in-2025\/"}},"id":"https:\/\/eocanha.org\/blog\/?p=701","updated":"2026-01-26T09:34:37+00:00","content":"\n                <img class=\"face\" src=\"\/images\/eocanha.png\" width=\"100\" height=\"100\" alt=\"\" align=\"right\" style=\"float: right\" \/>\n<p>Now that 2025 is over, it&#8217;s time to look back and feel proud of the path we&#8217;ve walked. Last year has been really exciting in terms of contributions to GStreamer and WebKit for the Igalia Multimedia team.<\/p>\n\n\n\n<p>With more than 459 contributions along the year, we&#8217;ve been one of the top contributors to the GStreamer project, in areas like Vulkan Video, GstValidate, VA, GStreamer Editing Services, WebRTC or H.266 support.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/gstreamer-contributions.jpg\"><img width=\"943\" height=\"530\" src=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/gstreamer-contributions.jpg\" alt=\"Pie chart of Igalia's contributions to different areas of the GStreamer project:\nother (30%)\nvulkan (24%)\nvalidate (7%)\nva (6%)\nges (4%)\nwebrtc (3%)\nh266parse (3%)\npython (3%)\ndots-viewer (3%)\ntests (2%)\ndocs (2%)\ndevtools (2%)\nwebrtcbin (1%)\ntracers (1%)\nqtdemux (1%)\ngst (1%)\nci (1%)\ny4menc (1%)\nvideorate (1%)\ngl (1%)\nalsa (1%)\" class=\"wp-image-706\" \/><\/a><figcaption>Igalia&#8217;s contributions to the GStreamer project<\/figcaption><\/figure>\n\n\n\n<p>In Vulkan Video we&#8217;ve worked on the VP9 video decoder, and cooperated with other contributors to push the AV1 decoder as well. There&#8217;s now an H.264 base class for video encoding that is designed to support general hardware-accelerated processing.<\/p>\n\n\n\n<p>GStreaming Editing Services, the framework to build video editing applications, has gained time remapping support, which now allows to include fast\/slow motion effects in the videos. Video transformations (scaling, cropping, rounded corners, etc) are now hardware-accelerated thanks to the addition of new Skia-based GStreamer elements and integration with OpenGL. Buffer pool tuning and pipeline improvements have helped to optimize memory usage and performance, enabling the edition of 4K video at 60 frames per second. Much of this work to improve and ensure quality in GStreamer Editing Services has also brought improvements in the GstValidate testing framework, which will be useful for other parts of GStreamer.<\/p>\n\n\n\n<p>Regarding H.266 (VVC), full playback support (with decoders such as <code>vvdec<\/code> and <code>avdec_h266<\/code>, demuxers and muxers for Matroska, MP4 and TS, and parsers for the <code>vvc1<\/code> and <code>vvi1<\/code> formats) is now available in GStreamer 1.26 thanks to Igalia&#8217;s work. This allows user applications such as the WebKitGTK web browser to leverage the hardware accelerated decoding provided by VAAPI to play H.266 video using GStreamer.<\/p>\n\n\n\n<p>Igalia has also been one of the top contributors to GStreamer Rust, with 43 contributions. Most of the commits there have been related to Vulkan Video.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/gstreamer-rs-contributions.jpg\"><img width=\"943\" height=\"530\" src=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/gstreamer-rs-contributions.jpg\" alt=\"Pie chart of Igalia's contributions to different areas of the GStreamer Rust project:\nvulkan (28%)\nother (26%)\ngstreamer (12%)\nci (12%)\ntracer (7%)\nvalidate (5%)\nges (7%)\nexamples (5%)\" class=\"wp-image-708\" \/><\/a><figcaption>Igalia&#8217;s contributions to the GStreamer Rust project<\/figcaption><\/figure>\n\n\n\n<p>In addition to GStreamer, the team also has a strong presence in WebKit, where we leverage our GStreamer knowledge to implement many features of the web engine related to multimedia. From the 1739 contributions to the WebKit project done last year by Igalia, the Multimedia team has made 323 of them. Nearly one third of those have been related to generic multimedia playback, and the rest have been on areas such as WebRTC, MediaStream, MSE, WebAudio, a new Quirks system to provide adaptations for specific hardware multimedia platforms at runtime, WebCodecs or MediaRecorder.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/webkit-contributions.jpg\"><img width=\"943\" height=\"530\" src=\"https:\/\/eocanha.org\/blog\/wp-content\/uploads\/2026\/01\/webkit-contributions.jpg\" alt=\"Pie chart of Igalia's contributions to different areas of the WebKit project:\nGeneric Gstreamer work (33%)\nWebRTC (20%)\nRegression bugfixing (9%)\nOther (7%)\nMSE (6%)\nBuildStream SDK (4%)\nMediaStream (3%)\nWPE platform (3%)\nWebAudio (3%)\nWebKitGTK platform (2%)\nQuirks (2%)\nMediaRecorder (2%)\nEME (2%)\nGlib (1%)\nWTF (1%)\nWebCodecs (1%)\nGPUProcess (1%)\nStreams (1%) \" class=\"wp-image-709\" \/><\/a><figcaption>Igalia Multimedia Team&#8217;s contributions to different areas of the WebKit project<\/figcaption><\/figure>\n\n\n\n<p>We&#8217;re happy about what we&#8217;ve achieved along the year and look forward to maintaining this success and bringing even more exciting features and contributions in 2026.<\/p>                ","author":{"name":"eocanha","uri":"https:\/\/eocanha.org\/blog"}},{"title":"Luke Lau: Closing the gap, part 2: Probability and profitability","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2.html"}},"id":"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2","updated":"2026-01-25T16:00:00+00:00","content":"\n<p>Welcome back to the second post in this series looking at how we can\nimprove the performance of RISC-V code from LLVM.<\/p>\n\n<p>Previously in <a href=\"http:\/\/lukelau.me\/2025\/12\/10\/closing-the-gap-pt1.html\">part 1<\/a>\nwe looked at how we can use <a href=\"https:\/\/cc-perf.igalia.com\">LNT<\/a> to\nanalyze performance gaps, then identified and fixed a missed <code class=\"language-plaintext highlighter-rouge\">fmsub.d<\/code>\nopportunity during instruction selection, giving a modest 1.77%\nspeedup on a SPEC CPU 2017 benchmark.<\/p>\n\n<p>In this post we\u2019ll be improving another SPEC benchmark by <strong>7%<\/strong> by\nteaching the loop vectorizer to make smarter cost modelling\ndecisions. It involves a relatively non-trivial analysis, but thanks\nto LLVM\u2019s modular infrastructure we can do it in just a handful of\nlines of code. Let\u2019s get started.<\/p>\n\n<h2 id=\"analysis\">Analysis<\/h2>\n\n<p>Just like last time, all fruitful performance work begins by analysing\nsome workloads. In the last post we had already run some comparisons\nof SPEC CPU 2017 benchmarks on LNT, so we can return to those results\nand pick another benchmark to focus on.  Here\u2019s one that\u2019s 12% slower\nthan GCC:<\/p>\n\n<p><img src=\"http:\/\/lukelau.me\/assets\/531.deepsjeng_r-before.png\" alt=\"Screenshot of LNT showing the 531.deepsjeng_r benchmark being 12.14% slower on Clang vs GCC\" \/><\/p>\n\n<p>531.deepsjeng_r is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Sjeng_(software)#Deep_Sjeng\">chess\nengine<\/a>\nthat tied first in the World Computer Chess Championships back\nin 2009. It consists of a lot bitwise arithmetic and complex loops,\nsince the state of the game is encoded in 64 element arrays: one\nelement for each square on the board. Unlike 508.namd_r from last\ntime, there\u2019s no floating point arithmetic.<\/p>\n\n<p>Drilling into the profile and its list of functions, right off the bat\nwe can see that one function is much slower on LLVM. On GCC\n<code class=\"language-plaintext highlighter-rouge\">qsearch(state_t*, int, int, int, int)<\/code> makes up 9.1% of the overall\ncycles, but on LLVM it\u2019s 16.1%. And if we click in on the function and\nview the cumulative total of cycles spent in user mode, Clang takes\n74.6 billion cycles to do what takes GCC only 37.7 billion cycles.<\/p>\n\n<figure>\n<img src=\"http:\/\/lukelau.me\/assets\/531.deepsjeng_r-total-cumulative-cycles.png\" alt=\"Screenshot of Clang disassembly and GCC disassembly side by side, with inline total cumulative cycle annotations showing Clang taking 74.6 billion cycles and GCC taking 37.7\" \/>\n<figcaption>Left shows Clang taking 74.6 billion cycles, right shows GCC taking 37.7 billion.<\/figcaption>\n<\/figure>\n\n<p>So there\u2019s probably something we can improve upon here, but it\u2019s not\nimmediately obvious from staring at the disassembly. <code class=\"language-plaintext highlighter-rouge\">qsearch<\/code> is a\npretty big function with a couple hundred instructions, so switching\nto the CFG view gives us a better overview.<\/p>\n\n<p>On LLVM\u2019s side we see the offending loop that\u2019s consuming so many\ncycles: It\u2019s long, vectorized, and completely if-predicated: there\u2019s\nno control flow inside the loop itself. This is typical of a loop\nthat\u2019s been auto-vectorized by the loop vectorized. If you look at the\nload and store instructions you can see that they are masked with the\n<code class=\"language-plaintext highlighter-rouge\">v0.t<\/code> operand, stemming from the original control flow that was\nflattened.<\/p>\n\n<p><img src=\"http:\/\/lukelau.me\/assets\/531.deepsjeng_r-disassembly-before.png\" alt=\"Screenshot of the disassembly from Clang, showing a very hot block\nwith a lot of masked vector\ninstructions.\" \/><\/p>\n\n<p>But on the GCC side there\u2019s no equivalent vectorized loop. The loop is\nin there somewhere, but <strong>all the loops are still in their original\nscalar form with the control flow intact<\/strong>. And if we look at the\nedges coming from the loop headers, we can see that most of the time\nit visits one or two basic blocks and then branches back up to the\nheader. Most of the blocks in the loop are completely cold.<\/p>\n\n<p>Unfortunately the sources for deepsjeng aren\u2019t open source so we can\u2019t\nshare them in this post, but the very rough structure of the loop is\nsomething like this:<\/p>\n\n<div class=\"language-c highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">N<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">foo<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">a<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">bar<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">b<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n            <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">baz<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">c<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n                <span class=\"n\">qux<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">123<\/span><span class=\"p\">;<\/span>\n                <span class=\"c1\">\/\/ lots of work here...<\/span>\n            <span class=\"p\">}<\/span>\n        <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>For any given iteration, it\u2019s statistically unlikely that we enter the\nfirst if statement. It\u2019s even more unlikely that the second if\u2019s\ncondition is also true. And even more so for the third nested if where\nwe eventually have lots of work to compute.<\/p>\n\n<p>In a scalar loop this doesn\u2019t matter because if an if statement\u2019s\ncondition is false, then we don\u2019t execute the code inside it. We just\nbranch back to the start of the loop. But with a vectorized loop, we\nexecute every single instruction regardless of the condition.<\/p>\n\n<p>This is the core of the performance gap that we\u2019re seeing versus GCC:\nGiven that the majority of the work in this loop is so deeply nested\nin the control flow, it would have been better to have not vectorized\nit given that we need to if-convert it.<\/p>\n\n<h1 id=\"cost-modelling\">Cost modelling<\/h1>\n\n<p>One of the hardest problems when making an optimizing compiler is to\nknow when an optimization is profitable. Some optimizations are a\ndouble edged sword that can harm performance just as much as they can\nimprove it (if not more), and loop vectorization falls squarely into\nthis category. So rather than blindly applying optimizations at any\ngiven opportunity, LLVM has detailed cost models for each target to\ntry and estimate how expensive or cheap a certain sequence of\ninstructions is, which it can then use to evaluate whether or not a\ntransform will be a net positive.<\/p>\n\n<p>It\u2019s hard to overstate the amount of effort in LLVM spent fine tuning\nthese cost models, applying various heuristics and approximations to\nmake sure different optimizations don\u2019t shoot themselves in the\nfoot. In fact there are some optimizations like loop distribute that\nare in-tree but disabled by default due to the difficulty in getting\nthe cost model right.<\/p>\n\n<p>So naturally, we would expect that the loop vectorizer already has a\nsophisticated solution for the problem we\u2019re seeing in our analysis:\nGiven any predicated block that\u2019s if-converted during vectorization,\nwe would expect the scalar cost for that block to be made slightly\ncheaper because the scalar block may not always be executed. And the\nless likely it is to be executed, the cheaper it should be \u2014 the\nmost deeply nested if block should be discounted more than the\noutermost if block.<\/p>\n\n<p>So how does the loop vectorizer handle this?<\/p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/\/ A helper function that returns how much we should divide the cost of a<\/span>\n<span class=\"c1\">\/\/\/ predicated block by. Typically this is the reciprocal of the block<\/span>\n<span class=\"c1\">\/\/\/ probability, i.e. if we return X we are assuming the predicated block will<\/span>\n<span class=\"c1\">\/\/\/ execute once for every X iterations of the loop header so the block should<\/span>\n<span class=\"c1\">\/\/\/ only contribute 1\/X of its cost to the total cost calculation, but when<\/span>\n<span class=\"c1\">\/\/\/ optimizing for code size it will just be 1 as code size costs don't depend<\/span>\n<span class=\"c1\">\/\/\/ on execution probabilities.<\/span>\n<span class=\"c1\">\/\/\/<\/span>\n<span class=\"c1\">\/\/\/ TODO: We should use actual block probability here, if available. Currently,<\/span>\n<span class=\"c1\">\/\/\/       we always assume predicated blocks have a 50% chance of executing.<\/span>\n<span class=\"kr\">inline<\/span> <span class=\"kt\">unsigned<\/span>\n<span class=\"nf\">getPredBlockCostDivisor<\/span><span class=\"p\">(<\/span><span class=\"n\">TargetTransformInfo<\/span><span class=\"o\">::<\/span><span class=\"n\">TargetCostKind<\/span> <span class=\"n\">CostKind<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"n\">CostKind<\/span> <span class=\"o\">==<\/span> <span class=\"n\">TTI<\/span><span class=\"o\">::<\/span><span class=\"n\">TCK_CodeSize<\/span> <span class=\"o\">?<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"mi\">2<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We\u2019ve come across a load bearing TODO here. Either the block is\nexecuted or its not, so it\u2019s a fifty\/fifty chance.<\/p>\n\n<p>On its own this hardcoded probability doesn\u2019t seem like an\nunreasonable guess.  But whilst 50% may be an accurate estimate as to\nwhether or not a <strong>branch<\/strong> will be taken, it\u2019s an inaccurate estimate\nas to whether or not a <strong>block<\/strong> will be executed. Assuming that a\nbranch has a 1\/2 chance of being taken, the most deeply nested block\nin our example ends up having a <code class=\"language-plaintext highlighter-rouge\">1\/2 * 1\/2 * 1\/2 = 1\/8<\/code> chance of\nbeing executed.<\/p>\n\n<div class=\"language-c highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">N<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">foo<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">a<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"c1\">\/\/ 1\/2 chance of being executed<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">bar<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">b<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n            <span class=\"c1\">\/\/ 1\/4 chance of being executed<\/span>\n            <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">baz<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">c<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n                <span class=\"c1\">\/\/ 1\/8 chance of being executed<\/span>\n                <span class=\"c1\">\/\/ ...<\/span>\n            <span class=\"p\">}<\/span>\n        <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The fix to get the loop vectorizer to not unprofitably vectorize this\nloop will be to teach <code class=\"language-plaintext highlighter-rouge\">getPredBlockCostDivisor<\/code> to take into account\ncontrol flow between blocks.<\/p>\n\n<p>It\u2019s worth mentioning the fact that a hardcoded constant managed to\nwork well enough up until this point is the sign of an good trade\noff. 1% of the effort for 90% of the benefit. A patch can go off the\nrails very easily by trying to implement too much in one go, so\ndeferring the more complex cost modelling here till later was an\nastute choice. Incremental development is key to making progress\nupstream.<\/p>\n\n<h2 id=\"vplan-cost-modeling\">VPlan cost modeling<\/h2>\n\n<p>To get a better picture of how the loop vectorizer is calculating the\ncost for each possible loop, lets start with a simplified LLVM IR reproducer:<\/p>\n\n<div class=\"language-llvm highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">; for (int i = 0; i &lt; 1024; i++)<\/span>\n<span class=\"c1\">;   if (c0)<\/span>\n<span class=\"c1\">;     if (c1)<\/span>\n<span class=\"c1\">;       p1[p0[i]] = 0; \/\/ extra work to increase the cost in the predicated block<\/span>\n\n<span class=\"k\">define<\/span> <span class=\"kt\">void<\/span> <span class=\"vg\">@nested<\/span><span class=\"p\">(<\/span><span class=\"kt\">ptr<\/span> <span class=\"k\">noalias<\/span> <span class=\"nv\">%p0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">ptr<\/span> <span class=\"k\">noalias<\/span> <span class=\"nv\">%p1<\/span><span class=\"p\">,<\/span> <span class=\"kt\">i1<\/span> <span class=\"nv\">%c0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">i1<\/span> <span class=\"nv\">%c1<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n<span class=\"nl\">entry:<\/span>\n  <span class=\"k\">br<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%loop<\/span>\n\n<span class=\"nl\">loop:<\/span>\n  <span class=\"nv\">%iv<\/span> <span class=\"p\">=<\/span> <span class=\"k\">phi<\/span> <span class=\"kt\">i32<\/span> <span class=\"p\">[<\/span> <span class=\"m\">0<\/span><span class=\"p\">,<\/span> <span class=\"nv\">%entry<\/span> <span class=\"p\">],<\/span> <span class=\"p\">[<\/span> <span class=\"nv\">%iv.next<\/span><span class=\"p\">,<\/span> <span class=\"nv\">%latch<\/span> <span class=\"p\">]<\/span>\n  <span class=\"k\">br<\/span> <span class=\"kt\">i1<\/span> <span class=\"nv\">%c0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%then.0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%latch<\/span>\n\n<span class=\"nl\">then.0:<\/span>\n  <span class=\"k\">br<\/span> <span class=\"kt\">i1<\/span> <span class=\"nv\">%c1<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%then.1<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%latch<\/span>\n\n<span class=\"nl\">then.1:<\/span>\n  <span class=\"nv\">%gep0<\/span> <span class=\"p\">=<\/span> <span class=\"k\">getelementptr<\/span> <span class=\"kt\">i32<\/span><span class=\"p\">,<\/span> <span class=\"kt\">ptr<\/span> <span class=\"nv\">%p0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">i32<\/span> <span class=\"nv\">%iv<\/span>\n  <span class=\"nv\">%x<\/span> <span class=\"p\">=<\/span> <span class=\"k\">load<\/span> <span class=\"kt\">i32<\/span><span class=\"p\">,<\/span> <span class=\"kt\">ptr<\/span> <span class=\"nv\">%gep0<\/span>\n  <span class=\"nv\">%gep1<\/span> <span class=\"p\">=<\/span> <span class=\"k\">getelementptr<\/span> <span class=\"kt\">i32<\/span><span class=\"p\">,<\/span> <span class=\"kt\">ptr<\/span> <span class=\"nv\">%p1<\/span><span class=\"p\">,<\/span> <span class=\"kt\">i32<\/span> <span class=\"nv\">%x<\/span>\n  <span class=\"k\">store<\/span> <span class=\"kt\">i32<\/span> <span class=\"m\">0<\/span><span class=\"p\">,<\/span> <span class=\"kt\">ptr<\/span> <span class=\"nv\">%gep1<\/span>\n  <span class=\"k\">br<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%latch<\/span>\n\n<span class=\"nl\">latch:<\/span>\n  <span class=\"nv\">%iv.next<\/span> <span class=\"p\">=<\/span> <span class=\"k\">add<\/span> <span class=\"kt\">i32<\/span> <span class=\"nv\">%iv<\/span><span class=\"p\">,<\/span> <span class=\"m\">1<\/span>\n  <span class=\"nv\">%done<\/span> <span class=\"p\">=<\/span> <span class=\"k\">icmp<\/span> <span class=\"k\">eq<\/span> <span class=\"kt\">i32<\/span> <span class=\"nv\">%iv.next<\/span><span class=\"p\">,<\/span> <span class=\"m\">1024<\/span>\n  <span class=\"k\">br<\/span> <span class=\"kt\">i1<\/span> <span class=\"nv\">%done<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%exit<\/span><span class=\"p\">,<\/span> <span class=\"kt\">label<\/span> <span class=\"nv\">%loop<\/span>\n\n<span class=\"nl\">exit:<\/span>\n  <span class=\"k\">ret<\/span> <span class=\"kt\">void<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We can run <code class=\"language-plaintext highlighter-rouge\">opt -p loop-vectorize -debug<\/code> on this example to see how the loop\nvectorizer decides if it\u2019s profitable to vectorize the loop or not:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>$ opt -p loop-vectorize -mtriple riscv64 -mattr=+v nested.ll -disable-output -debug\n...\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %iv = phi i32 [ 0, %entry ], [ %iv.next, %latch ]\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %c0, label %then.0, label %latch\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %c1, label %then.1, label %latch\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %gep0 = getelementptr i32, ptr %p0, i32 %iv\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %x = load i32, ptr %gep0, align 4\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %gep1 = getelementptr i32, ptr %p1, i32 %x\nLV: Found an estimated cost of 1 for VF 1 For instruction:   store i32 0, ptr %gep1, align 4\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br label %latch\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %iv.next = add i32 %iv, 1\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %done = icmp eq i32 %iv.next, 1024\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %done, label %exit, label %loop\nLV: Scalar loop costs: 3.\n...\nCost of 1 for VF vscale x 4: induction instruction   %iv.next = add i32 %iv, 1\nCost of 0 for VF vscale x 4: induction instruction   %iv = phi i32 [ 0, %entry ], [ %iv.next, %latch ]\nCost of 1 for VF vscale x 4: exit condition instruction   %done = icmp eq i32 %iv.next, 1024\nCost of 0 for VF vscale x 4: EMIT vp&lt;%4&gt; = CANONICAL-INDUCTION ir&lt;0&gt;, vp&lt;%index.next&gt;\nCost of 0 for VF vscale x 4: EXPLICIT-VECTOR-LENGTH-BASED-IV-PHI vp&lt;%5&gt; = phi ir&lt;0&gt;, vp&lt;%index.evl.next&gt;\nCost of 0 for VF vscale x 4: EMIT-SCALAR vp&lt;%avl&gt; = phi [ ir&lt;1024&gt;, vector.ph ], [ vp&lt;%avl.next&gt;, vector.body ]\nCost of 1 for VF vscale x 4: EMIT-SCALAR vp&lt;%6&gt; = EXPLICIT-VECTOR-LENGTH vp&lt;%avl&gt;\nCost of 0 for VF vscale x 4: vp&lt;%7&gt; = SCALAR-STEPS vp&lt;%5&gt;, ir&lt;1&gt;, vp&lt;%6&gt;\nCost of 0 for VF vscale x 4: CLONE ir&lt;%gep0&gt; = getelementptr ir&lt;%p0&gt;, vp&lt;%7&gt;\nCost of 0 for VF vscale x 4: vp&lt;%8&gt; = vector-pointer ir&lt;%gep0&gt;\nCost of 2 for VF vscale x 4: WIDEN ir&lt;%x&gt; = vp.load vp&lt;%8&gt;, vp&lt;%6&gt;, vp&lt;%3&gt;\nCost of 0 for VF vscale x 4: WIDEN-GEP Inv[Var] ir&lt;%gep1&gt; = getelementptr ir&lt;%p1&gt;, ir&lt;%x&gt;\nCost of 12 for VF vscale x 4: WIDEN vp.store ir&lt;%gep1&gt;, ir&lt;0&gt;, vp&lt;%6&gt;, vp&lt;%3&gt;\nCost of 0 for VF vscale x 4: EMIT vp&lt;%index.evl.next&gt; = add nuw vp&lt;%6&gt;, vp&lt;%5&gt;\nCost of 0 for VF vscale x 4: EMIT vp&lt;%avl.next&gt; = sub nuw vp&lt;%avl&gt;, vp&lt;%6&gt;\nCost of 0 for VF vscale x 4: EMIT vp&lt;%index.next&gt; = add nuw vp&lt;%4&gt;, vp&lt;%0&gt;\nCost of 0 for VF vscale x 4: EMIT branch-on-count vp&lt;%index.next&gt;, vp&lt;%1&gt;\nCost of 0 for VF vscale x 4: vector loop backedge\nCost of 0 for VF vscale x 4: EMIT-SCALAR vp&lt;%bc.resume.val&gt; = phi [ ir&lt;0&gt;, ir-bb&lt;entry&gt; ]\nCost of 0 for VF vscale x 4: IR   %iv = phi i32 [ 0, %entry ], [ %iv.next, %latch ] (extra operand: vp&lt;%bc.resume.val&gt; from scalar.ph)\nCost of 0 for VF vscale x 4: EMIT vp&lt;%3&gt; = logical-and ir&lt;%c0&gt;, ir&lt;%c1&gt;\nCost for VF vscale x 4: 17 (Estimated cost per lane: 2.1)\n...\nLV: Selecting VF: vscale x 4.\nLV: Minimum required TC for runtime checks to be profitable:0\nLV: Interleaving is not beneficial.\nLV: Found a vectorizable loop (vscale x 4) in nested.ll\nLV: Vectorizing: innermost loop.\nLEV: Unable to vectorize epilogue because no epilogue is allowed.\nLV: Loop does not require scalar epilogue\nLV: Loop does not require scalar epilogue\nExecuting best plan with VF=vscale x 4, UF=1\n<\/code><\/pre><\/div><\/div>\n\n<p>First we see it work out the cost of the original scalar loop, or as\nthe vectorizer sees it, the loop with a vectorization factor (VF)\nof 1. It goes through each instruction calling into\nTargetTransformInfo, and arrives at a total scalar cost of 3. You\nmight have noticed though, if you went through and manually summed up\nthe individual instruction costs you would have gotten a total cost\nof 4. However the load and store instructions belong to the predicated\n<code class=\"language-plaintext highlighter-rouge\">then.1<\/code> block, so they have their cost divided by 2 from\n<code class=\"language-plaintext highlighter-rouge\">getPredBlockCostDivisor<\/code>.<\/p>\n\n<p>For the vectorized loop, the loop vectorizer uses\n<a href=\"https:\/\/llvm.org\/docs\/VectorizationPlan.html\">VPlan<\/a> to cost the one\nplan for a range of different VFs<sup id=\"fnref:scalar-vplan\"><a href=\"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2.html#fn:scalar-vplan\" class=\"footnote\" rel=\"footnote\">1<\/a><\/sup>. VPlan is an IR\nspecific to the loop vectorizer to help represent various\nvectorization strategies, which is why you see all the <code class=\"language-plaintext highlighter-rouge\">EMIT<\/code> and\n<code class=\"language-plaintext highlighter-rouge\">WIDEN<\/code> \u201crecipes\u201d in the output. It calculates a total cost for the\nloop and divides it by the estimated number of lanes \u2014 we\u2019re working\nwith scalable vectors on RISC-V so the target needs to make an\nestimate of what <code class=\"language-plaintext highlighter-rouge\">vscale<\/code> is \u2014 and arrives at 2.1 per lane. There\u2019s\nno predication discount applied here because it\u2019s a vectorized\nloop. 2.1 is cheaper than 3, so it ultimately picks the vectorized\nloop.<\/p>\n\n<h2 id=\"blockfrequencyinfo\">BlockFrequencyInfo<\/h2>\n\n<p>Computing an accurate probability that a given block will be executed\nis a non-trivial task, but thankfully LLVM already has an analysis we\ncan use for this called BlockFrequencyInfo.<\/p>\n\n<p>BlockFrequencyInfo computes how often a block can be expected to\nexecute relative to other blocks in a function. It in turn uses\nanother analysis called BranchProbabilityInfo to work out how likely a\nbranch to a specific block is going to be taken. And because\nBranchProbabilityInfo uses profiling information when available, it\ncan give you much more accurate block frequencies when compiling with\n<a href=\"https:\/\/clang.llvm.org\/docs\/UsersManual.html#profile-guided-optimization\">PGO<\/a>. Otherwise\nit will fall back to guessing the probability of a branch being taken,\nwhich is just 50\/50 a lot of the time, but sometimes influenced by\ninteresting heuristics too: like the probability of <code class=\"language-plaintext highlighter-rouge\">icmp eq i32 %x,\n0<\/code> is 0.375 instead of 0.5, and floats have a near zero chance of\nbeing NaN.<\/p>\n\n<p>Plugging BlockFrequencyInfo into the loop vectorizer is\nstraightforward, all we need to do is tell the pass manager that we\nwant to access BlockFrequencyInfo from LoopVectorizePass:<\/p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PreservedAnalyses<\/span> <span class=\"n\">LoopVectorizePass<\/span><span class=\"o\">::<\/span><span class=\"n\">run<\/span><span class=\"p\">(<\/span><span class=\"n\">Function<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">F<\/span><span class=\"p\">,<\/span>\n                                         <span class=\"n\">FunctionAnalysisManager<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">AM<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n   <span class=\"p\">...<\/span>\n   <span class=\"n\">BFI<\/span> <span class=\"o\">=<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">AM<\/span><span class=\"p\">.<\/span><span class=\"n\">getResult<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">BlockFrequencyAnalysis<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">(<\/span><span class=\"n\">F<\/span><span class=\"p\">);<\/span>\n   <span class=\"p\">...<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>(BlockFrequencyAnalysis is the pass that computes the analysis result BlockFrequencyInfo, if you\u2019re wondering why the names are different)<\/p>\n\n<p>Then we can use it to lookup the relative frequencies of whatever\nblock and work out the probability of it being executed in the loop:<\/p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">uint64_t<\/span> <span class=\"n\">LoopVectorizationCostModel<\/span><span class=\"o\">::<\/span><span class=\"n\">getPredBlockCostDivisor<\/span><span class=\"p\">(<\/span>\n    <span class=\"n\">TargetTransformInfo<\/span><span class=\"o\">::<\/span><span class=\"n\">TargetCostKind<\/span> <span class=\"n\">CostKind<\/span><span class=\"p\">,<\/span> <span class=\"k\">const<\/span> <span class=\"n\">BasicBlock<\/span> <span class=\"o\">*<\/span><span class=\"n\">BB<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">CostKind<\/span> <span class=\"o\">==<\/span> <span class=\"n\">TTI<\/span><span class=\"o\">::<\/span><span class=\"n\">TCK_CodeSize<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">return<\/span> <span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n\n  <span class=\"kt\">uint64_t<\/span> <span class=\"n\">HeaderFreq<\/span> <span class=\"o\">=<\/span>\n      <span class=\"n\">BFI<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">getBlockFreq<\/span><span class=\"p\">(<\/span><span class=\"n\">TheLoop<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">getHeader<\/span><span class=\"p\">()).<\/span><span class=\"n\">getFrequency<\/span><span class=\"p\">();<\/span>\n  <span class=\"kt\">uint64_t<\/span> <span class=\"n\">BBFreq<\/span> <span class=\"o\">=<\/span> <span class=\"n\">BFI<\/span><span class=\"o\">-&gt;<\/span><span class=\"n\">getBlockFreq<\/span><span class=\"p\">(<\/span><span class=\"n\">BB<\/span><span class=\"p\">).<\/span><span class=\"n\">getFrequency<\/span><span class=\"p\">();<\/span>\n  <span class=\"k\">return<\/span> <span class=\"n\">HeaderFreq<\/span> <span class=\"o\">\/<\/span> <span class=\"n\">BBFreq<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The frequencies returned from BlockFrequencyInfo are relative to the\nthe entry block of a function. So if a block has a frequency of 50 and\nthe entry block has a frequency of 100, then you can expect that block\nto execute 50 times for every 100 times the entry block is executed.<\/p>\n\n<p>You can use this to work out probabilities of a block being taken in a\nfunction, so in this example that block has a 50\/100 = 50% chance of\nbeing executed every time the function is executed. However this only\nworks in the case that the CFG has no loops: otherwise a block may be\nexecuted more times than the entry block and we\u2019d end up with\nprobabilities greater than 100%.<\/p>\n\n<p>If we want to calculate the probability of a block being executed\n<em>inside a loop<\/em> though, that\u2019s fine since the loop vectorizer\ncurrently only vectorizes inner-most loops<sup id=\"fnref:vplan-native\"><a href=\"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2.html#fn:vplan-native\" class=\"footnote\" rel=\"footnote\">2<\/a><\/sup>, i.e. loops\nthat contain no other loops.<\/p>\n\n<p>We can consider the frequencies of each block in the loop relative to\nthe frequency of the header block. To give a brief <a href=\"https:\/\/llvm.org\/docs\/LoopTerminology.html#id7\">loop\nterminology<\/a> recap,\nthe header is the first block inside the loop body which dominates all\nother blocks in the loop, and is the destination of all backedges. So\nthe header is guaranteed to have a frequency greater than or equal to\nany other block in the loop \u2014 this invariant is important as we\u2019ll\nsee later.<\/p>\n\n<p><img src=\"https:\/\/llvm.org\/docs\/_images\/loop-terminology.svg\" alt=\"A diagram showing off terminology for different parts of a loop\" \/><\/p>\n\n<p>Then to calculate the probability of a block in a loop being executed,\nwe divide the block frequency by the header frequency. To work out how\nmuch we should divide the cost of the scalar block by, we return the\ninverse of that.<\/p>\n\n<p>Trying out this change on our sample loop, first we\u2019ll see the debug\noutput from BlockFrequencyInfo as it\u2019s computed:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>$ opt -p loop-vectorize -mtriple riscv64 -mattr=+v nested.ll -disable-output -debug\n...\nblock-frequency-info: nested\n - entry: float = 1.0, int = 562949953421312\n - loop: float = 32.0, int = 18014398509481984\n - then.0: float = 16.0, int = 9007199254740992\n - then.1: float = 8.0, int = 4503599627370496\n - latch: float = 32.0, int = 18014398509481984\n - exit: float = 1.0, int = 562949953421312\n<\/code><\/pre><\/div><\/div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">loop<\/code> is the header block and <code class=\"language-plaintext highlighter-rouge\">then.1<\/code> is the nested if block, and\nwith BlockFrequencyInfo\u2019s frequency we get a probability of 8\/32 =\n0.25. So we would expect <code class=\"language-plaintext highlighter-rouge\">then.1<\/code>\u2019s scalar cost to be divided by 4:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>...\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %iv = phi i32 [ 0, %entry ], [ %iv.next, %latch ]\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %c0, label %then.0, label %latch\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %c1, label %then.1, label %latch\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %gep0 = getelementptr i32, ptr %p0, i32 %iv\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %x = load i32, ptr %gep0, align 4\nLV: Found an estimated cost of 0 for VF 1 For instruction:   %gep1 = getelementptr i32, ptr %p1, i32 %x\nLV: Found an estimated cost of 1 for VF 1 For instruction:   store i32 0, ptr %gep1, align 4\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br label %latch\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %iv.next = add i32 %iv, 1\nLV: Found an estimated cost of 1 for VF 1 For instruction:   %done = icmp eq i32 %iv.next, 1024\nLV: Found an estimated cost of 0 for VF 1 For instruction:   br i1 %done, label %exit, label %loop\nLV: Scalar loop costs: 2.\n...\nCost for VF vscale x 4: 17 (Estimated cost per lane: 2.1)\n...\nLV: Selecting VF: 1.\nLV: Vectorization is possible but not beneficial.\n<\/code><\/pre><\/div><\/div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">then.1<\/code>s scalar cost is now 2\/4 = 0, so the total cost of the\nscalar loop is now 2 and the loop vectorizer no longer decides to\nvectorize. If we try this out on 538.deepsjeng_r, we can see that it\nno longer vectorizes that loop in <code class=\"language-plaintext highlighter-rouge\">qsearch<\/code> either. Success!<\/p>\n\n<p><img src=\"http:\/\/lukelau.me\/assets\/531.deepsjeng_r-after.png\" alt=\"Screenshot of LNT showing a 6.82% improvement on 531.deepsjeng_r\" \/><\/p>\n\n<p>Running it again on LNT showed a ~7% speedup in execution time. Not\njust as fast as GCC yet, but a welcome improvement for only a handful\nof lines of code.<\/p>\n\n<h2 id=\"upstreaming\">Upstreaming<\/h2>\n\n<p>Now that we know the fix we want to land, we can start to think about\nhow we want to upstream this into LLVM.<\/p>\n\n<p>If we run <code class=\"language-plaintext highlighter-rouge\">llvm-lit --update-tests\nllvm\/test\/Transforms\/LoopVectorize<\/code>, we actually get quite a few\nunexpected test changes. One of the side effects of using\nBlockFrequencyInfo is that <a href=\"https:\/\/github.com\/llvm\/llvm-project\/pull\/160449\">tail folded loops no longer discount the\nscalar loop if it wasn\u2019t predicated to begin\nwith<\/a>. A tail folded\nloop is a loop where the scalar epilogue is folded into the vector loop itself by predicating the vector operations:<\/p>\n\n<div class=\"language-c highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ non-tail folded loop:<\/span>\n<span class=\"c1\">\/\/ process as many VF sized vectors that fit in n<\/span>\n<span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">n<\/span> <span class=\"o\">-<\/span> <span class=\"p\">(<\/span><span class=\"n\">n<\/span> <span class=\"o\">%<\/span> <span class=\"n\">VF<\/span><span class=\"p\">);<\/span> <span class=\"n\">i<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">VF<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">x<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">..<\/span><span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"n\">VF<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">y<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">..<\/span><span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"n\">VF<\/span><span class=\"p\">];<\/span>\n<span class=\"c1\">\/\/ process the remaining n % VF scalar elements<\/span>\n<span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">n<\/span> <span class=\"o\">-<\/span> <span class=\"p\">(<\/span><span class=\"n\">n<\/span> <span class=\"o\">%<\/span> <span class=\"n\">VF<\/span><span class=\"p\">);<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">n<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">x<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">y<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">];<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"language-c highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ tail folded loop:<\/span>\n<span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">n<\/span><span class=\"p\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">VF<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">x<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">..<\/span><span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"n\">VF<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">y<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">..<\/span><span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"n\">VF<\/span><span class=\"p\">]<\/span> <span class=\"n\">mask<\/span><span class=\"o\">=<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span> <span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span> <span class=\"p\">...,<\/span> <span class=\"n\">i<\/span><span class=\"o\">+<\/span><span class=\"n\">VF<\/span><span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">n<\/span><span class=\"p\">];<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>However because this block is technically predicated due to the mask\non the vector instructions, the loop vectorizer applied\n<code class=\"language-plaintext highlighter-rouge\">getPredBlockCostDivisor<\/code> to the scalar loop cost even if the original\nscalar loop had no control flow in its body. BlockFrequencyInfo here\ncan detect that if the block had no control flow, its probability of\nbeing executed is 1 and so the scalar loop cost isn\u2019t made cheaper\nthan it needs to be. I split off and landed this change separately,\n<a href=\"http:\/\/lukelau.me\/2024\/07\/17\/how-to-land-a-change-to-llvm-in-20-easy-patches.html\">since it makes the test changes easier to review<\/a>.<\/p>\n\n<p>Now that the remaining changes in <code class=\"language-plaintext highlighter-rouge\">llvm\/test\/Transforms\/LoopVectorize<\/code>\nlooked more contained, I was almost ready to open a pull request. I\njust wanted to quickly kick the tyres on\n<a href=\"https:\/\/github.com\/llvm\/llvm-test-suite\">llvm-test-suite<\/a> with a few\nother targets, since this wasn\u2019t a RISC-V specific change. The plan\nwas to quickly collect some stats on how many loops were vectorized,\ncheck for any anomalies when compared to beforehand, and then be on\nour way:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>$ cd llvm-test-suite\n$ ninja -C build\n...\n[222\/7278] Building C object External\/...nchspec\/CPU\/500.perlbench_r\/src\/pp.c.o\nFAILED: External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o \n\/root\/llvm-test-suite\/build.x86_64-ReleaseLTO-a\/tools\/timeit --summary External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o.time \/root\/llvm-project\/build\/bin\/clang -DDOUBLE_SLASHES_SPECIAL=0 -DNDEBUG -DPERL_CORE -DSPEC -DSPEC_AUTO_BYTEORDER=0x12345678 -DSPEC_AUTO_SUPPRESS_OPENMP -DSPEC_CPU -DSPEC_LINUX -DSPEC_LINUX_X64 -DSPEC_LP64 -DSPEC_SUPPRESS_OPENMP -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/dist\/IO -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/cpan\/Time-HiRes -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/cpan\/HTML-Parser -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/ext\/re -I\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/specrand -march=x86-64-v3 -save-temps=obj     -O3 -fomit-frame-pointer -flto -DNDEBUG   -w -Werror=date-time -save-stats=obj -save-stats=obj -fno-strict-aliasing -MD -MT External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o -MF External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o.d -o External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o -c \/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c\nPLEASE submit a bug report to https:\/\/github.com\/llvm\/llvm-project\/issues\/ and include the crash backtrace, preprocessed source, and associated run script.\nStack dump:\n0.      Program arguments: \/root\/llvm-project\/build\/bin\/clang-19 -cc1 -triple x86_64-unknown-linux-gnu -O3 -emit-llvm-bc -flto=full -flto-unit -save-temps=obj -disable-free -clear-ast-before-backend -main-file-name pp.c -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=none -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64-v3 -debugger-tuning=gdb -fdebug-compilation-dir=\/root\/llvm-test-suite\/build.x86_64-ReleaseLTO-a -fcoverage-compilation-dir=\/root\/llvm-test-suite\/build.x86_64-ReleaseLTO-a -resource-dir \/root\/llvm-project\/build\/lib\/clang\/23 -Werror=date-time -w -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -vectorize-loops -vectorize-slp -stats-file=External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.stats -faddrsig -fdwarf2-cfi-asm -o External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.c.o -x ir External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.bc\n1.      Optimizer\n2.      Running pass \"function&lt;eager-inv&gt;(float2int,lower-constant-intrinsics,chr,loop(loop-rotate&lt;header-duplication;prepare-for-lto&gt;,loop-deletion),loop-distribute,inject-tli-mappings,loop-vectorize&lt;no-interleave-forced-only;no-vectorize-forced-only;&gt;,infer-alignment,loop-load-elim,instcombine&lt;max-iterations=1;no-verify-fixpoint&gt;,simplifycfg&lt;bonus-inst-threshold=1;forward-switch-cond;switch-range-to-icmp;switch-to-arithmetic;switch-to-lookup;no-keep-loops;hoist-common-insts;no-hoist-loads-stores-with-cond-faulting;sink-common-insts;speculate-blocks;simplify-cond-branch;no-speculate-unpredictables&gt;,slp-vectorizer,vector-combine,instcombine&lt;max-iterations=1;no-verify-fixpoint&gt;,loop-unroll&lt;O3&gt;,transform-warning,sroa&lt;preserve-cfg&gt;,infer-alignment,instcombine&lt;max-iterations=1;no-verify-fixpoint&gt;,loop-mssa(licm&lt;allowspeculation&gt;),alignment-from-assumptions,loop-sink,instsimplify,div-rem-pairs,tailcallelim,simplifycfg&lt;bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;switch-to-arithmetic;no-switch-to-lookup;keep-loops;no-hoist-common-insts;hoist-loads-stores-with-cond-faulting;no-sink-common-insts;speculate-blocks;simplify-cond-branch;speculate-unpredictables&gt;)\" on module \"External\/SPEC\/CINT2017rate\/500.perlbench_r\/CMakeFiles\/500.perlbench_r.dir\/root\/cpu2017\/benchspec\/CPU\/500.perlbench_r\/src\/pp.bc\"\n3.      Running pass \"loop-vectorize&lt;no-interleave-forced-only;no-vectorize-forced-only;&gt;\" on function \"Perl_pp_coreargs\"\n #0 0x0000556ff93ab158 llvm::sys::PrintStackTrace(llvm::raw_ostream&amp;, int) (\/root\/llvm-project\/build\/bin\/clang-19+0x2d5c158)\n #1 0x0000556ff93a8835 llvm::sys::RunSignalHandlers() (\/root\/llvm-project\/build\/bin\/clang-19+0x2d59835)\n #2 0x0000556ff93abf01 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0\n #3 0x00007f305ce49df0 (\/lib\/x86_64-linux-gnu\/libc.so.6+0x3fdf0)\n #4 0x0000556ffaa0dbfb llvm::LoopVectorizationCostModel::expectedCost(llvm::ElementCount) (\/root\/llvm-project\/build\/bin\/clang-19+0x43bebfb)\n #5 0x0000556ffaa22a0d llvm::LoopVectorizationPlanner::computeBestVF() (\/root\/llvm-project\/build\/bin\/clang-19+0x43d3a0d)\n #6 0x0000556ffaa36f3b llvm::LoopVectorizePass::processLoop(llvm::Loop*) (\/root\/llvm-project\/build\/bin\/clang-19+0x43e7f3b)\n #7 0x0000556ffaa413eb llvm::LoopVectorizePass::runImpl(llvm::Function&amp;) (\/root\/llvm-project\/build\/bin\/clang-19+0x43f23eb)\n ...\n...\n<\/code><\/pre><\/div><\/div>\n\n<p>A crash when building for X86. No assertion message, but a backtrace\nthat points to the loop vectorizer cost model. Unfortunately this did\nnot turn out to be simple to debug and instead turned into a whole\nother ordeal, so I\u2019ll leave the details of that rabbit hole to the\nnext post. But in the meantime, here are some hints if you want to\nguess what went wrong:<\/p>\n\n<ul>\n  <li>The crash stems from a SIGFPE signal<\/li>\n  <li>It only occurs when building on X86. Building on AArch64 is\nunaffected, even when cross-compiling to X86<\/li>\n  <li>It only occurs with LTO<\/li>\n<\/ul>\n\n<p>Hopefully this also gives a bit of insight into the type of upstream\nwork that we carry out at <a href=\"https:\/\/www.igalia.com\">Igalia<\/a>. If you\nhave an LLVM or RISC-V project that we could help with, <a href=\"mailto:luke@igalia.com\">feel free to\nreach out<\/a>.<\/p>\n<div class=\"footnotes\">\n  <ol>\n    <li id=\"fn:scalar-vplan\">\n      <p>The scalar loop is also modeled in VPlan, but\ncurrently costed with the legacy cost model and not the VPlan\nitself. This is another <a href=\"https:\/\/github.com\/llvm\/llvm-project\/blob\/a51eab9492c9d79f5717975bfa659d91e27985a3\/llvm\/lib\/Transforms\/Vectorize\/LoopVectorize.cpp#L7182\">load bearing TODO<\/a>.\u00a0<a href=\"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2.html#fnref:scalar-vplan\" class=\"reversefootnote\">&#8617;<\/a><\/p>\n    <\/li>\n    <li id=\"fn:vplan-native\">\n      <p>Whilst not enabled default, there is experimental\nsupport for outer loop vectorization in the VPlan native path.\u00a0<a href=\"http:\/\/lukelau.me\/2026\/01\/26\/closing-the-gap-pt2.html#fnref:vplan-native\" class=\"reversefootnote\">&#8617;<\/a><\/p>\n    <\/li>\n  <\/ol>\n<\/div>                ","author":{"name":"Luke Lau","uri":"http:\/\/lukelau.me\/"}},{"title":"Igalia Compilers Team: Legacy RegExp features in JavaScript","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/"}},"id":"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/","updated":"2026-01-20T00:00:00+00:00","content":"\n<p>In June 2025, I joined the <a href=\"https:\/\/www.igalia.com\/coding-experience\/\">Igalia Coding Experience<\/a> program. My role was to implement the TC39 proposal <a href=\"https:\/\/github.com\/tc39\/proposal-regexp-legacy-features\">Legacy RegExp Features<\/a> in SpiderMonkey, the JavaScript engine in Mozilla Firefox. This wasn't my first proposal implementation. I'd already implemented the <a href=\"https:\/\/tc39.es\/proposal-is-error\/#sec-fundamental-objects\">Error.isError<\/a> and <a href=\"https:\/\/spidermonkey.dev\/blog\/2025\/03\/05\/iterator-range.html\">Iterator.range<\/a> TC39 proposals in SpiderMonkey, but implementing the Legacy RegExp Features proposal involved delving deeper into the Mozilla codebase, and new challenges for me.<\/p>\n<p>To begin with, I created an implementation plan with a timeline of how I was going to approach the proposal. Additionally, I added links to the codebase where I thought I was going to make changes as per the specification, which helped me have a clear starting point and path for integrating the feature. It also meant I could get feedback from SpiderMonkey developers before actually beginning the implementation.<\/p>\n<p>The Legacy RegExp features proposal disables legacy static properties and RegExp.prototype.compile for instances of proper subclasses of RegExp as well as for cross-realm regexps.<\/p>\n<p>The following operations are modified in SpiderMonkey:<\/p>\n<h3 id=\"regexp-prototype-compile-pattern-flags\" tabindex=\"-1\">RegExp.prototype.compile(pattern, flags) <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>This method reinitializes an existing RegExp object with a new pattern and\/or flags. It modifies the RegExp object in place rather than creating a new one.<\/p>\n<p><strong>Modification:<\/strong> The proposal modifies <code>RegExp.prototype.compile<\/code> to throw errors for objects that are not direct instances of the RegExp as well as for cross-realm mismatches. The <code>compile()<\/code> method initializes a RegExp object similar to the way a RegExp literal is created, bypassing any preprocessing of the pattern that might be done by a RegExp subclass's constructor, and potentially breaking a subclass's custom &quot;exec&quot; method. Thus, compile is disallowed for subclasses. It is now forbidden for a RegExp compile method to be applied to a RegExp object belonging to a different realm, as this would typically result in static properties of the incorrect realm being updated.<\/p>\n<p>Example of newly restricted behaviour:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token punctuation\">(<\/span>base<span class=\"token punctuation\">)<\/span> $ .\/mach run<br \/> <span class=\"token number\">0<\/span>:00.29 \/Users\/default\/firefox\/obj-aarch64-apple-darwin25.2.0\/dist\/bin\/js<br \/>js<span class=\"token operator\">><\/span> <span class=\"token builtin class-name\">let<\/span> g <span class=\"token operator\">=<\/span> newGlobal<span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>js<span class=\"token operator\">><\/span> <span class=\"token builtin class-name\">let<\/span> re <span class=\"token operator\">=<\/span> g.RegExp<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"x\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>js<span class=\"token operator\">><\/span> RegExp.prototype.compile.call<span class=\"token punctuation\">(<\/span>re<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/>typein:3:26 TypeError: RegExp operation not permitted on object from different realm<br \/>Stack:<br \/>  @typein:3:26<br \/>js<span class=\"token operator\">><\/span><\/code><\/pre>\n<p>To explain each line of the JavaScript code in detail:<\/p>\n<ul>\n<li>\n<p><code>let g<\/code> = <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/shell\/js.cpp#7449\"><code>newGlobal()<\/code><\/a> creates a new JavaScript global object in SpiderMonkey, similar to opening a new window in a browser. Each global object has its own realm.\nA realm is a JavaScript execution context that contains its own set of global objects and built-in functions. Every object in SpiderMonkey has a realm pointer which identifies which realm it belongs to.<\/p>\n<\/li>\n<li>\n<p><code>let re = g.RegExp(\u201cx\u201d)<\/code> creates a new RegExp object from <code>g<\/code>'s realm, with a distinct instance of the RegExp constructor. Although the object behaves like one created from <code>RegExp(&quot;x&quot;)<\/code>, the two are not wholly compatible with one another.<\/p>\n<\/li>\n<li>\n<p><code>RegExp.prototype.compile.call(re)<\/code> invokes the <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/e1eada69e2ddd86a398ccb141dcbf772254162eb\/js\/src\/builtin\/RegExp.cpp\"><code>compile()<\/code><\/a> method with the regexp initialized above for a realm returned from newGlobal(). Per <a href=\"https:\/\/github.com\/tc39\/proposal-regexp-legacy-features?tab=readme-ov-file#regexpprototypecompile--pattern-flags-\">step 5<\/a> of the modified <code>RegExp.prototype.compile()<\/code> algorithm in the proposal, this results in a <code>TypeError<\/code> exception being thrown.<\/p>\n<\/li>\n<\/ul>\n<p>Initially, I added my changes in <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/builtin\/RegExp.cpp#582\"><code>regexp_compile_impl()<\/code><\/a>, but when testing with <code>.\/mach try auto<\/code>, the feature failed test262 cross-realm tests when run with the <code>ion eager<\/code> and <code>--more-compartments<\/code> flag. Debug output showed that when invoking the RegExp.prototype.compile(re)<code> both the receiver or (<\/code>this`)  of the RegExp.prototype.compile() method, and the RegExp object were in the same realm while they weren\u2019t. In other words, the cross-realm check was passing, when it should have been failing, according to the test expectations.<\/p>\n<p>By the time execution reached <code>regexp_compile()<\/code>, the <code>CallNonGenericMethod&lt;IsRegExpObject, regexp_compile_impl&gt;<\/code> wrapper had already processed the &quot;receiver&quot; or &quot;this&quot; of the compile method. According to the <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/public\/CallNonGenericMethod.h#88-100\">CallNonGenericMethod documentation<\/a>, if <code>args.thisv()<\/code> is not of the correct type, it will attempt to unwrap <code>this<\/code> and if successful, call the implementation function on the unwrapped <code>this<\/code>. For a bit of context on this, SpiderMonkey has a concept of <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/public\/Wrapper.h#120-133\">Wrapper<\/a> objects, which decorate an object in a sort of proxy membrane to provide security boundary enforcement. For instance, ensuring that a method can be invoked or a field can be written to from the presently entered compartment. Unwrapping an object means removing that proxy membrane, to access the actual object, similar to how you\u2019d unwrap a gift. This can be done using <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/proxy\/Wrapper.cpp#353\"><code>js::CheckedUnwrapStatic()<\/code><\/a>.<\/p>\n<p>With <code>--more-compartments<\/code>, <code>CallNonGenericMethod<\/code> in <code>regexp_compile()<\/code> was automatically unwrapping cross-compartment proxies through <code>CallMethodIfWrapped <\/code>before calling <code>regexp_compile_impl()<\/code>.<\/p>\n<p>This unwrapping process also switched the JSContext to the target object's realm. This meant that by the time my realm checks executed in <code>regexp_compile_impl()<\/code>, both <code>cx-&gt;realm()<\/code> and the RegExp object's realm pointed to the same realm (the object's home realm), making them appear equal even in genuine cross-realm call scenarios where the original call came from a different realm.<\/p>\n<p>So I moved the same-realm testing and [[LegacyFeaturesEnabled]] bit testing to <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/builtin\/RegExp.cpp#647\"><code>regexp_compile()<\/code><\/a>, just before <code>CallNonGenericMethod<\/code> is called and added <code>js::CheckedUnwrapStatic()<\/code> to unwrap any proxy wrappers before checking the realm. This ensures we\u2019re checking the realm of the actual RegExp object and not the compartment wrappers around it.<\/p>\n<h3 id=\"subclass-instances\" tabindex=\"-1\">Subclass Instances <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>As mentioned above, the RegExp method <code>RegExp.prototype.compile()<\/code> re-initializes a RegExp using a newly created matcher for the specified pattern and flags. The proposal adds some restrictions to this which prevent oddities such as subclasses not functioning as expected (for instance, by not preprocessing the pattern and adding context used by their <code>exec()<\/code> implementation). More importantly, when applied to a cross-realm object, this would result in execution modifying the static RegExp members for the incorrect realm.<\/p>\n<p>The proposal modifies the behavior so that legacy static properties are only updated when direct instances of the built-in RegExp constructor are used, not subclass instances or cross-realm objects, using similar logic to <code>RegExp.prototype.compile()<\/code>:<\/p>\n<blockquote>\n<ol start=\"7\">\n<li>If SameValue(thisRealm, rRealm) is true, then\n<ul>\n<li>i. If the value of R\u2019s [[LegacyFeaturesEnabled]] internal slot is true, then\n<ul>\n<li>a. Perform UpdateLegacyRegExpStaticProperties(%RegExp%, S, lastIndex, e, capturedValues).<\/li>\n<\/ul>\n<\/li>\n<li>ii. Else,\n<ul>\n<li>a. Perform InvalidateLegacyRegExpStaticProperties(%RegExp%).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<\/blockquote>\n<p>The properties are specced and implemented as accessors with a getter and no setter, except for <code>RegExp.input<\/code> (and its alias <code>RegExp.$_<\/code>), which remains writable. Inside each of the accessors, if the receiver <code>this<\/code> and the %RegExp% realm intrinsic (the standard RegExp constructor) are not the same, we throw a <code>TypeError<\/code>.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token punctuation\">(<\/span>base<span class=\"token punctuation\">)<\/span> $ .\/mach run<br \/> <span class=\"token number\">0<\/span>:00.28 \/Users\/default\/firefox\/obj-aarch64-apple-darwin25.2.0\/dist\/bin\/js<br \/>js<span class=\"token operator\">><\/span> \/a<span class=\"token punctuation\">(<\/span>b<span class=\"token punctuation\">)<\/span>c\/.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"abc\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <br \/><span class=\"token punctuation\">[<\/span><span class=\"token string\">\"abc\"<\/span>, <span class=\"token string\">\"b\"<\/span><span class=\"token punctuation\">]<\/span><br \/>js<span class=\"token operator\">><\/span> RegExp.<span class=\"token variable\">$1<\/span> <br \/><span class=\"token string\">\"b\"<\/span><br \/>js<span class=\"token operator\">><\/span> new RegExp<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"a(b)\"<\/span><span class=\"token punctuation\">)<\/span>.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"ab\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <br \/><span class=\"token punctuation\">[<\/span><span class=\"token string\">\"ab\"<\/span>, <span class=\"token string\">\"b\"<\/span><span class=\"token punctuation\">]<\/span><br \/>js<span class=\"token operator\">><\/span> RegExp.<span class=\"token variable\">$1<\/span>                       <br \/><span class=\"token string\">\"b\"<\/span><br \/>js<span class=\"token operator\">><\/span> new <span class=\"token punctuation\">(<\/span>class extends RegExp <span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"a(b)\"<\/span><span class=\"token punctuation\">)<\/span>.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"ab\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> <br \/><span class=\"token punctuation\">[<\/span><span class=\"token string\">\"ab\"<\/span>, <span class=\"token string\">\"b\"<\/span><span class=\"token punctuation\">]<\/span><br \/>js<span class=\"token operator\">><\/span> RegExp.<span class=\"token variable\">$1<\/span>                                          <br \/>typein:6:1 TypeError: RegExp static property <span class=\"token string\">'static_paren1_getter'<\/span> is invalid<br \/>Stack:<br \/>  @typein:6:1<br \/>js<span class=\"token operator\">><\/span> <\/code><\/pre>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">\/a<span class=\"token punctuation\">(<\/span>b<span class=\"token punctuation\">)<\/span>c\/.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"abc\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> RegExp.<span class=\"token variable\">$1<\/span>  \/\/ should <span class=\"token builtin class-name\">return<\/span> <span class=\"token string\">\"b\"<\/span><br \/>new RegExp<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"a(b)\"<\/span><span class=\"token punctuation\">)<\/span>.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"ab\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> RegExp.<span class=\"token variable\">$1<\/span>  \/\/ <span class=\"token string\">\"b\"<\/span><br \/>new <span class=\"token punctuation\">(<\/span>class extends RegExp <span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"a(b)\"<\/span><span class=\"token punctuation\">)<\/span>.exec<span class=\"token punctuation\">(<\/span><span class=\"token string\">\"ab\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> RegExp.<span class=\"token variable\">$1<\/span> \/\/  throws<\/code><\/pre>\n<h3 id=\"normalisation-of-regexp-static-properties\" tabindex=\"-1\">Normalisation of RegExp Static Properties <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>RegExp static properties are now defined as configurable and non-enumerable. This is so that the associated features may be easily removed by using the JavaScript <code>delete<\/code> operator. This is important for consistency with modern ECMA262 and for allows for applications to further reduce the number of side-affect producing globals, including VM native methods.<\/p>\n<p>In SpiderMonkey, the legacy static properties are defined <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/builtin\/RegExp.cpp#1499-1552\">in RegExp.cpp<\/a>. To implement the proposal, I enclosed the properties with a NIGHTLY_BUILD directive, removing the <code>JS_PROP_PERMANEN<\/code> and <code>JS_PROP_ENUMERATE<\/code> flags to make them configurable and non-enumerable for the Nightly environment, where they can be tested by the community. Outside of Nightly, we continue supporting the old implementation for beta\/release environments.<\/p>\n<p>Then, I updated the test262 AnnexB RegExp tests to support the change and to limit the tests to Nightly.<\/p>\n<h2 id=\"understanding-the-implementation-challenges-and-solutions\" tabindex=\"-1\">Understanding the Implementation: Challenges and Solutions <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h2>\n<h3 id=\"1-creative-bit-packing\" tabindex=\"-1\">1. Creative Bit Packing <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>Once the legacy RegExp statics were normalised, the next step was adding a <code>LegacyFeaturesEnabled<\/code> internal slot. This slot keeps a reference to its constructor and is checked whenever legacy features are accessed. If the <code>RegExp<\/code> is a subclass instance or is is associated with a different realm, the slot indicates that legacy features should throw an error.<\/p>\n<p>Initially, I added the slot to the <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/vm\/RegExpObject.h#43\">RegExpObject <\/a>:<\/p>\n<pre class=\"language-cpp\" tabindex=\"0\"><code class=\"language-cpp\"><span class=\"token keyword\">static<\/span> <span class=\"token keyword\">const<\/span> <span class=\"token keyword\">unsigned<\/span> LEGACY_FEATURES_ENABLED_SLOT <span class=\"token operator\">=<\/span> <span class=\"token number\">3<\/span><span class=\"token punctuation\">;<\/span> <\/code><\/pre>\n<p>This presented a couple of problems for me:<\/p>\n<ul>\n<li>\n<p>The number of reserved slots must match the allocation kind defined in <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/gc\/AllocKind.h#60\">FOR_EACH_OBJECT_ALLOCKIND(D)<\/a>. The number of reserved slots increased to 5, which meant that I had to choose between OBJECT6 or OBJECT8. During implementation, I somehow missed OBJECT6 and went with OBJECT8.<\/p>\n<\/li>\n<li>\n<p>I knew that I\u2019d get some pushback in code review, as my changes increased the size of the RegExp Object by 32 bytes (four 8-byte slots). I could see that there was a way for <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/public\/RegExpFlags.h\">Boolean flags to share a slot<\/a> but I didn't know how to implement my changes without breaking the JIT.<\/p>\n<\/li>\n<\/ul>\n<p>I decided to leave the implementation as is and wait for SpiderMonkey engineers \/ reviewers to give me feedback and their preference on how to add the Boolean.<\/p>\n<p>During code review, my reviewer Iain pointed out that since we\u2019re only storing a single bit of information (whether legacy features are enabled or not), and the existing <code>FLAGS_SLOT<\/code> only uses 8 bits, I could store the legacy features in the unused higher bits.<\/p>\n<p>The slot implementation includes a getter, <code>bool legacyFeaturesEnabled()<\/code>, that reads the bit from the <code>FLAGS_SLOT<\/code>; and a setter, <code>setLegacyFeaturesEnabled(bool)<\/code>, that writes the bit to the <code>FLAGS_SLOT<\/code>.<\/p>\n<p>The new approach involved defining some constants based on the size of RegExp Flags so that the code keeps working if RegExpFlags gets bigger in future:<\/p>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\"><span class=\"token keyword\">static<\/span> <span class=\"token keyword\">const<\/span> size_t RegExpFlagsMask <span class=\"token operator\">=<\/span> <span class=\"token constant\">JS<\/span><span class=\"token operator\">:<\/span><span class=\"token operator\">:<\/span>RegExpFlag<span class=\"token operator\">:<\/span><span class=\"token operator\">:<\/span>AllFlags<span class=\"token punctuation\">;<\/span><br \/><span class=\"token keyword\">static<\/span> <span class=\"token keyword\">const<\/span> size_t LegacyFeaturesEnabledBit <span class=\"token operator\">=<\/span> <span class=\"token function\">Bit<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">8<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><br \/><span class=\"token function\">static_assert<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">(<\/span>RegExpFlagsMask <span class=\"token operator\">&amp;<\/span> LegacyFeaturesEnabledBit<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">==<\/span> <span class=\"token number\">0<\/span><span class=\"token punctuation\">,<\/span><br \/>              <span class=\"token string\">\"LegacyFeaturesEnabledBit must not overlap\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p><a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/vm\/RegExpObject.h#65\">RegExpFlagsMask<\/a> has a bit set to 1 if that bit is part of the RegExpFlags, and 0 otherwise. The lowest 8 bits are currently set to other RegExp flags, which leaves us with the highest bits to pack our slot in.<\/p>\n<p>We perform two operations: <code>raw &amp; RegExpFlagsMask<\/code>, which gets only the traditional RegExp flags; and <code>raw &amp; ~RegExpFlagsMask<\/code>, which gets everything apart from the RegExp flags.Those are bits 0-7. We use bit 8 to store <code>LegacyFeaturesEnabled<\/code>.\nWhen we read the flags, we mask off any bits that are not part of the RegExpFlags.<\/p>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\"><span class=\"token keyword\">return<\/span> <span class=\"token constant\">JS<\/span><span class=\"token operator\">:<\/span><span class=\"token operator\">:<\/span><span class=\"token function\">RegExpFlags<\/span><span class=\"token punctuation\">(<\/span>raw <span class=\"token operator\">&amp;<\/span> RegExpFlagsMask<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>When we write to the flags, we combine the new value of the RegExpFlags bits <code>(flags.value())<\/code> with the old value of the other bits in <code>(raw &amp; RegExpFlagsMask)<\/code>.<\/p>\n<pre class=\"language-js\" tabindex=\"0\"><code class=\"language-js\">uint32_t newValue <span class=\"token operator\">=<\/span> flags<span class=\"token punctuation\">.<\/span><span class=\"token function\">value<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">|<\/span> <span class=\"token punctuation\">(<\/span>raw <span class=\"token operator\">&amp;<\/span> <span class=\"token operator\">~<\/span>RegExpFlagsMask<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><br \/><span class=\"token function\">setFixedSlot<\/span><span class=\"token punctuation\">(<\/span><span class=\"token constant\">FLAGS_SLOT<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token function\">Int32Value<\/span><span class=\"token punctuation\">(<\/span>newValue<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span><\/code><\/pre>\n<p>When we read the <code>LegacyFeaturesEnabledBit<\/code>, we check if it\u2019s set. When we write it, we take the existing raw value and either set or clear the <code>LegacyFeaturesEnabledBit<\/code>.<\/p>\n<h3 id=\"2-lazy-evaluation\" tabindex=\"-1\">2. Lazy Evaluation <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>The proposal specifies RegExp properties as internal slots of the RegExp Object, and the abstract operations <code>UpdateLegacyRegExpStaticProperties (C, S, startIndex, endIndex, capturedValues)<\/code> and <code>InvalidateLegacyRegExpStaticProperties(C)<\/code> were initially confusing. The confusion came from a specification detail: we need to eagerly update the properties at a specific point in time, as opposed to SpiderMonkey\u2019s lazily evaluated implementation.<\/p>\n<p>It was the first time I had come across lazy evaluation and thought, naively, that it would be possible to change the implementation to eagerly update static properties after a successful match. This didn't work for a few reasons.<\/p>\n<p>First, lazy evaluation is heavily embedded in the JIT, so the idea of just changing that was\u2026 ambitious. Second, lazy evaluation is a way to defer regexp evaluation until RegExp properties are accessed. Third, there\u2019s no observable difference to the end user whether the RegExp properties were lazily or eagerly evaluated. Lastly, internal slots are a way for ECMA262 to describe the internal state of the object.<\/p>\n<p>So, <code>UpdateLegacyRegExpStaticProperties (C, S, startIndex, endIndex, capturedValues)<\/code> wasn\u2019t needed, as it codifies already existing behaviour in SpiderMonkey. For <code>InvalidateLegacyRegExpStaticProperties(C)<\/code>, my mentor suggested implementing it as a <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/vm\/RegExpStatics.h#17\">boolean flag in RegExpStatics<\/a>.<\/p>\n<p>When a subclass or cross-realm regexp executes, this flag is set to true, preventing legacy static properties from being accessed. The flag is cleared after normal RegExp executions, allowing legacy features to work for standard RegExp instances.<\/p>\n<p>Because <code>InvalidateLegacyRegExpStaticProperties(C)<\/code> marks the values of the static properties as unavailable by setting the internal slots to empty, in step 4 of the accessors <code>GetLegacyRegExpStaticProperty(C, thisValue, internalSlotName)<\/code>, we throw a TypeError if the static properties are invalidated.<\/p>\n<p>Then, we add the equivalent code in the <a href=\"https:\/\/searchfox.org\/firefox-main\/rev\/6ece603789f6751c37c48b23f39dbbb16b290592\/js\/src\/jit\/CodeGenerator.cpp#2229-2243\">JIT path<\/a> and so that when a regexp is executed, we lazily store enough information to be able to rerun the regexp later if the RegExpStatics are accessed.<\/p>\n<h3 id=\"3-gating-the-implementation-behind-a-preference\" tabindex=\"-1\">3. Gating the implementation behind a preference <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>The first step to implementing a TC39 proposal in SpiderMonkey is adding a preference for it. This allows the feature to be enabled or disabled at runtime, which is important in gating the feature until it has been tested enough for release.<\/p>\n<p>With this proposal, it was awkward, because this was not a new syntax or library method, but behavioral modifications to the existing RegExp static properties and the <code>compile()<\/code> method.<\/p>\n<p>At first, I enclosed my changes in an <code>#ifdef NIGHTLY_BUILD<\/code> directive so that they are only available in the nightly environment. But given the potential for web compatibility risks, we needed to put the changes behind a preference. That way, we can flip the feature back in case we break something.<\/p>\n<p>This created an awkward situation: the static RegExp properties themselves (like <code>RegExp.$1, RegExp.input<\/code>) are defined in <code>regexp_static_props<\/code>, which is baked into the static RegExp JSClass and embedded in the binary at compile time. I ended up wrapping these property definitions in an <code>#ifdef NIGHTLY_BUILD<\/code>, meaning they only exist in Nightly builds.<\/p>\n<p>But the behavior of these properties \u2014 that is, whether accessing them should throw errors for subclasses and cross-realm regexps \u2014 is gated behind a runtime preference. This is even more awkward, because it will change behaviour in Nightly even without the preference enabled.<\/p>\n<p>Thus, the preference only controls whether the new throwing behavior is active. As Iain noted, there wasn't a particularly clean way to avoid this. We'd need two parallel RegExp classes and then have to switch between them at runtime based on the pref, which seemed like overkill.<\/p>\n<p>The compromise was to ship the properties in Nightly, use the preference to control the new behavior, and rely on extra-careful testing.<\/p>\n<h3 id=\"4-wild-goose-chase\" tabindex=\"-1\">4. Wild Goose Chase <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>Around August, when I had the initial implementation working without memory optimization or centralized legacy and realm checks, I was updating legacy regexp statics in <code>RegExpBuiltinExec()<\/code> only when matches succeeded.<\/p>\n<p><code>RegExpBuiltinExec()<\/code> has two execution paths: a <code>forTest<\/code> path for <code>RegExp.prototype.test<\/code> (where we can skip allocating a result object) and a normal path for full execution. I had legacy feature validation in both paths, but only for successful matches.<\/p>\n<p>My mentor suggested we needed to update the legacy regexp statics not just on success, but also on failure. That made sense from a spec perspective, so I spent the next week and a half trying to figure out how to implement this. I was looking into the execution paths, trying to understand where and how to trigger updates on failed matches.<\/p>\n<p>After about a week, we realized that they had misread the proposal! Oops. Turns out, SpiderMonkey doesn't update legacy regexp properties on failure at all: it just returns the last successful result. I'd been chasing a solution to a problem that didn't actually exist in the implementation.<\/p>\n<h3 id=\"next-steps-and-final-thoughts\" tabindex=\"-1\">Next Steps and Final Thoughts <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p>The &quot;Legacy RegExp features in JavaScript&quot; proposal is, at the time of this writing, in <a href=\"https:\/\/tc39.es\/process-document\/\">stage 3<\/a> of the TC39 process, meaning the proposal is stable and no further changes can be made to it. There are potential backward compatibility risks and any attempt to use a disabled feature will throw a Type Error. More on that can be found in <a href=\"https:\/\/github.com\/tc39\/proposal-regexp-legacy-features\/blob\/918a4b09723b34e4f857f10b4576028a8a02e97d\/web-breaking-hazards.md\">the Breaking Hazards portion of the proposal<\/a>.<\/p>\n<p>Before implementing this proposal I had briefly interacted with C++ on a production level codebase when working on the Error.isError proposal, but working on legacy RegExp properties was a deeper dive into C++ and browser internals, which was difficult but also very much appreciated!<\/p>\n<p>Working on this proposal exposed gaps in my knowledge but also gave me confidence in navigating large C++ codebases. I\u2019m particularly grateful to my mentor, and Daniel Minor and Iain Ireland (from the SpiderMonkey team) for pointing me in the right direction and brainstorming solutions with me.<\/p>\n<h3 id=\"you-may-also-like\" tabindex=\"-1\">You may also like: <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/compilers\/2026\/01\/20\/legacy-regexp-features-in-javascript\/\">#<\/a><\/h3>\n<p><a href=\"https:\/\/hacks.mozilla.org\/2020\/06\/a-new-regexp-engine-in-spidermonkey\/\">A New RegExp Engine in SpiderMonkey<\/a><\/p>\n<p><a href=\"https:\/\/spidermonkey.dev\/blog\/2025\/03\/05\/iterator-range.html\">Implementing Iterator.range in SpiderMonkey<\/a><\/p>\n<hr \/>                ","author":{"name":"Igalia Compilers Team","uri":"https:\/\/blogs.igalia.com\/compilers\/"}},{"title":"Igalia WebKit Team: WebKit Igalia Periodical #53","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-53\/"}},"id":"https:\/\/blogs.igalia.com\/webkit\/blog\/2026\/wip-53\/","updated":"2026-01-19T19:25:32+00:00","content":"\n<p>Update on what happened in WebKit in the week from December 26 to January 19.<\/p>\n<p>\nWe're back! The first periodical of 2026 brings you performance optimizations, improvements to the memory footprint calculation, new APIs, the removal of the legacy Qt5 WPE backend, and as always, progress on JSC's Temporal implementation.\n<\/p>\n<h2 id=\"cross-port-cat\">Cross-Port \ud83d\udc31<\/h2>\n  <div class=\"wip-item\">\n<p>The memory footprint calculation mechanism <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/pull\/56493\">has been unified<\/a> across GTK, JSC, and WPE ports. Therefore, the expensive <code>\/proc\/self\/smaps<\/code> is not used anymore and the WPE uses <code>\/proc\/self\/statm<\/code> with extra cache now to prevent frequent file reading.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p><a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305444@main\">Added<\/a> a new <code>webkit_context_menu_get_position()<\/code> function to the API that allows obtaining the pointer coordinates, relative to the web view origin, at the moment when a context menu was triggered.<\/p>\n<p>Additionally, behaviour of context menus <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305461@main\">has been made more consistent<\/a> between the GTK and WPE ports, and handling of <code>GAction<\/code> objects attached to menu items has been <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305267@main\">rewritten<\/a> and <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305504@main\">improved<\/a> with the goal of better supporting context menus in the WPE port.<\/p>\n  <\/div>\n<h3 id=\"javascriptcore-fish\">JavaScriptCore \ud83d\udc1f<\/h3>\n<div class=\"wip-description\">\n<p>The built-in JavaScript\/ECMAScript engine for WebKit, also known as JSC or SquirrelFish.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>In JavaScriptCore's implementation of Temporal, <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/pull\/56210\/\">fixed a bug<\/a> in <code>Temporal.PlainTime.from<\/code> that read options in the wrong order, which caused a test262 test to fail.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>In JavaScriptCore's implementation of Temporal, <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/pull\/56460\">fixed several bugs<\/a> in <code>PlainYearMonth<\/code> methods and enabled all <code>PlainYearMonth<\/code> tests that don't depend on the <code>Intl<\/code> object. This completes the implementation of Temporal <code>PlainYearMonth<\/code> objects in JSC.<\/p>\n  <\/div>\n<h3 id=\"graphics-frame-photo\">Graphics \ud83d\uddbc\ufe0f<\/h3>\n  <div class=\"wip-item\">\n<p>In WebKit's Skia graphics backend, <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/304898@main\">fixed GrDirectContext management<\/a> for GPU resources. Operations on GPU-backed resources must use the context that created them, not the current thread's context. The fix stores <code>GrDirectContext<\/code> at creation time for <code>NativeImage<\/code> and uses <code>surface-&gt;recordingContext()-&gt;asDirectContext()<\/code> for SkSurface, correcting multiple call sites that previously used the shared display's context incorrectly.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>Damage propagation <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/pull\/55697\">has been added<\/a> to the recently-added, non-composited mode in WPE.<\/p>\n  <\/div>\n  <div class=\"wip-item\">\n<p>In WebKit's Skia graphics backend for GTK\/WPE, <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305273@main\">added canvas 2D operation recording<\/a> for GPU-accelerated rendering. Instead of executing drawing commands immediately, operations are recorded into an <code>SkPicture<\/code> and replayed in batch when the canvas contents are needed, reducing GPU state change overhead for workloads with many small drawing operations, improving the MotionMark <em>Canvas Lines<\/em> performance on embedded devices with low-end tiled GPUs.<\/p>\n  <\/div>\n<h2 id=\"wpe-webkit-pager\">WPE WebKit \ud83d\udcdf<\/h2>\n  <div class=\"wip-item\">\n<p>Due to Qt5 not receiving maintenance since mid-2025, the WPE Qt5 binding that used the legacy libwpe API <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305824@main\">has been removed<\/a> from the tree. The Qt6 binding <a rel=\"external\" href=\"https:\/\/github.com\/WebKit\/WebKit\/tree\/main\/Source\/WebKit\/UIProcess\/API\/wpe\/qt6\">remains part of the source tree<\/a>, which is a better alternative that allows using supported Qt versions, and is built atop the new WPEPlatform API, making it a future-proof option. The WPE Qt API may be enabled when configuring the build with CMake, using the <code>ENABLE_WPE_QT_API<\/code> option.<\/p>\n  <\/div>\n<h3 id=\"wpe-platform-api-jigsaw\">WPE Platform API \ud83e\udde9<\/h3>\n<div class=\"wip-description\">\n<p>New, modern platform API that supersedes usage of libwpe and WPE backends.<\/p>\n<\/div>\n  <div class=\"wip-item\">\n<p>The <code>WPEScreenSyncObserver<\/code> class has been improved to <a rel=\"external\" href=\"https:\/\/commits.webkit.org\/305509@main\">support multiple callbacks<\/a>. Instead of a single callback set with <code>wpe_screen_sync_observer_set_callback()<\/code>, clients of the API can now use <code>wpe_screen_sync_observer_add_callback()<\/code> and <code>wpe_screen_sync_observer_remove_callback()<\/code>. The observer will be paused automatically when there are no callbacks attached to it.<\/p>\n  <\/div>\n<div class=\"wip-end\">\n<p>That\u2019s all for this week!<\/p>\n<\/div>                ","author":{"name":"Igalia WebKit Team","uri":"https:\/\/blogs.igalia.com\/webkit"}},{"title":"Manuel Rego: Servo 2025 Stats","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/"}},"id":"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/","updated":"2026-01-14T00:00:00+00:00","content":"\n<p>This is a brief blog post to highlight the growth of the Servo community in recent years, particularly since <a href=\"https:\/\/igalia.com\">Igalia<\/a> took over the project maintenance in 2023.<\/p>\n<p>Note that this doesn\u2019t talk about the technical achievements, though there have been tons of them in the last years. <em>A picture is worth a thousand words<\/em> so just take a look at <a href=\"https:\/\/blogs.igalia.com\/mrego\/servo-a-new-web-engine-written-in-rust\/\">this slide from my latest Servo talk<\/a> which shows how <a href=\"https:\/\/www.google.com\/\">google.com<\/a> was rendered with Servo at the beginning of 2023 vs September 2025.<\/p>\n<figure>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2025\/09\/servo-talk-at-gosim\/slide-11.png\" alt=\"Slide showing screenshots of Servo rendering google.com in January 2025 vs September 2025\" \/><\/p>\n  <figcaption>Slide showing screenshots of Servo rendering google.com in January 2023 vs September 2025<\/figcaption>\n<\/figure>\n<h2 id=\"prs-numbers\" tabindex=\"-1\">PRs numbers <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<p>So like <a href=\"https:\/\/blogs.igalia.com\/mrego\/servo-revival-2023-2024\/\">we did last year<\/a>, let\u2019s take a look at the PRs merged on the main <a href=\"https:\/\/github.com\/servo\/servo\">Servo repository on GitHub<\/a> since 2018.<\/p>\n<div>\n  <table>\n    <thead>\n      <tr>\n        <th><\/th>\n        <th>2018<\/th>\n        <th>2019<\/th>\n        <th>2020<\/th>\n        <th>2021<\/th>\n        <th>2022<\/th>\n        <th>2023<\/th>\n        <th>2024<\/th>\n        <th>2025<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td><strong>PRs<\/strong><\/td>\n        <td>1,188<\/td>\n        <td>986<\/td>\n        <td>669<\/td>\n        <td>118<\/td>\n        <td>65<\/td>\n        <td>776<\/td>\n        <td>1,771<\/td>\n        <td>3,183<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>Contributors<\/strong><\/td>\n        <td>27.33<\/td>\n        <td>27.17<\/td>\n        <td>14.75<\/td>\n        <td>4.92<\/td>\n        <td>2.83<\/td>\n        <td>11.33<\/td>\n        <td>26.33<\/td>\n        <td>42.42<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>Contributors \u2265 10<\/strong><\/td>\n        <td>2.58<\/td>\n        <td>1.67<\/td>\n        <td>1.17<\/td>\n        <td>0.08<\/td>\n        <td>0.00<\/td>\n        <td>1.58<\/td>\n        <td>4.67<\/td>\n        <td>8.50<\/td>\n    <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n<ul>\n<li><strong>PRs<\/strong>: total number of PRs merged.<\/li>\n<li><strong>Contributors<\/strong>: average number of contributors per month.<\/li>\n<li><strong>Contributors \u2265 10<\/strong>: average number of contributors that have merged more than 10 PRs per month.<\/li>\n<\/ul>\n<p>As a clarification, these numbers don\u2019t include PRs from bots (<code>dependabot<\/code> and <code>Servo WPT Sync<\/code>).<\/p>\n<p>Checking this we can see we are close to <strong>double the numbers from last year<\/strong>! The numbers in 2025 are way bigger than in the previous years (even checking the numbers from 2018-2019), showing a healthy community working on Servo.<\/p>\n<p><\/p>\n\n\n<p>The next chart is a different view of the same data but split per month, with the number of PRs landed every month, the number of contributors and the number of contributors with more than 10 patches. It shows the evolution over the years and the high activity last year.<\/p>\n<p><\/p>\n\n<h2 id=\"number-of-contributors\" tabindex=\"-1\">Number of contributors <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<p>Now let\u2019s focus on the last 3 years, since the project reactivation, and the numbers of contributors to the Servo project.<\/p>\n<div>\n  <table>\n    <thead>\n      <tr>\n        <th><\/th>\n        <th>2023<\/th>\n        <th>2024<\/th>\n        <th>2025<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td><strong>Contributors<\/strong><\/td>\n        <td>54<\/td>\n        <td>129<\/td>\n        <td>146<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>\u2265 100 PRs<\/strong><\/td>\n        <td>1 (2%)<\/td>\n        <td>3 (2%)<\/td>\n        <td>8 (5%)<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>\u2265 10 PRs<\/strong><\/td>\n        <td>8 (15%)<\/td>\n        <td>29 (22%)<\/td>\n        <td>43 (29%)<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>Only 1 PR<\/strong><\/td>\n        <td>31 (57%)<\/td>\n        <td>53 (41%)<\/td>\n        <td>55 (38%)<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n<\/div>\n<p>The number of contributors to Servo has tripled since 2023, reaching <strong>146 different contributors in 2025<\/strong>.<\/p>\n<p>If we analyze the rest of the data in this table, we can see that the percentage of contributors that do a single PR to Servo in a year has been reduced, meaning that Servo contributors are now usually doing more than one PR to the project.<\/p>\n<p>If we check the number of contributors that have done more than 10 PRs in a year, we see the percentage almost doubling from 15% to 29% in the last 3 years.<\/p>\n<p>And for the top contributors doing more than 100 PRs in a year, we have gone from 1 in 2023 and 3 in 2024 to 8 last year, which represent the 5% of the Servo contributors, showing a good team of very active contributors to the project.<\/p>\n<h2 id=\"wpt-pass-rate\" tabindex=\"-1\">WPT pass-rate <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<p>Let\u2019s take a look at <a href=\"https:\/\/web-platform-tests.org\/\">WPT<\/a> evolution in 2025.<\/p>\n<div>\n  <table>\n  <thead>\n    <tr>\n      <th>2025<\/th>\n      <th>January 1st<\/th>\n      <th>December 31st<\/th>\n      <th>Diff<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td><strong>Score %<\/strong><\/td>\n      <td>48.2%<\/td>\n      <td>61.6%<\/td>\n      <td><strong>+13.4pp<\/strong><\/td>\n    <\/tr>\n    <tr>\n      <td><strong>Subtests (passed\/total)<\/strong><\/td>\n      <td>1396647\/1998146<\/td>\n      <td>1866247\/1998146<\/td>\n      <td><strong>+469,600<\/strong><\/td>\n    <\/tr>\n    <tr>\n      <td><strong>Subtests %<\/strong><\/td>\n      <td>69.9%<\/td>\n      <td>93.4%<\/td>\n      <td><strong>+23.5pp<\/strong><\/td>\n    <\/tr>\n  <\/tbody>\n  <\/table>\n<\/div>\n<figure>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/01\/wpt.png\" alt=\"Evolution of WPT pass rates for Servo in 2025\" \/><\/p>\n  <figcaption>Evolution of WPT pass rates for Servo in 2025<\/figcaption>\n<\/figure>\n<p>You can check more information about WPT pass-rates at <a href=\"https:\/\/servo.org\/wpt\/\">Servo\u2019s website<\/a> (where you can also find an explanation of the <em>Score<\/em> number).<\/p>\n<p>Note that these numbers differ from <a href=\"https:\/\/wpt.fyi\/\">wpt.fyi<\/a> because we\u2019re still not running all the WPT tests in Servo, so the total numbers here are smaller.<\/p>\n<p>It\u2019s not easy to extract conclusions from this data, but it shows the Servo project keeps progressing and supporting more web platform features as time passes.<\/p>\n<p>Sometimes these numbers grow artificially as new tests are added to WPT for features that Servo already supports (for example, the biggest jump last year was in October getting 188,281 new subtests passing without any change in Servo, just because new tests were added to WPT).<\/p>\n<h2 id=\"github-stars\" tabindex=\"-1\">GitHub stars <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<figure>\n<p><img src=\"https:\/\/blogs.igalia.com\/mrego\/files\/2026\/01\/github-stars.png\" alt=\"Evolution of GitHub stars for Servo from https:\/\/www.star-history.com\/#servo\/servo\" \/><\/p>\n  <figcaption>Evolution of GitHub stars for Servo from <a href=\"https:\/\/www.star-history.com\/#servo\/servo\">star-history.com<\/a><\/figcaption>\n<\/figure>\n<p>We are about to reach <strong>35,000 stars on GitHub<\/strong>. It\u2019s good to see the project has not stopped growing since the beginning, and the curve has become steeper in recent years.<\/p>\n<h2 id=\"other\" tabindex=\"-1\">Other <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<p>If we check to the <a href=\"https:\/\/github.com\/servo\/project\/blob\/main\/governance\/README.md\">official project roles<\/a>, we have now:<\/p>\n<ul>\n<li>5 administrators<\/li>\n<li>17 TSC members<\/li>\n<li>25 maintainers<\/li>\n<li>18 contributors<\/li>\n<\/ul>\n<p>We have also started doing <a href=\"https:\/\/servo.org\/blog\/2025\/10\/20\/servo-0.0.1-release\/\"><strong>Servo releases<\/strong><\/a>, we have done <a href=\"https:\/\/github.com\/servo\/servo\/releases\">3 so far<\/a>.<\/p>\n<p>Also the TSC has setup <a href=\"https:\/\/servo.org\/blog\/2025\/11\/21\/sponsorship-tiers\/\">sponsorship tiers<\/a> for donations. We got <a href=\"https:\/\/servo.org\/#acknowledgements\"><strong>4 bronze sponsors<\/strong><\/a> in 2025 and we hope to increase the number of sponsorships in 2026.<\/p>\n<p>Regarding donations, we have defined a <a href=\"https:\/\/github.com\/servo\/project\/blob\/main\/FUNDING_REQUEST.md\"><strong>funding process<\/strong><\/a> to request usage of that money. We are currently using it to sponsor <a href=\"https:\/\/servo.org\/blog\/2025\/09\/17\/your-donations-at-work-funding-jdm\/\">Josh Matthews\u2019 contributions<\/a>, and <a href=\"https:\/\/www.azabani.com\/2025\/12\/18\/shoestring-web-engine-ci.html\">pay for self-hosted runners to speed up CI times<\/a>.<\/p>\n<p>Servo has been present in several events last year, we ended up giving <a href=\"https:\/\/servo.org\/about\/\"><strong>10 talks<\/strong><\/a> all around the globe.<\/p>\n<h2 id=\"wrap-up\" tabindex=\"-1\">Wrap-up <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mrego\/servo-2025-stats\/\">#<\/a><\/h2>\n<p>The idea here was to do a quick recap of the Servo stats in 2025. Taking a look at these numbers every now and then is useful, and gives you a different perspective about the status of the project, that one can easily ignore during the day-to-day tasks.<\/p>\n<p>In general things have grown a lot in 2025, who knows what would happen in 2026, but we hope we can at least keep similar numbers or maybe even keep growing them further. That would be really great news for the Servo project.<\/p>\n<p>Igalia is really proud of what the whole Servo community has achieved together in the recent years, and we hope for a bright future for the project going forward.<\/p>\n<p>As an aside note, by the end of the month I\u2019ll be at <a href=\"https:\/\/pretalx.fosdem.org\/fosdem-2026\/talk\/review\/PQPRDZ8DM7L8SYHBKGNZUJZUWGEXQTTP\">FOSDEM talking about Servo<\/a>, other Servo folks like <a href=\"https:\/\/www.igalia.com\/team\/dazabani\">Delan Azabani<\/a> and <a href=\"https:\/\/www.igalia.com\/team\/mrobinson\">Martin Robinson<\/a> will also be there. If you are around, don\u2019t hesitate to say hi and ask anything about the project.<\/p>                ","author":{"name":"Manuel Rego","uri":"https:\/\/blogs.igalia.com\/mrego\/"}},{"title":"Miyoung Shin: Our Journey to support Extensions for embedders","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/blogs.igalia.com\/mshin\/2026\/01\/13\/our-journey-to-support-extensions-for-embedders\/"}},"id":"https:\/\/blogs.igalia.com\/mshin\/?p=145","updated":"2026-01-13T02:00:28+00:00","content":"\n<h1 class=\"wp-block-heading\"><strong>A History of Extensions for Embedders \u2014 and Where We\u2019re Heading<\/strong><\/h1>\n\n\n\n<p>Chromium\u2019s Extensions platform has long been a foundational part of the desktop browsing experience. Major Chromium-based browsers\u2014such as Chrome and Microsoft Edge\u2014ship with full support for the Chrome Extensions ecosystem, and user expectations around extension availability and compatibility continue to grow.<\/p>\n\n\n\n<p>In contrast, some Chromium embedders\u2014 for instance, products built directly on the \/\/content API without the full \/\/chrome stack\u2014do not naturally have access to Extensions. Similarly, the traditional Chrome for Android app does not support Extensions. While some embedders have attempted to enable limited Extensions functionality by pulling in selected pieces of the \/\/chrome layer, this approach is heavyweight, difficult to maintain, and fundamentally incapable of delivering full feature parity.<\/p>\n\n\n\n<p>At Igalia we have been willing to help on the long term-goal of making Extensions usable on lightweight, \/\/content-based products, without requiring embedders to depend on \/\/chrome. This post outlines the background of that effort, the phases of work so far, the architectural challenges involved, and where the project is headed.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><strong>Note:<\/strong> ChromeOS supporting extensions (ChromeOS has <a href=\"https:\/\/blog.chromium.org\/2024\/06\/building-faster-smarter-chromebook.html\">announced<\/a> plans to incorporate more of the Android build stack) is not the same thing as Chrome-Android App supporting extensions. The two codepaths and platform constraints differ significantly. While the traditional Chrome app on Android phones and tablets still does not officially support extensions, recent beta builds of desktop-class Chrome on Android have begun to close this gap by enabling native extension installation and execution.<br \/><br \/>Tracking bug: <a href=\"https:\/\/issues.chromium.org\/issues\/356905053\">https:\/\/issues.chromium.org\/issues\/356905053<\/a><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Extensions Architecture \u2014 Layered View<\/h3>\n\n\n\n<p>The following diagram illustrates the architectural evolution of Extensions support for Chromium embedders.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Traditional Chromium Browser Stack<\/h4>\n\n\n\n<p>At the top of the stack, Chromium-based browsers such as Chrome and Edge rely on the full <code>\/\/chrome<\/code> layer. Historically, the Extensions platform has lived deeply inside this layer, tightly coupled with Chrome-specific concepts such as <code>Profile<\/code>, browser windows, UI surfaces, and Chrome services.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>+-----------------------+\n|      \/\/chrome         |\n|  (UI, Browser, etc.)  |\n+-----------------------+\n|     \/\/extensions      |\n+-----------------------+\n|      \/\/content        |\n+-----------------------+\n<\/code><\/pre>\n\n\n\n<p>This architecture works well for full browsers, but it is problematic for embedders. Products built directly on <code>\/\/content<\/code> cannot reuse Extensions without pulling in a large portion of <code>\/\/chrome<\/code>, leading to high integration and maintenance costs.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Phase 1 \u2014 Extensions on Android (Downstream Work)<\/strong><\/h3>\n\n\n\n<p>In 2023, a downstream project at Igalia required extension support on a Chromium-based <strong>Android<\/strong> application. The scope was limited\u2014we only needed to support a small number of specific extensions\u2014so we implemented:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>basic installation logic,<\/li>\n\n\n\n<li>manifest handling,<\/li>\n\n\n\n<li>extension launch\/execution flows, and<\/li>\n\n\n\n<li>a minimal subset of Extensions APIs that those extensions depended on.<\/li>\n<\/ul>\n\n\n\n<p>This work demonstrated that Extensions <em>can<\/em> function in an Android environment. However, it also highlighted a major problem: <strong>modifying the Android <code>\/\/chrome<\/code> codepath is expensive<\/strong>. Rebasing costs are high, upstream alignment is difficult, and the resulting solution is tightly coupled to Chrome-specific abstractions. The approach was viable only because the downstream requirements were narrow and controlled.<\/p>\n\n\n\n<p>I shared this experience at <a href=\"https:\/\/youtu.be\/FYYi_XiyL74?si=jG6ZrqZANRygo6AJ&t=300\"><strong>BlinkOn Lightning Talk: &#8220;Extensions on Android&#8221;<\/strong><\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n\n<\/div><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 2 \u2014 Extensions for Embedders <br \/>( \/\/content + \/\/extensions + \/\/components\/extensions )<\/h3>\n\n\n\n<p>Following Phase 1, we began asking a broader question:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em>Can we provide a reusable, upstream-friendly Extensions implementation that works for embedders without pulling in the <code>\/\/chrome<\/code> layer?<\/em><\/p>\n<\/blockquote>\n\n\n\n<h4 class=\"wp-block-heading\">Motivation<\/h4>\n\n\n\n<p>Many embedders aim to remain as lightweight as possible. Requiring <code>\/\/chrome<\/code> introduces unnecessary complexity, long build times, and ongoing maintenance costs. Our hypothesis was that <strong>large portions of the Extensions stack could be decoupled from Chrome and reused directly by content-based products<\/strong>.<\/p>\n\n\n\n<p>One early idea was to componentize the Extensions code by migrating substantial parts of <code>\/\/chrome\/*\/extensions<\/code> into <code>\/\/components\/extensions<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>+-------------------------+\n| \/\/components\/extensions |\n+-------------------------+\n|      \/\/extensions       |\n+-------------------------+\n|       \/\/content         |\n+-------------------------+<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Proof-of-concept : Wolvic<\/h4>\n\n\n\n<p>We tested this idea through <a href=\"https:\/\/www.wolvic.com\/en\/\">Wolvic <\/a>, a VR browser used in several commercial<br \/>solutions.  Wolvic has two implementations:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>a Gecko-based version, and <\/li>\n\n\n\n<li>a Chromium-based version built directly on the <code>\/\/content<\/code> API.<\/li>\n<\/ul>\n\n\n\n<p><br \/>Originally, Extensions were already supported in Wolvic-Gecko, but not in Wolvic-Chromium. To close that gap, we migrated core pieces of the Extensions machinery into <code>\/\/components\/extensions<\/code> and enabled extension loading and execution in a content-only environment.<\/p>\n\n\n\n<p>By early 2025, this work successfully demonstrated that Extensions could run without the <code>\/\/chrome<\/code> layer.<\/p>\n\n\n\n<p>Demo video::<br \/><a href=\"https:\/\/youtube.com\/shorts\/JmQnpC-lxR8?si=Xf0uB6q__j4pmlSj\">https:\/\/youtube.com\/shorts\/JmQnpC-lxR8?si=Xf0uB6q__j4pmlSj<\/a><\/p>\n\n\n\n<p>Design document:<br \/><a href=\"https:\/\/docs.google.com\/document\/d\/1I5p4B0XpypR7inPqq1ZnGMP4k-IGeOpKGvCFS0EDWHk\/edit?usp=sharing\">https:\/\/docs.google.com\/document\/d\/1I5p4B0XpypR7inPqq1ZnGMP4k-IGeOpKGvCFS0EDWHk\/edit?usp=sharing<\/a><\/p>\n\n\n\n<p>However, this work lived entirely in the Wolvic repository, which is a fork of Chromium. While open source, this meant that other embedders could not easily benefit without additional rebasing and integration work.<\/p>\n\n\n\n<p>This raised an important question:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Why not do this work directly in the Chromium upstream so that all embedders can benefit?<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Phase 3 \u2014 Extensions for Embedders<br \/>(\/\/content + \/\/extensions)<\/h3>\n\n\n\n<p>Following discussions with the Extensions owner (rdevlin.cronin@chromium.org), we refined the approach further.<\/p>\n\n\n\n<p>Rather than migrating functionality into <code>\/\/components<\/code>, the preferred long-term direction is to <strong>move Extensions logic directly into the <code>\/\/extensions<\/code> layer wherever possible<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>+-----------------------+\n|      Embedder UI      | (minimal interfaces)\n+-----------------------+\n|      \/\/extensions     |\n+-----------------------+\n|       \/\/content       |\n+-----------------------+<\/code><\/pre>\n\n\n\n<p>This approach offers several advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>clearer layering and ownership,<\/li>\n\n\n\n<li>fewer architectural violations,<\/li>\n\n\n\n<li>reduced duplication between Chrome and embedders,<\/li>\n\n\n\n<li>a cleaner API surface for integration.<\/li>\n<\/ul>\n\n\n\n<p>We aligned on this direction and began upstream work accordingly.<\/p>\n\n\n\n<p>Tracking bug: <img src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/1f517.png\" alt=\"\ud83d\udd17\" class=\"wp-smiley\" \/> <a href=\"https:\/\/issues.chromium.org\/issues\/358567092\">https:\/\/issues.chromium.org\/issues\/358567092<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img width=\"697\" height=\"366\" src=\"https:\/\/blogs.igalia.com\/mshin\/files\/2026\/01\/image.png\" alt=\"\" class=\"wp-image-151\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Our goals for Content Shell + \/\/extensions are:<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Embedders should only implement a small set of interfaces, primarily for UI surfaces (install prompts, permission dialogs) and optional behaviors.<\/li>\n\n\n\n<li><strong>Full Web Extensions APIs support<\/strong> <br \/>w3c standard : <a href=\"https:\/\/w3c.github.io\/webextensions\/specification\/\">https:\/\/w3c.github.io\/webextensions\/specification\/<\/a><\/li>\n\n\n\n<li><strong>Chrome Web Store compatibility<\/strong><br \/>Embedders should be able to install and run extensions directly from the Chrome Web Store.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Short-term Goal: Installation Support<\/strong><\/h4>\n\n\n\n<p>Our immediate milestone is to make <strong>installation<\/strong> work entirely using \/\/content + \/\/extensions.<\/p>\n\n\n\n<p><strong>Current progress:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><img src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> <code>.zip<\/code> installation support already lives in \/\/extensions<\/li>\n\n\n\n<li><img src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/1f6a7.png\" alt=\"\ud83d\udea7\" class=\"wp-smiley\" \/> Migrating <strong>Unpacked directory installation<\/strong> from \/\/chrome to \/\/extensions<br \/>(including replacing Profile with BrowserContext abstractions)<\/li>\n\n\n\n<li><img src=\"https:\/\/s.w.org\/images\/core\/emoji\/16.0.1\/72x72\/1f51c.png\" alt=\"\ud83d\udd1c\" class=\"wp-smiley\" \/> Moving <strong>.crx installation<\/strong> code from \/\/chrome \u2192 \/\/extensions<br \/><br \/>As part of this effort, we are introducing <strong>clean, well-defined interfaces<\/strong> for install prompts and permission confirmations:<\/li>\n\n\n\n<li>Chrome will continue to provide its full-featured UI<\/li>\n\n\n\n<li>Embedders can implement minimal, custom UI as needed<\/li>\n<\/ul>\n\n\n\n<p><strong>What Comes Next<\/strong>:<\/p>\n\n\n\n<p>Once installation is fully supported, we will move on to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Chrome Web Store integration flows<\/li>\n\n\n\n<li>Core WebExtensions APIs required by commonly used extensions<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Main Engineering Challenge \u2014 Detaching from the Chrome Layer<\/strong><\/h4>\n\n\n\n<p>The hardest part of this migration is not moving files\u2014it is <strong>breaking long-standing dependencies on the <code>\/\/chrome<\/code> layer<\/strong>.<\/p>\n\n\n\n<p>The Extensions codebase is large and historically coupled to Chrome-only concepts such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Profile<\/code><\/li>\n\n\n\n<li><code>Browser<\/code><\/li>\n\n\n\n<li>Chrome-specific <code>WebContents<\/code> delegates<\/li>\n\n\n\n<li>Chrome UI surfaces<\/li>\n\n\n\n<li>Chrome services (sync, signin, prefs)<\/li>\n<\/ul>\n\n\n\n<p>Each migration requires careful refactoring, layering reviews, and close collaboration with component owners. While the process is slow, it has already resulted in meaningful architectural improvements.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What&#8217;s Next?<\/strong><\/h3>\n\n\n\n<p>In the next post, We\u2019ll demonstrate:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>A functioning version of Extensions running on top of<br \/>\/\/content + \/\/extensions only \u2014 capable of installing and running extensions app.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>from Igalia side, we continue working on ways to make easier integrating Chromium on other platforms, etc. This will mark the first end-to-end, \/\/chrome-free execution path for extensions in content-based browsers.<\/p>\n\n\n\n<p>Stay tuned!<\/p>                ","author":{"name":"mshin","uri":"https:\/\/blogs.igalia.com\/mshin"}},{"title":"Alex Bradbury: Per-query energy consumption of LLMs","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/muxup.com\/2026q1\/per-query-energy-consumption-of-llms"}},"id":"https:\/\/muxup.com\/2026q1\/per-query-energy-consumption-of-llms","updated":"2026-01-07T12:00:00+00:00","content":"\n<p>How much energy is consumed when querying an LLM? We're largely in the dark\nwhen it comes to proprietary models, but for open weight models that anyone\ncan host on readily available, albeit eye-wateringly expensive, hardware this\nis something that can be measured and reported, right? In fact, given other\npeople are <a href=\"https:\/\/inferencemax.semianalysis.com\/\">doing the hard work<\/a> of\nsetting up and running benchmarks across all kinds of different hardware and\nsoftware configurations for common open weight models, can we just re-use that\nto get a reasonable figure in terms of Watt-hours (Wh) per query?<\/p>\n<p>For the kind of model you can run locally on a consumer GPU then of course\nthere's some value in seeing how low the per-query energy usage might be on a\nlarge scale commercial setup. But my main interest is in larger and more\ncapable models, the kind that you wouldn't realistically run locally and end\nup using in a pay-per-token manner either directly with your host of choice or\nthrough an intermediary like <a href=\"https:\/\/openrouter.ai\/\">OpenRouter<\/a>. In these\ncases where models are efficiently served with a minimum of 4-8 GPUs or even\n<a href=\"https:\/\/www.perplexity.ai\/hub\/blog\/lower-latency-and-higher-throughput-with-multi-node-deepseek-deployment\">multi-node\nclusters<\/a>\nit's not easy to get a feel for the resources you're using. I'm pretty happy\nthat simple back of the envelope maths shows that whether providers are\nproperly amortising the cost of their GPUs or not, it's implausible that\nthey're selling per-token API access for open models at below the cost of\nelectricity. That gives a kind of upper bound on energy usage, and looking at\nthe pennies I spend on such services it's clearly a drop in the ocean compared\nto my overall energy footprint. But it's not a very tight bound, which means\nit's hard to assess the impact of increasing my usage.<\/p>\n<p>We can look at things like <a href=\"https:\/\/arxiv.org\/pdf\/2508.15734\">Google's published figures on energy usage for\nGemini<\/a> but this doesn't help much. They\ndon't disclose the length of the median prompt and its response, or details of\nthe model used to serve that median query meaning it's not helpful for\neither estimating how it might apply to other models or how it might apply to\nyour own usage (which may be far away from this mysterious median query).\nMistral <a href=\"https:\/\/mistral.ai\/news\/our-contribution-to-a-global-environmental-standard-for-ai\">released\ndata<\/a>\non the per query environmental impact (assuming for a 400 token query), but\nthe size of the Mistral Large 2 model is not disclosed and they don't calculate\na Wh per query figure. CO2 and water per query are very helpful to evaluate a\nparticular deployment, but the actual energy used is a better starting point\nthat can be applied to other providers assuming different levels of carbon\nintensity. If one of the API providers were to share statistics based on a\nreal world deployment of one of the open models with a much higher degree of\ntransparency (i.e. sharing stats on the number of queries served during the\nperiod, statistics on their length, and measured system power draw) that would\nbe a useful source of data. But today we're looking at what we can conclude\nfrom the <a href=\"https:\/\/inferencemax.semianalysis.com\/\">InferenceMAX benchmark\nsuite<\/a> published results.<\/p>\n<p>I'd started looking at options for getting good figures thinking I might\nhave to invest in the hassle and expense of renting a multi-GPU cloud\ninstance to run my own benchmarks, then felt InferenceMAX may make that\nunnecessary. After writing this up along with all my provisos I'm perhaps\ntempted again to try to generate figures myself. Anyway, read on for a more\ndetailed look at that benchmark suite. You can scroll past all the provisos\nand <a href=\"https:\/\/muxup.com\/feed.xml#results\">jump ahead to the figures<\/a> giving the Wh\/query\nfigures implied by the benchmark results across different GPUs, different\naverage input\/output sequence lengths, and for gpt-oss 120B and\nDeepSeek-R1-0528. But I hope you'll feel a bit guilty about it.<\/p>\n<p>If you see any errors, please let me know.<\/p>\n<h2 id=\"high-level-notes-on-inferencemax\"><a href=\"https:\/\/muxup.com\/feed.xml#high-level-notes-on-inferencemax\" class=\"anchor\" tabindex=\"-1\"><\/a>High-level notes on InferenceMAX<\/h2>\n<p><a href=\"https:\/\/inferencemax.semianalysis.com\/\">InferenceMAX benchmark suite<\/a> has the\n<a href=\"https:\/\/newsletter.semianalysis.com\/p\/inferencemax-open-source-inference\">stated\ngoal<\/a>\nto \"provide benchmarks that both emulate real world applications as much as\npossible and reflect the continuous pace of software innovation.\" They\ndifferentiate themselves from other benchmarking efforts noting \"Existing\nperformance benchmarks quickly become obsolete because they are static, and\nparticipants often game the benchmarks with unrealistic, highly specific\nconfigurations.\"<\/p>\n<p>The question I'm trying to answer is \"what is the most 'useful AI' I can\nexpect for a modern GPU cluster in a realistic deployment and how much energy\ndoes it consume\". Any benchmark is going to show peak throughput higher than\nyou'd expect to achieve in real workload and there's naturally a desire to\nkeep it pinned on a specific model for as long as it isn't <em>totally<\/em>\nirrelevant in order to enable comparisons as hardware and software evolves\nwith a common point of reference. But although I might make slightly\ndifferent choices about what gets benchmarked and how, the InferenceMAX setup\nat first look seems broadly aligned with what I want to achieve.<\/p>\n<p>They benchmark\n<a href=\"https:\/\/huggingface.co\/deepseek-ai\/DeepSeek-R1-0528\">DeepSeek-R1-0528<\/a> (both\nat the native fp8 quantisation and at fp4) which is a 671B parameter model\nwith 37B active weights released ~7 months ago and seems a fair representative\nof a large MoE open weight model.\n<a href=\"https:\/\/huggingface.co\/openai\/gpt-oss-120b\">gpt-oss-120b<\/a> is also\nbenchmarked, providing a point of comparison for a much smaller and efficient\nto run model. Different input sequence length and output sequence length (ISL\nand OSL - the number of input and output tokens) are tested: 1k\/1k, 1k\/8k,\n8k\/1k, which provides coverage of different query types. Plus tests against a\nwide range of GPUs (including the 72-GPU GB200 NVL72 cluster) and sweeps\ndifferent settings.<\/p>\n<p>At the time of writing you might reasonably consider to be 'InferenceMAX' is\nsplit into around three pieces:<\/p>\n<ul>\n<li>The frontend website you can <a href=\"https:\/\/inferencemax.semianalysis.com\/\">see at\ninferencemax.semianalysis.com<\/a> (not\ncurrently open source but <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/315\">planned to\nbe<\/a>)<\/li>\n<li>The <a href=\"https:\/\/github.com\/kimbochen\/bench_serving\">script for executing queries against the LLM serving infrastructure and\ncollecting stats<\/a> (currently in\na seperate repo but <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/338\">planned to be incorporated into the main InferenceMAX\nrepository<\/a>),<\/li>\n<li>The wrapper\/runner scripts and GitHub actions workflows that live in the\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\">main InferenceMAX\nrepository<\/a>.\n<ul>\n<li>This is actively contributed to by at least Nvidia and AMD engineers.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>GitHub Actions is used to orchestrate the runs, ultimately producing a zip\nfile containing JSON with the statistics of each configuration (e.g.\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/actions\/runs\/20216709902\/job\/58149531774\">here<\/a>).\nThe <code>benchmark_serving.py<\/code> script is invoked via the <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/benchmarks\/benchmark_lib.sh#L107\"><code>run_benchmark_serving<\/code> wrapper\nin\n<code>benchmark_lib.sh<\/code><\/a>\nwhich hardcodes some options and passes through some others from the workflow\nYAML. The results logged by <code>benchmark_serving.py<\/code> are <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/utils\/process_result.py\">processed in\nInferenceMAX's <code>process_result.py<\/code>\nhelper<\/a>\nwhich will produce JSON in the desired output format. Together, these scripts\nprovide statistics like throughput (input and output token), end to end\nlatency, interactivity (output tokens per second) etc.<\/p>\n<h2 id=\"further-studying-the-benchmark-setup\"><a href=\"https:\/\/muxup.com\/feed.xml#further-studying-the-benchmark-setup\" class=\"anchor\" tabindex=\"-1\"><\/a>Further studying the benchmark setup<\/h2>\n<p>So, let's look at the benchmarking logic in more detail to look for any\nsurprises or things that might affect the accuracy of the Wh-per-query figure\nI want to generate. I'll note that InferenceMAX is an ongoing project that is\nactively being developed. These observations are based on a recent repo\ncheckout, but of course things may have changed since then if you're reading\nthis post some time after it was first published.<\/p>\n<p>Looking through I made the following observations. Some represent potential\nissues (see the next subheading for a list of the upstream issues I filed),\nwhile others are just notes based on aspects of the benchmark I wanted to\nbetter understand.<\/p>\n<ul>\n<li>One of the required arguments to the benchmark serving script is\n<code>--random-range-ratio<\/code>. This is set by default <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/.github\/workflows\/benchmark-tmpl.yml#L56\">to 0.8 in\n<code>benchmark-tmpl.yml<\/code><\/a>\nand <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/.github\/workflows\/benchmark-multinode-tmpl.yml#L49\">in\n<code>benchmark-multinode-tmpl.yml<\/code><\/a>\nand is not overridden elsewhere.\n<ul>\n<li>This argument is ultimately used in\n<a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L366\"><code>sample_random_requests<\/code><\/a>.\nIt uses <code>np.random.randint<\/code> to sample input\/output lengths between the\n<code>range_ratio * {input,output}_len<\/code> and <code>{input,output}_len<\/code>.<\/li>\n<li>Taken together, this logic means for for a workload advertised as having\n8k input or output tokens (8192), the benchmark will actually run with an\naverage ~7373 (<code>0.9*num_tokens<\/code>, due to the length being a random number\nbetween <code>0.8*num_tokens<\/code> and <code>num_tokens<\/code>) tokens.<\/li>\n<li>Because the throughput figures are <a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L498\">calculated using the actual input and\noutput token\nlengths<\/a>,\nthe figure <em>does<\/em> represent what was observed, it's just the workload\ndoesn't quite match the description. The reported end to end latency for\ninstance will be misleadingly lower than you would get for a workload that\nactually did have the expected input \/ output sequence lengths.<\/li>\n<\/ul>\n<\/li>\n<li>The various request functions in\n<a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/backend_request_func.py\">backend_request.func.py<\/a>\nwill set <code>output.success = False<\/code> if they don't get a HTTP 200 status code\nback for a request. There is no logic to retry a refused request and\n<a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L485\">metrics will be calculated skipping any failed\nrequests<\/a>.\nThis means an overloaded server will perform better on this benchmark for\nmetrics like E2E latency and TTFT if it refuses requests rather than accept\nthem and serve them slowly. As the number of failed requests isn't included\nin the results json it's not easy to tell if this is a factor for any\nbenchmarks.<\/li>\n<li>Many of the various scripts in the benchmarks\/ subdirectory <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/benchmarks\/gptoss_fp4_b200_docker.sh#L22\">set a\nmax-model-len\nparameter<\/a>\nor the similar <code>--max_seq_len<\/code> parameter for trt-llm (e.g. <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/benchmarks\/gptoss_fp4_b200_trt_docker.sh#L65\">the b200\nconfig<\/a>\nwhich if I'm not mistaken will ultimately be set from the max_model_len\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/utils\/matrix_logic\/generate_sweep_configs.py\">defined in\ngenerate_sweep_configs.py<\/a>.\nThis parameter is <a href=\"https:\/\/docs.vllm.ai\/en\/latest\/cli\/serve\/#-max-model-len\">documented in\nvllm<\/a> and <a href=\"https:\/\/nvidia.github.io\/TensorRT-LLM\/1.0.0rc2\/commands\/trtllm-serve.html#cmdoption-trtllm-serve-serve-max_seq_len\">in\nTensortRT-LLM<\/a>\nand controls the maximum supported length of a request, including both the\nprompt and any generated output. Setting it 20 or 200 tokens above the sum\nof the benchmarked ISL+OSL to minimise memory use does not seem like a\nrealistic real-world deployment, which seems the wrong choice given the\nInferenceMAX complaint that in other suites \"participants often\ngame the benchmarks with unrealistic, highly specific configurations\".\nBenchmarks naturally show a 'best case', but if you're generating figures\nlike $ per M tokens it's a figure that makes little sense if it reflects a\nconfiguration you wouldn't feasibly use\/sell.<\/li>\n<li>Throughput is <a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L546\">calculated in\n<code>benchmark_serving.py<\/code><\/a>\nbased on the total number of tokens divided by the duration of the\nbenchmark. This is then normalised on a per-GPU basis <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/utils\/process_result.py#L90\">in\nprocess_result.py<\/a>.\nNo problems here, I just wanted to clarify the source of the figure.<\/li>\n<li>In terms of the source of the input tokens themselves, we can see that\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/benchmarks\/benchmark_lib.sh#L222\"><code>--dataset-name random<\/code> is always passed to\n<code>benchmark_serving.py<\/code><\/a>.\nThis leads to\n<a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L366\"><code>sample_random_requests<\/code><\/a>\nbeing called, which will pick random token ids and create a list of tokens\nof the desired length (mapping these randomly picked IDs to tokens).\n<ul>\n<li>The <code>--ignore-eos<\/code> flag is passed to the <code>benchmark_serving.py<\/code> script\nwhich will in turn set this option in the JSON when making the LLM request.\n<a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/backend_request_func.py\"><code>backend_request_func.py<\/code><\/a>\nsets this and also sets <code>max_tokens<\/code> to the desired <code>output_len<\/code> which\n<em>should<\/em> ensure that the response has that exact desired number of output\ntokens. <code>ignore_eos<\/code> means that the LLM server will keep generating tokens\neven after seeing the end of sequence token.<\/li>\n<li>It's interesting that some of the benchmark configurations enable\nmulti-token prediction, and presumably find it beneficial even given the\ntotally random token inputs. Is it possible that such configurations\nbenefit from undesirable looped outputs (due to a combination of random\ninputs and continuing to sample tokens past the EOS marker) that\npotentially are very predictable and give an extra boost?<\/li>\n<\/ul>\n<\/li>\n<li>The --num-prompts parameter controls the total number of requests that are\nissued. The benchmark script is written so it will wait for all of these to\ncomplete (either successfully or unsuccessfully). This is\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/84320a0aadacae1114265b553830f48b56231817\/benchmarks\/gptoss_fp4_h100_slurm.sh#L51\">typically<\/a>\nset to the concurrency times 10, but some benchmark setups set it higher\n(presumably as the default figure finishes too quickly for good results).<\/li>\n<li>In terms of how requests are submitted with a certain level of concurrency:\n<ul>\n<li>See above for a discussion of the total number of requests<\/li>\n<li><code>--request-rate inf<\/code> is always passed, so there's no limit on submitting\nrequests up to the concurency limit.<\/li>\n<li>It <a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L962\">precomputes a list of requests to\nsubmit<\/a>\nand then <a href=\"https:\/\/github.com\/kimbochen\/bench_serving\/blob\/499c0b171b499b02a1fd546fb2326d2175a5d66e\/benchmark_serving.py#L664\">uses a semaphore to limit\nconcurrency<\/a>\nbut otherwise continuously submits requests up to the concurrency limit,\nand then waits until they call complete.<\/li>\n<\/ul>\n<\/li>\n<li>There are no tests that the configuration is serving the model with the\nexpected quality currently, but there's an <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/123\">issue tracking at least adding a\nsimple quality\nbenchmark<\/a>.\nAlthough none of the explored settings <em>should<\/em> impact the quality of output,\nit's always possible they trigger a bug and in this case it's not\ninteresting to benchmark.<\/li>\n<li>It would be helpful for reproducibility if more complete system information\nfor the benchmark runners was released. This is <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/393\">being worked\non<\/a>.<\/li>\n<li>You should of course consider whether the tested input and output sequence\nlengths correspond to a workload you are interested in (thank you to Aaron\nZhao for <a href=\"https:\/\/www.linkedin.com\/feed\/update\/urn:li:activity:7414767337058242562?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7414767337058242562%2C7415321431900905472%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287415321431900905472%2Curn%3Ali%3Aactivity%3A7414767337058242562%29\">reminding me to mention\nthis<\/a>.\nThis benchmarking approach also doesn't consider caching. Both factors could\nbe highly relevant if trying to estimate energy cost for a long context chat\nor 'agentic' flow. But I'm happy enough with the tested workloads as a\nstarting point, and my main focus here is trying to get a degree of comfort\nwith the reported numbers for the ISL\/OSL combinations they've chosen to\ntest.<\/li>\n<\/ul>\n<h2 id=\"filed-issues\"><a href=\"https:\/\/muxup.com\/feed.xml#filed-issues\" class=\"anchor\" tabindex=\"-1\"><\/a>Filed issues<\/h2>\n<p>I ended up filing the following issues upstream:<\/p>\n<ul>\n<li>FIXED <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/293\">Token throughput per MW is described as reflecting the generated tokens but\nis actually processed+generated\ntokens<\/a>\n<ul>\n<li>The companion <a href=\"https:\/\/newsletter.semianalysis.com\/p\/inferencemax-open-source-inference\">article introducing\nInferenceMAX<\/a>\nhas previously defined throughput as the rate at which the GPU\n<strong>generates<\/strong> tokens yet the figure displayed in the UI was the total\nnumber of output <em>and<\/em> input tokens per second. The definition in the\narticle has now been fixed, and changes to the UI make it more obvious\nbased on context that throughput refers to input+output tokens (as y-axis\nmetric options now exist to show \"input token throughput per GPU\" and\n\"output token throughput per GPU\").<\/li>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=yYha_OtxA14\">This talking head video from\nNvidia<\/a> seems to make the\nsame error, talking about the number of tokens 'generated' per second per\nGPU when looking at the relevant results these sem to be the total throughput\n(i.e. output <strong>plus<\/strong> the much faster to process input tokens).<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/299\">Presented input\/output token throughput per GPU for disaggregated setups\nnot usefully comparable to standard multi-gpu\nsetups<\/a>\n<ul>\n<li>In disaggregated setups you have some number of GPUs dedicated to prefill\n(processing input tokens) and some number dedicated to decode (generating\noutput tokens). In this case, the reported input\/output throughput figures\nrefer to the input or output throughput per prefill GPU or per decode GPU.\nIt doesn't make sense (IMHO) to plot this figure against the input\/output\nthroughput figures for a non-disaggregated setup. To make it comparable,\nthe input\/output throughput per GPU should be calculated by averaging\nacross the whole cluster rather than just the GPUs dedicated to prefill or\ndecode respectively.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/300\">Standard deviatation of interactivity (std_intvty) in result json is\nincorrectly\ncalculated<\/a>\n<ul>\n<li>Not a big issue as the figure isn't used anywhere. Interactivity\n(tokens\/second) metrics are calculated from the recorded time per output\ntoken. <code>1000\/$tpot_metric<\/code> is correct for the mean, median, and p99 figures\nbut mathematically incorrect for the standard deviation. e.g. a small\nstandard deviation for time per output token will result in a huge\nstandard deviation being computed for interactivity.<\/li>\n<\/ul>\n<\/li>\n<li>FIXED <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/349\">Reference kW figures no longer shown in frontend for each\nGPU<\/a>\n<ul>\n<li>At some point updates to the frontend logic meant that the per-GPU kW\nfigures used in calculating the token throughput per utility MW were no\nlonger displayed. This has now been fixed.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/350\">How will full workflow run output be retained beyond 90\ndays<\/a>\n<ul>\n<li>The benchmark frontend helpfully links to the GitHub Actions run that\ngenerated the displayed results and has a datepicker to view previous\nresults. Clicking through to GitHub means you can download the original\n.zip of the JSON format benchmark results which is something I take\nadvantage of in the analysis later in this article. According to GitHub\ndocs, <a href=\"https:\/\/docs.github.com\/en\/organizations\/managing-organization-settings\/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization\">the maximum retention period for Actions artifacts and logs is 90\ndays for a public\nrepo<\/a>.\nIt would be good to have a mechanism so that this information is backed up\nrather than lost.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/365\">Contents of CONFIG_DIR path as used in launch_gb200-nv.sh is\nundisclosed<\/a>\n<ul>\n<li>Most benchmark configuration lives in the main repository, but\nunfortunately one of the Nvidia DeepSeek R1 configurations <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/blob\/ff7dfc7365034aa84245f41c517c38618860d484\/runners\/launch_gb200-nv.sh#L26\">relies on\na config dir that's not publicly\navailable<\/a>\nmeaning it can't be audited or reproduced. This is a case where tightening\nup benchmark rules and review process can hopefully avoid it happening in\nthe future.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/359\">Reconsider allowing setting max_model_len \/ max_seq_len to\nisl+osl+tiny_margin<\/a>\n<ul>\n<li>As explained above, a number of benchmarks set <code>max_model_len<\/code> (or for\nNvidia's TensorRT, <code>--max_seq_len<\/code>) to some figure that is just above\nISL+OSL. Although some degree of tuning is expected, to me this goes\nagainst the idea that \"<a href=\"https:\/\/newsletter.semianalysis.com\/p\/inferencemax-open-source-inference\">We want server configs to reflect real world\ndeployments as much as\npossible<\/a>\"\nand the stated goal \"to provide benchmarks that both emulate real world\napplications as much as possible and reflect the continuous pace of\nsoftware innovation\". It's hard to imagine a realistic deployment that\nwould configure their serving engine in a way such that it errors if\ninput+output tokens passes ~2k tokens for instance. Looking at the\n<a href=\"https:\/\/openrouter.ai\/deepseek\/deepseek-r1-0528\">DeepSeek R1 0528 providers on\nOpenRouter<\/a>, the vast\nmajority offer greater than 128k context.<\/li>\n<li>By my understanding, with PagedAttention the KV cache is dynamically\nallocated anyway so this setting would largely impact other data\nstructures. Plus vllm at least contains a startup check that there is\nsufficient VRAM to serve at least one request at the maximum configured\ncontext. I would really like to see what impact this setting has on\nbenchmarks.<\/li>\n<li>The repository maintainers renamed my issue to a title that doesn't\nreflect my report. I'm hopeful they will review my recent comment and\ntitle it back.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/357\">Some reported metrics will be inflated if a serving engine sheds\nload<\/a>\n<ul>\n<li>This covers the observation made above that failed requests are simply\nskipped. As the number of failed requests isn't tracked, it's not easy to\nsee if a particular configuration may appear better (better E2E latency,\nlower time to first token) as a reset of shedding load rather than\nqueueing.<\/li>\n<li>The repository maintainers renamed this issue to \"[feature suggestion for\nvllm\/vllm benchmark_serving]\" and closed it. I'm hopeful they will read my\n<a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/357#issuecomment-3680821210\">response<\/a>\nand reconsider on the grounds that:\n<ul>\n<li>The benchmark_serving script isn't doing anything \"wrong\" necessarily.\nIt is simply making an implementation choice with potential impact on\nresults that the InferenceMAX harness isn't tracking.<\/li>\n<li>The script is planned to be added to the repo soon anyway.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/issues\/356\">Benchmarked ISL and OSL averages 0.9*target_length meaning results are\nover-optimistic<\/a>.\n<ul>\n<li>This is the problem mentioned above where the introduced variance in\ninput\/output sequence length has an average lower than the headline rate.\nAs noted, this means specifically the end to end latency figure is\nmisleading, but also impacts tokens\/second and throughput to the extent\nthat the cost of serving a query doesn't scale with O(n).<\/li>\n<li>This will be fixed by <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/pull\/339\">PR\n339<\/a> which\nupstreams the <code>benchmark_serving.py<\/code> script and in that modified branch\nchanges <code>sample_random_requests<\/code> to sample a range with multiplier between\n<code>1 - RANGE_RATIO<\/code> and <code>1 + RANGE_RATIO<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>In the best case, you'd hope to look at the benchmark results, accept they're\nprobably represent a higher degree of efficiency than you'd likely get on a\nreal workload, that an API provider might achieve 50% of that and double the\neffective cost per query to give a very rough upper estimate on per-query cost\nBut that only really works if the reported benchmark results roughly match the\nachievable throughput in a setup configured for commercial serving.  Given the\ntuning to specific isl\/osl values, I'm not at all confident thats the case and\nI don't know how wide the gap is.<\/p>\n<h2 id=\"generating-results\"><a href=\"https:\/\/muxup.com\/feed.xml#generating-results\" class=\"anchor\" tabindex=\"-1\"><\/a>Generating results<\/h2>\n<p>Firstly I wrote a <a href=\"https:\/\/gist.github.com\/asb\/44fe17f4f5b7abed7836481be45c5a38#file-check-py\">quick\nscript<\/a>\nto check some assumptions about the data and look for anything that seems\nanomalous. Specifically:<\/p>\n<ul>\n<li>Check that total throughput per GPU matches what you'd expect based on the\ninput token and output token throughput per GPU, even in the disaggregated\ncase. i.e. the total thoughput per GPU averaged over the whole cluster\nshould equal the sum of the input and output throughput per GPU provided\nthose figures are averaged over the whole cluster.<\/li>\n<li>The ratio of input token throughput to output token throughput should be\nalmost equal to the to the ratio of input to output tokens in the\nbenchmark's workload. If not, there is something surprising that needs\ninvestigating.<\/li>\n<\/ul>\n<p>Based on the information available in the generated result JSON and the\nreported all-in power per GPU (based on SemiAnalysis' model), we can calculate\nthe Watt hours per query. First calculate the joules per token (watts per GPU\ndivided by the total throughput per GPU). This gives a weighted average of the\njoules per token for the measured workload (i.e. reflecting the ratio of\nisl:osl). Multiplying joules per token by the tokens per query (isl+osl) gives\nthe joules per query, and we can just divide by 3600 to get Wh.<\/p>\n<p>There is some imprecision because we're constructing the figure for e.g.\n8192\/1024 ISL based on measurements with an average <code>0.9*8192<\/code> input and\n<code>0.9*1024<\/code> output length. The whole calculation would be much simpler if the\nbenchmark harness recorded the number of queries executed and in what time,\nmeaning we can directly calculate the Wh\/query from the Wh for the system over\nthe benchmark duration divided by the number of queries served (and\nremembering that in the current setup each query is on average 90% of the\nadvertised sequence length).<\/p>\n<p>This logic is wrapped up in a <a href=\"https:\/\/gist.github.com\/asb\/44fe17f4f5b7abed7836481be45c5a38#file-process_results-py\">simple\nscript<\/a>.<\/p>\n<p>There's been a recent change to <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/pull\/381\">remove the 'full sweep'\nworkflows<\/a> in favour of\nonly triggering a subset of runs when there is a relevant change. But I\ngrabbed my results from before this happened, from a December 15th 2025 run.\nHowever when finalising this article I spotted Nvidia managed to land some new\nNVL72 DeepSeek R1 0528 configurations just before Christmas, so I've merged in\nthose results as well, using a run from December 19th. All data and scripts are\ncollected together <a href=\"https:\/\/gist.github.com\/asb\/44fe17f4f5b7abed7836481be45c5a38\">in this\nGist<\/a>.<\/p>\n<h2 id=\"results\"><a href=\"https:\/\/muxup.com\/feed.xml#results\" class=\"anchor\" tabindex=\"-1\"><\/a>Results<\/h2>\n<p>As well as giving the calculated Wh per query, the script also gives a\ncomparison point of minutes of PS5 gameplay (<a href=\"https:\/\/www.playstation.com\/en-gb\/legal\/ecodesign\/\">according to\nSony<\/a>, \"Active Power\nConsumption\" ranges from ~217W to ~197W depending on model - we'll just use\n200W). The idea here is to provide some kind of reference point for what a\ngiven Wh figure means in real-world times, rather than focusing solely on the\nrelative differences between different deployments. Comparisons to \"minutes of\ninternet streaming\" seem popular at the moment, presumably as it's because an\nactivity basically everyone does. I'm steering away from that because I'd\nbe comparing one value that's hard to estimate accurately and has many\nprovisos to another figure that's hard to estimate accurately and has many\nprovisos, which just injects more error and uncertainty into this effort to\nbetter measure\/understand\/contextualise energy used for LLM inference.<\/p>\n<p>I'm now going to cherry-pick some results for discussion. Firstly for DeepSeek\nR1 0528 with 8k\/1k ISL\/OSL, we see that the reported configurations that give\na usable level of interactivity at fp8 report between 0.96-3.74 Wh\/query\n(equivalent to 0.29-1.12 minutes of PS5 gaming). The top row which is\nsubstantially\nmore efficient is the newer <a href=\"https:\/\/github.com\/SemiAnalysisAI\/InferenceX\/commit\/c040b5cf23ced2c7e23d1da03e1abae89e6426aa\">GB200 NVL72 configuration added at the end of\nlast\nyear<\/a>.\nIt's not totally easy to trace the configuration changes given they're\naccompanied by a reworking of the associated scripts, but as far as I can see\nthe configuration ultimately used is <a href=\"https:\/\/github.com\/ai-dynamo\/dynamo\/blob\/b7107d008\/examples\/backends\/sglang\/slurm_jobs\/scripts\/gb200-fp8\/disagg\/8k1k-max-tpt.sh\">this file from the dynamo\nrepository<\/a>.\nLooking at the JSON the big gain comes from significantly higher prefill\nthroughput (with output throughput per GPU remaining roughly the same). This\nindicates the older results (the second row) were bottlenecked waiting for\nwaiting for prefill to complete.<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">39.5<\/td>\n<td align=\"left\">36.5<\/td>\n<td align=\"left\">gb200 dynamo-sglang (72 GPUs disagg, conc: 2048, pfill_dp_attn, dec_dp_attn)<\/td>\n<td align=\"left\">0.96<\/td>\n<td align=\"left\">0.29<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">31.3<\/td>\n<td align=\"left\">55.2<\/td>\n<td align=\"left\">gb200 dynamo-sglang (72 GPUs disagg, conc: 1024, pfill_dp_attn, dec_dp_attn)<\/td>\n<td align=\"left\">3.13<\/td>\n<td align=\"left\">0.94<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">20.9<\/td>\n<td align=\"left\">48.8<\/td>\n<td align=\"left\">h200 trt (8 GPUs, conc: 64, dp_attn)<\/td>\n<td align=\"left\">3.32<\/td>\n<td align=\"left\">1.00<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">19.5<\/td>\n<td align=\"left\">49.6<\/td>\n<td align=\"left\">h200 sglang (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">3.39<\/td>\n<td align=\"left\">1.02<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">23.9<\/td>\n<td align=\"left\">39.9<\/td>\n<td align=\"left\">b200-trt trt (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">3.39<\/td>\n<td align=\"left\">1.02<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">22.3<\/td>\n<td align=\"left\">44.5<\/td>\n<td align=\"left\">b200 sglang (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">3.74<\/td>\n<td align=\"left\">1.12<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Now taking a look at the results for an fp4 quantisation of the same workload,\nthe result is significantly cheaper to serve with similer or better\ninteractivity and the NVL72 setup Nvidia submitted does have a significant\nadvantage over the 4\/8 GPU clusters. This time we see 0.63-1.67 Wh\/query\n(equivalent to 0.19-0.50 minutes of PS5 power draw while gaming). Serving at a\nlower quantisation impacts the quality of results of course, but the improved\nefficiency, including on smaler 4 GPU setups helps demonstrate why models like\n<a href=\"https:\/\/huggingface.co\/moonshotai\/Kimi-K2-Thinking\">Kimi K2 thinking<\/a> are\ndistributed as \"native int4\", with benchmark results reported at this\nquantisation and quantisation aware training used to maintain quality of\nresult.<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">41.6<\/td>\n<td align=\"left\">24.6<\/td>\n<td align=\"left\">gb200 dynamo-trt (40 GPUs disagg, conc: 1075, pfill_dp_attn, dec_dp_attn)<\/td>\n<td align=\"left\">0.63<\/td>\n<td align=\"left\">0.19<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">22.8<\/td>\n<td align=\"left\">43.2<\/td>\n<td align=\"left\">b200-trt trt (4 GPUs, conc: 128, dp_attn)<\/td>\n<td align=\"left\">0.93<\/td>\n<td align=\"left\">0.28<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">18.7<\/td>\n<td align=\"left\">59.3<\/td>\n<td align=\"left\">b200 sglang (4 GPUs, conc: 128)<\/td>\n<td align=\"left\">1.25<\/td>\n<td align=\"left\">0.38<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 8k\/1k<\/td>\n<td align=\"left\">30.3<\/td>\n<td align=\"left\">39.4<\/td>\n<td align=\"left\">b200 sglang (4 GPUs, conc: 64)<\/td>\n<td align=\"left\">1.67<\/td>\n<td align=\"left\">0.50<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Looking now at the 1k\/8k workload (i.e. generating significant output) and the\ncost is 15.0-16.3 Wh\/query (equivalent to 4.49-4.89 minutes of PS5 power draw\nwhile gaming). As expected this is significantly higher than the 8k\/1k\nworkload as prefill (processing input tokens) is much cheaper per token than\ndecode (generating output tokens)<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">42.5<\/td>\n<td align=\"left\">176.3<\/td>\n<td align=\"left\">b200 sglang (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">15.0<\/td>\n<td align=\"left\">4.49<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">31.9<\/td>\n<td align=\"left\">232.2<\/td>\n<td align=\"left\">h200 sglang (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">15.9<\/td>\n<td align=\"left\">4.76<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">31.2<\/td>\n<td align=\"left\">237.9<\/td>\n<td align=\"left\">h200 trt (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">16.3<\/td>\n<td align=\"left\">4.88<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp8 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">39.1<\/td>\n<td align=\"left\">189.5<\/td>\n<td align=\"left\">b200-trt trt (8 GPUs, conc: 64)<\/td>\n<td align=\"left\">16.3<\/td>\n<td align=\"left\">4.89<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Again, fp4 has a significant improvement in efficiency:<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">29.7<\/td>\n<td align=\"left\">251.5<\/td>\n<td align=\"left\">b200-trt trt (4 GPUs, conc: 256, dp_attn)<\/td>\n<td align=\"left\">2.73<\/td>\n<td align=\"left\">0.82<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">37.7<\/td>\n<td align=\"left\">197.5<\/td>\n<td align=\"left\">b200-trt trt (8 GPUs, conc: 256, dp_attn)<\/td>\n<td align=\"left\">4.31<\/td>\n<td align=\"left\">1.29<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">34.2<\/td>\n<td align=\"left\">221.2<\/td>\n<td align=\"left\">b200 sglang (4 GPUs, conc: 128)<\/td>\n<td align=\"left\">4.75<\/td>\n<td align=\"left\">1.43<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 DS R1 0528 1k\/8k<\/td>\n<td align=\"left\">33.1<\/td>\n<td align=\"left\">223.1<\/td>\n<td align=\"left\">b200-trt trt (4 GPUs, conc: 128)<\/td>\n<td align=\"left\">4.79<\/td>\n<td align=\"left\">1.44<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As you'd expect for a much smaller model at native fp4 quantisation,\nGPT-OSS-120B is much cheaper to serve. e.g. for 8k\/1k:<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 8k\/1k<\/td>\n<td align=\"left\">45.8<\/td>\n<td align=\"left\">20.8<\/td>\n<td align=\"left\">b200-trt trt (1 GPUs, conc: 128)<\/td>\n<td align=\"left\">0.11<\/td>\n<td align=\"left\">0.03<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 8k\/1k<\/td>\n<td align=\"left\">93.1<\/td>\n<td align=\"left\">10.5<\/td>\n<td align=\"left\">b200-trt trt (2 GPUs, conc: 128, dp_attn)<\/td>\n<td align=\"left\">0.11<\/td>\n<td align=\"left\">0.03<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 8k\/1k<\/td>\n<td align=\"left\">44.3<\/td>\n<td align=\"left\">21.4<\/td>\n<td align=\"left\">b200 vllm (1 GPUs, conc: 128)<\/td>\n<td align=\"left\">0.11<\/td>\n<td align=\"left\">0.03<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 8k\/1k<\/td>\n<td align=\"left\">145.7<\/td>\n<td align=\"left\">6.7<\/td>\n<td align=\"left\">b200-trt trt (2 GPUs, conc: 64, dp_attn)<\/td>\n<td align=\"left\">0.14<\/td>\n<td align=\"left\">0.04<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 8k\/1k<\/td>\n<td align=\"left\">103.8<\/td>\n<td align=\"left\">9.2<\/td>\n<td align=\"left\">b200 vllm (2 GPUs, conc: 64)<\/td>\n<td align=\"left\">0.20<\/td>\n<td align=\"left\">0.06<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Or for 1k\/8k:<\/p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Workload<\/th>\n<th align=\"left\">Intvty (tok\/s)<\/th>\n<th align=\"left\">E2EL (s)<\/th>\n<th align=\"left\">Details<\/th>\n<th align=\"left\">Wh\/Q<\/th>\n<th align=\"left\">PS5 min<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 1k\/8k<\/td>\n<td align=\"left\">80.5<\/td>\n<td align=\"left\">91.6<\/td>\n<td align=\"left\">b200-trt trt (1 GPUs, conc: 128)<\/td>\n<td align=\"left\">0.49<\/td>\n<td align=\"left\">0.15<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 1k\/8k<\/td>\n<td align=\"left\">72.3<\/td>\n<td align=\"left\">102.0<\/td>\n<td align=\"left\">b200 vllm (1 GPUs, conc: 128)<\/td>\n<td align=\"left\">0.55<\/td>\n<td align=\"left\">0.16<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 1k\/8k<\/td>\n<td align=\"left\">144.9<\/td>\n<td align=\"left\">51.1<\/td>\n<td align=\"left\">b200-trt trt (2 GPUs, conc: 128, dp_attn)<\/td>\n<td align=\"left\">0.55<\/td>\n<td align=\"left\">0.17<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">fp4 GPT-OSS 120B 1k\/8k<\/td>\n<td align=\"left\">129.4<\/td>\n<td align=\"left\">57.0<\/td>\n<td align=\"left\">b200-trt trt (2 GPUs, conc: 128)<\/td>\n<td align=\"left\">0.61<\/td>\n<td align=\"left\">0.18<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"conclusion\"><a href=\"https:\/\/muxup.com\/feed.xml#conclusion\" class=\"anchor\" tabindex=\"-1\"><\/a>Conclusion<\/h2>\n<p>Well, this took rather a lot more work than I thought it would and I'm\nnot yet fully satisfied with the result. Partly we have to accept a degree of\nfuzziness about marginal energy usage of an individual query - it's going to\ndepend on the overall workload of the system so there's going to be some\napproximation when you try to cost a single query.<\/p>\n<p>I'm glad that InferenceMAX exists and am especially glad that it's open and\npublicly developed, which is what has allowed me to dive into its\nimplementation to the extent I have and flag concerns\/issues. I feel it's not\nyet fully living up to its aim of providing results that reflect real world\napplication, but I hope that will improve with further maturation and better\nrules for benchmark participants. Of course, it may still make most sense to\ncollect benchmark figures myself and even if doing so, being able to refer to\nthe benchmarked configurations and get an indication of what hardware can\nachieve what performance is helpful in doing so. Renting a 72-GPU cluster is\nexpensive and as far as I can see not typically available for a short time, so\nany benchmarking run by myself would be limited to 4-8 GPU configurations. If\nthe gap in efficiency is huge for such setups vs the NVL72 then these smaller\nsetups are maybe less interesting.<\/p>\n<p>If I found the time to run benchmarks myself, what would I be testing? I'd\nmove to <a href=\"https:\/\/huggingface.co\/deepseek-ai\/DeepSeek-V3.2\">DeepSeek V3.2<\/a>. One\nof the big features of this release was the movement to a new attention\nmechanism which <a href=\"https:\/\/huggingface.co\/deepseek-ai\/DeepSeek-V3.2\/resolve\/main\/assets\/paper.pdf#section.3\">scales <em>much<\/em> closer to linearly with sequence\nlength<\/a>.\nWith e.g. <a href=\"https:\/\/github.com\/MoonshotAI\/Kimi-Linear\">Kimi Linear<\/a> and\n<a href=\"https:\/\/qwen.ai\/blog?id=4074cca80393150c248e508aa62983f9cb7d27cd\">Qwen3-Next<\/a>,\nother labs are moving in a similar direction experimentally at least. I'd\ntry to set up 8 GPU configuration with sglang\/vllm configured in a way that it\nwould be capable of serving a commercial workload with varied input\/output\nsequence lengths and test this is the case (Chutes <a href=\"https:\/\/chutes.ai\/app\/chute\/398651e1-5f85-5e50-a513-7c5324e8e839?tab=source\">provide their deployed\nconfigs<\/a>\nwhich may be another reference point). I'd want to see how much the effective\nWh per million input\/output tokens varies depending on the different isl\/osl\nworkloads. These <em>should<\/em> be relatively similar given the linear attention\nmechanism, and if so it's a lot easier to estimate the rough energy cost of a\nseries of your own queries of varied length. I would stick with the random\ninput tokens for the time being.<\/p>\n<p>So where does that leave us? All of this and we've got figures for two\nparticular models, with one benchmark harness, a limited set of input\/output\nsequence lengths, and a range of\npotential issues that might impact the conclusion. I think this is a useful\nyardstick \/ datapoint, though I'd like to get towards something that's even\nmore useful and that I have more faith in.<\/p>\n<hr \/><a href=\"https:\/\/muxup.com\/feed.xml#article-changelog\" class=\"anchor\" tabindex=\"-1\"><\/a>Article changelog\n<ul>\n<li>2026-02-17:\n<ul>\n<li>Changed GitHub links to point to SemiAnalysisAI\/InferenceX rather than\nInferenceMAX\/InferenceMAX, as they were broken by the upstream rename.<\/li>\n<\/ul>\n<\/li>\n<li>2026-01-09:\n<ul>\n<li>Fix broken link.<\/li>\n<li>Add note that more complete system info would be helpful for\nreproducibility.<\/li>\n<li>Add note about variety of input\/output sequence lengths tested.<\/li>\n<\/ul>\n<\/li>\n<li>2026-01-07: Initial publication date.<\/li>\n<\/ul>                ","author":{"name":"Alex Bradbury","uri":"https:\/\/muxup.com"}},{"title":"Brian Kardell: The Secret Life of Custom Elements","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/bkardell.com\/blog\/SecretLifeOfCustomElements.html"}},"id":"https:\/\/bkardell.com\/blog\/SecretLifeOfCustomElements.html","updated":"2026-01-06T05:00:00+00:00","content":"\n<h1 class=\"contextual-heading\">The Secret Life of Custom Elements<\/h1>\n\n<p class=\"segue\">Twenty years ago last month, Google published <a href=\"https:\/\/web.archive.org\/web\/20060203035414\/http:\/\/code.google.com\/webstats\/index.html\">an analysis of \"slightly over a billion documents,\"<\/a> a snapshot of the web that helped shape the early direction of HTML5. It followed a lineage of smaller, more personal studies \u2014 individuals poking at the web to answer some narrow question, often with datasets that would easily fit on a thumb drive today. For about half those two decades, I\u2019ve been arguing that we need <strong>more<\/strong> study of the web, not less. The platform evolves faster than our understanding of it, and the only way to know what the web actually is \u2014 not what we imagine it to be \u2014 is to look.<\/p>\n<p>Every month the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Internet_Archive\">HTTP Archive<\/a> quietly captures a snapshot of the web as it actually exists\u2014not the idealized web that we hope for, but the messy, improvised, duct\u2011taped reality of millions of sites in the wild.  I\u2019ve been collecting and studying these elements for the last six years. <\/p>\n<p>This new dataset is the largest I\u2019ve ever worked with: Billions of pages, hundreds of thousands of distinct non-standard element names, and a long tail that stretches into places no standards body has ever seriously examined. And unlike the Google study, which looked for patterns in class names, this dataset captures the long tail of non\u2011standard elements \u2014 the names people invent for actual elements when the platform doesn\u2019t give them what they need.<\/p>\n<p>What emerges is a portrait of the web as it is lived: messy, inventive, repetitive, global, and full of reinvention. It\u2019s also a mirror held up to the platform itself.<\/p>\n<p>But, it's also much more complex to study than I could have imagined a decade ago, and I really wish that the W3C (and member orgs which include academia) had taken up the charge to begin to figure out how to really study the web and use that information to inform standards work.<\/p>\n\n<h2 class=\"contextual-heading\">What's difficult about it...<\/h2>\n<p>One problem is that the dataset itself has some fairly extreme bias.  The crawl doesn't hit anything that isn't on the public internet - that means it excludes intranets which are <em>massive<\/em>.  In fact, most of my career was spent working on intranets.  The crawl captures only home pages, plus the target of whatever it interprets as the largest link on that page.  It also can't get to anything that requires login - which means that for a site like twitter or bluesky or mastodon, you're going to get something very unrepresentative of any of those.  So, one challenge I'd love to see us trying to tackle is how to get even better data representation.  It's hard to \"pave cowpaths\" if they're in a country we can't even see into.<\/p>\n<p>Initially I had this idea that we could watch for the adoption of tags - imagining that we'd get some that would become <em>very<\/em> popular, just like we did with JavaScript libraries and frameworks.  However, it turns out that this is not the signal it might first appear to be.  An element appearing in tens of thousands or even hundreds of thousands of pages is often simply because they are part of a larger successful system.  If Wix or Shopify create some custom elements that work behind the WYSIWYG tooling, and lots of people use it to create their pages - then suddenly that element gets very very popular - even if it isn't actually particularly good.  In fact, we can see shifts in the data where the teams themselves changed their minds and another version supplants the first very quickly because it's simply internal.<\/p>\n<p>Then, I thought that perhaps what we can do with the dataset instead, is to squint at it and look a little more abstractly at what people are naming their elements and see if people are re-solving similar problems.  Do we find, for example, multiple non-standard element names that appear to be about tabs?  Yes! Clearly that is indicative that we need a native element, right?  <em>Maybe<\/em>.  It's a bit more nuanced than that.  Here are the most commonly re-created\/repeated non-standard element themes:<\/p>\n<ul>\n<li>Navigation  <\/li>\n<li>Headers and footers  <\/li>\n<li>Carousels and sliders<\/li>\n<li>Modals  <\/li>\n<li>Search bars  <\/li>\n<li>Product cards  <\/li>\n<li>Login forms  <\/li>\n<li>Cookie banners  <\/li>\n<li>Accordions  <\/li>\n<li>Tabs   <\/li>\n<li>Toasts  <\/li>\n<li>Breadcrumbs<\/li>\n<\/ul>\n<p>While we don't have several of these in standard HTML, we <em>do<\/em> have native <code>&lt;header&gt;<\/code>, <code>&lt;footer&gt;<\/code>, <code>&lt;nav&gt;<\/code>, <code>&lt;dialog&gt;<\/code>, and <code>&lt;search&gt;<\/code> elements, and even accordions via the <code>name<\/code> attribute of <code>&lt;details&gt;<\/code>.  And yet, the wild still contains hundreds or thousands of custom elements with names like <code>&lt;app-header&gt;<\/code>, <code>&lt;site-footer&gt;<\/code>, <code>&lt;main-nav&gt;<\/code>, <code>&lt;modal-dialog&gt;<\/code>, <code>&lt;search-box&gt;<\/code>, and <code>&lt;accordion-panel&gt;<\/code>.<\/p>\n<p>Native primitives may exist, but not at the same level of abstraction as these.  <code>&lt;header&gt;<\/code> and <code>&lt;footer&gt;<\/code> in HTML are structural, not behavioral.  <code>&lt;dialog&gt;<\/code> is behavioral, but not styled.  <code>&lt;search&gt;<\/code> exists, but doesn\u2019t solve autocomplete, filtering, or results.<\/p>\n<p>So developers build those - and, if you stop and think about it, not all non-standard elements are equally as undesirable.  Many of them will be simple decorations or thin wrappers that <em>do<\/em> use their native counterparts.  Where there is definitely some interesting thing to study is where there is clear generic need where the platform doesn't provide anything close.  Above, tabs, for example.<\/p>\n\n\n\t<h2 class=\"contextual-heading\">Observations..<\/h2>\n<p>Here are many observations from the data, in no real particular order of importance.<\/p>\n\t\n\t\t<h3 class=\"contextual-heading\">Forms and Inputs: Tweaked, Wrapped, and Re\u2011Wrapped<\/h3>\n<p>Forms and inputs are a great example of the constant re-invention I just described.  Sometimes it's because the native element is insufficient, but that's not <em>necessarily<\/em> the case.  In some cases they're just slight wrappers.  Among them are lots and lots of \"pickers\" and \"selecters\" that show up...<\/p>\n<ul>\n<li><code>&lt;custom-select&gt;<\/code><\/li>\n<li><code>&lt;date-picker&gt;<\/code><\/li>\n<li><code>&lt;variant-picker&gt;<\/code><\/li>\n<li><code>&lt;quantity-selector&gt;<\/code><\/li>\n<\/ul>\n<p>There is already a lot of ongoing work to make native form elements (including selects) require less code and just be more stylable and flexible, and the data at least suggests that such efforts will be very welcome.<\/p>\n\n\n<h3 class=\"contextual-heading\">Hidden Machinery<\/h3>\n<p>A surprising number of elements aren\u2019t UI components at all. They\u2019re runtime markers:<\/p>\n<ul>\n<li><code>&lt;ng-container&gt;<\/code><\/li>\n<li><code>&lt;router-outlet&gt;<\/code><\/li>\n<li><code>&lt;astro-island&gt;<\/code><\/li>\n<li><code>&lt;ion-router-outlet&gt;<\/code><\/li>\n<li><code>&lt;next-route-announcer&gt;<\/code><\/li>\n<\/ul>\n<p>These exist because frameworks need declarative boundaries for hydration, routing, rendering or template expansion. I suppose it is debatable wither these are an indicator of \u201cmissing HTML features\u201d, or just how much.  <\/p>\n\n\n\n<h3 class=\"contextual-heading\">Carousels (and sliders... and toasts)<\/h3>\n<p>I don't love carousels, but it's hard to deny that they are popular.  There are <em>dozens<\/em> of distinct and identifiable carousel\/slider elements in the dataset and they appear <em>a lot<\/em>.  I really dislike a few bits of Google's attempt to make CSS-only carousels possible, but it's pretty clear why they chose to tackle that problem. I guess it is worth stressing again the bias in the dataset here - if there is a page I most expect to see a carousel, it is exactly the primary one the archive crawls.  So, while it is the most popular in the dataset, I don't know that it is the most popular all-around. You can see why Google winds up with their proposals though, toasts are on that top list too.<\/p>\n\n\n\n<h3 class=\"contextual-heading\">Structural semantics?<\/h3>\n<p>There are a few broad categories where the main point seems to be \"semantics\". That is, very often many of these don't actually <em>do<\/em> anything, beyond provide some hooks, mainly for styling.  They aren't actually even custom elements sometimes (or maybe even often) - just non-standard elements. <\/p>\n\n<h4 class=\"contextual-heading\">e-commerce<\/h4>\n<p>Dozens of these surround e-commerce.  There are tens of thousands of sites that use elements with names (and variants).<\/p>\n\nProduct &amp; merchandising\n<ul>\n<li><code>&lt;product-card&gt;<\/code><\/li>\n<li><code>&lt;product-title&gt;<\/code><\/li>\n<li><code>&lt;product-price&gt;<\/code><\/li>\n<li><code>&lt;product-rating&gt;<\/code><\/li>\n<li><code>&lt;product-variant&gt;<\/code><\/li>\n<li><code>&lt;product-gallery&gt;<\/code><\/li>\n<li><code>&lt;product-description&gt;<\/code><\/li>\n<li><code>&lt;product-badge&gt;<\/code><\/li>\n<\/ul>\n\n\nPricing &amp; money\n<ul>\n<li><code>&lt;price-money&gt;<\/code><\/li>\n<li><code>&lt;sale-price&gt;<\/code><\/li>\n<li><code>&lt;compare-at-price&gt;<\/code><\/li>\n<li><code>&lt;discount-amount&gt;<\/code><\/li>\n<li><code>&lt;currency-display&gt;<\/code><\/li>\n<\/ul>\n\n\nInventory &amp; availability\n<ul>\n<li><code>&lt;stock-status&gt;<\/code><\/li>\n<li><code>&lt;pickup-availability&gt;<\/code><\/li>\n<li><code>&lt;delivery-estimate&gt;<\/code><\/li>\n<li><code>&lt;inventory-level&gt;<\/code><\/li>\n<\/ul>\n\n\n\nCart &amp; checkout\n<ul>\n<li><code>&lt;cart-items&gt;<\/code><\/li>\n<li><code>&lt;cart-count&gt;<\/code><\/li>\n<li><code>&lt;checkout-button&gt;<\/code><\/li>\n<li><code>&lt;order-summary&gt;<\/code><\/li>\n<\/ul>\n\n\n<p>Very interestingly they are often used alongside actual machine readable semantics via jsonLD in the same markup.  <\/p>\n<p>While the vast majority of these elements appear because of common tooling, the fact that there are dozens of variants of similar names appearing on smaller numbers of sites indicates there is something widely interesting here. It's hard to say what it is other than that it would be nice to have a common structural semantic that would work for both purposes.<\/p>\n<p>I guess the biggest surprise here is that if it's true, why hasn't such a thing arisen already? It is entirely within the community's power to develop such a thing.  Perhaps the answer is that there is just so much variance it isn't easily plausible.  Maybe templating would somehow allow us to achieve a common pattern which achieved this based on the shared jsonLD semantics.<\/p>\n\n\n<h4 class=\"contextual-heading\">Publishing &amp; Editorial Semantics<\/h4>\n<p>CMSes and news sites often invent tags for editorial structure, and many of these are sticking around.<\/p>\n\nContent structure\n<ul>\n<li><code>&lt;article-header&gt;<\/code><\/li>\n<li><code>&lt;article-summary&gt;<\/code><\/li>\n<li><code>&lt;article-author&gt;<\/code><\/li>\n<li><code>&lt;article-date&gt;<\/code><\/li>\n<li><code>&lt;article-tags&gt;<\/code><\/li>\n<li><code>&lt;article-tag&gt;<\/code><\/li>\n<li><code>&lt;article-category&gt;<\/code><\/li>\n<li><code>&lt;byline&gt;<\/code><\/li>\n<li><code>&lt;dateline&gt;<\/code><\/li>\n<li><code>&lt;pullquote&gt;<\/code><\/li>\n<li><code>&lt;footnote&gt;<\/code><\/li>\n<\/ul>\n\n\nTaxonomy\n<ul>\n<li><code>&lt;tag-list&gt;<\/code><\/li>\n<li><code>&lt;category-label&gt;<\/code><\/li>\n<li><code>&lt;topic-header&gt;<\/code><\/li>\n<\/ul>\n\n<p>These reflect the needs of journalism and long\u2011form content.<\/p>\n\n\n\t<h4 class=\"contextual-heading\">Social &amp; Community Semantics<\/h4>\n<p>These show up in comment systems, forums, and social platforms.<\/p>\n\t\n\n\tUser\u2011generated content\n<ul>\n<li><code>&lt;comment&gt;<\/code><\/li>\n<li><code>&lt;comment-list&gt;<\/code><\/li>\n<li><code>&lt;comment-item&gt;<\/code><\/li>\n<li><code>&lt;comment-author&gt;<\/code><\/li>\n<li><code>&lt;comment-content&gt;<\/code><\/li>\n<li><code>&lt;comment-date&gt;<\/code><\/li>\n<li><code>&lt;comment-form&gt;<\/code><\/li>\n<\/ul>\n\n\n\nIdentity\n<ul>\n<li><code>&lt;user-avatar&gt;<\/code><\/li>\n<li><code>&lt;user-name&gt;<\/code><\/li>\n<li><code>&lt;profile-card&gt;<\/code><\/li>\n<\/ul>\n<p>These encode relationships and interactions, not UI patterns.<\/p>\n\n\n\nEvents\n<ul>\n<li><code>&lt;event-date&gt;<\/code><\/li>\n<li><code>&lt;event-location&gt;<\/code><\/li>\n<li><code>&lt;event-schedule&gt;<\/code><\/li>\n<li><code>&lt;event-details&gt;<\/code><\/li>\n<\/ul>\n<p>Again, these are domain objects, not widgets - and they have well established schema.org or microformats as well. <\/p>\n\n\n\nInvoicing\n<ul>\n<li><code>&lt;invoice&gt;<\/code><\/li>\n<li><code>&lt;invoice-line&gt;<\/code><\/li>\n<li><code>&lt;invoice-total&gt;<\/code><\/li>\n<li><code>&lt;invoice-summary&gt;<\/code><\/li>\n<\/ul>\n\n\n<p>Before the web came along, there were already national and international standards around electronically trading informtation like invoices - and when XML was sold, invoices were a common example.  Here we are again.<\/p>\n\n\n\n\n\n\t<h3 class=\"contextual-heading\">\"Namespaced\" Elements<\/h3>\n\n<p>Several elements like `o:p`, `rdf:rdf`, `dc:format`, `cc:work`, `fb:like`, `g:plusone` appear in the top 100.  These basically were thinking of an XHTML future (namespacing) that never really arrived.  However, HTML has always allowed it - so that's just the tag name.  In many ways, it's just as good.  Interestingly, these may be some of the better examples of what I'd like to see happen - they are widely understood.<\/p>\n\n<p>Conversely, while hugely successful, the share buttons are more an indication of a desire than something we could actually standardize in precisely that way.  They also point to a desire _in time_.  Google Plus doesn't even exist anymore, `fb:like` is from a time when Facebook was at the top of the most interesting places to be.  Maybe one of the things we've learned is that this is way handier to do at the browser\/OS levels?  I suppose the Web Share API was a part of thinking how we'd deal with this.<\/p>\n\n<p>The fact that they both still appear so much is also kind of an indication of age of the page and slow replacement of underlying tools.<\/p>\n\n\n\n\n\t<h3 class=\"contextual-heading\">Typos, Encoding Errors, and the Weird Stuff<\/h3>\n<p>One of the most delightful parts of the dataset is the long tail of what are almost certainly just typos:<\/p>\n<ul>\n<li><code>&lt;prodcut-card&gt;<\/code><\/li>\n<li><code>&lt;navgation&gt;<\/code><\/li>\n<li><code>&lt;contianer&gt;<\/code><\/li>\n<\/ul>\n<p>The fact that these can appear on tens of thousands of sites because they are part of common tooling helps re-enforce that not every non-standard element is a signal. :)<\/p>\n\n\n<h3 class=\"contextual-heading\">In conclusion...<\/h3>\n<p>I wish that I could say \"Ah ha - the data says very clearly that <em>these<\/em> are the specific things we should definitely 'just write down' now\" in the way that I imagined a decade ago, but I don't think we're there yet.  I guess if I had to give three things I'd like to see happen from here they'd be:<\/p>\n<ol>\n<li><p>We need lots more effort in thinking about how to study these things.  I would love to see real investment in this space.  This year, at last, the W3C is hiring someone to study the web.  I'm not yet sure what that looks like but I look forward to trying to discuss more with them.<\/p>\n<\/li>\n<li><p>We need a real community effort - an <a href=\"https:\/\/ul.org\/about\/our-history\/\">Underwriters Labs<\/a> for custom elements, with participation and funding from orgs with money.  We don't necessarily need \"the one true tabs\" as much as we need a place to find what I expect will be a very few sets of tabs as custom elements which we can trust like we trust native elements.  Given a little bit of time, I have faith that this will naturally sort itself into a few 'winners'.  <\/p>\n<\/li>\n<li><p>That community effort might also include things which won't ever have native implmentations, but which lay down some kind of light semantic meaning or compound styling structure that we all begin to agree on - like product cards or breadcrumbs.<\/p>\n<\/li>\n<\/ol>\n<p>A lot of this is pretty adjacent\/close to the ideas behind OpenUI and it's possible some of this could just happen there.  However, due mainly to limits and participation, OpenUI has really not really produced custom elements or worked to somehow list or grade and promote them (though we did study them quite a bit in the tabs research).  The effort led by Brad Frost to think about a \"global design system\" in particular might be closer to some of these ideas.<\/p>                ","author":{"name":"Brian Kardell","uri":"http:\/\/bkardell.com\/"}},{"title":"Andy Wingo: pre-tenuring in v8","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/wingolog.org\/archives\/2026\/01\/05\/pre-tenuring-in-v8"}},"id":"https:\/\/wingolog.org\/2026\/01\/05\/pre-tenuring-in-v8","updated":"2026-01-05T15:38:38+00:00","content":"\n<div><p>Hey hey happy new year, friends!  Today I was going over some V8 code\nthat touched <i>pre-tenuring<\/i>: allocating objects directly in the old\nspace instead of the nursery.  I knew the theory here but I had never\nlooked into the mechanism.  Today\u2019s post is a quick overview of how it\u2019s\ndone.<\/p><h3>allocation sites<\/h3><p>In a JavaScript program, there are a number of source code locations\nthat allocate.  Statistically speaking, any given allocation is likely\nto be short-lived, so generational garbage collection partitions\nfreshly-allocated objects into their own space.  In that way, when the\nsystem runs out of memory, it can preferentially reclaim memory from the\nnursery space instead of groveling over the whole heap.<\/p><p>But you know what they say: there are lies, damn lies, and statistics.\nSome programs are outliers, allocating objects in such a way that they\ndon\u2019t die young, or at least not young enough.  In those cases,\nallocating into the nursery is just overhead, because minor collection\nwon\u2019t reclaim much memory (because too many objects survive), and\nbecause of useless copying as the object is scavenged within the nursery\nor promoted into the old generation.  It would have been better to\neagerly tenure such allocations into the old generation in the first\nplace.  (The more I think about it, the funnier <i>pre-tenuring<\/i> is as a\nterm; what if some PhD programs could pre-allocate their graduates into\nnamed chairs?  Is going straight to industry the equivalent of dying\nyoung?  Does collaborating on a paper with a full professor imply a\nwrite barrier?  But I digress.)<\/p><p>Among the set of allocation sites in a program, a subset should\npre-tenure their objects.  How can we know which ones?  There is a\nliterature of static techniques, but this is JavaScript, so the answer\nin general is dynamic: we should observe how many objects survive\ncollection, organized by allocation site, then optimize to assume that\nthe future will be like the past, falling back to a general path if the\nassumptions fail to hold.<\/p><h3>my runtime doth object<\/h3><p>The high-level overview of how V8 implements pre-tenuring is based on\nper-program-point <i>AllocationSite<\/i> objects, and per-allocation\n<i>AllocationMemento<\/i> objects that point back to their corresponding\nAllocationSite.  Initially, V8 doesn\u2019t know what program points would\nprofit from pre-tenuring, and instead allocates everything in the\nnursery.  Here\u2019s a quick picture:<\/p><figure><img src=\"https:\/\/wingolog.org\/pub\/v8-allocation-sites.png\" alt=\"diagram of linear allocation buffer containing interleaved objects and allocation mementos\" \/>\n<figcaption><i>A linear allocation buffer containing objects allocated with allocation mementos<\/i><\/figcaption>\n<\/figure><p>Here we show that there are two allocation sites, <tt>Site1<\/tt> and <tt>Site2<\/tt>.\nV8 is currently allocating into a linear allocation buffer (LAB) in the\nnursery, and has allocated three objects.  After each of these objects\nis an <tt>AllocationMemento<\/tt>; in this example, <tt>M1<\/tt> and <tt>M3<\/tt> are\n<tt>AllocationMemento<\/tt> objects that point to <tt>Site1<\/tt> and <tt>M2<\/tt> points to\n<tt>Site2<\/tt>.  When V8 allocates an object, it <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/heap\/factory.cc;l=343-345;drc=56535b80c32d3a618b8fc8cfd7b1afdd3862e1b2\">increments the \u201ccreated\u201d\ncounter on the corresponding\n<tt>AllocationSite<\/tt><\/a>\n(if available; it\u2019s possible an allocation comes from C++ or something\nwhere we don\u2019t have an <tt>AllocationSite<\/tt>).<\/p><p>When the free space in the LAB is too small for an allocation, V8 gets\nanother LAB, or collects if there are no more LABs in the nursery.  When\nV8 does a minor collection, as the scavenger visits objects, it will\n<a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/heap\/pretenuring-handler-inl.h;l=73-150\">look to see if the object is followed by an\nAllocationMemento<\/a>.\nIf so, it dereferences the memento to find the <tt>AllocationSite<\/tt>, then\nincrements its \u201cfound\u201d counter, and adds the <tt>AllocationSite<\/tt> to a set.\n<a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/heap\/pretenuring-handler.cc;l=158\">Once an AllocationSite has had 100\nallocations<\/a>,\nit is enqueued for a pre-tenuring decision; <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/heap\/pretenuring-handler.cc;l=50\">sites with 85%\nsurvival<\/a>\nget marked for pre-tenuring.<\/p><p>If an allocation site is marked as needing pre-tenuring, the code in\nwhich it is embedded it will get de-optimized, and then next time it is\noptimized, the code generator arranges to allocate into the old\ngeneration instead of the default nursery.<\/p><p>Finally, if a major collection collects more than 90% of the old\ngeneration, V8 <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/heap\/heap.cc;l=3034-3054\">resets all pre-tenured allocation\nsites<\/a>,\nunder the assumption that pre-tenuring was actually premature.<\/p><h3>tenure for me but not for thee<\/h3><p>What kinds of allocation sites are eligible for pre-tenuring?  Sometimes\nit depends on object kind; wasm memories, for example, are almost always\nlong-lived, so they are always pre-tenured.  Sometimes it depends on who\nis doing the allocation; allocations from the bootstrapper, literals\nallocated by the parser, and many allocations from C++ go straight to\nthe old generation.  And sometimes the compiler has enough information\nto determine that pre-tenuring might be a good idea, as when it\n<a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/main:v8\/src\/maglev\/maglev-graph-builder.cc;l=4719\">generates a store of a fresh object to a field in an known-old\nobject<\/a>.<\/p><p>But otherwise I thought that the whole AllocationSite mechanism would\napply generally, to any object creation.  It turns out, nope: it seems\nto only apply to object literals, array literals, and <tt>new Array<\/tt>.\nWeird, right?  I guess it makes sense in that these are the ways to\ncreate objects that also creates the field values at creation-time,\nallowing the whole block to be allocated to the same space.  If instead\nyou make a pre-tenured object and then initialize it via a sequence of\nstores, this would likely create old-to-new edges, preventing the new\nobjects from dying young while incurring the penalty of copying and\nwrite barriers.  Still, I think there is probably some juice to squeeze\nhere for pre-tenuring of class-style allocations, at least in the\noptimizing compiler or in short inline caches.<\/p><p>I suspect this state of affairs is somewhat historical, as the\nAllocationSite mechanism seems to have originated with <a href=\"https:\/\/dl.acm.org\/doi\/10.1145\/2509136.2509531\">typed array\nstorage strategies<\/a> and\nV8\u2019s \u201cboilerplate\u201d object literal allocators; both of these predate\nper-AllocationSite pre-tenuring decisions.<\/p><h3>fin<\/h3><p>Well that\u2019s adaptive pre-tenuring in V8!  I thought the \u201cjust stick a\nmemento after the object\u201d approach is pleasantly simple, and if you are\nonly bumping creation counters from baseline compilation tiers, it\nlikely amortizes out to a win.  But does the restricted application to\nliterals point to a fundamental constraint, or is it just accident?  If\nyou have any insight, let me know :)  Until then, happy hacking!<\/p><\/div>                ","author":{"name":"Andy Wingo","uri":"https:\/\/wingolog.org\/"}},{"title":"Qiuyi Zhang (Joyee): require(esm) in Node.js: from experiment to stability","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-from-experiment-to-stability\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-from-experiment-to-stability\/","updated":"2026-01-05T10:36:13+00:00","content":"\n<p>More than a year ago, I set out to revive <a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/nodejs.org\/docs\/latest-v25.x\/api\/modules.html#loading-ecmascript-modules-using-require\"><code>require(esm)<\/code> in Node.js<\/a> and <\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}},{"title":"Qiuyi Zhang (Joyee): require(esm) in Node.js: implementer's tales","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-implementers-tales\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-implementers-tales\/","updated":"2026-01-05T10:36:11+00:00","content":"\n<p>In earlier posts, I wrote about <a href=\"https:\/\/joyeecheung.github.io\/blog\/2024\/03\/18\/require-esm-in-node-js\/\" title=\"reviving require(esm)\">reviving require(esm)<\/a> and <a href=\"https:\/\/joyeecheung.github.io\/blog\/2025\/12\/30\/require-esm-in-node-js-from-experiment-to-stability\/\" title=\"its iteration process\">its iteration process<\/a><\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}},{"title":"Jasmine Tang: Rewriting analysis based warnings fall through","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/badumbatish.github.io\/posts\/rewriting_analysisbasedwarnings"}},"id":"https:\/\/badumbatish.github.io\/blog\/rewriting_analysisbasedwarnings","updated":"2026-01-05T00:00:00+00:00","content":"\nUhhh hey :)                ","author":{"name":"Jasmine Tang","uri":"https:\/\/badumbatish.github.io\/blog\/rss.xml"}},{"title":"Qiuyi Zhang (Joyee): require(esm) in Node.js","link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/joyeecheung.github.io\/blog\/2024\/03\/18\/require-esm-in-node-js\/"}},"id":"https:\/\/joyeecheung.github.io\/blog\/2024\/03\/18\/require-esm-in-node-js\/","updated":"2025-12-30T18:58:11+00:00","content":"\n<p>Recently I landed experimental <a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/github.com\/nodejs\/node\/pull\/51977\">support for <code>require()<\/code>-ing synchronous ES modules in Node.js<\/a>, a feature that has been long overdue<\/p>                ","author":{"name":"Qiuyi Zhang (Joyee)","uri":"https:\/\/joyeecheung.github.io\/blog\/"}}]}