<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<feed xmlns="http://www.w3.org/2005/Atom">

	<title>Planet Igalia</title>
	<!--<link rel="self" type="text/atom" href=""/>-->
	<link rel="alternate" type="text/html" href="https://planet.igalia.com/"/>
	<id>https://planet.igalia.com//atom.xml</id>
	<updated>2026-07-19T16:00:54+00:00</updated>
	<generator uri="http://www.planetplanet.org/">Planet/2.0 +http://www.planetplanet.org</generator>
	 <link href="https://planet.igalia.com//atom.xml" rel="self" type="application/atom+xml" />

	<entry>
		<title>Manuel Rego</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/mrego/blog/2026-07-15/"/>
		<id>https://blogs.igalia.com/mrego/blog/2026-07-15/</id>
		<updated>2026-07-15T00:00:00+00:00</updated>
		<content type="html">
&lt;p&gt;Glad to see the &lt;a href=&quot;https://webengineshackfest.org/&quot;&gt;Web Engines Hackfest&lt;/a&gt; covered in the local tech magazine &lt;a href=&quot;https://codigocero.com/&quot;&gt;C&#243;digo Cero&lt;/a&gt;. Of course I&#8217;m biased, but I&#8217;m very happy with the growth of the event and the really interesting discussions that happen there every year.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://codigocero.com/Os-responsables-das-tecnoloxias-que-fan-posible-unha-web-aberta-deronse-cita-na&quot;&gt;full interview in Galician&lt;/a&gt; (&lt;a href=&quot;https://codigocero.com/codigoceropapel276.pdf&quot;&gt;PDF version&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/mrego/files/2026/07/codigocero-weh.webp&quot; alt=&quot;Picture of C&#243;digo Cero magazine with article about the Web Engines Hackfest&quot; /&gt;&lt;/p&gt;                </content>
		<author>
			<name>Manuel Rego</name>
			<uri>https://blogs.igalia.com/mrego/</uri>
		</author>
	</entry>

	<entry>
		<title>Nikolas Zimmermann: Reducing layer overhead in LBSE</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/nzimmermann/posts/2026-07-14-lbse-conditional-layers/"/>
		<id>https://blogs.igalia.com/nzimmermann/posts/2026-07-14-lbse-conditional-layers/</id>
		<updated>2026-07-14T00:00:00+00:00</updated>
		<content type="html">
Conditional layer creation in the layer based SVG engine                </content>
		<author>
			<name>Nikolas Zimmermann</name>
			<uri>https://blogs.igalia.com/nzimmermann/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #70</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-70/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-70/</id>
		<updated>2026-07-13T22:59:14+00:00</updated>
		<content type="html">
&lt;p&gt;Update on what happened in WebKit in the week from June 30 to July 13.&lt;/p&gt;
&lt;p&gt;
The summer continues with many updates to the new SVG engine (LBSE),
improvements to the new Skia-based compositor, some small API additions,
and ever-important stable releases with security fixes.
&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/316680@main&quot;&gt;Enabled&lt;/a&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CloseWatcher&quot;&gt;CloseWatcher
API&lt;/a&gt; and
dialog's &lt;code&gt;closedby&lt;/code&gt; attribute in stable.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;wip-item&quot;&gt;
&lt;p&gt;New API &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/316808@main&quot;&gt;has been added&lt;/a&gt; which allows
specifying per-navigation &lt;code&gt;User-Agent&lt;/code&gt; string values using
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/stable/wpe-webkit-2.0/method.PolicyDecision.use_with_policies.html&quot;&gt;webkit_policy_decision_use_with_policies()&lt;/a&gt;.
Applications now have more
granularity to decide which &lt;code&gt;User-Agent&lt;/code&gt; websites are presented with,
complementing the existing global
&lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/stable/wpe-webkit-2.0/property.Settings.user-agent.html&quot;&gt;WebKitSettings:user-agent&lt;/a&gt;
setting.&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;Roughly &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/316833@main&quot;&gt;halved the cost of the Skia based
compositor&lt;/a&gt; on WPE running on Vivante
GPUs with the Etnaviv driver, by turning off Skia's mipmap sharpening option.
That option is enabled by default and makes the Skia shader generator append a
small negative level-of-detail (LOD) bias to every mipmap-capable texture
sample.  WPE does not use mipmapping at all, so the bias sharpened nothing,
but it still turned each texture fetch into a LOD lookup, which is a slow path
on the tiled GPUs found in the i.MX series. Disabling it restores usage of
faster, plain fetch operations.&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/316716@main&quot;&gt;Fixed broken rendering with the Skia
compositor&lt;/a&gt; on WPE when super-tiled
textures are enabled on Vivante GPUs. Those tile buffers are allocated padded
up to a multiple of 64 pixels, so the physical texture is larger than the
logical tile, but the Skia backing failed to take this difference into
account, leading to distorted tile images being rendered.&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/316881@main&quot;&gt;Stopped the Skia compositor from blending opaque
layers&lt;/a&gt; on WPE. Every layer was drawn
with the default source-over blend mode, which leaves GPU blending switched on
even for fully opaque layers that do not need it, so the cost was paid on
every composited frame.&lt;/p&gt;
&lt;p&gt;Layers that are opaque, drawn at full opacity and using the default blend mode
are now composited with a plain source blend mode instead, which lets Skia
turn blending off and lowers GPU bandwidth usage, benefiting tiled GPUs the
most.&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/316543@main&quot;&gt;Cached the concatenated SVG &lt;code&gt;transform&lt;/code&gt; attribute
matrix&lt;/a&gt; on graphics elements in the
Layer-Based SVG Engine (LBSE).&lt;/p&gt;
&lt;p&gt;Reading the &lt;code&gt;transform&lt;/code&gt; attribute walked the whole transform list and
multiplied every item together again, and that happened around three times per
animation frame for each element, even though the result only changes when the
transform list itself is mutated.&lt;/p&gt;
&lt;p&gt;The concatenated matrix is now stored on the element and invalidated whenever
a transform-related attribute changes, so the multiplication runs once per
mutation instead of once per read. This cuts repeated matrix work out of the
per-frame path for animated SVG content.&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/317036@main&quot;&gt;Moved the clip out of the SVG child-paint
loop&lt;/a&gt; in the Layer-Based SVG Engine
(LBSE).&lt;/p&gt;
&lt;p&gt;Painting a container used to set up a clip rectangle for every child shape in
turn, so each shape did its own graphics-context save, clip and restore even
though the clip rectangle was identical for all of them. When there is a
single region to clip to and no child paints into its own layer, that clip is
now established once and shared by every child, transformed or not.&lt;/p&gt;
&lt;p&gt;This removes a per-shape save and clip from the hot painting path of SVG
documents with many children.&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/317077@main&quot;&gt;Cached the SVG transform origin&lt;/a&gt; on
SVG renderers in the Layer-Based SVG Engine (LBSE).&lt;/p&gt;
&lt;p&gt;Every transform flush recomputed the origin for each non-layered SVG shape,
even though it only depends on the &lt;code&gt;transform-origin&lt;/code&gt; style and the transform
reference box, and sampling MotionMark's Suits test at fixed complexity showed
that computation taking around 1% of the WebProcess main thread.&lt;/p&gt;
&lt;p&gt;The origin is now cached and keyed on the reference box, with a style change
to &lt;code&gt;transform-origin&lt;/code&gt; or &lt;code&gt;transform-box&lt;/code&gt; dropping the cache, and the fast path
is limited to plain SVG transforms so viewport containers and CSS-transformed
renderers keep computing it directly. This removes a repeated per-shape cost
from animated SVG content, and the caching scope can be widened later.&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/316545@main&quot;&gt;Cached the SVG viewport size&lt;/a&gt; used to
resolve the transform reference box in the Layer-Based SVG Engine (LBSE).&lt;/p&gt;
&lt;p&gt;The default &lt;code&gt;transform-box&lt;/code&gt; for SVG is &lt;code&gt;view-box&lt;/code&gt;, so every transformed shape
resolved the viewport from the SVG root's content box again on each query,
both when updating its local transform and again during paint. The viewport is
constant after layout, so it is now cached on the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element and only
recomputed when layout actually changes it, on resize, zoom or a &lt;code&gt;viewBox&lt;/code&gt;
update. This removes another repeated per-frame computation from the transform
path for animated SVG content.&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/316880@main&quot;&gt;Coalesced the SVG transform flush&lt;/a&gt;
into one minimal repaint per container in the Layer-Based SVG Engine (LBSE).&lt;/p&gt;
&lt;p&gt;Once per rendering update WebKit processes every SVG renderer whose transform
changed, whether from script or an animation, and that repaint pass was the
dominant per-frame cost on MotionMark's Suits subtest. Instead of walking each
moved renderer up to its repaint container, the flush now computes each
child's rectangle in its parent's coordinate space, unions the children per
parent, maps that single union up the chain once, and issues one
&lt;code&gt;repaintUsingContainer()&lt;/code&gt; call per repaint container rather than one per
shape.&lt;/p&gt;
&lt;p&gt;This also stops requesting outline bounds, which for SVG merely duplicated the
visual overflow rectangle, and refreshes the bounding-box and visual-overflow
caches that a layout would normally update, so &lt;code&gt;getBBox()&lt;/code&gt; and paint or
hit-test culling never read a stale rectangle. This collapses many
backing-store invalidations into one while keeping the repainted region
minimal, closing the performance gap to the legacy SVG engine.&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/316544@main&quot;&gt;Avoided re-resolving the SVG transform from style on every
paint&lt;/a&gt; in the Layer-Based SVG Engine
(LBSE).&lt;/p&gt;
&lt;p&gt;Non-layer SVG renderers already cache their transform in &lt;code&gt;m_localTransform&lt;/code&gt;,
but the painting code path used to recompute it from scratch each time,
concatenating the &lt;code&gt;transform&lt;/code&gt; list, applying &lt;code&gt;transform-origin&lt;/code&gt; and
multiplying matrices, only because the cached value uses a different transform
origin. The paint transform is now derived directly from the cached one by
translating around the nominal origin, which removes that per-paint
recomputation and cuts the cost of painting transformed SVG content.&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/316702@main&quot;&gt;Fixed a repaint bug&lt;/a&gt; in the
Layer-Based SVG Engine (LBSE) where dynamically changing a marker's
&lt;code&gt;markerUnits&lt;/code&gt; or &lt;code&gt;orient&lt;/code&gt; attribute left stale pixels behind. Such a change
resizes every shape that references the marker, but a referencing shape
without a layer gets no post-layout position update, so only its new bounds
were repainted&#8212;a shrinking marker left its former area on screen.&lt;/p&gt;
&lt;p&gt;The visual overflow rectangle, markers included, is now cached at the end of
shape layout while the geometry is still current, so a marker change can
repaint the old bounds before recomputing the new ones. The extra repaint is
limited to markers, since gradients and patterns do not affect a client's
bounds, and the resulting repaint rects are more accurate than the legacy SVG
engine's.&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;Added a &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/316840@main&quot;&gt;new feature flag&lt;/a&gt;,
&lt;code&gt;BackForwardCacheWithMedia&lt;/code&gt;, which may be used to disable storing pages with
media content in the back-forward cache. This should solve the problem with
hardware decoders kept occupied on low-end devices in case of caching pages
with media after navigation.&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/07/09/webkitgtk2.52.5-released.html&quot;&gt;WebKitGTK 2.52.5&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/release/wpewebkit-2.52.5.html&quot;&gt;WPE WebKit 2.52.5&lt;/a&gt; have been released, including a number of fixes for security issues, and therefore it is recommended to update. An accompanying security advisory will be published in the coming days. Additionally, these releases include small improvements and Web compatibility improvements.&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Brian Kardell: A New Browser Vision?</title>
		<link rel="alternate" type="text/html" href="https://bkardell.com/blog/NewBrowserVision.html"/>
		<id>https://bkardell.com/blog/NewBrowserVision.html</id>
		<updated>2026-07-10T04:00:00+00:00</updated>
		<content type="html">
&lt;h1 class=&quot;contextual-heading&quot;&gt;A New Browser Vision?&lt;/h1&gt;
	&lt;p class=&quot;segue&quot;&gt;In which I share some thoughts about the state of things, and how maybe we could hope to change them.&lt;/p&gt;

	&lt;p&gt;Back in 2020, after Microsoft gave up on their own engine, I began writing about a topic I called &quot;Web Ecosystem Health&quot;.  What ultimately makes a healthy system that will last?  Over time I became convinced that it is all much more fragile than we realize.  In 2021 I wrote &lt;a href=&quot;https://bkardell.com/blog/Webrise.html&quot;&gt;Web Rise&lt;/a&gt; beginning to detail some of this.&lt;/p&gt;
	&lt;p&gt;There have been several more articles and a whole series of &lt;a href=&quot;https://www.youtube.com/playlist?list=PL_BRVuWxk8spg9XL-XUPSptJNrC6y3y9e&quot;&gt;at least 20 podcast episodes&lt;/a&gt; with guests of all types talking about many, many different aspects of this.  But, at the heart of it is really how it is all funded.&lt;/p&gt;
	&lt;p&gt;The other day I asked on social media &quot;Imagine that one of the big 3 web engine stewards, for some reason, decided they would stop.  What do you think would happen?  Would someone step in and save the project?  Who?  Or would it just die?&quot;  My colleague Eric Meyer (who is on holiday and had no idea I was posting this, or why) replied&lt;/p&gt;
	&lt;blockquote&gt;
	&lt;p&gt;My prediction is that people would step in to save it, but the effort would falter and wane over the next several years as contributors lost momentum and interest until finally shuddering to a halt.&lt;/p&gt;
	&lt;/blockquote&gt;
	&lt;p&gt;And that's kind of why I asked - because I was landing in a sort of similar situation. I mean, I've said it before, but I think maybe even more radically now, we need to diversify investment.  Maybe even diverisfy &lt;em&gt;ownership&lt;/em&gt;, somehow in a bigger way.&lt;/p&gt;

	&lt;p&gt;The Supporters of Chromium-Based Browsers (&lt;a href=&quot;https://socbb.org/&quot;&gt;https://socbb.org/&lt;/a&gt; - which I've recently heard pronounced &quot;Sock Baby&quot; and have now latched onto) is an interesting collaborative initiative under the Linux Foundation that some of our discussions helped inspire.  Basically, it's a common pool of money that is paid into by Google, Microsoft, Meta and Opera which then tries to fund work and grow contributions from outside those organizations.  Together, they decide how the money is spent.&lt;/p&gt;

	&lt;p&gt;I think it's still &quot;small&quot; though and I wonder how much you could scale it up.  I was thinking about this with regard to Servo.  Servo is a &lt;em&gt;really&lt;/em&gt; interesting project. It gets people excited.  It is written in Rust, it's the first one to come without a long history of baggage that it has to deal with.  There is so much promise there.&lt;/p&gt;

	&lt;p&gt;But, it's also not really remotely ready to compete with Blink or WebKit or Gecko, and none of those are standing still.  In fact, while Servo can close ground quickly in some cases, there are just far, far fewer people paddling it forward. It takes a leap of faith to believe that we could make it really competitive, and to provide the resources to do it.  Again, very few orgs even &lt;em&gt;could&lt;/em&gt; carry the load on their own - and it's still kind of fragile if it's just one org too.  But what if we could collectively own it.  Something more like the Mozilla Foundation, but... better?  What if we could get a lot of companies to invest in that dream with the promise that a kind of collective ownership could really change things and that while no one could do it alone, probably we could all bear to take a chance on something with a lot of interesting upside.  What makes Servo interesting here is that doens't already have a powerful and weathy steward.  Diverisity of ownership it could ensure that the web would remain despite changes to buisness models and so on. It would help give them some kind of a louder voice - but also present really practical compelling reasons for building concensus and compromise -- because no one org is king.  &lt;/p&gt;
	&lt;p&gt;It would be an interesting new challenge in governance and so on, but... It could be really interesting.  &lt;/p&gt;                </content>
		<author>
			<name>Brian Kardell</name>
			<uri>http://bkardell.com/</uri>
		</author>
	</entry>

	<entry>
		<title>Tiago Vignatti: Excuse me while I kiss the sky &#127749;&#127812;&#8205;&#129003;</title>
		<link rel="alternate" type="text/html" href="https://vignatti.com/posts/excuse-me-while-i-kiss-the-sky/"/>
		<id>https://vignatti.com/posts/excuse-me-while-i-kiss-the-sky/</id>
		<updated>2026-07-09T14:16:03+00:00</updated>
		<content type="html">
My brother&amp;rsquo;s award-winning book.                </content>
		<author>
			<name>Tiago Vignatti</name>
			<uri>https://vignatti.com/posts/</uri>
		</author>
	</entry>

	<entry>
		<title>V&#237;ctor J&#225;quez: Notes on hacking gfxreconstruct to enable H.265 video encoding</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/"/>
		<id>https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/</id>
		<updated>2026-07-03T00:00:00+00:00</updated>
		<content type="html">
                &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;While working on Vulkan Video encode support in Mesa, I needed to capture H.265
encoding traces. &lt;code&gt;gfxreconstruct&lt;/code&gt; already handled H.264 video and several other
extensions, but &lt;code&gt;VK_KHR_video_encode_h265&lt;/code&gt; was explicitly blocked. Here&#8217;s how I
unblocked it and what I learned about &lt;code&gt;gfxreconstruct&lt;/code&gt;&#8217;s code generation
machinery along the way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/LunarG/gfxreconstruct&quot;&gt;&lt;code&gt;gfxreconstruct&lt;/code&gt;&lt;/a&gt; is LunarG&#8217;s suite
of tools for capturing and replaying graphics API calls. It intercepts Vulkan
(and D3D12) calls at the layer level, serializes them into a compressed binary
trace file, and can later replay that trace verbatim. This is useful for driver
regression testing, GPU bring up, architecture simulation, and bug reporting.&lt;/p&gt;
&lt;h2 id=&quot;the-blocking-mechanism&quot; tabindex=&quot;-1&quot;&gt;The blocking mechanism &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#the-blocking-mechanism&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gfxreconstruct&lt;/code&gt; has two independent mechanisms that prevent an extension from
being captured.&lt;/p&gt;
&lt;p&gt;The first is a runtime blocklist in
&lt;a href=&quot;https://github.com/LunarG/gfxreconstruct/blob/dev/framework/encode/vulkan_entry_base.cpp&quot;&gt;&lt;code&gt;framework/encode/vulkan_entry_base.cpp&lt;/code&gt;&lt;/a&gt;.
A static array called &lt;code&gt;kVulkanUnsupportedDeviceExtensions&lt;/code&gt; lists extension name
strings that the layer strips from &lt;code&gt;vkEnumerateDeviceExtensionProperties&lt;/code&gt;
results. If your extension is on that list, applications cannot even see it when
the capture layer is loaded, so they never attempt to use it and nothing gets
recorded.&lt;/p&gt;
&lt;p&gt;The second is a generation-time exclusion list in the Python code generator.
&lt;code&gt;gfxreconstruct&lt;/code&gt; does not hand-write capture and replay handlers for each Vulkan
function. Instead, it parses the Khronos XML registry (&lt;code&gt;vk.xml&lt;/code&gt;, &lt;code&gt;video.xml&lt;/code&gt;)
and auto-generates thousands of lines of C++ for encoding, decoding, and
consuming API calls. The generator has exclusion lists that tell it which
extensions and struct families to skip entirely.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;VK_KHR_video_encode_h265&lt;/code&gt; was on both lists.&lt;/p&gt;
&lt;h2 id=&quot;removing-the-runtime-block&quot; tabindex=&quot;-1&quot;&gt;Removing the runtime block &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#removing-the-runtime-block&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This part is trivial: open &lt;code&gt;framework/encode/vulkan_entry_base.cpp&lt;/code&gt; and delete
the line&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;from &lt;code&gt;kVulkanUnsupportedDeviceExtensions&lt;/code&gt;. One line. After rebuilding, the layer
reports the extension to applications. But that is not enough: without generated
capture/replay code, intercepted calls would have no handlers.&lt;/p&gt;
&lt;h2 id=&quot;removing-the-generator-exclusions&quot; tabindex=&quot;-1&quot;&gt;Removing the generator exclusions &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#removing-the-generator-exclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code generator lives under &lt;code&gt;framework/generated/&lt;/code&gt;. The file
&lt;a href=&quot;https://github.com/LunarG/gfxreconstruct/blob/dev/framework/generated/khronos_generators/vulkan_generators/vulkan_base_generator.py&quot;&gt;&lt;code&gt;khronos_generators/vulkan_generators/vulkan_base_generator.py&lt;/code&gt;&lt;/a&gt;
maintains two exclusion lists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_remove_extensions&lt;/code&gt;: extensions whose entire API surface the generator should
skip.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_remove_video_extensions&lt;/code&gt;: lower-level codec standard headers
(&lt;code&gt;vulkan_video_codec_h265std*&lt;/code&gt;) that define the &lt;code&gt;StdVideoH265*&lt;/code&gt; struct
families.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From &lt;code&gt;_remove_extensions&lt;/code&gt; I removed:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;VK_KHR_video_decode_h265&lt;br /&gt;VK_KHR_video_encode_h265&lt;br /&gt;VK_KHR_video_maintenance2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From &lt;code&gt;_remove_video_extensions&lt;/code&gt; I removed:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;vulkan_video_codec_h265std&lt;br /&gt;vulkan_video_codec_h265std_decode&lt;br /&gt;vulkan_video_codec_h265std_encode&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I included &lt;code&gt;VK_KHR_video_maintenance2&lt;/code&gt; because its structs interact with video
session parameters relevant to H.265 sessions.&lt;/p&gt;
&lt;h2 id=&quot;the-xml-patch-when-the-generator-breaks&quot; tabindex=&quot;-1&quot;&gt;The XML patch: when the generator breaks &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#the-xml-patch-when-the-generator-breaks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After removing the exclusions, I ran the generator and hit an error. The
generator could not resolve a &lt;code&gt;len&lt;/code&gt; attribute in &lt;code&gt;video.xml&lt;/code&gt; for
&lt;code&gt;StdVideoH265HrdParameters&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The problematic members are &lt;code&gt;pSubLayerHrdParametersNal&lt;/code&gt; and
&lt;code&gt;pSubLayerHrdParametersVcl&lt;/code&gt;. Both are pointers, and &lt;code&gt;video.xml&lt;/code&gt; specifies
&lt;code&gt;len=&amp;quot;*_max_sub_layers_minus1 + 1&amp;quot;&lt;/code&gt; for them. The expression references
&lt;code&gt;*_max_sub_layers_minus1&lt;/code&gt;, a field that lives in an outer struct (VPS or SPS),
not in &lt;code&gt;StdVideoH265HrdParameters&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gfxreconstruct&lt;/code&gt;&#8217;s code generator resolves &lt;code&gt;len&lt;/code&gt; expressions by walking the
current struct&#8217;s members, but it cannot follow cross-struct references. This is
a reasonable limitation: the generator would need to understand the full
semantics of the Vulkan Video specification to know which outer struct provides
the length field.&lt;/p&gt;
&lt;p&gt;The fix is a small XML tree patch in
&lt;a href=&quot;https://github.com/LunarG/gfxreconstruct/blob/dev/framework/generated/khronos_generators/vulkan_generators/gencode.py&quot;&gt;&lt;code&gt;gencode.py&lt;/code&gt;&lt;/a&gt;.
Before the generator runs, I strip the &lt;code&gt;len&lt;/code&gt; attribute from both members:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;hrd_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; video_tree&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'types/type[@name=&quot;StdVideoH265HrdParameters&quot;]'&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; hrd_type &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&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; member_name &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'pSubLayerHrdParametersNal'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'pSubLayerHrdParametersVcl'&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;for&lt;/span&gt; member &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; hrd_type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;findall&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'member'&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;            name_elem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'name'&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; name_elem &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; name_elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; member_name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                member&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'len'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without a &lt;code&gt;len&lt;/code&gt; attribute, the generator falls back to treating each pointer as
pointing to a single element. This is safe for practical capture scenarios where
only one sub-layer is in use.&lt;/p&gt;
&lt;h2 id=&quot;regenerating-the-code&quot; tabindex=&quot;-1&quot;&gt;Regenerating the code &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#regenerating-the-code&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the generator changes in place, regenerating is a single command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;uv run &lt;span class=&quot;token parameter variable&quot;&gt;--with&lt;/span&gt; pyparsing python3 framework/generated/generate_vulkan.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv run --with pyparsing&lt;/code&gt; ensures the &lt;code&gt;pyparsing&lt;/code&gt; dependency is available
without a manual &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The generator overwrites all files under
&lt;code&gt;framework/generated/generated_vulkan_*.cpp&lt;/code&gt; and
&lt;code&gt;framework/generated/generated_vulkan_*.h&lt;/code&gt;. The diff was substantial: hundreds
of new functions for encoding and decoding &lt;code&gt;StdVideoH265*&lt;/code&gt; structs, plus all the
video session parameter handling infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;the-bigger-picture&quot; tabindex=&quot;-1&quot;&gt;The bigger picture &lt;a class=&quot;header-anchor&quot; href=&quot;https://blogs.igalia.com/vjaquez/notes-on-hacking-gfxreconstruct-to-enable-h-265-video-encoding/#the-bigger-picture&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The interesting part of this exercise was understanding &lt;code&gt;gfxreconstruct&lt;/code&gt;&#8217;s
architecture. The capture layer is not a monolithic block of hand-written
interceptors. It is a generator pipeline: Python scripts consume the Khronos XML
registry and emit C++ that handles serialization, deserialization, and replay
for every struct and function in the Vulkan API surface.&lt;/p&gt;
&lt;p&gt;This means enabling a new extension is mostly a matter of telling the generator
to stop ignoring it, then fixing any edge cases where the XML description
does not match the generator&#8217;s assumptions. The actual capture and replay logic
comes for free once the generator produces code for the extension&#8217;s types and
entry points.&lt;/p&gt;
&lt;p&gt;If you are considering enabling other video extensions (H.264 encode is still
blocked), the same recipe applies: remove from the runtime blocklist, remove
from &lt;code&gt;_remove_extensions&lt;/code&gt; and &lt;code&gt;_remove_video_extensions&lt;/code&gt;, regenerate, and fix
any XML &lt;code&gt;len&lt;/code&gt; expression issues that surface.&lt;/p&gt;                </content>
		<author>
			<name>V&#237;ctor J&#225;quez</name>
			<uri>https://blogs.igalia.com/vjaquez/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #69</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-69/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-69/</id>
		<updated>2026-06-29T21:00:20+00:00</updated>
		<content type="html">
&lt;p&gt;Update on what happened in WebKit in the week from June 22 to June 29.&lt;/p&gt;
&lt;p&gt;
After a small break after the Web Engines Hackgest, we're back with another
round of updates, this time with a couple of exciting improvements to the
SVG engine, a WebRTC fix, and support for WebP images with the toDataURL()
API.
&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;Made RenderLayer creation &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/315610@main&quot;&gt;conditional&lt;/a&gt; for SVG renderers in the new Layer-Based SVG Engine (LBSE), so a layer is now only created when one is actually needed for intrinsic reasons (3D transforms, opacity, etc.) instead of unconditionally for every renderer. Plain 2D transforms no longer force a layer and are applied directly during painting. This is the groundwork for follow-up patches that remove the intrinsic need for layers when applying clipping, masking and filters to SVG subtrees. It is an important milestone towards reducing the overhead that has been holding back LBSE performance compared to the legacy SVG engine.&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/315674@main&quot;&gt;Fixed the paint order&lt;/a&gt; of non-composited children around composited SVG siblings  in the Layer-Based SVG Engine (LBSE). A layered container paints its children from a single flat list in DOM (and SVG paint) order, but some children are composited into their own GraphicsLayer for reasons like &lt;code&gt;will-change&lt;/code&gt;, a 3D transform or certain &lt;code&gt;opacity&lt;/code&gt; cases. The flat child list is now split into contiguous paint-order segments at those boundaries, with each run of plain children painted by its own overlay layer placed at the correct depth in the compositor's child list. This keeps every child in its DOM order without giving trailing siblings a RenderLayer or backing store of their own, and a container with no composited children produces no segments at all, so the common case costs nothing. This allows us to support composition within LBSE subtrees in a performant way, after dropping the requirement that every renderer creates a layer.&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;Fixed &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/315815@main&quot;&gt;initial decoding issues&lt;/a&gt; on LibWebRTC on platforms that do video decoding on the final playback stage (for efficiency and performance), instead of on the LibWebRTC decoder component.&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/315446@main&quot;&gt;Added support for producing WebP images&lt;/a&gt; with canvas' &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL&quot;&gt;.toDataURL()&lt;/a&gt;. Using &lt;code&gt;1.0&lt;/code&gt; as the quality setting will &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/315446@main&quot;&gt;produce lossless images&lt;/a&gt;, which matches the behaviour of Chromium and Firefox.&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Eric Meyer: In situ Syntax Highlighting in macOS Applications Like Keynote</title>
		<link rel="alternate" type="text/html" href="https://meyerweb.com/eric/thoughts/2026/06/17/in-situ-syntax-highlighting-in-macos-applications-like-keynote/"/>
		<id>https://meyerweb.com/eric/thoughts/?p=5777</id>
		<updated>2026-06-17T13:03:09+00:00</updated>
		<content type="html">
&lt;p&gt;Earlier this month, I returned to &lt;a href=&quot;https://cssday.nl&quot;&gt;CSS Day&lt;/a&gt; for the first time since
2018 to deliver my first in-person talk since 2022.&#160; &#8220;Forging Our Own
Paths&#8221; should be available at some point; in the meantime, for the six or
seven people in my audience who might need to do something similar, I&#8217;d
like to share a small macOS workflow I developed to make syntax-highlighting
code blocks &lt;em&gt;in situ&lt;/em&gt; in Keynote a lot simpler.&#160; The
end result is to have an entry (or entries) in the Services submenu of
the contextual (right-click) menu for highlighted text.&#160; All this is adapted from an old blog
post I found copied in a few places, and which needed some updates to
make things work in 2026.&lt;/p&gt;

&lt;figure class=&quot;standalone&quot;&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/keynote-highlight-context-menu-scaled.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/keynote-highlight-context-menu-scaled.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;This is what it looks like for me.&#160; It could look much the same for you!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;First, install &lt;a href=&quot;http://andre-simon.de/doku/highlight/en/highlight.php&quot;&gt;&lt;code&gt;highlight&lt;/code&gt;&lt;/a&gt;.&#160; I used &lt;code&gt;brew install highlight&lt;/code&gt;, and the rest of this piece assumes you&#8217;ve done it that way.&#160; If you install it another way, such that it ends up in a different location than &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; would give it, you&#8217;ll need to modify a variable value later on, which I&#8217;ll point out when we get there.&lt;/p&gt;

&lt;p&gt;Next, you need to install the following (also &lt;a href=&quot;https://gist.github.com/meyerweb/1fee5442d96b3fbeec99763e46c28373&quot;&gt;available as a gist&lt;/a&gt;) as a shell script called &lt;code&gt;keynote-highlight&lt;/code&gt;:&lt;/p&gt;

&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!&#160; /bin/bash
set -e
while getopts 'h:o:i:s:t:' OPTION; do
	case &quot;$OPTION&quot; in
		h)
			highlighthome=&quot;$OPTARG&quot;
			;;
		o)
			outputrtf=&quot;$OPTARG&quot;
			;;
		i)
			inputrtf=&quot;$OPTARG&quot;
			;;
		s)
			syntax=&quot;$OPTARG&quot;
			;;
		t)
			theme=&quot;$OPTARG&quot;
			;;
		?)
			echo &quot;script usage incorrect?&quot; &gt;&amp;amp;2
			exit 1
			;;
	esac
done
shift &quot;$(($OPTIND -1))&quot;

#=============================

inputrtf=&quot;$(pbpaste -pboard -prefer public.rtf)&quot;

regex=&quot;fcharset0 ([a-zA-Z0-9 ]+);&quot;
if [[ &quot;$inputrtf&quot; =~ $regex ]]
then
	fontface=${BASH_REMATCH[1]}
else
	fontface=&quot;Courier&quot;
fi

regex=&quot;fs([0-9]{1,5})&quot;
if [[ &quot;$inputrtf&quot; =~ $regex ]]
then
	fontsize=${BASH_REMATCH[1]}
	fontsize2=$((fontsize/2))
else
	fontsize2=&quot;12&quot;
fi

if [ -z &quot;$theme&quot; ]; then
	theme=&quot;candy&quot;
fi

if [ -z &quot;$highlighthome&quot; ]; then
	highlighthome=&quot;/opt/homebrew/bin/highlight&quot;
fi

highlighted=$(&quot;$highlighthome&quot; --out-format=&quot;rtf&quot; --syntax=&quot;$syntax&quot; --style=&quot;$theme&quot; --font=&quot;$fontface&quot; --font-size=&quot;$fontsize2&quot; --no-trailing-nl --stdout)
echo &quot;$highlighted&quot;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Put the script wherever you store your shell scripts, and make sure
it&#8217;s both executable and can be invoked from the command line.&#160; I
&lt;em&gt;believe&lt;/em&gt;, without any real basis for doing so, that if you
already have &lt;a href=&quot;https://github.com/sbarex/SourceCodeSyntaxHighlight&quot;&gt;&lt;code&gt;syntax-highlight&lt;/code&gt;&lt;/a&gt;
installed, which is (among other things) a wrapper around
&lt;code&gt;highlight&lt;/code&gt;, you could use it by modifying the
&lt;code&gt;highlighthome&lt;/code&gt; variable assignment to point to it rather
than &lt;code&gt;highlight&lt;/code&gt;, as well as modifying a variable in an
upcoming bit of code.&#160; But, as I say, I&#8217;m just guessing about that.&lt;/p&gt;

&lt;p&gt;Once&#160;the shell script is installed and ready to execute, launch
Automator and create a new Quick Action.&#160; Call it &#8220;Syntax Highlight CSS&#8221;
or something similar.&#160; If you want to set up highlighting for other kinds
of code, like HTML or any of &lt;a href=&quot;http://andre-simon.de/doku/highlight/en/langs.php&quot;&gt;the nearly 250
languages (!!!) &lt;code&gt;highlight&lt;/code&gt; supports&lt;/a&gt;, each language has
to be given its own Quick Action.&#160; Thus, if you want them all next to
each other in the Services menu, pick an appropriate naming scheme.&#160; For
this one, we&#8217;re doing CSS, but later you&#8217;ll see how you can quickly set
up this same thing for other formats.&lt;/p&gt;

&lt;p&gt;At the top of the right-hand panel in the new Quick Action workflow,
check the &#8220;Workflow receives current&#8221; dropdown to make sure it&#8217;s set to
either &#8220;Automatic (rich text)&#8221; or &#8220;rich text&#8221;, the latter if you plan to
never, ever use this in any non-RTF setting.&#160; I go with the Automatic
option.&#160; If you want to restrict the action to a particular application,
like Keynote, change the dropdown that says &#8220;any application&#8221; to pick a
specific application.&#160; I leave mine to be available in any application,
just in case I&#8217;m ever syntax highlighting code in TextEdit or something.&#160; I also set the color to &#8220;Red&#8221;, because clearly that makes it go
faster.&lt;/p&gt;

&lt;p&gt;With all those things set, the first thing to add to the workflow is
a &#8220;Copy to Clipboard&#8221; action.&#160; That&#8217;s it for this step, just add that and
leave it alone.&lt;/p&gt;

&lt;p&gt;Now, add a &#8220;Run AppleScript&#8221; action.&#160; Paste the following (also &lt;a href=&quot;https://gist.github.com/meyerweb/4951fcba97c159d655694291618d23f6&quot;&gt;available
as a gist&lt;/a&gt;) into the textbox that contains the boilerplate skeleton
(replace the skeleton):&lt;/p&gt;

&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;on run {input, parameters}
	set highlightHome to &quot;/opt/homebrew/bin/highlight&quot;
	set syntaxType to &quot;css&quot;
	set themeName to &quot;navy&quot;
	set command to &quot;PATH_TO_SCRIPT/keynote-highlight -h &quot; &amp;amp; highlightHome &amp;amp; &quot; -s &quot; &amp;amp; syntaxType &amp;amp; &quot; -t &quot; &amp;amp; themeName
	do shell script &quot;/bin/bash -c 'pbpaste | &quot; &amp;amp; command &amp;amp; &quot; | pbcopy'&quot;
	delay 0.1
	tell application &quot;System Events&quot; to keystroke &quot;v&quot; using command down
end run&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Change the &lt;code&gt;PATH_TO_SCRIPT&lt;/code&gt; in there to wherever you put
the shell script, save the workflow, and it should be ready to go!&lt;/p&gt;

&lt;figure&gt;
&lt;a href=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/keynote-highlight-automator-workflow.png&quot;&gt;&lt;img src=&quot;https://meyerweb.com/eric/thoughts/wp-content/uploads/keynote-highlight-automator-workflow.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;What the Automator workflow should look like.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&#8230;unless your copy of &lt;code&gt;highlight&lt;/code&gt; lives somewhere else or
you&#8217;re trying out using &lt;code&gt;syntax-highlight&lt;/code&gt; in its place, or
you have a different theme you&#8217;d like to use.&#160; In either case, change the
value of the corresponding variable in the AppleScript.&#160; As for the
&lt;code&gt;syntaxType&lt;/code&gt; variable, that&#8217;s what you change if you want to
highlight HTML or Pascal or BASIC or whatever else, but since we&#8217;re
doing CSS, leave it as is.&lt;/p&gt;

&lt;p&gt;At this point, everything should be ready to go.&#160; In your Keynote
slides, wherever you want to syntax-highlight some CSS, drag-select (or
select-all) the CSS text in question.&#160; Just be sure you have the text
actually highlighted; just selecting the outer text box that holds the
text isn&#8217;t sufficient.&#160; Right-click on the selected text to bring up the
Context menu, and in there open the &#8220;Services&#8221;&#160;submenu.&#160; &#8220;Syntax
Highlight CSS&#8221;&#160;(or whatever you called yours) should be in that submenu.&#160; Select it, and after a second or two, the un-highlighted CSS should be
replaced with the same thing, except syntax-highlighted.&lt;/p&gt;

&lt;figure class=&quot;standalone&quot;&gt;
&lt;video controls=&quot;controls&quot;&gt;
&lt;source src=&quot;https://meyerweb.com/vid/2026/keynote-highlight-movie.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/source&gt;&lt;/video&gt;
&lt;figcaption&gt;A short movie showing the workflow in action. (2.1MB MP4; no audio)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Well, &#8220;the same thing&#8221;&#160;in the sense of being the same font face and
font size it was before you syntax-highlighted it.&#160; If you used a line
spacing other than 1.0, it will be reset to 1.0.&#160; This is due to a
limitation in &lt;code&gt;highlight&lt;/code&gt;, which doesn&#8217;t accept line-height
values as an argument, and thus will always return text with 1.0
spacing.&#160; It&#8217;s likely that other fancy adjustments like kerning will also
be reset to default, though I didn&#8217;t test them all.&#160; I just know that
&lt;code&gt;highlight&lt;/code&gt; only accepts font name and size as styling
parameters, so those were the only ones I could affect.&lt;/p&gt;

&lt;p&gt;I did try to capture the output of &lt;code&gt;highlight&lt;/code&gt; and
do find-and-replace to restore the line height and tab sizes.&#160; Alas, this
was ultimately unsuccessful.&#160; I&#8217;m fairly confident this is solvable, but
a great deal less confident that it&#8217;s solvable by me.&#160; Part of the
problem seems to be how &lt;code&gt;highlight&lt;/code&gt; returns the RTF, and part
of it seems to be some kind of recursive munging of the RTF result
(maybe?), and in the end I just gave up.&lt;/p&gt;

&lt;p&gt;If you want to set up something similar for HTML, then you need only
duplicate the workflow to a different name &amp;#x202F;&#8212;&amp;#x2009;&#160;say, &#8220;Syntax Highlight
HTML&#8221; &amp;#x202F;&#8212;&amp;#x2009;&#160;and then change the value of the AppleScript
&lt;code&gt;syntaxType&lt;/code&gt; variable from &lt;code&gt;css&lt;/code&gt; to
&lt;code&gt;html&lt;/code&gt; in the new workflow.&#160; That&#8217;s all.&#160; Similar steps should
be taken to set up a workflow for any other recognized language.&lt;/p&gt;

&lt;p&gt;There are a few things to note.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you try to syntax-highlight a block of text that contains
multiple font faces or sizes, all of the selected text will be reset to
the first face and size, or the script will simply fail to work.&#160; As far
as I can tell, preserving the face and size on a per-line (or
per-character) basis would be difficult to achieve and probably produce
terrible RTF.&#160; As a workaround, highlight each bit of differently-sized
or -faced text on its own, and invoke the service.&lt;/li&gt;
&lt;li&gt;There is no checking to see if the text you highlight matches the
type of highlighter to apply to it.&#160; If you try to use &#8220;Syntax Highlight
CSS&#8221; on some HTML, the CSS parser will be used.&#160; So be careful.&lt;/li&gt;
&lt;li&gt;Picking a new theme can be a cumbersome process, involving a fair
amount of trial and error.&#160; It looks like &lt;a href=&quot;https://github.com/sbarex/SourceCodeSyntaxHighlight#application-settings&quot;&gt;Syntax
Highlight has a standalone application&lt;/a&gt; that can be used for quick
previewing, but I haven&#8217;t tried it so I can&#8217;t vouch for or against
it.&lt;/li&gt;
&lt;li&gt;I did all this in macOS Sequoia.&#160; I would hope it still works in Tahoe, but if not, let me know in the comments and I&#8217;ll add a warning note and a heavy sigh.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
There are probably more efficient or more elegant ways to do the individual bits of both scripts, but this works for me, so I figured I&#8217;d pass it on to anyone else who&#8217;d like to use it.&#160; Improvements, or pointers to solid information that can help me overcome the limitations I mentioned, are always welcome!
&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/06/17/in-situ-syntax-highlighting-in-macos-applications-like-keynote/#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%22In%20situ%20Syntax%20Highlighting%20in%20macOS%20Applications%20Like%20Keynote%22&quot;&gt;email Eric directly&lt;/a&gt;.&lt;/p&gt;                </content>
		<author>
			<name>Eric Meyer</name>
			<uri>https://meyerweb.com/eric/thoughts</uri>
		</author>
	</entry>

	<entry>
		<title>Manuel Rego</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/mrego/blog/2026-06-17/"/>
		<id>https://blogs.igalia.com/mrego/blog/2026-06-17/</id>
		<updated>2026-06-17T00:00:00+00:00</updated>
		<content type="html">
&lt;p&gt;Full room for the WHATNOT meeting that is happening live during the &lt;a href=&quot;https://webengineshackfest.org/&quot;&gt;Web Engines Hackfest&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/whatwg/html/issues/12441&quot;&gt;https://github.com/whatwg/html/issues/12441&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/mrego/files/2026/06/weh-2026-whatnot.jpg&quot; alt=&quot;Picture of the Web Engines Hackfest during the WHATNOT meeting full of people&quot; /&gt;&lt;/p&gt;                </content>
		<author>
			<name>Manuel Rego</name>
			<uri>https://blogs.igalia.com/mrego/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #68</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-68/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-68/</id>
		<updated>2026-06-16T10:14:06+00:00</updated>
		<content type="html">
&lt;p&gt;Update on what happened in WebKit in the week from June 9 to June 16.&lt;/p&gt;
&lt;p&gt;
The major highlight this week is the Web Engines Hackfest! Despite it, there
are a variety of updates as well, such as various improvements to input
handling in WPE WebKit and WebKitGTK, WPE menu rendering changes, and a
plethora of other smaller improvements.
&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;Input methods may now &lt;a rel=&quot;external&quot; href=&quot;https://commits.webkit.org/315091@main&quot;&gt;know whether a field is intended to be used as search input&lt;/a&gt;, in which case the &lt;code&gt;WebKitInputMethodContext:input-purpose&lt;/code&gt; property will have the value &lt;code&gt;WEBKIT_INPUT_PURPOSE_SEARCH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Due to GTK not providing an equivalent value for &lt;code&gt;GtkInputPurpose&lt;/code&gt;, the default behaviour is to continue mapping search fields to &lt;code&gt;GTK_INPUT_PURPOSE_FREE_FORM&lt;/code&gt; as before; but custom input methods may use the new value to detect search inputs. When using WPEPlatform, the value is mapped to &lt;code&gt;WPE_INPUT_PURPOSE_SEARCH&lt;/code&gt;, which has been added as well.&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/314898@main&quot;&gt;Handle&lt;/a&gt; selections as part of &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/314884@main&quot;&gt;Corrected&lt;/a&gt; user activation propagation for close watchers.&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/314695@main&quot;&gt;Invalidate&lt;/a&gt; &lt;code&gt;:lang()&lt;/code&gt; and &lt;code&gt;:dir()&lt;/code&gt; selectors after &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/315081@main&quot;&gt;Enable&lt;/a&gt; Close Watchers in preview.&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;WPE &lt;a rel=&quot;external&quot; href=&quot;https://github.com/WebKit/WebKit/commit/5ccb692f3e65ac32d6d6b83796dd8c9ca0bf1e1b&quot;&gt;now&lt;/a&gt; renders its own popup menus for elements such as &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/select&quot;&gt;&lt;code&gt;select&lt;/code&gt;&lt;/a&gt;. It supports all styling options the web provides such as colors and fonts. The internal menu can be overriden with the existing &lt;a rel=&quot;external&quot; href=&quot;https://wpewebkit.org/reference/stable/wpe-webkit-2.0/signal.WebView.show-option-menu.html&quot;&gt;&lt;code&gt;WebView::show-option-menu&lt;/code&gt;&lt;/a&gt; signal. Cog for example still renders its own (with a &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Igalia/cog/commit/8a7051b553328638772188f617e1e05e7a5ca486&quot;&gt;recent commit&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/webkit/blog/2026/wip-68/context-menu-1.png&quot; alt=&quot;A colorful context menu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/webkit/blog/2026/wip-68/context-menu-2.png&quot; alt=&quot;A context menu with a simpler style&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/webkit/blog/2026/wip-68/context-menu-iframe.png&quot; alt=&quot;A context menu inside an iFrame&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogs.igalia.com/webkit/blog/2026/wip-68/context-menu-rotated.png&quot; alt=&quot;A context menu inside a rotated container&quot; /&gt;&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;The &lt;a rel=&quot;external&quot; href=&quot;https://webengineshackfest.org/&quot;&gt;Web Engines Hackfest&lt;/a&gt; started! We had a fantastic first day of talks, and now are heading to &lt;a rel=&quot;external&quot; href=&quot;https://webengineshackfest.org/#breakout-sessions&quot;&gt;breakout sessions&lt;/a&gt;. Make sure to check the schedule for sessions that may interest you!&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Jasmine Tang: A funny linker bug</title>
		<link rel="alternate" type="text/html" href="https://badumbatish.github.io/posts/linker_bug_2gb"/>
		<id>https://badumbatish.github.io/blog/linker_bug_2gb</id>
		<updated>2026-06-15T00:00:00+00:00</updated>
		<content type="html">
Linker funny oh no, not funny haha                </content>
		<author>
			<name>Jasmine Tang</name>
			<uri>https://badumbatish.github.io/blog/rss.xml</uri>
		</author>
	</entry>

	<entry>
		<title>Brian Kardell: Fine. What Is the Web?</title>
		<link rel="alternate" type="text/html" href="https://bkardell.com/blog/FWIW.html"/>
		<id>https://bkardell.com/blog/FWIW.html</id>
		<updated>2026-06-10T04:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Brian Kardell</name>
			<uri>http://bkardell.com/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #67</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-67/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-67/</id>
		<updated>2026-06-08T20:57:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Ma&#237;ra Canal: Bringing Runtime Power Management to the Raspberry Pi GPU</title>
		<link rel="alternate" type="text/html" href="https://mairacanal.github.io/bringing-runtime-pm-to-raspberry-pi-gpu/"/>
		<id>https://mairacanal.github.io/bringing-runtime-pm-to-raspberry-pi-gpu/</id>
		<updated>2026-06-08T15:30:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Ma&#237;ra Canal</name>
			<uri>https://mairacanal.github.io</uri>
		</author>
	</entry>

	<entry>
		<title>Olivier Tilloy: Embedded Recipes '26</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/otilloy/embedded-recipes-26/"/>
		<id>https://blogs.igalia.com/otilloy/embedded-recipes-26/</id>
		<updated>2026-06-04T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Olivier Tilloy</name>
			<uri>https://blogs.igalia.com/otilloy/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #66</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-66/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-66/</id>
		<updated>2026-06-02T00:12:49+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Pawel Lampe: WPE memory leak investigation playbook</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/"/>
		<id>https://blogs.igalia.com/plampe/wpe-memory-leak-investigation-playbook/</id>
		<updated>2026-06-02T00:00:00+00:00</updated>
		<content type="html">
&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.
Also, if WebKit is using a sanboxed mode in given configuration, it&#8217;s usually necessary to use &lt;code&gt;WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1&lt;/code&gt; as well. 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; (accompanied by &lt;code&gt;WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1&lt;/code&gt; if needed).&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;                </content>
		<author>
			<name>Pawel Lampe</name>
			<uri>https://blogs.igalia.com/plampe/</uri>
		</author>
	</entry>

	<entry>
		<title>Alex Bradbury: Minipost: at-agent</title>
		<link rel="alternate" type="text/html" href="https://muxup.com/2026q2/minipost-at-agent"/>
		<id>https://muxup.com/2026q2/minipost-at-agent</id>
		<updated>2026-05-31T12:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Alex Bradbury</name>
			<uri>https://muxup.com</uri>
		</author>
	</entry>

	<entry>
		<title>Eric Meyer: Accessible (I Think) Split-Cell Table Headers</title>
		<link rel="alternate" type="text/html" href="https://meyerweb.com/eric/thoughts/2026/05/28/accessible-i-think-split-cell-table-headers/"/>
		<id>https://meyerweb.com/eric/thoughts/?p=5756</id>
		<updated>2026-05-28T13:05:08+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Eric Meyer</name>
			<uri>https://meyerweb.com/eric/thoughts</uri>
		</author>
	</entry>

	<entry>
		<title>Alex Bradbury: shandbox</title>
		<link rel="alternate" type="text/html" href="https://muxup.com/shandbox"/>
		<id>https://muxup.com/shandbox</id>
		<updated>2026-05-26T12:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Alex Bradbury</name>
			<uri>https://muxup.com</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia Compilers Team: Five years of JavaScript on WebAssembly</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/"/>
		<id>https://blogs.igalia.com/compilers/2026/05/25/five-years-of-javascript-on-webassembly/</id>
		<updated>2026-05-25T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia Compilers Team</name>
			<uri>https://blogs.igalia.com/compilers/</uri>
		</author>
	</entry>

	<entry>
		<title>Brian Kardell: Get Clamped: Unwinding Some Difficult CSS History</title>
		<link rel="alternate" type="text/html" href="https://bkardell.com/blog/GetClamped.html"/>
		<id>https://bkardell.com/blog/GetClamped.html</id>
		<updated>2026-05-22T04:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Brian Kardell</name>
			<uri>http://bkardell.com/</uri>
		</author>
	</entry>

	<entry>
		<title>V&#237;ctor J&#225;quez: Getting ready for the GStreamer Spring Hackfest 2026 in Nice</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/vjaquez/getting-ready-for-the-gstreamer-spring-hackfest-2026-in-nice/"/>
		<id>https://blogs.igalia.com/vjaquez/getting-ready-for-the-gstreamer-spring-hackfest-2026-in-nice/</id>
		<updated>2026-05-22T00:00:00+00:00</updated>
		<content type="html">
                &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;                </content>
		<author>
			<name>V&#237;ctor J&#225;quez</name>
			<uri>https://blogs.igalia.com/vjaquez/</uri>
		</author>
	</entry>

	<entry>
		<title>Qiuyi Zhang (Joyee): How Node.js and V8 keep each other working - workflows, challenges and tips</title>
		<link rel="alternate" type="text/html" href="https://joyeecheung.github.io/blog/2026/05/18/how-nodejs-and-v8-keep-each-other-working/"/>
		<id>https://joyeecheung.github.io/blog/2026/05/18/how-nodejs-and-v8-keep-each-other-working/</id>
		<updated>2026-05-19T00:16:39+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Qiuyi Zhang (Joyee)</name>
			<uri>https://joyeecheung.github.io/blog/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #65</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-65/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-65/</id>
		<updated>2026-05-18T19:39:42+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Alex Bradbury: Building 32-bit RISC-V sysroots and images with Yocto</title>
		<link rel="alternate" type="text/html" href="https://muxup.com/building-32-bit-risc-v-sysroots-and-images-with-yocto"/>
		<id>https://muxup.com/building-32-bit-risc-v-sysroots-and-images-with-yocto</id>
		<updated>2026-05-18T12:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Alex Bradbury</name>
			<uri>https://muxup.com</uri>
		</author>
	</entry>

	<entry>
		<title>Gyuyoung Kim: Blink for Apple tvOS: 2026 Update</title>
		<link rel="alternate" type="text/html" href="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"/>
		<id>https://blogs.igalia.com/gyuyoung/?p=1031</id>
		<updated>2026-05-15T03:31:34+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>gyuyoung</name>
			<uri>https://blogs.igalia.com/gyuyoung</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #64</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-64/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-64/</id>
		<updated>2026-05-11T20:04:43+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Gyuyoung Kim: Introduce Blink for Apple tvOS</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/gyuyoung/2026/05/09/introduce-blink-for-apple-tvos/?pk_campaign=feed&amp;pk_kwd=introduce-blink-for-apple-tvos"/>
		<id>https://blogs.igalia.com/gyuyoung/?p=1019</id>
		<updated>2026-05-08T15:46:47+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>gyuyoung</name>
			<uri>https://blogs.igalia.com/gyuyoung</uri>
		</author>
	</entry>

	<entry>
		<title>Alex Bradbury: Bootable QEMU image menagerie with rootless debootstrap</title>
		<link rel="alternate" type="text/html" href="https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap"/>
		<id>https://muxup.com/2026q2/bootable-qemu-image-menagerie-with-rootless-debootstrap</id>
		<updated>2026-05-04T12:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Alex Bradbury</name>
			<uri>https://muxup.com</uri>
		</author>
	</entry>

	<entry>
		<title>Brian Kardell: Browsers and Language Features</title>
		<link rel="alternate" type="text/html" href="https://bkardell.com/blog/language.html"/>
		<id>https://bkardell.com/blog/language.html</id>
		<updated>2026-05-04T04:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Brian Kardell</name>
			<uri>http://bkardell.com/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #63</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-63/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-63/</id>
		<updated>2026-04-28T08:05:47+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Eric Meyer: Canvas-ing the Web</title>
		<link rel="alternate" type="text/html" href="https://meyerweb.com/eric/thoughts/2026/04/27/canvas-ing-the-web/"/>
		<id>https://meyerweb.com/eric/thoughts/?p=5735</id>
		<updated>2026-04-27T14:20:33+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Eric Meyer</name>
			<uri>https://meyerweb.com/eric/thoughts</uri>
		</author>
	</entry>

	<entry>
		<title>Pablo Saavedra: Fixing WebKit Unified Source Build Failures</title>
		<link rel="alternate" type="text/html" href=""/>
		<id>http://http503.gvatas.in/?p=2711</id>
		<updated>2026-04-21T23:45:29+00:00</updated>
		<content type="html">
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;]                </content>
		<author>
			<name>Pablo Saavedra</name>
			<uri>https://http503.gvatas.in</uri>
		</author>
	</entry>

	<entry>
		<title>Jasmine Tang: My first time with Nix</title>
		<link rel="alternate" type="text/html" href="https://badumbatish.github.io/posts/first_time_with_nix"/>
		<id>https://badumbatish.github.io/blog/first_time_with_nix</id>
		<updated>2026-04-18T04:00:00+00:00</updated>
		<content type="html">
Jasmine tries out Nix                </content>
		<author>
			<name>Jasmine Tang</name>
			<uri>https://badumbatish.github.io/blog/rss.xml</uri>
		</author>
	</entry>

	<entry>
		<title>Brian Kardell: Priority of Constituencies</title>
		<link rel="alternate" type="text/html" href="https://bkardell.com/blog/PriorityOfConstituencies.html"/>
		<id>https://bkardell.com/blog/PriorityOfConstituencies.html</id>
		<updated>2026-04-17T04:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Brian Kardell</name>
			<uri>http://bkardell.com/</uri>
		</author>
	</entry>

	<entry>
		<title>Mart&#237;n Abente Lahaye: Modern Yocto Linux Best Practices</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/"/>
		<id>https://blogs.igalia.com/mabente/modern-yocto-linux-best-practices/</id>
		<updated>2026-04-17T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Mart&#237;n Abente Lahaye</name>
			<uri>https://blogs.igalia.com/mabente/</uri>
		</author>
	</entry>

	<entry>
		<title>Jos&#233; Dapena: Container Timing: moving to Origin Trial</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/"/>
		<id>https://blogs.igalia.com/dape/2026/04/14/container-timing-moving-to-origin-trial/</id>
		<updated>2026-04-14T00:00:00+00:00</updated>
		<content type="html">
                &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;                </content>
		<author>
			<name>Jos&#233; Dapena</name>
			<uri>https://blogs.igalia.com/dape/</uri>
		</author>
	</entry>

	<entry>
		<title>St&#233;phane Cerveau: Introducing GstPrinceOfParser 0.4.3</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/"/>
		<id>https://blogs.igalia.com/scerveau/introducing-gstprinceofparser-0-4-3/</id>
		<updated>2026-04-08T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>St&#233;phane Cerveau</name>
			<uri>https://blogs.igalia.com/scerveau/</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #62</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-62/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-62/</id>
		<updated>2026-04-07T17:07:37+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>St&#233;phane Cerveau: Introducing GstPipelineStudio 0.5.1</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/"/>
		<id>https://blogs.igalia.com/scerveau/introducing-gstpipelinestudio-0-5-1/</id>
		<updated>2026-04-07T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>St&#233;phane Cerveau</name>
			<uri>https://blogs.igalia.com/scerveau/</uri>
		</author>
	</entry>

	<entry>
		<title>Miyoung Shin: Extension Migration Progress Update &#8211; Part 1</title>
		<link rel="alternate" type="text/html" href="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"/>
		<id>https://blogs.igalia.com/mshin/?p=161</id>
		<updated>2026-04-02T11:32:34+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>mshin</name>
			<uri>https://blogs.igalia.com/mshin</uri>
		</author>
	</entry>

	<entry>
		<title>Qiuyi Zhang (Joyee): Tinkering with Node.js Core on ARM64 Windows</title>
		<link rel="alternate" type="text/html" href="https://joyeecheung.github.io/blog/2026/01/31/tinkering-with-nodejs-core-on-arm64-windows/"/>
		<id>https://joyeecheung.github.io/blog/2026/01/31/tinkering-with-nodejs-core-on-arm64-windows/</id>
		<updated>2026-04-01T02:16:02+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Qiuyi Zhang (Joyee)</name>
			<uri>https://joyeecheung.github.io/blog/</uri>
		</author>
	</entry>

	<entry>
		<title>Andy Wingo: wastrelly wabbits</title>
		<link rel="alternate" type="text/html" href="https://wingolog.org/archives/2026/03/31/wastrelly-wabbits"/>
		<id>https://wingolog.org/2026/03/31/wastrelly-wabbits</id>
		<updated>2026-03-31T20:34:23+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Andy Wingo</name>
			<uri>https://wingolog.org/</uri>
		</author>
	</entry>

	<entry>
		<title>Alex Bradbury: Minipost: Routing a Linux user's traffic through a WireGuard interface</title>
		<link rel="alternate" type="text/html" href="https://muxup.com/2026q1/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface"/>
		<id>https://muxup.com/2026q1/minipost-routing-a-linux-users-traffic-through-a-wireguard-interface</id>
		<updated>2026-03-31T12:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Alex Bradbury</name>
			<uri>https://muxup.com</uri>
		</author>
	</entry>

	<entry>
		<title>Igalia WebKit Team: WebKit Igalia Periodical #61</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/webkit/blog/2026/wip-61/"/>
		<id>https://blogs.igalia.com/webkit/blog/2026/wip-61/</id>
		<updated>2026-03-30T21:46:57+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Igalia WebKit Team</name>
			<uri>https://blogs.igalia.com/webkit</uri>
		</author>
	</entry>

	<entry>
		<title>Ricardo Ca&#241;uelo Navarro: Why don't we do a demo? Part 2: software development</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/index.html"/>
		<id>https://blogs.igalia.com/rcn/posts/20260330-why_dont_we_do_a_demo_part_2/index.html</id>
		<updated>2026-03-30T11:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>rcn</name>
			<uri>https://blogs.igalia.com/rcn</uri>
		</author>
	</entry>

	<entry>
		<title>Qiuyi Zhang (Joyee): Teaching gdb to Unwind V8 JIT Frames on x64</title>
		<link rel="alternate" type="text/html" href="https://joyeecheung.github.io/blog/2026/03/24/teaching-gdb-to-unwind-v8-jit-frames-on-x64/"/>
		<id>https://joyeecheung.github.io/blog/2026/03/24/teaching-gdb-to-unwind-v8-jit-frames-on-x64/</id>
		<updated>2026-03-26T16:20:20+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Qiuyi Zhang (Joyee)</name>
			<uri>https://joyeecheung.github.io/blog/</uri>
		</author>
	</entry>

	<entry>
		<title>Jos&#233; Dapena: The implementation of Container Timing: aggregating paints in Blink</title>
		<link rel="alternate" type="text/html" href="https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/"/>
		<id>https://blogs.igalia.com/dape/2026/03/26/the-implementation-of-container-timing-aggregating-paints-in-blink/</id>
		<updated>2026-03-26T00:00:00+00:00</updated>
		<content type="html">
                &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;                </content>
		<author>
			<name>Jos&#233; Dapena</name>
			<uri>https://blogs.igalia.com/dape/</uri>
		</author>
	</entry>

	<entry>
		<title>Qiuyi Zhang (Joyee): Improving Single Executable Application Building for Node.js</title>
		<link rel="alternate" type="text/html" href="https://joyeecheung.github.io/blog/2026/01/26/improving-single-executable-application-building-for-node-js/"/>
		<id>https://joyeecheung.github.io/blog/2026/01/26/improving-single-executable-application-building-for-node-js/</id>
		<updated>2026-03-24T13:15:57+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Qiuyi Zhang (Joyee)</name>
			<uri>https://joyeecheung.github.io/blog/</uri>
		</author>
	</entry>

	<entry>
		<title>Javier Fern&#225;ndez: Protocol Handler Registration via Browser Extensions</title>
		<link rel="alternate" type="text/html" href="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"/>
		<id>https://blogs.igalia.com/jfernandez/?p=1961</id>
		<updated>2026-03-24T11:41:45+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>jfernandez</name>
			<uri>https://blogs.igalia.com/jfernandez</uri>
		</author>
	</entry>

	<entry>
		<title>Sim&#243;n Pena: Getting started with WPE WebKit: a minimal launcher</title>
		<link rel="alternate" type="text/html" href="https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit/"/>
		<id>https://simonpena.com/blog/2026/03/20/getting-started-with-wpe-webkit</id>
		<updated>2026-03-20T00:00:00+00:00</updated>
		<content type="html">
&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;                </content>
		<author>
			<name>Sim&#243;n</name>
			<uri>https://simonpena.com/</uri>
		</author>
	</entry>

</feed>
