<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">

<channel>
	<title>Planet Igalia</title>
	<link>https://planet.igalia.com/</link>
	<language>en</language>
	<description>Planet Igalia - https://planet.igalia.com/</description>

<item>
	<title>Brian Kardell: Fine. What Is the Web?</title>
	<guid>https://bkardell.com/blog/FWIW.html</guid>
	<link>https://bkardell.com/blog/FWIW.html</link>
	<description>
&lt;h1 class=&quot;contextual-heading&quot;&gt;Fine. What Is the Web?&lt;/h1&gt;
	&lt;p class=&quot;segue&quot;&gt;In which I am drawn into an unexpected sort of conversation...&lt;/p&gt;
	&lt;p&gt;I like to feel like I'm working on &#8212; or at least toward &#8212; something concrete. When things begin to seem too academic or esoteric, or feel disconnected from what appear to be obvious realities, I find it much less interesting. There are clear examples of things I've worked on (or am working on): custom elements, &lt;code&gt;:has&lt;/code&gt;, &lt;code&gt;:focus-visible&lt;/code&gt;, Custom Properties or even Container Queries. All of these are very concrete, and as such, now that we have them we're also starting to be able to see how successful they are (or aren't). Things take a long time, so in the end, even very concrete proposals can start to feel a bit esoteric when they're so far out ahead of our skis, leaning more and more onto foundations that aren't yet solid.&lt;/p&gt;
	&lt;p&gt;Anyway... In contrast to this, if you asked me to professionally come up with a definition for &quot;What is the web, exactly?&quot; I have this almost &lt;em&gt;visceral&lt;/em&gt; feeling that it's esoteric and I don't want to spend my limited energy on it. I want to run away from it. Far away. Who cares? It means whatever we collectively want it to mean. That's not my jam. It's stuff with URLs. It is &lt;em&gt;not&lt;/em&gt; a thing I relish discussing.&lt;/p&gt;
	&lt;p&gt;But...&lt;/p&gt;
	&lt;p&gt;Circumstances have put me in a time and place where the question keeps coming up &#8212; and I hate to admit it, but I think there are a few reasons to engage with it.&lt;/p&gt;
	&lt;p&gt;The question surfaced concretely around what belongs as a W3C Recommendation, and more broadly, what belongs at the W3C at all. Its catalog is pretty diverse, actually. Is it all equally &quot;the Web&quot;? The stuff in the browser certainly seems like a special &lt;em&gt;kind&lt;/em&gt; of thing. It carries special obligations around privacy, security, internationalization, and accessibility. It runs on just about every device imaginable. But then there's stuff like ActivityPub, JSON-LD, or XML &#8212; the browser doesn't do much with those. It &lt;em&gt;could&lt;/em&gt;, maybe, but that would come with its own considerations. And yet they're totally relevant, and they're totally the web.&lt;/p&gt;
	&lt;p&gt;Then there's a whole category of things that have emerged over the past decade pointing toward something... &lt;em&gt;more&lt;/em&gt;. Special kinds of apps that come preinstalled (on your TV or your smart toaster), or Electron apps you install yourself, or &quot;super apps&quot; that use web tech for UI while talking to things that aren't really the &quot;drive-by&quot; web we know from the browser. Different rules, but no standards. Yet.&lt;/p&gt;
	&lt;p&gt;Which of these do the words &quot;web platform&quot; and &quot;web&quot; actually apply to?&lt;/p&gt;
	&lt;p&gt;If we had a few more names, would it help us organize our thoughts, sharpen our priorities, and shape the overall architecture? Probably.&lt;/p&gt;

	
		&lt;h2 class=&quot;contextual-heading&quot;&gt;My current thinking&lt;/h2&gt;
	&lt;p&gt;My current thinking is: I don't know that it is worth defining &quot;the web&quot; very specifically. &quot;The Web Platform,&quot; however, I think is best used to describe what lives inside a web engine. And the embedded stuff? I feel like we need a group dedicated to that &#8212; an Embedded Web that tries to define something fairly minimal, grounded in the same concerns as the main web engines. We're working on getting people together to talk about this, because it really does affect what we prioritize and the direction we take things.&lt;/p&gt;
	&lt;hr /&gt;
	&lt;p&gt;I recently recorded a podcast on this topic with Dan Appelquist and Eric Meyer called &lt;a href=&quot;https://www.igalia.com/chats/isthistheweb&quot;&gt;Is this the web?&lt;/a&gt;.&lt;/p&gt;
	&lt;p&gt;I'd love to hear your thoughts.&lt;/p&gt;
	&lt;figure class=&quot;captioned-image&quot;&gt;&lt;a href=&quot;https://www.igalia.com/chats/isthistheweb&quot;&gt;&lt;img alt=&quot;&quot; src=&quot;https://notes.igalia.com/uploads/749e496f-76a3-400d-83e3-ff8064cd3c31.png&quot; /&gt;&lt;/a&gt;
	&lt;/figure&gt;
	&lt;hr /&gt;        </description>
	<pubDate>Wed, 10 Jun 2026 04:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #67</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-67/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-67/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from June 1 to June 8.&lt;/p&gt;
&lt;p&gt;
Another great week, this time we have a performance improvement implemented
in the Skia-based compositor, an excellent writeup about how to investigate
and isolate memory leaks in WPE WebKit, a couple of multimedia fixes, and a
variety of improvements and fixes across WebKit ports.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313910@main&quot;&gt;Implement&lt;/a&gt; dialog integration with close watcher.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/314384@main&quot;&gt;Implement&lt;/a&gt; node iterator and live range pre-remove steps for in-progress &lt;code&gt;moveBefore()&lt;/code&gt; implementation.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313888@main&quot;&gt;Fix&lt;/a&gt; an early return in CloseWatcher close to align with the spec.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The Web Inspector &lt;a rel=&quot;external&quot; href=&quot;https://github.com/WebKit/WebKit/commit/f7dd925a68af3c39406f6942a728e1428d00f802&quot;&gt;now shows&lt;/a&gt; DOM nodes associated with layout and rendering events in a separate column of layout timeline next to initiator, sizing, and timing information. Hovering over rows in the details table highlights the associated node, and clicking it reveals the node in the &quot;Elements&quot; tab. This makes it easier to match events with specific nodes and helps debugging changes to a web page.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/314400@main&quot;&gt;Fix&lt;/a&gt; popover light dismiss to account for disabled command buttons.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/show_bug.cgi?id=316190&quot;&gt;Fix&lt;/a&gt; &lt;code&gt;mediaTime&lt;/code&gt; provided with &lt;code&gt;requestVideoFrameCallback&lt;/code&gt; in case of captureCanvas as source.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/show_bug.cgi?id=316404&quot;&gt;Don't use the Rialto specific video decoder+sink on WebAudio, where its usage as a pure decoder isn't compatible&lt;/a&gt; and would cause issues.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/314626@main&quot;&gt;Batched painting&lt;/a&gt; support was implemented in the Skia-based compositor, improving the performance in several cases.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;community-events-handshake&quot;&gt;Community &amp;amp; Events &#129309;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Pawel Lampe published &lt;a rel=&quot;external&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;a blog post&lt;/a&gt; where he's presenting and discussing a guide on structured approach to narrowing down and debugging memory leaks within WPE WebKit.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 08 Jun 2026 20:57:00 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Ma&#237;ra Canal: Bringing Runtime Power Management to the Raspberry Pi GPU</title>
	<guid>https://mairacanal.github.io/bringing-runtime-pm-to-raspberry-pi-gpu/</guid>
	<link>https://mairacanal.github.io/bringing-runtime-pm-to-raspberry-pi-gpu/</link>
	<description>
&lt;p&gt;As part of Igalia&#8217;s collaboration with Raspberry Pi, I have previously blogged about several improvements we landed for the Broadcom VideoCore GPU (known as V3D), with the goal of extracting the best possible performance from the hardware. However, performance is not the whole story. On embedded devices, power consumption is just as important: reducing unnecessary activity helps lower heat generation, improve energy efficiency, and preserve performance over time by avoiding thermal throttling.&lt;/p&gt;

&lt;p&gt;That is why, over the last few months, we have been working on adding Runtime Power Management support to the upstream V3D DRM driver, allowing the GPU to be powered and clocked according to its actual usage.&lt;/p&gt;

&lt;h2 id=&quot;why-runtime-power-management&quot;&gt;Why Runtime Power Management?&lt;/h2&gt;

&lt;p&gt;In the Linux kernel, Runtime Power Management (known as Runtime PM) is the mechanism that allows individual devices to be suspended and resumed dynamically while the system as a whole remains running. Instead of keeping a device fully powered all the time, the kernel can put the device into a low-power state when it is idle and bring it back when it is needed again.&lt;/p&gt;

&lt;p&gt;In the graphics context, it is easy to see why runtime PM can be useful. A GPU is not necessarily active all the time: it may be heavily used while rendering a scene, but remain idle for long periods afterwards. If the driver keeps the GPU clocked during those idle periods, the system keeps spending energy on a block that is not doing useful work at all.&lt;/p&gt;

&lt;p&gt;For embedded platforms, this is even more pressing. Reducing unnecessary power consumption helps decrease heat generation and improve overall energy efficiency. Even if the board is not battery-powered, avoiding needless power usage can reduce the need for cooling and leave more thermal budget available for other blocks.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-an-idle-gpu-with-an-enabled-clock&quot;&gt;The Problem: an idle GPU with an enabled clock&lt;/h2&gt;

&lt;p&gt;Until now, the V3D driver had a very simple power model: the GPU clock was enabled during probe and remained enabled for the entire lifetime of the driver. In practice, this meant that once the driver was loaded, the V3D clock stayed on until the driver was removed, regardless of whether the GPU was actively executing jobs. This was simple and functional, but it meant that an idle GPU was not idle from a power-management point of view.&lt;/p&gt;

&lt;p&gt;On Raspberry Pi platforms, this is easy to observe with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vcgencmd&lt;/code&gt;. Even with no GPU workload running, the V3D clock would still report an enabled frequency:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;vcgencmd measure_clock v3d
frequency&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)=&lt;/span&gt;960016128
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the GPU is idle, the driver should be able to let the hardware become idle as well. Runtime PM provides the kernel infrastructure for that, but enabling it in the V3D driver required a bit more than simply adding suspend and resume callbacks.&lt;/p&gt;

&lt;h2 id=&quot;making-the-raspberry-pi-firmware-clocks-obey&quot;&gt;Making the Raspberry Pi firmware clocks obey&lt;/h2&gt;

&lt;p&gt;At first glance, adding Runtime PM to V3D might look like a driver-local change, but in practice, things were a bit more subtle.&lt;/p&gt;

&lt;p&gt;On Raspberry Pi platforms, some clocks are managed by the Raspberry Pi firmware. From the V3D driver&#8217;s point of view, this is supposed to be mostly transparent: the driver uses the standard Linux clock framework, and the clock provider takes care of talking to the firmware underneath. However, this abstraction only works if calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clk_prepare_enable()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clk_disable_unprepare()&lt;/code&gt; are translated into actual firmware requests to enable and disable the clock.&lt;/p&gt;

&lt;p&gt;Surprisingly, that was not happening. The Raspberry Pi firmware clock driver did not implement the prepare/unprepare hooks, so these calls did not actually ask the firmware to enable or disable the clock. We fixed that by translating the common clock framework operations into the corresponding Raspberry Pi firmware commands &lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-1&quot;&gt;[1]&lt;/a&gt;&lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-2&quot;&gt;[2]&lt;/a&gt;&lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-3&quot;&gt;[3]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, there was still one firmware-specific caveat: on current firmware versions, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPI_FIRMWARE_SET_CLOCK_STATE&lt;/code&gt; does not fully power off the clock as expected. To work around this limitation and achieve meaningful power savings, the clock rate also needs to be set to the minimum before disabling the clock. This behavior may change in future firmware releases, but for now the clock driver needs to account for it explicitly.&lt;/p&gt;

&lt;p&gt;With the firmware clock limitation addressed, the V3D driver could start relying on the usual kernel clock APIs as part of its Runtime PM flow. The next step was to reorganize the driver so that powering the GPU up and down became part of its operation.&lt;/p&gt;

&lt;h2 id=&quot;introducing-runtime-pm-to-v3d&quot;&gt;Introducing Runtime PM to V3D&lt;/h2&gt;

&lt;p&gt;With the clock side behaving as expected, we could move the V3D driver itself to a Runtime PM model &lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-7&quot;&gt;[7]&lt;/a&gt;&lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-8&quot;&gt;[8]&lt;/a&gt;&lt;a href=&quot;https://mairacanal.github.io/atom.xml#ref-9&quot;&gt;[9]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This required a small refactor of the probe path to separate power-independent setup from GPU-powered initialization. Resources that do not require the GPU to be powered are allocated during probe, while any initialization that depends on the GPU being clocked is handled during runtime resume. Runtime suspend then disables the clock again when the device becomes idle. The resulting flow is simple:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mairacanal.github.io/assets/images/runtime-pm-flow.png&quot; alt=&quot;Runtime PM Flow&quot; width=&quot;320&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With that in place, the change becomes visible from userspace. While a GPU workload such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glmark2&lt;/code&gt; is running, the V3D clock is enabled:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;vcgencmd measure_clock v3d
frequency&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)=&lt;/span&gt;960016128
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the workload finishes and the GPU becomes idle, the clock can drop back to zero:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;vcgencmd measure_clock v3d
frequency&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;)=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the behavior we wanted: the GPU remains available when there is work to do, but it no longer keeps its clock enabled while idle.&lt;/p&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;p&gt;To evaluate the effect of Runtime PM, we measured the board&#8217;s power consumption with an external power meter in three scenarios: an idle desktop session with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labwc&lt;/code&gt; running, an idle system without the compositor, and a full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glmark2&lt;/code&gt; run. Each condition was sampled at 100 Hz for around 300 seconds.&lt;/p&gt;

&lt;p&gt;The first case represents a mostly idle graphical session, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labwc&lt;/code&gt;, the compositor used by Raspberry Pi OS, may still wake the GPU occasionally. The second is a baseline with no graphical workload, while the third is a sustained GPU benchmark intended to keep the GPU active.&lt;/p&gt;

&lt;p&gt;The numbers behave the way one would hope. When the GPU is genuinely idle, the clock can be gated off and the savings show up as a clear drop: average draw falls from 3.30 W to 3.19 W with labwc running, and from 3.18 W to 3.09 W with no compositor at all. Both idle scenarios end up with savings of about 0.1 W (around 3%). Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glmark2&lt;/code&gt;, where the GPU is doing useful work for most of the run, the difference shrinks to about 0.015 W (0.3%), which is expected, as Runtime PM mainly affects the periods where the GPU becomes idle.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scenario&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
      &lt;th&gt;Difference&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Idle, compositor running&lt;/td&gt;
      &lt;td&gt;3.300 W&lt;/td&gt;
      &lt;td&gt;3.192 W&lt;/td&gt;
      &lt;td&gt;-0.108 W (-3.3%)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Idle, no compositor&lt;/td&gt;
      &lt;td&gt;3.179 W&lt;/td&gt;
      &lt;td&gt;3.093 W&lt;/td&gt;
      &lt;td&gt;-0.086 W (-2.7%)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glmark2&lt;/code&gt; full run&lt;/td&gt;
      &lt;td&gt;5.698 W&lt;/td&gt;
      &lt;td&gt;5.683 W&lt;/td&gt;
      &lt;td&gt;-0.015 W (-0.3%)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The distribution of idle samples with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labwc&lt;/code&gt; running also shows the effect clearly. With Runtime PM enabled, the distribution shifts toward lower power states. This indicates that the board spends more time in lower-power idle states once the V3D clock is no longer kept enabled unnecessarily.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mairacanal.github.io/assets/images/idle-power-with-compositor.png&quot; alt=&quot;Idle power consumption with labwc&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The effect is even cleaner with no compositor running. The samples collapse into two very narrow peaks with no overlap between them: without Runtime PM, the board sits at a stable 3.18 W; with Runtime PM, it sits at a stable 3.09 W.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mairacanal.github.io/assets/images/idle-power-without-compositor.png&quot; alt=&quot;Idle power consumption without compositor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glmark2&lt;/code&gt;, the time-series data shows that both configurations follow the same general workload pattern. Runtime PM does not significantly change the power profile while the GPU is busy, which is the intended behavior. The benefit appears when the workload leaves idle gaps or finishes, allowing the clock to be disabled again.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mairacanal.github.io/assets/images/glmark2-power-vs-time.png&quot; alt=&quot;glmark2 power consumption over time&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Overall, these measurements show that Runtime PM reduces power consumption where it matters most: when the GPU is idle. The absolute savings are modest at the board level, since the measurement includes the whole Raspberry Pi rather than the GPU power block alone, but the reduction is consistent with the intended change. The V3D clock no longer remains enabled for the full lifetime of the driver, and that translates into measurable reductions in idle power consumption.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Runtime PM support for V3D is one of those changes that is easy to overlook when everything is working correctly: userspace does not need to do anything differently, applications keep using the GPU as before, and the improvement happens underneath, in the way the kernel manages the hardware.&lt;/p&gt;

&lt;p&gt;Beyond improving raw GPU performance, our work at &lt;a href=&quot;https://www.igalia.com&quot;&gt;Igalia&lt;/a&gt; is also about making the upstream graphics stack behave better as a system: more efficient when idle, more robust across firmware interfaces, and better aligned with the expectations of the Linux kernel infrastructure.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p id=&quot;ref-1&quot;&gt;[1] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=919d6924ae9b4bcc9cb1d5ce4b78d5b92665d630&quot;&gt;clk: bcm: rpi: Turn firmware clock on/off when preparing/unpreparing - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-2&quot;&gt;[2] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6526402b9bac873d7a64c6e81eb53307d8471f08&quot;&gt;clk: bcm: rpi: Maximize V3D clock - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-3&quot;&gt;[3] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=672299736af6c398e867782708b7400957e62c76&quot;&gt;clk: bcm: rpi: Manage clock rate in prepare/unprepare callbacks - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-4&quot;&gt;[4] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=522567362b634015ca85b5460482ee0843feb105&quot;&gt;clk: bcm: rpi: Mark VEC clock as CLK_IGNORE_UNUSED - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-5&quot;&gt;[5] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b826d2c0b0ecb844c84431ba6b502e744f5d919a&quot;&gt;pmdomain: bcm: bcm2835-power: Increase ASB control timeout - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-6&quot;&gt;[6] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d797ecf3ffc5cc3e622bfee4cee6b17372c5bcc7&quot;&gt;pmdomain: bcm: bcm2835-power: Replace open-coded polling with readl_poll_timeout_atomic() - kernel/git/torvalds/linux.git - Linux kernel source tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-7&quot;&gt;[7] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=de1e32ef1d625ee4d717bcf10c23df2722324f62&quot;&gt;drm/v3d: Use devm_reset_control_get_optional_exclusive() - kernel/git/next/linux-next.git - The linux-next integration testing tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-8&quot;&gt;[8] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=ffd7371ed4179827dcf401543b37b69e5781f924&quot;&gt;drm/v3d: Allocate all resources before enabling the clock - kernel/git/next/linux-next.git - The linux-next integration testing tree&lt;/a&gt;&lt;/p&gt;

&lt;p id=&quot;ref-9&quot;&gt;[9] &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=458f2a712ab42b7d3615208862922dc35fe90ef9&quot;&gt;drm/v3d: Introduce Runtime Power Management - kernel/git/next/linux-next.git - The linux-next integration testing tree&lt;/a&gt;&lt;/p&gt;        </description>
	<pubDate>Mon, 08 Jun 2026 15:30:00 +0000</pubDate>
</item>
<item>
	<title>Olivier Tilloy: Embedded Recipes '26</title>
	<guid>https://blogs.igalia.com/otilloy/embedded-recipes-26/</guid>
	<link>https://blogs.igalia.com/otilloy/embedded-recipes-26/</link>
	<description>
&lt;p&gt;Last week the &lt;a href=&quot;https://embedded-recipes.org/2026/&quot;&gt;Embedded Recipes&lt;/a&gt; conference was held in Nice, France.
Igalia was sponsoring the event, and like last year, my colleague &lt;a href=&quot;https://www.igalia.com/team/mabente&quot;&gt;Mart&#237;n&lt;/a&gt; and myself were attending.
Unlike &lt;a href=&quot;https://blogs.igalia.com/otilloy/embedded-recipes-25/&quot;&gt;last year&lt;/a&gt;, we weren&#8217;t presenting, which for me means less stress and more opportunities for hallway conversations.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;source type=&quot;image/avif&quot;&gt;&lt;source type=&quot;image/webp&quot;&gt;&lt;img alt=&quot;Embedded Recipes de Nice&quot; src=&quot;https://blogs.igalia.com/otilloy/img/08j6Xz_nPn-595.png&quot; width=&quot;595&quot; height=&quot;417&quot; /&gt;
&lt;/source&gt;&lt;/source&gt;&lt;/p&gt;
&lt;p&gt;The event was extremely well organized, in a really cool venue (Parc Ph&#339;nix, in Nice) for the second year in a row. Kudos to the team at BayLibre!&lt;/p&gt;
&lt;p&gt;The selection of talks was overall quite interesting and relevant. Here are a few of my personal highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Yocto Project and the Cyber Resilience Act&lt;/strong&gt; where Paul Barker (Yocto Project) gave a few relevant definitions (Product with Digital Elements, stewards vs manufacturers) and discussed how this affects the Yocto project, which is essentially tooling, and what the project plans on implementing to remain compliant. [&lt;a href=&quot;https://youtu.be/dFtNisipSY0?list=PLwnbCeeZfQ_PKEzeCu_rYz9S6U6CLah9w&amp;t=391&quot;&gt;Recording&lt;/a&gt;]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;U-Boot on boot core as an always-on debug tool&lt;/strong&gt; where Marek Vasut presented a clever use of the separate Cortex-M33 core used for boot only to run U-Boot to get access to the Cortex-A core that runs Linux. This is intended for development purposes only, &lt;strong&gt;not&lt;/strong&gt; to be deployed in production. Really cool if you&#8217;re into that sort of low-level bringup work. [&lt;a href=&quot;https://youtu.be/dFtNisipSY0?list=PLwnbCeeZfQ_PKEzeCu_rYz9S6U6CLah9w&amp;t=18612&quot;&gt;Recording&lt;/a&gt;].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A Distributed Phone CI for postmarketOS&lt;/strong&gt; where Pablo Correa G&#243;mez walked the audience through the different attempts at implementing a CI pipeline running on physical phones by the postmarketOS project. This involved advanced custom hardware design, and the use of &lt;a href=&quot;https://docs.ci-tron.dev/&quot;&gt;CI-Tron&lt;/a&gt; as the orchestrator. [&lt;a href=&quot;https://youtu.be/q4Y-G73pumY?list=PLwnbCeeZfQ_PKEzeCu_rYz9S6U6CLah9w&amp;t=2222&quot;&gt;Recording&lt;/a&gt;].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Four NPUs, One Stack, Zero Blobs: Edge AI Acceleration in Mainline&lt;/strong&gt; where Tomeu Vizoso presented his work on the kernel userspace APIs and Mesa drivers to enable a truly open-source AI stack. [&lt;a href=&quot;https://youtu.be/q4Y-G73pumY?list=PLwnbCeeZfQ_PKEzeCu_rYz9S6U6CLah9w&amp;t=24010&quot;&gt;Recording&lt;/a&gt;].&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My personal interests normally tend to drive me towards higher-level concerns and constructs, which is why I feel I&#160;learnt so much in just two days, being immersed in a sea of hardware and low-level software to control it.&lt;/p&gt;
&lt;p&gt;The social event on the beach at the end of the first day was a perfect opportunity for networking, to meet old friends and new folks alike.&lt;/p&gt;
&lt;p&gt;I flew home after the end of the two days of conference, but fellow Igalians stayed/arrived to attend the following colocated events: &lt;a href=&quot;https://www.yoctoproject.org/event/embedded-recipes-2026/&quot;&gt;Yocto Project Workshop&lt;/a&gt;, &lt;a href=&quot;https://discourse.gstreamer.org/t/gstreamer-spring-hackfest-2026-on-29-31-may-2026-in-nice-france/5762&quot;&gt;GStreamer Spring Hackfest&lt;/a&gt;, &lt;a href=&quot;https://indico.freedesktop.org/event/13/&quot;&gt;Display Next Hackfest&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I sure hope to be attending again next year.&lt;/p&gt;        </description>
	<pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #66</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-66/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-66/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from May 19 to June 1.&lt;/p&gt;
&lt;p&gt;
The main feature of this week are new releases: stable ones with many security
fixes, and development ones with the new Skia-based compositor enabled. Additionally,
there was work on Web-facing features, optimizations, spell checking support for
the WPE port, and more.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;WebKit now &lt;a rel=&quot;external&quot; href=&quot;https://github.com/WebKit/WebKit/pull/63123&quot;&gt;supports&lt;/a&gt; mirroring
MathML stretchy operators using the OpenType &lt;code&gt;rtlm&lt;/code&gt; feature.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313671@main&quot;&gt;Replaced&lt;/a&gt; the &lt;code&gt;CloseWatcherManager&lt;/code&gt;'s
&lt;code&gt;escapeKeyHandler&lt;/code&gt;, which will allow other types of close signals to be supported.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313611@main&quot;&gt;Implemented&lt;/a&gt; queuing mutation observer
records in the work-in-progress &lt;code&gt;moveBefore()&lt;/code&gt; implementation.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313838@main&quot;&gt;Implemented&lt;/a&gt; popover integration with
close watcher.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313840@main&quot;&gt;Fixed&lt;/a&gt; popover light dismiss to
account for &lt;code&gt;popovertarget&lt;/code&gt; on input buttons.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Content filters now &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313681@main&quot;&gt;create temporary files in the compiled filters
directory&lt;/a&gt;, which ensures that a file
rename can always be used to place them at their final location. This avoids
falling back to a regular file copy, which can be slower, when the temporary
directory returned by &lt;code&gt;g_get_tmp_dir()&lt;/code&gt; (typically &lt;code&gt;/tmp&lt;/code&gt;) is in a different
volume than the filters' storage path configured for
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/stable/wpe-webkit-2.0/class.UserContentFilterStore.html&quot;&gt;WebKitUserContentFilterStore&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;wpe-webkit-pager&quot;&gt;WPE WebKit &#128223;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313825@main&quot;&gt;Enabled spell checking support in
WPE&lt;/a&gt;. The existing implementation for
the WebKitGTK port, which uses the
&lt;a rel=&quot;external&quot; href=&quot;https://rrthomas.github.io/enchant/&quot;&gt;Enchant&lt;/a&gt; library as a backend, was
generalized to provide spell checking support in WPE as well. The feature may
be toggled at build time using the &lt;code&gt;ENABLE_SPELLCHECK&lt;/code&gt; CMake option.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/06/02/webkitgtk2.52.4-released.html&quot;&gt;WebKitGTK
2.52.4&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.4.html&quot;&gt;WPE WebKit 2.52.4&lt;/a&gt; have
been released; they include a number of fixes for security issues, and it is a
highly recommended update. The corresponding security advisory, &lt;code&gt;WSA-2026-0003&lt;/code&gt;
(&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/security/WSA-2026-0003.html&quot;&gt;GTK&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/security/WSA-2026-0003.html&quot;&gt;WPE&lt;/a&gt; is available as well.
The release also includes a number of small improvements and Web compatibility
fixes.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Additionally, development releases &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/05/28/webkitgtk2.53.3-released.html&quot;&gt;WebKitGTK
2.53.3&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.53.3.html&quot;&gt;WPE WebKit 2.53.3&lt;/a&gt; are
available since last week. These include a change to use a new Skia-based
compositor by default, which is intended to replace TextureMapper once ready.
Therefore, &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org&quot;&gt;bug reports&lt;/a&gt; related to website rendering
are particularly welcome when using this and subsequent development releases.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;infrastructure-construction-site&quot;&gt;Infrastructure &#127959;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The deprecated and un-maintained Flatpak-based SDK was
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/WebKit/WebKit/pull/61566&quot;&gt;removed&lt;/a&gt;. Developers working on
the WPE and GTK WebKit ports are encouraged to migrate to the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;new
SDK&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Tue, 02 Jun 2026 00:12:49 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Pawel Lampe: WPE memory leak investigation playbook</title>
	<guid>https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/</guid>
	<link>https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/</link>
	<description>
&lt;!-- ############################################ SWEEP  ############################################--&gt;
&lt;p&gt;Depending on the web application, the WPE WebKit memory usage trend can vary. When simple web applications are being processed, the memory consumption tends to be virtually stable (the same) no matter the period. However, when more complicated web applications
are being executed, the memory usage usually grows over time while going back to normal from time to time e.g., when GC / memory pressure mechanism releases all kinds of caches and not-needed memory. Therefore, memory growth itself is not unusual.
Nevertheless, as the memory leaks happen in WPE at times, the memory growth is worth investigating &#8212; especially if very rapid or unbounded.&lt;/p&gt;
&lt;p&gt;This article presents a structured playbook for investigating such a memory growth and memory leaks in WPE. Rather than diving straight into debugging tools, it starts from first principles: confirming the problem is real, choosing the right
environment to work in, and narrowing down the leaking area before any heavy tooling is involved. The goal is to reach actual debugging as fast as possible, regardless of whether the environment is an embedded device or a desktop machine,
and regardless of how quickly the problem reproduces.&lt;/p&gt;
&lt;h2 id=&quot;playbook&quot; tabindex=&quot;-1&quot;&gt;Playbook &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The high-level list of recommended steps to follow is presented below. In a nutshell, the steps &lt;strong&gt;1&lt;/strong&gt;, &lt;strong&gt;2&lt;/strong&gt;, and &lt;strong&gt;3&lt;/strong&gt; are meant to choose and follow the fastest possible investigation path so that actual debugging of the problem
(step &lt;strong&gt;4&lt;/strong&gt;) can be started as soon as possible.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Confirming the problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Identifying the best setup for reproducing the problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Narrowing down&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;on &lt;strong&gt;embedded&lt;/strong&gt; when the problem takes a &lt;strong&gt;long time&lt;/strong&gt; to reproduce&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;on &lt;strong&gt;embedded&lt;/strong&gt; when the problem reproduces &lt;strong&gt;quickly&lt;/strong&gt;&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;on &lt;strong&gt;desktop&lt;/strong&gt; when the problem takes a &lt;strong&gt;long time&lt;/strong&gt; to reproduce&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;on &lt;strong&gt;desktop&lt;/strong&gt; when the problem reproduces &lt;strong&gt;quickly&lt;/strong&gt;&lt;/a&gt;,&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Debugging&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;1-confirming-the-problem&quot; tabindex=&quot;-1&quot;&gt;1. Confirming the problem &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The ultimate first step when working with alleged memory leak is to check whether the observed memory growth is actually abnormal. In the case of web browsers in general, the memory growth alone may not necessarily mean something is leaking.
&lt;strong&gt;There may be many regular reasons why the browser&#8217;s memory usage is growing&lt;/strong&gt;, but the usual suspects are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JavaScript-level memory allocations&lt;/strong&gt; &#8212; due to the very nature of JavaScript, the memory it allocates causes the overall web content process memory growth up until the garbage collector (GC) kicks in. Then (from the RSS perspective) some memory
is usually freed. However, as it&#8217;s not easy to predict when the GC will be invoked (e.g., when the browser processes an application that performs heavy rendering), it&#8217;s possible that memory will grow but remain garbage-collectible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript Just-in-Time (JIT) compilation&lt;/strong&gt; &#8212; when not explicitly disabled or limited, the processing of any web application that has JavaScript code associated with it will cause the browser to continuously compile the JavaScript code in the
background so that it executes such code faster in runtime at the expense of memory that is required for storing compiled artifacts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caches&lt;/strong&gt; &#8212; as the WPE operates, it caches things such as web resources, style resolution artifacts, textures, glyph atlases, layer tiles, display lists, rasterization artifacts, and many others. Naturally, the cache sizes are limited, however,
if many caches are growing at the same time, they may create an impression of a leak. The difference in that case is, the caches stop growing at some point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Due to the above, to confirm the memory growth is abnormal, one should usually try the following first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Triggering memory pressure&lt;/a&gt; to force the browser to trigger GC and evict as many cache entries as possible,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Rerunning the browser with JIT disabled&lt;/a&gt; to rule out the JIT-related memory growth &#8212; unless the application code is very small.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the memory growth doesn&#8217;t stop with JIT disabled or its level does not go back to normal after triggering memory pressure, the growth can be assumed to be abnormal, and one can proceed to the next step.&lt;/p&gt;
&lt;h3 id=&quot;2-identifying-the-best-setup-for-reproducing-the-problem&quot; tabindex=&quot;-1&quot;&gt;2. Identifying the best setup for reproducing the problem &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When the memory growth is atypical, it needs to be narrowed down in a way that the final debugging is possible. For both narrowing down and the debugging, &lt;strong&gt;one should aim at the most flexible development environment along with the smallest possible
web application that reproduces the problem quickly&lt;/strong&gt;. What it means in practice is &#8212; desktop environment along with small demo web application that reproduces the problem. Whilst it&#8217;s not always possible to have such an environment, the 3 general
rules are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Desktop environment is usually better than embedded one&lt;/strong&gt; in terms of working with memory leaks as it offers minimal overhead (e.g., in terms of compilation times) and huge flexibility in choosing the industry standard tools for profiling/debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small web application is always better than a big one as long as it still reproduces the same problem in the same amount of time&lt;/strong&gt;. In such case, a small application minimizes the amount of noise that usually stands in the way of profiling/debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A web application that reproduces the problem quickly is always better than the one that needs much more time for it&lt;/strong&gt;. The worst thing that can happen in the case of narrowing down memory leaks, is when the memory growth is noticeable or starts
after a very long time such as hours/days+.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Given the above, at this point one should go through the below steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Check if the setup is trivial enough already&lt;/strong&gt; &#8212; if the web application reproduces the problem quickly in a desktop environment and is simple enough, one should immediately jump to the &lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Debugging&lt;/a&gt; section.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check if the problem can be reproduced on desktop assuming it originally reproduces on embedded&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check if the problem can be reproduced faster if it&#8217;s not reproducing fast enough&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check if the web application could be simplified.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the setup is simplified as much as possible, one should proceed to one of &lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;narrowing down&lt;/a&gt; sections depending on the setup. Also, if the setup is still not ideal, one should actively seek opportunities for simplifying the setup
even during &lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;narrowing down&lt;/a&gt; as it&#8217;s likely that some new information will eventually open new possibilities in terms of simplifying setup.&lt;/p&gt;
&lt;h3 id=&quot;3-narrowing-down&quot; tabindex=&quot;-1&quot;&gt;3. Narrowing down &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When the problem has been confirmed but there are not enough clues to tell exactly which parts leak, the &lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;debugging&lt;/a&gt; cannot be started right away. In such case, it&#8217;s necessary to narrow down the problem to the browser/application area
that can be easily debugged.&lt;/p&gt;
&lt;p&gt;While in some cases narrowing down is not even necessary, quite often it takes orders of magnitude more time than actual debugging, and hence one should pay special attention to this step.&lt;/p&gt;
&lt;h3 id=&quot;3a-narrowing-down-on-embedded-when-the-problem-takes-a-long-time-to-reproduce&quot; tabindex=&quot;-1&quot;&gt;3a. Narrowing down on embedded when the problem takes a long time to reproduce &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is the toughest situation one can find themselves in. When a problem takes a long time to reproduce (hours/days+), every iteration/test comes automatically with a significant cost. Moreover, when the environment is an embedded one,
rebuilding WPE is usually more time-consuming and the amount of tooling is usually limited &#8212; or requires some work to bring it to the image at least.&lt;/p&gt;
&lt;p&gt;Due to the above, narrowing down the problem in this setup requires a structured approach with extra care. In such case, the things to check should be approached in steps defined as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rerunning the WebKit&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;in case of embedded devices, extra care is needed when attaching a memory profiler. On low-end devices, memory profilers tend to slow down the application hard enough to trigger otherwise non-existent problems.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rebuilding the WebKit&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;in case of embedded devices, one should prefer limiting JIT over disabling it as without it, the JS execution may be slow enough to trigger unexpected scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check if rebuilding WebKit&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ideally, while checking various things along the above steps, one should batch as many checks as possible within individual tests.&lt;/p&gt;
&lt;h3 id=&quot;3b-narrowing-down-on-embedded-when-the-problem-reproduces-quickly&quot; tabindex=&quot;-1&quot;&gt;3b. Narrowing down on embedded when the problem reproduces quickly &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When the problem reproduces quickly, the limitations of embedded environment are not that relevant. In this scenario, one should prioritize getting debug symbols (&lt;code&gt;RelWithDebInfo&lt;/code&gt; build) into the image and utilizing them by running
the browser with whatever profilers are available. For the specific things to check, one should seek inspiration in the following groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rebuilding the WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check if rebuilding WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3c-narrowing-down-on-desktop-when-the-problem-takes-a-long-time-to-reproduce&quot; tabindex=&quot;-1&quot;&gt;3c. Narrowing down on desktop when the problem takes a long time to reproduce &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This situation is similar to &lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;3a&lt;/a&gt; and hence one should follow the things to check from the following groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rerunning the WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rebuilding the WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check if rebuilding WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, this time, there are some extra opportunities around tooling:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There should be many more tools available already in the system or available to be installed.&lt;/li&gt;
&lt;li&gt;Tools such as memory profilers that could slow down the application making it unusable on embedded, may turn out to be working well when the desktop-class processing power is available.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the above in mind, it&#8217;s worth trying all the tools available with priority because if at least one tool works well, one can save hours of narrowing down.&lt;/p&gt;
&lt;h3 id=&quot;3d-narrowing-down-on-desktop-when-the-problem-reproduces-quickly&quot; tabindex=&quot;-1&quot;&gt;3d. Narrowing down on desktop when the problem reproduces quickly &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is technically the simplest possible scenario, so basically, all the possibilities are available. The most time-consuming activity in this case is very likely rebuilding WebKit itself &#8212; although it should still be relatively fast.
In such case, just after a few quick checks with the Web Inspector, it&#8217;s recommended to get debug symbols (&lt;code&gt;RelWithDebInfo&lt;/code&gt; build) and start with tools such as memory profilers.&lt;/p&gt;
&lt;p&gt;Other than the above, one should go through the following groups on things to check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check without rebuilding the WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Things to check if rebuilding WebKit&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4-debugging&quot; tabindex=&quot;-1&quot;&gt;4. Debugging &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The WPE debugging is twofold and depends on whether the problem is within the engine (usually C/C++ code) or the web application (JavaScript code).&lt;/p&gt;
&lt;h4 id=&quot;when-problem-lies-in-the-engine&quot; tabindex=&quot;-1&quot;&gt;When problem lies in the engine &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Debugging WPE WebKit is the same as debugging any other C/C++ application on Linux (or Mac if the issue is cross-port and one prefers an Apple port to work with), and hence is outside the scope of this article. Some WebKit-specific information
can be found in the &lt;a href=&quot;https://docs.webkit.org/Build%20%26%20Debug/BuildOptions.html&quot;&gt;WebKit Documentation article on building and debugging&lt;/a&gt; page and therefore is recommended as a first step.&lt;/p&gt;
&lt;h4 id=&quot;when-problem-lies-in-web-application&quot; tabindex=&quot;-1&quot;&gt;When problem lies in web application &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When the problem lies in JavaScript code, the situation is usually fairly straightforward. The majority of bugs in this area should be reproducible across various browser engines and hence a full variety of tooling should be available.
If the WebKit is preferred or if the problem reproduces only there, the tooling available is still very useful and helps debugging problems quickly. The ultimate tool in such case is the
&lt;a href=&quot;https://developer.apple.com/documentation/safari-developer-tools/web-inspector&quot;&gt;Web Inspector&lt;/a&gt;. On official WebKit&#8217;s web page there&#8217;s entire index of &lt;a href=&quot;https://webkit.org/web-inspector/&quot;&gt;articles on Web Inspector&lt;/a&gt;. Among those, the most interesting
read is about &lt;a href=&quot;https://webkit.org/web-inspector/timelines-tab/&quot;&gt;Timelines Tab&lt;/a&gt; where the most useful debugging can be done. Once the features of &lt;strong&gt;Timelines Tab&lt;/strong&gt; are understood, the next important article is
&lt;a href=&quot;https://webkit.org/blog/6425/memory-debugging-with-web-inspector/&quot;&gt;the memory debugging guide&lt;/a&gt;. It dives into the most important &lt;strong&gt;Timelines Tab&lt;/strong&gt; subsections and showcases the work with heap snapshots which is a key. To supplement it,
it&#8217;s very important to know the &lt;strong&gt;heap snapshot delta&lt;/strong&gt; feature which is basically about button:&lt;/p&gt;
&lt;center&gt;
&lt;source type=&quot;image/avif&quot;&gt;&lt;source type=&quot;image/webp&quot;&gt;&lt;img alt=&quot;Web Inspector heap delta.&quot; src=&quot;https://blogs.igalia.com/plampe/img/eSkdO_v8QP-1261.png&quot; width=&quot;1261&quot; height=&quot;285&quot; /&gt;
&lt;/source&gt;&lt;/source&gt;&lt;/center&gt;
&lt;p&gt;that allows one to inspect the delta-snapshot between 2 snapshots. It&#8217;s critical as it answers the question on what JS objects were added between the base snapshot and the later one. If some objects are piling up, it immediately shows
which ones.&lt;/p&gt;
&lt;p&gt;One important note on snapshots is that in some cases when using Web Inspector is not possible, one can generate the snapshots manually from the web engine&#8217;s C++ code by just calling &lt;code&gt;GarbageCollectionController::singleton().dumpHeap();&lt;/code&gt; at
some appropriate moment. In this case, the dump will be written to standard output. It can be then turned into a file and imported from any &lt;strong&gt;Web Inspector&lt;/strong&gt; using &lt;strong&gt;Import&lt;/strong&gt; button.&lt;/p&gt;
&lt;p&gt;As the &lt;strong&gt;Timelines Tab&lt;/strong&gt; with its subsections should be able to answer on &lt;em&gt;what happens&lt;/em&gt;, to understand &lt;em&gt;why&lt;/em&gt; it actually happens, the last missing piece is the JS debugger within &lt;strong&gt;Web Inspector&lt;/strong&gt;. It&#8217;s not very different to debuggers in
other engines, but it&#8217;s worth checking a &lt;a href=&quot;https://webkit.org/web-inspector/javascript-breakpoints/&quot;&gt;dedicated article&lt;/a&gt; on it just to understand the capabilities.&lt;/p&gt;
&lt;h2 id=&quot;appendix&quot; tabindex=&quot;-1&quot;&gt;Appendix &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;things-to-check-without-rerunning-the-webkit&quot; tabindex=&quot;-1&quot;&gt;Things to check without rerunning the webkit &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Even if the WPE is running with default settings in release mode, there are plenty of useful things that can be checked while the browser is still running:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identifying which WebKit process allocates abnormally,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;there are multiple ways to do this, but usually it&#8217;s as easy as using &lt;code&gt;ps&lt;/code&gt; utility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identifying how fast the process in question allocates the memory,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;this is useful to know at least for comparison purposes, but it may hint some problems already if the numbers correlate with what web application does.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checking logs from stdout, stderr, and journal (using &lt;code&gt;journalctl&lt;/code&gt;).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Checking detailed process memory statistics&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Triggering and checking the impact of memory pressure&lt;/a&gt; on given processes RSS,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;in short, memory pressure triggers the cleanup of the majority of caches along with GC. Therefore, if this is able to bring memory back to normal level, then the problem is about caches, JS Heap / GC, or fragmentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Attaching memory profilers&lt;/a&gt; if available,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;even if the debug symbols are not present, this may be useful to see what data is being captured and how the web application behaves when slowed down by profiler.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Attaching other tools&lt;/a&gt; if available,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;even if the debug symbols are not present, various tools offer different perspectives on what the browser is doing. In some cases, such information may reveal some anomalies that may be related to the main issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-checking with other browsers,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if other browsers show a similar pattern of memory usage, it&#8217;s very likely the problem lies in web application itself. Otherwise, it strongly suggests a bug in the WPE.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-checking with other ports,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if any other WebKit port shows a similar pattern of memory usage, it allows one to narrow down the area in the code a bit based on what port it is:
&lt;ul&gt;
&lt;li&gt;if the same behavior is visible in any of &lt;strong&gt;Apple&lt;/strong&gt; ports, the problem is most likely related to cross-platform code,&lt;/li&gt;
&lt;li&gt;if the same behavior is visible only in &lt;strong&gt;GTK&lt;/strong&gt; port, then the problem is most likely related to GLib-related part, coordinated graphics part, GStreamer-related part, or others that are shared.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;things-to-check-without-rebuilding-the-webkit&quot; tabindex=&quot;-1&quot;&gt;Things to check without rebuilding the webkit &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Tweaking and checking the logs from WPE&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;while generic logs may hint some unusual behavior, more specific ones such as GC logs (&lt;code&gt;JSC_logGC=1&lt;/code&gt;) may be used to check how the individual JS heap sizes evolve over time and how GC behaves. &lt;strong&gt;If it&#8217;s JavaScript
leaking the memory, this log will quickly provide the evidence&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Enabling Remote Web Inspector&lt;/a&gt; and checking:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;both breakdown and trend of memory usage in the &lt;a href=&quot;https://webkit.org/web-inspector/timelines-tab/#memory-timeline&quot;&gt;memory timeline&lt;/a&gt; after doing a bit of recording,&lt;/li&gt;
&lt;li&gt;the effects of &lt;code&gt;takeHeapSnapshot()&lt;/code&gt; invoked from JS console:
&lt;ul&gt;
&lt;li&gt;as this function usually triggers GC internally, it may be used to check how much RSS memory is reclaimed by GC in isolation (followed up by scavenger),&lt;/li&gt;
&lt;li&gt;as this function takes a JS heap snapshot, it then can be used to explore &lt;a href=&quot;https://webkit.org/blog/6425/memory-debugging-with-web-inspector/&quot;&gt;manually&lt;/a&gt; if its contents point towards something interesting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Disabling JIT&lt;/a&gt; and checking the memory usage,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if the memory usage is stable with JIT disabled, one should proceed to the step below.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Limiting JIT&lt;/a&gt; and checking the memory usage,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;there are at least a few places (levels) where JIT compilation engine allocates memory. If limiting doesn&#8217;t resolve the issue completely, it&#8217;s likely the engine itself leaks some memory around temporary helper-heaps such as &lt;code&gt;AssemblerData&lt;/code&gt; etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Experimenting with environment variables and runtime preferences&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;some environment variables and runtime preferences change the behavior of the web engine significantly. If changing one of them makes the problem go away, it usually helps to narrow down the problematic area quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Running WPE with system malloc (environment variable &lt;code&gt;Malloc=1&lt;/code&gt;) and checking the memory usage,&lt;/strong&gt; &lt;!-- comparing memory usage vs bmalloc/libpas,** &lt;\!-- (`Malloc=1`) -\-&gt; --&gt;
&lt;ul&gt;
&lt;li&gt;when one suspects bmalloc/libpas issues with fragmentation or scavenger, it&#8217;s worth running a browser with system malloc to compare the memory evolution over time against the bmalloc/libpas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Limiting device memory&lt;/a&gt; and checking the memory usage,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if &lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;triggering memory pressure&lt;/a&gt;&lt;/strong&gt; is not possible, an alternative solution is to limit the device memory so that the browser is under constant memory pressure.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Running WPE with sysprof&lt;/a&gt; and checking:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;stack traces&lt;/strong&gt; &#8212; to see what parts of engine are particularly active as it may hint some problematic area,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebKit marks&lt;/strong&gt; &#8212; to see what the engine is doing as well as quantitative data in marks such as &lt;code&gt;EventLoopRun&lt;/code&gt; etc. as in those cases the numeric value trends may reveal resource pile up.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;things-to-check-if-rebuilding-webkit&quot; tabindex=&quot;-1&quot;&gt;Things to check if rebuilding webkit &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Building WPE in release mode with debug symbols&lt;/a&gt; and re-trying memory profilers or other tools if the debug symbols were not present before,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if some desired tools such as &lt;strong&gt;heaptrack&lt;/strong&gt;, &lt;strong&gt;valgrind&lt;/strong&gt;, &lt;strong&gt;perf&lt;/strong&gt;, or &lt;strong&gt;strace&lt;/strong&gt; were not available before, it&#8217;s the right moment to get/build them as well,&lt;/li&gt;
&lt;li&gt;once the debug symbols are in, one should try:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;attaching memory profilers&lt;/a&gt;&lt;/strong&gt; if available, and inspecting detailed memory allocation reports,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;attaching other tools&lt;/a&gt;&lt;/strong&gt; if available, and investigating the traces.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building and running with Google perftools,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;as WPE allows switching to system malloc as an allocator, it&#8217;s possible to use custom malloc implementation with instrumentation such as &lt;a href=&quot;https://github.com/gperftools/gperftools&quot;&gt;gperftools&lt;/a&gt;. For that, the recommended read is
&lt;a href=&quot;https://http503.gvatas.in/2024/10/09/diagnosing-memory-leaks-in-wpewebkit-with-google-perftools/&quot;&gt;this article&lt;/a&gt; from fellow Igalian, Pablo Saavedra.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Building and running with sanitizers&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;if the problem is about low-level leak, address/leak sanitizer should be able to help pointing out the problematic area.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Building and running with memory sampler&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;the data produced by memory sampler is roughly the same as inspector&#8217;s &lt;a href=&quot;https://webkit.org/web-inspector/timelines-tab/#memory-timeline&quot;&gt;memory timeline&lt;/a&gt;, however, it&#8217;s much more convenient as it doesn&#8217;t need web inspector at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Building and running with node statistics&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;when memory growth seems to be related to DOM mutations, it&#8217;s worth enabling and reporting node statistics periodically &#8212; in some cases, it may directly suggest what the problem is about.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building and running with malloc heap breakdown,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;when all other means fail, a very good last-resort approach for investigating memory usage statistics via a debug-only WebKit feature called &lt;strong&gt;Malloc Heap Breakdown&lt;/strong&gt;. The details can be found in
&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/tracking-webkit-s-memory-allocations-with-malloc-heap-breakdown/&quot;&gt;the dedicated article about it&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;Building and running with libpas statistics&lt;/a&gt;,&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;On very rare occasions such as memory fragmentation or allocation issues, it may be worth checking the &lt;a href=&quot;https://docs.webkit.org/Deep%20Dive/Libpas/Libpas.html&quot;&gt;libpas&lt;/a&gt; (low-level memory allocation and management library)
statistics as WPE uses it by default on the vast majority of platforms.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;individual-instructions&quot; tabindex=&quot;-1&quot;&gt;Individual instructions &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;checking-detailed-process-memory-statistics&quot; tabindex=&quot;-1&quot;&gt;Checking detailed process memory statistics &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;As WPE WebKit uses multi-process architecture, there are multiple processes that can be checked, although the most interesting one is usually the &lt;strong&gt;Web Content Process&lt;/strong&gt;. Once the PID of the given process is determined (e.g., using &lt;code&gt;ps&lt;/code&gt; utility)
the usual steps to check detailed memory statistics are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat /proc/&amp;lt;PID&amp;gt;/status&lt;/code&gt; or &lt;code&gt;cat /proc/&amp;lt;PID&amp;gt;/statm&lt;/code&gt; for very basic statistics,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pmap -X &amp;lt;PID&amp;gt;&lt;/code&gt; - for detailed statistics (if available),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat /proc/&amp;lt;PID&amp;gt;/smaps_rollup&lt;/code&gt; and &lt;code&gt;cat /proc/&amp;lt;PID&amp;gt;/smaps&lt;/code&gt; for detailed statistics (requires &lt;code&gt;CONFIG_PROC_PAGE_MONITOR&lt;/code&gt; kernel configuration option).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;triggering-memory-pressure-from-os&quot; tabindex=&quot;-1&quot;&gt;Triggering memory pressure from OS &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;WPE uses a so-called &lt;strong&gt;Memory Pressure Monitor&lt;/strong&gt; to observe the memory usage in the system and to react if there&#8217;s not much memory left. The default thresholds are specified in &lt;code&gt;MemoryPressureMonitor.cpp&lt;/code&gt; and usually are
90% for non-critical and 95% for critical response. Depending on the response, WPE schedules GC and clears internal caches immediately.&lt;/p&gt;
&lt;p&gt;As the above is usually on by default, one can leverage it to trigger GC (along with cache cleanups) by filling up the available memory in the OS to 95+%. There are many ways to allocate memory, yet the simplest is using &lt;code&gt;stress&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;e.g. &lt;code&gt;stress --vm 1 --vm-bytes 1024M --vm-keep&lt;/code&gt; to allocate 1024 MB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;attaching-memory-profilers&quot; tabindex=&quot;-1&quot;&gt;Attaching memory profilers &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When attaching any memory profiler, unless one wants to profile only native allocations (Skia, GStreamer, ICU, etc.), the key is to use &lt;code&gt;Malloc=1&lt;/code&gt; environment variable on WPE startup so that bmalloc uses system malloc instead of libpas.
Then the commands are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to attach &lt;strong&gt;heaptrack&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;heaptrack -p &amp;lt;PID&amp;gt;&lt;/code&gt; so e.g. &lt;code&gt;heaptrack -p $(pgrep WPEWebProcess)&lt;/code&gt; (see &lt;a href=&quot;https://blogs.igalia.com/plampe/tracking-webkit-s-memory-allocations-with-malloc-heap-breakdown/#heaptrack&quot;&gt;this article&lt;/a&gt; for details),&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;to run with &lt;strong&gt;valgrind&#8217;s massif&lt;/strong&gt; (as attaching to running process is not possible):
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valgrind --tool=massif --trace-children=yes &amp;lt;WPE-BROWSER-COMMAND&amp;gt;&lt;/code&gt; (see &lt;a href=&quot;https://blogs.igalia.com/plampe/tracking-webkit-s-memory-allocations-with-malloc-heap-breakdown/#massif-valgrind&quot;&gt;this article&lt;/a&gt; for details).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;attaching-other-tools&quot; tabindex=&quot;-1&quot;&gt;Attaching other tools &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If memory profilers are unusable or unavailable, it&#8217;s worth checking if other tools are present and experimenting a bit with them if so. In some cases, tools other than memory profilers may give some hints on further investigation
or reveal a suspicious pattern within application execution. Some ideas for experiments with various tools are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;strace:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strace -c -p $(pgrep WPEWebProcess)&lt;/code&gt; &#8212; &lt;strong&gt;strace&lt;/strong&gt; called with &lt;code&gt;-c&lt;/code&gt; gives a nice summary of system calls executed by the traced application. It can be useful to check the overall syscall usage pattern to see if there are any anomalies.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strace -p $(pgrep WPEWebProcess) -e trace=mmap,munmap,mremap,madvise -tt&lt;/code&gt; &#8212; &lt;strong&gt;strace&lt;/strong&gt; focused on &lt;code&gt;mmap()&lt;/code&gt;-related system calls may be useful to debug libpas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;perf:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;perf record -F 999 -ag -p $(pgrep WPEWebProcess) -- sleep 60&lt;/code&gt; &#8212; regular recording with &lt;strong&gt;perf&lt;/strong&gt; can be very useful, especially if symbols are available. With that, one can generate
&lt;a href=&quot;https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html&quot;&gt;flamegraphs&lt;/a&gt; and investigate what&#8217;s going on in the browser. While it&#8217;s not about profiling memory, it may be helpful to narrow down at least a bit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perf record -F 999 -e syscalls:sys_enter_mmap,syscalls:sys_enter_munmap,syscalls:sys_enter_mremap:sys_enter_madvise -ag -p $(pgrep WPEWebProcess) -- sleep 60&lt;/code&gt; &#8212; &lt;strong&gt;perf&lt;/strong&gt; focused on &lt;code&gt;mmap()&lt;/code&gt;-related system calls is much more superior
than e.g. &lt;strong&gt;strace&lt;/strong&gt; as it also records stack traces. Therefore, if debug symbols are present, and if the memory growth is very rapid, it&#8217;s very likely the libpas &lt;code&gt;mmap()&lt;/code&gt; stacktraces will lead to the growth origin statistically.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perf trace -e mmap,munmap,mremap,madvise -p $(pgrep WPEWebProcess)&lt;/code&gt; &#8212; this is very much similar to &lt;strong&gt;strace&lt;/strong&gt; focused on &lt;code&gt;mmap()&lt;/code&gt;-related system calls as it shows a live preview of what&#8217;s happening.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sysprof:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sysprof-cli -f&lt;/code&gt; &#8212; while running system-wide &lt;strong&gt;sysprof&lt;/strong&gt; won&#8217;t make WPE push marks into it, the profiling trace may still be useful to some degree, especially if debug symbols are available.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;disabling-jit&quot; tabindex=&quot;-1&quot;&gt;Disabling JIT &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This can be done using an environment variable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JSC_useJIT=false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;limiting-jit&quot; tabindex=&quot;-1&quot;&gt;Limiting JIT &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Limiting JIT can be achieved via environment variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JSC_jitMemoryReservationSize=&amp;lt;BYTES&amp;gt;&lt;/code&gt; to limit JIT memory usage (the limit is semi-strict as some JIT compilation engine buffers are limited by this value indirectly),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSC_useFTLJIT=false&lt;/code&gt; to disable FTL tier,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSC_useDFGJIT=false&lt;/code&gt; to disable DFG and FTL tiers,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSC_useBaselineJIT=false&lt;/code&gt; to disable Baseline, DFG, and FTL tiers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tweaking-wpe-logs&quot; tabindex=&quot;-1&quot;&gt;Tweaking WPE logs &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;WPE is a fairly complex piece of software and hence it offers various logging capabilities related to WebKit itself, as well as to related libraries. The vast majority of logging can be controlled via environment variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_DEBUG=all&lt;/code&gt; to enable all logging channels,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_DEBUG=Layout,Media=debug,Events=debug&lt;/code&gt; to enable selected logging channels,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSC_logGC=2&lt;/code&gt; to enable JS garbage collector logs,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GST_DEBUG=4&lt;/code&gt; to enable gstreamer (multimedia-related) logs (see &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/tutorials/basic/debugging-tools.html?gi-language=c#printing-debug-information&quot;&gt;the documentation&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;G_MESSAGES_DEBUG=all&lt;/code&gt; to enable GLib-level logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;strong&gt;MiniBrowser&lt;/strong&gt; (or similar browser) is used, one can also set a runtime preference to enable JS &lt;code&gt;console.log(...)&lt;/code&gt; logging to the standard output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--features=+LogsPageMessagesToSystemConsole&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;enabling-remote-web-inspector&quot; tabindex=&quot;-1&quot;&gt;Enabling remote web inspector &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Enabling WPE&#8217;s remote web inspector is a twofold process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first step is to run WPE with the proper environment variable so that it starts listening on &lt;code&gt;IP:PORT&lt;/code&gt; using tcp socket:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_INSPECTOR_SERVER=IP:PORT&lt;/code&gt; is the most reasonable option as it uses &lt;code&gt;inspector://&lt;/code&gt; protocol that can be utilized by WebKit-native browsers such as &lt;a href=&quot;https://apps.gnome.org/Epiphany/&quot;&gt;GNOME Web (Epiphany)&lt;/a&gt; or Safari,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_INSPECTOR_HTTP_SERVER=IP:PORT&lt;/code&gt; is a less preferable alternative that uses HTTP protocol and technically works from any browser. However, no seamless integration is guaranteed in this case.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;The second step is to connect from a regular web browser to the WPE:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;using &lt;code&gt;inspector://IP:PORT/&lt;/code&gt; if native inspector server was started,&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;http://IP:PORT/&lt;/code&gt; if HTTP inspector server was started,&lt;/li&gt;
&lt;li&gt;forwarding the ports using &lt;code&gt;socat tcp-l:PORT,fork,reuseaddr tcp:IP:PORT&lt;/code&gt; if the WPE is running in unreachable network.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;experimenting-with-environment-variables-and-runtime-preferences&quot; tabindex=&quot;-1&quot;&gt;Experimenting with environment variables and runtime preferences &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The most outstanding environment variables changing the behavior of WPE are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WPE_DISPLAY&lt;/code&gt; &#8212; assuming the &lt;a href=&quot;https://wpewebkit.org/blog/2026-03-18-wpewebkit-2.52.html#api-changes&quot;&gt;new WPE platform API&lt;/a&gt; is used, this environment variable allows one to switch the pre-defined platform implementation thus
changing a platform-facing part of graphics pipeline. The valid options are:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WPE_DISPLAY=wpe-display-headless&lt;/code&gt; &#8212; for headless implementation,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WPE_DISPLAY=wpe-display-drm&lt;/code&gt; &#8212; for direct rendering manager integration,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WPE_DISPLAY=wpe-display-wayland&lt;/code&gt; &#8212; for wayland integration,&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_SKIA_ENABLE_CPU_RENDERING&lt;/code&gt; &#8212; when set to &lt;code&gt;1&lt;/code&gt;, rendering the DOM contents to the layers is done using Skia CPU backend instead of GPU one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most outstanding runtime preferences changing the behavior of WPE are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CanvasUsesAcceleratedDrawing&lt;/code&gt; &#8212; when disabled, 2D canvas will use Skia CPU backend instead of GPU one,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LayerBasedSVGEngine&lt;/code&gt; &#8212; when enabled, WPE uses a different SVG engine internally,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AcceleratedCompositing&lt;/code&gt; &#8212; when disabled, WPE uses experimental, non-composited mode that bypasses all of the compositor work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;limiting-device-memory&quot; tabindex=&quot;-1&quot;&gt;Limiting device memory &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;On the majority of embedded devices, the device memory can be limited by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Interrupting the boot sequence (usually holding some key such as &lt;code&gt;z&lt;/code&gt; upon booting),&lt;/li&gt;
&lt;li&gt;Invoking the command to change the limit and booting, e.g.:&lt;pre&gt;&lt;code&gt;&amp;gt; global linux.bootargs.console=&amp;quot;console=ttymxc0,115200n8 mem=2G&amp;quot;
&amp;gt; boot
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;running-wpe-with-sysprof&quot; tabindex=&quot;-1&quot;&gt;Running WPE with sysprof &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Regardless of whether it&#8217;s done on desktop (using &lt;a href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;wkdev-sdk&lt;/a&gt;) or on embedded device, the command is always as simple as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sysprof-cli -f -- &amp;lt;WPE-INVOCATION&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a href=&quot;https://wpewebkit.org/reference/unstable/wpe-webkit-2.0/profiling.html#profiling-with-sysprof&quot;&gt;the documentation entry&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h4 id=&quot;building-wpe-in-release-mode-with-debug-symbols&quot; tabindex=&quot;-1&quot;&gt;Building WPE in release mode with debug symbols &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;On desktop, the simplest way to get release with debug symbols is to utilize CMake&#8217;s build type by using &lt;code&gt;-DCMAKE_BUILD_TYPE=RelWithDebInfo&lt;/code&gt; within WPE build command, so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./Tools/Scripts/build-webkit --wpe --release --cmakeargs=&amp;quot;-DCMAKE_BUILD_TYPE=RelWithDebInfo&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On embedded, when Yocto is used, one should tweak settings such as:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;IMAGE_GEN_DEBUGFS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;                                                         &lt;br /&gt;IMAGE_FSTYPES_DEBUGFS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tar.bz2&quot;&lt;/span&gt;&lt;br /&gt;DEBUG_BUILD &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;br /&gt;EXTRA_IMAGE_FEATURES_append &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; dbg-pkgs&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and potentially &lt;code&gt;INHIBIT_PACKAGE_STRIP&lt;/code&gt; to control whether debug symbols should be kept with the binary or not. This may be necessary occasionally as some tools have problems reading &lt;code&gt;.gnu_debuglink&lt;/code&gt; and therefore work only
with symbols included in the binaries.&lt;/p&gt;
&lt;h4 id=&quot;building-and-running-with-sanitizers&quot; tabindex=&quot;-1&quot;&gt;Building and running with sanitizers &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;WebKit works pretty well with all kinds of sanitizers. To build with any of them a CMake-level helper called &lt;code&gt;ENABLE_SANITIZERS&lt;/code&gt; can be used by specifying &lt;code&gt;-DENABLE_SANITIZERS=address&lt;/code&gt;, &lt;code&gt;-DENABLE_SANITIZERS=leak&lt;/code&gt; etc. With that, the command for
building e.g. on desktop could look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./Tools/Scripts/build-webkit --wpe --debug --cmakeargs=-DENABLE_SANITIZERS=address&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details, one can refer to &lt;a href=&quot;https://blogs.igalia.com/fujii/building-webkit-and-libsoup-with-addresssanitizer-asan/&quot;&gt;this article&lt;/a&gt; from fellow Igalian, Fujii.&lt;/p&gt;
&lt;h4 id=&quot;building-and-running-with-memory-sampler&quot; tabindex=&quot;-1&quot;&gt;Building and running with memory sampler &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When WPE is built with &lt;code&gt;-DENABLE_MEMORY_SAMPLER=ON&lt;/code&gt;, the simple memory sampler can be started along with the browser using environment variable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WEBKIT_SAMPLE_MEMORY=1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that, the memory of various WPE processes is sampled every second, and saved to the files under &lt;code&gt;/tmp&lt;/code&gt; directory continuously.&lt;/p&gt;
&lt;h4 id=&quot;building-and-running-with-node-statistics&quot; tabindex=&quot;-1&quot;&gt;Building and running with node statistics &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Node statistics are a debug-only feature that can be enabled by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changing &lt;code&gt;0&lt;/code&gt; of &lt;code&gt;#define DUMP_NODE_STATISTICS 0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; in &lt;code&gt;Source/WebCore/dom/Element.h&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;adding &lt;code&gt;dumpStatistics()&lt;/code&gt; call, to e.g. &lt;code&gt;Node&lt;/code&gt; constructor in &lt;code&gt;Source/WebCore/dom/Node.cpp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;building-and-running-with-libpas-statistics&quot; tabindex=&quot;-1&quot;&gt;Building and running with libpas statistics &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Libpas statistics are a debug-only feature that can be enabled by changing &lt;code&gt;0&lt;/code&gt; of &lt;code&gt;#define PAS_ENABLE_STATS 0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; in &lt;code&gt;Source/bmalloc/libpas/src/libpas/pas_config.h&lt;/code&gt; and then running WPE with environment variable &lt;code&gt;PAS_STATS_ENABLE=1&lt;/code&gt;.&lt;/p&gt;        </description>
	<pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Alex Bradbury: Minipost: at-agent</title>
	<guid>https://muxup.com/2026q2/minipost-at-agent</guid>
	<link>https://muxup.com/2026q2/minipost-at-agent</link>
	<description>
&lt;h2 id=&quot;motivation&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#motivation&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Like most people, I've been playing with agents to see where they're helpful,
where they're not, and what kind of workflows are a good match for me. One
area I've found friction is in iterating on a piece of code - written by me or
otherwise. I &lt;em&gt;can&lt;/em&gt; describe the relevant section and my question/request in
command line chat, but it would be better to do so directly inline and have
the LLM pick it up.
&lt;a href=&quot;https://github.com/muxup/medley/blob/main/at-agent&quot;&gt;&lt;code&gt;at-agent&lt;/code&gt;&lt;/a&gt; is a super
minimal approach for picking out such directives from your worktree and
processing them.&lt;/p&gt;
&lt;p&gt;This is very much a &quot;worse is better&quot; approach. A separate structured channel
for attaching questions or requests to spans of code would have advantages.
But that requires an interface for creating and editing such annotations as
well as logic for handling edits after the annotation was made. Sticking
&lt;code&gt;@agent&lt;/code&gt; comments directly inline means it's trivial to intermingle manual
edits with requests for action, it's trivial to keep comments attached to the
region they were intended for, and reviewing and editing them at the file or
repository level is easy through &lt;code&gt;git diff&lt;/code&gt; and your text editor of choice.&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; get away with just asking your LLM of choice to &quot;find all
comments prefixed with @agent in this codebase, treat them as directions to
you, action them, and then remove them&quot;. But I still believe in building on
solid primitives, and limited as this little script is, I'd rather lean on its
deterministic behaviour and reduce the number of round trips and tool calls
the LLM needs to handle successfully. So far it's been helpful for some kinds
of tasks and a handy tool to have in the toolbelt.&lt;/p&gt;
&lt;p&gt;There are surely no end of IDE-integrated solutions and vim plugins that offer
something similar. &lt;code&gt;aider&lt;/code&gt; also &lt;a href=&quot;https://aider.chat/docs/usage/watch.html&quot;&gt;supports 'AI'
comments&lt;/a&gt; but relies on the model to
remove them after the fact.&lt;/p&gt;
&lt;h2 id=&quot;interface&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#interface&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Interface&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;at-agent&lt;/code&gt; doesn't try to understand language-specific comment syntax. It looks
for a whole line that starts with optional whitespace, then at least two
punctuation characters such as &lt;code&gt;//&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, or &lt;code&gt;///&lt;/code&gt;, then whitespace and
&lt;code&gt;@agent&lt;/code&gt;. A following &lt;code&gt;!&lt;/code&gt; marks an action request rather than a question, and
the rest of the line is the request text. Subsequent lines with the same
comment prefix are consumed as part of the same &lt;code&gt;@agent&lt;/code&gt; directive.&lt;/p&gt;
&lt;p&gt;What this means is that you might write something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Example: // @agent explain why std::vector isn't used here. How does the
Example: // custom vector compare in terms of reallocation strategy?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Example:&lt;/code&gt; prefix is only there to keep this blog post from containing live
annotations. Without it, running &lt;code&gt;at-agent&lt;/code&gt; over the Markdown file would treat
the example itself as a real request and remove it.&lt;/p&gt;
&lt;p&gt;Or for an action request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Example: ## @agent! split this into a helper and update the two other callers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@agent&lt;/code&gt; is a comment/explanation request, and &lt;code&gt;@agent!&lt;/code&gt; is a request to make
an edit. Only whole-line annotations are supported. If you want to nest a
request inside a long comment, just tweak the prefix appropriately, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Example: // This is a multi-line comment. We want to place a directive in it.
Example: /// @agent explain the paragraph below, with a worked example
         /// alongside appropriate source code snippets.
Example: // Normal comment continues here.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the tool removes the annotation lines from the working tree and emits
something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The user manually added these annotations for you, the agent, to react to.
They are either comment/explanation requests, which ask you to explain nearby
code or answer the user's question without editing files, or action requests,
which ask you to investigate and make the requested code change.

The @agent remark lines have already been removed from the working tree.
Relevant line numbers refer to the files after remark removal.

1. example.cc
   kind: comment/explanation request
   relevant line number: 42
   text:
     @agent explain why std::vector isn't used here. How does the
     custom vector compare in terms of reallocation strategy?
   nearby context before removal:
     ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;usage&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#usage&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Usage&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;at-agent&lt;/code&gt; can be pointed at specific files listed in command line arguments
(passing files selects args mode automatically), or via &lt;code&gt;--discover=rg&lt;/code&gt;
recursively grep the current working directory, or via
&lt;code&gt;--discover=inodes&lt;/code&gt; (or indeed, no args) walk the current working directory
recursively to find inodes flagged as being modified recently and potentially
containing &lt;code&gt;@agent&lt;/code&gt; directives. For that default mode, the idea is you set
your text editor to append to that list of inodes as you edit files and leave
&lt;code&gt;@agent&lt;/code&gt; directives in them. This is particularly helpful to avoid expensive
greps on large trees. There is also &lt;code&gt;--dry-run&lt;/code&gt;, which reports directives
without removing annotation lines.&lt;/p&gt;
&lt;p&gt;The use of inode numbers rather than path names means that this works
comfortably in the scenario where you are editing files in your normal
environment, but an agent is running &lt;a href=&quot;https://muxup.com/shandbox&quot;&gt;in a sandbox&lt;/a&gt; and so
may have paths mounted at a different location.&lt;/p&gt;
&lt;p&gt;You can just add something like this to your &lt;code&gt;.vimrc&lt;/code&gt; (the filter is
imprecise, but this doesn't matter as &lt;code&gt;at-agent&lt;/code&gt; will just do nothing for
files that have no valid directives):&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;let&lt;/span&gt; s:at_agent_dir = expand(&lt;span&gt;'~/.local/state/at-agent'&lt;/span&gt;)

&lt;span&gt;function&lt;/span&gt;! s:NoteAtAgentInode() abort
  &lt;span&gt;let&lt;/span&gt; &lt;span&gt;l&lt;/span&gt;:&lt;span&gt;file&lt;/span&gt; = expand(&lt;span&gt;'%:p'&lt;/span&gt;)
  &lt;span&gt;if&lt;/span&gt; empty(&lt;span&gt;l&lt;/span&gt;:&lt;span&gt;file&lt;/span&gt;) || !isdirectory(s:at_agent_dir) || search(&lt;span&gt;'\s@agent'&lt;/span&gt;, &lt;span&gt;'nw'&lt;/span&gt;) == &lt;span&gt;0&lt;/span&gt;
    &lt;span&gt;return&lt;/span&gt;
  &lt;span&gt;endif&lt;/span&gt;

  &lt;span&gt;call&lt;/span&gt; system(
        \ &lt;span&gt;'stat -c %i -- '&lt;/span&gt; . shellescape(&lt;span&gt;l&lt;/span&gt;:&lt;span&gt;file&lt;/span&gt;) .
        \ &lt;span&gt;' &amp;gt;&amp;gt; '&lt;/span&gt; . shellescape(s:at_agent_dir . &lt;span&gt;'/inodes'&lt;/span&gt;))
&lt;span&gt;endfunction&lt;/span&gt;

augroup at_agent
  autocmd!
  autocmd &lt;span&gt;BufWritePost&lt;/span&gt; * &lt;span&gt;call&lt;/span&gt; s:NoteAtAgentInode()
augroup END
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So a simple flow would be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Leave notes in the files while editing.&lt;/li&gt;
&lt;li&gt;Save as normal in vim.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;at-agent&lt;/code&gt; from the repository root in the agent session.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are all kinds of ways this could be integrated into a harness, but I
have a fondness for no integration at all, meaning it's easy to switch between
different options. e.g. just give a prompt such as &quot;Run &lt;code&gt;at-agent&lt;/code&gt; and action
its output.&quot;.&lt;/p&gt;
&lt;p&gt;If using &lt;code&gt;shandbox&lt;/code&gt; or similar, the list of inodes that potentially contain
directives needs to be exposed at the expected location. You can add something
like this to &lt;code&gt;.shandbox_meta/init&lt;/code&gt; (or &lt;code&gt;~/.config/shandbox/default-init&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;#!/bin/sh&lt;/span&gt;
&lt;span&gt;host_at_agent_dir=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.local/state/at-agent&amp;quot;&lt;/span&gt;
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$host_at_agent_dir&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$SHANDBOX_SELF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; add-mount --read-write &lt;span&gt;\&lt;/span&gt;
  &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$host_at_agent_dir&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  &lt;span&gt;&amp;quot;.local/state/at-agent&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-05-31: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Sun, 31 May 2026 12:00:00 +0000</pubDate>
</item>
<item>
	<title>Eric Meyer: Accessible (I Think) Split-Cell Table Headers</title>
	<guid>https://meyerweb.com/eric/thoughts/?p=5756</guid>
	<link>https://meyerweb.com/eric/thoughts/2026/05/28/accessible-i-think-split-cell-table-headers/</link>
	<description>
&lt;p&gt;My colleague Chris Griffith, with whom I collaborated to put &lt;a href=&quot;https://atomicarchive.com/resources/documents/effects/glasstone-dolan/&quot;&gt;&lt;cite&gt;The
Effects of Nuclear Weapons, Third Edition&lt;/cite&gt; (1977) online&lt;/a&gt;, is
also a &lt;a href=&quot;https://www.american-spacecraft.org/&quot;&gt;spaceflight
enthusiast&lt;/a&gt; (and an urban trails hiker: check out &lt;a href=&quot;https://urbantrails-sandiego.com/&quot;&gt;his new book&lt;/a&gt;!).&#160; He recently
asked me how I would mark up a table with a split diagonal header cell;
specifically, this one from the Apollo 16 documentation:&lt;/p&gt;

&lt;figure class=&quot;standalone&quot;&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/apollo_16-page_050-crop.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/apollo_16-page_050-crop.png&quot; alt=&quot;&quot; width=&quot;1500&quot; height=&quot;949&quot; class=&quot;alignnone size-full wp-image-5758&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;The top left corner of the page in question.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My immediate thought was to throw two spans in the header cell and
position or grid them within that cell, but the accessibility of that
seemed&#8230; questionable.&#160; It&#8217;s also &lt;a href=&quot;https://en.wikipedia.org/wiki/Template:Diagonal_split_header&quot;&gt;what
Wikipedia already does&lt;/a&gt;, and we here at meyerweb are nothing if not
obsessed with finding new ways to do niche stuff.&#160; So I tried something
different.&#160; But is its accessibility any better?&lt;/p&gt;

&lt;p&gt;If you want to see it as a live example, &lt;a href=&quot;https://codepen.io/meyerweb/pen/OPbyoWw?editors=1100&quot;&gt;it&#8217;s over at
Codepen&lt;/a&gt;.&#160; Most of the text in the table is what macOS Preview OCRed
out of the original image, which I kept intact because I think it&#8217;s
funny.&#160; Anyway, here is the original markup I came up with for the 
table head, which &lt;strong&gt;you should not use&lt;/strong&gt;:&lt;/p&gt;

&lt;pre class=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;thead&amp;gt;
	&amp;lt;tr&amp;gt;
		&amp;lt;th scope=&amp;quot;row&amp;quot;&amp;gt;SCIENTIFIC DISCIPLINE&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;GEOLOGY&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;GEOPHYSICS&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;GEOCHEMISTRY&amp;lt;/th&amp;gt;
	&amp;lt;/tr&amp;gt;
	&amp;lt;tr&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;EXPERIMENT&amp;lt;/th&amp;gt;
	&amp;lt;/tr&amp;gt;
 &amp;lt;/thead&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So one row for the headers across the top of the table, including the
top-left label that goes with them, and then another row with the header
that relates to the row headers for the rows below.&#160; That is to say, the
row-scoped table header in each of the rows in the table&#8217;s bodies (it
has more than one), like this:&lt;/p&gt;

&lt;pre class=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;tbody&amp;gt;
	&amp;lt;tr&amp;gt;
		&amp;lt;th scope=&amp;quot;row&amp;quot;&amp;gt;CONTINGENCY SAMPLE COLLECTION&amp;lt;/th&amp;gt;
		[&#8230;]
	&amp;lt;/tr&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The thing is, when I ran the idea past accessibility experts like &lt;a href=&quot;https://alice.boxhall.au/&quot; rel=&quot;colleague met&quot;&gt;Alice Boxhall&lt;/a&gt; and &lt;a href=&quot;https://adrianroselli.com/&quot; rel=&quot;colleague met&quot;&gt;Adrian Roselli&lt;/a&gt;, they identified
a problem: Not having a full row of cells, as is the case for the second
header row, fails &lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/sensory-characteristics.html&quot;&gt;WCAG
1.3.3&lt;/a&gt;.&#160; The suggested fix was to rowspan most of the cells in the
first row, like this:&lt;/p&gt;

&lt;pre class=&quot;html&quot;&gt;&lt;code&gt; &amp;lt;thead&amp;gt;
	&amp;lt;tr&amp;gt;
		&amp;lt;th scope=&amp;quot;row&amp;quot;&amp;gt;SCIENTIFIC DISCIPLINE&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot; rowspan=&amp;quot;2&amp;quot;&amp;gt;GEOLOGY&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot; rowspan=&amp;quot;2&amp;quot;&amp;gt;GEOPHYSICS&amp;lt;/th&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot; rowspan=&amp;quot;2&amp;quot;&amp;gt;GEOCHEMISTRY&amp;lt;/th&amp;gt;
	&amp;lt;/tr&amp;gt;
	&amp;lt;tr&amp;gt;
		&amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;EXPERIMENT&amp;lt;/th&amp;gt;
	&amp;lt;/tr&amp;gt;
 &amp;lt;/thead&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With that, table navigation wasn&#8217;t perfect, but it seemed decent, so
we could move forward.&lt;/p&gt;

&lt;p&gt;In terms of presentation, to get the upper-left header cell to do the
split-diagonal thing, I relatively position the
&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; and then absolutely position the second row
in the table head to sit over top of the first, pinned to the bottom
left corner.&lt;/p&gt;

&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;thead {
	position: relative;
}
thead tr:nth-child(2) th {
	&#160; position: absolute;
	&#160; bottom: 0;
	&#160; left: 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I fiddled around for a bit with trying to use a grid instead, but it
didn&#8217;t really add anything that positioning didn&#8217;t already provide and
threw some other wrenches into the works, like having to convert the
entire table into a grid so the columns would stay aligned, so I decided
to just stick with the positioning.&lt;/p&gt;

&lt;p&gt;Then I throw a linear gradient background into the first row&#8217;s first
cell to draw the diagonal, and everything&#8217;s thus more or less as
intended, visually speaking.&#160; (That diagonal could also be an SVG, in
fact probably should be in production, but I was seeing how an all-CSS
solution might work so a gradient is where things stand.)&lt;/p&gt;

&lt;p&gt;There are some layout caveats with this approach, but they&#8217;re pretty
much the same as other solutions I saw: primarily, the two bits of text
that the diagonal visually separates can stick out of their respective
halves of the split cell, or even overlap each other.&#160; Also, you
might need to explicitly set a minimum height of the first header row,
in order to not exacerbate the overlap risk just described.&lt;/p&gt;

&lt;p&gt;And then there&#8217;s a really big caveat: Safari, as of this writing,
doesn&#8217;t handle the layout at all well, because it doesn&#8217;t apply
relative positioning to &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; (or
&lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt;, but at least
it does &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;s).&#160; I went to file a bug and found &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=203449&quot;&gt;there&#8217;s already
one open&lt;/a&gt;, so maybe this will be fixed in the near future.&#160; I figured
out a way to get at least close to the intended result while still
allowing line-wrapping in the column header cells, but it mangled the
layout in Firefox and Chrome.&#160; In the end, to work around the problem, I
delved into &lt;a href=&quot;https://browserstrangeness.github.io/css_hacks.html&quot;&gt;browser
strangeness&lt;/a&gt; (at the suggestion of &lt;a href=&quot;https://mariusgundersen.net/&quot;&gt;Marius Gundersen&lt;/a&gt;) and settled on
the following:&lt;/p&gt;

&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;/* this is gross and I hate it but it works to fix 
&#160;  Safari&#8217;s layout of the table&#8217;s top headers */
@supports (font: -apple-system-body) {
	thead tr:nth-child(1) th {
		white-space: nowrap;
	}
	thead tr:nth-child(2) th {
		position: static;
		display: block;
		margin-block: -1.5lh 0;
		padding-block: 0;
		text-align: start;
		transform: translateY(0.25lh);
	}
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Thanks, I hate it!&#160; But it works, and I try to be pragmatic.&lt;/p&gt;

&lt;p&gt;Anyway, the point being, what I&#8217;ve done here &lt;em&gt;feels&lt;/em&gt; more accessible 
to me, and basic testing by both me and Adrian didn&#8217;t reveal any major
problems, but I still worry about the positioning dorking things up for
the users of screen readers I don&#8217;t have access to.&#160; So I throw it to the
audience, particularly the accessibility-technology-using part of the
audience: does this solution fall down for you, or is it good enough?
Please let me know!&lt;/p&gt;
	&lt;hr /&gt;&lt;p&gt;Have something to say to all that?  You can &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2026/05/28/accessible-i-think-split-cell-table-headers/#commentform&quot;&gt;add a comment to the post&lt;/a&gt;, or &lt;a href=&quot;mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Accessible%20(I%20Think)%20Split-Cell%20Table%20Headers%22&quot;&gt;email Eric directly&lt;/a&gt;.&lt;/p&gt;        </description>
	<pubDate>Thu, 28 May 2026 13:05:08 +0000</pubDate>
	<dc:creator>Eric Meyer</dc:creator>
</item>
<item>
	<title>Alex Bradbury: shandbox</title>
	<guid>https://muxup.com/shandbox</guid>
	<link>https://muxup.com/shandbox</link>
	<description>
&lt;p&gt;&lt;a href=&quot;https://github.com/muxup/medley/blob/main/shandbox&quot;&gt;&lt;code&gt;shandbox&lt;/code&gt;&lt;/a&gt; is a simple
Linux sandboxing script that serves my needs well. Perhaps it works for you
too? No dependencies between a shell and util-linux (&lt;code&gt;unshare&lt;/code&gt; and &lt;code&gt;nsenter&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;In short, it aims to provide fairly good isolation for personal files (i.e.
your &lt;code&gt;$HOME&lt;/code&gt;) while being very convenient for day to day use. It's designed to
be run as an unprivileged user - as long as you can make new namespaces you
should be good to go. By default &lt;code&gt;/home/youruser/sandbox&lt;/code&gt; shows up as
&lt;code&gt;/home/sandbox&lt;/code&gt; within the sandbox, and other than standard paths like &lt;code&gt;/usr&lt;/code&gt;,
&lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;/tmp&lt;/code&gt;, and so on it's left for you to either copy things into the
sandbox or expose them via a mount. There's a single shared sandbox (i.e.
processes within the sandbox can see and interact with each other, and the
exposed sandbox filesystem is shared as well), which trades off some ease of
use for the security you might get with a larger number of more targeted
sandboxes. On the other hand, you only gain security from a sandbox if you
actually use it and this is a setup that offers very low friction for me. The
network is not namespaced (although this is something you could change with a
simple edit). If you do want more than one sandbox environment, see the
relevant section below.&lt;/p&gt;
&lt;p&gt;Usability is both subjective and highly dependent on your actual use case, so
the tradeoffs may or may not align with what is interesting for you!
&lt;a href=&quot;https://github.com/containers/bubblewrap&quot;&gt;Bubblewrap&lt;/a&gt; is an example of a
mature alternative unprivileged sandboxing
tool that offers a lot of configurability as well as options with greater
degrees of sandboxing. Beyond that, look to
&lt;a href=&quot;https://firecracker-microvm.github.io/&quot;&gt;Firecracker&lt;/a&gt; based solutions or
&lt;a href=&quot;https://gvisor.dev/&quot;&gt;gvisor&lt;/a&gt;. &lt;code&gt;shandbox&lt;/code&gt; obviously aims to provide a
reasonable sandbox as much as Linux namespaces alone are able to offer, but if
you're looking for a security property stronger than &quot;makes it harder for
something to edit or access unwanted files&quot; it's down to you to both carefully
review its implementation and consider alternatives. The recent spate of
disclosed &lt;a href=&quot;https://copy.fail/&quot;&gt;local&lt;/a&gt;
&lt;a href=&quot;https://github.com/V4bel/dirtyfrag&quot;&gt;privilege&lt;/a&gt;
&lt;a href=&quot;https://github.com/v12-security/pocs/tree/main/fragnesia&quot;&gt;escalation&lt;/a&gt;
&lt;a href=&quot;https://ubuntu.com/blog/pintheft-linux-kernel-vulnerability-mitigation&quot;&gt;vulnerabilities&lt;/a&gt;
is helpful to keep in mind as a reminder of the limits of this namespacing
based approach.&lt;/p&gt;
&lt;h2 id=&quot;usage-example&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#usage-example&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Usage example&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ shandbox run uvx pycowsay
initialised sandbox at /home/asb/sandbox
created default ssh config at /home/asb/sandbox/.ssh/config
to add an init hook, create an executable script at: /home/asb/sandbox/.shandbox_meta/init
started (pid 1589289)
Installed 1 package in 5ms

  ------------
&amp;lt; Hello, world &amp;gt;
  ------------
   \   ^__^
    \  (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||
$ shandbox status
running (pid 1589364)

log:
  2026-02-11 13:02:51 stopped
  2026-02-11 13:05:06 started (pid 1589289)
$ shandbox add-mount ~/repos/medley
mounted /home/asb/repos/medley -&amp;gt; /home/sandbox/medley
$ shandbox run ls -lh /home/sandbox/medley/README.md
-rw-r--r-- 1 sandbox users 2.7K Feb 11 20:02 /home/sandbox/medley/README.md
$ shandbox run touch /home/sandbox/medley/write-attempt
touch: cannot touch '/home/sandbox/medley/write-attempt': Read-only file system
$ shandbox remove-mount /home/sandbox/medley
unmounted /home/sandbox/medley
$ shandbox add-mount --read-write ~/repos/medley
mounted /home/asb/repos/medley -&amp;gt; /home/sandbox/medley
$ shandbox run touch /home/sandbox/medley/write-attempt
$ shandbox list-mounts
/home/sandbox                /dev/mapper/root[/home/asb/sandbox]
/home/sandbox/medley         /dev/mapper/root[/home/asb/repos/medley]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;shandbox enter&lt;/code&gt; will open a shell within the sandbox for easy interactive
usage. As a convenience, if the current working directory is in
&lt;code&gt;$HOME/sandbox&lt;/code&gt; (e.g. &lt;code&gt;$HOME/sandbox/foo&lt;/code&gt;) then the working directory within
the sandbox for &lt;code&gt;shandbox run&lt;/code&gt; or &lt;code&gt;shandbox enter&lt;/code&gt; will be set to the
appropriate path within the sandbox (&lt;code&gt;/home/sandbox/foo&lt;/code&gt; in this case). i.e.,
the case where this mapping is trivial. Environment variables are not passed
through.&lt;/p&gt;
&lt;p&gt;You can also explicitly control the working directory used by &lt;code&gt;shandbox run&lt;/code&gt;
or &lt;code&gt;shandbox enter&lt;/code&gt; by setting &lt;code&gt;SB_PWD&lt;/code&gt; to an absolute in-sandbox path. If
&lt;code&gt;SB_PWD&lt;/code&gt; isn't set, paths within the sandbox home are translated to
&lt;code&gt;/home/sandbox/...&lt;/code&gt;, and some host paths that are directly visible in the
sandbox (such as &lt;code&gt;/tmp&lt;/code&gt;, &lt;code&gt;/usr&lt;/code&gt;, &lt;code&gt;/etc&lt;/code&gt;, and similar) are used as-is.&lt;/p&gt;
&lt;h2 id=&quot;functionality-overview&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#functionality-overview&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Functionality overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shandbox new &amp;lt;dir&amp;gt;&lt;/code&gt;: Initialise a sandbox directory, setting up the
&lt;code&gt;.shandbox_meta&lt;/code&gt; layout and a default &lt;code&gt;.ssh/config&lt;/code&gt; suitable for use with
&lt;code&gt;share-ssh&lt;/code&gt;. If &lt;code&gt;${XDG_CONFIG_HOME:-$HOME/.config}/shandbox/default-init&lt;/code&gt;
exists it is copied to &lt;code&gt;.shandbox_meta/init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox start&lt;/code&gt;: Start the sandbox, creating the necessary namespaces and
mount layout. Fails if the sandbox is already running. If the selected
&lt;code&gt;$SANDBOX_DIR&lt;/code&gt; hasn't been initialised yet, it is initialised first. If
present, the init script in &lt;code&gt;.shandbox_meta/init&lt;/code&gt; is always run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox stop&lt;/code&gt;: Stop the sandbox by killing the process holding the
namespaces. Fails if the sandbox is not running.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox status&lt;/code&gt;: Print whether the sandbox is running and if it is, the
pid. Also print the last 20 lines of the log.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox enter&lt;/code&gt;: Open bash within the sandbox, starting the sandbox first
if it's not already running.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox enter-root&lt;/code&gt;: Open bash within the outer &quot;root&quot; namespace. This is
mostly useful for debugging the namespace or mount layout.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox run &amp;lt;command&amp;gt; [args...]&lt;/code&gt;: Run a command inside the sandbox. The
current working directory is translated to an in-sandbox path when this is
straightforward, and &lt;code&gt;SB_PWD&lt;/code&gt; can be used to override it explicitly. Starts
the sandbox first if it isn't already running.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox add-mount [--read-write] &amp;lt;host-path&amp;gt; [&amp;lt;sandbox-path&amp;gt;]&lt;/code&gt;: Bind-mount
a host path into the running sandbox. Mounts are read-only by default; pass
&lt;code&gt;--read-write&lt;/code&gt; to allow writes. The sandbox must already be running. Both
directories and individual files are supported, and if no sandbox path is
provided the host path basename is mounted under &lt;code&gt;/home/sandbox&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox remove-mount &amp;lt;sandbox-path&amp;gt;&lt;/code&gt;: Remove a previously added bind mount
from the running sandbox.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox list-mounts [--all]&lt;/code&gt;: List mounts visible from the sandbox. By
default this is restricted to mounts under &lt;code&gt;/home/sandbox&lt;/code&gt;; &lt;code&gt;--all&lt;/code&gt; shows
the full namespace mount table.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shandbox share-ssh &amp;lt;socket-name&amp;gt; &amp;lt;ssh-target&amp;gt; [ssh args...]&lt;/code&gt;: Expose a
host-side ssh ControlMaster connection inside the sandbox without copying
private keys or ssh-agent state into the sandbox. The sandbox directory must
already have been initialised with &lt;code&gt;shandbox new&lt;/code&gt;. See below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;minimum-requirements-and-ubuntu-compatibility&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#minimum-requirements-and-ubuntu-compatibility&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Minimum requirements and Ubuntu compatibility&lt;/h2&gt;
&lt;p&gt;Two core requirement are the ability to create a new user namespace, and a
recent enough util-linux release (2.41 or newer should work). The earliest
Ubuntu release known to work is 25.10 (25.04 won't work, as its util-linux is
too old).&lt;/p&gt;
&lt;p&gt;Recent Ubuntu releases restrict unprivileged user namespaces through AppArmor,
meaning additional settings are required. Chromium's &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md&quot;&gt;AppArmor user namespace
restrictions
notes&lt;/a&gt;
describe this policy and workarounds.&lt;/p&gt;
&lt;p&gt;To change the relevant setting non-persistently:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can alternatively add an AppArmor profile covering the path you install
&lt;code&gt;shandbox&lt;/code&gt; to. e.g. put this at &lt;code&gt;/etc/apparmor.d/shandbox&lt;/code&gt; and then do &lt;code&gt;sudo service apparmor reload&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apparmor&quot;&gt;abi &amp;lt;abi/4.0&amp;gt;,
include &amp;lt;tunables/global&amp;gt;

profile shandbox /usr/local/bin/shandbox flags=(unconfined) {
  userns,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;self-contained-sandbox-directories&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#self-contained-sandbox-directories&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Self-contained sandbox directories&lt;/h2&gt;
&lt;p&gt;A sandbox is represented by a normal directory, defaulting to &lt;code&gt;$HOME/sandbox&lt;/code&gt;.
The files visible as &lt;code&gt;/home/sandbox&lt;/code&gt; live directly in that directory, and
&lt;code&gt;shandbox&lt;/code&gt;'s own state lives under &lt;code&gt;.shandbox_meta&lt;/code&gt; inside it. That means a
sandbox is self-contained: you can create another one with &lt;code&gt;shandbox new ~/other-sandbox&lt;/code&gt;, select it by setting &lt;code&gt;SANDBOX_DIR&lt;/code&gt; (using the absolute path
it prints, or a shell-expanded path such as &lt;code&gt;~/other-sandbox&lt;/code&gt;), and it will
have its own root layout, runtime directory, pid files, log, init hook, and
ssh socket directory.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ shandbox new ~/other-sandbox
$ &lt;span&gt;SANDBOX_DIR=&lt;/span&gt;~/other-sandbox shandbox run &lt;span&gt;pwd&lt;/span&gt;
/home/sandbox
$ shandbox new ~/throwaway-sandbox
$ &lt;span&gt;SANDBOX_DIR=&lt;/span&gt;~/throwaway-sandbox shandbox status
stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sandboxes in different &lt;code&gt;SANDBOX_DIR&lt;/code&gt; have independent state and home
directories. The contents of &lt;code&gt;.shandbox_meta&lt;/code&gt; is hidden from inside the
sandbox by mounting an empty tmpfs over it. I don't personally use separate
sandboxes outside of testing purposes. But it's simple functionality to
provide and it's easy to imagine cases where this is useful.&lt;/p&gt;
&lt;h2 id=&quot;sharing-ssh-connections&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#sharing-ssh-connections&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Sharing ssh connections&lt;/h2&gt;
&lt;p&gt;One aspect of this I'm pretty pleased with is the mechanism for exposing an
ssh connection without having to share any key material or password, or set up
credentials specifically for the sandbox. &lt;code&gt;shandbox share-ssh&lt;/code&gt; will create an
ssh ControlMaster and expose the control socket in the sandbox home directory.
The sandbox can use this connection for as long as that ssh process lives.&lt;/p&gt;
&lt;p&gt;e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ shandbox share-ssh buildbox user@example.com
shandbox share-ssh: connecting (user@example.com) using /home/asb/sandbox/.ssh/sockets/ext%buildbox
shandbox share-ssh: connected
shandbox share-ssh: from inside the sandbox, use ssh ext%buildbox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then from inside the sandbox:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh ext%buildbox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ext%...&lt;/code&gt; name format is recognised thanks to a config fragment installed
in &lt;code&gt;~/.ssh/config&lt;/code&gt; within the sandbox.&lt;/p&gt;
&lt;h2 id=&quot;init-hooks&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#init-hooks&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Init hooks&lt;/h2&gt;
&lt;p&gt;The main way of customising sandbox setup outside of hacking on the &lt;code&gt;shandbox&lt;/code&gt;
script yourself is through an &quot;init script&quot; which will be called for every
&lt;code&gt;shandbox start&lt;/code&gt; (implicit or explicit). Just place your script in
&lt;code&gt;.shandbox_meta/init&lt;/code&gt;, and if you want a default one that is copied into that
location for you when creating a new sandbox then put it in
&lt;code&gt;$XDG_CONFIG_HOME/.shandbox/default-init&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As the script is executed for each &lt;code&gt;shandbox start&lt;/code&gt;, you should either ensure
it is idempotent or have it create and check for some marker file so it exits
early for subsequent invocations.&lt;/p&gt;
&lt;p&gt;The following environment variables are passed through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SHANDBOX_SELF&lt;/code&gt;: Path to the &lt;code&gt;shandbox&lt;/code&gt; script being run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SANDBOX_DIR&lt;/code&gt;: The host-side sandbox directory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SB_HOME&lt;/code&gt;: The in-sandbox home path.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SB_PATH&lt;/code&gt;: The path used for sandboxed commands.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A trivial example that adds a default mount:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;#!/bin/sh&lt;/span&gt;

&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$SHANDBOX_SELF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; add-mount ~/repos/src src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;implementation-approach&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#implementation-approach&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Implementation approach&lt;/h2&gt;
&lt;p&gt;The core sandboxing functionality is provided by the Linux namespaces
functionality exposed by
&lt;a href=&quot;https://manpages.debian.org/unstable/util-linux/unshare.1.en.html&quot;&gt;&lt;code&gt;unshare&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://manpages.debian.org/unstable/util-linux/nsenter.1.en.html&quot;&gt;&lt;code&gt;nsenter&lt;/code&gt;&lt;/a&gt;.
The &lt;a href=&quot;https://github.com/muxup/medley/blob/main/shandbox&quot;&gt;script's
implementation&lt;/a&gt; should be
quite readable but I'll try to summarise some key points here.&lt;/p&gt;
&lt;p&gt;The goal is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Within the sandbox, you appear as an unprivileged user, with uid and gid
equal to your usual Linux user.&lt;/li&gt;
&lt;li&gt;It should be possible to expose additional files or directories to the
sandbox once it's running.&lt;/li&gt;
&lt;li&gt;Applications running within the sandbox have no way (modulo bugs or
vulnerabilities in the kernel or accessible applications) of reaching files
on the host filesystem that aren't explicitly exposed.
&lt;ul&gt;
&lt;li&gt;To underline: This is a goal, it is &lt;em&gt;not&lt;/em&gt; a guarantee.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It's possible to launch multiple processes within the sandbox which can all
see each other, and have the same shared sandboxed filesystem.&lt;/li&gt;
&lt;li&gt;This is all doable as an unprivileged user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To implement that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two sets of namespaces are used to provide this isolation: the outer
'shandbox_root' has the user mapped to root within the namespace and retains
access to standard / (allowing us to mount additional paths into after the
sandbox has started). The inner 'shandbox_user' represents a new user
namepsace mapping our uid/gid to an unprivileged user, but other namespaces
are shared with 'shandbox_root'. Sandboxed processes are launched within the
namespaces of 'shandbox_user'.&lt;/li&gt;
&lt;li&gt;The process IDs of the initial process within 'sandbox_root' and
'sandbox_user' are saved and recalled so the script can use &lt;code&gt;nsenter&lt;/code&gt; to
enter the namespace. On newer systems this uses util-linux's &lt;code&gt;getino&lt;/code&gt; to
store a pid:inode pid reference while on older systems it stores pid plus
process start time.&lt;/li&gt;
&lt;li&gt;To help make it easier to tell when you're in the sandbox, a dummy
&lt;code&gt;/etc/passwd&lt;/code&gt; is bind-mounted naming the current user as &lt;code&gt;sandbox&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;shandbox start&lt;/code&gt; is executed, the necessary directories are bind
mounted in a directory that will be used as root (&lt;code&gt;/&lt;/code&gt;) for the user sandbox
in &lt;code&gt;$SANDBOX_DIR/.shandbox_meta/root&lt;/code&gt;. This happens within the sandbox_root
namespace, which then uses &lt;code&gt;unshare&lt;/code&gt; again to create a new user namespace
with an unprivileged user, executing within a chroot.&lt;/li&gt;
&lt;li&gt;A small private &lt;code&gt;/dev&lt;/code&gt; is created rather than exposing the host &lt;code&gt;/dev&lt;/code&gt;
wholesale. Basic devices such as &lt;code&gt;/dev/null&lt;/code&gt;, &lt;code&gt;/dev/zero&lt;/code&gt;, &lt;code&gt;/dev/random&lt;/code&gt;,
and &lt;code&gt;/dev/tty&lt;/code&gt; are provided, along with a private devpts instance.&lt;/li&gt;
&lt;li&gt;'sandbox_root' retains access to the host filesystem, which is necessary to
allow mounting additional paths after the fact. Without this requirement, we
could likely rewrite &lt;code&gt;shandbox start&lt;/code&gt; to use &lt;code&gt;pivot_root&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The host-side &lt;code&gt;.shandbox_meta&lt;/code&gt; directory is hidden inside the sandbox by
mounting an empty unreadable tmpfs over &lt;code&gt;/home/sandbox/.shandbox_meta&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;/etc/ssh/ssh_config.d&lt;/code&gt; exists, &lt;code&gt;shandbox&lt;/code&gt; stages a user-owned copy of
that directory and bind-mounts it over the original inside the sandbox. This
avoids OpenSSH refusing to process included config snippets that appear as
owned by &lt;code&gt;nobody&lt;/code&gt; in the inner user namespace.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-05-30: Added note about Ubuntu compatibility and util-linux
requirements.&lt;/li&gt;
&lt;li&gt;2026-05-26: Update article to reflect a wide range of improvements to the script.
&lt;ul&gt;
&lt;li&gt;share-ssh functionality.&lt;/li&gt;
&lt;li&gt;init hooks&lt;/li&gt;
&lt;li&gt;Easy to use support for multiple independent sandboxes (with sandbox state
now localised to a single directory).&lt;/li&gt;
&lt;li&gt;list-mounts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2026-02-11: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Tue, 26 May 2026 12:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia Compilers Team: Five years of JavaScript on WebAssembly</title>
	<guid>https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/</guid>
	<link>https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/</link>
	<description>
&lt;p&gt;&lt;em&gt;This is a cross-post from my personal blog &lt;a href=&quot;https://saulecabrera.dev/blog/five-years-of-js-on-wasm/&quot;&gt;Five years of JavaScript on
WebAssembly&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This post summarizes my talk at Wasm I/O 2026: Five Years of JavaScript on WebAssembly. The recording will be uploaded to the &lt;a href=&quot;https://www.youtube.com/playlist?list=PLP3xGl7Eb-4P9UDywG2NOJLBtcch_Ry7A&quot;&gt;2026 playlist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By the end, you should have a clear picture of how JavaScript on WebAssembly evolved from an experiment into a production toolchain, the design decisions that shaped it, and where I think it's heading next.&lt;/p&gt;
&lt;p&gt;The talk is structured into 5 main sections, each representing a key event during the past 5 years (2021 - 2026) of development of JavaScript on WebAssembly and &lt;a href=&quot;https://github.com/bytecodealliance/javy&quot;&gt;Javy&lt;/a&gt;, which is the leading character of the talk.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2 id=&quot;april-23rd-2021-inception&quot; tabindex=&quot;-1&quot;&gt;April 23rd 2021 &#8212; Inception &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Javy's git log shows that on April 23rd 2021 I made the first commit:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Author: Sa&#250;l Cabrera &lt;br /&gt;Date:   Fri Apr 23 09:41:56 2021 -0400&lt;br /&gt;&lt;br /&gt;    First commit. Several important things to note:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A fair and usually common question that gets asked is why do you need to run JavaScript on WebAssembly? Isn't WebAssembly meant to run in the browser and alongside JavaScript? It all depends on the use-case. WebAssembly's inherent secure-by-default and near native performance properties are very appealing for use-cases that involve executing untrusted code server side.&lt;/p&gt;
&lt;p&gt;Thus, in 2021 I had set myself the objective of creating a toolchain to make it extremely easy and accessible to target WebAssembly. Additionally, the toolchain should also:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Produce the smallest possible WebAssembly modules, to minimize the operational infrastructure cost of managing the native binaries produced by compiling Wasm to machine code and to be competitive with other WebAssembly toolchains.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offer reasonably complete JavaScript spec support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offer decent runtime performance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;QuickJS is a natural fit for all three goals, and has powered Javy ever since.&lt;/p&gt;
&lt;h2 id=&quot;2022-production-ready-toolchain&quot; tabindex=&quot;-1&quot;&gt;2022 &#8212; Production ready toolchain &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;During 2022 &lt;a href=&quot;https://shopify.engineering/javascript-in-webassembly-for-shopify-functions&quot;&gt;Javy became production ready&lt;/a&gt;: it was put in the hands of developers and started serving real production use-cases.&lt;/p&gt;
&lt;p&gt;The first production ready version included an extremely simple interface for building WebAssembly modules:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;javy build index.js &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; index.wasm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it also included a feature to generate &lt;em&gt;extremely&lt;/em&gt; small WebAssembly modules, in the range of 1kb - 16kb. &lt;a href=&quot;https://www.youtube.com/watch?v=RA5tuHzHpeY&quot;&gt;During Wasm I/O 2023, Jeff Charles presented&lt;/a&gt; the underlying technical details that make this possible.&lt;/p&gt;
&lt;h2 id=&quot;2023-governance-model&quot; tabindex=&quot;-1&quot;&gt;2023 &#8212; Governance model &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;2023 brought no major technical milestones, but an equally important shift happened: Javy moved to a hosted-project model under the &lt;a href=&quot;https://bytecodealliance.org/&quot;&gt;Bytecode Alliance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While Javy was initially designed for a specific use-case, it eventually found its way into multiple companies operating in the WebAssembly space. This adoption prompted us to rethink its governance model, shifting towards an open, community-driven model that ensures a healthy environment for the project's growth and development.&lt;/p&gt;
&lt;h2 id=&quot;2024-a-profiler-for-javascript-on-webassembly&quot; tabindex=&quot;-1&quot;&gt;2024 &#8212; A profiler for JavaScript on WebAssembly &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;During 2024, we set out to solve the problem of profiling JavaScript programs on WebAssembly. It is critical for developers to understand how their programs behave inside WebAssembly in order to perform optimizations where applicable. This post intentionally skips over all the technical details behind the research and implementation work; however, if you're interested in reading further, you can check out the first post of my series, &lt;a href=&quot;https://saulecabrera.dev/blog/a-profiler-for-javascript-on-webassembly-part-1/&quot;&gt;A profiler for JavaScript on WebAssembly, Part 1&lt;/a&gt;, which accompanies the work &lt;a href=&quot;https://github.com/bytecodealliance/javy/issues/1206&quot;&gt;happening upstream&lt;/a&gt; to materialize this research. The series is not complete, but the first part lays the foundation for why this is important and the challenges behind it.&lt;/p&gt;
&lt;h2 id=&quot;2025-toolchain-extensibility&quot; tabindex=&quot;-1&quot;&gt;2025 &#8212; Toolchain extensibility &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Javy's design philosophy was always to provide a minimal and lightweight JavaScript on Wasm implementation by default, allowing for extensibility where appropriate. The very initial version of an extensible runtime required users to assemble their own runtime from a set of official Rust crates. This approach was not ergonomic and required a non-trivial knowledge of Javy's internals to get right.&lt;/p&gt;
&lt;p&gt;During 2025, we overhauled the extensibility story, making the Javy CLI the canonical starting point of extensibility, promoting the CLI from a simple entry point for the basic use-case into a build tool capable of orchestrating JavaScript-on-WebAssembly builds for third-party use cases.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/bytecodealliance/javy/issues/768&quot;&gt;official RFC&lt;/a&gt; behind this work contains all the technical details.&lt;/p&gt;
&lt;h2 id=&quot;2026-a-preliminary-look-at-potential-throughput-improvements&quot; tabindex=&quot;-1&quot;&gt;2026 &#8212; A preliminary look at potential throughput improvements &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WebAssembly, as a Harvard Architecture, does not allow just-in-time code generation yet. Thus, any dynamic language running on WebAssembly which requires just-in-time generation as a means to improve throughput is limited to interpreter mode only. This is the case for Javy today: it principally relies on QuickJS' interpreter for code execution. Even though QuickJS' interpreter is very well optimized, there is still room for improvement. Profiles, taken from the work done in 2024 and being upstreamed this year, show that optimizing for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Field accesses&lt;/li&gt;
&lt;li&gt;Closure creation&lt;/li&gt;
&lt;li&gt;Function calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;has the potential to considerably improve throughput. More concretely, a recent &lt;a href=&quot;https://github.com/saulecabrera/jac&quot;&gt;proof-of-concept&lt;/a&gt; of compiling QuickJS bytecode to Wasm, conducted during late 2025, showed that optimizing function calls can improve performance by ~1.2x over the interpreter, and optimizing closure creation, particularly by relying on the Wasm GC proposal, can improve performance by ~1.5x-~3.5x over the interpreter. Note that this work is in its very early days, and the benchmarks were conducted on a very small sample of programs. Our plan is to upstream the compiler as an experimental feature during the upcoming 6-12 months.&lt;/p&gt;
&lt;h2 id=&quot;au-revoir&quot; tabindex=&quot;-1&quot;&gt;Au revoir &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What started in 2021 as &#8212; &amp;quot;can we make JavaScript on WebAssembly easy?&amp;quot; &#8212; has turned into a question we now get to answer in more interesting ways: not just easy, but small, fast, extensible, and soon, possibly, faster.&lt;/p&gt;        </description>
	<pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Brian Kardell: Get Clamped: Unwinding Some Difficult CSS History</title>
	<guid>https://bkardell.com/blog/GetClamped.html</guid>
	<link>https://bkardell.com/blog/GetClamped.html</link>
	<description>
&lt;h1 class=&quot;contextual-heading&quot;&gt;Get Clamped: Unwinding Some Difficult CSS History&lt;/h1&gt;
  &lt;p class=&quot;segue&quot;&gt;Sometimes features take a long and winding road on the way to a good solution. We could use your help testing improvements for something that's been somehow developing for about a quarter of a century...&lt;/p&gt;
  
  
  &lt;p&gt;Back in the mid-2000s there was a great debate on where the web was going. The web itself had really exploded and people were really starting to use it as an application delivery platform. Most of the world, including W3C members, seemed to kind of assume that we would get a web for apps instead of documents, and there were several pieces being developed for this. Mozilla had XUL, Microsoft had XAML, Adobe had Flex, even Oracle wound up with this JFX thing. Back then, anyone using these would have definitely noticed that the layout models were different from the web which was then still all abspos and floats. David Baron &lt;a href=&quot;https://web.archive.org/web/20080406015507/http://xtech06.usefulinc.com/schedule/paper/146&quot;&gt;wrote about this in 2006&lt;/a&gt;.&lt;/p&gt;
  &lt;p&gt;This all brewed for a while until CSS picked up a similar concept. As often happens, they were experimenting with implementations before anyone submitted a public draft. David Baron &lt;a href=&quot;https://lists.w3.org/Archives/Public/www-style/2008Jun/0003.html&quot;&gt;wrote to the www-style mailing list in June 2008&lt;/a&gt;&lt;/p&gt;
  &lt;blockquote&gt;
    &lt;p&gt;Much of the specification is implemented in both Gecko and Webkit (with prefixes), and this implementation forms the basis of the formatting model of XUL&lt;/p&gt;
  &lt;/blockquote&gt;
  &lt;p&gt;This created &lt;code&gt;-webkit-box&lt;/code&gt; which was an earlier take on Flexbox.&lt;/p&gt;
  
    &lt;h2 class=&quot;contextual-heading&quot;&gt;This is &lt;em&gt;not&lt;/em&gt; flexbox's story.&lt;/h2&gt;
    &lt;p&gt;It is instead the strange story of the origins of &lt;em&gt;line clamping&lt;/em&gt;, if you can believe it! That's because given this internal ability, Apple added some support for line clamping which used it. Internally, WebKit supported &lt;code&gt;-apple-line-clamp&lt;/code&gt; (and later &lt;code&gt;-khtml-line-clamp&lt;/code&gt;). This became &lt;code&gt;-webkit-line-clamp&lt;/code&gt; and it accidentally escaped the lab and made it into the public releases of Safari.&lt;/p&gt;
    &lt;p&gt;Given it, one could suddenly have a few lines that would show ellipsis if rendering went beyond that.&lt;/p&gt;
    &lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.things {
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}&lt;/code&gt;&lt;/pre&gt; 
    &lt;p&gt;As far as I can tell, it was never actually publicized by Apple.  Despite this, it was useful enough that as people learned about it, they shared (as we do) and eventually so many people used it that it became something of a de facto standard. This is before the WebKit/Blink fork, and so it wound up in Chrome and later in other chromium flavored browsers, all with the &lt;code&gt;-webkit-&lt;/code&gt; prefixes. In the Project Spartan/EdgeHTML era Microsoft found that they needed to support it for compatibility and added support for the &lt;code&gt;-webkit-&lt;/code&gt; prefix in April 2018. Firefox added support in July 2019. The fact that it was internally  flex related at all changed in most browsers a while back, but this weird winding road led through lot of pains.. Chris Coyier wrote a piece about the state of it &lt;a href=&quot;https://css-tricks.com/line-clampin/&quot;&gt;in 2013&lt;/a&gt;. A few years later, in 2016 Nils Rasmusson wrote &lt;a href=&quot;https://medium.com/mofed/css-line-clamp-the-good-the-bad-and-the-straight-up-broken-865413f16e5&quot;&gt;CSS Line-Clamp &#8212; The Good, the Bad and the Straight-up Broken&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Today, according to &lt;a href=&quot;https://chromestatus.com/metrics/css/timeline/popularity/260&quot;&gt;chromestatus&lt;/a&gt;, it's in over 40% of page loads, and on over 30% of pages in the HTTPArchive crawl &#8212; and popularity is &lt;em&gt;still&lt;/em&gt; growing.&lt;/p&gt;
  
  
    &lt;h2 class=&quot;contextual-heading&quot;&gt;Interoperability and Correction&lt;/h2&gt;
    &lt;p&gt;Despite all of that, lots of it was not consistent or interoperable.&lt;/p&gt;
    &lt;p&gt;Lots of it didn't even really make sense internally.&lt;/p&gt;
    &lt;p&gt;It wasn't well designed.&lt;/p&gt;
    &lt;p&gt;So, there has been a real effort to create a good solution here for the past few years. Igalia (primarily my colleague &lt;a href=&quot;https://www.igalia.com/team/abotella&quot;&gt;Andreu Botella&lt;/a&gt;) has been working on it, and started with &lt;a href=&quot;https://github.com/Igalia/explainers/blob/main/css/line-clamp/README.md&quot;&gt;an explainer that is still pretty good and includes images and lots more info&lt;/a&gt;. It is now part of &lt;a href=&quot;https://drafts.csswg.org/css-overflow-4/&quot;&gt;CSS Overflow Level 4&lt;/a&gt;, and there is an updated implementation in Chromium &#8212; you can try it today by enabling experimental web platform features at &lt;code&gt;chrome://flags/#enable-experimental-web-platform-features&lt;/code&gt;. If you flip that flag, you're using it!&lt;/p&gt;
    &lt;p&gt;Guess what it lets you do now....&lt;/p&gt;
    &lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.thingr {
  line-clamp: 3;
}&lt;/code&gt;&lt;/pre&gt;
   &lt;figure class=&quot;captioned-image&quot;&gt; 
    &lt;img src=&quot;https://bkardell.com/media/2026/mbb.gif&quot; /&gt;
    &lt;figcaption&gt;So cool that Millie Bobby Brown's mind is blown... (&lt;a href=&quot;https://giphy.com/gifs/converse-3o8dFn5CXJlCV9ZEsg&quot;&gt;via GIPHY&lt;/a&gt;)&lt;/figcaption&gt;
   &lt;/figure&gt;
    &lt;p&gt;It's so simple by default. So good, right?&lt;/p&gt;
    &lt;p&gt;You can also use the &lt;code&gt;auto&lt;/code&gt; value to do this based on some kind of measured value &#8212; a &lt;code&gt;max-height&lt;/code&gt;, for example:&lt;/p&gt;
    &lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.thingr {
  max-height: 400px;
  line-clamp: auto;
}&lt;/code&gt;&lt;/pre&gt;
  
  
    &lt;h2 class=&quot;contextual-heading&quot;&gt;Great, how can I help?&lt;/h2&gt;
    &lt;p&gt;Excellent, I'm glad you asked!&lt;/p&gt;
    &lt;p&gt;Well, for one you can try out the new unprefixed &lt;code&gt;line-clamp&lt;/code&gt;, it's way nicer. You can read about it in &lt;a href=&quot;https://github.com/Igalia/explainers/blob/main/css/line-clamp/README.md#implementation&quot;&gt;the explainer&lt;/a&gt;.&lt;/p&gt;
    &lt;p&gt;Ask questions (you can send them to Andreu on &lt;a href=&quot;https://bsky.app/profile/andreubotella.com&quot;&gt;bluesky&lt;/a&gt; or &lt;a href=&quot;https://mastodon.andreubotella.com/@andreu&quot;&gt;mastodon&lt;/a&gt;), or report issues if you find them.&lt;/p&gt;

    &lt;p&gt;Maybe say &quot;thanks&quot; to Bloomberg Tech for funding the work!&lt;/p&gt;

    &lt;p&gt;But &lt;em&gt;more importantly&lt;/em&gt;: This work also involved reconciling as much as we can in order to share code and tests and standard, well-defined behavior for &lt;code&gt;-webkit-line-clamp&lt;/code&gt; and friends. We &lt;em&gt;believe&lt;/em&gt; that all of these decisions are good, but we need some time for research and feedback given the scale of existing deployments. Remember &#8212; this thing is on over 40% of page loads. That means even a small percentage of regressions is a lot of real pages and real users. Check your sites, let us know if you experience issues related to this, and help us make sure a quarter century of history ends with something everyone can be proud of.&lt;/p&gt;        </description>
	<pubDate>Fri, 22 May 2026 04:00:00 +0000</pubDate>
</item>
<item>
	<title>V&#237;ctor J&#225;quez: Getting ready for the GStreamer Spring Hackfest 2026 in Nice</title>
	<guid>https://blogs.igalia.com/vjaquez/getting-ready-for-the-gstreamer-spring-hackfest-2026-in-nice/</guid>
	<link>https://blogs.igalia.com/vjaquez/getting-ready-for-the-gstreamer-spring-hackfest-2026-in-nice/</link>
	<description>
        &lt;img class=&quot;face&quot; src=&quot;/images/vjaquez.png&quot; width=&quot;97&quot; height=&quot;150&quot; alt=&quot;&quot; align=&quot;right&quot; style=&quot;float: right&quot; /&gt;
&lt;p&gt;Next week, several Igalians will be heading to Nice for a bunch of open source
gatherings and hackfests.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://indico.freedesktop.org/event/13/&quot;&gt;Display Next Hackfest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://embedded-recipes.org/2026/&quot;&gt;Embedded Recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.gstreamer.org/t/gstreamer-spring-hackfest-2026-on-29-31-may-2026-in-nice-france/5762&quot;&gt;GStreamer Spring
Hackfest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/PipeWire-Hackfest-in-Nice---May-2026&quot;&gt;PipeWire
Hackfest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/orgs/bluez/discussions/1881&quot;&gt;BlueZ F2F&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lore.kernel.org/linux-media/e6c07c24-da54-4269-b42f-b9af544da2d8@kernel.org/&quot;&gt;Media
Summit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#8217;ll attend the &lt;em&gt;Media Summit&lt;/em&gt; remotely (I&#8217;m interested in the discussion about
the usage of Vulkan Video API in embedded devices), then I&#8217;ll travel, as other
multimedia folks, to the &lt;em&gt;GStreamer Hackfest&lt;/em&gt;. While other Igalians will be at
the &lt;em&gt;Display Next Hackfest&lt;/em&gt; and &lt;em&gt;Embedded Recipes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;My goal for the hackfest is to chat with other GStreamer developers about
hardware-accelerated encoders and how to test them, especially those using
VA-API and Vulkan.&lt;/p&gt;
&lt;p&gt;Here&#8217;s what my multimedia colleagues are planning for the hackfest:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;St&#233;phane Cerveau&lt;/strong&gt; will talk about new features in
&lt;a href=&quot;https://dabrain34.pages.freedesktop.org/GstPipelineStudio/&quot;&gt;GstPipelineStudio&lt;/a&gt;
and its future integration with
&lt;a href=&quot;https://github.com/dabrain34/gstpop&quot;&gt;GstPrinceOfParser&lt;/a&gt;, along with several
core GStreamer improvements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alicia Boya&lt;/strong&gt; will tackle subtle timing issues, out-of-order and race
conditions in frame processing, and other GStreamer bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Xabier Rodr&#237;guez Calvar&lt;/strong&gt; plans to close the issues around static compilation
of &lt;code&gt;gstreamer-rs&lt;/code&gt;, including selected plugins with &lt;code&gt;system-deps&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thibault Saunier&lt;/strong&gt; will aim to finish upstreaming some new features and focus
on WebAssembly support.&lt;/p&gt;        </description>
	<pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Qiuyi Zhang (Joyee): How Node.js and V8 keep each other working - workflows, challenges and tips</title>
	<guid>https://joyeecheung.github.io/blog/2026/05/18/how-nodejs-and-v8-keep-each-other-working/</guid>
	<link>https://joyeecheung.github.io/blog/2026/05/18/how-nodejs-and-v8-keep-each-other-working/</link>
	<description>
&lt;p&gt;Recently I&#8217;ve found myself repeating the same explanations to different people about how to test a V8 patch in Node.js, how to patch the Node.js fork run in V8&#8217;s integration CI, or how to get these patches into their repositories/CIs&lt;/p&gt;        </description>
	<pubDate>Tue, 19 May 2026 00:16:39 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #65</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-65/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-65/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from May 11 to May 18.&lt;/p&gt;
&lt;p&gt;
For this week we have quite a collection of news! Ranging a variety of improvements
to dialog.requestClose(), rendering fixes, the new Skia-based compositor enabled by
default, and proper versioning and improvements to the WebKit Container SDK, there's
news for everyone.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313246@main&quot;&gt;Update&lt;/a&gt; the &lt;code&gt;closeWatcher.requestClose()&lt;/code&gt; function to no longer require user activation, aligning with the spec.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313302&quot;&gt;Implement&lt;/a&gt; actually moving the node in the DOM when &lt;code&gt;moveBefore()&lt;/code&gt; is called.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313239@main&quot;&gt;Fix&lt;/a&gt; handling of nested calls to &lt;code&gt;dialog.requestClose()&lt;/code&gt;.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313251@main&quot;&gt;Add&lt;/a&gt; missing preliminary checks to &lt;code&gt;dialog.requestClose()&lt;/code&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313063@main&quot;&gt;Fixed&lt;/a&gt; an issue where background images were unexpectedly stretched, primarily affecting the reCAPTCHA checkmark image.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The new compositor using Skia API instead of TextureMapper is &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313297@main&quot;&gt;now enabled by default&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;infrastructure-construction-site&quot;&gt;Infrastructure &#127959;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313201@main&quot;&gt;Added&lt;/a&gt; opt-in auto-enter for the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;WebKit Container SDK&lt;/a&gt; - the GTK/WPE wrapper scripts (build-webkit, run-webkit-tests, run-api-tests, etc.) now relaunch themselves inside a pinned &lt;code&gt;wkdev-build&lt;/code&gt; podman container when &lt;code&gt;WEBKIT_CONTAINER_SDK_ENABLE_AUTOENTER=1&lt;/code&gt; is set. A new &lt;code&gt;.wkdev-sdk-version&lt;/code&gt; file at the repo root pins the SDK image, so the image can be bumped in a PR and validated through EWS. Without the flag, wrappers run on the host exactly as before.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/webkit-container-sdk/commit/120e2ddefe90cb07a784d5a3169e23b1d669d651&quot;&gt;Introduced&lt;/a&gt; a proper version scheme for the &lt;code&gt;wkdev-sdk&lt;/code&gt; image provided by the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;WebKit Container SDK&lt;/a&gt; so consumers can pin to a known revision. The &lt;code&gt;:latest&lt;/code&gt; tag, the WKDEV_SDK_TAG/--tag override and the tag/* branch mechanism are replaced by a single machine-checkable format &lt;strong&gt;&amp;lt;major&amp;gt;.&amp;lt;minor&amp;gt;-v&amp;lt;count&amp;gt;-&amp;lt;gitsha&amp;gt;&lt;/strong&gt; (e.g. 2.53-v1-916f9ef), where &lt;strong&gt;&amp;lt;major&amp;gt;.&amp;lt;minor&amp;gt;&lt;/strong&gt; tracks the WebKitGTK/WPE release cycle, &lt;strong&gt;v&amp;lt;count&amp;gt;&lt;/strong&gt; is the per-cycle SDK build counter, and &lt;strong&gt;&amp;lt;gitsha&amp;gt;&lt;/strong&gt; traces the image back to its source commit. &lt;code&gt;wkdev-create&lt;/code&gt; gains a &lt;code&gt;--version&lt;/code&gt; switch (full or bare &lt;strong&gt;&amp;lt;major&amp;gt;.&amp;lt;minor&amp;gt;&lt;/strong&gt;). &lt;code&gt;wkdev-update&lt;/code&gt; supports updating from &lt;code&gt;latest&lt;/code&gt; tag to the new versioning scheme, just run it on your host to update to the latest SDK.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/313275@main&quot;&gt;Switched&lt;/a&gt; the &lt;code&gt;wkdev-build&lt;/code&gt; container from a persistent container to ephemeral &lt;code&gt;podman run --rm --init&lt;/code&gt; per invocation. This removes the manual &lt;code&gt;podman rm&lt;/code&gt; step necessary whenever container creation arguments changed (which the tooling was not handling by itself), the first-run recursive-chown cost, and the &lt;code&gt;podman start&lt;/code&gt; step after host reboots.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 18 May 2026 19:39:42 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Alex Bradbury: Building 32-bit RISC-V sysroots and images with Yocto</title>
	<guid>https://muxup.com/building-32-bit-risc-v-sysroots-and-images-with-yocto</guid>
	<link>https://muxup.com/building-32-bit-risc-v-sysroots-and-images-with-yocto</link>
	<description>
&lt;p&gt;Thanks to the Debian 64-bit RISC-V port it's really easy to build a sysroot
appropriate for cross-compiling Clang/LLVM and its separate &lt;a href=&quot;https://llvm.org/docs/TestSuiteGuide.html&quot;&gt;test
suite&lt;/a&gt;. Either &lt;a href=&quot;https://muxup.com/2024q4/rootless-cross-architecture-debootstrap&quot;&gt;use my
rootless-deboostrap-wrapper
script&lt;/a&gt; or the
&lt;a href=&quot;https://llvm.org/docs/HowToCrossCompileLLVM.html#setting-up-a-sysroot&quot;&gt;command I documented in LLVM's cross-compilation
instructions&lt;/a&gt;,
being sure to &lt;a href=&quot;https://llvm.org/docs/HowToCrossCompileLLVM.html#working-around-a-ninja-dependency-issue&quot;&gt;see the note on working around a Ninja dependency
issue&lt;/a&gt;.
For a bootable QEMU image, Debian-based recipes are &lt;a href=&quot;https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap&quot;&gt;similarly
straightforward&lt;/a&gt;.
But we don't have the luxury of a precompiled distribution for 32-bit RISC-V
and so we'll lean on &lt;a href=&quot;https://www.yoctoproject.org/&quot;&gt;Yocto&lt;/a&gt; to produce the
needed sysroot by building from source. I cover three cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;building a sysroot for cross-compiling projects like LLVM&lt;/li&gt;
&lt;li&gt;doing the same but in a way that requires fewer build steps&lt;/li&gt;
&lt;li&gt;building an image approximating my debootstrap image recipes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this article I use release 6.0 ('Wrynose') and the &lt;code&gt;bitbake-setup&lt;/code&gt; helper
tool.
For documentation, I found the &lt;a href=&quot;https://docs.yoctoproject.org/6.0/brief-yoctoprojectqs/index.html&quot;&gt;Yocto quick build
guide&lt;/a&gt;, and
&lt;a href=&quot;https://docs.yoctoproject.org/6.0/brief-yoctoprojectqs/index.html&quot;&gt;bitbake-setup
docs&lt;/a&gt;, and
&lt;a href=&quot;https://docs.yoctoproject.org/6.0/dev-manual/customizing-images.html#customizing-images&quot;&gt;image customisation
guide&lt;/a&gt;
helpful.&lt;/p&gt;
&lt;p&gt;I'm not a Yocto developer, so if you are reading this and think there are other
approaches to consider or alternative ways of solving the problem that are
better, please do drop me a note!&lt;/p&gt;
&lt;h2 id=&quot;common-setup&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#common-setup&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Common setup&lt;/h2&gt;
&lt;p&gt;I'm running on Arch Linux which isn't one of the tested Yocto host
distributions, but seemed to work just fine.&lt;/p&gt;
&lt;p&gt;I found I needed to enable the &lt;code&gt;en_US&lt;/code&gt; locale:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo sed /etc/locale.gen -i -e &lt;span&gt;&amp;quot;s/^\#en_US.UTF-8 UTF-8.*/en_US.UTF-8 UTF-8/&amp;quot;&lt;/span&gt;
sudo locale-gen
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And install the following additional packages:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo pacman -S inetutils chrpath cpio diffstat rpcsvc-proto flex bison zstd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we will check out bitbake into a work directory and set a directory to be
used to hold downloaded files:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir yocto-work &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span&gt;cd&lt;/span&gt; yocto-work
git clone https://git.openembedded.org/bitbake
./bitbake/bin/bitbake-setup settings &lt;span&gt;set&lt;/span&gt; default dl-dir &lt;span&gt;$HOME&lt;/span&gt;/.cache/yocto/dl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;producing-a-sysroot-based-on-core-image-minimal&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#producing-a-sysroot-based-on-core-image-minimal&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Producing a sysroot based on core-image-minimal&lt;/h2&gt;
&lt;p&gt;As is often the case, the workload I'm interested in here is LLVM. If you're
looking to build a sysroot to cross-compile something else, you may need a
slightly different package list.&lt;/p&gt;
&lt;p&gt;In this first stanza, we use &lt;code&gt;bitbake-setup&lt;/code&gt; to initialise our development
environment. Because there isn't a predefined machine target for riscv32 in
&lt;code&gt;bitbake/default-registry/configurations/poky-wrynose.conf.json&lt;/code&gt;, we
avoid selecting &lt;code&gt;machine&lt;/code&gt; and will address it later. Importantly, we set a
&lt;code&gt;SSTATE_DIR&lt;/code&gt; which will be used for the shared state cache, avoiding
rebuilding packages when not necessary (I'm not totally sure when this isn't
exposed in &lt;code&gt;bitbake-setup settings&lt;/code&gt; like &lt;code&gt;dl-dir&lt;/code&gt; is). Another relevant
variable is &lt;code&gt;BB_HASHSERVE_BB_DIR&lt;/code&gt; which controls where the hash equivalence
database is stored. But with current &lt;code&gt;bitbake-setup&lt;/code&gt; this defaults to
&lt;code&gt;SSTATE_DIR&lt;/code&gt;, so there's no need to set it explicitly.&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./bitbake/bin/bitbake-setup init --non-interactive &lt;span&gt;\&lt;/span&gt;
  --skip-selection machine &lt;span&gt;\&lt;/span&gt;
  ./bitbake/default-registry/configurations/poky-wrynose.conf.json &lt;span&gt;\&lt;/span&gt;
  poky &lt;span&gt;\&lt;/span&gt;
  distro/poky

&lt;span&gt;printf&lt;/span&gt; &lt;span&gt;'SSTATE_DIR = &amp;quot;%s&amp;quot;\n'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.cache/yocto/sstate&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; bitbake-builds/site.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With that done, we can source the generated definitions to enter the build
environment (note we're using the default setup directory, you can override it
to something other than &lt;code&gt;poky-wrynose&lt;/code&gt; by using &lt;code&gt;--setup-dir-name&lt;/code&gt;) and
use &lt;code&gt;enable-fragment&lt;/code&gt; to set the qemuriscv32 machine:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;. bitbake-builds/poky-wrynose/build/init-build-env
bitbake-config-build enable-fragment machine/qemuriscv32
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now configure the build, indicating the additional libraries that need to be
present and run &lt;code&gt;bitbake&lt;/code&gt; to actually produce it:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat &amp;gt;&amp;gt; conf/local.conf &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL:append = &amp;quot; \&lt;/span&gt;
&lt;span&gt;  glibc-dev \&lt;/span&gt;
&lt;span&gt;  libgcc \&lt;/span&gt;
&lt;span&gt;  libgcc-dev \&lt;/span&gt;
&lt;span&gt;  libatomic \&lt;/span&gt;
&lt;span&gt;  libatomic-dev \&lt;/span&gt;
&lt;span&gt;  libstdc++ \&lt;/span&gt;
&lt;span&gt;  libstdc++-dev \&lt;/span&gt;
&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

bitbake core-image-minimal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This results in 4624 build tasks and takes quite some time to complete if you
haven't run it before (i.e. aren't hitting in the sstate cache). The next
section of this article explores how to produce the needed output while
building much less, but let's finish the job and extract a rootfs from what
was built. I would like to now &lt;a href=&quot;https://docs.yoctoproject.org/6.0/sdk-manual/appendix-obtain.html#extracting-the-root-filesystem&quot;&gt;follow advice in the
documentation&lt;/a&gt;
and run &lt;code&gt;runqemu-extract-sdk&lt;/code&gt; on the rootfs archive (I submitted a &lt;a href=&quot;https://lists.openembedded.org/g/openembedded-core/message/229316&quot;&gt;little
patch
upstream&lt;/a&gt;)
to fix this command for .zst which was applied:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;runqemu-extract-sdk tmp/deploy/images/qemuriscv32/core-image-minimal-qemuriscv32.rootfs.tar.zst ~/rv32sysroot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you have a sysroot that's &lt;em&gt;almost&lt;/em&gt; directly usable for
cross-compiling Clang/LLVM (with &lt;code&gt;--target=riscv32-poky-linux&lt;/code&gt;) but there are
three finalisation steps we will perform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add an additional symlink to the tree so that upstream Clang's search
procedure for the GCC install finds the correct directory. The combination
of
&lt;a href=&quot;https://git.openembedded.org/openembedded-core/tree/meta/recipes-devtools/clang/clang/0010-clang-Define-releative-gcc-installation-dir.patch?h=wrynose&quot;&gt;these&lt;/a&gt;
&lt;a href=&quot;https://git.openembedded.org/openembedded-core/tree/meta/recipes-devtools/clang/clang/0018-llvm-clang-Insert-anchor-for-adding-OE-distro-vendor.patch?h=wrynose&quot;&gt;two&lt;/a&gt;
downstream patches which Yocto applies to its own Clang builds would make
this unnecessary. I'm not sure if upstreaming has ever been pursued.&lt;/li&gt;
&lt;li&gt;Convert all absolute symlinks to relative ones. Yocto provides a Python
script for this, which is in our &lt;code&gt;$PATH&lt;/code&gt; after sourcing
&lt;code&gt;build/init-build-env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;(Optional) Apply workaround &lt;a href=&quot;https://llvm.org/docs/HowToCrossCompileLLVM.html#working-around-a-ninja-dependency-issue&quot;&gt;for a ninja
issue&lt;/a&gt;
that would otherwise mean incremental builds don't work.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/rv32sysroot/usr/lib/gcc&amp;quot;&lt;/span&gt;
ln -s ../riscv32-poky-linux &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/rv32sysroot/usr/lib/gcc/riscv32-poky-linux&amp;quot;&lt;/span&gt;
sysroot-relativelinks.py &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/rv32sysroot&amp;quot;&lt;/span&gt;
ln -s usr/include &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/rv32sysroot/include&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;producing-a-sysroot-with-fewer-build-steps&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#producing-a-sysroot-with-fewer-build-steps&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Producing a sysroot with fewer build steps&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;core-image-minimal&lt;/code&gt; recipe above is straightforward, but does a lot more
work than strictly necessary. We can reduce this by instead adding a
dependency-only recipe that explicitly lists the needed build-time
dependencies and contains logic to produce the sysroot.&lt;/p&gt;
&lt;p&gt;First, create a layer:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;. bitbake-builds/poky-wrynose/build/init-build-env
bitbake-layers create-layer --add-layer ../layers/meta-rv32-llvm-sysroot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then add the recipe:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;recipe_dir=&lt;/span&gt;&lt;span&gt;&amp;quot;../layers/meta-rv32-llvm-sysroot/recipes-devtools/rv32-llvm-deps-sysroot&amp;quot;&lt;/span&gt;
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$recipe_dir&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

cat &amp;gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$recipe_dir&lt;/span&gt;&lt;span&gt;/rv32-llvm-deps-sysroot.bb&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;SUMMARY = &amp;quot;Dependency-only recipe to export an RV32 sysroot&amp;quot;&lt;/span&gt;
&lt;span&gt;LICENSE = &amp;quot;MIT-0&amp;quot;&lt;/span&gt;

&lt;span&gt;INHIBIT_DEFAULT_DEPS = &amp;quot;1&amp;quot;&lt;/span&gt;
&lt;span&gt;EXCLUDE_FROM_WORLD = &amp;quot;1&amp;quot;&lt;/span&gt;
&lt;span&gt;PACKAGE_ARCH = &amp;quot;${MACHINE_ARCH}&amp;quot;&lt;/span&gt;

&lt;span&gt;DEPENDS = &amp;quot;virtual/libc libgcc virtual/${MLPREFIX}compilerlibs zlib zstd-native&amp;quot;&lt;/span&gt;

&lt;span&gt;inherit deploy nopackages&lt;/span&gt;

&lt;span&gt;do_configure[noexec] = &amp;quot;1&amp;quot;&lt;/span&gt;
&lt;span&gt;do_compile[noexec] = &amp;quot;1&amp;quot;&lt;/span&gt;
&lt;span&gt;do_install[noexec] = &amp;quot;1&amp;quot;&lt;/span&gt;
&lt;span&gt;do_populate_sysroot[noexec] = &amp;quot;1&amp;quot;&lt;/span&gt;

&lt;span&gt;do_deploy() {&lt;/span&gt;
&lt;span&gt;    export_dir=&amp;quot;${WORKDIR}/${PN}-export&amp;quot;&lt;/span&gt;
&lt;span&gt;    rm -rf &amp;quot;$export_dir&amp;quot;&lt;/span&gt;
&lt;span&gt;    mkdir -p &amp;quot;$export_dir&amp;quot;&lt;/span&gt;
&lt;span&gt;    cp -a &amp;quot;${RECIPE_SYSROOT}/.&amp;quot; &amp;quot;$export_dir/&amp;quot;&lt;/span&gt;

&lt;span&gt;    sysroot-relativelinks.py &amp;quot;$export_dir&amp;quot;&lt;/span&gt;

&lt;span&gt;    mkdir -p &amp;quot;$export_dir/usr/lib/gcc&amp;quot;&lt;/span&gt;
&lt;span&gt;    ln -s ../riscv32-poky-linux &amp;quot;$export_dir/usr/lib/gcc/riscv32-poky-linux&amp;quot;&lt;/span&gt;
&lt;span&gt;    ln -s usr/include &amp;quot;$export_dir/include&amp;quot;&lt;/span&gt;

&lt;span&gt;    tar -C &amp;quot;$export_dir&amp;quot; -cf - . | \&lt;/span&gt;
&lt;span&gt;      zstd -T0 -f -o &amp;quot;${DEPLOYDIR}/${PN}-${MACHINE}.tar.zst&amp;quot;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;addtask deploy after do_prepare_recipe_sysroot before do_build&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;do_deploy&lt;/code&gt; function implements the sysroot preparation logic that largely
mirrors the previous section. Otherwise, &lt;code&gt;DEPENDS&lt;/code&gt; specifies the needed
dependencies (of these, &lt;code&gt;virtual/${MLPREFIX}compilerlibs&lt;/code&gt; is a bit magic:
this resolves to the compiler runtime provider which pulls in things like
&lt;code&gt;libstdc++&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Build the sysroot with:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bitbake rv32-llvm-deps-sysroot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This performs ~948 build tasks and will produce the sysroot tarball at
&lt;code&gt;tmp/deploy/images/qemuriscv32/rv32-llvm-deps-sysroot-qemuriscv32.tar.zst&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can use it by doing something like:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;SYSROOT=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/rv32depssysroot&amp;quot;&lt;/span&gt;
rm -rf &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$SYSROOT&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$SYSROOT&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
tar --zstd -C &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$SYSROOT&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; -xf &lt;span&gt;\&lt;/span&gt;
  tmp/deploy/images/qemuriscv32/rv32-llvm-deps-sysroot-qemuriscv32.tar.zst
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The sysroot is slightly larger than the one in the section above because it
contains large unstripped static archives like &lt;code&gt;usr/lib/libstdc++.a&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;producing-a-featureful-image-bootable-in-qemu&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#producing-a-featureful-image-bootable-in-qemu&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Producing a featureful image bootable in QEMU&lt;/h2&gt;
&lt;p&gt;We could probably quibble on the definition of &quot;featureful&quot; as listed in the
subheading above. For me, this means an image that boots using systemd and you
can ssh into, roughly approximating what you get from &lt;a href=&quot;https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap&quot;&gt;my debootstrap
recipes&lt;/a&gt;.
But by adding other packages to the image recipe you can certainly make it
more featureful.&lt;/p&gt;
&lt;p&gt;First, let's start to set up the build environment and directories we'll use
for additional recipes. We use &lt;code&gt;distro/poky-altcfg&lt;/code&gt; which is just &lt;a href=&quot;https://git.openembedded.org/bitbake/tree/default-registry/configurations/poky-wrynose.conf.json?h=2.18&quot;&gt;Poky with
systemd as the init
manager&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; yocto-work
./bitbake/bin/bitbake-setup init --non-interactive &lt;span&gt;\&lt;/span&gt;
  --setup-dir-name poky-wrynose-systemd &lt;span&gt;\&lt;/span&gt;
  --skip-selection machine &lt;span&gt;\&lt;/span&gt;
  ./bitbake/default-registry/configurations/poky-wrynose.conf.json &lt;span&gt;\&lt;/span&gt;
  poky &lt;span&gt;\&lt;/span&gt;
  distro/poky-altcfg

. bitbake-builds/poky-wrynose-systemd/build/init-build-env
bitbake-config-build enable-fragment machine/qemuriscv32
bitbake-layers create-layer --add-layer ../layers/meta-rv32-qemu-image

mkdir -p &lt;span&gt;\&lt;/span&gt;
  ../layers/meta-rv32-qemu-image/recipes-core/images &lt;span&gt;\&lt;/span&gt;
  ../layers/meta-rv32-qemu-image/recipes-core/rv32-qemu-config/files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some may prefer to split different aspects of image configuration into
independent recipes, but I opt to combine it into one for simplicity (in this
case, just configuring systemd-networkd dhcp and adding a config file that
will enable &lt;code&gt;sudo&lt;/code&gt; for our user account):&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat &amp;gt; ../layers/meta-rv32-qemu-image/recipes-core/rv32-qemu-config/rv32-qemu-config.bb &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;SUMMARY = &amp;quot;Configuration for RV32 systemd images&amp;quot;&lt;/span&gt;
&lt;span&gt;LICENSE = &amp;quot;MIT-0&amp;quot;&lt;/span&gt;
&lt;span&gt;LIC_FILES_CHKSUM = &amp;quot;file://${COMMON_LICENSE_DIR}/MIT-0;md5=f41b3a5f969eb450434cf0e4f33449b9&amp;quot;&lt;/span&gt;


&lt;span&gt;SRC_URI = &amp;quot; \&lt;/span&gt;
&lt;span&gt;    file://20-wired.network \&lt;/span&gt;
&lt;span&gt;    file://90-rv32-qemu \&lt;/span&gt;
&lt;span&gt;&amp;quot;&lt;/span&gt;

&lt;span&gt;RDEPENDS:${PN} = &amp;quot;systemd-networkd sudo&amp;quot;&lt;/span&gt;
&lt;span&gt;FILES:${PN} = &amp;quot; \&lt;/span&gt;
&lt;span&gt;    ${sysconfdir}/systemd/network/20-wired.network \&lt;/span&gt;
&lt;span&gt;    ${sysconfdir}/sudoers.d/90-rv32-qemu \&lt;/span&gt;
&lt;span&gt;&amp;quot;&lt;/span&gt;

&lt;span&gt;S = &amp;quot;${UNPACKDIR}&amp;quot;&lt;/span&gt;

&lt;span&gt;do_install() {&lt;/span&gt;
&lt;span&gt;    install -d ${D}${sysconfdir}/systemd/network&lt;/span&gt;
&lt;span&gt;    install -m 0644 ${S}/20-wired.network ${D}${sysconfdir}/systemd/network/20-wired.network&lt;/span&gt;

&lt;span&gt;    install -d ${D}${sysconfdir}/sudoers.d&lt;/span&gt;
&lt;span&gt;    install -m 0440 ${S}/90-rv32-qemu ${D}${sysconfdir}/sudoers.d/90-rv32-qemu&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

cat &amp;gt; ../layers/meta-rv32-qemu-image/recipes-core/rv32-qemu-config/files/20-wired.network &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;[Match]&lt;/span&gt;
&lt;span&gt;Type=ether&lt;/span&gt;

&lt;span&gt;[Network]&lt;/span&gt;
&lt;span&gt;DHCP=yes&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

cat &amp;gt; ../layers/meta-rv32-qemu-image/recipes-core/rv32-qemu-config/files/90-rv32-qemu &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;%sudo ALL=(ALL) ALL&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we create the image recipe that will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the above config recipe as well as pull in other needed packages.&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;user&lt;/code&gt; account and set passwords of &lt;code&gt;root&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; to &lt;code&gt;root&lt;/code&gt; and
&lt;code&gt;user&lt;/code&gt; respectively. This follows the approach in the &lt;a href=&quot;https://docs.yoctoproject.org/6.0/ref-manual/classes.html#extrausers&quot;&gt;Yocto
docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make it so &lt;code&gt;runqemu&lt;/code&gt; will configure things so we can connect ssh in via a
Unix domain socket (as done in the debootstrap-based article). Alternatively
you can choose to set &lt;code&gt;QB_SLIRP_OPT = &quot;-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22&quot;&lt;/code&gt; if you'd rather just connect
to &lt;code&gt;localhost:2222&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;ROOT_PASSWORD_HASH=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(printf&lt;/span&gt; &lt;span&gt;&amp;quot;%q&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;openssl passwd -6 root&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;USER_PASSWORD_HASH=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(printf&lt;/span&gt; &lt;span&gt;&amp;quot;%q&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;openssl passwd -6 user&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

cat &amp;gt; ../layers/meta-rv32-qemu-image/recipes-core/images/rv32-qemu-systemd-ssh-image.bb &lt;span&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span&gt;SUMMARY = &amp;quot;Bootable RV32 QEMU image with systemd, networkd, and SSH access&amp;quot;&lt;/span&gt;
&lt;span&gt;LICENSE = &amp;quot;MIT-0&amp;quot;&lt;/span&gt;

&lt;span&gt;inherit image extrausers&lt;/span&gt;

&lt;span&gt;IMAGE_FSTYPES = &amp;quot;ext4&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_FEATURES = &amp;quot;allow-root-login&amp;quot;&lt;/span&gt;

&lt;span&gt;QB_DEFAULT_FSTYPE = &amp;quot;ext4&amp;quot;&lt;/span&gt;
&lt;span&gt;QB_CMDLINE_IP_SLIRP = &amp;quot;ip=none&amp;quot;&lt;/span&gt;
&lt;span&gt;QB_SLIRP_OPT = &amp;quot;-netdev user,id=net0,hostfwd=unix:/tmp/yoctorv32.sock-:22&amp;quot;&lt;/span&gt;
&lt;span&gt;SERIAL_CONSOLES = &amp;quot;115200;ttyS0&amp;quot;&lt;/span&gt;

&lt;span&gt;ROOT_PASSWORD_HASH = &amp;quot;$ROOT_PASSWORD_HASH&amp;quot;&lt;/span&gt;
&lt;span&gt;USER_PASSWORD_HASH = &amp;quot;$USER_PASSWORD_HASH&amp;quot;&lt;/span&gt;

&lt;span&gt;IMAGE_INSTALL = &amp;quot;packagegroup-core-boot&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;os-release&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;systemd-networkd&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;systemd-serialgetty&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;rv32-qemu-config&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;openssh&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;sudo&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;bash&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;iproute2&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;iputils&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_INSTALL += &amp;quot;procps&amp;quot;&lt;/span&gt;

&lt;span&gt;EXTRA_USERS_PARAMS = &amp;quot; \\&lt;/span&gt;
&lt;span&gt;    groupadd sudo; \\&lt;/span&gt;
&lt;span&gt;    usermod -p '\${ROOT_PASSWORD_HASH}' root; \\&lt;/span&gt;
&lt;span&gt;    useradd -m -d /home/user -s /bin/bash -G sudo -p '\${USER_PASSWORD_HASH}' user; \\&lt;/span&gt;
&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now write necessary configuration and build (disabling a number of distro
features that would lead to larger build time). The following results in 4305
build tasks on my machine:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;printf&lt;/span&gt; &lt;span&gt;'DL_DIR = &amp;quot;%s&amp;quot;\n'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.cache/yocto/dl&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; conf/local.conf
&lt;span&gt;printf&lt;/span&gt; &lt;span&gt;'SSTATE_DIR = &amp;quot;%s&amp;quot;\n'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.cache/yocto/sstate&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; conf/local.conf

cat &amp;gt;&amp;gt; conf/local.conf &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;PACKAGE_CLASSES = &amp;quot;package_ipk&amp;quot;&lt;/span&gt;
&lt;span&gt;EXTRA_IMAGE_FEATURES = &amp;quot;&amp;quot;&lt;/span&gt;
&lt;span&gt;IMAGE_FEATURES = &amp;quot;&amp;quot;&lt;/span&gt;

&lt;span&gt;DISTRO_FEATURES:remove = &amp;quot;x11 wayland opengl alsa bluetooth wifi 3g nfc pcmcia usbgadget usbhost nfs zeroconf pulseaudio gobject-introspection-data&amp;quot;&lt;/span&gt;
&lt;span&gt;SERIAL_CONSOLES = &amp;quot;115200;ttyS0&amp;quot;&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

bitbake rv32-qemu-systemd-ssh-image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally we can boot the image (&lt;code&gt;snapshot&lt;/code&gt; means changes to the filesystem
image won't persist, just drop this if that isn't what you desire):&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;DEPLOY=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;/tmp/deploy/images/qemuriscv32&amp;quot;&lt;/span&gt;
rm /tmp/yoctorv32.sock
runqemu &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$DEPLOY&lt;/span&gt;&lt;span&gt;/rv32-qemu-systemd-ssh-image-qemuriscv32.rootfs.qemuboot.conf&amp;quot;&lt;/span&gt; nographic slirp snapshot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And connect via ssh with something like &lt;code&gt;ssh root@unix/tmp/yoctorv32.sock&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-05-18: Add second part to article, covering building a bootable image.&lt;/li&gt;
&lt;li&gt;2026-05-18: Updated to use the Wrynose Yocto release.&lt;/li&gt;
&lt;li&gt;2026-05-11: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Mon, 18 May 2026 12:00:00 +0000</pubDate>
</item>
<item>
	<title>Gyuyoung Kim: Blink for Apple tvOS: 2026 Update</title>
	<guid>https://blogs.igalia.com/gyuyoung/?p=1031</guid>
	<link>https://blogs.igalia.com/gyuyoung/2026/05/15/blink-for-apple-tvos-2026-update/?pk_campaign=feed&amp;pk_kwd=blink-for-apple-tvos-2026-update</link>
	<description>
&lt;p&gt;At BlinkOn 20 in 2025, I introduced our experimental work on bringing &lt;a href=&quot;https://youtu.be/MpOxNPeWf6I?t=75&quot;&gt;Blink to Apple tvOS&lt;/a&gt;. You can also find a blog post covering that initial work here: &lt;a href=&quot;https://blogs.igalia.com/gyuyoung/2026/05/09/introduce-blink-for-apple-tvos/&quot;&gt;https://blogs.igalia.com/gyuyoung/2026/05/09/introduce-blink-for-apple-tvos/&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;If you&#8217;re interested in the background and early prototype, you can find more details in my previous post on Blink for iOS and related work: &lt;a href=&quot;https://blogs.igalia.com/gyuyoung/2024/08/08/chrome-ios-browser-on-blink/&quot;&gt;https://blogs.igalia.com/gyuyoung/2024/08/08/chrome-ios-browser-on-blink/&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Over the past year, we have continued developing this effort, and I recently had a chance to share &lt;a href=&quot;https://docs.google.com/presentation/d/1LrHI7AAqQcNg5t56lddZmdE-4GKZHTy4zaXjCY2-pEg/edit?usp=sharing&quot;&gt;an update&lt;/a&gt; along with a demo running on a real Apple TV device at BlinkOn 21 in 2026. In this post, I&#8217;d like to walk through what has changed since the initial prototype, what works today, and what challenges still remain.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;A quick recap&lt;/h2&gt;



&lt;p&gt;Apple TV runs tvOS, which is derived from iOS, but it comes with important differences. Most notably, tvOS does not provide a WebKit WebView for third-party applications. This means that any application requiring web functionality needs to embed its own web engine. This constraint was one of the key motivations behind exploring whether Blink, originally being ported to iOS, could also be adapted to tvOS.&lt;/p&gt;



&lt;p&gt;While the idea sounds straightforward, the reality is more complicated. tvOS lacks several low-level system APIs required for Chromium&#8217;s multi-process architecture, which makes it impossible to use the standard process model. On top of that, BrowserEngineKit, which the iOS Blink effort relies on, is not available on tvOS. There are also platform restrictions such as the lack of JIT support, and the input model is fundamentally different since Apple TV relies on a remote control rather than touch or pointer-based interaction. Because of these constraints, our goal has not been to build a full-featured browser, but rather to enable Blink-based web capabilities in a way that works within the limitations of the platform.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Progress over the last year&lt;/h2&gt;



&lt;p&gt;Over the past year, we have made steady progress toward that goal. One of the most significant milestones is that we have upstreamed the initial tvOS implementation. This means that the work is no longer just an isolated experiment, but part of the upstream Chromium codebase. As part of this effort, we enabled content_shell running on the tvOS simulator and on actual Apple TV devices. Moving from simulator-only execution to running on real hardware was an important step, as it allowed us to validate real-world behavior and platform integration.&lt;/p&gt;



&lt;p&gt;We have also improved platform integration in several areas. Crashpad support has been added, and input handling for the Apple TV Remote has been significantly improved. The latter is particularly important because navigating web content with a remote requires a focus-based interaction model, which is quite different from what Blink typically assumes on desktop or mobile platforms.&lt;/p&gt;



&lt;p&gt;On the web platform side, we have enabled a number of features that make it possible to run more realistic content. WebAssembly now works in interpreted mode, which allows execution within the constraints of the platform. We have also enabled VP9 software decoding and verified hardware-accelerated decoding for H.264 and H.265. These improvements are essential for media playback scenarios and were necessary to support the demo content.&lt;/p&gt;



&lt;p&gt;To support ongoing development, we also set up reference bots for tvOS builds and tests. This helps ensure that the port can be maintained over time and reduces the risk of regressions as upstream Chromium continues to evolve.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Demo on a real device&lt;/h2&gt;



&lt;p&gt;This demo shows playing a YouTube video in &lt;code&gt;content_shell&lt;/code&gt; running on a real Apple TV device. The demo also showed that we can navigate the video using the remote controller, which highlights the progress we&#8217;ve made in adapting Blink to the tvOS interaction model. While simple, this demonstration is an important milestone because it proves that Blink can run real-world web content on actual hardware.&lt;/p&gt;



&lt;figure class=&quot;wp-block-video&quot;&gt;&lt;video controls=&quot;controls&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/content_shell_tvos_device_kpop_daemon_hunders_small_size.mp4&quot;&gt;&lt;/video&gt;&lt;/figure&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Current limitations&lt;/h2&gt;



&lt;p&gt;Despite this progress, several challenges remain. One of the most noticeable issues is build stability. The tvOS port has been broken frequently, mainly because both the iOS and tvOS ports are still experimental and not always considered in upstream changes. In particular, configurations such as the WebAssembly interpreter mode are not consistently handled by all changes, leading to breakage.&lt;/p&gt;



&lt;p&gt;Testing is another area where limitations are evident. Since the port relies on a single-process model, running web tests is currently not supported, which makes it harder to validate correctness and compatibility. There are also platform-level gaps, such as missing accessibility support and the absence of certain UI components like file choosers and color pickers. As a result, some web pages do not behave as expected on tvOS.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Next steps&lt;/h2&gt;



&lt;p&gt;Looking ahead, our focus is on improving the robustness and maintainability of the port. This includes stabilizing the build, expanding test coverage, and investigating ways to run web tests in a single-process environment. We also plan to keep the port up to date with the latest tvOS SDK and continue maintaining it in upstream Chromium.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Closing thoughts&lt;/h2&gt;



&lt;p&gt;Over the past year, Blink for tvOS has evolved from an initial experiment into a working upstream port that can run on real devices. While it is still early and many challenges remain, the progress so far shows that it is possible to bring Blink-based web capabilities to a constrained platform like tvOS. We will continue exploring this space and see how far this effort can go.&lt;/p&gt;



&lt;p&gt;Finally, I would like to thank all the contributors, reviewers, and sponsors who made this work possible.&lt;/p&gt;



&lt;div class=&quot;wp-block-group&quot;&gt;&lt;div class=&quot;wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained&quot;&gt;
&lt;figure class=&quot;wp-block-image size-full is-resized&quot;&gt;&lt;img width=&quot;285&quot; height=&quot;110&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/youtube-logo.jpg&quot; alt=&quot;&quot; class=&quot;wp-image-1047&quot; /&gt;&lt;/figure&gt;
&lt;/div&gt;&lt;/div&gt;



&lt;figure class=&quot;wp-block-image size-full&quot;&gt;&lt;img width=&quot;234&quot; height=&quot;87&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/igalia-logo.jpg&quot; alt=&quot;&quot; class=&quot;wp-image-1049&quot; /&gt;&lt;/figure&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;&lt;strong&gt;Igalia Contributors&lt;/strong&gt;&lt;/h3&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Abhijeet Kandalkar&lt;/li&gt;



&lt;li&gt;Gyuyoung Kim&lt;/li&gt;



&lt;li&gt;Jeongeun Kim (Julie)&lt;/li&gt;



&lt;li&gt;Raphael Kubo da Costa&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;
&lt;img src=&quot;https://stats.igalia.com/piwik.php?idsite=23&amp;rec=1&amp;url=https%3A%2F%2Fblogs.igalia.com%2Fgyuyoung%2F2026%2F05%2F15%2Fblink-for-apple-tvos-2026-update%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dblink-for-apple-tvos-2026-update&amp;action_name=Blink%20for%20Apple%20tvOS%3A%202026%20Update&amp;urlref=https%3A%2F%2Fblogs.igalia.com%2Fgyuyoung%2Ffeed%2F&quot; width=&quot;0&quot; height=&quot;0&quot; alt=&quot;&quot; /&gt;        </description>
	<pubDate>Fri, 15 May 2026 03:31:34 +0000</pubDate>
	<dc:creator>gyuyoung</dc:creator>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #64</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-64/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-64/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from May 4 to May 11.&lt;/p&gt;
&lt;p&gt;
This week we have a bag of exciting updates, such as fixes to crashes, better
YouTube playback, a handful of advancements to WebXR, and the development
releases of WebKitGTK and WPE WebKit 2.53.2.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;If the filesystem runs out of space while the NetworkProcess is writing into its network cache, the process will crash with &lt;code&gt;SIGBUS&lt;/code&gt;. This would surface to users as the &lt;em&gt;&quot;Internal error fired from WebLoaderStrategy.cpp(559) : internallyFailedLoadTimerFired&quot;&lt;/em&gt; error, and would be handled by re-spawning another NetworkProcess that would similarly fail.&lt;/p&gt;
&lt;p&gt;This was &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312611@main&quot;&gt;addressed&lt;/a&gt; by using &lt;code&gt;fallocate&lt;/code&gt;, if available, to reserve the required size. If &lt;code&gt;fallocate&lt;/code&gt; fails to reserve, the NetworkProcess will skip caching, avoiding the crash. If &lt;code&gt;fallocate&lt;/code&gt; is not available, the existing behaviour is preserved.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;networking-signal-strength&quot;&gt;Networking &#128246;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;Networking support, including the libsoup HTTP library.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;libsoup &lt;a rel=&quot;external&quot; href=&quot;https://gitlab.gnome.org/GNOME/libsoup/-/commit/579ff56747c3aea2fa7dcbf125bfbd424b3ac59a&quot;&gt;now supports&lt;/a&gt; the zstd compression encoding.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;code&gt;getUserMedia()&lt;/code&gt; and &lt;code&gt;getDisplayMedia()&lt;/code&gt; support should work better thanks to a couple &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312594@main&quot;&gt;PipeWire related fixes&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Playback of some YouTube videos (usually at low framerate) has been &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312603@main&quot;&gt;fixed&lt;/a&gt;. Eventually a better solution will involve supporting edit lists in the GStreamer MSE backend.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;A crash when accessing the diagnostics &lt;code&gt;webkit://gpu&lt;/code&gt; page was &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312599@main&quot;&gt;fixed&lt;/a&gt;, making sure we handle the case where &lt;code&gt;libGL.so.1&lt;/code&gt; or &lt;code&gt;libOpenGL.so.0&lt;/code&gt; are missing.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312992@main&quot;&gt;Fixed&lt;/a&gt; missing glyph before ZWJ/ZWNJ if no font is found for the cluster.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The WebXR implementation based on OpenXR &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311589@main&quot;&gt;has&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312072@main&quot;&gt;gained&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/312596@main&quot;&gt;support&lt;/a&gt; for &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XRQuadLayer&quot;&gt;quad&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XREquirectLayer&quot;&gt;equirect&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XRCylinderLayer&quot;&gt;cylinder&lt;/a&gt; layers.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The second unstable releases for the current development cycle have been published: &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/05/06/webkitgtk2.53.2-released.html&quot;&gt;WebKitGTK 2.53.2&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.53.2.html&quot;&gt;WPE WebKit 2.53.2&lt;/a&gt;. Development releases are intended is to gather early feedback on upcoming changes, and as such issue reports are welcome &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org&quot;&gt;in Bugzilla&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 11 May 2026 20:04:43 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Gyuyoung Kim: Introduce Blink for Apple tvOS</title>
	<guid>https://blogs.igalia.com/gyuyoung/?p=1019</guid>
	<link>https://blogs.igalia.com/gyuyoung/2026/05/09/introduce-blink-for-apple-tvos/?pk_campaign=feed&amp;pk_kwd=introduce-blink-for-apple-tvos</link>
	<description>
&lt;p&gt;At BlinkOn 20 in 2025, I gave a short lightning talk about an experimental project called &lt;em&gt;&lt;a href=&quot;https://youtu.be/MpOxNPeWf6I?t=75&quot;&gt;Blink for Apple tvOS&lt;/a&gt;&lt;/em&gt;. Although the presentation took place about a year ago, I wanted to take some time to provide more context on why we started this work, what challenges we encountered along the way, and where the project stands today in this blog again.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Motivation&lt;/h2&gt;



&lt;p&gt;Apple TV runs tvOS, which is based on iOS, but it differs in some important ways. One of the most notable differences is that tvOS does not provide a WebKit WebView for third-party applications. This limitation has significant implications, as applications that need web functionality must embed their own web engine.&lt;/p&gt;



&lt;p&gt;A well-known example is the YouTube app on Apple TV, which uses a custom web engine called &lt;em&gt;Cobalt&lt;/em&gt;. This engine is based on an outdated Chromium fork, and maintaining such a fork becomes increasingly difficult over time, especially as the web platform continues to evolve.&lt;/p&gt;



&lt;p&gt;At the same time, the Chromium community has been exploring Blink-based implementations on Apple platforms, including the experimental Blink for iOS project. As that work progressed, it naturally led to a new question: whether Blink could also be brought to tvOS. Beyond that, we also started wondering if it would be possible to eventually upstream Blink support for tvOS. These questions became the starting point of this project.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Challenges&lt;/h2&gt;



&lt;p&gt;Although tvOS is derived from iOS, porting Blink to this platform turned out to be far from straightforward. One of the biggest challenges comes from the lack of support for the multi-process architecture that Chromium relies on. Several low-level system APIs, such as &lt;code&gt;fork()&lt;/code&gt;, &lt;code&gt;mach_msg()&lt;/code&gt;, and &lt;code&gt;posix_spawn_*()&lt;/code&gt;, are not available on tvOS, which makes it impossible to adopt the standard process model.&lt;/p&gt;



&lt;p&gt;Another major limitation is the absence of BrowserEngineKit, which the Blink port on iOS uses for process management and integration. Without this framework, alternative approaches are required to make the system work on tvOS.&lt;/p&gt;



&lt;p&gt;In addition, tvOS does not allow JIT compilation due to platform restrictions, which directly affects the execution model of V8. This requires running JavaScript in a more restricted mode compared to other platforms.&lt;/p&gt;



&lt;p&gt;The input model also differs significantly. Apple TV primarily relies on a remote control, which leads to a focus-based navigation model rather than pointer-based interaction. This affects how web content needs to be handled and navigated.&lt;/p&gt;



&lt;p&gt;Given all these constraints, it became clear that the goal of this project should not be to build a fully-featured browser. Instead, we focused on enabling Blink-based web capabilities on tvOS in a way that is both practical and maintainable.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Current progress&lt;/h2&gt;



&lt;p&gt;To get Blink running on tvOS, we made a number of changes across both the build system and the runtime. On the build side, we introduced tvOS-specific configurations, including a new toolchain, the &lt;code&gt;IS_IOS_TVOS&lt;/code&gt; build flag in C++ and Objective-C code, and a &lt;code&gt;target_platform = &quot;tvos&quot;&lt;/code&gt; setting in GN.&lt;/p&gt;



&lt;p&gt;On the runtime side, the lack of multi-process support required us to enable a single-process mode. We also removed or disabled code paths that depend on BrowserEngineKit and ensured that unsupported low-level system APIs are not used in the tvOS build.&lt;/p&gt;



&lt;p&gt;Several modifications were also necessary in core components. For example, JIT was disabled in V8, and build configurations were adjusted for third-party libraries such as ANGLE, V8, and Dawn to make them compatible with tvOS.&lt;/p&gt;



&lt;p&gt;At the same time, we worked on integrating platform-specific features. This includes support for hardware-accelerated graphics, media codecs, and Crashpad, as well as improvements to input handling to better support remote-based interaction.&lt;/p&gt;



&lt;p&gt;As a result of these efforts, &lt;code&gt;content_shell&lt;/code&gt; is now able to run in our internal repository, and work toward upstreaming is currently in progress.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Demo&lt;/h2&gt;



&lt;p&gt;To demonstrate the current state of the project, we prepared a simple demo showing Blink running on tvOS. In this demo, a YouTube video is played inside &lt;code&gt;content_shell&lt;/code&gt; on the tvOS simulator, which illustrates that Blink is capable of rendering and running real-world web content in this environment.&lt;br /&gt;&lt;/p&gt;



&lt;figure class=&quot;wp-block-video&quot;&gt;&lt;video controls=&quot;controls&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/content_shell_on_tvos_simulator.mp4&quot;&gt;&lt;/video&gt;&lt;/figure&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Next steps&lt;/h2&gt;



&lt;p&gt;There is still a significant amount of work ahead. In the short term, our focus is on making &lt;code&gt;content_shell&lt;/code&gt; build and run in upstream Chromium, setting up a reference bot for tvOS builds, and passing relevant unit tests, browser tests, and web platform tests.&lt;/p&gt;



&lt;p&gt;In other words, we are currently transitioning from a prototype that &#8220;works&#8221; to something that is stable, maintainable, and ready for upstream integration.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Closing thoughts&lt;/h2&gt;



&lt;p&gt;One of the most interesting aspects of this project is how different tvOS is, despite being closely related to iOS. Even relatively small platform restrictions can have large architectural implications when working with a complex system like Blink.&lt;/p&gt;



&lt;p&gt;While tvOS is a constrained environment, that is precisely what makes it an interesting engineering challenge. We will continue exploring how far we can take this effort and whether Blink on tvOS can eventually become part of upstream Chromium.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Acknowledgements&lt;/h2&gt;



&lt;p&gt;This work would not have been possible without the support of many contributors, including Blink and Chromium reviewers, the Google YouTube team, and many collaborators in the community.&lt;/p&gt;



&lt;p&gt;Thank you all!&lt;/p&gt;



&lt;figure class=&quot;wp-block-image size-full is-resized&quot;&gt;&lt;img width=&quot;285&quot; height=&quot;110&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/youtube-logo-1.jpg&quot; alt=&quot;&quot; class=&quot;wp-image-1053&quot; /&gt;&lt;/figure&gt;



&lt;figure class=&quot;wp-block-image size-full&quot;&gt;&lt;img width=&quot;234&quot; height=&quot;87&quot; src=&quot;https://blogs.igalia.com/gyuyoung/files/2026/04/igalia-logo-2.jpg&quot; alt=&quot;&quot; class=&quot;wp-image-1054&quot; /&gt;&lt;/figure&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;&lt;strong&gt;Igalia Contributors&lt;/strong&gt;&lt;/h3&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Abhijeet Kandalkar&lt;/li&gt;



&lt;li&gt;Gyuyoung Kim&lt;/li&gt;



&lt;li&gt;Jeongeun Kim (Julie)&lt;/li&gt;



&lt;li&gt;Raphael Kubo da Costa&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://stats.igalia.com/piwik.php?idsite=23&amp;rec=1&amp;url=https%3A%2F%2Fblogs.igalia.com%2Fgyuyoung%2F2026%2F05%2F09%2Fintroduce-blink-for-apple-tvos%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dintroduce-blink-for-apple-tvos&amp;action_name=Introduce%20Blink%20for%20Apple%20tvOS&amp;urlref=https%3A%2F%2Fblogs.igalia.com%2Fgyuyoung%2Ffeed%2F&quot; width=&quot;0&quot; height=&quot;0&quot; alt=&quot;&quot; /&gt;        </description>
	<pubDate>Fri, 08 May 2026 15:46:47 +0000</pubDate>
	<dc:creator>gyuyoung</dc:creator>
</item>
<item>
	<title>Alex Bradbury: Bootable QEMU image menagerie with rootless debootstrap</title>
	<guid>https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap</guid>
	<link>https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap</link>
	<description>
&lt;p&gt;Quite some time ago I shared a script and methodology for &lt;a href=&quot;https://muxup.com/2024q4/rootless-cross-architecture-debootstrap&quot;&gt;performing a
cross-architecture debootstrap in a rootless
way&lt;/a&gt;. I had a short
note on producing an image bootable in QEMU, but it was fairly minimal. This
page provides a cookbook / quick reference on producing such images across
various Debian target architectures supported by QEMU. The goal is that the
starting point here &quot;gets the basics right&quot; for local experimentation, but of
course you are encouraged to evolve the recipe for your needs.&lt;/p&gt;
&lt;p&gt;The basic process is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build a root filesystem with &lt;code&gt;rootless-debootstrap-wrapper&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure just enough networking, DNS, serial login, and SSH.&lt;/li&gt;
&lt;li&gt;Create a 30 GiB ext4 filesystem image directly with &lt;code&gt;mkfs.ext4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Boot it with &lt;code&gt;qemu-system-*&lt;/code&gt;, passing the Debian kernel and initrd directly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We use Debian trixie for amd64, arm64, armhf, ppc64el, riscv64, and s390x.
We use sid for ppc64 big endian and loong64. I ran all of this on a current
Arch Linux install.&lt;/p&gt;
&lt;h2 id=&quot;common-setup&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#common-setup&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Common setup&lt;/h2&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo pacman -S debootstrap fakeroot qemu-user-static qemu-user-static-binfmt &lt;span&gt;\&lt;/span&gt;
  qemu-emulators-full e2fsprogs socat debian-archive-keyring debian-ports-archive-keyring
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Put
&lt;a href=&quot;https://github.com/muxup/medley/blob/main/rootless-debootstrap-wrapper&quot;&gt;&lt;code&gt;rootless-debootstrap-wrapper&lt;/code&gt;&lt;/a&gt;
somewhere in your &lt;code&gt;PATH&lt;/code&gt;, then create a working directory:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir -p qemu-debian-images
&lt;span&gt;cd&lt;/span&gt; qemu-debian-images
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Paste the following into your terminal, which will be called to do the common
guest-side configuration. The main thing that's slightly non-standard in this
setup are the systemd drop-in overrides which allow authorised SSH keys to be
specified by teh systemd credential mechanism. If that's not something you're
interested in doing, you can skip the parts touch &lt;code&gt;/etc/systemd/system/ssh*&lt;/code&gt;
altogether.&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;configure_qemu_rootfs&lt;span&gt;()&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
  &lt;span&gt;rootfs=$1&lt;/span&gt;
  &lt;span&gt;console=$2&lt;/span&gt;
  &lt;span&gt;suite=$3&lt;/span&gt;
  &lt;span&gt;hostname=$4&lt;/span&gt;

  &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$rootfs&lt;/span&gt;&lt;span&gt;/_enter&amp;quot;&lt;/span&gt; sh &lt;span&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span&gt;mkdir -p /etc/systemd/network /etc/ssh/sshd_config.d&lt;/span&gt;

&lt;span&gt;cat &amp;gt; /etc/systemd/network/10-qemu.network &amp;lt;&amp;lt;'INNER'&lt;/span&gt;
&lt;span&gt;[Match]&lt;/span&gt;
&lt;span&gt;Type=ether&lt;/span&gt;

&lt;span&gt;[Network]&lt;/span&gt;
&lt;span&gt;DHCP=yes&lt;/span&gt;
&lt;span&gt;INNER&lt;/span&gt;

&lt;span&gt;cat &amp;gt; /etc/ssh/sshd_config.d/20-qemu-login.conf &amp;lt;&amp;lt;'INNER'&lt;/span&gt;
&lt;span&gt;PermitRootLogin yes&lt;/span&gt;
&lt;span&gt;PasswordAuthentication yes&lt;/span&gt;
&lt;span&gt;INNER&lt;/span&gt;
&lt;span&gt;rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub&lt;/span&gt;

&lt;span&gt;cat &amp;gt; /etc/systemd/system/ssh.service.d/10-ephemeral-authorized-keys.conf &amp;lt;&amp;lt;'INNER'&lt;/span&gt;
&lt;span&gt;[Service]&lt;/span&gt;
&lt;span&gt;ImportCredential=ssh.ephemeral-authorized_keys-all&lt;/span&gt;
&lt;span&gt;ExecStart=&lt;/span&gt;
&lt;span&gt;ExecStart=/usr/sbin/sshd -D \$SSHD_OPTS -o &amp;quot;AuthorizedKeysFile .ssh/authorized_keys&amp;quot; -o &amp;quot;AuthorizedKeysCommand /usr/bin/cat \${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all&amp;quot; -o &amp;quot;AuthorizedKeysCommandUser root&amp;quot;&lt;/span&gt;
&lt;span&gt;INNER&lt;/span&gt;

&lt;span&gt;cat &amp;gt; /etc/systemd/system/sshd-vsock@.service.d/10-ephemeral-authorized-keys.conf &amp;lt;&amp;lt;'INNER'&lt;/span&gt;
&lt;span&gt;[Service]&lt;/span&gt;
&lt;span&gt;ImportCredential=ssh.ephemeral-authorized_keys-all&lt;/span&gt;
&lt;span&gt;ExecStart=&lt;/span&gt;
&lt;span&gt;ExecStart=-/usr/sbin/sshd -i \$SSHD_OPTS -o &amp;quot;AuthorizedKeysFile .ssh/authorized_keys&amp;quot; -o &amp;quot;AuthorizedKeysCommand /usr/bin/cat \${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all&amp;quot; -o &amp;quot;AuthorizedKeysCommandUser root&amp;quot;&lt;/span&gt;
&lt;span&gt;INNER&lt;/span&gt;

&lt;span&gt;/usr/bin/systemd-firstboot --locale=C.UTF-8 --hostname=${hostname} --force&lt;/span&gt;
&lt;span&gt;ln -sf ../locale.conf /etc/default/locale&lt;/span&gt;
&lt;span&gt;printf '127.0.1.1 %s\n' &amp;quot;$hostname&amp;quot; &amp;gt;&amp;gt; /etc/hosts&lt;/span&gt;
&lt;span&gt;printf 'uninitialized\n' &amp;gt; /etc/machine-id&lt;/span&gt;
&lt;span&gt;mkdir -p /var/lib/dbus&lt;/span&gt;
&lt;span&gt;rm -f /var/lib/dbus/machine-id&lt;/span&gt;
&lt;span&gt;ln -sf /etc/machine-id /var/lib/dbus/machine-id&lt;/span&gt;

&lt;span&gt;systemctl enable systemd-networkd systemd-resolved systemd-timesyncd ssh&lt;/span&gt;
&lt;span&gt;systemctl enable serial-getty@${console}.service&lt;/span&gt;

&lt;span&gt;ln -sf ../run/systemd/resolve/resolv.conf /etc/resolv.conf&lt;/span&gt;
&lt;span&gt;printf 'root:root\n' | chpasswd&lt;/span&gt;
&lt;span&gt;adduser --gecos &amp;quot;,,,&amp;quot; --disabled-password user&lt;/span&gt;
&lt;span&gt;usermod -aG sudo user&lt;/span&gt;
&lt;span&gt;printf 'user:user\n' | chpasswd&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

  &lt;span&gt;if&lt;/span&gt; &lt;span&gt;[&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$suite&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; trixie &lt;span&gt;]&lt;/span&gt;; &lt;span&gt;then&lt;/span&gt;
    cat &amp;gt;&amp;gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$rootfs&lt;/span&gt;&lt;span&gt;/etc/apt/sources.list&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;deb https://security.debian.org/debian-security trixie-security main&lt;/span&gt;
&lt;span&gt;deb https://deb.debian.org/debian trixie-updates main&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;
  &lt;span&gt;fi&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This should &lt;em&gt;not&lt;/em&gt; be exposed on any public network without further
configuration. You can ssh in to either the root user or &lt;code&gt;user&lt;/code&gt; via ssh, using
password &lt;code&gt;root&lt;/code&gt; or &lt;code&gt;user&lt;/code&gt; respectively. The commands below expose ssh via a
unix domain socket. One potential gotcha: this unix domain socket must not
have any &lt;code&gt;-&lt;/code&gt; in its name as that collides with the splitting done for the
&lt;code&gt;hostfwd&lt;/code&gt; argument. The examples given below avoid this issue.  The boot
commands pass &lt;code&gt;net.ifnames=0&lt;/code&gt;, so the single QEMU network device is
consistently named &lt;code&gt;eth0&lt;/code&gt; and matched by the networkd config above (I found
this more reliable than &lt;code&gt;ln -sf /dev/null /etc/udev/rules.d/80-net-setup-link.rules&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;For simplicity we make use of &lt;code&gt;mkfs.ext4&lt;/code&gt;'s ability to populate the image from
a directory. Pleasingly, &lt;code&gt;mkfs.xfs&lt;/code&gt; gained a similar ability in the &lt;a href=&quot;https://lwn.net/Articles/1042751/&quot;&gt;xfsprogs
6.17.0 release&lt;/a&gt; in Oct 2025. If you have
a new enough version, and you prefer an XFS rootfs over ext4 you can tweak the
recipes below to do the following for the final image population step:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.xfs -f -q -L rootfs &lt;span&gt;\&lt;/span&gt;
    -d file,name&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt;,size&lt;span&gt;=&lt;/span&gt;30g &lt;span&gt;\&lt;/span&gt;
    -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;,atime&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;amd64--x86-64&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#amd64--x86-64&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;amd64 / x86-64&lt;/h2&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/amd64-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;amd64 &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-amd64,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttyS0 trixie qemu-amd64-trixie
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinuz-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; amd64-trixie-qemu
qemu-system-x86_64 &lt;span&gt;\&lt;/span&gt;
  -accel kvm &lt;span&gt;\&lt;/span&gt;
  -machine q35 &lt;span&gt;\&lt;/span&gt;
  -cpu host &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-pci,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_amd64.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-pci,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-pci,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttyS0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above assumes you are running on a x86-64 host, hence enables KVM. If not,
then drop &lt;code&gt;-accel kvm&lt;/code&gt; and use &lt;code&gt;-cpu max&lt;/code&gt; instead of &lt;code&gt;-cpu host&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;arm64--aarch64&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#arm64--aarch64&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;arm64 / AArch64&lt;/h2&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/arm64-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;arm64 &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-arm64,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttyAMA0 trixie qemu-arm64-trixie
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinuz-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; arm64-trixie-qemu
qemu-system-aarch64 &lt;span&gt;\&lt;/span&gt;
  -machine virt &lt;span&gt;\&lt;/span&gt;
  -cpu cortex-a57 &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-device,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_arm64.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-device,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-device,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttyAMA0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;armhf--32-bit-arm&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#armhf--32-bit-arm&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;armhf / 32-bit ARM&lt;/h2&gt;
&lt;p&gt;For this one I had to add the relevant virtio modules to the initrd.&lt;/p&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/armhf-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;armhf &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-armmp,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttyAMA0 trixie qemu-armhf-trixie
&lt;span&gt;printf&lt;/span&gt; &lt;span&gt;'%s\n'&lt;/span&gt; virtio_mmio virtio_blk virtio_net &amp;gt;&amp;gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/etc/initramfs-tools/modules&amp;quot;&lt;/span&gt;
&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/_enter&amp;quot;&lt;/span&gt; update-initramfs -u -k all
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinuz-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; armhf-trixie-qemu
qemu-system-arm &lt;span&gt;\&lt;/span&gt;
  -machine virt &lt;span&gt;\&lt;/span&gt;
  -cpu cortex-a15 &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 4G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-device,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_armhf.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-device,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-device,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttyAMA0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;riscv64&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#riscv64&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;riscv64&lt;/h2&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/riscv64-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;riscv64 &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-riscv64,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttyS0 trixie qemu-riscv64-trixie
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinux-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; riscv64-trixie-qemu
qemu-system-riscv64 &lt;span&gt;\&lt;/span&gt;
  -machine virt &lt;span&gt;\&lt;/span&gt;
  -cpu rv64 &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-device,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_riscv64.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-device,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-device,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -bios /usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.bin &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttyS0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The above assumes you have opensbi installed in /usr/share/qemu (it is put
here by the qemu-system-riscv-firmware package on Arch).&lt;/p&gt;
&lt;h2 id=&quot;ppc64el&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#ppc64el&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;ppc64el&lt;/h2&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/ppc64el-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;ppc64el &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-powerpc64le,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; hvc0 trixie qemu-ppc64el-trixie
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinux-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; ppc64el-trixie-qemu
qemu-system-ppc64 &lt;span&gt;\&lt;/span&gt;
  -machine pseries &lt;span&gt;\&lt;/span&gt;
  -cpu power9 &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-pci,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_ppc64el.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-pci,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-pci,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=hvc0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;s390x-systemz&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#s390x-systemz&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;s390x (SystemZ)&lt;/h2&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/s390x-trixie-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;s390x &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;trixie &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-s390x,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttysclp0 trixie qemu-s390x-trixie
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinuz-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; s390x-trixie-qemu
qemu-system-s390x &lt;span&gt;\&lt;/span&gt;
  -machine s390-ccw-virtio &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-ccw,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_s390x.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-ccw,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-ccw,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttysclp0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;ppc64-big-endian&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#ppc64-big-endian&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;ppc64 big-endian&lt;/h2&gt;
&lt;p&gt;This is a Debian ports target, so we use &lt;code&gt;sid&lt;/code&gt; and the ports mirror.&lt;/p&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/ppc64-sid-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;ppc64 &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;sid &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian-ports &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --keyring&lt;span&gt;=&lt;/span&gt;/usr/share/keyrings/debian-ports-archive-keyring.gpg &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-powerpc64,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; hvc0 sid qemu-ppc64-sid
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinux-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; ppc64-sid-qemu
qemu-system-ppc64 &lt;span&gt;\&lt;/span&gt;
  -machine pseries &lt;span&gt;\&lt;/span&gt;
  -cpu power9 &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-pci,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_ppc64.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-pci,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-pci,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=hvc0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;loong64--loongarch&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#loong64--loongarch&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;loong64 / LoongArch&lt;/h2&gt;
&lt;p&gt;For this one, we need EDK2 which you can obtain from Debian's
qemu-efi-loongarch64 package (&lt;code&gt;QEMU_EFI.fd&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Build:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WORK=$PWD&lt;/span&gt;/loong64-sid-qemu
&lt;span&gt;ROOTFS=$WORK&lt;/span&gt;/rootfs
mkdir -p &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

rootless-debootstrap-wrapper &lt;span&gt;\&lt;/span&gt;
  --arch&lt;span&gt;=&lt;/span&gt;loong64 &lt;span&gt;\&lt;/span&gt;
  --suite&lt;span&gt;=&lt;/span&gt;sid &lt;span&gt;\&lt;/span&gt;
  --mirror&lt;span&gt;=&lt;/span&gt;https://deb.debian.org/debian &lt;span&gt;\&lt;/span&gt;
  --cache-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/debcache&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --target-dir&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  --include&lt;span&gt;=&lt;/span&gt;linux-image-loong64,zstd,dbus,systemd-resolved,systemd-timesyncd,openssh-server,sudo

configure_qemu_rootfs &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; ttyS0 sid qemu-loong64-sid
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/vmlinuz-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/kernel&amp;quot;&lt;/span&gt;
cp &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;/boot/initrd.img-* &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/initrd&amp;quot;&lt;/span&gt;
fakeroot -i &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;/.fakeroot.env&amp;quot;&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  mkfs.ext4 -q -L rootfs -d &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ROOTFS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WORK&lt;/span&gt;&lt;span&gt;/rootfs.img&amp;quot;&lt;/span&gt; 30G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Boot:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;cd&lt;/span&gt; loong64-sid-qemu
cp ../QEMU_EFI.fd .
qemu-system-loongarch64 &lt;span&gt;\&lt;/span&gt;
  -machine virt,firmware&lt;span&gt;=&lt;/span&gt;QEMU_EFI.fd &lt;span&gt;\&lt;/span&gt;
  -smp &lt;span&gt;2&lt;/span&gt; &lt;span&gt;\&lt;/span&gt;
  -m 8G &lt;span&gt;\&lt;/span&gt;
  -drive &lt;span&gt;file=&lt;/span&gt;rootfs.img,if&lt;span&gt;=&lt;/span&gt;none,id&lt;span&gt;=&lt;/span&gt;hd,format&lt;span&gt;=&lt;/span&gt;raw &lt;span&gt;\&lt;/span&gt;
  -device virtio-blk-pci,drive&lt;span&gt;=&lt;/span&gt;hd &lt;span&gt;\&lt;/span&gt;
  -netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;unix:/tmp/qemu_loong64.sock-:22 &lt;span&gt;\&lt;/span&gt;
  -device virtio-net-pci,netdev&lt;span&gt;=&lt;/span&gt;net &lt;span&gt;\&lt;/span&gt;
  -object rng-random,filename&lt;span&gt;=&lt;/span&gt;/dev/urandom,id&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -device virtio-rng-pci,rng&lt;span&gt;=&lt;/span&gt;rng &lt;span&gt;\&lt;/span&gt;
  -kernel kernel &lt;span&gt;\&lt;/span&gt;
  -initrd initrd &lt;span&gt;\&lt;/span&gt;
  -nographic &lt;span&gt;\&lt;/span&gt;
  -append &lt;span&gt;&amp;quot;rw root=LABEL=rootfs console=ttyS0 net.ifnames=0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;logging-in&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#logging-in&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Logging in&lt;/h2&gt;
&lt;p&gt;As noted above, you can log in with &lt;code&gt;root&lt;/code&gt;/&lt;code&gt;root&lt;/code&gt; or &lt;code&gt;user&lt;/code&gt;/&lt;code&gt;user&lt;/code&gt;. The launch
commands above run QEMU with &lt;code&gt;-nographic&lt;/code&gt; causing your terminal to be
connected to the guest serial console. &lt;code&gt;Ctrl-c&lt;/code&gt; alone won't kill the virtual
machine, so it's helpful to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Ctrl-a x&lt;/code&gt; exits QEMU immediately.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl-a c&lt;/code&gt; switches between the guest serial console and the QEMU monitor.
From the monitor, &lt;code&gt;quit&lt;/code&gt; exits QEMU and &lt;code&gt;system_powerdown&lt;/code&gt; asks the guest to
shut down cleanly.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl-a h&lt;/code&gt; prints QEMU's help for the other &lt;code&gt;Ctrl-a&lt;/code&gt; shortcuts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the guest is booted, you can connect via ssh to the Unix domain socket
that forwards to guest port 22. Assuming you're on a recent system with
&lt;code&gt;systemd-ssh-proxy&lt;/code&gt; (and the ssh config file it adds) present, this can be
done with e.g.:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh root@unix/tmp/qemu_amd64.sock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without &lt;code&gt;systemd-ssh-proxy&lt;/code&gt;, you can specify &lt;code&gt;ProxyCommand&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;# For socat:&lt;/span&gt;
ssh -o &lt;span&gt;&amp;quot;ProxyCommand=socat - UNIX-CONNECT:/tmp/qemu_amd64.sock&amp;quot;&lt;/span&gt; root@vm
&lt;span&gt;# Or for OpenBSD netcat:&lt;/span&gt;
ssh -o &lt;span&gt;&amp;quot;ProxyCommand=nc -U /tmp/qemu_amd64.sock&amp;quot;&lt;/span&gt; root@vm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you'd rather use a TCP port, replace the &lt;code&gt;-netdev&lt;/code&gt; part of the qemu launch
command with something like the following and connect to &lt;code&gt;localhost:2222&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-netdev user,id&lt;span&gt;=&lt;/span&gt;net,hostfwd&lt;span&gt;=&lt;/span&gt;tcp:127.0.0.1:2222-:22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The systemd-provided config for use of &lt;code&gt;systemd-ssh-proxy&lt;/code&gt; disables host
identity checks, which is what you typically want with this setup. If using
one of the &lt;code&gt;ProxyCommand&lt;/code&gt; options above you may want to add &lt;code&gt;-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null&lt;/code&gt; to your `ssh
invocation.&lt;/p&gt;
&lt;h2 id=&quot;alternative-ssh-over-vsock&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#alternative-ssh-over-vsock&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Alternative: SSH over vsock&lt;/h2&gt;
&lt;p&gt;It's possible to avoid QEMU user-mode networking and use ssh via &lt;code&gt;AF_VSOCK&lt;/code&gt;.
This can even work without any additional image changes as
&lt;code&gt;systemd-ssh-generator&lt;/code&gt; in the guest will generate an appropriate
socket-activated sshd service if vsock is present. On the host, you'll need to
pick a numeric address for the vsock ('guest CID') that isn't already in use on
the system, and change the qemu command line to add the appropriate vsock
device with that CID assigned. The vsock device used depends on the machine
being emulated - e.g. whether to attach on PCI or the virtio device bus.&lt;/p&gt;
&lt;p&gt;For amd64, ppc64el, ppc64, and loong64, add:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-device vhost-vsock-pci,guest-cid&lt;span&gt;=&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For arm64, armhf, and riscv64, add:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-device vhost-vsock-device,guest-cid&lt;span&gt;=&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For s390x, use:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-device vhost-vsock-ccw,guest-cid&lt;span&gt;=&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Assuming your host has &lt;code&gt;systemd-ssh-proxy&lt;/code&gt; and its OpenSSH config installed,
you can connect with:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh root@vsock/42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-an-injected-ssh-key&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#using-an-injected-ssh-key&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Using an injected SSH key&lt;/h2&gt;
&lt;p&gt;Images set up using the recipes above allow a public key to be specified at
boot time using the systemd system credential mechanism. Just append the
following to the qemu launch command and you can ssh in using that key:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-smbios &lt;span&gt;&amp;quot;type=11,value=io.systemd.credential.binary:ssh.ephemeral-authorized_keys-all=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;base64 -w0 ~/.ssh/id_ed25519.pub&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-05-11:
&lt;ul&gt;
&lt;li&gt;Add notes on ssh over AF_VSOCK.&lt;/li&gt;
&lt;li&gt;Add note about ssh host key checking.&lt;/li&gt;
&lt;li&gt;Add support for injecting ssh keys using systemd's credential mechanism.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2026-05-10:
&lt;ul&gt;
&lt;li&gt;Add note about serial console shortcuts.&lt;/li&gt;
&lt;li&gt;Use systemd-ssh-proxy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2026-05-09:
&lt;ul&gt;
&lt;li&gt;Tweak shell commands slightly (no &lt;code&gt;cp -f&lt;/code&gt;) and use &lt;code&gt;Type=ether&lt;/code&gt; for
systemd-networkd match.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2026-05-07:
&lt;ul&gt;
&lt;li&gt;Add note about how to use an XFS rootfs.&lt;/li&gt;
&lt;li&gt;Get rid of vestigial errexit usage in common setup script.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2026-05-05: Use &lt;code&gt;net.ifnames=0&lt;/code&gt; command line argument rather than
&lt;code&gt;ln -sf /dev/null /etc/udev/rules.d/80-net-setup-link.rules&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;2026-05-04: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Mon, 04 May 2026 12:00:00 +0000</pubDate>
</item>
<item>
	<title>Brian Kardell: Browsers and Language Features</title>
	<guid>https://bkardell.com/blog/language.html</guid>
	<link>https://bkardell.com/blog/language.html</link>
	<description>
&lt;h1 class=&quot;contextual-heading&quot;&gt;Browsers and Language Features&lt;/h1&gt;
	&lt;p class=&quot;segue&quot;&gt;Web browsers do an astounding amount of stuff with language - and we're always trying to do more.  Like most things that we get &quot;for free&quot; and don't give a lot of attention to, there is a lot more to it than you might realize.&lt;/p&gt;
	
	&lt;p&gt;Recently, I've been bouncing around looking at several browser language features.  Today, our web browsers can listen to us and transcribe our words, they can speak to us, they can do spell checking, and grammar checking. At a surface level there is just a seeming &quot;need to understand words&quot;, right?  But what exactly that &lt;em&gt;means&lt;/em&gt; is actually really  variant!&lt;/p&gt;
	
		&lt;h2 class=&quot;contextual-heading&quot;&gt;Speech to Text... but more.&lt;/h2&gt;
	&lt;p&gt;Today, your browser can talk to you via the Web Speech API. That's not new.  Recently there was a W3C workshop on Voice Interaction. All of the presentations are &lt;a href=&quot;https://www.youtube.com/watch?v=gB7IHey8o_Q&amp;list=PLNhYw8KaLq2U_6YPLm6UitxQyXzGcy6jQ&quot;&gt;available on the W3C's playlist&lt;/a&gt;.  For some reason the sound on some of them seems pretty bad, and unfortunately many very good discusions aren't recorded.  One presentation &lt;a href=&quot;https://youtu.be/Bc04fXrR1U4?si=jL7suHS-v1kye_KY&quot;&gt;Solving Lead vs. Lead: Consistent Pronunciation for Web Content&lt;/a&gt; was very interesting to me. In my 2017 post &lt;a href=&quot;https://bkardell.com/blog/Greetings-Professor-Falken.html&quot;&gt;Greetings, Professor Falken&lt;/a&gt; I talked about how the underlying speech system was able to gather a &lt;em&gt;lot&lt;/em&gt; from context. For example, even back then, all of the browsers and OSes I could try got these &quot;right&quot; in that they were not naively read but rather read as the correct &quot;forms&quot;.&lt;/p&gt;
	&lt;pre&gt;&lt;code&gt;1. Pi is about 3.14
2. We loaded the 4x4
3. Please meet me at 3.14pm EST
4. My birthday is 2/17/1974
&lt;/code&gt;&lt;/pre&gt;
	&lt;p&gt;Going on 10 years later, today's speech systems would get &lt;em&gt;most&lt;/em&gt; &quot;lead&quot; (the heavy element) vs &quot;lead&quot; (being out in front) examples correct because of context.  But, in the talk and later discussion, there &lt;em&gt;are&lt;/em&gt; plenty of examples where you shouldn't really rely on that. For example, if we're trying to &lt;em&gt;teach&lt;/em&gt; someone how something is said.  That isn't really just about academics, instances abound.&lt;/p&gt;
	&lt;p&gt;In that same 2017 post I also mentioned that what the speech subsystems didn't get right was &quot;Greetings, Professor Falken&quot;.  They didn't pronounce it like the movie. I overcame that in the post by feeding it a misspelling, but this was sort of non-deterministic too, and solved by trial and error.  Sarah (the presenter of the W3C talk) lays into a lot of examples like this - we discussed way more examples than I was initially considering where either there was no great context, or where no amount of context is actually likely to help.  A lot of these cases are proper nouns or regional pronunciations.  Montpelier, VT and Montpelier in the south of France are pronounced very differently.  Barre, VT and Martin Barre the lead guitarist for Jethro Tull are pronounced very differently.  These are somewhat famous examples.  Ana can be pronounced several ways - which one is this?  How do you pronounce the names of fictional characters? Or companies and products? And so on.&lt;/p&gt;
	&lt;p&gt;Sarah is from ETS, who also participated in an earlier &quot;Spoken Presentation Task Force&quot; which &lt;a href=&quot;https://www.w3.org/TR/spoken-html/&quot;&gt;produced a proposal for Spoken HTML&lt;/a&gt; - and in &lt;em&gt;theory&lt;/em&gt; Web Speech should support SSML too.  So, that's the proposed solution for that kind of problem. Keep that in the back of your mind for now. &lt;/p&gt;
	&lt;p&gt;As you can imagine, this is all true in the reverse direction as well.  That is, if you are transcribing speech there are sound-alike words and so on - which do I transcribe? &#8220;cache&#8221; or &#8220;cash&#8221;? Is it &quot;Barre&quot; or &quot;Barry&quot; or &quot;Berry&quot; Vermont? Do I write &quot;4 by 4&quot; or &quot;4x4&quot; or &quot;Four by Four&quot;?  Again, today's models will do very well, generally - but you'll have problems with most of those same things, including especially all of those proper nouns.&lt;/p&gt;
	&lt;p&gt;At the end of the day all of the listening part is statistics based.  The listening machine is this many % confident that it heard X, a little less confident that it heard Y and so on... Then it's sort of a lot like an LLM.  So, given context it can do better.  Contextual biasing is a simple, common way to improve the result: Just tell it a list of words you might be more likely to use and it'll bias toward them (optionally, provide a weight as to how much more likely it is to be this word, vs something that sounds similar).  So, in our example above, if your page is a discussion forum about Vermont politics, it's probably going to hear what is pronounced like &quot;berry&quot; but we want it to write &quot;Barre&quot; and not &quot;Barry&quot; or &quot;bury&quot; or &quot;berry&quot; or anything else, even if it's just a single word without a larger context.  Like a host asks &quot;where was that?&quot; and someone replies &quot;Barre&quot;.&lt;/p&gt;
	&lt;p&gt;That functionality was recently added to the Web Speech API in Chrome.&lt;/p&gt;


	&lt;h2 class=&quot;contextual-heading&quot;&gt;More Different Than You Might Think...&lt;/h2&gt;
	&lt;p&gt;Listening, and speaking both &lt;em&gt;feel&lt;/em&gt; so related, but the approach to the two is actually &lt;em&gt;very&lt;/em&gt; different.   In one it's just a word and optionally a number, and in the other is more complex -  you need specialized knowledge about SSML and IPA (International Phonetic Alphabet), some mapping and... in practice more.  That's because while SSML &lt;em&gt;seems&lt;/em&gt; very deterministic compared to an arbitrary number and statistics, in practice, results vary.  In &lt;em&gt;theory&lt;/em&gt;, providing the IPA to pronounce &quot;tomato&quot; both of the popular ways- &lt;code&gt;/t&#601;&#712;m&#593;&#720;t&#601;&#650;/&lt;/code&gt; and &lt;code&gt;/t&#601;&#712;me&#618;to&#650;/&lt;/code&gt; (think of the old song &lt;a href=&quot;https://www.youtube.com/watch?v=J2oEmPP5dTM&quot;&gt;Let's Call the Whole Thing Off&lt;/a&gt;) should make them be pronounced as expected.  However, sending this to different speech engines yields unpredictable results.  There isn't a way to check the authoritativeness.  If the engine doesn't support IPA, it may read contents of the SSML itself, &lt;em&gt;if&lt;/em&gt; it exists.  If you Give it a name like &lt;a href=&quot;https://en.wikipedia.org/wiki/Saoirse_Ronan&quot;&gt;Saoirse Ronan&lt;/a&gt; - even with SSML and a very good engine that supports phonemes and IPA - it &lt;em&gt;often&lt;/em&gt;  still won't actually pronounce it properly unless it has a good Irish voice... And there aren't actually a lot of them.  &lt;/p&gt;
	&lt;p&gt;Contextual biasing is clearly no help on the pronunciation side of things.  But, flip it around and you might think that IPA would be a good input as to how to &lt;em&gt;hear&lt;/em&gt; words too - but I've not really found anyone who even tries to do that.&lt;/p&gt;


	&lt;h2 class=&quot;contextual-heading&quot;&gt;And now for something completely different&lt;/h2&gt;
	&lt;p&gt;As I hinted at the beginning, those are only two examples. We also have things like spell checking - that's the part that marks things as spelled incorrectly - or grammar checking.  You can independently style these two cases with the CSS &lt;code&gt;:spelling-error&lt;/code&gt; and &lt;code&gt;:grammar-error&lt;/code&gt; pseudo-classes independently, &lt;a href=&quot;https://blogs.igalia.com/schenney/css-spelling-and-grammar-styling/&quot;&gt;as written about by my colleague Stephen Chenney&lt;/a&gt; who did the work at Igalia thanks to funding from Bloomberg.  &lt;/p&gt;
	&lt;p&gt;Note that neither of these &lt;em&gt;suggests&lt;/em&gt; words in any way.  None of them deal with the concepts of autosuggest or autocorrect or autofill or hints or maybe soon even stuff to indicate and generate text rewrites in the future. &lt;/p&gt;
	&lt;p&gt;None of these are the same thing. Many of them &lt;em&gt;also&lt;/em&gt; inevitably have this same general specialized domains problem. For example, if I am editing some Hitchhiker's Guide to the Galaxy wiki and it contains a quote of Vogon poetry:&lt;/p&gt;
	&lt;blockquote&gt;Oh freddled gruntbuggly,
Thy micturations are to me, (with big yawning)
As plurdled gabbleblotchits,
On a lurgid bee,
That mordiously hath blurted out,
Its earted jurtles, grumbling
Into a rancid festering confectious organ squealer. 
[drowned out by moaning and screaming]&lt;/blockquote&gt;

	&lt;p&gt;None of those words are actually spelling errors in this context, and highlighting them as such entirely ruins the experience - there are so many false positives, you miss the real errors.&lt;/p&gt;
	&lt;p&gt;And almost all of these problems are also somehow still differently handled. &lt;/p&gt;
	&lt;p&gt;For example, spell check dictionaries are located at potentially several levels.  Sometimes you have one in a browser, sometimes the browser is just integrated with the OS level one. Sometimes a browser has 1 per profile. In Android, virtual keyboards have their own dictionaries.&lt;/p&gt;
	&lt;p&gt;But what does that even mean, &quot;dictionaries&quot;?  &lt;/p&gt;
	&lt;p&gt;Just as is in the cases of speech to text, or text to speech, it can mean something different. A lot of things use a thing called &lt;a href=&quot;https://hunspell.github.io/&quot;&gt;Hunspell&lt;/a&gt; which packs up language &quot;dictionaries&quot; that work for all languages and more efficiently can encode complexities like plural rules, autosuggestion help and all sorts of things.  For example, here is the LibreOffice &lt;a href=&quot;https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.dic&quot;&gt;en_GB.dic&lt;/a&gt;. In this file you'll find simple words like &lt;code&gt;ablaze&lt;/code&gt; but most words have some kind of 'affixes' and you'll find similar words in runs that look something like this&lt;/p&gt;
	&lt;pre&gt;&lt;code&gt;spoon-feed/SG
spoon/D6GSM
spoonbill/MS
Spooner/M
spoonerism/SM
spoonful/MS
spoonier
spooniest
spooniness/M	Noun: uncountable
spoonsful
spoony/SMY&lt;/code&gt;&lt;/pre&gt;
	&lt;p&gt;These connect to affix definitions in an parallel &lt;a href=&quot;https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.aff&quot;&gt;&quot;.aff&quot; file&lt;/a&gt;.  This is full of entries like &lt;/p&gt;
	&lt;pre&gt;&lt;code&gt;SFX n e ations [^ckt]e 
SFX n 0 ations [^e]r 
SFX n e ations [iou]te 
SFX n y ations py 
SFX n ke cation ke 
SFX n ke cation's ke 
SFX n ke cations ke 
SFX n y ication [^p]y 
SFX n y ication's [^p]y &lt;/code&gt;&lt;/pre&gt;
	&lt;p&gt;In other words... It's complicated, but covers a lot.&lt;/p&gt;
	&lt;p&gt;If you ever right clicked something and selected &quot;learn word&quot; or &quot;add to dictionary&quot; or something,  you're doing the equivalent of adding &lt;code&gt;ablaze&lt;/code&gt; in the &quot;.dic&quot; file - just a simple string.  But, unlike Hunspell or other complex formats, it's simple enough that even an &lt;em&gt;end&lt;/em&gt; user can do it.&lt;/p&gt;
	&lt;p&gt;Bloomberg is also sponsoring our work on a proposal called the &lt;code&gt;SpellCheckCustomDictionary&lt;/code&gt; API. We've been working on &lt;a href=&quot;https://github.com/Igalia/explainers/blob/main/spell-check-dictionary/README.md&quot;&gt;an explainer&lt;/a&gt;.  Realistically maybe we should call it &lt;code&gt;SpellCheckExclusions&lt;/code&gt; to be clearer that all it really does is allow a site to provide a list of words to not match as &lt;code&gt;:spelling-error&lt;/code&gt;. We also let you do that in groups so that any active spellchecking can just re-run once.&lt;/p&gt;
	&lt;p&gt;There was a lot of debate and thought about how much of this should be shared or centralized, but starting with a simple list that can be well defined in terms of standard exclusion seems like a nice first step.  It does mean that you would need to potentially add both &lt;code&gt;&quot;Gandalf&quot;&lt;/code&gt; and &lt;code&gt;&quot;Gandalf's&quot;&lt;/code&gt; as valid, and &lt;code&gt;&quot;hobbit&quot;&lt;/code&gt; and &lt;code&gt;&quot;hobbits&quot;&lt;/code&gt; and &lt;code&gt;&quot;hobbit's&quot;&lt;/code&gt; and &lt;code&gt;&quot;hobbits'&quot;&lt;/code&gt;.  But at least this is simple and understandable, works in every language and widens the pool of developers who might do it.  A nice trade-off here might be to allow some sense of regexp in this list - though, for practical purposes it should probably be a limited subset (() for grouping, | for alternation, ? for optional, plus maybe an &lt;code&gt;:i&lt;/code&gt; sigil for case insensitivity).  This would potentially help make it easier for some authors to express more with less and help shrink the memory initially required, while still not getting too specialized and fairly easy to make performant enough.&lt;/p&gt;
	&lt;p&gt;It would also be nice to follow this with a purely declarative solution.&lt;/p&gt;
	&lt;p&gt;I'm pleased that this went to Stage 1 in WHATWG this week and looking forward to figuring out how we move this forward.&lt;/p&gt;
	&lt;p&gt;Anyway, it's been really interesting and fun to dig into all of this and it's always eye-opening to look behind another curtain... I'm looking forward to continuing these conversations and ultimately getting something useful into all of the browsers!  Thanks again to Bloomberg Tech for the sponsorship!&lt;/p&gt;        </description>
	<pubDate>Mon, 04 May 2026 04:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #63</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-63/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-63/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from April 8 to April 28.&lt;/p&gt;
&lt;p&gt;
After a short hiatus, we return with a galore of releases, more Web Platform improvements,
tricky tweaks to thread scheduling, new niceties in the Web Inspector, and new build
options to take advantage of compiler optimizations.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Delivered a number of changes that have strengthened WPE WebKit and WebKitGTK's behaviour around real-time thread promotion and demotion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310907@main&quot;&gt;Real-time soft and hard limits are now set&lt;/a&gt; both when &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311660@main&quot;&gt;&lt;code&gt;sched_setscheduler&lt;/code&gt;&lt;/a&gt; or D-Bus based paths are taken (i.e. through &lt;a rel=&quot;external&quot; href=&quot;https://gitlab.freedesktop.org/pipewire/rtkit/&quot;&gt;rtkit&lt;/a&gt; or the corresponding XDG portal), with the soft limit set at 80% of the hard one.&lt;/li&gt;
&lt;li&gt;This means WebKit now has time to gracefully handle &lt;code&gt;SIGXCPU&lt;/code&gt;, and will do it in an &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310907@main&quot;&gt;async-signal-safe&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311082@main&quot;&gt;manner&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Additionally, the NetworkProcess' Cache Storage thread is now &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311064@main&quot;&gt;defined as &lt;code&gt;QOS::UserInitiated&lt;/code&gt; on Linux&lt;/a&gt;, which no longer maps to real-time priority. Its earlier mapping to real-time was previously reported as a NetworkProcess crash in the logs (it was in practice a kernel-delivered &lt;code&gt;SIGKILL&lt;/code&gt;, but WebKit doesn't make any distinction while logging). After limits were adjusted, this thread was successfully demoted, and now that the mapping has changed, this is no longer promoted to real-time to begin with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, logging around portal-related failures has been &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310444@main&quot;&gt;updated&lt;/a&gt; to reduce noise.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311850@main&quot;&gt;Implemented&lt;/a&gt; the &lt;code&gt;connectedMoveCallback()&lt;/code&gt; for custom elements to react to &lt;code&gt;moveBefore()&lt;/code&gt;.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311597@main&quot;&gt;Implemented&lt;/a&gt; the scaffolding for the &lt;code&gt;moveBefore()&lt;/code&gt; DOM function. This is the first step towards implementing the full feature and is currently behind a runtime feature flag.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The Web Inspector &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/311847@main&quot;&gt;now highlights&lt;/a&gt; the layout root element by hovering over a &lt;code&gt;Layout&lt;/code&gt; event in the &#8220;Layout &amp;amp; Rendering&#8221; timeline view and reveals it in the element tree by clicking a little &#8220;go to&#8221; arrow button.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/04/13/webkitgtk2.52.2-released.html&quot;&gt;WebKitGTK 2.52.2&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.2.html&quot;&gt;WPE WebKit 2.52.2&lt;/a&gt; have been released, which include a number of fixes. In particular, building for some less tested configurations should now be possible, and the WPE port includes fixes for input event handling in the Qt API bindings.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The releases were quickly followed by &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/04/16/webkitgtk2.52.3-released.html&quot;&gt;WebKitGTK 2.52.3&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.3.html&quot;&gt;WPE WebKit 2.52.3&lt;/a&gt;, with further fixes including an important patch for crashes in JavaScriptCore on architectures other than x86_64, support for the &lt;code&gt;scrollbar-color&lt;/code&gt; CSS property, and a fix for rendering certain emoji glyphs. Additionally, the WPE port also gained a new setting to disable overlay scroll bars and use always-visible ones, fixed focus handling for touch input in the built-in Wayland platform implementation, and a build fix for the Qt one.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;In addition to maintenance for the stable branch, the first unstable releases for the current development cycle are also available: &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/04/17/webkitgtk2.53.1-released.html&quot;&gt;WebKitGTK 2.53.1&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.53.1.html&quot;&gt;WPE WebKit 2.53.1&lt;/a&gt;. These are the first published versions that remove the option to use &lt;a rel=&quot;external&quot; href=&quot;https://cairographics.org/&quot;&gt;Cairo&lt;/a&gt; for 2D rendering&#8212;only &lt;a rel=&quot;external&quot; href=&quot;https://skia.org/&quot;&gt;Skia&lt;/a&gt; will be supported going forward. On the additions front, there are graphics subsystem improvements, a few API additions, and initial support in the CMake build system for builds using &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Profile-guided_optimization&quot;&gt;Profile-Guided Optimization&lt;/a&gt; (PGO, needs Clang for now). The goal of development releases is to gather early feedback on upcoming changes, and issue reports are welcome &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org&quot;&gt;in Bugzilla&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;infrastructure-construction-site&quot;&gt;Infrastructure &#127959;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;PGO (Profile-Guided Optimization) builds with Clang are &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310954@main&quot;&gt;now supported&lt;/a&gt; by the CMake build system.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Tue, 28 Apr 2026 08:05:47 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Eric Meyer: Canvas-ing the Web</title>
	<guid>https://meyerweb.com/eric/thoughts/?p=5735</guid>
	<link>https://meyerweb.com/eric/thoughts/2026/04/27/canvas-ing-the-web/</link>
	<description>
&lt;p&gt;Over the years, I&#8217;ve created an experiment or two that drew stuff to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element: a wave function collapse experiment here, a crystallizing palette there.&#160; After a while, I found a way to wire up a button so that clicking it would save the canvas&#8217;s contents to my computer as a PNG file.&#160; &lt;em&gt;Pretty cool&lt;/em&gt;, I thought.&#160; &lt;em&gt;Can I do the same thing with HTML+CSS structures?&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/wfv_2026-04-21T16_35_49.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/wfv_2026-04-21T16_35_49.png&quot; alt=&quot;An abstract image somewhat resembling a flower, rendered in dusky purples, greens, and similar colors.&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;
First I generated it on a canvas, then I clicked a button to save it.
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Turns out, no.&#160; I could use, and often have used, Firefox&#8217;s &#8220;Screenshot node&#8221; menu entry in the web inspector, or &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2018/08/24/firefoxs-screenshot-command-2018/&quot;&gt;the &lt;code&gt;:screenshot&lt;/code&gt; command&lt;/a&gt; in Firefox&#8217;s console, but not do it with an in-page button.&#160; Because HTML nodes don&#8217;t go in &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, you see, let alone styled and scripted ones.&lt;/p&gt;

&lt;p&gt;Or they &lt;em&gt;didn&#8217;t&lt;/em&gt;, until just recently, when Chrome shipped a flag-gated preview of the &lt;a href=&quot;https://github.com/WICG/html-in-canvas&quot;&gt;HTML-in-canvas API&lt;/a&gt;.&#160; How it works is, you add a &lt;code&gt;layoutsubtree&lt;/code&gt; attribute to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element, and then you can put whatever HTML you want in there, with whatever CSS and JS you would normally apply to it, add a couple of magic JScantations, and what the browser would normally have painted to the page is painted to the canvas, at whatever speed the browser can manage (usually 60 frames per second or more, because web browsers are high-end &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/&quot;&gt;first-person scrollers&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you want to try all this out for yourself, I commend you to &lt;a href=&quot;https://frontendmasters.com/blog/the-web-is-fun-again-first-experiments-with-html-in-canvas/&quot;&gt;Amit Sheen&#8217;s &#8220;The Web Is Fun Again&#8221;&lt;/a&gt; over at the Frontend Masters blog, where he details how to get yourself set up for the wackiness this makes possible, and then shows some experiments.&#160; Water ripples over your pages, lens distortions that follow the mouse pointer, chromatic aberrations!&lt;/p&gt;

&lt;p&gt;Which, I admit, all sound really off-putting to the &#8220;I just want to use the web&#8221; folks among us.&#160; What possible utility is there in having an input form that, say, makes ripples spread out from every character you type?&#160; Or having dropdown menus fall to the bottom of the page, but still actually work?&#160; Probably not a lot, unless you&#8217;re an expensive design studio working on a brag page.&lt;/p&gt;

&lt;p&gt;But remember, this is how any new graphic advancement goes: we, by which I mean the collective web industry, start by doing really outr&#233; and eye-catching stuff that we later have cause to regret.&#160; Remember parallax scrolling effects?&#160; The early days of CSS animation?&#160; &lt;em&gt;Drop shadows?&lt;/em&gt;&#160; There will be an initial period of excess, and then it will all settle down.&lt;/p&gt;

&lt;p&gt;I&#8217;ve already skipped straight to the settle down part, though.&lt;/p&gt;

&lt;p&gt;See, when I asked myself if I could render HTML+CSS on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; and then save the image to my computer, it wasn&#8217;t just me doing that &#8220;push at the limits of web features&#8221; thing &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2025/08/07/infinite-pixels/&quot;&gt;I do sometimes&lt;/a&gt;.&#160; I had an actual, practical use case in mind: I wanted to save social media banners and thumbnails from a browser-based tool I built for my work at &lt;a href=&quot;https://igalia.com&quot;&gt;Igalia&lt;/a&gt;, just by clicking or otherwise triggering a button.&lt;/p&gt;

&lt;p&gt;If you&#8217;re subscribed to our &lt;a href=&quot;https://www.youtube.com/igalia&quot;&gt;YouTube channel&lt;/a&gt;, you&#8217;ve seen these thumbnails; ditto if you&#8217;re following us on &lt;a href=&quot;https://floss.social/@igalia&quot;&gt;Mastodon&lt;/a&gt; or &lt;a href=&quot;https://bsky.app/profile/igalia.com&quot;&gt;Bluesky&lt;/a&gt;.&#160; To produce those, I have an in-browser thing I built out of custom elements.&#160; It&#8217;s where &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/&quot;&gt;the &lt;code&gt;super-slider&lt;/code&gt; pattern&lt;/a&gt; developed (though they have a different name in the tool).&#160; I&#8217;m not going to link to the tool because it&#8217;s on our intranet and very few of you have a login, so here&#8217;s a screenshot of it in all its dweeb-designed semi-glory.&lt;/p&gt;

&lt;figure class=&quot;standalone&quot;&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/bannermaker-screenshot.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/bannermaker-screenshot.png&quot; alt=&quot;The banner-making tool being discussed, showing a number of panels with range slider inputs to set things like font size for various pieces of the banner.&#160; There are also color inputs to change the coloration of both foreground and background elements, and a couple of places to drag and drop background or highlight images.&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;
The banner maker, with a recent thumbnail already loaded in.
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The text bits in the banner are all &lt;code&gt;contenteditable&lt;/code&gt; HTML elements, and the various themes are managed with various blocks of CSS.&#160; (And yeah, those range inputs are all &#8220;super sliders&#8221;.)&#160; The point of all this being, I built it so that anyone at work could use it to make
banners whenever they needed, without having to wait on me to do so.&lt;/p&gt;

&lt;p&gt;What I&#8217;ve always wanted, in order to make things easy for anyone who isn&#8217;t me, is a &#8220;click this button to save the banner as an image&#8221;&#160;feature.&#160; Anyone at Igalia could easily learn (if they didn&#8217;t already know) the web-inspector-or-console stuff I was using, of course, but it just felt so janky.&#160; A touch embarrassing, if I&#8217;m being honest.&lt;/p&gt;

&lt;p&gt;Well, now I have what I wanted.&#160; In any browser that supports HTML-in-canvas, there is a button labeled &#8220;Download banner image&#8221;.&#160; Right now, that&#8217;s recent Chrome with the proper developer flag enabled.&#160; For all other browsers, there&#8217;s no button, and you just use the same web inspector screenshot tricks we&#8217;ve always relied on.&lt;/p&gt;

&lt;p&gt;Making this happen wasn&#8217;t as easy as maybe that sounded, though.&#160; I hit a couple of snags along the way, one of which was quite frustrating.&#160; Those are what I actually brought you here to talk about.&lt;/p&gt;

&lt;p&gt;The first snag was that I had to get the thumbnail preview into a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element without blowing the call stack.&#160; To explain that, let me show you a rough skeleton of the tool&#8217;s markup.&lt;/p&gt;

&lt;pre class=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;section id=&quot;youtube_talks&quot;&amp;gt;
	&amp;lt;thumb-panel class=&quot;text&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;colors&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;highlightImage&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;backgroundImage&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;icons&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;scaler&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;loader&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-preview&amp;gt; &#8230; &amp;lt;/thumb-preview&amp;gt;
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can read, it&#8217;s basically all custom elements, each with their own &lt;code&gt;connectedCallback()&lt;/code&gt; function to do whatever scripting magic needs to be done when the browser first encounters them.&#160; To wrap that last element, the &lt;code&gt;&amp;lt;thumb-preview&amp;gt;&lt;/code&gt;, inside a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, I needed to create a new canvas element, shift the preview element into the new canvas, and then insert the preview-bearing canvas, ending up with this structure.&lt;/p&gt;

&lt;pre class=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;section id=&quot;youtube_talks&quot;&amp;gt;
	&amp;lt;thumb-panel class=&quot;text&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;colors&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;highlightImage&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;backgroundImage&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;icons&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;scaler&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
	&amp;lt;thumb-panel class=&quot;loader&quot;&amp;gt; &#8230; &amp;lt;/thumb-panel&amp;gt;
&lt;ins&gt;	&amp;lt;canvas layoutsubtree&amp;gt;&lt;/ins&gt;
		&amp;lt;thumb-preview&amp;gt; &#8230; &amp;lt;/thumb-preview&amp;gt;
&lt;ins&gt;	&amp;lt;/canvas&amp;gt;&lt;/ins&gt;
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Thus, when the &lt;code&gt;&amp;lt;thumb-preview&amp;gt;&lt;/code&gt; was loaded in, I had its &lt;code&gt;connectedCallback()&lt;/code&gt; run a check to see if HTML-in-canvas is supported.&#160; In situations where it is supported, I did what was needed to get to the above result.&lt;/p&gt;

&lt;p&gt;At which point, since the &lt;code&gt;&amp;lt;thumb-preview&amp;gt;&lt;/code&gt; is a custom element that was being placed into the DOM, it fired its &lt;code&gt;connectedCallback()&lt;/code&gt;, thus starting the process again, creating a canvas and inserting the &lt;code&gt;&amp;lt;thumb-preview&amp;gt;&lt;/code&gt; into the new canvas, which started the process again, &lt;a href=&quot;https://meyerweb.com/bkkt/recursion-w-innie-the-pooh.jpg&quot;&gt;recursing toward infinity&lt;/a&gt;.&#160; Within milliseconds, the call stack was exceeded.&lt;/p&gt;

&lt;p&gt;So&#8230; &lt;em&gt;that&lt;/em&gt; wasn&#8217;t going to work.&lt;/p&gt;

&lt;p&gt;I thought for a moment that I could avoid this by setting a flag variable to &lt;code&gt;true&lt;/code&gt; and then checking for its existence in order to skip the whole canvas-creation-preview-insertion part, but I couldn&#8217;t figure out how to make that actually work.&#160; Then I thought maybe I could sidestep the whole imbroglio using &lt;code&gt;connectedMoveCallback()&lt;/code&gt;, but this wasn&#8217;t a move, it was a (re-)creation.&lt;/p&gt;

&lt;p&gt;That callback was the route to fixing this problem, though.&#160; You see, there &lt;em&gt;is&lt;/em&gt; a way to move elements from one part of the DOM to another: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/moveBefore&quot;&gt;&lt;code&gt;Element.moveBefore()&lt;/code&gt;&lt;/a&gt;.&#160; There&#8217;s no &lt;code&gt;moveAfter()&lt;/code&gt; or &lt;code&gt;moveInto()&lt;/code&gt;, sadly, just &#8220;move this node to the spot right before some other node&#8221;.&lt;/p&gt;

&lt;p&gt;Here&#8217;s how I made use of that feature:&lt;/p&gt;

&lt;pre class=&quot;js&quot;&gt;&lt;code&gt;let canvas = document.createElement('canvas');
canvas.setAttribute('layoutsubtree','');
canvas.setAttribute('width','1280');
canvas.setAttribute('height','720');

this.closest('section').appendChild(canvas);

let beacon = document.createElement('span');
canvas.appendChild(beacon);
canvas.moveBefore(this,beacon);
beacon.remove();&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Yep.&#160; I created a canvas, stuck the canvas into the closest ancestor section, created a span, stuck the span into the canvas, moved the preview element to right before the span, and then deleted the span.&#160; (There may well be a better way to do this, one that my DuckDucking failed to turn up.&#160; If so, please comment below!)&lt;/p&gt;

&lt;p&gt;Oh, and here&#8217;s what gets executed when the preview is moved, instead of append-created:&lt;/p&gt;

&lt;pre class=&quot;js&quot;&gt;&lt;code&gt;connectedMoveCallback() {
	return;
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Heckuva way to run a railroad.&lt;/p&gt;

&lt;p&gt;At that point, I had the canvas where I wanted it and the preview where I wanted it, and the call stack remained un-blown.&#160; Huzzah!&#160; I then recited the magic JScantations to make the canvas actually render its subtree (see the &#8220;Web is Fun Again&#8221; article I linked earlier for details on this), and hey presto, DOM was being rendered into a canvas!&#160; Then, when I clicked the button, the canvas was rendered as a PNG and my browser downloaded that PNG!&#160; I had what I wanted!&lt;/p&gt;

&lt;p&gt;Almost.&lt;/p&gt;

&lt;p&gt;Because the second snag, you see, is that canvases have an explicit size.&#160; Are in effect &lt;em&gt;required&lt;/em&gt; to do so, because otherwise they default to zero pixels tall and wide.&#160; So if you want to see anything, you need to give them some dimensions.&#160; I did that, as the code before showed, making the canvas 1280&#215;720 (YouTube&#8217;s recommended thumbnail size) through &lt;code&gt;setAttribute()&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;The problem is, the default scale factor on the thumbnail preview is &lt;code&gt;0.75&lt;/code&gt;, which translates to 960&#215;540.&#160; Thus, when I clicked the image capture button, my browser downloaded a 1280&#215;720 image with the thumbnail in the top left, and transparency below and to its right.&lt;/p&gt;

&lt;figure class=&quot;standalone&quot;&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/banner-point75-acorn.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/banner-point75-acorn.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;
The previously-seen banner, which was rendered at 0.75 scale in an un-resized canvas, as shown in the macOS image editor &lt;a href=&quot;https://flyingmeat.com/acorn/&quot;&gt;Acorn&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&#8220;Just resize the canvas, ya dork!&#8221; you might say.&#160; I certainly did (say that, I mean).&#160; But if I set it to 960 wide and 540 tall, then when the scale was increased to &lt;code&gt;1&lt;/code&gt;, I got a 1280&#215;720 DOM node cropped to its top left 960&#215;540.&#160; I needed to dynamically resize the canvas element to have its size match the size of the thumb-preview.&lt;/p&gt;

&lt;p&gt;And this is where I ran headfirst into several brick walls, because orcing a canvas element to resize in all the situations you want it to, including when it&#8217;s spawned, is not nearly as easy as you&#8217;d think.&#160; It wasn&#8217;t for me, anyway.&#160; I bulled my way through to a solution, eventually, painfully, but I got there.&lt;/p&gt;

&lt;p&gt;(As I write this, I&#8217;m wondering if I should have also created a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, appended the canvas to &lt;em&gt;that&lt;/em&gt;, and then used CSS to change the div&#8217;s size while the canvas was set to have &lt;code&gt;100%&lt;/code&gt; height and width.&#160; Or maybe have the DOM subtree pinned to 1280&#215;720 and use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scale&quot;&gt;CSS &lt;code&gt;scale&lt;/code&gt;&lt;/a&gt; to change the canvas size visually.&#160; Or perhaps some kind of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver&quot;&gt;&lt;code&gt;resizeObserver&lt;/code&gt;&lt;/a&gt; shenanigans.&#160; Or probably just pass some parameters to the HTML-in-canvas &lt;a href=&quot;https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents&quot;&gt;&lt;code&gt;drawElementImage&lt;/code&gt;&lt;/a&gt; method.&#160; Hmmm.)&lt;/p&gt;

&lt;p&gt;Regardless of whether I overlooked a less frustrating way do what I wanted, this does still point to a fundamental tension in the HTML-in-canvas approach: sizing.&lt;/p&gt;

&lt;p&gt;Canvases do not, as a rule, grow or shrink to fit their contents.&#160; DOM elements, as a rule, very much do, unless you force them not to.&#160; HTML-in-canvas is taking a very fluid, flexible, mostly unbounded layout paradigm and rasterizing it, or at least some of it, into a very bounded window of a given size.&#160; Sixty times (or more) every second, the browser is taking a screenshot the size of the canvas&#8217;s content box and pasting said screenshot into that content box.&#160; You can do fun stuff to it along the way, with filters or shaders or canvas draw calls or whatever you can code up, so that each one of those screenshots gets jazzed up in some fashion, but at base, it&#8217;s still fundamentally screenshot, paste, screenshot, paste, over and over.&lt;/p&gt;

&lt;p&gt;For use cases like mine, this isn&#8217;t really a big problem.&#160; I am, in the end, trying to get a screenshot of a static part of the page.&#160; HTML-in-canvas is very good for that.&#160; It could &lt;em&gt;completely&lt;/em&gt; revolutionize the browser-based slideshow genre.&#160; The Reveal.js plugin landscape alone could be a sight to behold.&lt;/p&gt;

&lt;p&gt;But in the general cases&amp;#x202F;&#8212;&amp;#x2009;the kinds of things we mostly do most every day&amp;#x202F;&#8212;&amp;#x2009;I don&#8217;t think this is likely to catch on.&#160; We might develop some patterns to make it easier, some interesting hacks to overcome the mismatch, but I don&#8217;t think that will significantly move the needle.&#160; On the other hand, if canvases can be made as flexible and content-wrapping as a bog-standard &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, then I would expect to see a lot more usage.&lt;/p&gt;

&lt;p&gt;Although if &lt;em&gt;that&lt;/em&gt; can be done, then we wouldn&#8217;t really need to stay chained to HTML-in-canvas.&#160; Instead, we could define a syntax to mark standard HTML elements as more visually manipulable, via an HTML attribute or CSS property or DOM method or all three.&lt;/p&gt;

&lt;p&gt;We&#8217;ve gotten close to that before: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Properties_and_values_API/Houdini&quot;&gt;CSS Houdini&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/previous-versions/ms530752(v=vs.85)&quot;&gt;Microsoft&#8217;s original &lt;code&gt;filter&lt;/code&gt;&lt;/a&gt; property, to pick two examples.&#160; We could try again.&#160; Maybe the HTML-in-canvas period is how we figure out what that simpler syntax should look like, by figuring out what it should make possible, and what it should make easy.&lt;/p&gt;

&lt;p&gt;I&#8217;d be okay with that.&#160; How about you?&lt;/p&gt;

&lt;hr /&gt;
&lt;p class=&quot;note&quot;&gt;Many thanks to my colleagues &lt;a href=&quot;https://igalia.com/team/bkardell&quot;&gt;Brian Kardell&lt;/a&gt; and &lt;a href=&quot;https://igalia.com/team/schenney&quot;&gt;Stephen Chenney&lt;/a&gt; for their early review and feedback on this post.&lt;/p&gt;
	&lt;hr /&gt;&lt;p&gt;Have something to say to all that?  You can &lt;a href=&quot;https://meyerweb.com/eric/thoughts/2026/04/27/canvas-ing-the-web/#commentform&quot;&gt;add a comment to the post&lt;/a&gt;, or &lt;a href=&quot;mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Canvas-ing%20the%20Web%22&quot;&gt;email Eric directly&lt;/a&gt;.&lt;/p&gt;        </description>
	<pubDate>Mon, 27 Apr 2026 14:20:33 +0000</pubDate>
	<dc:creator>Eric Meyer</dc:creator>
</item>
<item>
	<title>Pablo Saavedra: Fixing WebKit Unified Source Build Failures</title>
	<guid>http://http503.gvatas.in/?p=2711</guid>
	<link></link>
	<description>
TL;DR: Some times, usually when you are not using the default build flags you can easily fall in the situation were you got fails to link the WebKit libs with undefined reference errors caused by missing #include directives in .cpp files. This post describes how I manage the situation to identify the missing headers in [&amp;#8230;]        </description>
	<pubDate>Tue, 21 Apr 2026 23:45:29 +0000</pubDate>
	<dc:creator>Pablo Saavedra</dc:creator>
</item>
<item>
	<title>Jasmine Tang: My first time with Nix</title>
	<guid>https://badumbatish.github.io/blog/first_time_with_nix</guid>
	<link>https://badumbatish.github.io/posts/first_time_with_nix</link>
	<description>
Jasmine tries out Nix        </description>
	<pubDate>Sat, 18 Apr 2026 04:00:00 +0000</pubDate>
</item>
<item>
	<title>Brian Kardell: Priority of Constituencies</title>
	<guid>https://bkardell.com/blog/PriorityOfConstituencies.html</guid>
	<link>https://bkardell.com/blog/PriorityOfConstituencies.html</link>
	<description>
&lt;h1 class=&quot;contextual-heading&quot;&gt;Priority of Constituencies&lt;/h1&gt;

	&lt;p class=&quot;segue&quot;&gt;I've been thinking a lot recently about the W3C's Priority of Constituencies...&lt;/p&gt;
	&lt;p&gt;You've probably heard it cited before, the Priority of Constituencies.  And what you've probably heard is &lt;/p&gt;
	&lt;blockquote&gt;
	&lt;p&gt;User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity.&lt;/p&gt;
	&lt;/blockquote&gt;
	&lt;p&gt;Everyone loves this principle. It's almost poetic right?  I often hear it cited as if it were part of a founding W3C document from 1995, so it might be surprising to learn that it wasn&#8217;t.&lt;/p&gt;
	&lt;p&gt;Back in 2004 there was a kind of a schism in the W3C that led to the creation of WHATWG and the effort to create &quot;HTML5&quot;.  In many ways it was a bit of a left turn from what was happening in the W3C at the time.  In 2007 there was a kind of admission that maybe the WHATWG was on to something, and a rechartering of HTML and, (inspired by a requirement suggested by David Baron), a group of people (Maciej Stachowiak, Anne van Kesteren, Marcos Caceres, Henri Sivonen and Ian Hickson) got together and drafted some &lt;a href=&quot;https://lists.w3.org/Archives/Public/public-html/2007Mar/0601.html&quot;&gt;Proposed Design Principles - March 2007&lt;/a&gt;... The &lt;a href=&quot;https://web.archive.org/web/20070503044841/http://esw.w3.org/topic/HTML/ProposedDesignPrinciples&quot;&gt;original text&lt;/a&gt; is available from the wayback machine.&lt;/p&gt;

	&lt;p&gt;However, it wasn't &quot;done&quot;.  People have had further thoughts on refinement over the years. For example, David Baron has some thoughts that he blogged about in 2015  &lt;a href=&quot;https://dbaron.org/log/20150318-priority-of-constituencies&quot;&gt;questioning what the nuances this principle originally left out&lt;/a&gt;.&lt;/p&gt;

	&lt;p&gt;The main statement was added to the W3C TAG's Design Principles in 2020 with minor tweaks and then later in 2020, Alice Boxhall (currently at Igalia, but at the time with Google) added some additional clarifications (incorporating David's thoughts) to be more or less &lt;a href=&quot;https://w3ctag.github.io/design-principles/#priority-of-constituencies&quot;&gt;what it reads today&lt;/a&gt;.&lt;/p&gt;
	&lt;p&gt;I've been thinking a lot about these additions because I think they're important, but somehow not talked about as much.  They're less &quot;poetic&quot;, but nevertheless actually critically pragmatic:&lt;/p&gt;
	&lt;blockquote&gt;
	&lt;p&gt;Like all principles, this isn&#8217;t absolute. Ease of authoring affects how content reaches users. User agents have to prioritize finite engineering resources, which affects how features reach authors. Specification writers also have finite resources, and theoretical concerns reflect underlying needs of all of these groups.&lt;/p&gt;
	&lt;/blockquote&gt;
	&lt;p&gt;I really appreciate the nuance these bits add because it really isn't just about some statement - it needs to be grounded in realities.  It really is about considering tradeoffs and looking for how to optimize the application of this principle.  At some level, for example, a decent feature that vendors agree they can deliver is of considerably more practical value to end users than a &quot;better&quot; one that we cannot.&lt;/p&gt;
	&lt;p&gt;The edits also add the most simple version, a one-liner: &lt;/p&gt;
	&lt;blockquote&gt;
	&lt;p&gt;If a trade-off needs to be made, always put user needs above all.&lt;/p&gt;
	&lt;/blockquote&gt;
	&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/w3c-vision/#user-first&quot;&gt;W3C Vision document also expresses something &lt;em&gt;similar&lt;/em&gt;&lt;/a&gt; &lt;/p&gt;
	&lt;blockquote&gt;
	&lt;p&gt;User-first: We prioritize the needs of users over other constituencies, including over those of W3C Members.&lt;/p&gt;
	&lt;/blockquote&gt;
	&lt;p&gt;But, again, it must be acknowledged that this is a &lt;em&gt;principle&lt;/em&gt; and not an absolute mechanism.  It bumps up against reality in several ways without additional nuance.  To take one example:  Users, and authors benefit from good MathML support.  I've argued before that the ability to share native mathematical text is &lt;em&gt;societally&lt;/em&gt; important.  You could say that its weight in a simplified Priority of Consituencies should be pretty high. But it's also pretty complex, and expensive and harder to appreciate.  In practice, browsers don't prioritize it.  In fact, they don't even belong to the Working Group.  Nearly all of the work on MathML has been the work of volunteers or outside sponsorships.  The recent work on MathML-Core has been successful largely because it has taken a more pragmatic approach wrestling with these sorts of optimizations: The MathML (or insert whatever feature you like) we &lt;em&gt;can&lt;/em&gt; get is considerably better than the one that we cannot.  &lt;/p&gt;
	&lt;p&gt;Lately I&#8217;ve been thinking the web&#8217;s constituencies are broader than the familiar list suggests. I&#8217;m not arguing we should rewrite the principle, but I do think there&#8217;s value in drawing a map of the parts of the web ecosystem, asking who else is affected, who else is missing, and how our mental models shape the choices we make. I feel like, if the Priority of Constituencies has taught us anything, it&#8217;s that the way we understand the players and frame the statement  can certainly have a positive influence on the way we approach it.&lt;/p&gt;        </description>
	<pubDate>Fri, 17 Apr 2026 04:00:00 +0000</pubDate>
</item>
<item>
	<title>Mart&#237;n Abente Lahaye: Modern Yocto Linux Best Practices</title>
	<guid>https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/</guid>
	<link>https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/</link>
	<description>
&lt;p&gt;If you&#8217;ve been working with the &lt;a href=&quot;https://www.yoctoproject.org/&quot;&gt;Yocto Project&lt;/a&gt; for a while, you already know it&#8217;s the de facto standard for building custom embedded Linux distributions. What you might not know is how much the tooling around it has improved.&lt;/p&gt;
&lt;p&gt;Many teams adopted Yocto years ago and have kept roughly the same workflows ever since, copying setup scripts between projects, letting each developer figure out how to clone layers, and building releases manually on dedicated machines. These were the common patterns at the time, but by now they are just unnecessary friction.&lt;/p&gt;
&lt;p&gt;The Yocto community and surrounding ecosystem have introduced tools and practices that significantly improve reproducibility, onboarding, and CI integration. But lack of comprehensive documentation for how to integrate these improvements has likely kept some people from adopting them. This post aims to cover the most important ones and close that gap.&lt;/p&gt;
&lt;h2 id=&quot;the-pain-points&quot; tabindex=&quot;-1&quot;&gt;The Pain Points &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Up until very recently, the Yocto documentation and tutorials encouraged developers to work with local files and gave little guidance on project structure. This nudged teams toward a set of common but costly habits: bootstrapping new projects by &lt;strong&gt;copying scripts&lt;/strong&gt; from existing ones, leaving each developer to &lt;strong&gt;figure out layer setup on their own&lt;/strong&gt;, and &lt;strong&gt;relying on specific machines&lt;/strong&gt; for production builds. The result was projects that were fragile to onboard, hard to reproduce, and difficult to scale.&lt;/p&gt;
&lt;h2 id=&quot;modern-approach&quot; tabindex=&quot;-1&quot;&gt;Modern Approach &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most mature and tested solution to the problem today is &lt;a href=&quot;https://kas.readthedocs.io/en/latest/&quot;&gt;kas&lt;/a&gt;. Kas is an open-source setup and automation tool to better manage Yocto layers. It simplifies the process of configuring a Yocto build environment into a single, declarative configuration that covers all of the issues mentioned before.&lt;/p&gt;
&lt;p&gt;Defining your layers, repositories, revisions, and build settings in one place makes it straightforward to spin up new projects and onboard new team members: &lt;strong&gt;a fresh clone and a single command is all it takes to get a working build environment&lt;/strong&gt;. Kas also provides container integration and CI/CD tooling out of the box, so the same environment that runs on a &lt;strong&gt;developer&#8217;s laptop runs identically in your CI pipeline&lt;/strong&gt;. And because everything is expressed in YAML, &lt;strong&gt;project definitions can be versioned, diffed, and collaborated on like any other source file&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here&#8217;s what a complete project definition looks like in practice:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# kas/derivative-image-base-raspberrypi5.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kas/include/layer/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;distro.yml&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kas/include/layer/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;raspberrypi.yml&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;local_conf_header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;30_meta-moonforge-raspberrypi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;    WKS_FILE = &quot;moonforge-image-base-raspberrypi.wks.in&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;20_meta-moonforge-distro&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;    OVERLAYFS_ETC_DEVICE = &quot;/dev/mmcblk0p3&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;meta-moonforge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/moonforgelinux/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge.git&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 628d710b7e076be1daa2376065ea12bb8eeded3a&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;distro&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; moonforge&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; raspberrypi5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example is enough to build a working image of &lt;a href=&quot;https://moonforgelinux.org/&quot;&gt;Moonforge Linux&lt;/a&gt; for the Raspberry Pi 5. If you are curious about Moonforge, check out this &lt;a href=&quot;https://moonforgelinux.org/docs/tutorials/derive/&quot;&gt;tutorial&lt;/a&gt; for how to create your own distribution.&lt;/p&gt;
&lt;p&gt;An official alternative to kas, called &lt;a href=&quot;https://docs.yoctoproject.org/bitbake/dev/bitbake-user-manual/bitbake-user-manual-environment-setup.html&quot;&gt;bitbake-setup&lt;/a&gt;, has recently been released by the maintainers of the Yocto project. Although still not as feature rich as kas, being part of BitBake makes bitbake-setup worth exploring and considering. A recent &lt;a href=&quot;https://sigma-star.at/blog/2025/10/the-evolving-landscape-of-yocto-project-setup-bitbake-setup-vs.-kas/&quot;&gt;comparison&lt;/a&gt; from Richard Weinberger clarifies the similarities and differences.&lt;/p&gt;
&lt;h2 id=&quot;containerized-environments&quot; tabindex=&quot;-1&quot;&gt;Containerized Environments &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using containerized build environments is now common practice across Desktop Operating Systems. Containers enable members of a team to use the same environment regardless of what they are running on their development machines.&lt;/p&gt;
&lt;p&gt;However, in the world of Yocto, many teams still follow the old practice of installing packages locally and then struggling to be able to reproduce the same conditions when building on a different machine. Nowadays, the best approach is to use a container that includes all the needed build dependencies at the desired version.&lt;/p&gt;
&lt;p&gt;Kas integrates naturally with containers by using &lt;code&gt;kas-container&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ kas-container build kas/derivative-image-base-raspberrypi5.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This containerized approach fits perfectly for CI workflows as well, so the conditions are &lt;em&gt;always&lt;/em&gt; the same regardless of the machine or the stage of development.&lt;/p&gt;
&lt;h2 id=&quot;ci-cd-integration-and-build-automation&quot; tabindex=&quot;-1&quot;&gt;CI/CD Integration and Build Automation &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The manual way of installing dependencies has meant that many teams have stayed away from CI/CD when using Yocto, building releases manually on developer machines. This tends to hold teams back, as errors are discovered too late and then problems might be hard to reproduce and solve.&lt;/p&gt;
&lt;p&gt;Running automated pipelines like GitHub Actions, GitLab CI, or even Jenkins with the same containers as those used locally by developers ensures consistency and reduces human error.&lt;/p&gt;
&lt;p&gt;Once these pipelines are enabled, remote computing resources and processes can be shared across multiple projects within the organization, and made available via reusable GitHub actions. As can be seen in this example:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# .github/workflows/main.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;hosted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v6&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; moonforgelinux/build&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action@v0.1.1&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;kas_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; kas/derivative&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;base&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;raspberrypi5.yml&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;dl_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /home/github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;runner/kas/cache/downloads&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;sstate_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /home/github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;runner/kas/cache/sstate&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cache&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;image_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.ref_name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;image_version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.run_id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; moonforgelinux/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action@v0.2.1&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;host_base&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.S3_HOST_BASE &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;access_key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.S3_ACCESS_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;secret_key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.S3_SECRET_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.S3_BUCKET &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build/tmp/deploy/images&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'builds/${{ github.run_id }}/'&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'*'&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'*.wic.bz2'&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token key atrule&quot;&gt;use_https&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example shows how Moonforge&#8217;s GitHub actions can be reused to build and publish OS images. These actions are available to all Moonforge derivative projects. Check this &lt;a href=&quot;https://moonforgelinux.org/docs/tutorials/ci/&quot;&gt;tutorial&lt;/a&gt; for how to reuse these actions with your own distribution.&lt;/p&gt;
&lt;h2 id=&quot;extending&quot; tabindex=&quot;-1&quot;&gt;Extending &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The OpenEmbedded build system allows you to isolate different types of customizations into multiple layers. Each layers can provide a specific solution that is reusable and extensible. In general, layers can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide recipes for new software.&lt;/li&gt;
&lt;li&gt;Modify recipes from existing layers.&lt;/li&gt;
&lt;li&gt;Provide new configuration files for machines and distributions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What layers can&#8217;t do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modify existing distribution or machine configurations.&lt;/li&gt;
&lt;li&gt;Manage dependencies on external repositories and layers.&lt;/li&gt;
&lt;li&gt;Set sensible defaults to the local configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these limitations can be overcome by a sensible combination of layers and kas fragments. Simply put, fragments are YAML files that can be reused by other kas files.&lt;/p&gt;
&lt;p&gt;Kas support for &lt;em&gt;include&lt;/em&gt; directives can help structure these fragments in reusable blocks. These fragments can also be pulled from remote repositories. With this, derivative projects can reuse existing combinations of layers and fragments to build their own distributions, reducing duplication, manual steps, increasing reproducibility and having a clear upstream and downstream separation.&lt;/p&gt;
&lt;p&gt;The next two examples illustrate this:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# kas/include/repo/meta-raspberrypi.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;meta-raspberrypi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//git.yoctoproject.org/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;raspberrypi&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5240b5c200e594b494a7f1a8f9d81e7c09bc8939&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scarthgap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example shows how external layers can be made available to other fragments, keeping them pinned to a specific upstream release to ensure reproducibility.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# kas/include/layer/meta-moonforge-raspberrypi.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; kas/include/repo/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lts&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;mixins.yml&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; kas/include/repo/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;raspberrypi.yml&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; kas/include/layer/meta&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;moonforge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;distro.yml&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;local_conf_header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;20_meta-moonforge-raspberrypi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;    ENABLE_UART = &quot;1&quot;&lt;br /&gt;    RPI_USE_U_BOOT = &quot;1&quot;&lt;br /&gt;    LICENSE_FLAGS_ACCEPTED += &quot;synaptics-killswitch&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;meta-moonforge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;layers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;meta-moonforge-raspberrypi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example shows how having separate fragments for each layer can address the above limitations, like managing dependencies on external layers, setting sensible defaults to the local configuration and more.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yocto remains the de facto standard for embedded Linux customization, but teams have to catch up with evolving workflows. Modern tooling and processes makes Yocto more reproducible, easier to use, maintain and scale.&lt;/p&gt;
&lt;p&gt;As a last recap, remember to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use kas for build configuration.&lt;/li&gt;
&lt;li&gt;Use containers for build environments.&lt;/li&gt;
&lt;li&gt;Automate builds in CI/CD.&lt;/li&gt;
&lt;li&gt;Keep layers modular and provide reusable fragments.&lt;/li&gt;
&lt;li&gt;Track everything in version control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And avoid:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manual setup scripts.&lt;/li&gt;
&lt;li&gt;Host-dependent builds.&lt;/li&gt;
&lt;li&gt;Unpinned dependencies.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Fri, 17 Apr 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Jos&#233; Dapena: Container Timing: moving to Origin Trial</title>
	<guid>https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/</guid>
	<link>https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/</link>
	<description>
        &lt;img class=&quot;face&quot; src=&quot;/images/dape.png&quot; width=&quot;74&quot; height=&quot;100&quot; alt=&quot;&quot; align=&quot;right&quot; style=&quot;float: right&quot; /&gt;
&lt;p&gt;It has been a busy few months for the Container Timing API. After &lt;a href=&quot;https://blogs.igalia.com/dape/2026/02/10/container-timing-measuring-web-components-performance/&quot;&gt;introducing the concept of measuring web components performance&lt;/a&gt; and &lt;a href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;detailing the native implementation in Blink&lt;/a&gt;, I have an update to share.&lt;/p&gt;
&lt;p&gt;The API is moving to the next phase: a Chromium Origin Trial. I will also be presenting a year of work at the upcoming &lt;a href=&quot;https://www.chromium.org/events/blinkon-21/&quot;&gt;BlinkOn 21&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-origin-trial&quot; tabindex=&quot;-1&quot;&gt;The Origin Trial &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &lt;a href=&quot;https://developer.chrome.com/origintrials/#/view_trial/3312960475884421121&quot;&gt;register for the trial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Until now, developers have had to manually enable the &lt;code&gt;ContainerTiming&lt;/code&gt; 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. &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/origin-trials&quot;&gt;More information on how to use origin trial tokens&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Please provide your feedback at the &lt;a href=&quot;https://github.com/WICG/container-timing/issues&quot;&gt;WICG Container Timing issue tracker&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the API design meet your needs?&lt;/li&gt;
&lt;li&gt;How could it be changed to be more useful?&lt;/li&gt;
&lt;li&gt;Any corner cases that are not working as expected?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;a-year-in-review-at-blinkon-21&quot; tabindex=&quot;-1&quot;&gt;A year in review at BlinkOn 21 &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next week, April 20th and 21st, the Chromium community will gather for BlinkOn 21. I&#8217;ll be giving a lightning talk summarizing the updates over the last year.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Origin Trial is a key step toward finalizing the specification. Real-world feedback from the trial itself and from &lt;a href=&quot;https://www.chromium.org/events/blinkon-21/&quot;&gt;BlinkOn 21&lt;/a&gt; discussions will feed into the standards working group discussions, and we expect the specification to evolve from there.&lt;/p&gt;
&lt;p&gt;If you build with Container Timing during the trial, please &lt;a href=&quot;https://github.com/WICG/container-timing/issues&quot;&gt;share what you find&lt;/a&gt;: what works, what does not, and what is missing. That input will shape the final API.&lt;/p&gt;
&lt;p&gt;You can also test locally by enabling the &lt;code&gt;ContainerTiming&lt;/code&gt; feature flag in Chromium, while you wait for your &lt;a href=&quot;https://developer.chrome.com/origintrials/#/view_trial/3312960475884421121&quot;&gt;Origin Trial registration&lt;/a&gt; to be approved.&lt;/p&gt;
&lt;h2 id=&quot;thanks&quot; tabindex=&quot;-1&quot;&gt;Thanks &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This work is part of the collaboration between &lt;a href=&quot;https://techatbloomberg.com&quot;&gt;Bloomberg&lt;/a&gt; and &lt;a href=&quot;https://www.igalia.com&quot;&gt;Igalia&lt;/a&gt;. Thanks!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.igalia.com&quot;&gt;
&lt;source media=&quot;(prefers-color-scheme: dark)&quot;&gt;
&lt;img src=&quot;https://blogs.igalia.com/dape/img/igalia_-_500px_-_RGB_-_Feb23-580x210.png&quot; alt=&quot;Igalia&quot; /&gt;
&lt;/source&gt;&lt;/a&gt; &lt;a href=&quot;https://techatbloomberg.com&quot;&gt;&lt;img src=&quot;https://blogs.igalia.com/dape/img/Bloomberg-logo-580x117.png&quot; alt=&quot;Bloomberg&quot; class=&quot;dark-invert&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/container-timing&quot;&gt;Container Timing explainer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/container-timing/&quot;&gt;Specification draft @ WICG&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/web-platform/origin-trials&quot;&gt;Get started with origin trials&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/dape/2026/02/10/container-timing-measuring-web-components-performance/&quot;&gt;Container Timing: measuring web components performance&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;The implementation of Container Timing: aggregating paints in Blink&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromium.org/events/blinkon-21/&quot;&gt;BlinkOn 21&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>St&#233;phane Cerveau: Introducing GstPrinceOfParser 0.4.3</title>
	<guid>https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/</guid>
	<link>https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/</link>
	<description>
&lt;h1 id=&quot;gstprinceofparser-an-all-in-one-tool-to-play-with-gstreamer-on-any-platform&quot; tabindex=&quot;-1&quot;&gt;GstPrinceOfParser: An All-in-One Tool to Play With GStreamer on Any Platform &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Introducing &lt;a href=&quot;https://github.com/dabrain34/gstpop&quot;&gt;gst-pop&lt;/a&gt;, the GStreamer Prince of Parser &#8212; a tool to make interaction with GStreamer easier, global, and remotely accessible.&lt;/p&gt;
&lt;h2 id=&quot;what-is-gstreamer&quot; tabindex=&quot;-1&quot;&gt;What is GStreamer? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gstreamer.freedesktop.org/&quot;&gt;GStreamer&lt;/a&gt; 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:&lt;/p&gt;
&lt;pre class=&quot;language-mermaid&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;    filesrc &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; mp3dec &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; audiosink&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more than 20 years, GStreamer has relied on its in-house toolbox to demonstrate the power
of its pipelines. As this toolbox is used in thousands of projects and serves as a reference
implementation, modifications and enhancements are deliberately kept minimal to maintain stability.
gst-pop was created to go beyond these limitations.&lt;/p&gt;
&lt;h2 id=&quot;a-unified-interface-for-gstreamer&quot; tabindex=&quot;-1&quot;&gt;A Unified Interface for GStreamer &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Accessible over the network, via CLI arguments, or through D-Bus, gst-pop aims to provide
a multi-pipeline-capable command-line tool.&lt;/p&gt;
&lt;p&gt;With a simple invocation of &lt;code&gt;gst-pop&lt;/code&gt; (or its alias &lt;code&gt;gst-popd&lt;/code&gt;), you can run a daemon that accepts
multiple pipelines simultaneously, accessible through D-Bus or WebSocket via the pipeline
ID. You&#8217;ll be able to control, query, and get information about each pipeline &#8212; all of that
over a remote network, secured with API key authentication and origin validation to prevent unauthorized access.&lt;/p&gt;
&lt;p&gt;As demonstrated in the &lt;a href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;blog post related to GstPipelineStudio&lt;/a&gt;, it will be possible to connect to a remote pipeline or launch new pipelines through the GStreamer GUI. If a GUI is not available
on the platform, it will soon be possible to use a web interface to control GStreamer, offering
everything GStreamer can provide and more, limited only by your imagination.&lt;/p&gt;
&lt;h2 id=&quot;remote-element-inspection&quot; tabindex=&quot;-1&quot;&gt;Remote Element Inspection &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;gst-pop (or its alias &lt;code&gt;gst-pop-inspect&lt;/code&gt;) 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.&lt;/p&gt;
&lt;h2 id=&quot;media-discovery&quot; tabindex=&quot;-1&quot;&gt;Media Discovery &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It can also provide information on a media file using GStreamer&#8217;s discovery interface using &lt;code&gt;gst-pop-discovery&lt;/code&gt;, offering an easy and remote-capable media discovery system for your setup.&lt;/p&gt;
&lt;h2 id=&quot;playback&quot; tabindex=&quot;-1&quot;&gt;Playback &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;And of course, it can serve as an alternative to the &lt;code&gt;gst-play&lt;/code&gt; tool, with &lt;code&gt;gst-pop-play&lt;/code&gt;, allowing you to instantiate as many playback sessions as you need, with the ability to use any sink you want.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://gps.mooday.dev&quot;&gt;GstPipelineStudio&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;cross-platform-and-language-support&quot; tabindex=&quot;-1&quot;&gt;Cross-Platform and Language Support &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &lt;a href=&quot;https://github.com/dabrain34/gstpop/releases/tag/v0.4.3&quot;&gt;release page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;examples&quot; tabindex=&quot;-1&quot;&gt;Examples &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Start the daemon&lt;/span&gt;&lt;br /&gt;gst-pop&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Launch a pipeline&lt;/span&gt;&lt;br /&gt;gst-pop launch videotestsrc &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; autovideosink&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Inspect an element&lt;/span&gt;&lt;br /&gt;gst-pop inspect videotestsrc&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Discover media info&lt;/span&gt;&lt;br /&gt;gst-pop discover file:///path/to/video.mp4&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Play a media file&lt;/span&gt;&lt;br /&gt;gst-pop play file:///path/to/video.mp4&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Create a pipeline with the client&lt;/span&gt;&lt;br /&gt;gst-popctl create &lt;span class=&quot;token string&quot;&gt;&quot;videotestsrc ! autovideosink&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# List pipelines on a remote daemon&lt;/span&gt;&lt;br /&gt;gst-popctl list&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Play the pipeline with ID 0&lt;/span&gt;&lt;br /&gt;gst-popctl play &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Stop the pipeline with ID 0&lt;/span&gt;&lt;br /&gt;gst-popctl stop &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Run via Docker&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;:9000 ghcr.io/dabrain34/gstpop:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Give it a try and let us know what ideas you might have &#8212; we have plenty coming, so stay tuned.&lt;/p&gt;
&lt;p&gt;As usual, if you would like to learn more about gst-pop, GStreamer, or any other open multimedia framework, please contact &lt;a href=&quot;https://www.igalia.com/&quot;&gt;us&lt;/a&gt;!&lt;/p&gt;        </description>
	<pubDate>Wed, 08 Apr 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #62</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-62/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-62/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from March 31 to April 7.&lt;/p&gt;
&lt;p&gt;
Support for iOS dialog light dismiss, a new API to obtain page icons,
WebKit nightly builds for Epiphany Canary produced by
GNOME GitLab, and more conservative checks for MPEG-4 Audio object types
are all part of this week's edition of the WebKit periodical.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310308@main&quot;&gt;A new API to obtain page icons&lt;/a&gt; (a.k.a. &#8220;favicons&#8221;) has been added to the GTK port. The new functionality reuses the recently added &lt;code&gt;WebKitImage&lt;/code&gt; class and provides access to multiple page icons at once through the added &lt;code&gt;WebKitImageList&lt;/code&gt; type, allowing applications to better choose an icon that suits their needs. Changes to the &lt;code&gt;WebKitWebView.page-icons&lt;/code&gt; 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 &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310697@main&quot;&gt;enabled for the WPE port&lt;/a&gt;, and the plan is to deprecate the old page favicon functionality going forward.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310478@main&quot;&gt;Added iOS support for dialog light dismiss&lt;/a&gt;, part of the experimental &lt;code&gt;closedby&lt;/code&gt; attribute implementation.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310634@main&quot;&gt;canPlayType() is now more conservative regarding MPEG-4 Audio object types&lt;/a&gt;. 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 &lt;code&gt;mp4a&lt;/code&gt;. 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 &#8212; for example, as of writing, FFmpeg support for xHE-AAC is only very recent and still incomplete.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310381@main&quot;&gt;canPlayType() now also reports support for Dolby AC-4&lt;/a&gt; in systems with a decoder capable of handling it.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The GStreamer WebRTC backend &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310637@main&quot;&gt;now&lt;/a&gt; rejects SDP including &lt;code&gt;rtpmap&lt;/code&gt; attributes in the disallowed range of 64-95 payload types. Compliance with RFC 7587 was also improved.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;infrastructure-construction-site&quot;&gt;Infrastructure &#127959;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The WebKitGTK nightly builds for Epiphany Canary are now handled entirely by the &lt;a rel=&quot;external&quot; href=&quot;https://gitlab.gnome.org&quot;&gt;GNOME GitLab&lt;/a&gt; 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.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Tue, 07 Apr 2026 17:07:37 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>St&#233;phane Cerveau: Introducing GstPipelineStudio 0.5.1</title>
	<guid>https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/</guid>
	<link>https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/</link>
	<description>
&lt;h1 id=&quot;gstpipelinestudio-0-5-1&quot; tabindex=&quot;-1&quot;&gt;GstPipelineStudio 0.5.1 &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;your-gstreamer-pipelines-at-a-glance&quot; tabindex=&quot;-1&quot;&gt;Your GStreamer Pipelines, at a Glance &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;New version of GstPipelineStudio is out!&lt;/p&gt;
&lt;p&gt;After months of improvements and intermediate releases since October 2024, it&#8217;s time for an official announcement for &lt;a href=&quot;https://gps.mooday.dev&quot;&gt;0.5.1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;GstPipelineStudio provides a visual interface to GStreamer, the marvelous Swiss Army knife of multimedia pipelines. But what is GStreamer exactly?&lt;/p&gt;
&lt;h2 id=&quot;what-is-gstreamer&quot; tabindex=&quot;-1&quot;&gt;What is GStreamer? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gstreamer.freedesktop.org/&quot;&gt;GStreamer&lt;/a&gt; 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:&lt;/p&gt;
&lt;pre class=&quot;language-mermaid&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;    filesrc &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; mp3dec &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; audiosink&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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&#8217;s where GstPipelineStudio comes in &#8212; providing a visual interface to help newcomers discover and adopt GStreamer, and skilled developers debug their pipelines.&lt;/p&gt;
&lt;h2 id=&quot;the-story-behind-gstpipelinestudio&quot; tabindex=&quot;-1&quot;&gt;The Story Behind GstPipelineStudio &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &#8212; Linux, macOS, Windows, iOS, Android, and even low-power devices such as a Raspberry Pi.&lt;/p&gt;
&lt;p&gt;GstPipelineStudio aims to work on all these platforms, easing GStreamer adoption where its use was not always obvious, such as on Windows.
GStreamer 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.
There was an attempt to create a GUI using Qt, named &lt;a href=&quot;https://github.com/virinext/pipeviz&quot;&gt;pipeviz&lt;/a&gt;, which has been a great inspiration for GPS, but the Qt Rust bindings were not mature enough, unlike those for GTK.&lt;/p&gt;
&lt;p&gt;The first official release of GPS was 0.3.4, and you can read its &lt;a href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-3-4/&quot;&gt;official blog post&lt;/a&gt; published in 2023. Since then, we have been devoted to providing new features
to bring GPS to another level.&lt;/p&gt;
&lt;p&gt;A first revision, GPS 0.4.0, came out before Christmas 2024 with a refreshed interface &#8212; including zoom on the graph
and 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.
A new icon has also been introduced to let GPS dive into another dimension.&lt;/p&gt;
&lt;h2 id=&quot;what-s-new-in-0-5-1&quot; tabindex=&quot;-1&quot;&gt;What&#8217;s New in &lt;a href=&quot;https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/releases&quot;&gt;0.5.1&lt;/a&gt; &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;0.5.1 is here, and it brings a game changer: the dot file reader.
Previously, 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
high-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.&lt;/p&gt;
&lt;p&gt;Here is the list of other improvements you&#8217;ll find in this release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open Dot Folder menu entry for loading dot files from the common GStreamer folder&lt;/li&gt;
&lt;li&gt;Remote pipeline introspection using the GStreamer tracers&lt;/li&gt;
&lt;li&gt;App ID renamed to dev.mooday.GstPipelineStudio&lt;/li&gt;
&lt;li&gt;Improved look and feel of the interface&lt;/li&gt;
&lt;li&gt;Auto-connect on node click (node-link-request)&lt;/li&gt;
&lt;li&gt;File selector button for location property&lt;/li&gt;
&lt;li&gt;Logger copy to clipboard with multi-selection support&lt;/li&gt;
&lt;li&gt;Auto-arrange elements on screen&lt;/li&gt;
&lt;li&gt;GStreamer 1.28.0&lt;/li&gt;
&lt;li&gt;GTK 4.20&lt;/li&gt;
&lt;li&gt;RPM and AppImage artifacts&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;remote-pipeline-introspection&quot; tabindex=&quot;-1&quot;&gt;Remote Pipeline Introspection &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The remote pipeline introspection is a new way to connect to the WebSocket tracer available in GStreamer, pipeline-snapshot.
In addition to dot file loader, it allows you to visualize a pipeline directly in GPS from an external process running with the tracer.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;gst-dots-viewer&lt;/code&gt; which creates a web server to display pipelines in a browser from the &lt;code&gt;$XDG_CACHE_DIR&lt;/code&gt; folder, see the &lt;a href=&quot;https://blogs.gnome.org/tsaunier/2025/05/16/gst-dots-viewer-a-new-tool-for-gstreamer-pipeline-visualization/&quot;&gt;blog post&lt;/a&gt; from Thibault about it.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;For example, to visualize a running pipeline in GPS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In GPS: &lt;strong&gt;Menu &#8594; Remote Pipeline &#8594; Listen&#8230;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter the WebSocket address (e.g., &lt;code&gt;ws://localhost:8080&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Run your GStreamer pipeline with the &lt;code&gt;pipeline-snapshot&lt;/code&gt; tracer:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;GST_TRACERS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pipeline-snapshot(dots-viewer-ws-url=ws://localhost:8080)&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  gst-launch-1.0 videotestsrc &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; autovideosink&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pipeline graph will appear in GPS once the tracer connects.&lt;/p&gt;
&lt;p&gt;These dot files are converted to GPS pipelines, making it possible to modify them. That&#8217;s a first step for real interaction with GStreamer pipelines &#8212; and there are more features coming in the &lt;em&gt;pipeline&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;coming-in-0-6-0&quot; tabindex=&quot;-1&quot;&gt;Coming in 0.6.0 &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In parallel, a new tool named &lt;a href=&quot;https://github.com/dabrain34/gstpop&quot;&gt;GstPrinceOfParser&lt;/a&gt; (gst-pop) has also been implemented. This tool allows remote control of all pipelines instantiated locally or over the network.
It is a multi-pipeline daemon accessible through WebSocket or D-Bus, aiming to centralize all GStreamer options in one tool for &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/tutorials/basic/gstreamer-tools.html#gstlaunch10&quot;&gt;launch&lt;/a&gt;, &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/tutorials/basic/gstreamer-tools.html#gstinspect10&quot;&gt;inspection&lt;/a&gt;, and &lt;a href=&quot;https://gstreamer.freedesktop.org/documentation/tutorials/basic/gstreamer-tools.html#gstdiscoverer10&quot;&gt;discovery&lt;/a&gt;. 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&#8230;&lt;/p&gt;
&lt;p&gt;A new tracer is under development: a WebSocket server that will allow you to inspect and interact with the current pipeline &#8212; modify the play state (pause, seek), fetch the logs, and of course see the current dot representation, all from the GstPipelineStudio interface.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;We hope you&#8217;ll enjoy this new version of the tool and please feel free to propose
new features with an RFC &lt;a href=&quot;https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/issues/new&quot;&gt;here&lt;/a&gt; or merge requests &lt;a href=&quot;https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/-/merge_requests&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Stay tuned for the next &lt;a href=&quot;https://discourse.gstreamer.org/t/gstreamer-spring-hackfest-2026-on-29-31-may-2026-in-nice-france/5762&quot;&gt;GStreamer Spring hackfest 2026&lt;/a&gt; coming soon (end of May) where new features and deeper interaction with GStreamer pipelines will be discussed.&lt;/p&gt;
&lt;p&gt;As usual, if you would like to learn more about GstPipelineStudio, GStreamer, or any other open multimedia framework, please contact &lt;a href=&quot;https://www.igalia.com/&quot;&gt;us&lt;/a&gt;!&lt;/p&gt;        </description>
	<pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Miyoung Shin: Extension Migration Progress Update &#8211; Part 1</title>
	<guid>https://blogs.igalia.com/mshin/?p=161</guid>
	<link>https://blogs.igalia.com/mshin/2026/04/02/extension-migration-progress-update-part-1/?pk_campaign=feed&amp;pk_kwd=extension-migration-progress-update-part-1</link>
	<description>
&lt;h1 class=&quot;wp-block-heading&quot;&gt;Background&lt;/h1&gt;



&lt;p&gt;Following up on my previous post, I would like to share an update on the progress of the &lt;strong&gt;Extension migration work&lt;/strong&gt; that has been underway over the past few months.&lt;/p&gt;



&lt;p&gt;To briefly recap the motivation behind this effort: Igalia&amp;#8217;s long-term goal is to enable &lt;strong&gt;embedders&lt;/strong&gt; to use the Extension system &lt;strong&gt;without depending on the &lt;code&gt;//chrome&lt;/code&gt; layer&lt;/strong&gt;. In other words, we want to make it possible to support Extension functionality with minimal implementation effort using only &lt;code&gt;//content + //extensions&lt;/code&gt;.&lt;/p&gt;



&lt;p&gt;Currently, some parts of the Extension system still rely on the &lt;code&gt;//chrome&lt;/code&gt; layer. Our objective is to remove those dependencies so that embedders can integrate Extension capabilities without needing to include the entire &lt;code&gt;//chrome&lt;/code&gt; layer.&lt;/p&gt;



&lt;p&gt;As a &lt;strong&gt;short-term milestone&lt;/strong&gt;, we focused on migrating the &lt;strong&gt;Extension installation implementation&lt;/strong&gt; from &lt;code&gt;//chrome&lt;/code&gt; to &lt;code&gt;//extensions&lt;/code&gt;. This phase of the work has now been completed, which is why I&#8217;m sharing this progress update.&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h1 class=&quot;wp-block-heading&quot;&gt;Extension Installation Formats&lt;/h1&gt;



&lt;p&gt;Chromium supports several formats for installing Extensions. The most common ones are &lt;strong&gt;zip&lt;/strong&gt;, &lt;strong&gt;unpacked&lt;/strong&gt; and &lt;strong&gt;crx.&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;Each format serves a different purpose:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;zip&lt;/strong&gt; &#8211; commonly used for internal distribution or packaged deployment&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;unpacked&lt;/strong&gt; &#8211; primarily used during development and debugging&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;crx&lt;/strong&gt; &#8211; the standard packaged format used by the Chrome Web Store&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;During this migration effort, the code responsible for supporting all three installation formats has been successfully moved to the &lt;code&gt;//extensions&lt;/code&gt; layer.&lt;/p&gt;



&lt;p&gt;As a result, the Extension installation pipeline is now significantly less dependent on the &lt;code&gt;//chrome&lt;/code&gt; layer, bringing us closer to enabling Extension support directly on top of &lt;code&gt;//content + //extensions&lt;/code&gt;.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Patch and References&lt;/h2&gt;



&lt;p&gt;To support this migration, several patches were introduced to move installation-related components into the &lt;code&gt;//extensions&lt;/code&gt; layer and decouple them from &lt;code&gt;//chrome&lt;/code&gt;.&lt;/p&gt;



&lt;p&gt;For readers who are interested in the implementation details, you can find the related changes and discussions here:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Chromium issue tracking this work:&lt;br /&gt;&lt;a href=&quot;https://issues.chromium.org/issues/358567092&quot;&gt;https://issues.chromium.org/issues/358567092&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;Related code reviews and patches:&lt;br /&gt;&lt;a href=&quot;https://chromium-review.googlesource.com/q/myid.shin@igalia.com&quot;&gt;https://chromium-review.googlesource.com/q/myid.shin@igalia.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;These links provide more insight into the design decisions, code changes, and ongoing discussions around the migration.&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h1 class=&quot;wp-block-heading&quot;&gt;Demo&lt;/h1&gt;



&lt;p&gt;Below is a short demo showing the current setup in action.&lt;/p&gt;



&lt;figure class=&quot;wp-block-video&quot;&gt;&lt;video controls=&quot;controls&quot; src=&quot;https://blogs.igalia.com/mshin/files/2026/03/demo-extension_installation.mp4&quot;&gt;&lt;/video&gt;&lt;/figure&gt;



&lt;p&gt;This demo was recorded using&#160;&lt;code&gt;app_shell&lt;/code&gt; on Linux, the minimal stripped-down browser container designed to run Chrome Apps and using only&#160;&lt;code&gt;//content&lt;/code&gt;&#160;and&#160;&lt;code&gt;//extensions&lt;/code&gt;/ layers.&lt;/p&gt;



&lt;p&gt;To have this executable launcher, we also extended &lt;strong&gt;&lt;code&gt;app_shell&lt;/code&gt; &lt;/strong&gt;with the minimal functionality required for embedders to install the extension app.&lt;/p&gt;



&lt;p&gt;This allows Extensions to be installed and executed &lt;strong&gt;without relying on the full Chrome browser implementation&lt;/strong&gt;, making it easier to experiment with and validate the migration work.&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h1 class=&quot;wp-block-heading&quot;&gt;Next Steps&lt;/h1&gt;



&lt;p&gt;The next short-term goal is to &lt;strong&gt;migrate the code required for installing Extensions via the Chrome Web Store&lt;/strong&gt; into the &lt;code&gt;//extensions&lt;/code&gt; layer as well.&lt;/p&gt;



&lt;p&gt;At the moment, parts of the Web Store installation flow still depend on the &lt;code&gt;//chrome&lt;/code&gt; layer. The next phase of this project will focus on removing those dependencies so that Web Store-based installation can also function within the &lt;code&gt;//extensions&lt;/code&gt; layer.&lt;/p&gt;



&lt;p&gt;Once this work is completed, embedders will be able to install Extension apps from Chrome WebStore with a significantly simpler architecture (&lt;code&gt;//content + //extensions&lt;/code&gt;). &lt;/p&gt;



&lt;p&gt;This will make the Extension platform &lt;strong&gt;more modular, reusable, and easier to integrate into custom Chromium-based products&lt;/strong&gt;.&lt;/p&gt;



&lt;p&gt;I will continue to share updates as the migration progresses.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;
&lt;img src=&quot;https://stats.igalia.com/piwik.php?idsite=19&amp;rec=1&amp;url=https%3A%2F%2Fblogs.igalia.com%2Fmshin%2F2026%2F04%2F02%2Fextension-migration-progress-update-part-1%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dextension-migration-progress-update-part-1&amp;action_name=Extension%20Migration%20Progress%20Update%20%26%238211%3B%20Part%201&amp;urlref=https%3A%2F%2Fblogs.igalia.com%2Fmshin%2Ffeed%2F&quot; width=&quot;0&quot; height=&quot;0&quot; alt=&quot;&quot; /&gt;        </description>
	<pubDate>Thu, 02 Apr 2026 11:32:34 +0000</pubDate>
	<dc:creator>mshin</dc:creator>
</item>
<item>
	<title>Qiuyi Zhang (Joyee): Tinkering with Node.js Core on ARM64 Windows</title>
	<guid>https://joyeecheung.github.io/blog/2026/01/31/tinkering-with-nodejs-core-on-arm64-windows/</guid>
	<link>https://joyeecheung.github.io/blog/2026/01/31/tinkering-with-nodejs-core-on-arm64-windows/</link>
	<description>
&lt;p&gt;A while back, I wrote about &lt;a href=&quot;https://joyeecheung.github.io/blog/2025/02/16/building-nodejs-on-windows-with-clang-cl/&quot; title=&quot;Building Node.js on Windows using the new ClangCL support&quot;&gt;Building Node.js on Windows using the new ClangCL support&lt;/a&gt;, which was done on an actual x64 Windows machine&lt;/p&gt;        </description>
	<pubDate>Wed, 01 Apr 2026 02:16:02 +0000</pubDate>
</item>
<item>
	<title>Andy Wingo: wastrelly wabbits</title>
	<guid>https://wingolog.org/2026/03/31/wastrelly-wabbits</guid>
	<link>https://wingolog.org/archives/2026/03/31/wastrelly-wabbits</link>
	<description>
&lt;div&gt;&lt;p&gt;Good day!  Today (tonight), some notes on the last couple months of
&lt;a href=&quot;https://codeberg.org/andywingo/wastrel/&quot;&gt;Wastrel&lt;/a&gt;, my ahead-of-time
WebAssembly compiler.&lt;/p&gt;&lt;p&gt;Back in the beginning of February, I showed &lt;a href=&quot;https://wingolog.org/archives/2026/02/06/ahead-of-time-wasm-gc-in-wastrel&quot;&gt;Wastrel running programs
that use garbage
collection&lt;/a&gt;,
using an embedded copy of the &lt;a href=&quot;https://github.com/wingo/whippet&quot;&gt;Whippet
collector&lt;/a&gt;, specialized to the types
present in the Wasm program.  But, the two synthetic GC-using programs I
tested on were just ported microbenchmarks, and didn&#8217;t reflect the
output of any real toolchain.&lt;/p&gt;&lt;p&gt;In this cycle I worked on compiling the output from the &lt;a href=&quot;https://spritely.institute/hoot/&quot;&gt;Hoot
Scheme-to-Wasm compiler&lt;/a&gt;.  There were
some interesting challenges!&lt;/p&gt;&lt;h3&gt;bignums&lt;/h3&gt;&lt;p&gt;When I originally wrote the Hoot compiler, it targetted the browser,
which already has a bignum implementation in the form of
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility&quot;&gt;BigInt&lt;/a&gt;,
which &lt;a href=&quot;https://wingolog.org/archives/2019/05/23/bigint-shipping-in-firefox&quot;&gt;I worked on back in the
day&lt;/a&gt;.
Hoot-generated Wasm files use host bigints via &lt;tt&gt;externref&lt;/tt&gt; (though
&lt;a href=&quot;https://wingolog.org/archives/2023/03/20/a-world-to-win-webassembly-for-the-rest-of-us#:~:text=common%20struct%20supertype&quot;&gt;wrapped in structs to allow for hashing and
identity&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;In Wastrel, then, I implemented the imports that implement bignum
operations: addition, multiplication, and so on.  I did so using
&lt;a href=&quot;https://gmplib.org/repo/gmp/file/tip/mini-gmp/README&quot;&gt;mini-gmp&lt;/a&gt;, a
stripped-down implementation of the workhorse GNU multi-precision
library.  At some point if bignums become important, this gives me the
option to link to the full GMP instead.&lt;/p&gt;&lt;p&gt;Bignums were the first managed data type in Wastrel that wasn&#8217;t defined
as part of the Wasm module itself, instead hiding behind &lt;tt&gt;externref&lt;/tt&gt;, so
I had to add a facility to &lt;a href=&quot;https://wingolog.org/archives/2026/02/18/two-mechanisms-for-dynamic-type-checks&quot;&gt;allocate type
codes&lt;/a&gt;
to these &#8220;host&#8221; data types.  More types will come in time: weak maps,
ephemerons, and so on.&lt;/p&gt;&lt;p&gt;I think bignums would be a great proposal for the Wasm standard, similar
to &lt;a href=&quot;https://github.com/WebAssembly/stringref&quot;&gt;stringref&lt;/a&gt; ideally
(&lt;a href=&quot;https://wingolog.org/archives/2023/10/19/requiem-for-a-stringref&quot;&gt;sniff!&lt;/a&gt;),
possibly in an &lt;a href=&quot;https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md&quot;&gt;attenuated
form&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;exception handling&lt;/h3&gt;&lt;p&gt;Hoot used to emit a &lt;a href=&quot;https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md&quot;&gt;pre-standardization form of exception
handling&lt;/a&gt;,
and hadn&#8217;t gotten around to updating to the &lt;a href=&quot;https://wingolog.org/archives/2026/03/10/nominal-types-in-webassembly&quot;&gt;newer
version&lt;/a&gt;
that was standardized last July.  I updated Hoot to emit the newer kind
of exceptions, as it was easier to implement them in Wastrel that way.&lt;/p&gt;&lt;p&gt;Some of the problems &lt;a href=&quot;https://cfallin.org/blog/2025/11/06/exceptions/&quot;&gt;Chris Fallin contended with in
Wasmtime&lt;/a&gt; don&#8217;t apply
in the Wastrel case: since the set of instances is known at
compile-time, we can statically allocate type codes for exception tags.
Also, I didn&#8217;t really have to do the back-end: I can just use &lt;a href=&quot;https://man7.org/linux/man-pages/man3/longjmp.3.html&quot;&gt;&lt;tt&gt;setjmp&lt;/tt&gt;
and &lt;tt&gt;longjmp&lt;/tt&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This whole paragraph was meant to be a bit of an aside in which I
briefly mentioned why just using &lt;tt&gt;setjmp&lt;/tt&gt; was fine.  Indeed, because
Wastrel never re-uses a temporary, relying entirely on GCC to &#8220;re-use&#8221;
the register / stack slot on our behalf, I had thought that I didn&#8217;t
need to worry about the &#8220;volatile problem&#8221;.  From the C99 specification:&lt;/p&gt;&lt;blockquote&gt; [...] values of objects of automatic storage duration that
are local to the function containing the invocation of the corresponding
setjmp macro that do not have volatile-qualified type and have been
changed between the setjmp invocation and longjmp call are
indeterminate.  &lt;/blockquote&gt;&lt;p&gt;My thought was, though I might set a value between &lt;tt&gt;setjmp&lt;/tt&gt; and
&lt;tt&gt;longjmp&lt;/tt&gt;, that would only be the case for values whose lifetime did
not reach the &lt;tt&gt;longjmp&lt;/tt&gt; (i.e., whose last possible use was before the
jump).  Wastrel didn&#8217;t introduce any such cases, so I was good.&lt;/p&gt;&lt;p&gt;However, I forgot about &lt;tt&gt;local.set&lt;/tt&gt;: mutations of locals (ahem, &lt;i&gt;objects
of automatic storage duration&lt;/i&gt;) in the source Wasm file could run afoul
of this rule.  So, because of writing this blog post, I went back and
did an analysis pass on each function to determine the set of locals
which are mutated inside the body of a &lt;tt&gt;try_table&lt;/tt&gt;.  Thank you, rubber duck readers!&lt;/p&gt;&lt;h3&gt;bugs&lt;/h3&gt;&lt;p&gt;Oh my goodness there were many bugs.  Lacunae, if we are being generous;
things not implemented quite right, which resulted in errors either when
generating C or when compiling the C.  The &lt;a href=&quot;https://wingolog.org/archives/2026/02/09/six-thoughts-on-generating-c&quot;&gt;type-preserving translation
strategy&lt;/a&gt;
does seem to have borne fruit, in that I have spent very little time in
GDB: once things compile, they work.&lt;/p&gt;&lt;h3&gt;coevolution&lt;/h3&gt;&lt;p&gt;Sometimes Hoot would use a browser facility where it was convenient, but
for which in a better world we would just do our own thing.  Such was the
case for the &lt;tt&gt;number-&amp;gt;string&lt;/tt&gt; operation on floating-point numbers: we
did something &lt;a href=&quot;https://codeberg.org/spritely/hoot/src/branch/main/reflect-js/reflect.js#L817-L828&quot;&gt;awful but
expedient&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I didn&#8217;t have this facility in Wastrel, so instead we moved to do
float-to-string conversions &lt;a href=&quot;https://codeberg.org/spritely/hoot/src/branch/main/lib/hoot/write.scm#L75-L208&quot;&gt;in
Scheme&lt;/a&gt;.
This turns out to have been a good test for bignums too; the &lt;a href=&quot;https://doi.org/10.1145/231379.231397&quot;&gt;algorithm
we use&lt;/a&gt; is a bit dated and relies
on bignums to do its thing.  The move to Scheme also allows for printing
floating-point numbers in other radices.&lt;/p&gt;&lt;p&gt;There are a few more Hoot patches that were inspired by Wastrel, about
which more later; it has been good for both to work on the two at the
same time.&lt;/p&gt;&lt;h3&gt;tail calls&lt;/h3&gt;&lt;p&gt;My plan for Wasm&#8217;s
&lt;a href=&quot;https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-return-call-x&quot;&gt;&lt;tt&gt;return_call&lt;/tt&gt;&lt;/a&gt;
and friends was to use the new &lt;tt&gt;musttail&lt;/tt&gt; annotation for calls, which
has been in clang for a while and was recently added to GCC.  I was
careful to &lt;a href=&quot;https://wingolog.org/archives/2026/02/09/six-thoughts-on-generating-c#:~:text=for%20ABI%20and%20tail%20calls%2C%20perform%20manual%20register%20allocation&quot;&gt;limit the number of function
parameters&lt;/a&gt;
such that no call should require stack allocation, and therefore a
compiler should have no reason to reject any particular tail call.&lt;/p&gt;&lt;p&gt;However, there were bugs.  Funny ones, at first: &lt;a href=&quot;https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124533&quot;&gt;attributes applying to
a preceding label instead of the following
call&lt;/a&gt;, or &lt;a href=&quot;https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124532&quot;&gt;the need
to insert &lt;tt&gt;if (1)&lt;/tt&gt; before the tail
call&lt;/a&gt;.  More dire
ones, in which &lt;a href=&quot;https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124534&quot;&gt;tail callers inlined into &lt;i&gt;their&lt;/i&gt; callees would cause
the tail calls to
fail&lt;/a&gt;, worked
around with judicious application of &lt;tt&gt;noinline&lt;/tt&gt;.  Thanks to GCC&#8217;s Andrew
Pinski for help debugging these and other issues; with GCC things are
fine now.&lt;/p&gt;&lt;p&gt;I did have to change the code I emitted to return &#8220;top types only&#8221;: if
you have a function returning type T, you can tail-call a function
returning U if U is a subtype of T, but there is &lt;a href=&quot;https://codeberg.org/andywingo/wastrel/issues/25&quot;&gt;no nice way to encode
this into the C type
system&lt;/a&gt;.  Instead, we
return the top type of T (or U, it&#8217;s the same), e.g. &lt;tt&gt;anyref&lt;/tt&gt;, and
insert downcasts at call sites to recover the precise types.  Not so
nice, but it&#8217;s what we got.&lt;/p&gt;&lt;p&gt;Trying tail calls on clang, I ran into a funny restriction: clang not
only requires that return types match, but requires that tail caller and
tail callee have the same parameters as well.  I can see why they did
this (it requires no stack shuffling and thus such a tail call is always
possible, even with 500 arguments), but it&#8217;s not the design point that I
need.  Fortunately there are discussions about &lt;a href=&quot;https://discourse.llvm.org/t/experience-with-clang-musttail/89085/7&quot;&gt;moving to a different
constraint&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;scale&lt;/h3&gt;&lt;p&gt;I spent way more time that I had planned to on improving the speed of
Wastrel itself.  My initial idea was to just emit one big C file, and
that would provide the maximum possibility for GCC to just go and do its
thing: it can see everything, everything is static, there are loads of
&lt;tt&gt;always_inline&lt;/tt&gt; helpers that should compile away to single instructions,
that sort of thing.  But, this doesn&#8217;t scale, in a few ways.&lt;/p&gt;&lt;p&gt;In the first obvious way, consider &lt;a href=&quot;https://mastodon.social/@wingo/115378481224538009&quot;&gt;whitequark&#8217;s
&lt;tt&gt;llvm.wasm&lt;/tt&gt;&lt;/a&gt;.  This
is all of LLVM in one 70 megabyte Wasm file.  Wastrel made a huuuuuuge C
file, then GCC chugged on it forever; 80 minutes at &lt;tt&gt;-O1&lt;/tt&gt;, and I wasn&#8217;t
aiming for &lt;tt&gt;-O1&lt;/tt&gt;.&lt;/p&gt;&lt;p&gt;I realized that in many ways, GCC wasn&#8217;t designed to be a compiler
target.  The shape of code that one might emit from a Wasm-to-C compiler
like Wastrel is different from that that one would write by hand.  I
even ran into a &lt;a href=&quot;https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124651&quot;&gt;segfault compiling with
-Wall&lt;/a&gt;, because GCC
accidentally recursed instead of iterated in the &lt;tt&gt;-Winfinite-recursion&lt;/tt&gt;
pass.&lt;/p&gt;&lt;p&gt;So, I dealt with this in a few ways.  After many hours spent pleading
and bargaining with different &lt;tt&gt;-O&lt;/tt&gt; options, I bit the bullet and made
Wastrel emit multiple C files.  It will compute a DAG forest of all the
functions in a module, where edges are direct calls, and go through that
forest, &lt;a href=&quot;https://codeberg.org/andywingo/wastrel/src/branch/main/module/wastrel/partitions.scm#L436&quot;&gt;greedily consuming (and possibly splitting) subtrees until we
have &#8220;enough&#8221; code to split out a partition&lt;/a&gt;, as measured by number of
Wasm instructions.  They say that &lt;tt&gt;-flto&lt;/tt&gt; makes this a fine approach,
but one never knows when a translation unit boundary will turn out to be
important.  I compute needed symbol visibilities as much as I can so as
to declare functions that don&#8217;t escape their compilation unit as
&lt;tt&gt;static&lt;/tt&gt;; who knows if this is of value.  Anyway, this partitioning
introduced no performance regression in my limited tests so far, and
compiles are much much much faster.&lt;/p&gt;&lt;h3&gt;scale, bis&lt;/h3&gt;&lt;p&gt;A brief observation: Wastrel used to emit indented code, because it
could, and what does it matter, anyway.  However, consider Wasm&#8217;s
&lt;a href=&quot;https://webassembly.github.io/spec/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-br-table-l-ast-l-n&quot;&gt;&lt;tt&gt;br_table&lt;/tt&gt;&lt;/a&gt;:
it takes an array of &lt;i&gt;n&lt;/i&gt; labels and an integer operand, and will branch
to the &lt;i&gt;n&lt;/i&gt;th label, or the last if the operand is out of range.  To set
up a label in Wasm, you make a block, of which there are a handful of
kinds; the label is visible in the block, and for &lt;i&gt;n&lt;/i&gt; labels, the
&lt;tt&gt;br_table&lt;/tt&gt; will be the most nested expression in the &lt;i&gt;n&lt;/i&gt; nested blocks.&lt;/p&gt;&lt;p&gt;Now consider that block indentation is proportional to &lt;i&gt;n&lt;/i&gt;.  This means,
the file size of an indented C file is quadratic in the number of branch
targets of the &lt;tt&gt;br_table&lt;/tt&gt;.&lt;/p&gt;&lt;p&gt;Yes, this actually bit me; there are &lt;tt&gt;br_table&lt;/tt&gt; instances with tens of
thousands of targets.  No, wastrel does not indent any more.&lt;/p&gt;&lt;h3&gt;scale, ter&lt;/h3&gt;&lt;p&gt;Right now, the long pole in Wastrel is the compile-to-C phase; the
C-to-native phase parallelises very well and is less of an issue.  So,
one might think: OK, you have partitioned the functions in this Wasm
module into a number of files, why not emit the files in parallel?&lt;/p&gt;&lt;p&gt;I gave this a go.  It did not speed up C generation.  From my cursory
investigations, I think this is because the bottleneck is garbage
collection in Wastrel itself; Wastrel is written in Guile, and Guile
still uses the Boehm-Demers-Weiser collector, which does not parallelize
well for multiple mutators.  It&#8217;s terrible but I ripped out
parallelization and things are fine.  &lt;a href=&quot;https://jorts.horse/@migratory/116279275172476524&quot;&gt;Someone on Mastodon suggested
&lt;tt&gt;fork&lt;/tt&gt;&lt;/a&gt;; they&#8217;re not
wrong, but also not Right either.  I&#8217;ll just keep this as a nice test
case for the Guile-on-Whippet branch I want to poke later this year.&lt;/p&gt;&lt;h3&gt;scale, quator&lt;/h3&gt;&lt;p&gt;Finally, I had another realization: GCC was having trouble compiling the
C that Wastrel emitted, because Hoot had emitted bad WebAssembly.  Not
bad as in &#8220;invalid&#8221;; rather, &#8220;not good&#8221;.&lt;/p&gt;&lt;p&gt;There were two cases in which Hoot emitted ginormous (technical term)
functions.  One, for an odd debugging feature: Hoot does a CPS transform
on its code, and allocates return continuations on a stack.  This is a
gnarly technique but it gets us delimited continuations and all that
goodness even before stack switching has landed, so it&#8217;s here for now.
It also gives us a reified return stack of &lt;tt&gt;funcref&lt;/tt&gt; values, which lets
us print Scheme-level backtraces.&lt;/p&gt;&lt;p&gt;Or it would, if we could associate data with a &lt;tt&gt;funcref&lt;/tt&gt;.  Unfortunately
&lt;tt&gt;func&lt;/tt&gt; is not a subtype of &lt;tt&gt;eq&lt;/tt&gt;, so we can&#8217;t.  Unless... we pass the
funcref out to the embedder (e.g. JavaScript), and the embedder checks
the funcref for equality (e.g. using &lt;tt&gt;===&lt;/tt&gt;); then we can map a funcref
to an index, and use that index to map to other properties.&lt;/p&gt;&lt;p&gt;How to pass that &lt;tt&gt;funcref&lt;/tt&gt;/index map to the host?  When I initially
wrote Hoot, I didn&#8217;t want to just, you know, put the funcrefs of interet
into a table and let the index of a function&#8217;s slot be the value in the
key-value mapping; that would be useless memory usage.  Instead, we
emitted functions that took an integer, and which would return a
funcref.  Yes, these used &lt;tt&gt;br_table&lt;/tt&gt;, and yes, there could be tens of
thousands of cases, depending on what you were compiling.&lt;/p&gt;&lt;p&gt;Then to map the integer index to, say, a function name, likewise I
didn&#8217;t want a table; that would force eager allocation of all strings.
Instead I emitted a function with a &lt;tt&gt;br_table&lt;/tt&gt; whose branches would
return &lt;tt&gt;string.const&lt;/tt&gt; values.&lt;/p&gt;&lt;p&gt;Except, of course, &lt;a href=&quot;https://wingolog.org/archives/2023/10/19/requiem-for-a-stringref&quot;&gt;stringref didn&#8217;t become a
thing&lt;/a&gt;,
and so instead we would end up lowering to allocate string constants as
globals.&lt;/p&gt;&lt;p&gt;Except, of course, Wasm&#8217;s idea of what a &#8220;constant&#8221; is is quite
restricted, so &lt;a href=&quot;https://codeberg.org/spritely/hoot/src/branch/main/module/wasm/lower-globals.scm&quot;&gt;we have a pass that moves non-constant global
initializers to the &#8220;start&#8221;
function&lt;/a&gt;.
This results in an enormous start function.  The straightforward
solution was to partition global initializations into separate
functions, called by the start function.&lt;/p&gt;&lt;p&gt;For the &lt;tt&gt;funcref&lt;/tt&gt; debugging, the solution was more intricate: firstly,
we represent the &lt;tt&gt;funcref&lt;/tt&gt;-to-index mapping just as a table.  It&#8217;s fine.
Then for the side table mapping indices to function names and sources,
we emit DWARF, and attach a special attribute to each &#8220;introspectable&#8221;
function.  In this way, reading the DWARF sequentially, we reconstruct a
mapping from index to DWARF entry, and thus to a byte range in the Wasm
code section, and thus to source information in the &lt;tt&gt;.debug_line&lt;/tt&gt;
section.  It sounds gnarly but Guile already used DWARF as its own
debugging representation; switching to emit it in Hoot was not a huge
deal, and as we only need to consume the DWARF that we emit, we only
needed some &lt;a href=&quot;https://codeberg.org/spritely/hoot/src/branch/main/reflect-js/reflect.js#L3-L377&quot;&gt;400 lines of
JS&lt;/a&gt;
for the web/node run-time support code.&lt;/p&gt;&lt;p&gt;This switch to data instead of code removed the last really long pole
from the GCC part of Wastrel&#8217;s pipeline.  What&#8217;s more, Wastrel can now
implement the &lt;tt&gt;code_name&lt;/tt&gt; and &lt;tt&gt;code_source&lt;/tt&gt; imports for Hoot programs
ahead of time: it can parse the DWARF at compile-time, and generate
functions that look up functions by address in a sorted array to return
their names and source locations.  As of today, this works!&lt;/p&gt;&lt;h3&gt;fin&lt;/h3&gt;&lt;p&gt;There are still a few things that Hoot wants from a host that Wastrel
has stubbed out: weak refs and so on.  I&#8217;ll get to this soon; my goal is
a proper Scheme REPL.  Today&#8217;s note is a waypoint on the journey.  Until
next time, happy hacking!&lt;/p&gt;&lt;/div&gt;        </description>
	<pubDate>Tue, 31 Mar 2026 20:34:23 +0000</pubDate>
	<dc:creator>Andy Wingo</dc:creator>
</item>
<item>
	<title>Alex Bradbury: Minipost: Routing a Linux user's traffic through a WireGuard interface</title>
	<guid>https://muxup.com/2026q1/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface</guid>
	<link>https://muxup.com/2026q1/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface</link>
	<description>
&lt;p&gt;Simple goal: take advantage of my home router's WireGuard support and have one
of my external servers connect using this, and pass all traffic from a certain
user through that interface.&lt;/p&gt;
&lt;h2 id=&quot;create-wireguard-credentials&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#create-wireguard-credentials&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Create WireGuard credentials&lt;/h2&gt;
&lt;p&gt;This part of the note won't be that useful to you unless you're using a
Fritzbox router. But if you're me or someone suspiciously like me you may want
to know to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigate to &lt;code&gt;https://192.168.178.1/#/access/wireguard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click &quot;Add WireGuard connection&quot; and ensure &quot;Connect a single device&quot; is
selected on the modal that appears. Then click &quot;Next&quot;.&lt;/li&gt;
&lt;li&gt;Enter a unique name for the connection (I typically use
&lt;code&gt;$remote_host_name-wg&lt;/code&gt;) and click Finish. Follow request to confirm by
pressing a button on the router.&lt;/li&gt;
&lt;li&gt;Click &quot;Download settings&quot; and a &lt;code&gt;wg_config.conf&lt;/code&gt; will be downloaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;add-user&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#add-user&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Add user&lt;/h2&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;VPN_USER=&lt;/span&gt;asbvpn
sudo useradd -m -g users -G wheel -s /bin/bash &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$VPN_USER&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
sudo passwd &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$VPN_USER&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id=&quot;configure-systemd-networkd&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#configure-systemd-networkd&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Configure systemd-networkd&lt;/h2&gt;
&lt;p&gt;First, extracting the relevant values from the &lt;code&gt;wg_config.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;WG_CONF=&lt;/span&gt;wg_config.conf
&lt;span&gt;PRIVATE_KEY=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;sed -n &lt;span&gt;'s/^PrivateKey = //p'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WG_CONF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;PUBLIC_KEY=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;sed -n &lt;span&gt;'s/^PublicKey = //p'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WG_CONF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;PRESHARED_KEY=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;sed -n &lt;span&gt;'s/^PresharedKey = //p'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WG_CONF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;ENDPOINT=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;sed -n &lt;span&gt;'s/^Endpoint = //p'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WG_CONF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;

&lt;span&gt;ADDRS=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;sed -n &lt;span&gt;'s/^Address = //p'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$WG_CONF&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;IPV4_ADDR=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(printf&lt;/span&gt; &lt;span&gt;'%s\n'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ADDRS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; | cut -d, -f1&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;span&gt;IPV6_ADDR=&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$(printf&lt;/span&gt; &lt;span&gt;'%s\n'&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$ADDRS&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt; | cut -d, -f2&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What we want to do is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Define the wireguard interface &lt;code&gt;wg0&lt;/code&gt; and specify the necessary keys, IP
addresses etc for it to be brought up successfully.&lt;/li&gt;
&lt;li&gt;Specify a routing policy so that all traffic from the given user account
goes via that interface.
&lt;ul&gt;
&lt;li&gt;As you can see below, we specify a RouteTable called &quot;vpn&quot;, associate that
with the interface, and specify rules for that table.&lt;/li&gt;
&lt;li&gt;Ideally this would &quot;fail closed&quot; and no traffic from the user would be
routed if &lt;code&gt;wg0&lt;/code&gt; is down. That appears to use additional rules managed
outside of systemd-networkd. I haven't tried to implement this.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The above can be achieved with:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo mkdir -p /etc/systemd/networkd.conf.d
sudo tee /etc/systemd/networkd.conf.d/90-vpn-table.conf &amp;gt;/dev/null &lt;span&gt;&amp;lt;&amp;lt;'EOF'&lt;/span&gt;
&lt;span&gt;[Network]&lt;/span&gt;
&lt;span&gt;RouteTable=vpn:100&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

sudo tee /etc/systemd/network/50-wg0.netdev &amp;gt;/dev/null &lt;span&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span&gt;[NetDev]&lt;/span&gt;
&lt;span&gt;Name=wg0&lt;/span&gt;
&lt;span&gt;Kind=wireguard&lt;/span&gt;

&lt;span&gt;[WireGuard]&lt;/span&gt;
&lt;span&gt;PrivateKey=$PRIVATE_KEY&lt;/span&gt;
&lt;span&gt;RouteTable=vpn&lt;/span&gt;

&lt;span&gt;[WireGuardPeer]&lt;/span&gt;
&lt;span&gt;PublicKey=$PUBLIC_KEY&lt;/span&gt;
&lt;span&gt;PresharedKey=$PRESHARED_KEY&lt;/span&gt;
&lt;span&gt;AllowedIPs=0.0.0.0/0&lt;/span&gt;
&lt;span&gt;AllowedIPs=::/0&lt;/span&gt;
&lt;span&gt;Endpoint=$ENDPOINT&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

sudo tee /etc/systemd/network/50-wg0.network &amp;gt;/dev/null &lt;span&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span&gt;[Match]&lt;/span&gt;
&lt;span&gt;Name=wg0&lt;/span&gt;

&lt;span&gt;[Network]&lt;/span&gt;
&lt;span&gt;Address=$IPV4_ADDR&lt;/span&gt;
&lt;span&gt;Address=$IPV6_ADDR&lt;/span&gt;

&lt;span&gt;[RoutingPolicyRule]&lt;/span&gt;
&lt;span&gt;User=$VPN_USER&lt;/span&gt;
&lt;span&gt;Table=vpn&lt;/span&gt;
&lt;span&gt;Priority=10000&lt;/span&gt;
&lt;span&gt;Family=both&lt;/span&gt;
&lt;span&gt;EOF&lt;/span&gt;

sudo chgrp systemd-network /etc/systemd/network/50-wg0.netdev
sudo chmod &lt;span&gt;0640&lt;/span&gt; /etc/systemd/network/50-wg0.netdev
sudo systemctl restart systemd-networkd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Doing it this way, we've stored the secret keys in the 50-wg0.netdev file
itself but restricted access to the file. It's possible to have the keys
stored in a separate file, but for my setup it didn't seem worthwhile.&lt;/p&gt;
&lt;p&gt;Then check the status with e.g.:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo networkctl status wg0
sudo ip rule show
sudo ip route show table &lt;span&gt;100&lt;/span&gt;
sudo wg show wg0
sudo -u &lt;span&gt;$VPN_USER&lt;/span&gt; curl https://ifconfig.me/all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;IPv6 does not work in this setup (&lt;code&gt;curl -6 google.com&lt;/code&gt; will fail),&lt;/p&gt;
&lt;h2 id=&quot;copying-authorized_keys-to-new-user&quot;&gt;&lt;a href=&quot;https://muxup.com/feed.xml#copying-authorized_keys-to-new-user&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Copying authorized_keys to new user&lt;/h2&gt;
&lt;p&gt;This is more a note to myself than anything else:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo install -d -m &lt;span&gt;700&lt;/span&gt; -o &lt;span&gt;$VPN_USER&lt;/span&gt; -g users /home/&lt;span&gt;$VPN_USER&lt;/span&gt;/.ssh
sudo install -m &lt;span&gt;600&lt;/span&gt; -o &lt;span&gt;$VPN_USER&lt;/span&gt; -g users &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.ssh/authorized_keys&amp;quot;&lt;/span&gt; /home/&lt;span&gt;$VPN_USER&lt;/span&gt;/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-03-31: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Tue, 31 Mar 2026 12:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #61</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-61/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-61/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from March 23 to March 30.&lt;/p&gt;
&lt;p&gt;
This week comes with a mixed bag of new features, incremental improvements,
and a new release with the ever important security issue fixes. Also: more
blog posts!
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/309558@main&quot;&gt;Implemented&lt;/a&gt; initial support for
&lt;code&gt;closedby=any&lt;/code&gt; on dialog elements, which adds light dismiss behaviour. This is
behind the &lt;code&gt;ClosedbyAttributeEnabled&lt;/code&gt; feature flag.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/309802@main&quot;&gt;Added&lt;/a&gt; the remaining values for the
experimental &lt;code&gt;closedby&lt;/code&gt; attribute implementation.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310070@main&quot;&gt;MiniBrowser now has&lt;/a&gt; a
&lt;code&gt;--profile-dir=DIR&lt;/code&gt; command line option that can be used to specify a custom
directory where website data and cache can be stored, to test, for example,
behavior in a clean session.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to)
playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Video decoding limits had been enforced on &lt;code&gt;HTMLMediaElement.canPlayType()&lt;/code&gt; so
far, but &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/show_bug.cgi?id=310192&quot;&gt;they are now also enforced in MediaCapabilities
queries&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310220@main&quot;&gt;Fixed&lt;/a&gt; several OpenGL state
restoration bugs in &lt;code&gt;BitmapTexture&lt;/code&gt; . These could cause a mismatch between the
GL state assumed by Skia and the actual one, leading to rendering artifacts
with certain GPU drivers and configurations.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The &lt;code&gt;SKIA_DEBUG&lt;/code&gt; CMake option &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/310215@main&quot;&gt;has been
enabled&lt;/a&gt; for &lt;code&gt;Debug&lt;/code&gt; builds, enabling
Skia's internal assertions, debug logging, and consistency checks (e.g. bounds
checking, resource key diagnostics). It remains off by default for &lt;code&gt;Release&lt;/code&gt;
and &lt;code&gt;RelWithDebInfo&lt;/code&gt; builds, and can still be explicitly configured via
&lt;code&gt;-DSKIA_DEBUG=ON|OFF&lt;/code&gt;.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;wpe-webkit-pager&quot;&gt;WPE WebKit &#128223;&lt;/h2&gt;
&lt;h3 id=&quot;wpe-platform-api-jigsaw&quot;&gt;WPE Platform API &#129513;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;New, modern platform API that supersedes usage of libwpe and WPE backends.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The new &lt;code&gt;WPE_SETTING_OVERLAY_SCROLLBARS&lt;/code&gt; setting &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/309811@main&quot;&gt;is now
available&lt;/a&gt;, and disabling it will use a
more traditional, always visible scrollbar style.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;A new &lt;code&gt;USE_GSTREAMER&lt;/code&gt; build option &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/309883@main&quot;&gt;may now be
used&lt;/a&gt; to toggle the features that
require GStreamer at once. This can be used to effectively disable all
multimedia support, which previously needed toggling four CMake options.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/03/27/webkitgtk2.52.1-released.html&quot;&gt;WebKitGTK
2.52.1&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.1.html&quot;&gt;WPE WebKit 2.52.1&lt;/a&gt; have
been released. On top of a small corrections typical of the first point
releases in a new stable series, this one includes a number of fixes for
security issues, and it is a recommended update. The corresponding security
advisory, &lt;code&gt;WSA-2026-0002&lt;/code&gt;
(&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/security/WSA-2026-0002.html&quot;&gt;GTK&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/security/WSA-2026-0002.html&quot;&gt;WPE&lt;/a&gt;) has been published as
well.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;community-events-handshake&quot;&gt;Community &amp;amp; Events &#129309;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Sim&#243;n Pena wrote a blog post showing &lt;a rel=&quot;external&quot; href=&quot;https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit/&quot;&gt;how to create a minimal WPE
launcher&lt;/a&gt;,
which uses a Fedora Podman container with pre-built WPE WebKit libraries and a
launcher with barely 10 lines of code to display a web view. This complements
Kate Lee's &lt;a rel=&quot;external&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;custom HTML context menu blog
post&lt;/a&gt;
from last week.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 30 Mar 2026 21:46:57 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Ricardo Ca&#241;uelo Navarro: Why don't we do a demo? Part 2: software development</title>
	<guid>https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/index.html</guid>
	<link>https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/index.html</link>
	<description>
&lt;p&gt;In &lt;a href=&quot;https://blogs.igalia.com/rcn/posts/20260317-why_dont_we_do_a_demo_part_1/index.html&quot;&gt;part
          1&lt;/a&gt; of this series I talked about the beginning of this
          story and laid out the plan. In this post we'll start the
          actual work, beginning with the software part.&lt;/p&gt;

        &lt;h3&gt;Problem 5: base peripheral device&lt;/h3&gt;

        &lt;p&gt;I'll start with the most basic device: the peripheral. It
          will provide a simple BLE service to allow toggling the board
          LED remotely and displaying its current status.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;The &lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/blob/main/samples/bluetooth/peripheral/src/main.c&quot;&gt;Zephyr samples&lt;/a&gt; are a good starting point for the firmware
          skeleton. The XIAO nRF54L15 is also well supported in Zephyr,
          so defining a custom BLE service and operating the on-board
          LED is not a challenge. A minimal sketch firmware with the
          basic functionality can be done reasonably quickly starting
          from scratch. To test the BLE service we can use a smartphone
          and &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-mobile&quot;&gt;nRF
          Connect for Mobile&lt;/a&gt;.&lt;/p&gt;

        &lt;p&gt;I probably don't need to go all the trouble of doing a custom
          BLE service and characteristic for this, but it's an exercise
          I'll need to do at some point, and it has the added bonus of
          giving us full freedom to define the functionalities we
          want.&lt;/p&gt;

        &lt;p&gt;For the BLE services and characteristics, I picked up a
          random
          128-bit &lt;div class=&quot;tooltip&quot;&gt;UUID&lt;span class=&quot;tooltiptext&quot;&gt;Universally
          Unique Identifier&lt;/span&gt;&lt;/div&gt; generated
          with &lt;a href=&quot;https://www.uuidgenerator.net/version4&quot;&gt;https://www.uuidgenerator.net/version4&lt;/a&gt;.&lt;/p&gt;

        The BLE-related boilerplate code for the basic functionality
        uses the appropriate macros to define the GATT service and
        characteristics:

        &lt;pre&gt;&lt;code&gt;/* LED service UUID: 46239800-1bed5-4c51-a215-9251faaae809 */
#define LED_SERVICE_UUID_VAL \
	BT_UUID_128_ENCODE(0x46239800, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809)

static struct bt_uuid_128 led_svc_uuid =
	BT_UUID_INIT_128(LED_SERVICE_UUID_VAL);

/* Characteristic UUID: 46239801-1bed5-4c51-a215-9251faaae809 */
static struct bt_uuid_128 led_char_uuid = BT_UUID_INIT_128(
	BT_UUID_128_ENCODE(0x46239801, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809));

/* Characteristic UUID: 46239802-1bed5-4c51-a215-9251faaae809 */
static struct bt_uuid_128 led_indication_char_uuid = BT_UUID_INIT_128(
	BT_UUID_128_ENCODE(0x46239802, 0x1bed5, 0x4c51, 0xa215, 0x9251faaae809));

[...]

BT_GATT_SERVICE_DEFINE(led_svc,
	BT_GATT_PRIMARY_SERVICE(&amp;amp;led_svc_uuid),
	BT_GATT_CHARACTERISTIC(&amp;amp;led_char_uuid.uuid,
			BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
			BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
			read_led_state, write_led_state, &amp;amp;led_state),
	BT_GATT_CHARACTERISTIC(&amp;amp;led_indication_char_uuid.uuid,
			BT_GATT_CHRC_INDICATE,
			BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
			NULL, NULL, NULL),
	BT_GATT_CCC(led_ccc_changed,
		BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);&lt;/code&gt;&lt;/pre&gt;

        Where the &lt;code&gt;read_led_state&lt;/code&gt;, &lt;code&gt;write_led_state&lt;/code&gt;
        and &lt;code&gt;led_ccc_changed&lt;/code&gt; callbacks look something like
        this:

        &lt;pre&gt;&lt;code&gt;/*
 * LED state characteristic read callback.
 */
static ssize_t read_led_state(struct bt_conn *conn,
                             const struct bt_gatt_attr *attr, void *buf,
                             uint16_t len, uint16_t offset) {
	const uint8_t *val = attr-&amp;gt;user_data;
	return bt_gatt_attr_read(conn, attr, buf, len, offset, val,
				sizeof(*val));
}

/*
 * LED state characteristic write callback.
 * A write to this characteristic will trigger a LED toggle, the data
 * sent is irrelevant so we can just ignore it.
 */
static ssize_t write_led_state(struct bt_conn *conn,
                              const struct bt_gatt_attr *attr, const void *buf,
                              uint16_t len, uint16_t offset, uint8_t flags) {
	ARG_UNUSED(conn);
	ARG_UNUSED(attr);
	ARG_UNUSED(buf);
	ARG_UNUSED(offset);
	ARG_UNUSED(flags);

	/*
	 * Ignore received data (dummy): *((uint8_t *)buf)
	 * and override (toggle) the led_state here as a side-effect.
	 */
	LOG_DBG(&quot;LED toggle received: %d -&amp;gt; %d&quot;, led_state, led_state ? 0 : 1);
	led_state = led_state ? 0 : 1;
	gpio_pin_set_dt(&amp;amp;led, led_state);
	gpio_pin_set_dt(&amp;amp;led_board, led_state);
	if (led_indication_enabled)
		k_work_schedule(&amp;amp;led_indicate_work, K_NO_WAIT);

	return len;
}

/*
 * LED indication Client Characteristic Configuration callback.
 */
static void led_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
	ARG_UNUSED(attr);

	led_indication_enabled = (value == BT_GATT_CCC_INDICATE);
	LOG_DBG(&quot;Indication %s&quot;, led_indication_enabled ? &quot;enabled&quot; : &quot;disabled&quot;);
}&lt;/code&gt;&lt;/pre&gt;

        &lt;p&gt;This should be good enough for now, we'll surely need to
        complicate it later.&lt;/p&gt;


        &lt;h3&gt;Problem 6: unexpected LED behavior&lt;/h3&gt;

        &lt;p&gt;The user LED in the XIAO nRF54L15 turns off
          with &lt;a href=&quot;https://docs.zephyrproject.org/latest/doxygen/html/group__gpio__interface.html#ga541064fb9e575c0c559c101754466fa8&quot;&gt;&lt;code&gt;gpio_pin_set_dt(&amp;amp;led,
          1)&lt;/code&gt;&lt;/a&gt; and on with &lt;code&gt;gpio_pin_set_dt(&amp;amp;led,
          0)&lt;/code&gt;. Not a problem if we only want to toggle it instead
          of setting a specific value, but not ideal, since we also want
          to keep track of its current state and report it.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;This one's easy. According to the
          &lt;a href=&quot;https://files.seeedstudio.com/wiki/XIAO_nRF54L15/Getting_Start/nRF54L15_Sense_Schematic.pdf&quot;&gt;schematic&lt;/a&gt;,
          this LED is active low, but
          the &lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/blob/main/boards/seeed/xiao_nrf54l15/xiao_nrf54l15_common.dtsi&quot;&gt;device
          tree for this SoC&lt;/a&gt; defines it as active
          high. &lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/pull/97808/changes&quot;&gt;Fixed
          and upstreamed&lt;/a&gt;.&lt;/p&gt;

        &lt;h3&gt;Problem 7: modeling the behavior of the central device&lt;/h3&gt;

        &lt;p&gt;In the BLE central-peripheral architecture proposed, the
          peripheral will work as an autonomous device that provides a
          service but does no other action except when requested by the
          user through a button press. Other than that, it'll sit there
          waiting for requests from the central (the controller device
          in our case), which will be the one governing the bulk of the
          application and, more importantly, managing the connection and
          doing the necessary actions to establish and monitor it.&lt;/p&gt;

        &lt;p&gt;Some of the tasks under the responsibility of the controller
          are:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Scanning for peripherals.
          &lt;/li&gt;
          &lt;li&gt;
            Connecting to peripherals.
          &lt;/li&gt;
          &lt;li&gt;
            Service discovery.
          &lt;/li&gt;
          &lt;li&gt;
            Keep track of the connected devices.
          &lt;/li&gt;
          &lt;li&gt;
            Handle disconnection requests and lost connections.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;We need a way to model this behavior into the controller so
          we can integrate these tasks with the rest of the firmware
          gracefully.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;I'll abstract the list of tasks above in a simple state
          machine that will run in a separate thread taking care of
          handling the connections, running the necessary actions as
          response to specific events, interacting with the rest of the
          firmware and reacting to the actions triggered by the user via
          the board buttons or by external sources.&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/sm.png&quot; /&gt;
        &lt;/div&gt;

        &lt;p&gt;That way, the main thread will set up the hardware and the
          necessary software subsystems, and the state machine will keep
          track of most of the BLE-related tasks and of the connected
          devices.&lt;/p&gt;

        &lt;p&gt;So, when the initialization is done, the main thread will
          start the state machine thread and then wait for events such
          as button presses, managing and restarting common services,
          while the state machine works on its own.&lt;/p&gt;

        &lt;p&gt;For our purposes we'll only need three states:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            &lt;b&gt;Event listen&lt;/b&gt;: the device waits for events from the
            user or from external sources. In the most basic scenario,
            it waits for a &quot;scan&quot; request, which will make the machine
            move to the &quot;Scan&quot; state.
          &lt;/li&gt;
          &lt;li&gt;
            &lt;b&gt;Scan&lt;/b&gt;: this state handles device scanning and
            connection. Once connected to a suitable device, the state
            machine will move to the &quot;Discover&quot; state. If no connection
            is done after a period of time, the machine will go back to
            the &quot;Event listen&quot; state.
          &lt;/li&gt;
          &lt;li&gt;
            &lt;b&gt;Discover&lt;/b&gt;: here, the firmware will run the discovery
            process for a connected peripheral, looking for a specific
            set of services and characteristics. If the process is
            successful, the controller will save the necessary data
            about the peripheral for later use and move to the &quot;Event
            listen&quot; state.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;I can reuse most of this architecture as the basis for the
          console device as well, since it'll be a central device to the
          controllers (remember the controllers are both central and
          peripheral BLE devices at the same time), so I can start
          sketching the console firmware as well as a generic central
          device.&lt;/p&gt;

        &lt;h3&gt;Problem 8: designing the UX for the controller device&lt;/h3&gt;

        &lt;p&gt;We need a way for the controller to interact with the
          connected peripherals, and in the controller boards (nRF54L15
          DK) we have as user-facing devices four LEDs and four
          buttons. The operations we'll need to perform are:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Scan for peripherals.
          &lt;/li&gt;
          &lt;li&gt;
            Disconnect from a connected peripheral.
          &lt;/li&gt;
          &lt;li&gt;
            Toggle the LED of a connected peripheral.
          &lt;/li&gt;
          &lt;li&gt;
            Check the status of the peripherals.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/nrf54l15dk_leds_buttons.jpg&quot; /&gt;
        &lt;/div&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;The most useful thing we could do with the board LEDs is to
          replicate the status of the peripheral LEDs. That way we could
          have a real-time overview of the state of the connected
          peripherals at all times.&lt;/p&gt;

        &lt;p&gt;The downside of this is that the board only has four LEDs, so
          if I want to show the status of the connected peripherals at a
          glance, I'm limited to four of them. And it'd be good to keep
          one LED to show the status of the controller itself, so lets
          start by limiting the amount of simultaneously connected
          peripherals to three.&lt;/p&gt;

        &lt;p&gt;Now, about the buttons, I'm going to need a way to perform at
          least three actions: scanning, disconnecting and toggling, and
          I'll probably need to make room for additional actions down
          the road.&lt;/p&gt;

        &lt;p&gt;One option is to assign one button to each peripheral &quot;slot&quot;,
          so I could use button 0 to perform an action on slot 0, button
          1 for slot 1, etc. In this case, I'd need to encode multiple
          actions on the same button: scanning and toggling at least.&lt;/p&gt;

        &lt;p&gt;A different approach is to use one or two buttons to select
          the active slot, and then the action buttons would operate on
          the selected slot. I feel like this method could be easier to
          adapt in case I need to add additional functionalities later,
          so this is what I'll do:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Button 0: select the next slot as the &quot;active slot&quot;.
          &lt;/li&gt;
          &lt;li&gt;
            Button 1: &quot;action button&quot;, trigger an action on the
            peripheral connected in the active slot. For now, the action
            will be to toggle the LED.
          &lt;/li&gt;
          &lt;li&gt;
            Button 2: select the previous slot as the &quot;active slot&quot;.
          &lt;/li&gt;
          &lt;li&gt;
            Button 3: disconnect the peripheral in the active slot, if
            any, and start scanning on it.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;I'll also need a way to tell which one is the selected
          slot. Since I'm using the LEDs to represent the slots, an easy
          way to do this is by briefly blink the LED of the currently
          active slot when we use buttons 0 or 2 to cycle through the
          slots. Additionally, I can use the same method to encode
          whether the slot contains a connected peripheral or not, since
          I'm using a static LED to show the status of the peripheral
          LED (i.e. we can't tell from a LED that's off if the connected
          peripheral has its LED off or of there's no peripheral
          connected at all): when cycling through the slots selecting
          the active one, the LED can do a short blink cycle to
          represent a disconnected slot and a long blink cycle to
          represent a connected one.&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;video width=&quot;320&quot; controls=&quot;controls&quot; height=&quot;240&quot;&gt;
            &lt;source src=&quot;https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/slot_cycle.webm&quot; type=&quot;video/webm&quot;&gt;&lt;/source&gt;
          &lt;/video&gt;
        &lt;/div&gt;

        &lt;h3&gt;Problem 9: simulation and testing&lt;/h3&gt;

        &lt;p&gt;During development, it's very inconvenient to run all the
          firmware changes we do on real hardware, even if these boards
          can be flashed very fast. And for debugging and testing,
          relying on the hardware is overkill most of the time, even if
          we have direct access to a serial console and we have plenty
          of tracing possibilities. I'd need a better way to test our
          changes.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;Fortunately, Zephyr includes
          a &lt;a href=&quot;https://docs.zephyrproject.org/latest/boards/native/native_sim/doc/index.html&quot;&gt;native
          simulator&lt;/a&gt; that allows to build a firmware as a native
          binary that I can run on the development machine using
          emulated devices. For my purposes, the
          &lt;a href=&quot;https://docs.zephyrproject.org/latest/boards/native/doc/bsim_boards_design.html#bsim-boards&quot;&gt;native
          bsim boards&lt;/a&gt; even let me simulate the specific SoC used in
          the boards, including most of the SoC hardware, and run the
          firmware natively in
          &lt;a href=&quot;https://babblesim.github.io/&quot;&gt;BabbleSim&lt;/a&gt; to
          simulate real BLE usage.&lt;/p&gt;

        &lt;p&gt;This offers many advantages over testing on hardware:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Faster development cycles.
          &lt;/li&gt;
          &lt;li&gt;
            Easier debugging of runtime errors.
          &lt;/li&gt;
          &lt;li&gt;
            Triggering of specific corner cases programmatically.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;Ideally, what I'd like is to configure the environment so
          that I can selectively build and test the firmware on the
          simulator, or build a release firmware for the real
          hardware. A way to do this is to keep two separate project
          config files, create the necessary device tree overlay files
          for the different target boards (real and simulated) and
          compile certain parts of the firmware conditionally, so that I
          can enable test code and emulated devices only on the
          simulator build and I can keep hardware-dependent code only
          for the release build:&lt;/p&gt;

        &lt;pre&gt;&lt;code&gt;&#9500;&#9472;&#9472; boards
&#9474;&#160;&#160; &#9500;&#9472;&#9472; nrf52_bsim.conf
&#9474;&#160;&#160; &#9500;&#9472;&#9472; nrf52_bsim.overlay
&#9474;&#160;&#160; &#9500;&#9472;&#9472; nrf54l15bsim_nrf54l15_cpuapp.conf
&#9474;&#160;&#160; &#9492;&#9472;&#9472; nrf54l15bsim_nrf54l15_cpuapp.overlay
&#9500;&#9472;&#9472; build.sh
&#9500;&#9472;&#9472; CMakeLists.txt
&#9500;&#9472;&#9472; flash.sh
&#9500;&#9472;&#9472; Kconfig
&#9500;&#9472;&#9472; prj.conf
&#9500;&#9472;&#9472; prj_sim.conf
&#9500;&#9472;&#9472; sim_bin
&#9500;&#9472;&#9472; sim_build.sh
&#9500;&#9472;&#9472; sim_run.sh
&#9492;&#9472;&#9472; src
    &#9500;&#9472;&#9472; common.h
    &#9500;&#9472;&#9472; emul.c
    &#9500;&#9472;&#9472; emul.h
    &#9500;&#9472;&#9472; main.c
    &#9500;&#9472;&#9472; peripheral_mgmt.c
    &#9500;&#9472;&#9472; peripheral_mgmt.h
    &#9500;&#9472;&#9472; sim_test.c
    &#9500;&#9472;&#9472; sm.c
    &#9492;&#9472;&#9472; sm.h&lt;/code&gt;&lt;/pre&gt;

        &lt;p&gt;Code compiled conditionally for the simulator looks like
        this:&lt;/p&gt;

        &lt;pre&gt;&lt;code&gt;[...]
int main(void)
{
	static struct gpio_callback button_cb_data;
	int log_sources = log_src_cnt_get(0);
	int ret;
	int i;

#ifdef CONFIG_BOARD_NRF52_BSIM
	/* Set all logging to INFO level by default */
	for (i = 0; i &amp;lt; log_sources; i++) {
		log_filter_set(NULL, 0, i, LOG_LEVEL_INF);
	}
	int id = log_source_id_get(&quot;controller__main&quot;);
	log_filter_set(NULL, 0, id, LOG_LEVEL_DBG);
#else
	/* Disable all logging by default */
	for (i = 0; i &amp;lt; log_sources; i++) {
		log_filter_set(NULL, 0, i, LOG_LEVEL_NONE);
	}
#endif&lt;/code&gt;&lt;/pre&gt;

            
        

        
        &lt;p&gt;From now on, I can do most of the development on the
          simulator, and once things are the way I want I can test them
          on the real hardware.&lt;/p&gt;

        &lt;h3&gt;Problem 10: battery-powered peripheral setup&lt;/h3&gt;

        &lt;p&gt;While the peripheral devices can be powered via USB, just the
          same as the bigger boards, the demo would be both more
          realistic and more diverse if we used batteries for them. The
          XIAO nRF54L15 is prepared for that and
          has &lt;a href=&quot;https://wiki.seeedstudio.com/xiao_nrf54l15_sense_getting_started/#battery-powered-board&quot;&gt;battery
          pads&lt;/a&gt; and the necessary hardware to manage a LiPo
          battery. I need to provide the batteries and add the
          appropriate battery leads to the boards, though.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;Any suitable LiPo battery will do, but I'll search for
          batteries with an appropriate dimensions and capacity for this
          application.&lt;/p&gt;

        &lt;p&gt;I found this bundle containing five batteries and a charger,
          which should be good enough for our purposes: we can have up
          to 5 battery-powered peripherals and a convenient way to
          recharge the batteries if they're easy to detach from the
          devices.&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/batteries_charger.jpg&quot; /&gt;
        &lt;/div&gt;

        &lt;p&gt;The battery connectors are Molex 51005, so I'll also need to
          source a bunch of male and female leads. The pads are big
          enough to solder the leads to them with a conventional pen
          solder:&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/peripheral_and_battery.jpg&quot; /&gt;
        &lt;/div&gt;

        &lt;h3&gt;Problem 11: hardware unreliability&lt;/h3&gt;

        &lt;p&gt;The XIAO nRF54L15 seems very flaky. In particular, after
          flashing it sometimes the device crashes and Zephyr reports a
          bus data error in the serial console. It seems to be random,
          it happens only after flashing some builds and it also seems
          to depend on timing.&lt;/p&gt;

        &lt;p&gt;Even worse, when battery-powered, the board won't boot. When
          powered via USB, though, it will boot, and then I can plug in
          the battery, unplug the USB cable and the board will keep on
          running.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;After some investigation and tests, it looks like the crashes
          are related to the logging through the UART console. Why, I
          don't know. The kind of crashes I'm seeing right during
          booting are bus faults, and the first things I'd check for are
          null pointer dereferences and stack overflows, but in this
          case I'm not even getting a valid PC in the error
          report. Besides, there are a few signs that this will be hard
          to pinpoint:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Altering the logging does cause different results.
          &lt;/li&gt;
          &lt;li&gt;
            Different builds and flashings of the same firmware
            sometimes crash and sometimes don't.
          &lt;/li&gt;
          &lt;li&gt;
            It doesn't seem related to the size of the logging stack.
          &lt;/li&gt;
          &lt;li&gt;
            Deferred vs immediate logging causes different results.
          &lt;/li&gt;
          &lt;li&gt;
            It doesn't fail on the simulator.
          &lt;/li&gt;
          &lt;li&gt;
            It seems related to timing.
          &lt;/li&gt;
          &lt;li&gt;
            There's a big randomness factor.
          &lt;/li&gt;
          &lt;li&gt;
            The same firmware on the same SoC but on a different board
            design (nRF54L15 DK) works fine.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;All of these hint that there's some flakiness involved in the
          XIAO nRF54L15, particularly related to either power
          management, flashing or the use of the builtin USB for UART
          output.&lt;/p&gt;

        &lt;p&gt;Judging by some issues raised in
          the &lt;a href=&quot;https://forum.seeedstudio.com/&quot;&gt;Seeed Studio
          forums&lt;/a&gt;, it looks like the USB-based SWD circuitry could be
          the cause of these problems. Regarding the problems booting
          when battery-powered, after asking about it in the forums, I
          got a
          &lt;a href=&quot;https://forum.seeedstudio.com/t/xiao-nrf54l15-with-a-coin-battery/294019/33?u=rcn&quot;&gt;response&lt;/a&gt;
          explaining the reason: when logging is enabled, the TX line
          back-feeds and powers up the USB-UART chip, causing a brownout
          and a shutdown/reboot.&lt;/p&gt;

        &lt;p&gt;The most reasonable fix or workaround for all of this is to
          simply disable all logging and UART usage when the board is
          battery-powered&lt;a href=&quot;https://blogs.igalia.com/rcn/feed.xml#fn1&quot; id=&quot;ref1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;. In
          order to do this, I created another build type that will be
          used for &quot;production&quot; releases. For the non-production builds
          (the ones I'll use for development and debugging) I'll keep
          logging disabled with the possibility of enabling it through
          shell commands. That'll reduce the chances of crashing the
          system at boot time.&lt;/p&gt;

        &lt;h3&gt;Problem 12: network connectivity in the console device&lt;/h3&gt;

        &lt;p&gt;We can take advantage of the builtin web server capabilities
          provided by Zephyr for the console board. Since it'll be
          governing the application and monitoring / controlling the
          connected devices, we'll need a user interface to manage
          it. Implementing it in the form of a web interface should be
          easy enough, and it'd give us a lot of freedom to design the
          interface. The idea would be to connect the console board to a
          client (a laptop, for instance) using a point-to-point
          Ethernet link and have the client access the web page served
          by the console board.&lt;/p&gt;

        &lt;p&gt;The problem is that the board doesn't have an Ethernet
        interface.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;Everything's not lost, though. The board doesn't have an
          Ethernet interface but it has a general USB interface besides
          the one used for flashing and debugging. And, fortunately, the
          USB stack in Zephyr
          supports &lt;a href=&quot;https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/usb/device_next/class/usbd_cdc_ncm.c&quot;&gt;USB
          CDC NCM&lt;/a&gt; (Ethernet-over-USB) and we even have an
          &lt;a href=&quot;https://docs.zephyrproject.org/latest/samples/net/sockets/http_server/README.html&quot;&gt;example&lt;/a&gt;
          of the web server running on the same board we're using for
          the console device, so setting it up shouldn't be too much of
          an issue.&lt;/p&gt;

        &lt;p&gt;I can run the sample code on the board and check that it
          works, I can connect to it and see the web page published by
          the web server. Integrating the basic code into our sketchy
          console firmware is mostly painless, although I'm publishing
          only a placeholder web page. For now, that's good enough. I'll
          see what we can do with it later.&lt;/p&gt;

        &lt;p&gt;In the next post we'll continue through the rest of the
          software development part of the project.&lt;/p&gt;

        &lt;div class=&quot;footnotes&quot;&gt;
          &lt;p id=&quot;fn1&quot;&gt;1: This is now &lt;a href=&quot;https://github.com/Seeed-Studio/wiki-documents/commit/e94991dad122407c8df5a7d6c045625e7b7d1a56&quot;&gt;documented&lt;/a&gt; in the &lt;a href=&quot;https://wiki.seeedstudio.com/xiao_nrf54l15_sense_getting_started/#battery-voltage-detection&quot;&gt;Seeed Studio wiki&lt;/a&gt;&lt;a href=&quot;https://blogs.igalia.com/rcn/feed.xml#ref1&quot;&gt;&#8617;&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;        </description>
	<pubDate>Mon, 30 Mar 2026 11:00:00 +0000</pubDate>
	<dc:creator>rcn</dc:creator>
</item>
<item>
	<title>Qiuyi Zhang (Joyee): Teaching gdb to Unwind V8 JIT Frames on x64</title>
	<guid>https://joyeecheung.github.io/blog/2026/03/24/teaching-gdb-to-unwind-v8-jit-frames-on-x64/</guid>
	<link>https://joyeecheung.github.io/blog/2026/03/24/teaching-gdb-to-unwind-v8-jit-frames-on-x64/</link>
	<description>
&lt;p&gt;Recently I landed a &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chromium-review.googlesource.com/c/v8/v8/+/7543454&quot;&gt;custom Python gdb unwinder in V8&lt;/a&gt; that allows gdb to unwind through V8&#8217;s JIT-compiled frames on x64&lt;/p&gt;        </description>
	<pubDate>Thu, 26 Mar 2026 16:20:20 +0000</pubDate>
</item>
<item>
	<title>Jos&#233; Dapena: The implementation of Container Timing: aggregating paints in Blink</title>
	<guid>https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/</guid>
	<link>https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/</link>
	<description>
        &lt;img class=&quot;face&quot; src=&quot;/images/dape.png&quot; width=&quot;74&quot; height=&quot;100&quot; alt=&quot;&quot; align=&quot;right&quot; style=&quot;float: right&quot; /&gt;
&lt;p&gt;Measuring paint performance is a balancing act: you need precision, but the measurement itself can&#8217;t slow things down.&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://blogs.igalia.com/dape/2026/02/10/container-timing-measuring-web-components-performance/&quot;&gt;previous post&lt;/a&gt;, I introduced &lt;strong&gt;Container Timing&lt;/strong&gt;, 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 &lt;strong&gt;Blink&lt;/strong&gt;, the rendering engine used by Chromium.&lt;/p&gt;
&lt;h2 id=&quot;the-architecture-hooking-into-paint&quot; tabindex=&quot;-1&quot;&gt;The Architecture: Hooking into Paint &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In Blink, the rendering pipeline goes through several stages: Style, Layout, Paint, and Composite. The Container Timing implementation relies heavily on the &lt;strong&gt;Paint&lt;/strong&gt; stage.&lt;/p&gt;
&lt;p&gt;The main idea was &lt;strong&gt;not reinventing the wheel&lt;/strong&gt;. Blink already provides paint timing detection for the implementation of Large Contentful Paint (LCP) and Element Timing. However, this is targeted for &lt;em&gt;specific&lt;/em&gt; nodes (an image, a text block). In Container Timing we care about &lt;em&gt;subtrees&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So, when a paint is detected, we need to quickly decide whether the paint  is relevant to Container Timing.&lt;/p&gt;
&lt;h2 id=&quot;is-a-paint-interesting-for-container-timing&quot; tabindex=&quot;-1&quot;&gt;Is a paint interesting for Container Timing? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As the DOM tree is built (on parsing, or because of a script), we check the value of the attribute &lt;code&gt;containertiming&lt;/code&gt; for each &lt;code&gt;Element&lt;/code&gt;. When found, we flag that element and all its descendants with the flag &lt;code&gt;SelfOrAncestorHasContainerTiming&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also have the attribute &lt;code&gt;containertiming-ignore&lt;/code&gt;. When found, we will stop the propagation.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-important&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;What about DOM tree updates after parsing?&lt;/p&gt;&lt;p&gt;This is a pain point for performance. When a DOM element starts/stops having the &lt;code&gt;containertiming&lt;/code&gt; or &lt;code&gt;containertiming-ignore&lt;/code&gt; attribute after the DOM tree is created, we need to traverse the tree to update the flag.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;collecting-paint-updates&quot; tabindex=&quot;-1&quot;&gt;Collecting Paint Updates &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a paint is detected, we just reuse the existing implementation in the &lt;code&gt;ImagePaintTimingDetector&lt;/code&gt; and &lt;code&gt;TextPaintTimingDetector&lt;/code&gt;, that are also used for LCP and Element Timing for the relevant elements.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-note&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;Note&lt;/p&gt;&lt;p&gt;Only text and image paints are currently tracked. Video, canvas, and SVG are not yet supported.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We first determine if the paint should be recorded for Container Timing. And this is fast because of the &lt;code&gt;SelfOrAncestorHasContainerTiming&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;The timing detectors give us the area of the visual rectangle, the bounding box on screen that was painted.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;containertiming&lt;/code&gt; attribute (a &lt;strong&gt;container timing root&lt;/strong&gt;), we report that paint event to it.&lt;/p&gt;
&lt;p&gt;This &#8220;bubbling up&#8221; of paint events is illustrated in the diagram below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/images/propagation-to-ancestor.png&quot; alt=&quot;Within the Blink rendering pipeline, paint events from individual text and image nodes are captured by the paint timing detectors and then &quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;Is this expensive?&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;aggregating-regions&quot; tabindex=&quot;-1&quot;&gt;Aggregating Regions &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the most interesting challenges was determining the &lt;code&gt;size&lt;/code&gt; of the container. It is not just the size of the &lt;em&gt;container timing root&lt;/em&gt;. It is the &lt;strong&gt;union of all painted content&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Two reasons for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Being able to incrementally determine the updated area, in a way that is inspired by Largest Contentful Paint.&lt;/li&gt;
&lt;li&gt;To reduce the amount of performance events generated, we &lt;strong&gt;discard&lt;/strong&gt; the paints that do not increase the area.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We maintain a &lt;code&gt;PaintedRegion&lt;/code&gt; for each container. This is a non-overlapping union of the rectangles that cover the updated area:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Initial Paint:&lt;/strong&gt; When the first child paints, we initialize the region with its visual rectangle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subsequent Paints:&lt;/strong&gt; As more images load or text renders, we perform a union operation: &lt;code&gt;CurrentRegion = Union(CurrentRegion, NewPaintRect)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, as paints are detected, each container will aggregate the parts of the screen that have been painted by all their children.&lt;/p&gt;
&lt;p&gt;We use &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/cc/base/region.h&quot;&gt;&lt;code&gt;cc::Region&lt;/code&gt;&lt;/a&gt;, based on &lt;a href=&quot;https://api.skia.org/classSkRegion.html&quot;&gt;&lt;code&gt;SkRegion&lt;/code&gt;&lt;/a&gt; from the Skia graphics library to handle these unions efficiently.&lt;/p&gt;
&lt;p&gt;The following diagram shows this process in action over three frames.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/images/painted-regions.png&quot; alt=&quot;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.&quot; class=&quot;dark-invert&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;buffering-and-reporting&quot; tabindex=&quot;-1&quot;&gt;Buffering and Reporting &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &lt;strong&gt;candidates&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;PerformanceContainerTiming&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;This object contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startTime&lt;/code&gt;: 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 &lt;code&gt;presentationTime&lt;/code&gt; from &lt;code&gt;PaintTimingMixin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;firstRenderTime&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;The container element, in two ways. The &lt;code&gt;identifier&lt;/code&gt; is the value of the &lt;code&gt;containertiming&lt;/code&gt; attribute. &lt;code&gt;rootElement&lt;/code&gt; is the actual element.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;: The total area of the aggregated &lt;code&gt;PaintedRegion&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastPaintedElement&lt;/code&gt;: the last element that triggered a paint &#8212; handy for debugging which child caused the latest candidate.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;markdown-alert markdown-alert-note&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;Note&lt;/p&gt;&lt;p&gt;We support the &lt;a href=&quot;https://www.w3.org/TR/paint-timing/#the-paint-timing-mixin&quot;&gt;&lt;code&gt;PaintTimingMixin&lt;/code&gt;&lt;/a&gt;, which adds &lt;code&gt;paintTime&lt;/code&gt; (when the paint was committed to the compositor) and &lt;code&gt;presentationTime&lt;/code&gt; (when the frame was presented to the user). In Chromium, &lt;code&gt;startTime&lt;/code&gt; is set to &lt;code&gt;presentationTime&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;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 &lt;code&gt;size&lt;/code&gt; stops growing.&lt;/p&gt;
&lt;h2 id=&quot;handling-ignore&quot; tabindex=&quot;-1&quot;&gt;Handling &#8220;Ignore&#8221; &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We also implemented the &lt;code&gt;containertiming-ignore&lt;/code&gt; attribute. When a node has this attribute, it stops the &lt;code&gt;SelfOrAncestorHasContainerTiming&lt;/code&gt; 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 &lt;code&gt;PaintedRegion&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring&lt;/strong&gt; is useful for a number of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug overlays and instrumentation widgets, which should not inflate the measured painted area.&lt;/li&gt;
&lt;li&gt;Visually independent nested components: child dialogs or overlays that paint independently from the container and would affect the size metric if included.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;Tip&lt;/p&gt;&lt;p&gt;&lt;code&gt;containertiming-ignore&lt;/code&gt; on large untracked subtrees also reduces traversal depth, helping with the cost mentioned above.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;how-to-test&quot; tabindex=&quot;-1&quot;&gt;How to test &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With flag propagation, region aggregation, candidate buffering, and selective ignoring all in place, the implementation is complete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Container Timing&lt;/strong&gt; is &lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/FnM3lweVssM/m/eVhhCtG5AQAJ&quot;&gt;ready for test&lt;/a&gt; in Chromium. Just use the Blink feature flag &lt;code&gt;ContainerTiming&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;chrome --enable-blink-features&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ContainerTiming&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-s-next&quot; tabindex=&quot;-1&quot;&gt;What&#8217;s next? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We are preparing an Origin Trial in Chromium, a new step towards enabling Container Timing by default. Stay tuned!&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Support for detecting paints in other parts of the tree. Shadow DOM is specially interesting here due to its importance in web components.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Building this native implementation was a great exercise in reusing Blink&#8217;s existing performance infrastructure while extending it to support subtree-level aggregation.&lt;/p&gt;
&lt;p&gt;The key insight: subtree-level metrics didn&#8217;t require a new paint tracking system. Only a way to aggregate and bubble up what Blink was already measuring.&lt;/p&gt;
&lt;p&gt;The result is a native, low-overhead API for measuring the rendering performance of entire components.&lt;/p&gt;
&lt;h2 id=&quot;thanks&quot; tabindex=&quot;-1&quot;&gt;Thanks! &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This  has been done as part of the collaboration between &lt;a href=&quot;https://techatbloomberg.com&quot;&gt;Bloomberg&lt;/a&gt; and &lt;a href=&quot;https://www.igalia.com&quot;&gt;Igalia&lt;/a&gt;. Thanks!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.igalia.com&quot;&gt;
&lt;source media=&quot;(prefers-color-scheme: dark)&quot;&gt;
&lt;img src=&quot;https://blogs.igalia.com/dape/img/igalia_-_500px_-_RGB_-_Feb23-580x210.png&quot; alt=&quot;Igalia&quot; /&gt;
&lt;/source&gt;&lt;/a&gt; &lt;a href=&quot;https://techatbloomberg.com&quot;&gt;&lt;img src=&quot;https://blogs.igalia.com/dape/img/Bloomberg-logo-580x117.png&quot; alt=&quot;Bloomberg&quot; class=&quot;dark-invert&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Implementation of SelfOrAncestorHasContainerTiming: &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/ccebd1fb24b76dc2594e66b6fbad6c1192107405/third_party/blink/renderer/core/dom/node.h#1153&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/ccebd1fb24b76dc2594e66b6fbad6c1192107405/third_party/blink/renderer/core/html/html_element.cc#3777&quot;&gt;2&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/ccebd1fb24b76dc2594e66b6fbad6c1192107405/third_party/blink/renderer/core/paint/timing/container_timing.cc&quot;&gt;ContainerTiming aggregation at container_timing.cc&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Paint detection in &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/ccebd1fb24b76dc2594e66b6fbad6c1192107405/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc&quot;&gt;text&lt;/a&gt; and &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/ccebd1fb24b76dc2594e66b6fbad6c1192107405/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector.cc&quot;&gt;image&lt;/a&gt; paint timing detectors.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/container-timing/&quot;&gt;Specification draft @ WICG&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Related specifications: &lt;a href=&quot;https://w3c.github.io/element-timing/&quot;&gt;Element Timing&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/largest-contentful-paint/&quot;&gt;Largest Contentful Paint&lt;/a&gt;, &lt;a href=&quot;https://www.w3.org/TR/paint-timing/#the-paint-timing-mixin&quot;&gt;Paint Timing Mixin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/container-timing&quot;&gt;Explainer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/container-timing/issues/14&quot;&gt;Shadow DOM Handling discussion&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://issues.chromium.org/489959278&quot;&gt;Optimizing hierarchy traversals&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://bit.ly/lifeofapixel&quot;&gt;Life of a Pixel&lt;/a&gt; presentation about the Blink rendering pipeline by Steve Kobes.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Qiuyi Zhang (Joyee): Improving Single Executable Application Building for Node.js</title>
	<guid>https://joyeecheung.github.io/blog/2026/01/26/improving-single-executable-application-building-for-node-js/</guid>
	<link>https://joyeecheung.github.io/blog/2026/01/26/improving-single-executable-application-building-for-node-js/</link>
	<description>
&lt;p&gt;Recently, I &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/nodejs/node/pull/61167&quot;&gt;landed a change that moves the Single Executable Application (SEA) build process directly into Node.js core&lt;/a&gt; - a hobby project I&#8217;d been tinkering with for some time&lt;/p&gt;        </description>
	<pubDate>Tue, 24 Mar 2026 13:15:57 +0000</pubDate>
</item>
<item>
	<title>Javier Fern&#225;ndez: Protocol Handler Registration via Browser Extensions</title>
	<guid>https://blogs.igalia.com/jfernandez/?p=1961</guid>
	<link>https://blogs.igalia.com/jfernandez/2026/03/24/protocol-handler-registration-via-browser-extensions/?pk_campaign=feed&amp;pk_kwd=protocol-handler-registration-via-browser-extensions</link>
	<description>
&lt;h2 class=&quot;wp-block-heading&quot;&gt;Motivation&lt;/h2&gt;



&lt;p&gt;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 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers&quot;&gt;Custom Scheme Handlers&lt;/a&gt; API, which enables websites to register themselves as handlers for specific URL schemes.&lt;/p&gt;



&lt;p&gt;While the Web API is appropriate for origin-scoped integrations, its security model imposes several structural constraints:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Registration must be initiated from a visited website.&lt;/li&gt;



&lt;li&gt;It requires user activation.&lt;/li&gt;



&lt;li&gt;The handler URL must share the same origin as the registering site.&lt;/li&gt;



&lt;li&gt;Each registration is processed individually and requires explicit user approval.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;In collaboration with the &lt;a href=&quot;https://openimpact.foundation/&quot;&gt;Open Impact Foundation&amp;#8217;s IPFS Implementations grants program&lt;/a&gt;, &lt;a href=&quot;http://igalia.com&quot;&gt;Igalia&lt;/a&gt; has implemented support for declaring protocol handlers directly in the Web Extension Manifest for Chromium-based browsers &amp;#8211; &lt;strong&gt;achieving interoperability with Firefox&lt;/strong&gt;. The goal is to make protocol registration a first-class extension capability, while preserving the security invariants established by the HTML Standard.&lt;/p&gt;



&lt;p&gt;The proposal was &lt;a href=&quot;https://github.com/w3c/webextensions/issues/365&quot;&gt;discussed&lt;/a&gt; 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.&lt;/p&gt;



&lt;p&gt;This article introduces the motivation behind the feature, explains the design decisions that shaped it, and describes its internal security and lifecycle model.&lt;/p&gt;



&lt;p&gt;The feature has been shipped behind an experimental flag in &lt;strong&gt;Chrome 146&lt;/strong&gt;. To test it, just launch Chrome from the command line with this option:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;--enable-features=ExtensionProtocolHandlers&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Case study: IPFS Companion&lt;/h2&gt;



&lt;p&gt;IPFS introduces schemes such as ipfs://, backed by a content-addressed data model rather than traditional origin-based addressing. In Chromium&#8217;s previous extension model, &lt;a href=&quot;https://chromewebstore.google.com/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch&quot;&gt;IPFS Companion&lt;/a&gt; must request &lt;em&gt;declarativeNetRequest&lt;/em&gt;, &lt;em&gt;webRequest&lt;/em&gt;, &lt;em&gt;webNavigation&lt;/em&gt;, and &lt;em&gt;&amp;lt;all_urls&amp;gt;&lt;/em&gt; host permissions &amp;#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 &amp;#8220;Read and change all your data on all websites&amp;#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&amp;#8217;s reputation alone.&lt;/p&gt;



&lt;p&gt;These broad permissions also create &lt;strong&gt;friction with the Chrome Web Store review&lt;/strong&gt; process. Extensions requesting &lt;em&gt;webRequest&lt;/em&gt; 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.&lt;/p&gt;



&lt;p&gt;In the absence of a native mechanism, IPFS Companion resorts to detecting when the browser converts an unrecognized &lt;em&gt;ipfs://&lt;/em&gt; URL into a search engine query, then intercepting and redirecting that query. This works, but it &lt;strong&gt;depends on browser-specific URL encoding&lt;/strong&gt; behavior, breaks silently when search providers change their format, and may not work on all platforms due to security software interfering with such hijacking.&lt;/p&gt;



&lt;p&gt;With manifest-declared protocol handlers, the extension can register &lt;em&gt;IPFS&lt;/em&gt; directly. Navigation dispatch becomes declarative rather than interceptive. The permission model narrows, the architecture simplifies, and the integration aligns with the browser&#8217;s native routing mechanisms. These would be an example of the Extension Manifest:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;  &quot;protocol_handlers&quot;: &amp;#91;
    {
      &quot;protocol&quot;: &quot;ipns&quot;,
      &quot;name&quot;: &quot;IPFS Companion: IPNS Protocol Handler&quot;,
      &quot;uriTemplate&quot;: &quot;https://dweb.link/ipns/?uri=%s&quot;
    },
    {
      &quot;protocol&quot;: &quot;ipfs&quot;,
      &quot;name&quot;: &quot;IPFS Companion: IPFS Protocol Handler&quot;,
      &quot;uriTemplate&quot;: &quot;https://dweb.link/ipfs/?uri=%s&quot;
    }
  ]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Broader integration scenarios&lt;/h3&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;  &quot;protocol_handlers&quot;: &amp;#91;
    {
      &quot;protocol&quot;: &quot;irc&quot;,
      &quot;name&quot;: &quot;Corporate IRC client&quot;,
      &quot;uriTemplate&quot;: &quot;https://mycompany.com/irc/?params=%s&quot;
    },
    {
      &quot;protocol&quot;: &quot;mailto&quot;,
      &quot;name&quot;: &quot;Corporate Email client&quot;,
      &quot;uriTemplate&quot;: &quot;https://mycompany.com/webmail/?params=%s&quot;
    },
    {
      &quot;protocol&quot;: &quot;webcal&quot;,
      &quot;name&quot;: &quot;Corporate Calendar client&quot;,
      &quot;uriTemplate&quot;: &quot;https://mycompany.com/calendar/?params=%s&quot;
    },
    {
      &quot;protocol&quot;: &quot;web+plan&quot;,
      &quot;name&quot;: &quot;Corporate Planning client&quot;,
      &quot;uriTemplate&quot;: &quot;https://mycompany.com/planning/?params=%s&quot;
    },&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This establishes a structured integration surface between browser navigation and external systems while maintaining explicit user control and security guarantees.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;The limits of existing mechanisms&lt;/h2&gt;



&lt;p&gt;The HTML Standard&#8217;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler&quot;&gt;navigator.registerProtocolHandler()&lt;/a&gt; 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.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;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 &lt;em&gt;declarativeNetRequest&lt;/em&gt;. 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.&lt;/p&gt;



&lt;p&gt;The absence of manifest-declared protocol handlers in Chromium created a gap between the capabilities of extensions and the needs of advanced integration scenarios.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;A step forward: PWAs as &#8220;URL handlers&#8221;&lt;/h3&gt;



&lt;p&gt;Progressive Web Apps provided a partial evolution of the model by allowing protocol handlers to be declared &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/protocol_handlers&quot;&gt;via the Web App Manifest&lt;/a&gt;. This &lt;a href=&quot;https://blogs.windows.com/msedgedev/2022/01/20/getting-started-url-protocol-handlers-microsoft-edge&quot;&gt;improved declarative configuration&lt;/a&gt;, but remained tightly coupled to the application&#8217;s origin and lifecycle. It did not address scenarios where the integration logic belongs to an extension rather than a web application.&lt;/p&gt;



&lt;p&gt;Back in 2020, Chrome started prototyping a feature called &lt;a href=&quot;https://developer.chrome.com/docs/capabilities/pwa-url-handler&quot;&gt;PWAs as URL Handlers&lt;/a&gt;, allowing apps to register themselves as handlers for URLs matching a certain pattern. This feature has been abandoned in favor of &lt;a href=&quot;https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md&quot;&gt;Scoped Extensions for Web App Manifest&lt;/a&gt;, which precisely allows web apps to overcome some of the challenges that the same-origin policy imposes on this type of site architecture.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Manifest-declared protocol handlers&lt;/h2&gt;



&lt;p&gt;As a result of our work, Chromium now supports the &lt;code&gt;protocol_handlers&lt;/code&gt; key directly in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers&quot;&gt;Web Extension Manifest&lt;/a&gt;. This feature aligns protocol registration with the extension lifecycle instead of the web-origin lifecycle.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;&quot;protocol_handlers&quot;: &amp;#91;
  {
    &quot;protocol&quot;: &quot;ircs&quot;,
    &quot;name&quot;: &quot;IRC Mozilla Extension&quot;,
    &quot;uriTemplate&quot;: &quot;https://irccloud.mozilla.com/#!/%s&quot;
  }
]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;This design introduces several important properties:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Registration is declarative and tied to the extension artifact.&lt;/li&gt;



&lt;li&gt;Validation enforces HTML Standard constraints at parse time.&lt;/li&gt;



&lt;li&gt;Activation requires runtime user consent.&lt;/li&gt;



&lt;li&gt;Disabling or uninstalling the extension automatically removes its handlers.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;By shifting protocol registration into the manifest, the browser gains a clearer separation between declaration, validation, and activation.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Security Model and Validation&lt;/h2&gt;



&lt;p&gt;Because protocol handlers influence navigation routing, the feature inherits strict &lt;a href=&quot;https://html.spec.whatwg.org/multipage/system-state.html#normalize-protocol-handler-parameters&quot;&gt;validation rules&lt;/a&gt; from the HTML Standard. During manifest parsing, the browser verifies that declared schemes belong to a predefined &lt;a href=&quot;https://html.spec.whatwg.org/multipage/system-state.html#safelisted-scheme&quot;&gt;safe list&lt;/a&gt; and that handler URLs use HTTP or HTTPS.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt; &quot;bitcoin&quot;, &quot;cabal&quot;,  &quot;dat&quot;,    &quot;did&quot;,  &quot;doi&quot;,  &quot;dweb&quot;, &quot;ethereum&quot;,
 &quot;geo&quot;,     &quot;hyper&quot;,  &quot;im&quot;,     &quot;ipfs&quot;, &quot;ipns&quot;, &quot;irc&quot;,  &quot;ircs&quot;,
 &quot;magnet&quot;,  &quot;mailto&quot;, &quot;matrix&quot;, &quot;mms&quot;,  &quot;news&quot;, &quot;nntp&quot;, &quot;openpgp4fpr&quot;,
 &quot;sip&quot;,     &quot;sms&quot;,    &quot;smsto&quot;,  &quot;ssb&quot;,  &quot;ssh&quot;,  &quot;tel&quot;,  &quot;urn&quot;,
 &quot;webcal&quot;,  &quot;wtai&quot;,   &quot;xmpp&quot;&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Given that the same-origin requirement is relaxed in this model, we need to validate explicitly that the target handler operates in a &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#secure-context&quot;&gt;secure context&lt;/a&gt;. This ensures that the user doesn&amp;#8217;t leave a &lt;a href=&quot;https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin&quot;&gt;trustworthy origin&lt;/a&gt; due to the redirection performed by the protocol handler.&lt;/p&gt;



&lt;p&gt;The Web API model imposes a requirement of a mandatory &lt;a href=&quot;https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation&quot;&gt;User Activation&lt;/a&gt; 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.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;figure class=&quot;wp-block-image size-large&quot;&gt;&lt;a href=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/2ec5495e-2b57-4eec-b478-ffcdea00e5f4.png&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;427&quot; src=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/2ec5495e-2b57-4eec-b478-ffcdea00e5f4-1024x427.png&quot; alt=&quot;&quot; class=&quot;wp-image-1966&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;The resulting model separates concerns:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Installation authorizes registration.&lt;/li&gt;



&lt;li&gt;Runtime approval authorizes use.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;This layered approach preserves the security intent of the HTML model while adapting it to the extension trust boundary.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Runtime permission flow&lt;/h3&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;figure class=&quot;wp-block-image size-large&quot;&gt;&lt;a href=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/85d4f18b-6f6b-4fd2-befd-a52bca18fe13.png&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;666&quot; src=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/85d4f18b-6f6b-4fd2-befd-a52bca18fe13-1024x666.png&quot; alt=&quot;&quot; class=&quot;wp-image-1967&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;figure class=&quot;wp-block-image size-full&quot;&gt;&lt;a href=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/fa531859-3d0f-4387-9a6a-418f47db1afc.png&quot;&gt;&lt;img width=&quot;978&quot; height=&quot;516&quot; src=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/fa531859-3d0f-4387-9a6a-418f47db1afc.png&quot; alt=&quot;&quot; class=&quot;wp-image-1968&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Cross-origin considerations&lt;/h3&gt;



&lt;p&gt;The same-origin requirement in the HTML Standard&#8217;s 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.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;For example, consider decentralized protocols such as IPFS. &lt;a href=&quot;https://docs.ipfs.tech/how-to/ipfs-in-web-apps/#addressing-data-by-cid&quot;&gt;Content addressing&lt;/a&gt; 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.&lt;/p&gt;



&lt;p&gt;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:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Extension &lt;a href=&quot;https://developer.chrome.com/docs/webstore/review-process&quot;&gt;store review&lt;/a&gt; and distribution controls.&lt;/li&gt;



&lt;li&gt;Explicit consent during the installation.&lt;/li&gt;



&lt;li&gt;Manifest-declared capabilities.&lt;/li&gt;



&lt;li&gt;Runtime approval before handler activation.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;p&gt;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&#8217;s data. Standard web security boundaries&#8212;such as the Same-Origin Policy and CORS&#8212;remain fully enforced after navigation completes.&lt;/p&gt;



&lt;p&gt;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 &#8220;origin that called the API&#8221; to &#8220;extension the user chose to install,&#8221; but the system continues to require explicit consent by the user before navigation control is delegated.&lt;/p&gt;



&lt;figure class=&quot;wp-block-image size-large&quot;&gt;&lt;a href=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/31849cd2-7376-4452-95df-81065967d63f.png&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;899&quot; src=&quot;https://blogs.igalia.com/jfernandez/files/2026/03/31849cd2-7376-4452-95df-81065967d63f-1024x899.png&quot; alt=&quot;&quot; class=&quot;wp-image-1970&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Conflict resolution across registration mechanisms&lt;/h2&gt;



&lt;p&gt;With protocol handlers now registrable through multiple mechanisms&#8212;the Web API, PWA manifests, and extension manifests&#8212;conflict resolution becomes necessary. The implementation preserves backward compatibility by prioritizing Web API registrations. If a handler has been registered via &lt;em&gt;navigator.registerProtocolHandler()&lt;/em&gt;, 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.&lt;/p&gt;



&lt;p&gt;This deterministic ordering ensures predictable behavior and avoids ambiguity when multiple registration surfaces coexist.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Why this feature matters&lt;/h2&gt;



&lt;p&gt;Adding manifest-declared protocol handlers to Chromium closes a &lt;strong&gt;long-standing capability gap&lt;/strong&gt; 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.&lt;/p&gt;



&lt;p&gt;Manifest-declared protocol_handlers replace all of this with a single, narrowly scoped declaration. The &lt;strong&gt;permissions surface shrink&lt;/strong&gt; from &#8220;read and change all your data on all websites&amp;#8221; to a runtime prompt scoped to the specific protocol: &amp;#8220;Allow this extension to open IPFS links through dweb.link&amp;#8221;.&lt;/p&gt;



&lt;p&gt;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 &lt;strong&gt;modern runtime permission patterns&lt;/strong&gt;, and provides deterministic conflict resolution across registration surfaces. Store reviewers can verify the declared intent directly in the manifest without auditing request interception logic.&lt;/p&gt;



&lt;p&gt;For &lt;strong&gt;browser engineers&lt;/strong&gt;, the feature introduces a cleaner architectural boundary between navigation routing and network interception. For &lt;strong&gt;web authors&lt;/strong&gt; building advanced integrations, it enables robust, declarative protocol handling without relying on brittle implementation techniques. For &lt;strong&gt;extension developers&lt;/strong&gt;, it means protocol handling can finally be expressed as what it is (a navigation capability) rather than being disguised as request rewriting.&lt;/p&gt;



&lt;p&gt;With the Web Extensions CG moving toward &lt;a href=&quot;https://w3c.github.io/charter-drafts/2025/webextensions-wg.html&quot;&gt;WG status&lt;/a&gt;, this is a good opportunity to advance the standardization of the protocol_handlers key by proposing its inclusion in the &lt;a href=&quot;https://w3c.github.io/webextensions/specification/index.html#manifest-keys&quot;&gt;Manifest Keys section&lt;/a&gt; of the Draft Community Group Report.&lt;/p&gt;
&lt;img src=&quot;https://stats.igalia.com/piwik.php?idsite=41&amp;rec=1&amp;url=https%3A%2F%2Fblogs.igalia.com%2Fjfernandez%2F2026%2F03%2F24%2Fprotocol-handler-registration-via-browser-extensions%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Dprotocol-handler-registration-via-browser-extensions&amp;action_name=Protocol%20Handler%20Registration%20via%20Browser%20Extensions&amp;urlref=https%3A%2F%2Fblogs.igalia.com%2Fjfernandez%2Ffeed%2F&quot; width=&quot;0&quot; height=&quot;0&quot; alt=&quot;&quot; /&gt;        </description>
	<pubDate>Tue, 24 Mar 2026 11:41:45 +0000</pubDate>
	<dc:creator>jfernandez</dc:creator>
</item>
<item>
	<title>Sim&#243;n Pena: Getting started with WPE WebKit: a minimal launcher</title>
	<guid>https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit</guid>
	<link>https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit/</link>
	<description>
&lt;p&gt;My colleague Kate recently &lt;a href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;demonstrated on her blog&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href=&quot;https://www.yoctoproject.org/&quot;&gt;Yocto/OpenEmbedded-based image&lt;/a&gt;, but that can be the focus of another post.&lt;/p&gt;

&lt;h2 id=&quot;getting-wpe-webkit&quot;&gt;Getting WPE WebKit&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://wpewebkit.org/about/get-wpe.html&quot;&gt;Get WPE&lt;/a&gt; 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:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2.52 on Fedora&lt;/li&gt;
  &lt;li&gt;2.50 on Debian Forky, 2.52 on Debian Sid&lt;/li&gt;
  &lt;li&gt;2.50 on ArchLinux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, since WPE Platform hasn&#8217;t officially been released, we need to use Fedora, where my colleague &lt;a href=&quot;https://www.igalia.com/team/pnormand&quot;&gt;Philippe&lt;/a&gt; maintains a Copr repository with it enabled.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf copr &lt;span class=&quot;nb&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; philn/wpewebkit
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;wpewebkit-devel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alternatively, you can use a container. Here is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Containerfile&lt;/code&gt; based on Fedora 42:&lt;/p&gt;

&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; fedora:42&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    dnf-plugins-core &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dnf copr &lt;span class=&quot;nb&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; philn/wpewebkit &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dnf &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    gcc-c++ &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    cmake &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    pkg-config &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    wpewebkit-devel

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /src&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Build and run it with:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;podman build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; wpe-dev &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;WAYLAND_DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$WAYLAND_DISPLAY&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;XDG_RUNTIME_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/run/user/&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$XDG_RUNTIME_DIR&lt;/span&gt;/&lt;span class=&quot;nv&quot;&gt;$WAYLAND_DISPLAY&lt;/span&gt;:/run/user/&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;/&lt;span class=&quot;nv&quot;&gt;$WAYLAND_DISPLAY&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; /dev/dri:/dev/dri &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
wpe-dev bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-build-system&quot;&gt;The build system&lt;/h2&gt;

&lt;p&gt;Kate&#8217;s post builds the launcher as part of the WebKit tree using WebKit&#8217;s own CMake infrastructure. For a standalone project, we need a self-contained &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMakeLists.txt&lt;/code&gt; that finds WPE WebKit through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pkg-config&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-cmake highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cmake_minimum_required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;VERSION 3.16&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;wpe_sample CXX&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;CMAKE_CXX_STANDARD 17&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;find_package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;PkgConfig REQUIRED&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The Wayland WPE Platform already depends on wpe-platform-2.0&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;pkg_check_modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;WebKitDeps REQUIRED
    IMPORTED_TARGET
    wpe-webkit-2.0
    wpe-platform-wayland-2.0
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;add_executable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;wpe_sample main.cpp&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;target_link_libraries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;wpe_sample
    PRIVATE
        PkgConfig::WebKitDeps
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-launcher&quot;&gt;The launcher&lt;/h2&gt;

&lt;p&gt;Here is a minimal launcher &#8212; the smallest amount of code needed to display a web page with WPE WebKit:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;wpe/webkit.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g_autoptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GMainLoop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_main_loop_new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g_autoptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebKitWebView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WEBKIT_WEB_VIEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g_object_new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WEBKIT_TYPE_WEB_VIEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;webkit_web_view_load_uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://wpewebkit.org&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g_main_loop_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EXIT_SUCCESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;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&#8230;&lt;/p&gt;

&lt;p&gt;Again, &lt;a href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;Kate&#8217;s post&lt;/a&gt; does a more realistic job at showing how the various pieces are created and connected together.&lt;/p&gt;

&lt;h2 id=&quot;building-and-running&quot;&gt;Building and running&lt;/h2&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cmake &lt;span class=&quot;nt&quot;&gt;-B&lt;/span&gt; build
cmake &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; build
./build/wpe_sample https://wpewebkit.org/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://simonpena.com/assets/images/wpe-webkit-sample-screenshot.png&quot; alt=&quot;WPE WebKit minimal launcher&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;display-backends&quot;&gt;Display backends&lt;/h2&gt;

&lt;p&gt;WPE WebKit can render to different display backends depending on your environment, which you can select through environment variables:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Wayland (e.g. desktop, Weston).&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;WPE_DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;wpe-display-wayland &lt;span class=&quot;nv&quot;&gt;WAYLAND_DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;wayland-1 ./build/wpe_sample https://wpewebkit.org/

&lt;span class=&quot;c&quot;&gt;# DRM/KMS (e.g. embedded, no compositor)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;WPE_DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;wpe-display-drm ./build/wpe_sample https://wpewebkit.org/

&lt;span class=&quot;c&quot;&gt;# Headless (e.g. testing, CI)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;WPE_DISPLAY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;wpe-display-headless ./build/wpe_sample https://wpewebkit.org/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can take a look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpe_display_get_default()&lt;/code&gt; in &lt;a href=&quot;https://github.com/WebKit/WebKit/blob/wpewebkit-2.52.0/Source/WebKit/WPEPlatform/wpe/WPEDisplay.cpp&quot;&gt;WPEPlatform/wpe/WPEDisplay.cpp&lt;/a&gt; to understand how the automatic selection takes place in the absence of an explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WPE_DISPLAY&lt;/code&gt; request.&lt;/p&gt;

&lt;p&gt;(In our example, we are only listing Wayland as a CMake dependency. If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libwpewebkit&lt;/code&gt; was compiled without DRM or headless support, the environment variable approach would not work.)&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For a more complete example that includes a custom HTML context menu and JavaScript injection, see &lt;a href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;Kate&#8217;s post&lt;/a&gt;.&lt;/p&gt;        </description>
	<pubDate>Fri, 20 Mar 2026 00:00:00 +0000</pubDate>
	<dc:creator>Sim&#243;n</dc:creator>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #60</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-60/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-60/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from March 10 to March 18.&lt;/p&gt;
&lt;p&gt;
The big ticket item in this week's update are the 2.52.0 releases, which
include the work from the last six-month development period, and come with
a security advisory. Meanwhile, WPE-Android also gets a release, and a number
of featured blog posts.
&lt;/p&gt;
&lt;h2 id=&quot;wpe-webkit-pager&quot;&gt;WPE WebKit &#128223;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Last week we &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/309047@main&quot;&gt;added support&lt;/a&gt; to WPE
MiniBrowser to load &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/2.52.0/wpe-webkit-2.0/class.Settings.html&quot;&gt;settings&lt;/a&gt; from a key file. This extended the existing
&lt;code&gt;--config-file=FILE&lt;/code&gt; feature, which previously only loaded WPEPlatform
settings under the &lt;code&gt;[wpe-platform]&lt;/code&gt; group. Now the feature uses
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/2.52.0/wpe-webkit-2.0/method.Settings.apply_from_key_file.html&quot;&gt;webkit_settings_apply_from_key_file()&lt;/a&gt;
to load properties such as &lt;code&gt;user-agent&lt;/code&gt; or &lt;code&gt;enable-developer-extras&lt;/code&gt;
from the &lt;code&gt;[websettings]&lt;/code&gt; group as well.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/03/18/webkitgtk2.52.0-released.html&quot;&gt;WebKitGTK
2.52.0&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.0.html&quot;&gt;WPE WebKit 2.52.0&lt;/a&gt; are
now available. These include the results of the effort made by the team during
the last six months, including rendering improvements and performance
optimizations, better security for WebRTC, a more complete WebXR
implementation, and a second preview of the WPEPlatform API for the WPE
port&#8212;among many other changes.&lt;/p&gt;
&lt;p&gt;More information about the changes and improvements brought by these major
releases can be found at the &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/03/18/webkitgtk-2.52-highlights.html&quot;&gt;blog post about WebKitGTK
2.52&lt;/a&gt;, and the
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/blog/2026-03-18-wpewebkit-2.52.html&quot;&gt;corresponding one for WPE WebKit
2.52&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Accompanying these releases there is security advisory &lt;code&gt;WSA-2026-0001&lt;/code&gt;
(&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/security/WSA-2026-0001.html&quot;&gt;GTK&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/security/WSA-2026-0001.html&quot;&gt;WPE&lt;/a&gt;), with information
about solved security issues. As usual, we encourage everybody to use the most
recent versions where such issues are known to be fixed.&lt;/p&gt;
&lt;p&gt;Bug reports are always welcome &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/&quot;&gt;at the WebKit
Bugzilla&lt;/a&gt;.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/wpe-android/releases/tag/v0.3.3&quot;&gt;WPE Android 0.3.3&lt;/a&gt; has been released, and prebuilt packages are available &lt;a rel=&quot;external&quot; href=&quot;https://central.sonatype.com/artifact/org.wpewebkit.wpeview/wpeview/&quot;&gt;at the Maven Central repository&lt;/a&gt;. 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.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;community-events-handshake&quot;&gt;Community &amp;amp; Events &#129309;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Kate Lee wrote a &lt;a rel=&quot;external&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;very interesting blog
post&lt;/a&gt;
showing how to create a small application using the WPEPlatform API to
demonstrate one of its newly available features: the Context Menu API. It is
rendered entirely as an HTML overlay, enabling richer and more portable context
menu implementations.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;WebXR support for WebKitGTK and WPE has been reworked and aligned with the
modern multi-process architecture, using OpenXR to enable XR device integration
on Linux and Android. Sergio Villar &lt;a rel=&quot;external&quot; href=&quot;https://blogs.igalia.com/svillar/post/webkitgtk-wpe-webxr/&quot;&gt;wrote a blog post that explains all the
work done&lt;/a&gt; in the
last months around it.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Wed, 18 Mar 2026 19:46:56 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Emmanuele Bassi: Let&#8217;s talk about&#160;Moonforge</title>
	<guid>tag:www.bassi.io,2026-03-17:/articles/2026/03/17/lets-talk-about-moonforge/</guid>
	<link>https://www.bassi.io/articles/2026/03/17/lets-talk-about-moonforge/</link>
	<description>
&lt;p&gt;Last week, Igalia finally &lt;a href=&quot;https://www.igalia.com/2026/03/09/Introducing-Moonforge-A-Yocto-Based-Linux-OS.html&quot;&gt;announced Moonforge&lt;/a&gt;, a project we&amp;#8217;ve been working on for basically all of 2025. It&amp;#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&amp;nbsp;constraints.&lt;/p&gt;
&lt;p&gt;Of course, as soon as somebody announces a new Linux-based &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt;, folks immediately think it&amp;#8217;s a new general purpose Linux distribution, as that&amp;#8217;s the &lt;a href=&quot;https://www.youtube.com/watch?v=cUbIkNUFs-4&quot;&gt;square shaped hole&lt;/a&gt; where everything &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt;-related ends up. So, first things first, let&amp;#8217;s get a couple of things out of the way about &lt;a href=&quot;https://moonforgelinux.org&quot;&gt;Moonforge&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Moonforge is &lt;strong&gt;not&lt;/strong&gt; a general purpose Linux&amp;nbsp;distribution&lt;/li&gt;
&lt;li&gt;Moonforge is &lt;strong&gt;not&lt;/strong&gt; an embedded Linux&amp;nbsp;distribution&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What is&amp;nbsp;Moonforge&lt;/h3&gt;
&lt;p&gt;Moonforge is a set of feature-based, well-maintained layers for &lt;a href=&quot;https://yoctoproject.org&quot;&gt;Yocto&lt;/a&gt;, that allows you to assemble your own &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; for embedded devices, or single-application environments, with specific emphasys on immutable, read-only root file system &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; images that are easy to deploy and update, through tight integration with &lt;span class=&quot;caps&quot;&gt;CI&lt;/span&gt;/&lt;span class=&quot;caps&quot;&gt;CD&lt;/span&gt;&amp;nbsp;pipelines.&lt;/p&gt;
&lt;h3&gt;Why?&lt;/h3&gt;
&lt;p&gt;Creating a whole new &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; 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 &lt;a href=&quot;https://store.steampowered.com/steamos/&quot;&gt;get away&lt;/a&gt; with using existing Linux distributions, filing off the serial numbers, and removing any extant packaging mechanism; or you can rely on the &lt;a href=&quot;https://universal-blue.org/&quot;&gt;containerised tech stack&lt;/a&gt;, and boot into&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;When it comes to embedded platforms, on the other hand, you&amp;#8217;re still very much working on bespoke, artisanal, locally sourced, organic operating systems. A good number of device manufacturers coalesced their &lt;a href=&quot;https://en.wikipedia.org/wiki/Board_support_package&quot;&gt;BSPs&lt;/a&gt; around the &lt;a href=&quot;https://www.yoctoproject.org/&quot;&gt;Yocto Project&lt;/a&gt; and &lt;a href=&quot;https://www.openembedded.org/wiki/Main_Page&quot;&gt;OpenEmbedded&lt;/a&gt;, which simplifies adaptations, but you&amp;#8217;re still supposed to build the thing mostly as a one&amp;nbsp;off.&lt;/p&gt;
&lt;p&gt;While Yocto has improved leaps and bounds over the past 15 years, putting together an &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; image, especially when it comes to bundling features while keeping the overall size of the base image down, is still an exercise in artisanal&amp;nbsp;knowledge.&lt;/p&gt;
&lt;h3&gt;A little detour:&amp;nbsp;Poky&lt;/h3&gt;
&lt;p&gt;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 &amp;#8220;reference distribution&amp;#8221; that would help people getting started with their own project. That reference was called &lt;a href=&quot;https://web.archive.org/web/20070402231645/http://projects.o-hand.com/poky&quot;&gt;Poky&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
        &lt;figcaption class=&quot;image-caption&quot;&gt;
          &lt;p&gt;We had a beaver mascot before it was&amp;nbsp;cool&lt;/p&gt;
        &lt;/figcaption&gt;
        &lt;div&gt;&lt;img src=&quot;https://www.bassi.io/images/poky-beaver.jpeg&quot; /&gt;&lt;/div&gt;
      &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;These days, Poky exists as part of the Yocto Project, and it&amp;#8217;s still the reference distribution for it, but since it&amp;#8217;s part of Yocto, it has to abide to the basic constraint of the project: you still need to set up your &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; using shell scripts and copy-pasting layers and recipes inside your own repository. The Yocto project is working on &lt;a href=&quot;https://github.com/kanavin/bitbake/commits/akanavin/bitbake-setup&quot;&gt;a setup tool&lt;/a&gt; to
simplify those steps, but there are&amp;nbsp;alternatives&#8230;&lt;/p&gt;
&lt;h3&gt;Another little detour:&amp;nbsp;Kas&lt;/h3&gt;
&lt;p&gt;One alternative is &lt;a href=&quot;https://kas.readthedocs.io/en/latest/&quot;&gt;kas&lt;/a&gt;, a tool that allows you to generate the &lt;code&gt;local.conf&lt;/code&gt; configuration file used by bitbake through various &lt;span class=&quot;caps&quot;&gt;YAML&lt;/span&gt; fragments exported by each layer you&amp;#8217;re interested in, as well as additional fragments that can be used to set up customised&amp;nbsp;environments.&lt;/p&gt;
&lt;p&gt;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 &lt;span class=&quot;caps&quot;&gt;CI&lt;/span&gt;/&lt;span class=&quot;caps&quot;&gt;CD&lt;/span&gt; pipelines that already rely on&amp;nbsp;containers.&lt;/p&gt;
&lt;h3&gt;What Moonforge&amp;nbsp;provides&lt;/h3&gt;
&lt;p&gt;Moonforge lets you create a new &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; in minutes, selecting a series of features you care about from various &lt;a href=&quot;https://moonforgelinux.org/docs/layers/&quot;&gt;available layers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each layer provides a single feature,&amp;nbsp;like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;support for a specific architecture or device (&lt;span class=&quot;caps&quot;&gt;QEMU&lt;/span&gt; x86_64,&amp;nbsp;RaspberryPi)&lt;/li&gt;
&lt;li&gt;containerisation (through Docker or&amp;nbsp;Podman)&lt;/li&gt;
&lt;li&gt;A/B updates (through &lt;span class=&quot;caps&quot;&gt;RAUC&lt;/span&gt;, systemd-sysupdate, and&amp;nbsp;more)&lt;/li&gt;
&lt;li&gt;graphical session, using&amp;nbsp;Weston&lt;/li&gt;
&lt;li&gt;a &lt;a href=&quot;https://webkit.org/wpe/&quot;&gt;&lt;span class=&quot;caps&quot;&gt;WPE&lt;/span&gt;&lt;/a&gt;&amp;nbsp;environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every layer comes with its own kas fragment, which describes what the layer needs to add to the project configuration in order to&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;Since every layer is isolated, we can reason about their dependencies and interactions, and we can combine them into a final, custom&amp;nbsp;product.&lt;/p&gt;
&lt;p&gt;Through various tools, including kas, we can set up a Moonforge project that generates and validates &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; images as the result of a &lt;span class=&quot;caps&quot;&gt;CI&lt;/span&gt;/&lt;span class=&quot;caps&quot;&gt;CD&lt;/span&gt; pipeline on platforms like GitLab, GitHub, and BitBucket; &lt;span class=&quot;caps&quot;&gt;OS&lt;/span&gt; updates are also generated as part of that pipeline, just as comprehensive &lt;span class=&quot;caps&quot;&gt;CVE&lt;/span&gt; reports and Software Bill of Materials (&lt;span class=&quot;caps&quot;&gt;SBOM&lt;/span&gt;) through custom Yocto&amp;nbsp;recipes.&lt;/p&gt;
&lt;p&gt;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&amp;nbsp;board.&lt;/p&gt;
&lt;p&gt;While this is the beginning of the project, it&amp;#8217;s already fairly usable; we are planning a lot more in this space, so keep an eye out on &lt;a href=&quot;https://github.com/moonforgelinux&quot;&gt;the repository&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Trying Moonforge&amp;nbsp;out&lt;/h3&gt;
&lt;p&gt;If you want to check out Moonforge, I will point you in the direction of its &lt;a href=&quot;https://moonforgelinux.org/docs/tutorials/&quot;&gt;tutorials&lt;/a&gt;, as well as the &lt;a href=&quot;https://github.com/moonforgelinux/meta-derivative/&quot;&gt;meta-derivative&lt;/a&gt; repository, which should give you a good overview on how Moonforge works, and how you can use&amp;nbsp;it.&lt;/p&gt;        </description>
	<pubDate>Tue, 17 Mar 2026 17:44:31 +0000</pubDate>
	<dc:creator>ebassi</dc:creator>
</item>
<item>
	<title>Sergio Villar: Implementing WebXR in WebKit for WPE</title>
	<guid>https://blogs.igalia.com/svillar/post/wpe-webxr/</guid>
	<link>https://blogs.igalia.com/svillar/post/wpe-webxr/</link>
	<description>
        &lt;img class=&quot;face&quot; src=&quot;/images/svillar.png&quot; width=&quot;103&quot; height=&quot;109&quot; alt=&quot;&quot; align=&quot;right&quot; style=&quot;float: right&quot; /&gt;
&lt;p&gt;Since 2022, my main focus has been working on the &lt;a href=&quot;https://wolvic.com&quot;&gt;Wolvic browser&lt;/a&gt;, still the only open source WebXR-capable browser for Android/AOSP devices (Meta, Pico, Huawei, Lenovo, Lynx, HTC&amp;hellip;) out there. That&amp;rsquo;s an effort that continues to this day (although to a &lt;a href=&quot;https://wolvic.com/blog/next-steps/&quot;&gt;much lesser extent nowadays&lt;/a&gt;). 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.&lt;/p&gt;        </description>
	<pubDate>Tue, 17 Mar 2026 08:46:59 +0000</pubDate>
</item>
<item>
	<title>Ricardo Ca&#241;uelo Navarro: Why don't we do a demo? Part 1: the plan</title>
	<guid>https://blogs.igalia.com/rcn/posts/20260317-why_dont_we_do_a_demo_part_1/index.html</guid>
	<link>https://blogs.igalia.com/rcn/posts/20260317-why_dont_we_do_a_demo_part_1/index.html</link>
	<description>
&lt;h3&gt;Introduction&lt;/h3&gt;

        &lt;p&gt;&lt;a href=&quot;https://blogs.igalia.com/rcn/posts/20250807-first_steps_with_zephyr/index.html&quot;&gt;Some
        time ago&lt;/a&gt;, I saw myself with some extra time in my hands and
        I started experimenting with Zephyr as a way to reconnect with
        my professional past and also to see how embedded software looks
        like nowadays.
        &lt;/p&gt;

        &lt;p&gt;Initially, I had no further intentions beyond playing around
          a bit, gaining enough know-how to undertake typical embedded
          software projects and doing the occasional upstream
          contribution here and there, until
          a &lt;a href=&quot;https://blogs.igalia.com/siglesias/&quot;&gt;colleague&lt;/a&gt;
          told me &quot;Now that you've spent some time with Zephyr, what do
          you think about doing a demo about it?&quot;. Not a bad idea. The
          goal is to have something to show at conferences and that
          showcases Zephyr's possibilities using a simple
          application.&lt;/p&gt;

        &lt;p&gt;At work, I'm not a specialist. What I do most of the time is
          basically one thing, and it typically doesn't fit in a
          specific field, area, or team: I solve problems &lt;a href=&quot;https://blogs.igalia.com/rcn/feed.xml#fn1&quot; id=&quot;ref1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;. So this is an example of how to
          solve a single-sentence problem (&quot;Let's do a demo&quot;) using
          whatever means necessary, involving software, hardware,
          planning, design, logistics, decision making and
          improvisation. It's also a personal expression of the
          importance, meaning and value of human work.&lt;/p&gt;

        &lt;p&gt;The following is a non-exhaustive list of the problems faced
          along the way and the solutions found.&lt;/p&gt;

        &lt;h3&gt;Problem 1: the idea&lt;/h3&gt;

        &lt;p&gt;The starting point is just a phrase: &quot;Why don't we do a
          demo?&quot;, and a deadline. Nothing more. The amount of
          possibilities alone can already be an obstacle if we can't
          find a way to limit the solution space. Obviously, we'll find
          limitations and constraints down the road that will shape the
          final solution but, right now, everything is uncertainty.&lt;/p&gt;

        &lt;p&gt;What we want to show in the demo is the possibilities offered
          by Zephyr for embedded development using 100% open source
          software, how we can undertake complex application development
          with Zephyr, and show a variety of development cases within
          the same application.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;There are may approaches to a technical demo. However, having
          been to conferences with demo booths, it's clear that the live
          and interactive demos are the ones that gather the most
          attention of the general public by a large margin. Regardless
          of the technical merits displayed in the demo, people are
          drawn to things they can touch, blinking lights, sounds, video
          games.&lt;/p&gt;

        &lt;p&gt;So a hard requirement since the beginning was that the demo
          should be interactive. Fortunately, the nature of the
          technology behind it lends itself to that easily, although
          I've seen many Zephyr-based demos that were rather static and
          only for display. The intention here is to allow the public to
          actually use it.&lt;/p&gt;

        &lt;p&gt;Another important thing to take into account is that
          widespread or hot technologies and buzzwords will be more
          attractive than obscure or niche terms. Fortunately, I'm
          building the demo from scratch, so I get to decide what to
          show. In this case, I picked up
          &lt;div class=&quot;tooltip&quot;&gt;BLE&lt;span class=&quot;tooltiptext&quot;&gt;Bluetooth
          Low Energy&lt;/span&gt;&lt;/div&gt;
          as a base technology. Not world-changing, but familiar enough to everyone.&lt;/p&gt;

        &lt;p&gt;The goal, then, is to develop a hardware/software solution
          using Zephyr and its BLE stack, allowing interaction from the
          public and incorporating some way to display real-time
          information about it. The initial idea is to have small
          battery-powered devices in the demo booth and track their
          position using trilateration based on any available
          distance-measurement mechanism available in BLE devices, and
          have a central device that displays the position of the
          devices in real time.&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260317-why_dont_we_do_a_demo_part_1/idea_sketch.jpg&quot; /&gt;
        &lt;/div&gt;

        &lt;h3&gt;Problem 2: selecting the hardware&lt;/h3&gt;

        &lt;p&gt;Now that I settled on an initial idea, even if it's in a very
          rough and sketchy form, with no further technical details, I
          can start experimenting with the options. The first step is to
          do some research about the hardware and software possibilities
          to reach our goal, pick up some evaluation boards and start
          sketching ideas to have a better understanding of the
          feasibility of what I want to achieve and the limitations I
          can find (time, software/hardware constraints, skills,
          etc.)&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;A good option for BLE-based applications is to use some of
          the Nordic development kits. They're easy to source and
          inexpensive. Besides, the recent nRF54L15 SoC
          supports &lt;a href=&quot;https://www.bluetooth.com/learn-about-bluetooth/feature-enhancements/channel-sounding/&quot;&gt;channel
          sounding&lt;/a&gt;, which promises precise distance estimations
          between devices. Just what I'm looking for.&lt;/p&gt;

        &lt;p&gt;I'll need two types of devices for this: one of them needs to
          be small (wearable size, if possible) and battery-powered. The
          other type will be at a fixed location and can have a cabled
          power supply. The idea is to have three devices at fixed
          locations in the booth measuring the distance to a number of
          battery-powered devices that will be moving. Then, a central
          device will collect the distance information from the metering
          devices and use it to calculate the position of each
          battery-powered device.&lt;br /&gt;

          This central device will need to have some way to display the
          position of the devices in some kind of graphical interface,
          so I need to search for a device that can connect to the
          metering devices, that is well supported in Zephyr and that
          can support some kind of display out-of-the-box.&lt;/p&gt;

        &lt;p&gt;With all these requirements in mind I came up with this list
        of devices:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            For the battery-powered
            devices: &lt;a href=&quot;https://wiki.seeedstudio.com/xiao_nrf54l15_sense_getting_started/&quot;&gt;Seeed
            Studio XIAO nRF54L15&lt;/a&gt;, based on the nRF54L15 SoC.
          &lt;/li&gt;
          &lt;li&gt;
            For the metering
            devices: &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-hardware/nRF54L15-DK&quot;&gt;nRF54L15
            DK&lt;/a&gt; boards from Nordic, also based on the nRF54L15.
          &lt;/li&gt;
          &lt;li&gt;
            For the central
            device: &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-hardware/nRF52840-DK&quot;&gt;nRF52840
            DK&lt;/a&gt; board, which supports a serial SPI touchscreen like
            &lt;a href=&quot;https://www.buydisplay.com/arduino-3-5-tft-lcd-touch-shield-serial-spi-example-for-mega-due&quot;&gt;this
            one&lt;/a&gt;.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;All the hardware is already supported in Zephyr, so that
          should eliminate a lot of the initial friction and save us
          time.&lt;/p&gt;

        &lt;h3&gt;Problem 3: practical limitations, redefining the idea&lt;/h3&gt;

        &lt;p&gt;After some initial experiments with the hardware, running
          sample BLE applications and getting familiar with the
          ecosystem, I found out that, while the nRF54L15 hardware
          supports Bluetooth channel sounding, the Zephyr BLE stack
          still doesn't support it, so in order to use it I'd need to
          use
          Nordic's &lt;a href=&quot;https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/softdevice_controller/README.html&quot;&gt;SoftDevice
          controller&lt;/a&gt; instead of the upstream Zephyr controller,
          together with the
          &lt;a href=&quot;https://www.nordicsemi.com/Products/Development-software/nRF-Connect-SDK&quot;&gt;nRF Connect BLE&lt;/a&gt; stack.&lt;/p&gt;

        &lt;p&gt;This is a problem because a key feature of this demo should
          be that it's done using 100% open source code and, preferably,
          upstream Zephyr code.&lt;/p&gt;

        &lt;p&gt;Another, even bigger obstacle, is that it's not clear that
          collecting distance data from multiple sources simultaneously
          for trilateration, and doing it for multiple peripherals at
          the same time, is practically viable. I couldn't find any
          examples or documentation on it, and I could be entering
          uncharted territory. Considering that we have a deadline for
          this, I'd rather find an alternative.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;The immediate solution is to find a less audacious idea to
          develop using the same hardware that I already have, keeping
          it interactive but simpler, and keeping the same goals.&lt;/p&gt;

        &lt;p&gt;The idea I finally settled on is an extension of the typical
          BLE peripheral -- central application, where the peripheral
          publishes some services and the central device connects to it
          and issues
          &lt;a href=&quot;https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/lessons/lesson-4-bluetooth-le-data-exchange/topic/gatt-operations/&quot;&gt;GATT&lt;/a&gt;
          reads and writes to the peripheral characteristics, but adding
          a multi-level network topology instead of a simple star
          network, and adding real-time remote display and control of
          the devices using a graphical interface. So we'd have three
          device types: the battery-powered peripherals, which will
          provide the basic services, then the controller devices, which
          will connect to the peripherals to control them remotely, and
          then a console device which will connect to the controllers
          and can show and control the devices remotely using a
          graphical interface.&lt;/p&gt;

        &lt;div align=&quot;center&quot;&gt;
          &lt;br /&gt;
          &lt;img src=&quot;https://blogs.igalia.com/rcn/posts/20260317-why_dont_we_do_a_demo_part_1/demo_idea.png&quot; /&gt;
        &lt;/div&gt;

        &lt;p&gt;Zephyr supports BLE Mesh already, but we'd lose part of the
          challenge of implementing the networking routing ourselves, so
          I'm keeping things more interesting by implementing a custom
          tree topology that provides us with finer grained control, and
          which can be tailored to a specific application use case.&lt;/p&gt;

        &lt;p&gt;This means that the controller device will need to act both
          as a BLE central and peripheral device simultaneously, while
          the peripheral devices will act only as peripherals and the
          console will be only a central.&lt;/p&gt;

        &lt;h3&gt;Problem 4: initial planning&lt;/h3&gt;

        &lt;p&gt;With the development boards at hand, I can start designing
          and developing the firmwares for the three board types,
          including testing and documentation. The other certain thing I
          have right now is a deadline: the conference where we want to
          show the demo. Now I need to draw a rough plan with concrete
          dates.&lt;/p&gt;

        &lt;h3&gt;Solution&lt;/h3&gt;

        &lt;p&gt;Considering that I'll surely find a few bad surprises down
          the road and that there'll be uncertainty and problems that I
          can't yet anticipate, since it's the first time we're doing a
          demo with these characteristics, I set myself a personal hard
          deadline: one month before the real hard deadline. Ideally,
          the firmware should be all done and thoroughly tested one
          month before that, so that'd leave two full months for
          additional preparations and for sorting out whichever
          last-minute obstacles I could find in the end.&lt;/p&gt;

        &lt;p&gt;Of course, all of this rough planning is based purely on
          intuition. I could fall into the trap of wanting to plan
          everything beforehand and write a well-specified roadmap of
          everything that needs to be done in minute detail, but I'd be
          setting myself up for failure from the start, since 90% of the
          work ahead is a big question mark. I'm defining everything as
          we go, and in cases like this it's much more reasonable to
          plan and work based on different principles:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Define reasonable and achievable milestones and iterate
            based on them.
          &lt;/li&gt;
          &lt;li&gt;
            Iterate fast and as many times as needed.
          &lt;/li&gt;
          &lt;li&gt;
            Re-draw the plan after an iteration if needed.
          &lt;/li&gt;
          &lt;li&gt;
            Be ready to improvise.
          &lt;/li&gt;
          &lt;li&gt;
            &lt;a href=&quot;https://en.wikipedia.org/wiki/Kaizen&quot;&gt;Improve
              incrementally&lt;/a&gt; and have faith in the process. Don't
              look at the top of the mountain, you know where it
              is. Focus on the next meter of path in front.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;Doing this as a one-person-army has both pros and cons. Fear
          and uncertainty are something you have to shoulder on your
          own, but you're also free to take whatever decision you need
          whenever you need.&lt;/p&gt;

        &lt;p&gt;So, now we're ready to start developing. A rough milestones
          sketch for the firmware development could be:&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
            Base application for the peripheral: board setup and hardware
            handling.
          &lt;/li&gt;
          &lt;li&gt;
            Base application for the controller device: board setup and
            hardware handling.
          &lt;/li&gt;
          &lt;li&gt;
            Basic peripheral-central BLE application using the
            peripheral and controller devices.
          &lt;/li&gt;
          &lt;li&gt;
            Base application for the console device: board setup and
            hardware handling.
          &lt;/li&gt;
          &lt;li&gt;
            Make the controller device work as both a BLE peripheral and
            central device.
          &lt;/li&gt;
          &lt;li&gt;
            Incorporate the console device to the peripheral +
            controller application.
          &lt;/li&gt;
          &lt;li&gt;
            Graphical interface design and implementation.
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;p&gt;Testing and simulation should be a part of every
          milestone.&lt;/p&gt;

        &lt;p&gt;In the next post we'll go through the firmware development
          part of the project.&lt;/p&gt;

        &lt;div class=&quot;footnotes&quot;&gt;
          &lt;p id=&quot;fn1&quot;&gt;1: I like to think that's a specialty,
          though. Maybe one day that'll be a role in the
          company.&lt;a href=&quot;https://blogs.igalia.com/rcn/feed.xml#ref1&quot;&gt;&#8617;&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;        </description>
	<pubDate>Tue, 17 Mar 2026 07:00:00 +0000</pubDate>
	<dc:creator>rcn</dc:creator>
</item>
<item>
	<title>Hironori Fujii: Async Scrolling Improvements</title>
	<guid>https://blogs.igalia.com/fujii/async-scrolling-improvements/</guid>
	<link>https://blogs.igalia.com/fujii/async-scrolling-improvements/</link>
	<description>
&lt;p&gt;WPE WebKit and WebKitGTK support &lt;a href=&quot;https://docs.webkit.org/Ports/WebKitGTK%20and%20WPE%20WebKit/Graphics.html#async-scrolling&quot;&gt;async scrolling&lt;/a&gt; for wheel events.
I landed several improvements for the upcoming 2.52 release.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=305451&quot;&gt;Bug 305451&lt;/a&gt; &#8211; wheel event async scrolling doesn&#8217;t start while the main thread is blocked&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=305560&quot;&gt;Bug 305560&lt;/a&gt; &#8211; rendering glitches for unpainted tiles&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=305561&quot;&gt;Bug 305561&lt;/a&gt; &#8211; Paint scrollbars in the scrolling thread for async scrolling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are videos of before and after the changes.
This is &lt;a href=&quot;https://bug-305441-attachments.webkit.org/attachment.cgi?id=477989&quot;&gt;the test content&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://blogs.igalia.com/fujii/video/async-scrolling-before.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;
&lt;video src=&quot;https://blogs.igalia.com/fujii/video/async-scrolling-after.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;There is still room for further improvement.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The scrollbar hiding animation timer is still running in the main thread.
&lt;ul&gt;
&lt;li&gt;It can use CoordinatedPlatformLayer::setAnimations.&lt;/li&gt;
&lt;li&gt;Or CoordinatedPlatformLayer::setOpacity.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add the showing animation and transition animations of mouse hover states like GTK Adwaita theme&lt;/li&gt;
&lt;li&gt;Support touch and gesture events async scrolling&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Kate Lee: Building a Custom HTML Context Menu with the New WPEPlatform API</title>
	<guid>https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/</guid>
	<link>https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/</link>
	<description>
&lt;p&gt;&lt;a href=&quot;https://wpewebkit.org/&quot;&gt;WPE WebKit&lt;/a&gt; is a WebKit port optimized for embedded devices &#8212; think set-top boxes, digital signage, kiosk displays, and in-vehicle infotainment systems. It is developed by &lt;a href=&quot;https://www.igalia.com/&quot;&gt;Igalia&lt;/a&gt; and powers web experiences on millions of devices worldwide, from set-top boxes to smart TVs and beyond.&lt;/p&gt;
&lt;p&gt;WPE WebKit has recently introduced a &lt;strong&gt;brand-new platform API called WPEPlatform&lt;/strong&gt;, which replaces the legacy &lt;code&gt;libwpe&lt;/code&gt; + &lt;code&gt;wpebackend-fdo&lt;/code&gt; stack. In this post, I will walk you through building a minimal WPE browser launcher using &lt;strong&gt;only the new WPEPlatform API&lt;/strong&gt;, and demonstrate one of its newly available features: the &lt;strong&gt;Context Menu API&lt;/strong&gt; &#8212; rendered entirely as an HTML overlay.&lt;/p&gt;
&lt;h2 id=&quot;why-a-new-api&quot; tabindex=&quot;-1&quot;&gt;Why a New API? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The legacy stack (&lt;code&gt;libwpe&lt;/code&gt; + &lt;code&gt;wpebackend-fdo&lt;/code&gt; + Cog platform plugins) had several pain points: nested Wayland compositor complexity, dependency on Mesa&#8217;s now-deprecated &lt;code&gt;EGL_WL_bind_wayland_display&lt;/code&gt; extension, rigid C function-pointer tables, and platform code scattered across three libraries.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;new WPEPlatform API&lt;/strong&gt; replaces all of this with a single, clean &lt;a href=&quot;https://docs.gtk.org/gobject/&quot;&gt;GObject&lt;/a&gt;-based layer &#8212; providing automatic backend creation, DMA-BUF direct buffer sharing, unified window management (fullscreen, maximize, resize, title), and easy language bindings via GObject Introspection.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Timeline&lt;/strong&gt;: The stable release of WPEPlatform is planned for &lt;strong&gt;September 2026&lt;/strong&gt;. At that point, the legacy API will be officially deprecated. We strongly recommend new projects to adopt the WPEPlatform API from the start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;wpeplatform-launcher-a-minimal-browser-in-250-lines&quot; tabindex=&quot;-1&quot;&gt;WPEPlatform Launcher: A Minimal Browser in ~250 Lines &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To demonstrate the new API, I built &lt;strong&gt;WPEPlatformLauncher&lt;/strong&gt; &#8212; a minimal but functional WPE WebKit browser that uses only the WPEPlatform API. No legacy &lt;code&gt;libwpe&lt;/code&gt;, no &lt;code&gt;wpebackend-fdo&lt;/code&gt;, no Cog &#8212; just the new API.&lt;/p&gt;
&lt;p&gt;The full source code is available at:
&lt;strong&gt;&lt;a href=&quot;https://github.com/kate-k-lee/WebKit/commit/aed6402b267475f79ae7a8d417d18239b53be651&quot;&gt;kate-k-lee/WebKit@aed6402&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-simple-is-it&quot; tabindex=&quot;-1&quot;&gt;How Simple Is It? &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is the core of the launcher &#8212; creating a WebView with the new API:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* WPEPlatform backend is created automatically &#8212; no manual setup needed */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; webView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;WEBKIT_WEB_VIEW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;g_object_new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WEBKIT_TYPE_WEB_VIEW&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;web-context&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;network-session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; networkSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;settings&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;user-content-manager&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userContentManager&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Get the WPEPlatform view &#8212; this is where the new API shines */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; wpeView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webkit_web_view_get_wpe_view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; toplevel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wpe_view_get_toplevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wpeView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Window management: fullscreen, resize, title &#8212; all built-in */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;wpe_toplevel_fullscreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;wpe_toplevel_resize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;wpe_toplevel_set_title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WPEPlatform Launcher&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Input events: just connect a GObject signal */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;g_signal_connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wpeView&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;event&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;G_CALLBACK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onViewEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compare this with the legacy API, which required:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Manually creating a &lt;code&gt;WPEToolingBackends::ViewBackend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Wrapping it in a &lt;code&gt;WebKitWebViewBackend&lt;/code&gt; with a destroy callback&lt;/li&gt;
&lt;li&gt;Creating a C++ &lt;code&gt;InputClient&lt;/code&gt; class and registering it&lt;/li&gt;
&lt;li&gt;Having no window management (no maximize, minimize, title, etc.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The new API handles backend creation, display detection, and input forwarding automatically.&lt;/p&gt;
&lt;h3 id=&quot;keyboard-shortcuts&quot; tabindex=&quot;-1&quot;&gt;Keyboard Shortcuts &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Handling keyboard events is straightforward with the WPEPlatform event system:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; gboolean &lt;span class=&quot;token function&quot;&gt;onViewEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WPEView&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; view&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WPEEvent&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; WebKitWebView&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; webView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wpe_event_get_event_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; WPE_EVENT_KEYBOARD_KEY_DOWN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; FALSE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; modifiers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wpe_event_get_modifiers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; keyval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wpe_event_keyboard_get_keyval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Ctrl+Q: Quit */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modifiers &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; WPE_MODIFIER_KEYBOARD_CONTROL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; keyval &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; WPE_KEY_q&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;g_application_quit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;g_application_get_default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; TRUE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* F11: Toggle fullscreen via WPEToplevel */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyval &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; WPE_KEY_F11&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; toplevel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wpe_view_get_toplevel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wpe_toplevel_get_state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; WPE_TOPLEVEL_STATE_FULLSCREEN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;wpe_toplevel_unfullscreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;wpe_toplevel_fullscreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toplevel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; TRUE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; FALSE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;html-based-context-menu-solving-the-no-native-ui-challenge&quot; tabindex=&quot;-1&quot;&gt;HTML-Based Context Menu: Solving the &#8220;No Native UI&#8221; Challenge &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WPE WebKit is designed for embedded environments where there is &lt;strong&gt;no native UI toolkit&lt;/strong&gt; &#8212; 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.&lt;/p&gt;
&lt;p&gt;The approach: &lt;strong&gt;intercept WebKit&#8217;s &lt;code&gt;context-menu&lt;/code&gt; signal, read the menu items, and render them as an HTML/CSS overlay&lt;/strong&gt; injected into the page DOM.&lt;/p&gt;
&lt;h3 id=&quot;the-architecture&quot; tabindex=&quot;-1&quot;&gt;The Architecture &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;User right-clicks
  &#8594; WebKit emits &amp;quot;context-menu&amp;quot; signal
  &#8594; onContextMenu() handler:
      1. Reads menu items via webkit_context_menu_get_items()
      2. Gets position via webkit_context_menu_get_position()
      3. Builds JavaScript that creates DOM elements
      4. Injects via webkit_web_view_evaluate_javascript()
      5. Returns TRUE (suppresses default menu)

User clicks a menu item
  &#8594; JS: window.webkit.messageHandlers.contextMenuAction.postMessage(actionId)
  &#8594; C: onContextMenuAction() receives the action ID
      &#8594; Executes: webkit_web_view_go_back(), execute_editing_command(&amp;quot;Copy&amp;quot;), etc.

User clicks outside the menu
  &#8594; JS: overlay click handler removes the DOM elements
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;reading-context-menu-items&quot; tabindex=&quot;-1&quot;&gt;Reading Context Menu Items &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Context Menu API provides everything we need:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; gboolean &lt;span class=&quot;token function&quot;&gt;onContextMenu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WebKitWebView&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; webView&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    WebKitContextMenu&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; contextMenu&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gpointer &lt;span class=&quot;token comment&quot;&gt;/* event */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    WebKitHitTestResult&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; hitTestResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gpointer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Save hit test result for link-related actions */&lt;/span&gt;&lt;br /&gt;    savedHitTestResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;WEBKIT_HIT_TEST_RESULT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;g_object_ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hitTestResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Iterate through menu items */&lt;/span&gt;&lt;br /&gt;    GList&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; items &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webkit_context_menu_get_items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contextMenu&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GList&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; l &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; items&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; l&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; l &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; l&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;WEBKIT_CONTEXT_MENU_ITEM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;webkit_context_menu_item_is_separator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;/* Render as a horizontal line */&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webkit_context_menu_item_get_title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt; action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webkit_context_menu_item_get_stock_action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;/* Build HTML element with title and action ID */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Get position for menu placement */&lt;/span&gt;&lt;br /&gt;    gint posX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; posY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;webkit_context_menu_get_position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contextMenu&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;posX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;posY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; TRUE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Suppress default menu */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-html-menu-dark-theme-for-embedded&quot; tabindex=&quot;-1&quot;&gt;The HTML Menu: Dark Theme for Embedded &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The context menu is rendered with a dark theme CSS, designed for embedded/kiosk displays:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;#__wpe_ctx_menu&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fixed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #2b2b2b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid #505050&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 6px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 8px 24px &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;0.4&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; system-ui&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sans-serif&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 13px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #e0e0e0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.__wpe_ctx_item:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #0060df&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #ffffff&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;handling-actions-via-script-message-handler&quot; tabindex=&quot;-1&quot;&gt;Handling Actions via Script Message Handler &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Communication between the HTML menu and the C application uses WebKit&#8217;s script message handler mechanism:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Register message handler */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; ucm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webkit_user_content_manager_new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;webkit_user_content_manager_register_script_message_handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    ucm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;contextMenuAction&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;g_signal_connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ucm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;script-message-received::contextMenuAction&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;G_CALLBACK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onContextMenuAction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In the generated HTML menu item:&lt;/span&gt;&lt;br /&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'click'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messageHandlers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contextMenuAction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Handle the action in C */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onContextMenuAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WebKitUserContentManager&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; JSCValue&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gpointer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; actionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;jsc_value_to_int32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; WEBKIT_CONTEXT_MENU_ACTION_RELOAD&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;webkit_web_view_reload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; WEBKIT_CONTEXT_MENU_ACTION_COPY&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;webkit_web_view_execute_editing_command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webView&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Copy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;webkit_web_view_load_uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webView&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;webkit_hit_test_result_get_link_uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;savedHitTestResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* ... more actions ... */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;demo&quot; tabindex=&quot;-1&quot;&gt;Demo &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is the WPEPlatformLauncher in action, showing the HTML context menu with various actions:&lt;/p&gt;
&lt;img src=&quot;https://blogs.igalia.com/klee/blog/wpeplatform-launcher-context-menu/context-menu-demo.gif&quot; alt=&quot;WPEPlatformLauncher context menu demo&quot; /&gt;
&lt;p&gt;&lt;em&gt;Right-clicking shows the HTML context menu. Clicking &#8220;Reload&#8221; triggers an actual page reload.&lt;/em&gt;&lt;/p&gt;
&lt;img src=&quot;https://blogs.igalia.com/klee/blog/wpeplatform-launcher-context-menu/context-menu-link-demo.gif&quot; alt=&quot;Context menu on a link&quot; /&gt;
&lt;p&gt;&lt;em&gt;Right-clicking a link shows link-specific actions like &#8220;Open Link&#8221; and &#8220;Copy Link Address&#8221;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;building-and-running&quot; tabindex=&quot;-1&quot;&gt;Building and Running &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I built and ran the WPEPlatformLauncher inside a container using the &lt;a href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;WebKit Container SDK&lt;/a&gt;, which provides a pre-configured development environment with all the dependencies needed to build WPE WebKit.&lt;/p&gt;
&lt;p&gt;The WPEPlatformLauncher integrates into the WebKit build system:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Build WPE WebKit with the launcher&lt;/span&gt;&lt;br /&gt;Tools/Scripts/build-webkit &lt;span class=&quot;token parameter variable&quot;&gt;--wpe&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--release&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Run&lt;/span&gt;&lt;br /&gt;./WebKitBuild/WPE/Release/bin/WPEPlatformLauncher https://wpewebkit.org&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Run in fullscreen (kiosk mode)&lt;/span&gt;&lt;br /&gt;./WebKitBuild/WPE/Release/bin/WPEPlatformLauncher &lt;span class=&quot;token parameter variable&quot;&gt;--fullscreen&lt;/span&gt; https://your-app.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full source is a single &lt;code&gt;main.cpp&lt;/code&gt; file (~600 lines including the context menu), integrated into the WebKit tree alongside MiniBrowser:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WebKit/Tools/
&#9500;&#9472;&#9472; MiniBrowser/wpe/          &#8592; Existing (supports both old + new API)
&#9500;&#9472;&#9472; WPEPlatformLauncher/      &#8592; New (WPEPlatform API only)
&#9474;   &#9500;&#9472;&#9472; main.cpp
&#9474;   &#9492;&#9472;&#9472; CMakeLists.txt
&#9492;&#9472;&#9472; PlatformWPE.cmake         &#8592; Modified to add WPEPlatformLauncher
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The new &lt;strong&gt;WPEPlatform API&lt;/strong&gt; makes building WPE WebKit applications significantly simpler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No manual backend setup&lt;/strong&gt; &#8212; the platform is detected and configured automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GObject-based&lt;/strong&gt; &#8212; signals, properties, and ref counting instead of C function pointers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMA-BUF direct sharing&lt;/strong&gt; &#8212; no dependency on Mesa&#8217;s deprecated EGL extensions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unified window management&lt;/strong&gt; &#8212; fullscreen, maximize, minimize, resize, and title&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language binding friendly&lt;/strong&gt; &#8212; works with Python, JavaScript, and more via GObject Introspection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For embedded browser developers building kiosk UIs, set-top box interfaces, or digital signage with WPE WebKit &#8212; now is the time to adopt the new API. The stable release is coming in September 2026, and the legacy stack (&lt;code&gt;libwpe&lt;/code&gt;, &lt;code&gt;wpebackend-fdo&lt;/code&gt;, Cog) will be deprecated at that point.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot; tabindex=&quot;-1&quot;&gt;Resources &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/klee/building-a-custom-html-context-menu-with-the-new-wpeplatform-api/&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wpewebkit.org/&quot;&gt;WPE WebKit official site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wpewebkit.org/reference/2.51.92/wpe-webkit-2.0/index.html&quot;&gt;WPE WebKit API Reference (2.51.92)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wpewebkit.org/reference/2.51.92/wpe-webkit-2.0/class.ContextMenu.html&quot;&gt;WebKitContextMenu API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kate-k-lee/WebKit/commit/aed6402b267475f79ae7a8d417d18239b53be651&quot;&gt;WPEPlatformLauncher source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.igalia.com/project/wpe&quot;&gt;Igalia &#8212; WPE WebKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Igalia/cog&quot;&gt;Cog &#8212; WPE launcher (legacy)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Hironori Fujii: Building WebKit and libsoup with AddressSanitizer (ASan)</title>
	<guid>https://blogs.igalia.com/fujii/building-webkit-and-libsoup-with-addresssanitizer-asan/</guid>
	<link>https://blogs.igalia.com/fujii/building-webkit-and-libsoup-with-addresssanitizer-asan/</link>
	<description>
&lt;p&gt;I built libsoup and WebKit with ASan today.
It works almost out of the box.
I used Clang.
GCC also supports ASan, but WebKit has a problem with it.
&lt;a href=&quot;https://github.com/Igalia/webkit-container-sdk&quot;&gt;WebKit Container SDK&lt;/a&gt; is based on Ubuntu 20.04 LTS at the moment.
It contains clang 18 by default.&lt;/p&gt;
&lt;p&gt;Installed required packages.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; libclang-rt-18-dev llvm-18-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set env vars.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CC&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;clang &lt;span class=&quot;token assign-left variable&quot;&gt;CXX&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;clang++&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Passed some flags to libsoup.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token coord&quot;&gt;--- /jhbuild/webkit-sdk-deps.modules.orig&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;+++ /jhbuild/webkit-sdk-deps.modules&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;@@ -149,7 +149,7 @@&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    &amp;lt;/dependencies&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  &amp;lt;/meson&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  &amp;lt;meson id=&quot;libsoup&quot; mesonargs=&quot;-Dtests=false&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  &amp;lt;meson id=&quot;libsoup&quot; mesonargs=&quot;-Dtests=false -Db_sanitize=address -Db_lundef=false&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    &amp;lt;branch repo=&quot;github.com&quot;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            checkoutdir=&quot;libsoup&quot;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            module=&quot;GNOME/libsoup.git&quot; tag=&quot;3.6.6&quot;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, build and install libsoup.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;jhbuild buildone &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; libsoup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, build WebKit with ASan.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./Tools/Scripts/build-webkit &lt;span class=&quot;token parameter variable&quot;&gt;--gtk&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--release&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--cmakeargs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;-DENABLE_SANITIZERS&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;address&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WebKit has a lot of memory leaks by design. Don&#8217;t detect leaks.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;ASAN_OPTIONS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;detect_leaks&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For run-webkit-tests, I had to modify a script a bit.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/Tools/Scripts/webkitpy/port/driver.py b/Tools/Scripts/webkitpy/port/driver.py&lt;br /&gt;index eb12801a455b..c9f74eeab4e2 100644&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;--- a/Tools/Scripts/webkitpy/port/driver.py&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;+++ b/Tools/Scripts/webkitpy/port/driver.py&lt;/span&gt;&lt;br /&gt;@@ -482,7 +482,7 @@ class Driver(object):&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        else:&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        environment['LOCAL_RESOURCE_ROOT'] = str(self._port.layout_tests_dir())&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        environment['ASAN_OPTIONS'] = &quot;allocator_may_return_null=1&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        environment['ASAN_OPTIONS'] = &quot;allocator_may_return_null=1:detect_leaks=0&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        environment['__XPC_ASAN_OPTIONS'] = environment['ASAN_OPTIONS']&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        # Disable vnode-guard related simulated crashes for WKTR / DRT (rdar://problem/40674034).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#8217;s it. Enjoy.&lt;/p&gt;        </description>
	<pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Andy Wingo: nominal types in webassembly</title>
	<guid>https://wingolog.org/2026/03/10/nominal-types-in-webassembly</guid>
	<link>https://wingolog.org/archives/2026/03/10/nominal-types-in-webassembly</link>
	<description>
&lt;div&gt;&lt;p&gt;Before the managed data types extension to WebAssembly was incorporated
in the standard, there was a huge debate about type equality.  The end
result is that if you have two types in a Wasm module that look the
same, like this:&lt;/p&gt;&lt;pre class=&quot;pre-wat&quot;&gt;(type $t (struct i32))
(type $u (struct i32))
&lt;/pre&gt;&lt;p&gt;Then they are for all intents and purposes equivalent.  When a Wasm
implementation loads up a module, it has to partition the module&#8217;s types
into equivalence classes.  When the Wasm program references a given type
by name, as in &lt;tt&gt;(struct.get $t 0)&lt;/tt&gt; which would get the first field of
type &lt;tt&gt;$t&lt;/tt&gt;, it maps &lt;tt&gt;$t&lt;/tt&gt; to the equivalence class containing &lt;tt&gt;$t&lt;/tt&gt; and
&lt;tt&gt;$u&lt;/tt&gt;.  See the &lt;a href=&quot;https://webassembly.github.io/spec/core/valid/conventions.html#rolling-and-unrolling&quot;&gt;spec&lt;/a&gt;, for more details.&lt;/p&gt;&lt;p&gt;This is a form of &lt;i&gt;structural type equality&lt;/i&gt;.  Sometimes this is what you
want.  But not always!  Sometimes you want &lt;i&gt;nominal types&lt;/i&gt;, in which no
type declaration is equivalent to any other.  WebAssembly doesn&#8217;t have
that, but it has something close: &lt;i&gt;recursive type groups&lt;/i&gt;.  In fact, the
type declarations above are equivalent to these:&lt;/p&gt;&lt;pre class=&quot;pre-wat&quot;&gt;(rec (type $t (struct i32)))
(rec (type $u (struct i32)))
&lt;/pre&gt;&lt;p&gt;Which is to say, each type is in a group containing just itself.  One
thing that this allows is self-recursion, as in:&lt;/p&gt;&lt;pre class=&quot;pre-way&quot;&gt;(type $succ (struct (ref null $succ)))
&lt;/pre&gt;&lt;p&gt;Here the struct&#8217;s field is itself a reference to a &lt;tt&gt;$succ&lt;/tt&gt; struct, or
null (because it&#8217;s &lt;tt&gt;ref null&lt;/tt&gt; and not just &lt;tt&gt;ref&lt;/tt&gt;).&lt;/p&gt;&lt;p&gt;To allow for mutual recursion between types, you put them in the same &lt;tt&gt;rec&lt;/tt&gt;
group, instead of each having its own:&lt;/p&gt;&lt;pre class=&quot;pre-wat&quot;&gt;(rec
 (type $t (struct i32))
 (type $u (struct i32)))
&lt;/pre&gt;&lt;p&gt;Between &lt;tt&gt;$t&lt;/tt&gt; and &lt;tt&gt;$u&lt;/tt&gt; we don&#8217;t have mutual recursion though, so why
bother?  Well &lt;tt&gt;rec&lt;/tt&gt; groups have another role, which is that they are the
unit of structural type equivalence.  In this case, types &lt;tt&gt;$t&lt;/tt&gt; and &lt;tt&gt;$u&lt;/tt&gt;
are not in the same equivalence class, because they are part of the same
&lt;tt&gt;rec&lt;/tt&gt; group.  Again, see &lt;a href=&quot;https://webassembly.github.io/spec/core/valid/conventions.html#defined-types&quot;&gt;the spec&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Within a Wasm module, &lt;tt&gt;rec&lt;/tt&gt; gives you an approximation of nominal
typing.  But what about between modules?  Let&#8217;s imagine that &lt;tt&gt;$t&lt;/tt&gt;
carries important capabilities, and you don&#8217;t want another module to be
able to forge those capabilities.  In this case, &lt;tt&gt;rec&lt;/tt&gt; is not enough:
the other module could define an equivalent &lt;tt&gt;rec&lt;/tt&gt; group, construct a
&lt;tt&gt;$t&lt;/tt&gt;, and pass it to our module; because of isorecursive type equality,
this would work just fine.  What to do?&lt;/p&gt;&lt;h3&gt;curs&#232;d nominal typing&lt;/h3&gt;&lt;p&gt;I said before that Wasm doesn&#8217;t have nominal types.  That was true in
the past, but no more!  The &lt;a href=&quot;https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md&quot;&gt;nominal typing
proposal&lt;/a&gt;
was incorporated in the standard last July.  Its vocabulary is a bit
odd, though.  You have to define your data types with the &lt;a href=&quot;https://webassembly.github.io/spec/core/syntax/types.html#tag-types&quot;&gt;&lt;tt&gt;tag&lt;/tt&gt; keyword&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;(tag $v (param $secret i32))
&lt;/pre&gt;&lt;p&gt;Syntactically, these data types are a bit odd: you have to declare
fields using &lt;tt&gt;param&lt;/tt&gt; instead of &lt;tt&gt;field&lt;/tt&gt; and you don&#8217;t have to wrap the
fields in &lt;tt&gt;struct&lt;/tt&gt;.&lt;/p&gt;&lt;p&gt;They also omit some features relative to isorecursive structs, namely
subtyping and mutability.  However, sometimes subtyping is not
necessary, and one can always assignment-convert mutable fields, wrapping them in mutable structs as needed.&lt;/p&gt;&lt;p&gt;To construct a nominally-typed value, the mechanics are somewhat
involved; instead of &lt;tt&gt;(struct.new $t (i32.const 42))&lt;/tt&gt;, you use &lt;a href=&quot;https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-x&quot;&gt;&lt;tt&gt;throw&lt;/tt&gt;&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;(block $b (result (ref exn))
 (try_table
  (catch_all_ref $b)
  (throw $v (i32.const 42)))
 (unreachable))
&lt;/pre&gt;&lt;p&gt;Of course, as this is a new proposal, we don&#8217;t yet have precise type
information on the Wasm side; the new instance instead is returned as
the top type for nominally-typed values, &lt;tt&gt;exn&lt;/tt&gt;.&lt;/p&gt;&lt;p&gt;To check if a value is a &lt;tt&gt;$v&lt;/tt&gt;, you need to write a bit of code:&lt;/p&gt;&lt;pre&gt;(func $is-v? (param $x (ref exn)) (result i32)
  (block $yep (result (ref exn))
   (block $nope
    (try_table
     (catch_ref $v $yep)
     (catch_all $nope)
     (throw_ref (local.get $x))))
   (return (i32.const 0)))
  (return (i32.const 1)))
&lt;/pre&gt;&lt;p&gt;Finally, field access is a bit odd; unlike structs which have
&lt;tt&gt;struct.get&lt;/tt&gt;, nominal types receive all their values via a &lt;tt&gt;catch&lt;/tt&gt;
handler.&lt;/p&gt;&lt;pre&gt;(func $v-fields (param $x (ref exn)) (result i32)
  (try_table
   (catch $v 0)
   (throw_ref (local.get $x)))
  (unreachable))
&lt;/pre&gt;&lt;p&gt;Here, the &lt;tt&gt;0&lt;/tt&gt; in the &lt;tt&gt;(catch $v 0)&lt;/tt&gt; refers to the function call itself:
all fields of &lt;tt&gt;$v&lt;/tt&gt; get returned from the function call.  In this case
there&#8217;s only one, othewise a get-fields function would return multiple
values.  Happily, this accessor preserves type safety: if &lt;tt&gt;$x&lt;/tt&gt; is not
actually &lt;tt&gt;$v&lt;/tt&gt;, an exception will be thrown.&lt;/p&gt;&lt;p&gt;Now, sometimes you want to be quite strict about your nominal type
identities; in that case, just define your &lt;tt&gt;tag&lt;/tt&gt; in a module and don&#8217;t
export it.  But if you want to enable composition in a principled way,
not just subject to the randomness of whether another module happens to
implement a type structurally the same as your own, the nominal typing
proposal also gives a preview of &lt;a href=&quot;https://github.com/WebAssembly/proposal-type-imports/blob/main/proposals/type-imports/Overview.md&quot;&gt;type
imports&lt;/a&gt;.
The facility is direct: you simply export your &lt;tt&gt;tag&lt;/tt&gt; from your module,
and allow other modules to import it.  Everything will work as expected!&lt;/p&gt;&lt;h3&gt;fin&lt;/h3&gt;&lt;p&gt;Friends, as I am sure is abundantly clear, this is a troll post :)  It&#8217;s
not wrong, though!  All of the facilities for nominally-typed structs
without subtyping or field mutability are present in the
exception-handling proposal.&lt;/p&gt;&lt;p&gt;The context for this work was that I was updating
&lt;a href=&quot;https://spritely.institute/hoot/&quot;&gt;Hoot&lt;/a&gt; to use the newer version of
Wasm exception handling, instead of the pre-standardization version.  It
was a nice change, but as it introduces the &lt;tt&gt;exnref&lt;/tt&gt; type, it does open
the door to some funny shenanigans, and I find it hilarious that the
committee has been hemming and hawwing about type imports for 7 years
and then goes and ships it in this backward kind of way.&lt;/p&gt;&lt;p&gt;Next up, exception support in
&lt;a href=&quot;https://codeberg.org/andywingo/wastrel&quot;&gt;Wastrel&lt;/a&gt;, as soon as I can
figure out where to allocate type tags for this new nominal typing
facility.  Onwards and upwards!&lt;/p&gt;&lt;/div&gt;        </description>
	<pubDate>Tue, 10 Mar 2026 08:19:34 +0000</pubDate>
	<dc:creator>Andy Wingo</dc:creator>
</item>
<item>
	<title>Yeunjoo Choi: Smarter Chromium GN in Vim with gn-language-server</title>
	<guid>https://duswnchl.github.io/posts/smarter-chromium-gn-in-vim-with-gn-language-server/</guid>
	<link>https://duswnchl.github.io/posts/smarter-chromium-gn-in-vim-with-gn-language-server/</link>
	<description>
&lt;p&gt;GN Language Server for Chromium development was announced on &lt;a href=&quot;https://groups.google.com/a/chromium.org/g/chromium-dev/c/uTa5mrlvbvw/m/vTVpKZPVDwAJ&quot;&gt;chromium-dev&lt;/a&gt;.
It&#8217;s very easy to install in VSCode, NeoVim or Emacs. But how can we configure
it with classic Vim + &lt;a href=&quot;https://github.com/ycm-core/YouCompleteMe&quot;&gt;YCM&lt;/a&gt;?&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;p&gt;First, install the language server with Cargo.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cargo &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--locked&lt;/span&gt; gn-language-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then, add this to your vimrc.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;let &lt;/span&gt;g:ycm_language_server &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;  &lt;span class=&quot;s1&quot;&gt;'name'&lt;/span&gt;: &lt;span class=&quot;s1&quot;&gt;'gn'&lt;/span&gt;,
      &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;  &lt;span class=&quot;s1&quot;&gt;'cmdline'&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'gn-language-server'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;  &lt;span class=&quot;s1&quot;&gt;'filetypes'&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'gn'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That easy, right?&lt;/p&gt;

&lt;h2 id=&quot;whats-working&quot;&gt;What&#8217;s Working&lt;/h2&gt;

&lt;h3 id=&quot;hover-documentation&quot;&gt;Hover Documentation&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://duswnchl.github.io/assets/posts/smarter-chromium-gn-in-vim-with-gn-language-server/hover.gif&quot; alt=&quot;hover&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;go-to-imports&quot;&gt;Go To Imports&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://duswnchl.github.io/assets/posts/smarter-chromium-gn-in-vim-with-gn-language-server/jump_import.gif&quot; alt=&quot;jump_import&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;go-to-dependencies&quot;&gt;Go To Dependencies&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://duswnchl.github.io/assets/posts/smarter-chromium-gn-in-vim-with-gn-language-server/jump_deps.gif&quot; alt=&quot;jump_deps&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;current-limitations&quot;&gt;Current Limitations&lt;/h2&gt;
&lt;p&gt;The following features are not working yet. They may need more configuration or
further work:&lt;/p&gt;

&lt;h3 id=&quot;code-folding&quot;&gt;Code Folding&lt;/h3&gt;
&lt;p&gt;Classic Vim and YCM don&#8217;t support LSP-based folding, and I&#8217;m not a big fan of
that feature anyway. But you can configure another plugin that supports
LSP-based folding, or simply rely on indent-based folding.&lt;/p&gt;

&lt;h3 id=&quot;go-to-definition&quot;&gt;Go To Definition&lt;/h3&gt;
&lt;p&gt;When I try to go to the definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;template&lt;/code&gt;, I get an error &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KeyError:
'uri'&lt;/code&gt;. I&#8217;m not sure whether this is caused by my local configuration, but it
needs further investigation.
&lt;img src=&quot;https://duswnchl.github.io/assets/posts/smarter-chromium-gn-in-vim-with-gn-language-server/go_def_error.gif&quot; alt=&quot;go_def_error&quot; /&gt;&lt;/p&gt;        </description>
	<pubDate>Tue, 10 Mar 2026 03:06:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #59</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-59/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-59/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from March 2 to March 9.&lt;/p&gt;
&lt;p&gt;
As part of this week's handful of news, WebKitGTK and WPE WebKit
now have support for Gamepad's &quot;VibationActuator&quot; property, the
video decoding limit is now configurable at runtime in addition
to build time, and an interesting fix that makes WebKit render
fonts like other browsers by making it blend text incorrectly (!).
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Using &lt;code&gt;libmanette&lt;/code&gt;'s &lt;em&gt;rumble&lt;/em&gt; support, enabled &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/vibrationActuator&quot;&gt;Gamepad &lt;em&gt;VibrationActuator&lt;/em&gt;&lt;/a&gt; for &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308799@main&quot;&gt;WebKitGTK&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308792@main&quot;&gt;WPE WebKit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With these changes, &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/GamepadHapticActuator/playEffect&quot;&gt;playEffect()&lt;/a&gt; can be used to play &lt;em&gt;dual-rumble&lt;/em&gt; vibration effects.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;code&gt;VIDEO_DECODING_LIMIT&lt;/code&gt; is now &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/show_bug.cgi?id=308969&quot;&gt;configurable at runtime&lt;/a&gt;, 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.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Landed &lt;a rel=&quot;external&quot; href=&quot;https://github.com/WebKit/WebKit/pull/59880&quot;&gt;a change&lt;/a&gt; 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.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;One more set of release candidates for the upcoming stable branch,
&lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/03/06/webkitgtk2.51.93-released.html&quot;&gt;WebKitGTK 2.51.93&lt;/a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.51.93.html&quot;&gt;WPE WebKit 2.51.93&lt;/a&gt;,
have been published. For those interested in previewing the upcoming 2.52.x
series this release is expected to be quite stable. Reporting &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/&quot;&gt;issues in Bugzilla&lt;/a&gt; are,
as usual, more than welcome.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 09 Mar 2026 20:02:33 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Tiago Vignatti: Accessibility and PDF documents</title>
	<guid>https://vignatti.com/posts/accessibility-and-pdfs/</guid>
	<link>https://vignatti.com/posts/accessibility-and-pdfs/</link>
	<description>
&lt;div&gt;
 
&lt;h2 class=&quot;relative group&quot;&gt;Accessibility
 &lt;div id=&quot;accessibility&quot; class=&quot;anchor&quot;&gt;&lt;/div&gt;
 
 &lt;span class=&quot;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&quot;&gt;
 &lt;a class=&quot;text-primary-300 dark:text-neutral-700 !no-underline&quot; href=&quot;https://vignatti.com/posts/index.xml#accessibility&quot;&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;When we think of &lt;strong&gt;accessibility&lt;/strong&gt;, we tend to picture it as something designed for a small minority. The reality is much broader: &lt;strong&gt;16% of the world&amp;rsquo;s population &#8212; 1.3 billion people &#8212; live with a significant disability&lt;/strong&gt;&lt;a href=&quot;https://www.who.int/news-room/fact-sheets/detail/disability-and-health&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;&#185;&lt;/a&gt;. In &lt;strong&gt;Brazil&lt;/strong&gt; alone, where I live, that means around &lt;strong&gt;14.4 million people report some form of disability&lt;/strong&gt;&lt;a href=&quot;https://agenciadenoticias.ibge.gov.br/en/agencia-news/2184-news-agency/news/43477-2022-census-brazil-has-14-4-million-persons-with-disabilities&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;&#178;&lt;/a&gt;. And those numbers capture only permanent disabilities.&lt;/p&gt;&lt;/div&gt;        </description>
	<pubDate>Wed, 04 Mar 2026 13:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #58</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-58/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-58/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from February 23 to March 2.&lt;/p&gt;
&lt;p&gt;
This installment of the periodical brings news about support
for Qualcomm qtivdec2 and qtivenc2 on GStreamer, GPU texture
atlas creation and replay substitution, enhancement of the scroll
gesture in WPE, and two new releases: WebKitGTK 2.51.92 and WPE
WebKit 2.51.92.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Work on adding support for the Qualcomm GStreamer qtivdec2 and qtivenc2 elements is on-going&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308458@main&quot;&gt;Implemented GPU texture atlas creation and replay substitution&lt;/a&gt; in the Skia painting engine on GTK/WPE. After recording, raster images are packed into GPU atlases via &lt;code&gt;BitmapTexture&lt;/code&gt;, 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 &lt;code&gt;BitmapTexture::updateContents()&lt;/code&gt;. Atlas uploads are synchronized across workers using a countdown-latch fence. During replay, &lt;code&gt;SkiaReplayCanvas&lt;/code&gt; intercepts raster image draws and substitutes them with atlas texture draws, mapping source coordinates into atlas space.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;wpe-webkit-pager&quot;&gt;WPE WebKit &#128223;&lt;/h2&gt;
&lt;h3 id=&quot;wpe-platform-api-jigsaw&quot;&gt;WPE Platform API &#129513;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;New, modern platform API that supersedes usage of libwpe and WPE backends.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The recent WPE WebKit 2.51.92 release is the first one to have its &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/2.51.92/wpe-platform-2.0/&quot;&gt;WPEPlatform documentation online&lt;/a&gt;, but it was not included in the tarball. This issue &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308408@main&quot;&gt;has been corrected&lt;/a&gt; and tarballs for future releases  will also include this documentation.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Scrolling using touch input with WPEPlatform would result in scrolling faster when more than one touch point was in effect. The gesture detector &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308271@main&quot;&gt;has been fixed&lt;/a&gt; to make scrolling have always a consistent speed.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;The third &#8212;and likely the last&#8212; release candidates for the upcoming stable branch, &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/2026/02/27/webkitgtk2.51.92-released.html&quot;&gt;WebKitGTK 2.51.92&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.51.92.html&quot;&gt;WPE WebKit 2.51.92&lt;/a&gt;, 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 &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/&quot;&gt;issues in Bugzilla&lt;/a&gt; are, as usual, more than welcome.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 02 Mar 2026 20:11:00 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Ziran Sun: A Day in &#8220;State of the Browser 2026&#8221; Conference</title>
	<guid>https://blogs.igalia.com/zsun/?p=837</guid>
	<link>https://blogs.igalia.com/zsun/2026/03/02/a-day-in-state-of-the-browser-2026-conference/?pk_campaign=feed&amp;pk_kwd=a-day-in-state-of-the-browser-2026-conference</link>
	<description>
&lt;p&gt;The &amp;#8220;State of the Browser 2026&amp;#8221; Conference was held on Saturday, the 28th of February in &lt;a href=&quot;https://www.barbican.org.uk/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;The Barbican Centre&lt;/a&gt;, London. It is a yearly conference organised by &lt;a href=&quot;https://londonwebstandards.org/&quot;&gt;London Web Standards&lt;/a&gt;. This is year is the 14th Edition.&lt;/p&gt;



&lt;p&gt;From Igalia, this year we had&lt;a href=&quot;https://www.igalia.com/team/lwarlow&quot;&gt; Luke Warlow&lt;/a&gt; and myself attended in person,  &lt;a href=&quot;https://www.igalia.com/team/jfernandez&quot;&gt;Javier Fern&#225;ndez&lt;/a&gt;  attended online. My colleague &lt;a href=&quot;https://www.igalia.com/team/sstimac&quot;&gt;Stephanie Stimac&lt;/a&gt; introduced this event to &lt;a href=&quot;https://www.igalia.com/&quot;&gt;Igalia&lt;/a&gt; a couple of years ago. Now &lt;a href=&quot;https://www.igalia.com/&quot;&gt;Igalia&lt;/a&gt; has become one of the sponsors for this great event. Luke had participated this event previously so it&amp;#8217;s very helpful to understand more about this event from his note.&lt;/p&gt;



&lt;p&gt;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&amp;#8217;d say, this year was not an exception. I thoroughly enjoyed the talks, and the whole experiences.&lt;/p&gt;



&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/@londonwebstandards8403&quot;&gt;Talks&lt;/a&gt; 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&amp;#8230;and a bit fun factor?&lt;/p&gt;



&lt;p&gt;The open talk was &lt;a href=&quot;https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/&quot;&gt;Anchor positioning by Bramus Van Damme&lt;/a&gt;. The walk-through on the feature with examples were pretty cool, especially the case of a popover&amp;#8230; with a little triangle (You&amp;#8217;ll know what I mean if you look up the talk). &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1808823&quot;&gt;Igalia worked on popover for Firefox&lt;/a&gt; in 2024, sponsored by Google. It&amp;#8217;s really great to see that anchor positioning is in Firefox &amp;#8211; popover has now found its place.&lt;/p&gt;



&lt;p&gt;It was nice to hear that Igalians&amp;#8217; names were mentioned in the &lt;a href=&quot;https://2026.stateofthebrowser.com/speaker/jason-williams/&quot;&gt;Temporal talk by Jason Williams from Bloomberg&lt;/a&gt;. A big shout out to &lt;a href=&quot;https://www.igalia.com/team/pchimento&quot;&gt;Philip Chimento&lt;/a&gt;, &lt;a href=&quot;https://www.igalia.com/team/usharma&quot;&gt;Ujjwal Sharma&lt;/a&gt; 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 &lt;a href=&quot;https://blogs.igalia.com/compilers/2026/02/02/implementing-the-temporal-proposal-in-javascriptcore/&quot;&gt;Tim Chevalier&amp;#8217;s blog on &amp;#8220;Implementing the Temporal proposal in JavaScriptCore&amp;#8221;&lt;/a&gt; if you&amp;#8217;d like to find out more.&lt;/p&gt;



&lt;p&gt;The atmosphere of the event was friendly, inclusive and energetic. I was very happy bumping into some ex-colleagues and making new friends.&lt;/p&gt;



&lt;p&gt;One final note &amp;#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, &amp;#8220;&lt;a href=&quot;https://www.igalia.com/&quot;&gt;Igalia&lt;/a&gt;&amp;#8221; is a new name. I had a question like &amp;#8220;Oh, is it the company with rainbow colours in the sponsors?&amp;#8221;. Yes, &lt;em&gt;Igalia&lt;/em&gt; is &lt;em&gt;a private, worker-owned, employee-run cooperative model consultancy focused on open source software&lt;/em&gt;[&lt;a href=&quot;https://en.wikipedia.org/wiki/Igalia&quot;&gt;1&lt;/a&gt;]. And Igalia has been a part of the Interop Project since its inception in 2021. Here is Igalia&amp;#8217;s &amp;#8220;rainbowy&amp;#8221; logo :-).&lt;/p&gt;


&lt;div class=&quot;wp-block-image&quot;&gt;
&lt;figure class=&quot;aligncenter size-large is-resized&quot;&gt;&lt;img width=&quot;940&quot; height=&quot;426&quot; src=&quot;https://blogs.igalia.com/zsun/files/2026/03/igalia_logo-940x426.png&quot; alt=&quot;&quot; class=&quot;wp-image-844&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;


&lt;p&gt;&lt;/p&gt;
&lt;img src=&quot;https://stats.igalia.com/piwik.php?idsite=46&amp;rec=1&amp;url=https%3A%2F%2Fblogs.igalia.com%2Fzsun%2F2026%2F03%2F02%2Fa-day-in-state-of-the-browser-2026-conference%2F%3Fpk_campaign%3Dfeed%26pk_kwd%3Da-day-in-state-of-the-browser-2026-conference&amp;action_name=A%20Day%20in%20%26%238220%3BState%20of%20the%20Browser%202026%26%238221%3B%20Conference&amp;urlref=https%3A%2F%2Fblogs.igalia.com%2Fzsun%2Ffeed%2F&quot; width=&quot;0&quot; height=&quot;0&quot; alt=&quot;&quot; /&gt;        </description>
	<pubDate>Mon, 02 Mar 2026 18:11:17 +0000</pubDate>
	<dc:creator>zsun</dc:creator>
</item>
<item>
	<title>Fr&#233;d&#233;ric Wang: Stage d'impl&#233;mentation des normes Web (session 2026)</title>
	<guid>https://frederic-wang.fr//2026/02/25/stage-implementation-des-normes-web</guid>
	<link>https://frederic-wang.fr//2026/02/25/stage-implementation-des-normes-web/</link>
	<description>
&lt;p&gt;Les candidatures pour les &lt;a href=&quot;https://www.igalia.com/2026/02/27/Igalia-2026-Coding-Experience-Open-for-Applications.html&quot;&gt;&#171;&#160;stages de programmation informatique&#160;&#187;&lt;/a&gt; d&#8217;&lt;a href=&quot;https://www.igalia.com/about/&quot;&gt;Igalia&lt;/a&gt; sont officiellement ouvertes jusqu&#8217;&#224; d&#233;but avril. Ils offrent aux &#233;tudiant&#183;e&#183;s l&#8217;occasion de participer au d&#233;veloppement de logiciels libres tout en &#233;tant r&#233;mun&#233;r&#233;&#183;e&#183;s 7&#160;000&#160;&#8364; brut pour 450&#160;heures, r&#233;parties de juin &#224; d&#233;cembre 2026.&lt;/p&gt;

&lt;p&gt;Comme chaque ann&#233;e, j&#8217;encadrerai un&#183;e  &#233;tudiant&#183;e sur l&#8217;&#171;&#160;Impl&#233;mentation des normes Web&#160;&#187; (&lt;em&gt;Web Standards&lt;/em&gt; en anglais). L&#8217;objectif &#233;tant de modifier les navigateurs (&lt;a href=&quot;https://fr.wikipedia.org/wiki/Chromium&quot;&gt;Chromium&lt;/a&gt;, &lt;a href=&quot;https://fr.wikipedia.org/wiki/Mozilla_Firefox&quot;&gt;Firefox&lt;/a&gt; ou &lt;a href=&quot;https://fr.wikipedia.org/wiki/Safari_(navigateur_web)&quot;&gt;Safari&lt;/a&gt;&#8230;) afin d&#8217;am&#233;liorer le support de technologies Web (&lt;a href=&quot;https://fr.wikipedia.org/wiki/Hypertext_Markup_Language&quot;&gt;HTML&lt;/a&gt;, &lt;a href=&quot;https://fr.wikipedia.org/wiki/Feuilles_de_style_en_cascade&quot;&gt;CSS&lt;/a&gt;, &lt;a href=&quot;https://fr.wikipedia.org/wiki/Document_Object_Model&quot;&gt;DOM&lt;/a&gt;&#8230;). Il faudra notamment &#233;tudier les sp&#233;cifications correspondantes et &#233;crire des &lt;a href=&quot;https://web-platform-tests.org/&quot;&gt;tests de conformit&#233;&lt;/a&gt;. Notez bien que ce n&#8217;est &lt;em&gt;pas&lt;/em&gt; un stage de d&#233;veloppement Web mais de d&#233;veloppement &lt;a href=&quot;https://fr.wikipedia.org/wiki/C%2B%2B&quot;&gt;C++&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Un des objectifs de ce programme &#233;tant de lutter contre les discriminations professionnelles, tout le monde (y compris celles et ceux qui se sentent sous-repr&#233;sent&#233;&#183;e&#183;s dans le secteur informatique) sont invit&#233;&#183;e&#183;s &#224; candidater. Depuis 2016, mon &#233;quipe &#171;&#160;Web Platform&#160;&#187; a ainsi encadr&#233; 13 &#233;tudiant&#183;e&#183;s de diff&#233;rents pays dans le monde (Espagne, Inde, Italie, &lt;a href=&quot;https://www.azabani.com/2020/09/27/my-internship-with-igalia.html&quot;&gt;Australie&lt;/a&gt;, Cameroun, Chine, Vietnam, Angleterre et &#201;tats-Unis) dont 7 femmes. L&#8217;ann&#233;e derni&#232;re, nous avions s&#233;lectionn&#233; &lt;a href=&quot;https://github.com/Charlotte-McCleary&quot;&gt;Charlotte McCleary&lt;/a&gt;, une Am&#233;ricaine non-voyante qui a travaill&#233; sur l&#8217;accessibilit&#233; dans Firefox au cours de son stage et a depuis rejoint  &lt;a href=&quot;https://fizz.studio/about.html&quot;&gt;Fizz Studio&lt;/a&gt;. J&#8217;aimerais encourager les &#233;tudiant&#183;e&#183;s Sourd&#183;e&#183;s &#224; postuler et donne dans la vid&#233;o ci-dessous une br&#232;ve pr&#233;sentation du programme en LSF (en esp&#233;rant que ce soit compr&#233;hensible et que vous serez indulgents avec mon pi&#232;tre niveau en langue des signes &#128517;):&lt;/p&gt;

&lt;div&gt;&lt;/div&gt;


&lt;p&gt;Si vous &#234;tes int&#233;r&#233;ss&#233;&#183;e&#183;s, &lt;a href=&quot;https://www.igalia.com/coding-experience/&quot;&gt;remplissez ce formulaire&lt;/a&gt; en cochant la case &lt;em&gt;Web Standards&lt;/em&gt; et en pr&#233;cisant &#233;ventuellement que vous avez trouv&#233; cette offre via mon site Web. Enfin, si vous connaissez des &#233;tudiant&#183;e&#183;s qui pourraient participer, n&#8217;h&#233;sitez pas &#224; partager l&#8217;annonce !&lt;/p&gt;        </description>
	<pubDate>Tue, 24 Feb 2026 23:00:00 +0000</pubDate>
</item>
<item>
	<title>Igalia WebKit Team: WebKit Igalia Periodical #57</title>
	<guid>https://blogs.igalia.com/webkit/blog/2026/wip-57/</guid>
	<link>https://blogs.igalia.com/webkit/blog/2026/wip-57/</link>
	<description>
&lt;p&gt;Update on what happened in WebKit in the week from February 9 to February 23.&lt;/p&gt;
&lt;p&gt;
In this week we have a nice fix for video streams timestamps, a fix
for a PDF rendering regression, support for rendering video buffers
provided by Qualcomm video decoders, and a fix for a font selection
issue. Also notable we had a new WPE Android release, and the libsoup
3.6.6 release.
&lt;/p&gt;
&lt;h2 id=&quot;cross-port-cat&quot;&gt;Cross-Port &#128049;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;Added a &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/307348@main&quot;&gt;new &lt;code&gt;webkit_feature_list_find()&lt;/code&gt; convenience function&lt;/a&gt; to the public API, which searches for a &lt;a rel=&quot;external&quot; href=&quot;https://webkitgtk.org/reference/webkitgtk/2.51.91/struct.Feature.html&quot;&gt;WebKitFeature&lt;/a&gt; given its identifier.&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;multimedia-movie-camera&quot;&gt;Multimedia &#127909;&lt;/h3&gt;
&lt;div class=&quot;wip-description&quot;&gt;
&lt;p&gt;GStreamer-based multimedia support for WebKit, including (but not limited to) playback, capture, WebAudio, WebCodecs, and WebRTC.&lt;/p&gt;
&lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/307359@main&quot;&gt;Opportunistically fix decoding timestamps to prevent deletion of preexisting samples when PTS doesn't conflict&lt;/a&gt;, fixing potential glitches when inserting videos (eg: ad insertion).&lt;/p&gt;
  &lt;/div&gt;
&lt;h3 id=&quot;graphics-frame-photo&quot;&gt;Graphics &#128444;&#65039;&lt;/h3&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/308033@main&quot;&gt;Fixed&lt;/a&gt; a &lt;a rel=&quot;external&quot; href=&quot;https://bugs.webkit.org/show_bug.cgi?id=306621&quot;&gt;PDF rendering regression&lt;/a&gt; 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.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/307174@main&quot;&gt;Added support&lt;/a&gt; for rendering video buffers provided by Qualcomm hardware-accelerated decoders, with aid from the &lt;a rel=&quot;external&quot; href=&quot;https://registry.khronos.org/OpenGL/extensions/EXT/EXT_YUV_target.txt&quot;&gt;EXT_YUV_target&lt;/a&gt; OpenGL extension.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/307565@main&quot;&gt;Fixed&lt;/a&gt; the font selection issue that the system fallback font cache mixed up different font styles.&lt;/p&gt;
  &lt;/div&gt;
&lt;h2 id=&quot;releases-package&quot;&gt;Releases &#128230;&#65039;&lt;/h2&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/wpe-android/releases/tag/v0.3.2&quot;&gt;WPE Android 0.3.2&lt;/a&gt; has been released, and prebuilt packages are available &lt;a rel=&quot;external&quot; href=&quot;https://central.sonatype.com/artifact/org.wpewebkit.wpeview/wpeview/&quot;&gt;at the Maven Central repository&lt;/a&gt;. This is a stable maintenance release which updates WPE WebKit to 2.50.5, which is the most recent stable release.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://gitlab.gnome.org/GNOME/libsoup/-/releases/3.6.6&quot;&gt;libsoup 3.6.6&lt;/a&gt; has been released with numerous bug and security fixes.&lt;/p&gt;
  &lt;/div&gt;
&lt;div class=&quot;wip-end&quot;&gt;
&lt;p&gt;That&#8217;s all for this week!&lt;/p&gt;
&lt;/div&gt;        </description>
	<pubDate>Mon, 23 Feb 2026 19:52:49 +0000</pubDate>
	<dc:creator>Igalia WebKit Team</dc:creator>
</item>
<item>
	<title>Mauricio Faria de Oliveira: page_owner Part 2: optimizing output</title>
	<guid>https://mfo.dev.br/posts/2026-02-23-page_owner-part-2-optimizing-output/</guid>
	<link>https://mfo.dev.br/posts/2026-02-23-page_owner-part-2-optimizing-output/</link>
	<description>
&lt;p&gt;This blog post is &lt;a href=&quot;https://mfo.dev.br/tags/page_owner/&quot;&gt;part of a series&lt;/a&gt; about the &lt;code&gt;page_owner&lt;/code&gt; debug feature in the Linux memory management subsystem, related to the talk &lt;em&gt;&lt;a href=&quot;http://www.youtube.com/watch?v=qFdjO3t5F9I&quot;&gt;Improving &lt;code&gt;page_owner&lt;/code&gt; for profiling and monitoring memory usage per allocation stack trace&lt;/a&gt;&lt;/em&gt; presented at &lt;a href=&quot;https://lpc.events/event/19/contributions/2202/&quot;&gt;Linux Plumbers Conference 2025&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mfo.dev.br/posts/2026-02-23-page_owner-part-1-quick-introduction/&quot;&gt;Part 1&lt;/a&gt; is a quick introduction to &lt;code&gt;page_owner&lt;/code&gt; and its debugfs files.&lt;/li&gt;
&lt;li&gt;Part 2 describes challenges with processing &lt;code&gt;page_owner&lt;/code&gt; files over time and a solution with new debugfs files in Linux v6.19.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;problem-stack-traces-over-time&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#problem-stack-traces-over-time&quot;&gt;
 Problem: stack traces over time
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;As described in Part 1, &lt;code&gt;page_owner&lt;/code&gt;&amp;rsquo;s debugfs files contain stack traces for the most part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sys/kernel/debug/page_owner&lt;/code&gt; has one stack trace per allocated page, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/kernel/debug/page_owner_stacks/show_stacks&lt;/code&gt; lists the stack traces that allocated pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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&amp;rsquo;t be an issue if done only once, but it does pose a concern if done repeatedly.&lt;/p&gt;
&lt;p&gt;Take the processing of stack traces one step further and that concern materializes into a technical problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How to store information (say, number of pages) &lt;em&gt;per-stack trace&lt;/em&gt; and &lt;em&gt;over time&lt;/em&gt;?&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;For that, the stack trace must become a &lt;em&gt;key&lt;/em&gt; to be assigned &lt;em&gt;values&lt;/em&gt; 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).&lt;/p&gt;
&lt;h1 id=&quot;workaround-stack-trace-hashing&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#workaround-stack-trace-hashing&quot;&gt;
 Workaround: stack trace hashing
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;One possible solution to this problem is hashing the stack traces and using the resulting hash values as keys.&lt;/p&gt;
&lt;p&gt;However, this is inefficient with &lt;code&gt;page_owner&lt;/code&gt; since there is significant duplication of stack traces on both debugfs files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;page_owner&lt;/code&gt; file, even on a single read, some stack traces may have tens/hundreds/thousands of duplicates; and they compound on multiple reads over time.&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;show_stacks&lt;/code&gt; file, there are no duplicates on a single read, but duplicates frequently happen on multiple reads over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &amp;ndash; wasting time and computational resources.&lt;/p&gt;
&lt;p&gt;For illustration purposes, compare the execution time of &lt;a href=&quot;https://mfo.dev.br/posts/2026-02-23-page_owner-part-2-optimizing-output/#script-1-page_owner-to-show_stackspy&quot;&gt;script &lt;code&gt;page_owner-to-show_stacks.py&lt;/code&gt;&lt;/a&gt;, which parses the &lt;code&gt;page_owner&lt;/code&gt; file hashing the stack traces (with the &lt;a href=&quot;https://github.com/Cyan4973/xxHash?tab=readme-ov-file#benchmarks&quot;&gt;extremely fast&lt;/a&gt; &lt;code&gt;XXH3_64&lt;/code&gt;) and accumulating the number of pages per stack trace, reporting it at the end &amp;ndash; basically mimicking &lt;code&gt;show_stacks&lt;/code&gt; &amp;ndash; with just reading the equivalent file.&lt;/p&gt;
&lt;p&gt;The single read with hashing is 38.55 times slower:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# time ./page_owner-to-show_stacks.py &amp;lt;/sys/kernel/debug/page_owner &amp;gt;/dev/null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;real 0m1.542s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;user 0m1.486s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sys 0m0.057s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# time cat /sys/kernel/debug/page_owner_stacks/show_stacks &amp;gt;/dev/null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;real 0m0.040s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;user 0m0.000s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sys 0m0.040s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, considering the single-read results with the &lt;code&gt;page_owner&lt;/code&gt; file, it&amp;rsquo;s not compelling to use it for multiple reads. However, multiple reads of the &lt;code&gt;show_stacks&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;Check the execution time of &lt;a href=&quot;https://mfo.dev.br/posts/2026-02-23-page_owner-part-2-optimizing-output/#script-2-show_stacks-over-timepy&quot;&gt;script &lt;code&gt;show_stacks-over-time.py&lt;/code&gt;&lt;/a&gt;, which parses copies of &lt;code&gt;show_stacks&lt;/code&gt; (collected over time), similarly hashing the stack traces and storing the number of pages per stack trace over time (that is, per copy).&lt;/p&gt;
&lt;p&gt;For 100 copies, the execution time is almost 1 second:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# time ./show_stacks-over-time.py show_stacks.{1..100} &amp;gt;/dev/null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;real 0m0.944s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;user 0m0.900s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sys 0m0.044s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is a great improvement (comparing to processing a single read of the &lt;code&gt;page_owner&lt;/code&gt; 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).&lt;/p&gt;
&lt;h1 id=&quot;solution-stack-trace-handle-numbers&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#solution-stack-trace-handle-numbers&quot;&gt;
 Solution: stack trace handle numbers
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;The hashing of stack traces is only required in order to obtain a &lt;em&gt;unique identifier&lt;/em&gt; for each stack trace, so that it can be used as a &lt;em&gt;key&lt;/em&gt;. However, if such an identifier were already available, the hashing step (and associated computational cost) could be avoided altogether.&lt;/p&gt;
&lt;p&gt;Fortunately, that is now the case with Linux 6.19! The stack trace storage used by &lt;code&gt;page_owner&lt;/code&gt; ( &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/stackdepot.h?h=v6.19&quot;&gt;&lt;code&gt;stackdepot&lt;/code&gt;&lt;/a&gt;) provides a &lt;em&gt;handle number&lt;/em&gt; to uniquely refer to stack traces &amp;ndash; which meets the requirement.&lt;/p&gt;
&lt;p&gt;Linux 6.19 &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/Documentation/mm/page_owner.rst?h=v6.19&amp;id=0de9a442eeba4a6435af74120822b10b12ab8449&quot;&gt;contains two new debugfs files&lt;/a&gt; with &lt;em&gt;handle numbers&lt;/em&gt; for optimized output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sys/kernel/debug/page_owner_stacks/show_handles&lt;/code&gt;: this lists &lt;code&gt;nr_base_pages:&lt;/code&gt; per &lt;code&gt;handle:&lt;/code&gt; (instead of per stack trace as in &lt;code&gt;show_stacks&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/kernel/debug/page_owner_stacks/show_stacks_handles&lt;/code&gt;: this lists &lt;code&gt;handle:&lt;/code&gt; per stack trace (for resolving handle numbers to stack traces)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the example in the previous post, &lt;code&gt;show_stacks&lt;/code&gt; contains:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# cat /sys/kernel/debug/page_owner_stacks/show_stacks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; get_page_from_freelist+0x1416/0x1600
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __alloc_frozen_pages_noprof+0x18c/0x1000
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; alloc_pages_mpol+0x43/0x100
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; folio_alloc_noprof+0x56/0xa0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; page_cache_ra_unbounded+0xd9/0x230
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; filemap_fault+0x305/0x1000
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __do_fault+0x2c/0xb0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __handle_mm_fault+0x6f4/0xeb0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; handle_mm_fault+0xd9/0x210
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; do_user_addr_fault+0x205/0x600
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; exc_page_fault+0x61/0x130
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; asm_exc_page_fault+0x26/0x30
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;nr_base_pages: &lt;span&gt;9643&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While, for the same snippet, &lt;code&gt;show_handles&lt;/code&gt; contains:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;...
handle: 27000838
nr_base_pages: 9643

...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the handle number can be resolved to a stack trace with &lt;code&gt;show_stacks_handles&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;...
 get_page_from_freelist+0x1416/0x1600
 __alloc_frozen_pages_noprof+0x18c/0x1000
 alloc_pages_mpol+0x43/0x100
 folio_alloc_noprof+0x56/0xa0
 page_cache_ra_unbounded+0xd9/0x230
 filemap_fault+0x305/0x1000
 __do_fault+0x2c/0xb0
 __handle_mm_fault+0x6f4/0xeb0
 handle_mm_fault+0xd9/0x210
 do_user_addr_fault+0x205/0x600
 exc_page_fault+0x61/0x130
 asm_exc_page_fault+0x26/0x30
handle: 27000838

...
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;comparison-show_stacks-vs-show_handles&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#comparison-show_stacks-vs-show_handles&quot;&gt;
 Comparison: &lt;code&gt;show_stacks&lt;/code&gt; vs. &lt;code&gt;show_handles&lt;/code&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;From the previous post, for &lt;code&gt;show_stacks&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;# time cat /sys/kernel/debug/page_owner_stacks/show_stacks \
 | wc --bytes | numfmt --to=iec
402K

real 0m0.042s
user 0m0.004s
sys 0m0.046s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, for &lt;code&gt;show_handles&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# time cat /sys/kernel/debug/page_owner_stacks/show_handles \&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; | wc --bytes | numfmt --to&lt;span&gt;=&lt;/span&gt;iec
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;31K
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;real 0m0.015s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;user 0m0.004s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sys 0m0.019s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is only 7.7% of the size and 35.7% of the time! Nice improvements.&lt;/p&gt;
&lt;p&gt;Finally, compare the execution time of &lt;a href=&quot;https://mfo.dev.br/posts/2026-02-23-page_owner-part-2-optimizing-output/#script-3-show_handles-over-timepy&quot;&gt;script &lt;code&gt;show_handles-over-time.py&lt;/code&gt;&lt;/a&gt; with the previous one; it uses handle numbers as keys for stack traces instead of hashing them.&lt;/p&gt;
&lt;p&gt;For 100 copies, the execution time is approximately 1/3 of a second, roughly 3 times faster.&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# time ./show_handles-over-time.py show_stacks_handles show_handles.ln.{1..100} &amp;gt;/dev/nul&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;real 0m0.348s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;user 0m0.319s
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;sys 0m0.030s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&quot;conclusion&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#conclusion&quot;&gt;
 Conclusion
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;The original debugfs files provided by &lt;code&gt;page_owner&lt;/code&gt; consist mainly of stack traces, which isn&amp;rsquo;t an efficient format for reading and processing repeatedly.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;In order to address this issue, Linux 6.19 provides new debugfs files for &lt;code&gt;page_owner&lt;/code&gt; with &lt;em&gt;handle numbers&lt;/em&gt;, which are unique identifiers for stack traces and can be used as keys, instead of hashing.&lt;/p&gt;
&lt;p&gt;This optimizes the reading and processing of &lt;code&gt;page_owner&lt;/code&gt; 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.&lt;/p&gt;
&lt;h1 id=&quot;scripts&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#scripts&quot;&gt;
 Scripts
 &lt;/a&gt;
&lt;/h1&gt;
&lt;h2 id=&quot;script-1-page_owner-to-show_stackspy&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#script-1-page_owner-to-show_stackspy&quot;&gt;
 Script 1: &lt;code&gt;page_owner-to-show_stacks.py&lt;/code&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# SPDX-License-Identifier: GPL-2.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Script to parse /sys/kernel/debug/page_owner, hashing the stack trace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# of each page and accumulating the number of pages per stack trace.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# At the end, print all stack traces and their number of pages in a format&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# like /sys/kernel/debug/page_owner_stacks/show_stacks.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Usage: page_owner-to-show_stacks.py &amp;lt;/sys/kernel/debug/page_owner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Author: Mauricio Faria de Oliveira &amp;lt;mfo@igalia.com&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; xxhash
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_page &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^Page allocated via order ([0-9]+)'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_stack &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^ '&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_empty &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^$'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pages &lt;span&gt;=&lt;/span&gt; {} &lt;span&gt;# key -&amp;gt; number of pages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;stacks &lt;span&gt;=&lt;/span&gt; {} &lt;span&gt;# key -&amp;gt; stack trace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; line &lt;span&gt;in&lt;/span&gt; sys&lt;span&gt;.&lt;/span&gt;stdin:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# middle lines: try stack trace first as it occurs more often&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; re_stack&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stack &lt;span&gt;=&lt;/span&gt; stack &lt;span&gt;+&lt;/span&gt; line
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# first line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;match&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; re_page&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; &lt;span&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; order &lt;span&gt;=&lt;/span&gt; int(&lt;span&gt;match&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;group(&lt;span&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stack &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; re_empty&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; key &lt;span&gt;=&lt;/span&gt; xxhash&lt;span&gt;.&lt;/span&gt;xxh3_64_hexdigest(stack)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; nr_pages &lt;span&gt;=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt; &lt;span&gt;**&lt;/span&gt; order
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; key &lt;span&gt;in&lt;/span&gt; pages:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; pages[key] &lt;span&gt;+=&lt;/span&gt; nr_pages
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; pages[key] &lt;span&gt;=&lt;/span&gt; nr_pages
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stacks[key] &lt;span&gt;=&lt;/span&gt; stack
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; key &lt;span&gt;in&lt;/span&gt; stacks&lt;span&gt;.&lt;/span&gt;keys():
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; print(&lt;span&gt;&quot; &quot;&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; stacks[key]&lt;span&gt;.&lt;/span&gt;strip())
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; print(&lt;span&gt;&quot;nr_base_pages: &quot;&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; str(pages[key]))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; print()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&quot;script-2-show_stacks-over-timepy&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#script-2-show_stacks-over-timepy&quot;&gt;
 Script #2: &lt;code&gt;show_stacks-over-time.py&lt;/code&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# SPDX-License-Identifier: GPL-2.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Script to parse /sys/kernel/debug/page_owner_stacks/show_stacks in multiple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# reads, hashing each stack trace and recording the number of base pages per&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# stack trace in each read.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# At the end, print all stack traces and their number of pages in each read.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Usage: show_stacks-over-time.py &amp;lt;read1&amp;gt; &amp;lt;read2&amp;gt; &amp;lt;read3&amp;gt; ... &amp;lt;read N&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Author: Mauricio Faria de Oliveira &amp;lt;mfo@igalia.com&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; xxhash
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_pages &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^nr_base_pages: ([0-9]+)'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_stack &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^ '&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_empty &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^$'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;stacks &lt;span&gt;=&lt;/span&gt; {}	&lt;span&gt;# key -&amp;gt; stack trace (all reads)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pages &lt;span&gt;=&lt;/span&gt; {}	&lt;span&gt;# key -&amp;gt; array of number of pages (per read)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;read &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;	&lt;span&gt;# number of the current read&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt; len(sys&lt;span&gt;.&lt;/span&gt;argv) &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	exit(&lt;span&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;files &lt;span&gt;=&lt;/span&gt; sys&lt;span&gt;.&lt;/span&gt;argv[&lt;span&gt;1&lt;/span&gt;:]
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;nr_files &lt;span&gt;=&lt;/span&gt; len(files)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; file &lt;span&gt;in&lt;/span&gt; files:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	&lt;span&gt;with&lt;/span&gt; open(file, &lt;span&gt;'r'&lt;/span&gt;) &lt;span&gt;as&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		stack &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		&lt;span&gt;for&lt;/span&gt; line &lt;span&gt;in&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# first lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; re_stack&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				stack &lt;span&gt;=&lt;/span&gt; stack &lt;span&gt;+&lt;/span&gt; line
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# next to last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;match&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; re_pages&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; &lt;span&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				nr_pages &lt;span&gt;=&lt;/span&gt; int(&lt;span&gt;match&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;group(&lt;span&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; re_empty&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				key &lt;span&gt;=&lt;/span&gt; xxhash&lt;span&gt;.&lt;/span&gt;xxh3_64_hexdigest(stack)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;if&lt;/span&gt; key &lt;span&gt;not&lt;/span&gt; &lt;span&gt;in&lt;/span&gt; stacks:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;					stacks[key] &lt;span&gt;=&lt;/span&gt; stack;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;if&lt;/span&gt; key &lt;span&gt;not&lt;/span&gt; &lt;span&gt;in&lt;/span&gt; pages:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;					pages[key] &lt;span&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				pages[key][read] &lt;span&gt;=&lt;/span&gt; nr_pages
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				stack &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		read &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; key &lt;span&gt;in&lt;/span&gt; stacks&lt;span&gt;.&lt;/span&gt;keys():
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print(&lt;span&gt;&quot; &quot;&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; stacks[key]&lt;span&gt;.&lt;/span&gt;strip())
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	pages_per_read &lt;span&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	&lt;span&gt;for&lt;/span&gt; read &lt;span&gt;in&lt;/span&gt; range(nr_files):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		nr_pages &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		&lt;span&gt;if&lt;/span&gt; read &lt;span&gt;in&lt;/span&gt; pages[key]:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			nr_pages &lt;span&gt;=&lt;/span&gt; pages[key][read]
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		pages_per_read&lt;span&gt;.&lt;/span&gt;append(str(nr_pages))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print(&lt;span&gt;' '&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;join(pages_per_read))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&quot;script-3-show_handles-over-timepy&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#script-3-show_handles-over-timepy&quot;&gt;
 Script #3: &lt;code&gt;show_handles-over-time.py&lt;/code&gt;
 &lt;/a&gt;
&lt;/h2&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# SPDX-License-Identifier: GPL-2.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Script to parse /sys/kernel/debug/page_owner_stacks/show_handles in multiple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# reads, collecting handle numbers and recording the number of base pages per&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# handle number in each read.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# At the end, print all stack traces and their number of pages in each read,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# resolving handle numbers with /sys/kernel/debug/page_owner_stacks/show_stacks_handles.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Usage: show_handles-over-time.py &amp;lt;show_stacks_handles&amp;gt; &amp;lt;read1&amp;gt; &amp;lt;read2&amp;gt; &amp;lt;read3&amp;gt; ... &amp;lt;read N&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# Author: Mauricio Faria de Oliveira &amp;lt;mfo@igalia.com&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt; xxhash
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_pages &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^nr_base_pages: ([0-9]+)'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_stack &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^ '&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_empty &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^$'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;re_handle &lt;span&gt;=&lt;/span&gt; re&lt;span&gt;.&lt;/span&gt;compile(&lt;span&gt;'^handle: ([0-9]+)'&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;stacks &lt;span&gt;=&lt;/span&gt; {}	&lt;span&gt;# handle number -&amp;gt; stack trace (all reads)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;pages &lt;span&gt;=&lt;/span&gt; {}	&lt;span&gt;# handle number -&amp;gt; array of number of pages (per read)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;read &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;	&lt;span&gt;# number of the current read&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;if&lt;/span&gt; len(sys&lt;span&gt;.&lt;/span&gt;argv) &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	exit(&lt;span&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;resolver &lt;span&gt;=&lt;/span&gt; sys&lt;span&gt;.&lt;/span&gt;argv[&lt;span&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;files &lt;span&gt;=&lt;/span&gt; sys&lt;span&gt;.&lt;/span&gt;argv[&lt;span&gt;2&lt;/span&gt;:]
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;nr_files &lt;span&gt;=&lt;/span&gt; len(files)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; file &lt;span&gt;in&lt;/span&gt; files:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	&lt;span&gt;with&lt;/span&gt; open(file, &lt;span&gt;'r'&lt;/span&gt;) &lt;span&gt;as&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		&lt;span&gt;for&lt;/span&gt; line &lt;span&gt;in&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# first line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;match&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; re_handle&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; &lt;span&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				handle &lt;span&gt;=&lt;/span&gt; int(&lt;span&gt;match&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;group(&lt;span&gt;1&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# next to last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;match&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; re_pages&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; &lt;span&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				nr_pages &lt;span&gt;=&lt;/span&gt; int(&lt;span&gt;match&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;group(&lt;span&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;# last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			&lt;span&gt;if&lt;/span&gt; re_empty&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				key &lt;span&gt;=&lt;/span&gt; handle
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;if&lt;/span&gt; key &lt;span&gt;not&lt;/span&gt; &lt;span&gt;in&lt;/span&gt; pages:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;					pages[key] &lt;span&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				pages[key][read] &lt;span&gt;=&lt;/span&gt; nr_pages
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;				&lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		read &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;with&lt;/span&gt; open(resolver, &lt;span&gt;'r'&lt;/span&gt;) &lt;span&gt;as&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stack &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;for&lt;/span&gt; line &lt;span&gt;in&lt;/span&gt; fd:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# first line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; re_stack&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stack &lt;span&gt;=&lt;/span&gt; stack &lt;span&gt;+&lt;/span&gt; line
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# next to last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;match&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; re_handle&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line)
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; &lt;span&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; handle &lt;span&gt;=&lt;/span&gt; int(&lt;span&gt;match&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;group(&lt;span&gt;1&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;# last line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;if&lt;/span&gt; re_empty&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;(line):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stacks[handle] &lt;span&gt;=&lt;/span&gt; stack
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; stack &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt; key &lt;span&gt;in&lt;/span&gt; pages&lt;span&gt;.&lt;/span&gt;keys():
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print(&lt;span&gt;&quot; &quot;&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; stacks[key]&lt;span&gt;.&lt;/span&gt;strip())
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	pages_per_read &lt;span&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	&lt;span&gt;for&lt;/span&gt; read &lt;span&gt;in&lt;/span&gt; range(nr_files):
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		nr_pages &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		&lt;span&gt;if&lt;/span&gt; read &lt;span&gt;in&lt;/span&gt; pages[key]:
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;			nr_pages &lt;span&gt;=&lt;/span&gt; pages[key][read]
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;		pages_per_read&lt;span&gt;.&lt;/span&gt;append(str(nr_pages))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print(&lt;span&gt;' '&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;join(pages_per_read))
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;	print()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        </description>
	<pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Mauricio Faria de Oliveira: page_owner Part 1: a quick introduction</title>
	<guid>https://mfo.dev.br/posts/2026-02-23-page_owner-part-1-quick-introduction/</guid>
	<link>https://mfo.dev.br/posts/2026-02-23-page_owner-part-1-quick-introduction/</link>
	<description>
&lt;p&gt;This blog post is &lt;a href=&quot;https://mfo.dev.br/tags/page_owner/&quot;&gt;part of a series&lt;/a&gt; about the &lt;code&gt;page_owner&lt;/code&gt; debug feature in the Linux memory management subsystem, related to the talk &lt;em&gt;&lt;a href=&quot;http://www.youtube.com/watch?v=qFdjO3t5F9I&quot;&gt;Improving &lt;code&gt;page_owner&lt;/code&gt; for profiling and monitoring memory usage per allocation stack trace&lt;/a&gt;&lt;/em&gt; presented at &lt;a href=&quot;https://lpc.events/event/19/contributions/2202/&quot;&gt;Linux Plumbers Conference 2025&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;what-is-page_owner&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#what-is-page_owner&quot;&gt;
 What is &lt;code&gt;page_owner&lt;/code&gt;?
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In the Linux kernel, &lt;code&gt;page_owner&lt;/code&gt; is a debug feature that tracks the memory allocation (and release) of pages in the system &amp;ndash; so as to tell the &amp;lsquo;&lt;em&gt;owner of a page&lt;/em&gt;&amp;rsquo; ;-).&lt;/p&gt;
&lt;p&gt;For each memory allocation, &lt;code&gt;page_owner&lt;/code&gt; stores its order, &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/gfp_types.h?h=v6.19&quot;&gt;GFP flags&lt;/a&gt;, 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).&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;page_owner&lt;/code&gt;, one can find out &amp;ldquo;&lt;em&gt;What allocated this page?&lt;/em&gt;&amp;rdquo; and &amp;ldquo;&lt;em&gt;How many pages are allocated by this particular stack trace, PID, or comm&lt;/em&gt;&amp;rdquo;, for example.&lt;/p&gt;
&lt;p&gt;This is &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/page_owner.c?h=v6.19#n24&quot;&gt;struct page_owner&lt;/a&gt; in Linux &lt;code&gt;v6.19&lt;/code&gt;. It stores additional information per-page, as an extension of &lt;code&gt;struct page&lt;/code&gt; with &lt;code&gt;CONFIG_PAGE_EXTENSION&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;struct&lt;/span&gt; page_owner {
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;unsigned&lt;/span&gt; &lt;span&gt;short&lt;/span&gt; order;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;short&lt;/span&gt; last_migrate_reason;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;gfp_t&lt;/span&gt; gfp_mask;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;depot_stack_handle_t&lt;/span&gt; handle;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;depot_stack_handle_t&lt;/span&gt; free_handle;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; u64 ts_nsec;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; u64 free_ts_nsec;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;char&lt;/span&gt; comm[TASK_COMM_LEN];
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;pid_t&lt;/span&gt; pid;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;pid_t&lt;/span&gt; tgid;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;pid_t&lt;/span&gt; free_pid;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;span&gt;pid_t&lt;/span&gt; free_tgid;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&quot;usage&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#usage&quot;&gt;
 Usage
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In order to use &lt;code&gt;page_owner&lt;/code&gt;, build the kernel with &lt;code&gt;CONFIG_PAGE_OWNER=y&lt;/code&gt; (see &lt;code&gt;mm/Kconfig.debug&lt;/code&gt;) and boot the kernel with &lt;code&gt;page_owner=on&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The debugfs file &lt;code&gt;/sys/kernel/debug/page_owner&lt;/code&gt; provides the information in &lt;code&gt;struct page_owner&lt;/code&gt; for every page, listed per &lt;code&gt;PFN&lt;/code&gt; (page frame number).&lt;/p&gt;
&lt;p&gt;This example shows the entry for a page (line continuation added for clarity) &amp;ndash; it tells &amp;ldquo;&lt;em&gt;What allocated this page?&lt;/em&gt;&amp;rdquo;:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;# cat /sys/kernel/debug/page_owner
...
Page allocated via order 0, \
 mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), \
 pid 5640, tgid 5640 (stress-ng-brk), ts 414987114269 ns
PFN 0x114 type Unmovable Block 0 type Unmovable Flags 0x200(workingset|node=0|zone=0)
 get_page_from_freelist+0x1416/0x1600
 __alloc_frozen_pages_noprof+0x18c/0x1000
 alloc_pages_mpol+0x43/0x100
 new_slab+0x349/0x460
 ___slab_alloc+0x811/0xd90
 __kmem_cache_alloc_bulk+0xb8/0x1f0
 __prefill_sheaf_pfmemalloc+0x42/0x90
 kmem_cache_prefill_sheaf+0xa9/0x240
 mas_preallocate+0x32f/0x420
 __split_vma+0xdc/0x300
 vms_gather_munmap_vmas+0xa4/0x240
 do_vmi_align_munmap+0xe9/0x180
 do_vmi_munmap+0xcb/0x160
 __vm_munmap+0xa7/0x150
 __x64_sys_munmap+0x16/0x20
 do_syscall_64+0xa4/0x310

...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One can use &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/mm/page_owner_sort.c?h=v6.19&quot;&gt;tools/mm/page_owner_sort&lt;/a&gt; to process the information in the file, or come up with custom commands, scripts, or programs.&lt;/p&gt;
&lt;p&gt;For example: calculate the total size of pages allocated by &lt;code&gt;stress-ng-brk&lt;/code&gt; with any order, in MiB:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# COMM=stress-ng-brk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# cat /sys/kernel/debug/page_owner \&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; | awk -F &lt;span&gt;'[ ,]'&lt;/span&gt; &lt;span&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt; &lt;span&gt;'/^Page allocated via order .* \('&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;COMM&lt;span&gt;}&lt;/span&gt;&lt;span&gt;'\)/ { PAGES+=2^$5 } 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; END { print PAGES*4096/2**20 &quot; MiB&quot; }'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0.0429688 MiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;More information about &lt;code&gt;page_owner&lt;/code&gt; is available in &lt;a href=&quot;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/Documentation/mm/page_owner.rst?h=v6.19&quot;&gt;Documentation/mm/page_owner.rst&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;problem-output-size&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#problem-output-size&quot;&gt;
 Problem: output size
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In the &lt;code&gt;page_owner&lt;/code&gt; file, note the significant amount of text that is produced &lt;em&gt;per-page&lt;/em&gt;: 745 bytes, in the example above.&lt;/p&gt;
&lt;p&gt;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! (&lt;code&gt;745 [bytes/page] * (2**30 [bytes of RAM] / 4096 [bytes/page]) / 2**20 [bytes/MiB]&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;For validation, a test VM with 1 GiB of RAM after just a warm-up level of stress (&lt;code&gt;stress-ng --sequential --timeout 1&lt;/code&gt;) produced 125 MiB, which was not quick to read even in idle state:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;# time cat /sys/kernel/debug/page_owner \
 | wc --bytes | numfmt --to=iec
125M

real 0m3.009s
user 0m0.512s
sys 0m3.542s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this might not be a serious issue for reading and processing the file only once, it can likely impact a sequence of operations.&lt;/p&gt;
&lt;h1 id=&quot;alternative-optimized-output&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#alternative-optimized-output&quot;&gt;
 Alternative: optimized output
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Fortunately, another debugfs file, &lt;code&gt;/sys/kernel/debug/page_owner_stacks/show_stacks&lt;/code&gt;, provides an optimized output for obtaining the memory usage per stack trace. Even though it doesn&amp;rsquo;t address all needs as the generic output, it resembles the default operation of &lt;code&gt;page_owner_sort&lt;/code&gt; (without &lt;code&gt;PFN&lt;/code&gt; lines) and provides an often interesting information for kernel development or analysis.&lt;/p&gt;
&lt;p&gt;This example shows the entry for a stack trace &amp;ndash; it tells &amp;ldquo;&lt;em&gt;How many pages are allocated by this particular stack trace?&lt;/em&gt;&amp;rdquo;&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;# cat /sys/kernel/debug/page_owner_stacks/show_stacks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; get_page_from_freelist+0x1416/0x1600
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __alloc_frozen_pages_noprof+0x18c/0x1000
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; alloc_pages_mpol+0x43/0x100
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; folio_alloc_noprof+0x56/0xa0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; page_cache_ra_unbounded+0xd9/0x230
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; filemap_fault+0x305/0x1000
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __do_fault+0x2c/0xb0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; __handle_mm_fault+0x6f4/0xeb0
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; handle_mm_fault+0xd9/0x210
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; do_user_addr_fault+0x205/0x600
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; exc_page_fault+0x61/0x130
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; asm_exc_page_fault+0x26/0x30
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;nr_base_pages: &lt;span&gt;9643&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;nr_base_pages&lt;/code&gt; field tells the number of base pages (i.e., not huge pages) allocated by a stack trace. So, this particular stack trace for &lt;em&gt;readahead&lt;/em&gt; (&lt;code&gt;page_cache_ra_unbounded()&lt;/code&gt;) has allocated approximately 37 MiB (&lt;code&gt;9643 [pages] * 4096 [bytes/page] / 2**20 [ bytes/MiB]&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;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):&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;# time cat /sys/kernel/debug/page_owner_stacks/show_stacks \
 | wc --bytes | numfmt --to=iec
402K

real 0m0.042s
user 0m0.004s
sys 0m0.046s
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;conclusion&quot;&gt;
 &lt;a class=&quot;header-link&quot; href=&quot;https://mfo.dev.br/tags/igalia/index.xml#conclusion&quot;&gt;
 Conclusion
 &lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;page_owner&lt;/code&gt; debug feature (enabled with &lt;code&gt;CONFIG_PAGE_OWNER=y&lt;/code&gt; and &lt;code&gt;page_owner=on&lt;/code&gt;) provides information about the memory allocation of pages in the system in debugfs files &lt;code&gt;/sys/kernel/debug/page_owner&lt;/code&gt; with a generic format (dense description per-page) and &lt;code&gt;/sys/kernel/debug/page_owner_stacks/show_stacks&lt;/code&gt; with an optimized format (number of base pages per stack trace).&lt;/p&gt;        </description>
	<pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Alex Bradbury: Minipost: Additional figures for per-query energy consumption of LLMs</title>
	<guid>https://muxup.com/2026q1/minipost-additional-figures-for-per-query-energy-consumption-of-LLMs</guid>
	<link>https://muxup.com/2026q1/minipost-additional-figures-for-per-query-energy-consumption-of-LLMs</link>
	<description>
&lt;p&gt;Last month I wrote up a fairly long piece on &lt;a href=&quot;https://muxup.com/2026q1/per-query-energy-consumption-of-llms&quot;&gt;per-query energy consumption of
LLMs using the data from
InferenceMAX&lt;/a&gt; (note:
InferenceMAX has since been renamed to InferenceX). Much of the write-up was
dedicated to exploring what you can actually conclude from these figures and
how that interacts with some of the implementation decisions in the benchmark,
but I feel the results still give a useful yardstick. Beyond concerns about
overly-specialised serving engine configurations and whether the workload is
representative of real-world model serving in a paid API host, the other
obvious limitation is that InferenceMAX is only testing GPT-OSS 120b and
DeepSeek R1 0528 when there is a world of other models out there. I dutifully
added &quot;run my own tests using other models&quot; to the todo list and here we are.
By &quot;here we are&quot; I of course mean I made no progress towards that goal but
&lt;a href=&quot;https://muellerzr.github.io/&quot;&gt;Zach Mueller&lt;/a&gt; at &lt;a href=&quot;https://lambda.ai/&quot;&gt;Lambda&lt;/a&gt;
started publishing &lt;a href=&quot;https://lambda.ai/inference-models&quot;&gt;model cards with the needed
data&lt;/a&gt; - thanks Zach!&lt;/p&gt;
&lt;p&gt;The setup for Lambda is simple - each model card lists the observed token
generation throughput and total throughput (along with other stats) for an
input sequence length / output sequence length (ISL/OSL) of 8192/1024, as
benchmarked using &lt;code&gt;vllm bench serve&lt;/code&gt;. The command used to serve the LLM (using
sglang or vllm depending on the model) is also given. As a starting point this
is no worse than the InferenceMAX data, and potentially somewhat better due to
figures being taken from a configuration that's not &lt;a href=&quot;https://github.com/SemiAnalysisAI/InferenceX/issues/359#issue-3750796719&quot;&gt;overly specialised to a
particular query
length&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The figures each Lambda model card gives us that are relevant for calculating
the energy per query are: the hardware used, token generation throughput and
total token throughput (input+output tokens). Other statistics such as the
time to first token, inter-token latency, and parallel requests tested help
confirm whether this is a configuration someone would realistically use. Using
an equivalent methodology to before, we get the Watt hours per query by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Determining the total Watts for the GPU cluster. We take the figures used by
SemiAnalysis (2.17kW for a single B200) and multiply by the number of GPUs.&lt;/li&gt;
&lt;li&gt;Calculate the joules per token by dividing this total Watts figure by the
total token throughput. This gives a weighted average of the joules per
token for the measured workload, reflecting the ratio of isl:osl.&lt;/li&gt;
&lt;li&gt;Multiply this weighted average of joules per token by the tokens per query
(isl+osl) to get the joules per query. Then divide by 3600 to get Wh.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Collecting the data from the individual model cards we can generate the
following (as before, using minutes of PlayStation 5 gameplay as a point of
comparison):&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span&gt;data&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; {
    &lt;span&gt;&amp;quot;Qwen/Qwen3.5-397B-A17B&amp;quot;&lt;/span&gt;: {
        &lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;: &lt;span&gt;8&lt;/span&gt;,
        &lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;: &lt;span&gt;11092&lt;/span&gt;,
    },
    &lt;span&gt;&amp;quot;MiniMaxAI/MiniMax-M2.5&amp;quot;&lt;/span&gt;: {
        &lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;: &lt;span&gt;2&lt;/span&gt;,
        &lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;: &lt;span&gt;8062&lt;/span&gt;,
    },
    &lt;span&gt;&amp;quot;zai-org/GLM-5-FP8&amp;quot;&lt;/span&gt;: {
        &lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;: &lt;span&gt;8&lt;/span&gt;,
        &lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;: &lt;span&gt;6300&lt;/span&gt;,
    },
    &lt;span&gt;&amp;quot;zai-org/GLM-4.7-Flash&amp;quot;&lt;/span&gt;: {
        &lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;: &lt;span&gt;1&lt;/span&gt;,
        &lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;: &lt;span&gt;8125&lt;/span&gt;,
    },
    &lt;span&gt;&amp;quot;arcee-ai/Trinity-Large-Preview&amp;quot;&lt;/span&gt;: {
        &lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;: &lt;span&gt;8&lt;/span&gt;,
        &lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;: &lt;span&gt;15611&lt;/span&gt;,
    },
}

&lt;span&gt;# 8192 + 1024&lt;/span&gt;
&lt;span&gt;TOKENS_PER_QUERY&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;9216&lt;/span&gt;

&lt;span&gt;# Taken from &amp;lt;https://inferencex.semianalysis.com/&amp;gt;&lt;/span&gt;
&lt;span&gt;B200_KW&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;2.17&lt;/span&gt;

&lt;span&gt;# Reference power draw for PS5 playing a game. Taken from&lt;/span&gt;
&lt;span&gt;# &amp;lt;https://www.playstation.com/en-gb/legal/ecodesign/&amp;gt; (&amp;quot;Active Power&lt;/span&gt;
&lt;span&gt;# Consumption&amp;quot;). Ranges from ~217W to ~197W depending on model.&lt;/span&gt;
&lt;span&gt;PS5_KW&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0.2&lt;/span&gt;


&lt;span&gt;def&lt;/span&gt; &lt;span&gt;wh_per_query&lt;/span&gt;(&lt;span&gt;num_b200&lt;/span&gt;, &lt;span&gt;total_throughput&lt;/span&gt;, &lt;span&gt;tokens_per_query&lt;/span&gt;):
    &lt;span&gt;total_cluster_kw&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;num_b200&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;B200_KW&lt;/span&gt;
    &lt;span&gt;total_cluster_watts&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;total_cluster_kw&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;1000&lt;/span&gt;
    &lt;span&gt;# joules_per_token is a weighted average for the measured mix of input&lt;/span&gt;
    &lt;span&gt;# and output tokens.&lt;/span&gt;
    &lt;span&gt;joules_per_token&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;total_cluster_watts&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;total_throughput&lt;/span&gt;
    &lt;span&gt;joules_per_query&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;joules_per_token&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;tokens_per_query&lt;/span&gt;
    &lt;span&gt;# Convert joules to watt-hours&lt;/span&gt;
    &lt;span&gt;return&lt;/span&gt; &lt;span&gt;joules_per_query&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;3600.0&lt;/span&gt;

&lt;span&gt;def&lt;/span&gt; &lt;span&gt;ps5_minutes&lt;/span&gt;(&lt;span&gt;wh&lt;/span&gt;):
    &lt;span&gt;ps5_watts&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;PS5_KW&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;1000&lt;/span&gt;
    &lt;span&gt;return&lt;/span&gt; (&lt;span&gt;wh&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;ps5_watts&lt;/span&gt;) &lt;span&gt;*&lt;/span&gt; &lt;span&gt;60.0&lt;/span&gt;

&lt;span&gt;MODEL_WIDTH&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;31&lt;/span&gt;
&lt;span&gt;WH_WIDTH&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;8&lt;/span&gt;
&lt;span&gt;PS5_WIDTH&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;8&lt;/span&gt;

&lt;span&gt;header&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;f&amp;quot;{'Model':&amp;lt;{&lt;/span&gt;&lt;span&gt;MODEL_WIDTH&lt;/span&gt;&lt;span&gt;}} | {'Wh/q':&amp;lt;{&lt;/span&gt;&lt;span&gt;WH_WIDTH&lt;/span&gt;&lt;span&gt;}} | {'PS5 min':&amp;lt;{&lt;/span&gt;&lt;span&gt;PS5_WIDTH&lt;/span&gt;&lt;span&gt;}}&amp;quot;&lt;/span&gt;
&lt;span&gt;separator&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;f&amp;quot;{'-'&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;MODEL_WIDTH&lt;/span&gt;&lt;span&gt;} | {'-'&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;WH_WIDTH&lt;/span&gt;&lt;span&gt;} | {'-'&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;PS5_WIDTH&lt;/span&gt;&lt;span&gt;}&amp;quot;&lt;/span&gt;

&lt;span&gt;print&lt;/span&gt;(&lt;span&gt;header&lt;/span&gt;)
&lt;span&gt;print&lt;/span&gt;(&lt;span&gt;separator&lt;/span&gt;)

&lt;span&gt;for&lt;/span&gt; &lt;span&gt;model&lt;/span&gt;, &lt;span&gt;vals&lt;/span&gt; &lt;span&gt;in&lt;/span&gt; &lt;span&gt;data.items&lt;/span&gt;():
    &lt;span&gt;wh&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;wh_per_query&lt;/span&gt;(&lt;span&gt;vals&lt;/span&gt;[&lt;span&gt;&amp;quot;num_b200&amp;quot;&lt;/span&gt;], &lt;span&gt;vals&lt;/span&gt;[&lt;span&gt;&amp;quot;total_throughput&amp;quot;&lt;/span&gt;], &lt;span&gt;TOKENS_PER_QUERY&lt;/span&gt;)
    &lt;span&gt;ps5_min&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;ps5_minutes&lt;/span&gt;(&lt;span&gt;wh&lt;/span&gt;)

    &lt;span&gt;wh_str&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;f&amp;quot;{&lt;/span&gt;&lt;span&gt;wh&lt;/span&gt;&lt;span&gt;:.2f}&amp;quot;&lt;/span&gt; &lt;span&gt;if&lt;/span&gt; &lt;span&gt;wh&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;10&lt;/span&gt; &lt;span&gt;else&lt;/span&gt; &lt;span&gt;f&amp;quot;{&lt;/span&gt;&lt;span&gt;wh&lt;/span&gt;&lt;span&gt;:.1f}&amp;quot;&lt;/span&gt;
    &lt;span&gt;print&lt;/span&gt;(&lt;span&gt;f&amp;quot;{&lt;/span&gt;&lt;span&gt;model.strip&lt;/span&gt;()&lt;span&gt;:&amp;lt;{&lt;/span&gt;&lt;span&gt;MODEL_WIDTH&lt;/span&gt;&lt;span&gt;}} | {&lt;/span&gt;&lt;span&gt;wh_str&lt;/span&gt;&lt;span&gt;:&amp;lt;{&lt;/span&gt;&lt;span&gt;WH_WIDTH&lt;/span&gt;&lt;span&gt;}} | {&lt;/span&gt;&lt;span&gt;ps5_min&lt;/span&gt;&lt;span&gt;:.2f}&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives the following figures (reordered to show Wh per query in ascending
order, and added a column for interactivity (1/TPOT)):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Model&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Intvty (tok/s)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Wh/q&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;PS5 min.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;zai-org/GLM-4.7-Flash (bf16)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;34.0&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0.68&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;MiniMaxAI/MiniMax-M2.5 (fp8)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;30.3&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1.38&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;arcee-ai/Trinity-Large-Preview (bf16)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;58.8&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2.85&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;Qwen/Qwen3.5-397B-A17B (bf16)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;41.7&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;4.01&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;1.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;zai-org/GLM-5-FP8 (fp8)&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;23.3&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;7.05&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;2.12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As a point of comparison, the most efficient 8 GPU deployment of fp8 DeepSeek
R1 0528 from my figures in the &lt;a href=&quot;https://muxup.com/2026q1/per-query-energy-consumption-of-llms&quot;&gt;previous
article&lt;/a&gt; was 3.32 Wh
per query.&lt;/p&gt;
&lt;p&gt;And that's all I really have for today. Some interesting datapoints with
hopefully more to come as Lambda puts up more model cards in this format.
There's a range of interesting potential further experiments to do, but for
now, I just wanted to share this initial look.&lt;/p&gt;

&lt;hr /&gt;&lt;a href=&quot;https://muxup.com/feed.xml#article-changelog&quot; class=&quot;anchor&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;Article changelog
&lt;ul&gt;
&lt;li&gt;2026-02-17: Initial publication date.&lt;/li&gt;
&lt;/ul&gt;        </description>
	<pubDate>Tue, 17 Feb 2026 12:00:00 +0000</pubDate>
</item>

</channel>
</rss>
