This Month in Ladybird – April 2026

💥 Discover this insightful post from Hacker News 📖

📂 **Category**:

✅ **What You’ll Learn**:

honor the align presentational attribute, fixing button placement on bricklink.com. (#9177)

Before After
Before After
  • stroke-dasharray interpolation : SVG dashes finally animate smoothly. (#9133)

  • autofocus : Elements with the autofocus attribute actually receive focus on page load now. (#9016)

  • List markers in RTL text : Bullets now sit on the right side of right-to-left text, fixing list rendering on Arabic Wikipedia. (#9099)

    Before After
    Before After
  • Inline flex/grid baselines : An inline flex or grid container now derives its baseline from its child’s first line box, not its last wrapped line. Fixes link text and icon alignment on nos.nl. (#9183)

    Before After
    Before After
  • Networking

    getaddrinfo no longer blocks the event loop. LibDNS now runs lookups on a thread pool, fires A and AAAA queries in parallel (RFC 8305-ish), and coalesces concurrent lookups for the same name. RequestServer’s preconnect path was sneaking past our resolver and letting libcurl spawn its own threaded resolver that would pthread_join us on the main thread; that’s now routed through the same DNS pool. (#9109)

    Profile of loading x.com when DNS is slow, before and after:

    Before
    After

    Over in RequestServer, draining queued response data was O(n²) when WebContent was slower than the network. RequestServer was spending ~30 seconds in memcpy and 3 seconds in Vector::remove while opening a YouTube video! Switching AllocatingMemoryStream to a singly-linked chunk list made consumption O(1). (#9028)

    We now advertise AVIF and WebP in our Accept header for image requests, matching other engines. Some CDNs use the Accept header to decide whether to serve modern formats or fall back to JPEG. (#9046)

    Style invalidation

    Selector invalidation used to be straightforward: selectors always looked downward. :host ruined that. :has() made it way worse. Any descendant change can now force you to walk up the tree finding ancestors whose :has() arguments just flipped, and a lot of this month’s invalidation work is about making that walk less wasteful.

    Four big wins this month:

    • Reddit rule cache rebuilds: 13.2s → 3.2s. Stylesheet mutations no longer rebuild every style scope’s cache when only one scope changed. (#9138)
    • Reddit infinite scroll: 11% fewer pointless recomputes. Sibling structural invalidation stopped fanning out to descendants that don’t observe the position. (#9155)
    • :has() mutation invalidation skips unaffected anchors , with substantial reductions measured on azure.com. (#9168)
    • :has() child-list visits on the Intel ISA PDF: 71k → 1.6k. Coalesced when pending data already covers every concrete feature bucket the scope cares about, saving ~650ms on the pdf.js load. (#9179)

    A large new structural-invalidation test battery exposed and fixed several invalidation holes (#9095), and a string of smaller tightenings landed around hover, stylesheet mutation scope, custom-property maps, and computed-style diffing (#9077, #9049, #9079, #9080, #9141).

    Linux GPU painting via dmabuf

    On Linux Vulkan builds, GPU-backed painting was being secretly undone every frame: WebContent painted into a GPU-backed Skia surface, but the buffer it shared with the UI process was a CPU bitmap, which forced a full GPU-to-CPU readback on every flush. SharedImage can now carry a Linux dmabuf handle, so the front and back buffers stay GPU-resident the whole way to the UI process. (#8917, #8920)

    mimalloc as the main allocator

    Our C++ and Rust code now share a single allocator instance, mimalloc v2, instead of each going through the system allocator separately (#8752). We don’t override malloc() system-wide, so third-party libraries keep their own allocator contracts. JS benchmarks improved across the board.

    Sites that work better

    The biggest visible wins this month are on Reddit and YouTube.

    Reddit image gallery carousels actually work now, after fixing two unrelated layout bugs around ::slotted() matching and absolutely positioned descendants of split inlines (#9148). And thanks to TextDecoderStream, the SPA stops swallowing link clicks, so you can finally open the comments! Infinite scroll also benefits from the structural-invalidation work covered above.

    YouTube benefits from a stack of unrelated improvements: off-thread top-level JS compile, off-thread WOFF2 decompression (saves ~170ms on Gmail too, #8976), reduced @font-face fetch fanout (177 → ~9 fetches on initial load, #9032), the RequestServer memory churn fix, and zero-copy TransferArrayBuffer.

    A handful of smaller fixes:

    Web Platform Tests (WPT)

    Our WPT score went from 2,003,537 to 2,067,263 this month, a headline gain of 63,726 subtests. There’s an asterisk on that number: WPT imported test262, the official ECMAScript conformance suite, upstream this month, which added 53,207 JavaScript subtests to the count. We pass 52,045 of them (a 97.8% pass rate), since we’ve been running test262 independently for years and LibJS conformance is in great shape. So roughly 52k of the 63.7k gain is from the import, and the remaining ~11.7k is genuine new browser-platform progress, in the same ballpark as January’s 13,690.

    The upside of the import is that WPT now actually measures JavaScript conformance alongside the rest of the platform, which is the way it should have been all along.

    Other notable changes

    • Rust is now mandatory : The ENABLE_RUST build option is gone (#8742), and the GN build system was removed entirely, leaving CMake as the single source of truth (#8931).
    • Stack-zeroing for the GC’s benefit : We now compile with -ftrivial-auto-var-init=zero, which overwrites stale GC pointers on function entry so our conservative stack scanner finds fewer of them. (#9171)
    • Selecting through ligatures : Selection and hit testing on text with ligatures used to assume one glyph per code unit, so trying to highlight half of a selected either all or none. We now walk grapheme clusters and split each glyph’s advance across the graphemes it covers. (#8829)
    • Layout state slimming : Rarely-used UsedValues properties moved behind a lazy pointer, dropping the struct from 424 to 176 bytes and cutting LayoutState::populate_node_from() from 139ms to 65ms while loading sainsburys.co.uk. (#9104)
    • Targeted ShadowRoot layout invalidation : Setting innerHTML on a shadow root no longer invalidates the entire document’s layout tree. Reduces layout-and-paint time on pomax.github.io/bezierinfo by 21%. (#9191)
    • Fetch body chunks straight into the stream : Fetched bytes used to be delivered through a pull-promise dance that allocated seven GC objects per chunk and did nothing useful. They now go directly into the byte stream controller. (#9169)
    • Cross-site popup navigation : Navigating a popup tab to a different site no longer kills the parent’s WebContent process. (#8730)
    • Ctrl+Tab for tab navigation : On the Qt UI, Ctrl+Tab and Ctrl+Shift+Tab cycle through open tabs. (#8704)
    • Middle-mouse autoscroll : Hold the middle mouse button and drag to scroll, or click in place to enter autoscroll mode. (#8881, #8928)
    • Address-bar error page : When you type text into the address bar that can’t be sanitized into a URL or search query (for instance, with search disabled), you now get a proper error page instead of the input being silently dropped. (#9072)
    • TextDecoderStream : The streaming counterpart to TextDecoder is now implemented, including the partial-UTF-8 hold-back across chunk boundaries that makes the Reddit comments fix above possible. (#9143)
    • Cross-process BroadcastChannel : Messages now route over IPC between WebContent and WebWorker processes, so a BroadcastChannel works the same way it does in other browsers regardless of which process the listener is in. (#8865)

    Credits

    We’d like to give a special shout-out to the 7 people who made their first code contribution this month:

    • CalebC48. Made the Qt UI open the new tab page when “New Window” is invoked without a URL (#8864)
    • Darshanx256
      • Preserved copy flags when recursing into directories in LibFileSystem (#8834)
      • Updated documentation to use the Release CMake preset (#8814)
    • j-stechmann. Added Ctrl+Tab and Ctrl+Shift+Tab for tab navigation in the Qt UI (#8704)
    • James Raspass. Switched the settings dialogs over to native light dismiss (#8808)
    • jarusll. Added support for multiple --debug-process arguments to LibWebView (#8841)
    • slydetector. Various devcontainer fixes to get things working again (#9149)
    • Yayoi-cs. Reported and fixed a use-after-free in TypedArray views over shared WebAssembly.Memory (GHSA-w89h-j2xg-c457)

    And of course we’d like to thank everyone who contributed code this month:

    Ali Mohammad Pur, Aliaksandr Kalenik, Andreas Kling, Andrew Kaster, ayeteadoe, CalebC48, Callum Law, Christian Frey, Darshanx256, Glenn Skrzypczak, InvalidUsernameException, James Raspass, Jelle Raaijmakers, Johan Dahlin, Jonathan Gamble, Jonathan (j-stechmann), jarusll, Luke Wilde, mikiubo, Nicolas Danelon, Ollie Hensman-Crook, Pavel Shliak, Psychpsyo, R-Goc, RubenKelevra, Sam Atkins, Shannon Booth, slydetector, Suraj Yadav, Tete17, Tim Ledbetter, Timothy Flynn, Undefine, Yayoi-cs, Zaggy1024

    ⚡ **What’s your take?**
    Share your thoughts in the comments below!

    #️⃣ **#Month #Ladybird #April**

    🕒 **Posted on**: 1777757193

    🌟 **Want more?** Click here for more info! 🌟

    By

    Leave a Reply

    Your email address will not be published. Required fields are marked *