이번 달의 Ladybird – 2026년 4월

1 hour ago 1
  • Ladybird는 4월에 35명 기여자의 333개 PR을 병합했고, Human Rights Foundation의 50,000달러와 Jakub Stęplowski의 1,000달러 후원을 새로 받음
  • 인라인 PDF 뷰어, 방문 기록 기반 주소창 자동완성, GTK4/libadwaita Linux 프런트엔드, about:bookmarks 관리 UI가 추가되어 브라우징 기능과 데스크톱 UI가 확장됨
  • HTML 파서는 응답 본문을 점진적으로 파싱하고 추측 파서가 리소스를 미리 가져오며, JavaScript 최상위 컴파일은 백그라운드 스레드로 이동해 YouTube 로딩에서 약 200ms의 메인 스레드 시간을 줄임
  • JavaScript 엔진은 for-in 캐시, O(1) 레지스터 할당기, zero-copy 식별자 공유, lazy JS::Substring, typed-array view cache 개선 등으로 Speedometer와 실제 사이트 로딩 성능이 개선됨
  • Cache/CacheStorage, image-set(), CSS anchor positioning, 비동기 DNS, dmabuf 기반 GPU 페인팅, mimalloc 기본화, Rust 필수화가 포함됐고 WPT 점수는 2,003,537에서 2,067,263으로 상승함

개발 규모와 후원

  • 4월 Ladybird는 35명 기여자의 333개 PR을 병합했고, 이 중 7명은 Ladybird에 처음 커밋함
  • 신규 후원자로 Human Rights Foundation이 “AI for Individual Rights” 프로그램을 통해 50,000달러, Jakub Stęplowski1,000달러를 후원함
  • Ladybird는 오픈 웹을 지지하는 기업과 개인의 후원으로 전액 운영됨

브라우징 기능과 프런트엔드

  • 인라인 PDF 뷰어

    • PDF가 번들된 pdf.js 뷰어를 통해 인라인 렌더링됨(#9132)
    • pdf.js는 JavaScript, HTML, CSS만으로 작성된 PDF 뷰어이며 페이지 탐색, 텍스트 선택, 확대/축소, 문서 내 검색을 제공함
    • Intel ISA Manual을 pdf.js로 로딩하는 과정에서 typed-array view cache와 :has() 무효화 개선 지점이 드러남
  • 방문 기록과 주소창 자동완성

    • 주소창 입력 시 방문 기록 기반의 풍부한 추천이 표시되며, 이전 방문 페이지의 파비콘과 제목, 검색 엔진 바로가기, 일반 URL 완성을 포함함(#8933)
    • SQLite 기반 HistoryStore가 모든 탐색의 제목, 파비콘, 방문 횟수, 마지막 방문 시각을 저장함
    • 개인정보 설정 페이지에 “Clear browsing history”가 연결됐고, Qt와 AppKit UI 모두 새 리치 행을 렌더링함
  • GTK4 / libadwaita 프런트엔드

    • Ladybird에 GTK4와 libadwaita 기반의 새 Linux 프런트엔드가 추가됐으며 기존 Qt 프런트엔드와 나란히 제공됨(#8691)
    • GNOME Web(Epiphany)에서 영감을 받았고 GNOME 설계 지침에 따라 메뉴바 없이 햄버거 메뉴와 AdwTabView 탭을 사용함
    • URL 바 자동완성 및 보안 아이콘, 페이지 내 찾기, 전체화면, 콘텍스트 메뉴, alert/confirm/prompt/color/file 대화상자, 클립보드, 다중 창, 라이트/다크 테마, DPR 스케일링을 제공함
    • 아직 초기 단계라 Qt 및 AppKit 프런트엔드와 기능 동등성에는 도달하지 못함
  • 북마크

    • 지난달 추가된 북마크에 관리 UI가 더해짐
    • about:bookmarks 페이지에서 북마크와 폴더를 관리할 수 있음(#8825)
    • 새 페이지에서 북마크 가져오기와 내보내기를 지원함(#8938)
    • 북마크와 폴더 편집용 콘텍스트 메뉴가 추가됨(#8715)
    • 모든 북마크와 폴더에 date_added 타임스탬프가 추가됨(#8867)
    • 북마크 바에서 새 탭 열기, URL 복사, 가운데 클릭 및 Ctrl/Cmd+클릭 새 탭 열기를 지원함(#8758)
    • HTML5 드래그 앤 드롭 API가 연결됐고 about:bookmarks는 이를 재정렬에 사용하며 일반 웹 페이지에서도 동작함(#8783)

HTML 파싱, 스크립트 실행, 렌더링 파이프라인

  • 추측 및 점진적 HTML 파싱

    • HTML 파서가 응답 본문을 점진적으로 소비하도록 바뀜(#9151)
    • 바이트는 스트리밍 텍스트 디코더를 지나 토크나이저에 청크 단위로 들어가며, 입력이 부족하면 토크나이저가 멈췄다가 더 도착하면 재개함
    • 전체 본문 수신을 기다린 뒤 파싱을 시작하던 기존 모델을 대체함
    • 추측 HTML 파서도 구현됨(#9114)
    • 메인 파서가 동기 외부 스크립트에서 막히면 별도 토크나이저가 아직 파싱되지 않은 입력을 앞서 스캔하고 <script src>, <link rel=stylesheet|preload>, <img src> 리소스의 추측 가져오기를 발행함
    • <base href>를 추적하고 템플릿 및 foreign content 내부를 올바르게 건너뜀
    • 추측 파서가 문서의 preload map에 연결되어 추측으로 발견된 리소스가 일반 파서의 이후 가져오기와 중복 제거됨(#9164)
  • 오프스레드 JavaScript 컴파일

    • 가져온 스크립트의 최상위 코드 바이트코드 생성이 백그라운드 스레드 풀에서 실행됨(#9118)
    • 워커 스레드는 바이트코드와 Executable 생성에 필요한 데이터를 만들고, VM 또는 GC 힙을 건드리는 작업은 메인 스레드에 남김
    • classic script, module, 최상위 IIFE를 처리하며 YouTube 로딩만으로도 약 200ms의 메인 스레드 시간이 백그라운드 스레드로 이동함
  • Navigable별 래스터화

    • 각 Navigable이 자체 스레드에서 독립적으로 래스터화됨(#8793)
    • 이전에는 iframe이 부모 display list 내부의 중첩 display list로 동기 페인트되어 최상위 traversable의 렌더링 스레드만 활성화됐음
    • 부모 display list는 이제 ExternalContentSource를 통해 각 iframe의 래스터화된 출력을 참조하므로 iframe 무효화가 부모 재기록을 요구하지 않음
    • 병렬성 외에도 iframe을 별도 샌드박스 프로세스로 옮기기 위한 준비 작업임
  • Linux dmabuf 기반 GPU 페인팅

    • Linux Vulkan 빌드에서 WebContent는 GPU-backed Skia surface에 페인트했지만 UI 프로세스와 공유하는 버퍼가 CPU 비트맵이라 매 flush마다 GPU-to-CPU readback이 발생했음
    • SharedImage가 Linux dmabuf 핸들을 담을 수 있게 되어 front/back buffer가 UI 프로세스까지 GPU에 상주함(#8917, #8920)

JavaScript 엔진 성능과 호환성

  • JS-to-JS 호출 최적화

    • Call, Return, End 명령이 일반적인 경우 AsmInt 어셈블리 인터프리터 안에 머물도록 여러 PR이 적용됨(#8891, #8909, #8912)
    • 레지스터 저장/복구에는 수작업으로 조정된 ARM64 paired load/store(ldp/stp)를 사용함
    • native function 호출도 AK::Function 대신 일반 함수 포인터를 담는 새 RawNativeFunction variant를 통해 AsmInt에서 직접 디스패치됨(#8922)
  • O(1) 바이트코드 레지스터 할당기

    • Generator::allocate_register는 사용 가능한 풀을 스캔해 가장 낮은 번호의 레지스터를 찾던 구조였고, x.com 로딩 중 이 함수만 약 800ms를 소비했음
    • C++/Rust 파이프라인 동등성 기간이 끝난 뒤 할당기는 단순 LIFO 스택으로 바뀜(#9007)
  • 캐시된 for-in 반복

    • for (key in obj) 위치가 평탄화된 enumerable key 스냅샷을 캐시하고, receiver의 shape, indexed storage, prototype chain이 유지되는 동안 재사용함(#8856)
    • Speedometer 2는 67.7 → 73.6, Speedometer 3는 4.11 → 4.22로 상승함
  • 기타 엔진 개선

    • 파서가 lexer, parser, scope collector 전반에서 식별자 이름을 zero-copy로 공유해 웹사이트 JS 코퍼스에서 파싱이 1.14배 빨라지고 RSS가 282MB 감소함(#8801)
    • 짧은 문자열 연결은 결과가 어차피 flat string으로 관찰될 때 rope 표현을 건너뛰며, 촘촘한 a + b 루프에서 2.13배 빨라짐(#9184)
    • lexical-this arrow function은 호출마다 function environment를 할당하지 않아 마이크로벤치마크에서 2.13배 개선됨(#9192)
    • sparse array는 hole에 대한 즉시 비용을 내지 않으며 Array(20_000_000)이 2천만 개의 가상 요소에 비례하는 작업 대신 대부분 메타데이터로 유지됨(#8847)
    • 새 lazy JS::Substring 타입이 regexp capture와 slice, split, indexed access 같은 string builtin을 뒷받침하며 Octane regexp 벤치마크에서 1.066배 개선됨(#8863)
    • 바이트코드 source map에서 source position이 end-to-end로 보존되어 x.com에서 약 250ms를 절약함(#9027)
    • zero-copy TransferArrayBuffer가 YouTube 로딩에서 약 130ms를 절약함(#9088)
    • cached typed-array view가 WeakHashSet에서 intrusive list로 바뀌어 pdf.js에서 Intel ISA PDF를 로딩할 때 약 250ms를 절약함(#9180)
    • 모든 Promise가 capture하지 않는 AK::Function closure를 가진 PromiseResolvingFunction 셀 2개를 할당하던 구조에서, Kind enum으로 디스패치되는 static function으로 바뀌어 resolver별 할당이 제거됨(#9188)
    • non-dictionary shape의 property-table marking을 건너뛰어 maptiler.com 로딩 중 GC 시간이 1.3초 줄어듦(#9044)
    • packed array의 Array.prototype.indexOf fast path가 추가됨(#9123)
    • Array.prototype.sort가 비교마다 재트랜스코딩하지 않고 캐시된 UTF-16을 재사용함(#9036)
    • WASM, JSON, CSS modules import가 추가됨(#6029)
    • ShadowRealm 제안이 표준화 과정에서 정체되어 지원이 제거됨(#8753)

웹 플랫폼 API와 CSS

  • Cache와 CacheStorage

    • Cache와 CacheStorage가 end-to-end로 구현됨(#8745)
    • open, has, delete, keys, match, matchAll, add, addAll, put 등 9개 메서드가 일시적 인메모리 저장소를 기반으로 동작함
  • CSS 기능과 렌더링 수정

    • image-set()의 표준 및 -webkit- prefix 형태에 대한 기본 지원이 추가됐고, 페인트 시점에 device pixel ratio와 가장 잘 맞는 해상도 후보를 선택하며 지원하지 않는 MIME 타입은 건너뜀(#9090)
    • image-set() 지원으로 gocomics.com의 헤더 이미지가 표시됨
    • position-anchor와 CSS anchor positioning 초기 지원이 추가되어 cssdoom.wtf의 손과 총 위치가 수정됨(#8686)
    • 색상 보간이 css-color-4에 맞춰 재작성되어 u8 대신 float에서 보간하고, missing/powerless component, out-of-gamut sRGB, alpha multiplier를 일관되게 처리함(#8934)
    • align, bgcolor 같은 legacy presentational HTML attribute가 직접 cascaded properties를 쓰지 않고 일반 author declaration처럼 cascade를 통과해 var() 치환과 invalid-at-computed-value-time fallback이 올바르게 동작함(#9176)
    • presentational hint cascade 변경으로 html.spec.whatwg.org의 crash가 수정됨
    • <thead>, <tbody>, <tfoot>, <tr>가 align presentational attribute를 반영해 bricklink.com의 버튼 배치가 수정됨(#9177)
    • stroke-dasharray 보간으로 SVG dash가 부드럽게 애니메이션됨(#9133)
    • autofocus attribute가 있는 요소가 페이지 로드 시 실제로 focus를 받음(#9016)
    • RTL 텍스트의 list marker가 오른쪽에 배치되어 Arabic Wikipedia의 목록 렌더링이 수정됨(#9099)
    • inline flex/grid container의 baseline이 마지막 wrapped line이 아니라 child의 first line box에서 파생되어 nos.nl의 링크 텍스트와 아이콘 정렬이 수정됨(#9183)

네트워킹과 스타일 무효화

  • 네트워킹

    • getaddrinfo가 더 이상 event loop를 막지 않음
    • LibDNS는 lookup을 thread pool에서 실행하고 A와 AAAA query를 병렬로 발행하며, 같은 이름에 대한 동시 lookup을 병합함(#9109)
    • RequestServer의 preconnect 경로가 resolver를 우회해 libcurl의 threaded resolver가 main thread에서 pthread_join을 유발하던 문제가 같은 DNS pool 경로로 라우팅되도록 수정됨(#9109)
    • WebContent가 네트워크보다 느릴 때 RequestServer의 queued response data drain이 O(n²)였고, YouTube 비디오를 열 때 memcpy에 약 30초, Vector::remove에 3초를 소비했음
    • AllocatingMemoryStream이 singly-linked chunk list로 바뀌어 소비가 O(1)이 됨(#9028)
    • 이미지 요청의 Accept header에 AVIF와 WebP를 광고해 다른 엔진과 맞췄고, 일부 CDN은 이 헤더로 최신 포맷 제공 여부나 JPEG fallback을 결정함(#9046)
  • 스타일 무효화

    • 기존 selector invalidation은 selector가 아래 방향만 본다는 전제에서 단순했지만, :host와 :has() 때문에 descendant 변경이 ancestor의 :has() 결과를 바꿀 수 있어 위로 올라가는 walk가 필요해짐
    • stylesheet mutation이 하나의 scope만 바꿨을 때 모든 style scope cache를 재구성하지 않도록 하여 Reddit rule cache rebuild가 13.2초 → 3.2초로 줄어듦(#9138)
    • sibling structural invalidation이 position을 관찰하지 않는 descendant로 퍼지지 않게 되어 Reddit infinite scroll에서 불필요한 recompute가 11% 감소함(#9155)
    • :has() mutation invalidation이 영향받지 않는 anchor를 건너뛰며 azure.com에서 큰 감소가 측정됨(#9168)
    • Intel ISA PDF에서 :has() child-list visit이 71k → 1.6k로 줄었고, pdf.js 로딩에서 약 650ms를 절약함(#9179)
    • 새 structural-invalidation 테스트 묶음이 여러 무효화 누락을 드러냈고 이를 수정함(#9095)
    • hover, stylesheet mutation scope, custom-property map, computed-style diffing 주변의 작은 개선도 포함됨(#9077, #9049, #9079, #9080, #9141)

메모리 할당과 빌드 체계

  • mimalloc 기본 할당기

    • C++와 Rust 코드가 각각 system allocator를 거치지 않고 mimalloc v2 단일 allocator instance를 공유함(#8752)
    • malloc()을 시스템 전체로 override하지 않으므로 third-party library는 자체 allocator contract를 유지함
    • JS benchmark가 전반적으로 개선됨
  • Rust 필수화와 빌드 시스템 정리

    • ENABLE_RUST 빌드 옵션이 제거되어 Rust가 필수가 됨(#8742)
    • GN build system이 완전히 제거되어 CMake가 단일 기준으로 남음(#8931)
  • GC와 메모리 관련 변경

    • -ftrivial-auto-var-init=zero로 컴파일해 함수 진입 시 오래된 GC pointer를 0으로 덮고, conservative stack scanner가 이를 덜 찾도록 함(#9171)
    • 드물게 쓰이는 UsedValues property가 lazy pointer 뒤로 이동해 struct가 424바이트 → 176바이트로 줄었고, sainsburys.co.uk 로딩 중 LayoutState::populate_node_from()가 139ms → 65ms로 줄어듦(#9104)
    • fetch body chunk가 pull-promise 경로를 거치며 chunk당 GC object 7개를 할당하던 구조에서 byte stream controller로 직접 들어가도록 바뀜(#9169)

개선된 사이트 동작

  • Reddit

    • Reddit 이미지 갤러리 캐러셀이 동작하며, ::slotted() matching과 split inline의 absolutely positioned descendant 주변의 서로 무관한 layout bug 2개가 수정됨(#9148)
    • TextDecoderStream 덕분에 SPA가 링크 클릭을 삼키지 않아 댓글을 열 수 있음
    • infinite scroll도 structural invalidation 작업의 영향을 받음
  • YouTube

    • YouTube는 off-thread top-level JS compile, off-thread WOFF2 decompression, @font-face fetch fanout 감소, RequestServer memory churn 수정, zero-copy TransferArrayBuffer의 영향을 받음
    • off-thread WOFF2 decompression은 Gmail에서도 약 170ms를 절약함(#8976)
    • 초기 로드에서 @font-face fetch fanout이 177 → 약 9개로 줄어듦(#9032)
  • 기타 사이트

    • gocomics.com은 image-set() 덕분에 헤더 이미지가 표시됨
    • yandex.com/maps는 WEBGL_debug_renderer_info 확장을 포함한 WebGL 수정으로 vector-tile WebGL 렌더링이 동작함(#9043)
    • strava.com는 Navigator.getBattery가 자체 오류가 아니라 spec-mandated error type을 던지게 되어 로그인이 동작함(#8770)
    • GitHub Insights는 Element.matches()와 .closest() selector cache 덕분에 약 100ms 빠르게 로드됨(#8987)
    • tweakers.net의 노트북 비교 페이지는 indexed HTMLFormElement property name lookup으로 약 31% 빨라짐(#9009)
    • neon.com은 더 이상 crash하지 않음(#8812)
    • channel4.com은 flex auto-margin resolution 수정으로 category text의 세로 정렬 문제가 해결됨(#9050)
    • Cloudflare Turnstile은 아직 통과하지 못하지만, auth-scheme handling, Array.prototype.shift() 최적화, <input> range 및 number element의 UA event handler hardening 덕분에 훨씬 빠르게 실패함(#9063)

WPT와 기타 플랫폼 변화

  • Web Platform Tests

    • WPT 점수가 2,003,537 → 2,067,263으로 올라 이번 달 63,726개 subtest 증가를 기록함
    • 다만 WPT가 공식 ECMAScript conformance suite인 test262를 upstream으로 가져오며 JavaScript subtest 53,207개가 추가됨
    • Ladybird는 test262를 수년간 별도로 실행해 왔고 LibJS conformance가 좋은 상태라 이 중 52,045개, 즉 97.8% 를 통과함
    • 63.7k 증가분 중 약 52k는 test262 import에서 왔고, 나머지 약 11.7k가 실제 새 브라우저 플랫폼 진전임
    • test262 import로 WPT가 JavaScript conformance를 나머지 플랫폼과 함께 측정하게 됨
  • 텍스트, 레이아웃, 프로세스, UI

    • ligature가 있는 텍스트의 selection과 hit testing이 한 glyph당 한 code unit을 가정하던 구조에서 grapheme cluster를 순회하고 glyph advance를 해당 grapheme들에 나누는 방식으로 바뀜(#8829)
    • shadow root에 innerHTML을 설정해도 전체 document layout tree를 무효화하지 않으며, pomax.github.io/bezierinfo에서 layout-and-paint 시간이 21% 줄어듦(#9191)
    • popup tab을 다른 site로 탐색해도 parent의 WebContent process가 종료되지 않음(#8730)
    • Qt UI에서 Ctrl+Tab과 Ctrl+Shift+Tab으로 열린 탭을 순환할 수 있음(#8704)
    • 가운데 마우스 버튼을 누른 채 드래그해 스크롤하거나, 제자리 클릭으로 autoscroll mode에 들어갈 수 있음(#8881, #8928)
    • 주소창 입력이 URL이나 검색 질의로 sanitize될 수 없을 때 입력을 조용히 버리는 대신 적절한 error page가 표시됨(#9072)
    • TextDecoder의 streaming counterpart인 TextDecoderStream이 구현됐고, chunk 경계에서 partial UTF-8을 hold-back해 Reddit 댓글 수정이 가능해짐(#9143)
    • cross-process BroadcastChannel 메시지가 WebContent와 WebWorker process 사이에서 IPC로 라우팅되어 listener가 어느 process에 있든 다른 브라우저와 같은 방식으로 동작함(#8865)
Read Entire Article