NextBSD WebKit v5 — Cocoa-native GNUstep porting plan Plan v5

Grounded in a source-level audit of WebKit trunk (July 2026), the GNUstep core library stack installed on NextBSD (libs-opal, libs-quartzcore, libs-corebase, libs-av, gnustep-base, gnustep-gui), and lessons from four prior porting attempts (v1–v4). This plan ports the macOS/Cocoa WebKit — not the GTK or Linux port — using native GNUstep framework implementations with zero GTK, GLib, GStreamer, Skia, Mesa, or Wayland dependencies.

TL;DR

What this plan is

Philosophy

Hard rules

Model macOS exclusively

We are porting the Cocoa WebKit. Every architectural decision follows what Apple's port does. When in doubt about how to structure something — a process, a rendering pipeline step, a threading model — look at Source/WebKit/Platform/mac/, Source/WebCore/platform/cocoa/, and Source/WebKit/UIProcess/Cocoa/. Those are the references.

No Linux desktop stack

Zero GTK, GLib, GStreamer, Skia, libsoup, Mesa, Wayland as WebKit dependencies. GNUstep's backends may use X11/Cairo internally — that's behind the framework boundary, not WebKit's concern. WebKit calls CGContextFillRect(), not cairo_fill(). What Cairo does internally inside libs-opal is irrelevant to WebKit.

Framework parity over workarounds

Where GNUstep is missing an API that macOS provides, the correct fix is to add that API to the appropriate GNUstep core lib (or create a new one) — not to substitute a Linux library. Substituting a Linux library is what v1 through v4 attempted in various ways, and it always led to architectural rot. The GNUstep framework stack exists precisely to avoid this. Use it. Extend it where necessary.

Gaps are the roadmap

Every missing API is documented (see §5 below). The gap list IS the work plan for GNUstep framework development. Gaps have three resolution paths:

  1. Implement in the appropriate GNUstep lib — the correct long-term answer for public Apple APIs.
  2. Stub for compile-time — acceptable for Apple SPI (private API) that we initially don't exercise.
  3. Guard with #if PLATFORM(GNUSTEP) — acceptable for code paths we explicitly disable (no Metal, no GPU process, etc.).

Minimal dependencies

If macOS WebKit doesn't link against it, neither do we. The dependency list should be: GNUstep core libs + libcurl (inside NSURLSession) + libxml2 + libxslt + sqlite3 + ICU + image libs (png, jpeg, webp) + font libs (freetype, harfbuzz, fontconfig) + woff2. That's it. Any build script that adds glib, gtk, gstreamer, or libsoup to the dependency list has gone off-track.

Start with the smallest possible feature set

Disable everything optional. Get data:text/html,<body style="background:red"> rendering in MiniBrowser.app. Then iterate. Each phase adds one category of capability and verifies it before moving on. A browser that shows a red window is infinitely more valuable than a browser that almost compiles with video support.

Prior art — lessons from v1–v4

VersionBase PortGraphicsResult
v1 GTK (strip GLib) Skia Abandoned — ~565 files to replace; GLib is woven through every GTK WebKit source file at the include level
v2 Windows port (no GLib) Skia Better — ~279 files, but still building a from-scratch browser on top of a non-Cocoa base
v3 Windows + Mac MiniBrowser shell Skia Build succeeded — window opens, no pixels rendered; AcceleratedSurface::create() was RELEASE_ASSERT_NOT_REACHED()
v4 v3 + PageClient bridge Skia Bridge designedWKViewGNUstepPaint → NSBitmapImageRep → drawInRect: path architected; pixels never appeared because Skia's surface creation was unimplemented

Key lessons carried forward

Architecture

The macOS WebKit rendering pipeline

macOS WebKit: WebCore → CGContext (CoreGraphics/Apple) → CGBitmapContext backing store → CTFont/CTLine/CTRun (CoreText) for text shaping and layout → CGImage/CGImageSource (ImageIO) for image decoding → CALayer tree (QuartzCore) for compositing → NSView drawRect: displays to screen via WindowServer

GNUstep equivalent (same API, different backend)

GNUstep WebKit (v5): WebCore → CGContext (libs-opal/Cairo) → CGBitmapContext backing store → CTFont/CTLine/CTRun (libs-opal) for text via FreeType + HarfBuzz → CGImage/CGImageSource (libs-opal) for image decoding → CALayer tree (libs-quartzcore) for compositing via OpenGL → NSView drawRect: (gnustep-gui) displays to screen via X11/gnustep-back

The critical point: WebKit code doesn't change. It calls CGContextFillRect(), CTFontGetGlyphsForCharacters(), [CALayer addSublayer:], [NSView setNeedsDisplay:] — identical calls on macOS and GNUstep. The framework implementations differ internally. The call sites in WebKit source remain the same.

Multi-process architecture

MiniBrowser.app (UIProcess) │ ├── WebKitWebProcess (WebProcess) — renders pages via CGContext → CGBitmapContext │ └── IPC ──────────────────────────── Unix domain socket (not Mach ports) ├── WebKitNetworkProcess (NetworkProcess) — handles network via NSURLSession/libcurl │ └── IPC ──────────────────────────── Unix domain socket └── Shared memory (POSIX shm_open / memfd_create) for pixel buffer transfer

A note on IPC transport

macOS WebKit uses Mach ports for IPC and XPC for process launching. GNUstep uses Unix domain sockets — this was proven viable in v3/v4 using the PlayStation port's Source/WebKit/Platform/IPC/unix/ConnectionUnix.cpp. We do not use GLib's GSocket or any GLib construct. MachSendRight wrapper types get POSIX file-descriptor equivalents. NSXPCConnection in gnustep-base provides basic XPC-style IPC; process launching uses fork(2)/execve(2) directly.

For the initial single-process build (Phase 1–3), none of this IPC machinery is exercised — everything runs in a single process. Multi-process comes in Phase 3.

Framework gap analysis Core of the plan

For each Apple framework that macOS WebKit depends on, this section documents what GNUstep currently provides and what is missing. This gap table is the definitive work plan.

5a. CoreGraphics (CGContext, CGImage, CGPath, …)

GNUstep implementation: libs-opal — 35 public headers, Cairo rendering backend, LCMS2 color management.

APIGNUstep StatusNotes
CGContext (full drawing API)YesAll blend modes, paths, gradients, clipping, transforms, text drawing
CGBitmapContextYesCGBitmapContextCreate, CGBitmapContextCreateImage, data access
CGPath (mutable/immutable)YesFull path construction, arcs, curves, ellipses, rounded rects
CGImageYesAll pixel formats, alpha modes, premultiplied/non-premultiplied
CGImageSource / CGImageDestinationYesPNG, JPEG, TIFF, GIF decoding and encoding
CGColor / CGColorSpaceYesLCMS2 backend for color management; sRGB, DisplayP3, generic CMYK
CGFontYesFreeType/Fontconfig backend; glyph metrics, advances, bounding boxes
CGGradient / CGShadingYesLinear, radial gradients; axial and radial shadings
CGPatternYesPattern fills, colored and stencil patterns
CGLayerYesOffscreen drawing surfaces, reusable across contexts
CGPDFDocument / CGPDFPage / CGPDFContextYesFull PDF read/write support via Cairo PDF backend
CGAffineTransformYesAll transform construction and application functions
CGDataProvider / CGDataConsumerYesCallback-based and direct-pointer data access

CoreGraphics gaps

Gap: CGContextDrawConicGradient — not in libs-opal. Needed for CSS conic-gradient(). Cairo supports conic gradients since 1.17; needs to be wrapped.
Gap: CGPathAddUnevenCornersRoundedRect, CGPathAddContinuousRoundedRect — not in libs-opal. Required for CSS border-radius with per-corner radii and iOS-style "squircle" rounding. Must add to libs-opal.
Gap (SPI — stub): CGColorSpaceUsesExtendedRange, CGColorSpaceCopyICCProfileDescription — color management SPI. Stub as no-op / return nil initially; add LCMS2 implementation later.
Gap (SPI — stub): CGImageSetCachingFlags, CGImageSetProperty — image cache control SPI. Safe to stub as no-ops.
Gap (SPI — stub): CGContextSetBaseCTM — SPI. Map to CGContextSetCTM or stub; verify impact on text rendering.
Gap (SPI — implement over time): CGStyleRef family — CGStyleCreateFocusRingWithColor, CGStyleCreateShadow2, CGStyleCreateGaussianBlur, CGStyleCreateColorMatrix. Used for CSS filters (blur, drop-shadow, color-matrix). Stub initially; implement using Cairo's filter/surface API or a software pass.
Gap (requires new lib): CGIOSurfaceContextCreate and all IOSurface-backed CG APIs. Requires libs-iosurface (see §6). Not needed for Phase 1–4 software rendering path.
Gap (platform): CGDisplayScreenSize, CGDisplayModeGetPixelsWide, CGDisplayModeGetPixelsHigh — display management. Implement behind the API using X11/RandR queries or XGetGeometry.
Gap (not applicable): CGS* Window Server SPI — CGSConnectionID, CGSSetWindowAlpha, CGSSetWindowLevel, etc. Mach/WindowServer-specific. Guard with #if PLATFORM(GNUSTEP) and stub; not applicable under X11.
Verdict: CoreGraphics is 90%+ covered by libs-opal. The gaps are almost entirely Apple SPI (private API) that can be safely stubbed for an initial build, plus a handful of missing public functions (CGContextDrawConicGradient, CGPathAddUnevenCornersRoundedRect) that need to be added to libs-opal.

5b. CoreText (CTFont, CTLine, CTRun, …) — part of libs-opal

GNUstep implementation: CoreText is provided by libs-opal (the same library that provides CoreGraphics and ImageIO). Opal bundles CoreText as the OpalText subproject with 15 public headers under CoreText/, backed by FreeType/Fontconfig for font access and HarfBuzz for complex script shaping. This mirrors Apple's architecture where CoreText and CoreGraphics are both part of the same low-level graphics stack.

APIGNUstep StatusNotes
CTFont (create, metrics, glyphs)YesFull font creation, size, weight, slant, metric accessors
CTFontDescriptorYesAttribute-based font matching via Fontconfig
CTFontCollectionYesSystem font enumeration via Fontconfig
CTFontManagerYesFont registration, dynamic font loading from file
CTLine (create, draw, metrics)YesLine layout, line metrics (ascent/descent/leading/width)
CTRun (glyphs, advances, positions)YesPer-run glyph access, advance widths, origins
CTFrame / CTFramesetterYesFrame-based multi-line layout in an arbitrary path
CTTypesetterYesLine breaking, soft hyphenation
CTParagraphStyleYesParagraph alignment, line spacing, writing direction

CoreText gaps

Gap (SPI — important): CTFontCreateForCharactersWithLanguageAndOption — SPI for font fallback with language hint. Critical for correct international text rendering (CJK, Arabic, Indic scripts). Needs implementation in libs-opal using Fontconfig's language matching and HarfBuzz font selection.
Gap (verify): CTFontDrawGlyphs — public API for drawing an array of glyphs to a CGContext. May need verification that libs-opal's implementation handles all edge cases (vertical text, color emoji).
Gap (SPI — complex shaping): CTFontShapeGlyphs — SPI for complex script glyph shaping (Arabic ligatures, Indic conjuncts). HarfBuzz performs this internally; the SPI exposes it as a CT call. Needs a CT-API wrapper around HarfBuzz's hb_shape().
Gap (SPI — stub initially): CTFontCopyColorGlyphCoverage — SPI for detecting which codepoints have color (emoji) glyphs. Stub as returning NULL; implement by inspecting font CBDT/COLR tables later.
Gap (SPI — stub initially): CTFontIsAppleColorEmoji — SPI. Stub to always return false. Web content degrades gracefully (text emoji instead of color).
Gap (SPI — optimization): CTLineCreateWithUniCharProvider — SPI for efficient line layout avoiding NSAttributedString allocation per line. Implement later as a performance optimization; not needed for correctness.
Gap (SPI — vertical text): CTRunGetBaseAdvancesAndOrigins — SPI for vertical text metrics. Stub initially; implement when adding vertical writing mode support.
Gap (important for CSS): CTFontDescriptorCreateForCSSFamily — SPI mapping CSS generic font families (serif, sans-serif, monospace, cursive, fantasy) to actual system fonts. Essential for web content to render with appropriate fonts. Implement using Fontconfig's generic alias system.
Gap (important for fallback): CTFontCopyDefaultCascadeListForLanguages — public API returning the font fallback cascade list for a set of BCP 47 language tags. Needed for correct rendering of multilingual pages. Implement using Fontconfig's font substitution database.
Gap (SPI — stub): OTSVG color vector font rendering — Apple SPI for rendering SVG-in-OpenType fonts (color vector emoji). Stub; not critical for initial web content.
Verdict: CoreText is ~80% covered. The base text layout pipeline works. Font fallback cascade (CTFontCopyDefaultCascadeListForLanguages) and CSS family mapping (CTFontDescriptorCreateForCSSFamily) are the most critical missing pieces for correct web content rendering — these must be implemented before Phase 6 (real web content).

5c. CoreFoundation (CFString, CFRunLoop, CFURL, …)

GNUstep implementation: libs-corebase — 36 public headers.

APIGNUstep StatusNotes
CFString / CFMutableStringYesFull Unicode string API, encoding conversion, string comparison
CFArray / CFMutableArrayYesTyped collections with custom callbacks
CFDictionary / CFMutableDictionaryYesHash table collections
CFSet / CFMutableSetYesSet collections
CFData / CFMutableDataYesByte buffer type
CFNumber / CFBooleanYesNumeric boxing
CFRunLoop / CFRunLoopSource / CFRunLoopTimer / CFRunLoopObserverYesEvent loop primitives
CFURL / CFURLComponentsYesURL creation, resolution, component access
CFBundleYesBundle resource loading, Info.plist access
CFPropertyListYesPlist serialization (XML and binary formats)
CFDate / CFCalendar / CFTimeZone / CFLocaleYesDate, time, locale primitives
CFErrorYesError domain/code/userInfo model
CFSocket / CFStream / CFReadStream / CFWriteStreamYesSocket and stream I/O
CFUUIDYesUUID generation and string conversion

CoreFoundation gaps

Gap (critical — must implement): CFNotificationCenterMISSING from libs-corebase. WebKit uses CFNotificationCenterGetLocalCenter() and CFNotificationCenterGetDistributedCenter() extensively for inter-component notifications (preference changes, cache invalidation, font change broadcasts). Must add to libs-corebase. Implementation strategy: bridge to NSNotificationCenter — they share semantics.
Gap (critical — must implement): CFPreferencesMISSING from libs-corebase. CFPreferencesGetAppBooleanValue, CFPreferencesCopyAppValue, CFPreferencesSetAppValue are used for feature flags and user settings. Must add to libs-corebase. Implementation strategy: map to NSUserDefaults.
Gap (important — must implement): CFStringTokenizerMISSING from libs-corebase. Used for text boundary detection (word breaks, sentence breaks) for text selection, double-click word selection, spell checking. Implementation: wrap ICU's BreakIterator API.
Gap (stub initially): CFHTTPCookieRef, CFHTTPCookieStorageRef, CFURLStorageSessionRef — CFNetwork-layer types. Not in libs-corebase. Create stubs that delegate cookie management to NSHTTPCookieStorage.
Gap (stub initially): CFMessagePortMISSING. Used for lightweight in-process IPC. Stub with Unix domain socket or POSIX shared memory backing; implement fully later.
Gap (implement): CFFileDescriptorMISSING. A CFRunLoop source wrapping a file descriptor. Needed for integrating Unix sockets into the run loop. Implement using kqueue (NextBSD) via EVFILT_READ/EVFILT_WRITE.
Verdict: CoreFoundation is ~75% covered. CFNotificationCenter and CFPreferences are the most critical missing pieces — they must be added to libs-corebase before WebCore will compile cleanly. CFStringTokenizer is important for correct text interaction. The remaining gaps are CFNetwork types that can be delegated to NSURLSession/NSHTTPCookieStorage at a higher level.

5d. QuartzCore / CoreAnimation (CALayer, CAAnimation, …)

GNUstep implementation: libs-quartzcore — 24 public headers, OpenGL compositor.

APIGNUstep StatusNotes
CALayer (core properties, sublayers, display)PartialbackgroundColor, opacity, transform, sublayers, zPosition work; mask, frame, geometry conversion missing
CABasicAnimationYesfrom/to/by value interpolation
CAKeyframeAnimationYesvalues array, path, calculationMode, keyTimes
CASpringAnimationYesmass, stiffness, damping, initialVelocity
CATransactionPartialbegin/commit, disableActions, animationDuration work; completionBlock is TODO in source
CAMediaTimingFunctionYesNamed functions (ease, linear, etc.) + cubic bezier control points
CATransform3DYesFull 4×4 matrix math, perspective, rotation, scale, translate
CAShapeLayerYesFull CGPath-based vector layer with stroke/fill control
CARendererYesOpenGL-backed compositor for rendering layer trees
CAFilterPartialFilter name constants defined; CAFilter class is a stub

QuartzCore gaps

Gap (SPI — critical for GPU process): CAContextMISSING. SPI class critical for multi-process layer-tree compositing. On macOS, CAContext allows a WebProcess CALayer tree to be hosted in the UIProcess window via Mach port-based layer hosting. Without Mach ports, we need an alternative. For Phase 1–4 (ENABLE_GPU_PROCESS=OFF), not needed — we use software rendering via CGBitmapContext + POSIX shared memory blit. Long-term: implement CAContext in libs-quartzcore using Unix domain socket signaling + shared memory pixel buffers.
Gap (must implement): CADisplayLink — header exists as stub only. Needed for animation frame timing synchronized to display refresh. Implement using a timer thread that approximates 60Hz, or using X11's XSync extension, or DRM's vblank wait. Must implement before smooth CSS animations work.
Gap (must implement): CATiledLayer — stub. Used by WebKit for large-canvas tile-based page rendering (scrolling). Without it, very long pages won't render correctly. Must implement: a CALayer subclass that draws content in tiles across multiple threads.
Gap (must implement): CATextLayer — stub. Used for direct text compositing into the layer tree. Implement using CTLine/CGContext drawing to a CALayer backing store.
Gap (must implement): CAGradientLayer — stub. Used for gradient compositing. Implement using CGGradient drawing to a CALayer backing bitmap.
Gap (must implement for CSS masking): CALayer.mask — property not implemented. Needed for CSS mask, clip-path. Must add to CALayer in libs-quartzcore.
Gap (must implement): CALayer.frame — not implemented (only bounds+position are). Frame is derived but WebKit sets it directly. Must implement the setter (sets bounds + position) and getter (computes from bounds + position + anchorPoint).
Gap (must implement): CALayer geometry conversion: convertPoint:fromLayer:, convertPoint:toLayer:, convertRect:fromLayer:, convertRect:toLayer:. Used by WebKit hit testing and event routing. Must implement by walking the layer tree and composing transforms.
Gap (must implement): CATransaction completionBlock — marked TODO in libs-quartzcore source. Used to chain animation completions. Must implement.
Gap (SPI — stub): CATransaction addCommitHandler:forPhase: — SPI for commit-phase callbacks. Stub initially.
Gap (SPI — stub): CABackdropLayer — SPI for backdrop blur (CSS backdrop-filter). Stub; implement software blur pass later.
Gap (SPI — stub): CAPresentationModifier, CAMachPort — Mach-specific SPI. Guard with #if PLATFORM(GNUSTEP); replace CAMachPort with POSIX file descriptor equivalent.
Verdict: QuartzCore is ~50% covered. CALayer basics work, and the animation system is solid. For the initial software-rendering build (ENABLE_GPU_PROCESS=OFF), the biggest gaps to address first are: CALayer.frame, geometry conversion methods, CATransaction completionBlock, CADisplayLink, and CATextLayer/CAGradientLayer. CAContext and multi-process compositing are Phase 3+ concerns.

5e. Foundation (NSObject, NSString, NSRunLoop, NSURLSession, …)

GNUstep implementation: gnustep-base — 167 public headers. The most mature component in the GNUstep stack.

Verdict: Foundation is 95%+ covered. GNUstep Foundation is the most battle-tested part of the stack. The following APIs are all present and working: NSURLSession (libcurl backend), NSRunLoop, NSFileManager, NSJSONSerialization, NSXMLParser, NSOperationQueue, NSThread, NSNotificationCenter, NSUserDefaults, NSURL, NSData, NSString, NSAttributedString, NSRegularExpression, NSPredicate, NSKeyValueObserving, NSKeyedArchiver, NSDateFormatter, NSNumberFormatter, NSHashTable, NSMapTable, NSPointerArray, NSProgress, NSUndoManager, NSTask, NSPipe, dispatch_* (libdispatch). Minor gaps:

5f. AppKit (NSApplication, NSWindow, NSView, NSEvent, …)

GNUstep implementation: gnustep-gui — 303 public headers (256 implemented in source), rendered by gnustep-back X11 backend.

Verdict: AppKit is 90%+ covered for MiniBrowser needs. Everything required to build and display a browser window is present: NSApplication, NSWindow, NSView, NSTextField, NSButton, NSToolbar, NSMenu, NSMenuItem, NSEvent (mouse, keyboard, scroll), NSCursor, NSPasteboard, NSColor, NSFont, NSImage, NSBezierPath, NSGraphicsContext, NSScrollView, NSClipView, NSAlert, NSOpenPanel, NSSavePanel, NSSplitView, NSStackView, NSOpenGLView, NSScreen, NSWorkspace, NSTextInputClient (protocol), NSSpellChecker, NSPrintOperation. Notable status:

5g. Security Framework (SecCertificate, SecTrust, CommonCrypto)

GNUstep implementation: MISSING — no libs-security exists.

Gap (critical for HTTPS): The entire Security framework is absent. WebKit needs: Needs new libs-security core lib. For the initial build, TLS is handled by libcurl inside NSURLSession (using OpenSSL or GnuTLS). Security framework APIs can be stubbed initially and implemented later wrapping OpenSSL's X.509 API. Certificate errors will not display detailed information, but HTTPS connections will work through libcurl.
Gap (crypto primitives): CommonCryptoMISSING. CC_SHA1, CC_SHA256, CCHmac, CCCryptorGCM used in WebKit for SubtleCrypto, authentication tokens, and session key derivation. Implement as thin wrappers around OpenSSL's EVP API or libgcrypt. Low complexity — about 20 functions to wrap. Create libs-commoncrypto or incorporate into libs-security.

5h. IOSurface

GNUstep implementation: MISSING — no libs-iosurface exists.

Gap (Phase 3+ / GPU compositing): IOSurface is the macOS shared-memory buffer for zero-copy GPU texture sharing between processes. macOS WebKit uses IOSurface to pass rendered frames from WebProcess to UIProcess without copying pixels. For the initial software-rendering build (ENABLE_GPU_PROCESS=OFF), IOSurface is NOT needed. We use CGBitmapContext + POSIX shared memory (shm_open/memfd_create) for cross-process pixel transfer — exactly the approach proven in v3/v4. Long-term: create libs-iosurface wrapping DMA-BUF (Linux/BSD kernel) or POSIX shared memory with the full IOSurface C API (IOSurfaceCreate, IOSurfaceLock, IOSurfaceGetBaseAddress, IOSurfaceGetBytesPerRow).

5i. CoreVideo (CVDisplayLink, CVPixelBuffer)

GNUstep implementation: MISSING — no libs-corevideo exists.

Gap (Phase 3+ / display sync): CoreVideo is used for: CVDisplayLink (display refresh synchronization for smooth animation), CVPixelBuffer (video frame buffer management), CVImageBuffer, CVMetalTextureCacheRef. For initial build with ENABLE_VIDEO=OFF and ENABLE_GPU_PROCESS=OFF: not needed. Stub the headers. Long-term: create libs-corevideo. CVDisplayLink can be implemented using X11's XSync extension, DRM's DRM_IOCTL_WAIT_VBLANK, or a high-resolution timer thread targeting 60Hz. CVPixelBuffer is a tagged pixel buffer with plane descriptors — straightforward to implement wrapping a malloc'd buffer or DMA-BUF.

5j. CoreMedia / CoreAudio / AudioToolbox / VideoToolbox

GNUstep implementation: libs-av already provides CMTime and CMTimeRange in AVTime.h, and its FFmpeg backend already handles the audio/video decode pipeline that CoreMedia, AudioToolbox, and VideoToolbox represent on macOS. The remaining work is expanding libs-av's API surface to expose these capabilities through Apple-compatible headers.

Gap (Phase 3+ / media): For initial build with ENABLE_VIDEO=OFF and ENABLE_WEB_AUDIO=OFF: these entire frameworks can be stubbed. Long-term, these are extensions to libs-av (not new standalone libs), since libs-av already has the FFmpeg backend:

5k. AVFoundation (AVPlayer, AVAudioPlayer)

GNUstep implementation: libs-av — 11 public headers, FFmpeg backend for basic playback.

Gap (Phase 3+ / media): For initial build with ENABLE_VIDEO=OFF: not needed. libs-av covers basic AVPlayer/AVAudioPlayer for future video support. Gaps for eventual video support:

5l. Metal (GPU rendering)

GNUstep implementation: MISSING — not applicable on X11/OpenGL platforms.

Gap (disabled for initial build): macOS WebKit uses Metal for WebGL, WebGPU, and GPU-accelerated compositing. For initial build with ENABLE_WEBGL=OFF, ENABLE_WEBGPU=OFF, and ENABLE_GPU_PROCESS=OFF: not needed at all. Zero Metal code will be compiled. Long-term: consider Vulkan as a Metal-compatible GPU API (MoltenVK proves the semantic equivalence), or implement WebGL directly via OpenGL through libs-quartzcore's existing OpenGL infrastructure. This is the lowest priority item in the entire porting plan.

5m. Accelerate (vDSP, vImage)

GNUstep implementation: MISSING.

Gap (Phase 3+): Accelerate framework is used for: For initial build with ENABLE_WEB_AUDIO=OFF: vDSP not needed. CSS filters without vImage can use fallback software paths in WebCore (WebKit has software fallbacks for all CSS filters). Long-term: implement the specific vDSP functions used (about 10–15: vDSP_fft_zrip, vDSP_vmul, vDSP_vadd, etc.) using FFTW or direct SIMD intrinsics. Implement vImage pixel conversion and convolution using optimized C loops or libswscale.

5n. CoreImage (CIFilter, CIContext)

GNUstep implementation: MISSING.

Gap (disabled for initial build): CoreImage is used only for video frame processing in the AVFoundation pipeline (applying color correction filters to decoded video frames). For initial build with ENABLE_VIDEO=OFF: not needed at all. No stubs required — just guard the code paths with #if ENABLE(VIDEO) which is already how WebKit structures these calls.

5o. Mach IPC / XPC (process communication)

GNUstep: Unix domain sockets + POSIX shared memory.

This is not a gap — it is a proven alternative. macOS WebKit uses Mach ports for low-latency IPC and XPC for process lifecycle management. GNUstep uses Unix domain sockets — this was proven viable in v3/v4 using the PlayStation port's Source/WebKit/Platform/IPC/unix/ConnectionUnix.cpp. The PlayStation port ships this file as production code. We use it directly. Key implementation notes:

5p. Other Apple frameworks (all disabled or stubbed for initial build)

FrameworkWebKit FeatureInitial Build Status
PassKitApple PayDisable: ENABLE_APPLE_PAY=OFF
GameControllerGamepad APIDisable: ENABLE_GAMEPAD=OFF
CoreLocationGeolocationDisable: ENABLE_GEOLOCATION=OFF
SpeechSpeech recognition / synthesisStub; disabled by feature flags
NaturalLanguageText analysis, language detectionStub
DataDetectorsLink/phone number detectionDisable: ENABLE_TELEPHONE_NUMBER_DETECTION=OFF
VisionKitLive Text in imagesStub
LinkPresentationRich link previewsStub
ContactsContact autofillStub
ScreenTimeScreen time enforcementStub
WritingToolsApple Intelligence writing assistanceDisable: ENABLE_WRITING_TOOLS=OFF
ARKitWebXR AR contentStub; ENABLE_WEBXR=OFF
BrowserEngineKitProcess hosting (iOS-new API)Replace with Unix socket + shared memory
CoreUINative widget rendering (SPI)Not needed — gnustep-gui/Eau renders native widgets via AppKit
ColorSyncICC color profile applicationStub — use LCMS2 via libs-opal's existing color management
IOKit / IOPMLibSleep prevention, battery statusStub — not critical; use kqueue power events if needed later
MediaRemoteAirPlay, Now Playing infoStub
Network.frameworkLow-level networking (NW path monitor)Not needed — use NSURLSession; stub NWPathMonitor
UniformTypeIdentifiersFile type system (UTType)Implement basic UTType → MIME mapping or stub common types

Feature flags that eliminate missing framework dependencies

Nearly every missing Apple framework can be sidestepped for the initial build by disabling the WebKit feature that requires it. This table maps each missing framework to the CMake flag that removes the dependency entirely:

Missing FrameworkCMake Flag to DisableEffect
AVFoundation / CoreMedia / AudioToolbox / VideoToolboxENABLE_VIDEO=OFFCascades to disable MediaSource, MediaStream, WebRTC, MediaRecorder, MediaSession, EncryptedMedia, PictureInPicture, AVF captions — removes entire media pipeline
CoreAudio / Accelerate (vDSP)ENABLE_WEB_AUDIO=OFFNo Web Audio API, no audio DSP, no FFT — eliminates all Accelerate/vDSP usage
MetalENABLE_WEBGL=OFF, ENABLE_WEBGPU=OFF, ENABLE_GPU_PROCESS=OFFNo GPU rendering path needed at all
IOSurfaceENABLE_GPU_PROCESS=OFFNo cross-process GPU surface sharing — software blit via shared memory instead
CoreVideo (CVDisplayLink)ENABLE_GPU_PROCESS=OFFNo display-sync needed for software rendering path
CoreImage (CIFilter, CIContext)ENABLE_VIDEO=OFFOnly used in video frame processing — disabled with video
Security frameworkNo flag neededTLS handled internally by libcurl inside NSURLSession; disable ENABLE_WEB_AUTHN=OFF to remove SecKey/FIDO2 dependency
GameControllerENABLE_GAMEPAD=OFFNo Gamepad API
CoreLocationENABLE_GEOLOCATION=OFFNo geolocation
PassKitENABLE_APPLE_PAY=OFFNo Apple Pay / Payment Request API
Speech frameworkENABLE_SPEECH_SYNTHESIS=OFFNo speech synthesis or recognition
WritingToolsENABLE_WRITING_TOOLS=OFFNo Apple Intelligence writing features
DataDetectorsENABLE_TELEPHONE_NUMBER_DETECTION=OFFNo phone number / link detection
PDFKitENABLE_PDF_PLUGIN=OFF, ENABLE_PDFKIT_PLUGIN=OFF, ENABLE_UNIFIED_PDF=OFFNo inline PDF viewing
ARKit / WebXRENABLE_WEBXR=OFFNo AR/VR/XR content
ContactsStub onlyContact picker not critical — empty stub
ScreenTimeStub onlyScreen time enforcement not applicable — empty stub
Result: With all the above flags set to OFF, the only frameworks that must actually work for the initial build are:

Every other missing framework is either disabled by a feature flag or covered by an empty stub header. Zero new GNUstep core libs are needed for the initial build. The new libs (libs-security, libs-iosurface, libs-corevideo, etc.) become needed only as features are re-enabled in later phases.

New GNUstep core libs needed Phase 2+

These libraries follow the GNUstep pattern: same Apple public header API, different internal implementation. Each is a new pkg in the GNUstep ecosystem.

LibraryPriorityWhat it providesComplexity
libs-security Phase 2 Security framework: SecCertificateRef, SecTrustRef, SecKeyRef, SecPolicyRef, SecTrustEvaluateWithError, SecCertificateCopySubjectSummary — wrapping OpenSSL's X.509 API. Also includes CommonCrypto: CC_SHA1/256/512, CCHmac, CCCryptor, CCCryptorGCM. Medium — ~50 functions to wrap; OpenSSL API is straightforward but Security framework semantics require careful mapping
libs-commoncrypto Phase 2 CommonCrypto standalone: hash functions (SHA1, SHA256, SHA512, MD5), HMAC, symmetric ciphers (AES-CBC, AES-GCM, 3DES), PBKDF2, random bytes. May be folded into libs-security. Low — ~20 thin wrappers around OpenSSL EVP or libgcrypt
libs-iosurface Phase 3+ IOSurface C API: IOSurfaceCreate, IOSurfaceLock/Unlock, IOSurfaceGetBaseAddress, IOSurfaceGetBytesPerRow, IOSurfaceGetWidth/Height, IOSurfaceGetPixelFormat, cross-process surface passing via file descriptor. Backend: DMA-BUF (preferred for GPU sharing) or POSIX shared memory (simpler, no GPU sharing). Medium — DMA-BUF integration requires kernel knowledge; POSIX shm backend is straightforward
libs-corevideo Phase 3+ CoreVideo: CVDisplayLink (display-sync timer), CVPixelBuffer (pixel plane management), CVPixelBufferPool, CVOpenGLTextureCacheRef. Backend: DRM vblank for CVDisplayLink; malloc/mmap for CVPixelBuffer. Medium — CVDisplayLink synchronization requires platform-specific vblank source
libs-av expansion (CoreMedia/AudioToolbox/VideoToolbox headers) Phase 3+ Expand libs-av with Apple-compatible headers: CMSampleBuffer, CMBlockBuffer, CMFormatDescription, CMBufferQueue (wrapping FFmpeg AVPacket/AVFrame); AudioConverter (wrapping swr_convert); VTDecompressionSession (wrapping avcodec). CMTime/CMTimeRange already in libs-av. Medium — the FFmpeg backend already exists in libs-av; this is adding Apple-compatible API wrappers around it
libs-accelerate Phase 3+ Accelerate framework subset: vDSP FFT (vDSP_fft_zrip, vDSP_fft_zop), vector arithmetic (vDSP_vadd, vDSP_vmul, vDSP_vsmul, vDSP_vdiv), vImage pixel format conversion and convolution (Gaussian blur, color matrix). Backend: FFTW for FFT; SIMD intrinsics or libswscale for vImage. Low-Medium — the ~15 vDSP functions WebKit uses are well-documented; vImage needs SIMD optimization work

All of these libs should be hosted alongside the existing GNUstep core libs (libs-opal, libs-quartzcore, libs-corebase, libs-av) in the GNUstep GitHub organization and follow the same CMake/GNUstep-make dual build system pattern.

Build system — buildtool (libs-xcode), not CMake+Ninja

Hard rule: model macOS

On macOS, WebKit is built with Xcode via .xcodeproj files — not CMake, not Ninja, not Make. The CMake+Ninja path exists only for the GTK/WPE/Windows/PlayStation ports. Since we are modeling the macOS port exclusively, we use buildtool from libs-xcode — GNUstep's tool for building from .xcodeproj files.

Why buildtool, not CMake

WebKit's .xcodeproj structure

WebKit.xcworkspace                    ← master workspace
  Source/WTF/WTF.xcodeproj            ← Web Template Framework (platform abstractions)
  Source/bmalloc/bmalloc.xcodeproj    ← memory allocator
  Source/JavaScriptCore/JavaScriptCore.xcodeproj  ← JS engine
  Source/WebCore/WebCore.xcodeproj    ← rendering engine
  Source/WebKit/WebKit.xcodeproj      ← multi-process layer (UIProcess/WebProcess/NetworkProcess)
  Tools/MiniBrowser/MiniBrowser.xcodeproj  ← browser shell

Build approach

. /System/Library/Makefiles/GNUstep.sh

# Build order (matching Xcode workspace dependency graph):
buildtool build -project Source/WTF/WTF.xcodeproj
buildtool build -project Source/bmalloc/bmalloc.xcodeproj
buildtool build -project Source/JavaScriptCore/JavaScriptCore.xcodeproj
buildtool build -project Source/WebCore/WebCore.xcodeproj
buildtool build -project Source/WebKit/WebKit.xcodeproj
buildtool build -project Tools/MiniBrowser/MiniBrowser.xcodeproj

Feature disable — via build settings, not CMake flags

On macOS, features are controlled by GCC_PREPROCESSOR_DEFINITIONS in the Xcode build configuration. To disable features for the minimal build, override build settings:

buildtool build -project Source/WebCore/WebCore.xcodeproj \
  GCC_PREPROCESSOR_DEFINITIONS='$(inherited) ENABLE_VIDEO=0 ENABLE_WEB_AUDIO=0 \
  ENABLE_WEBGL=0 ENABLE_WEBGPU=0 ENABLE_GPU_PROCESS=0 ENABLE_APPLE_PAY=0 \
  ENABLE_GAMEPAD=0 ENABLE_WEB_RTC=0 ENABLE_WEB_AUTHN=0 ENABLE_WEBXR=0 \
  ENABLE_PDF_PLUGIN=0 ENABLE_WEBASSEMBLY=0 ENABLE_SPEECH_SYNTHESIS=0'
Open question: buildtool needs to be validated against WebKit's .xcodeproj files, which are large and complex (WebCore.xcodeproj alone has thousands of file references). If buildtool cannot handle the full project initially, a fallback is to use buildtool generate to produce GNUmakefiles from the .xcodeproj and then build with gmake. This is still modeling macOS (same source file list, same build settings) but using GNUstep-make as the driver instead of xcodebuild.

Platform detection

WebKit's .xcodeproj build settings assume macOS/Xcode. We need to add GNUstep detection so the correct platform headers and libraries are found:

Source/WTF/wtf/PlatformLegacy.h
  ← modify: add PLATFORM(GNUSTEP) detection (same as v3/v4 approach)

Source/WTF/wtf/PlatformHave.h
  ← modify: set HAVE() macros for GNUstep capabilities

Source/WTF/wtf/PlatformUse.h
  ← modify: set USE() macros for GNUstep choices

Source/WTF/wtf/PlatformEnable.h
  ← modify: set ENABLE() overrides for disabled features

What this minimal build gives you

FreeBSD/NextBSD pkg dependencies

Only what macOS WebKit equivalent needs — no Linux desktop stack.

Build tools — must install

On macOS, Ruby and gperf ship with Xcode Command Line Tools. WebKit is built by Xcode (xcodebuild), not CMake+Ninja. On GNUstep, buildtool (from libs-xcode, already installed) replaces xcodebuild. Install from pkg:

pkg install -y ruby gperf

Why these two: On macOS, both ship with Xcode Command Line Tools. Ruby runs WebKit's build-time code generators (DOM bindings from .idl, CSS property tables, settings from .yaml). gperf generates perfect hash tables for CSS properties, CSS values, CSS pseudo-selectors, and HTTP header names.

Not needed: bison flex — macOS WebKit does not use them (JSC's parser is hand-written). ninja — macOS does not ship Ninja; WebKit uses Xcode, we use buildtool. cmake — the .xcodeproj files are the build system, not CMake (CMake is only for the GTK/WPE Linux ports).

Deferred: woff2 — on macOS, CoreText handles WOFF2 web font decoding natively (HAVE_WOFF_SUPPORT=1), so no external library is needed. libs-opal does not yet decode WOFF2 natively. If web font rendering issues arise, either: (a) install pkg install woff2 and set HAVE_WOFF_SUPPORT=0 to use WebKit's bundled WOFFFileFormat.cpp with libwoff2, or (b) add WOFF2 decoding support to libs-opal's OpalText (the macOS-model approach). System fonts render fine without WOFF2.

Required libraries — already installed on NextBSD

These are already present on this NextBSD system (verified via pkg-config). On a fresh FreeBSD system they would need installing:

# Already installed — listed for reference only
# libxml2 2.15  libxslt 1.1.45  sqlite3 3.53  icu 76.1
# freetype2 26.6  fontconfig 2.17  harfbuzz 14.2
# libpng 1.6.58  libjpeg 3.1  libwebp 1.6  lcms2 2.19

Already on NextBSD (GNUstep stack — not from pkg)

Package / LibraryProvides
gnustep-baseFoundation (NSString, NSURLSession, NSRunLoop, NSThread, NSNotificationCenter, …)
gnustep-guiAppKit (NSApplication, NSWindow, NSView, NSEvent, NSFont, NSImage, …)
gnustep-backX11 backend for gnustep-gui (event loop, window creation, OpenGL surface)
libs-opalCoreGraphics + CoreText + ImageIO (CGContext, CGPath, CTFont, CTLine, CGImageSource)
libs-quartzcoreQuartzCore / CoreAnimation (CALayer, CABasicAnimation, CATransform3D)
libs-corebaseCoreFoundation (CFString, CFArray, CFRunLoop, CFURL)
libs-avAVFoundation (AVPlayer, AVAudioPlayer, CMTime) via FFmpeg
libobjc2Objective-C 2.0 runtime (GNUstep-maintained, LLVM-compatible)
libdispatchGrand Central Dispatch (libpthread backend)
libBlocksRuntimeBlocks closure support (from LLVM compiler-rt)

Explicitly NOT needed

If these appear in any build script, something has gone wrong:

glib2   gtk3   gtk4   gstreamer1   gstreamer1-plugins-base   libsoup3   skia   mesa-libs   wayland   libepoxy   enchant2   at-spi2-core   libmanette   libsecret   libbacktrace   sysprof   bubblewrap   xdg-dbus-proxy

Optional — for later phases

# Phase 2: TLS certificate validation and crypto
pkg install -y openssl gnutls libgcrypt

# Performance
pkg install -y ccache

# Phase 3+: video support via libs-av/FFmpeg
pkg install -y ffmpeg

# Phase 3+: OpenGL compositing (may already be present)
pkg install -y mesa-libGL mesa-libEGL

Note: Mesa GL/EGL are needed only by libs-quartzcore's OpenGL compositor — they are a dependency of libs-quartzcore, not of WebKit directly. WebKit calls CALayer/CARenderer APIs; the OpenGL calls are behind the libs-quartzcore boundary.

Implementation phases

Phase 0: Platform registration Setup

Goal: CMake configures without errors. No source files compiled yet.

Success criterion: cmake -B Build -G Ninja -DPORT=GNUstep [flags] completes without errors and generates a valid Ninja build graph.

Phase 1: Build WTF + JavaScriptCore Core

Goal: jsc (JavaScript shell) binary runs and executes JavaScript.

Success criterion: Build/bin/jsc -e "print(1+1)" outputs 2.

Phase 2: Build WebCore Core

Goal: libWebCore.so links successfully.

Success criterion: ninja -C Build WebCore completes. Build/lib/libWebCore.so exists and is a valid shared library.

Phase 3: Build WebKit (UIProcess/WebProcess/NetworkProcess) Core

Goal: All three process executables build. MiniBrowser.app builds.

Success criterion: ninja -C Build MiniBrowser completes. MiniBrowser.app/Contents/MacOS/MiniBrowser is a valid executable. Build/WebKitWebProcess and Build/WebKitNetworkProcess executables exist.

Phase 4: MiniBrowser.app — first pixels Milestone

Goal: A red window. Any pixel on screen.

Success criterion: A window appears on screen showing a solid red (or any non-black, non-white) background. This is the inflection point — once there are pixels, everything else is incremental improvement.

Phase 5: Interactivity Iterate

Goal: User can navigate pages by typing URLs and clicking links.

Success criterion: Type a URL in the address bar, press Enter, page loads and is visible. Can click links. Can select text and copy it.

Phase 6: Real web content Iterate

Goal: example.com, wikipedia.org, and typical websites render correctly.

Success criterion: example.com and wikipedia.org render legibly and interactively.

Phase 7+: Features Future

Enable features one at a time, each as a self-contained sub-project:

FeatureCMake flagDependenciesNotes
Web InspectorENABLE_REMOTE_INSPECTOR=ONNone extraFull DevTools UI via WebKit's own inspector frontend
VideoENABLE_VIDEO=ONlibs-av / FFmpeg, libs-coremediaH.264, VP8/VP9 via FFmpeg decode pipeline
Web AudioENABLE_WEB_AUDIO=ONlibs-accelerate (vDSP), audio outputAudioContext, GainNode, ConvolverNode
WebGLENABLE_WEBGL=ONMesa OpenGL, libs-quartzcoreOpenGL ES 2.0 / 3.0 via EGL
WebRTCENABLE_WEB_RTC=ONlibwebrtc, audio/videoComplex — requires full media stack first
HTTPS certificatesN/A (always on)libs-security (OpenSSL)Implement SecTrust for certificate error UI
GPU ProcessENABLE_GPU_PROCESS=ONlibs-iosurface, CAContextRequires Phase 3+ libs; enables GPU compositing
WebAssemblyENABLE_WEBASSEMBLY=ONNone extra (JIT-based)JSC's WASM interpreter and B3 JIT — largely works on any platform
AccessibilityENABLE_ACCESSIBILITY=ONATK/AT-SPI via gnustep-guiScreen reader support

Open questions

Q1: OptionsGNUstep.cmake as new file vs. guards in OptionsMac.cmake?

Should we fork OptionsMac.cmake into a new OptionsGNUstep.cmake that includes it and overrides as needed, or add if(GNUSTEP) / #if PLATFORM(GNUSTEP) guards throughout the existing Mac cmake files?

Analysis: A separate OptionsGNUstep.cmake is cleaner — it's a first-class port alongside Mac, GTK, Win. Guards in Mac files make the Mac port harder to read and harder to upstream. The separate file approach also makes it easy to track what differs from the Mac baseline. Recommendation: separate file, include OptionsMac.cmake at the top, override below it.
Q2: Multi-process or single-process first?

macOS WebKit is always multi-process. Should we start with single-process mode (everything in MiniBrowser.app, no separate WebProcess/NetworkProcess) for Phase 3/4 simplicity, then graduate to multi-process? Or go multi-process from the start since v3/v4 proved Unix socket IPC works?

Analysis: Single-process first is simpler for debugging (one process, one address space, one log stream). The risk is that the single-process → multi-process transition requires significant architectural changes later. v3/v4's IPC is proven. Recommendation: go multi-process from Phase 3, but start with --single-web-process launch flag for debugging. WebKit already has this flag.
Q3: CAContext replacement for multi-process GPU compositing?

On macOS, CAContext hosts a layer tree across process boundaries via Mach ports and WindowServer. Without Mach ports, what's the alternative for GPU-accelerated cross-process compositing?

Options: Recommendation: (a) for Phase 4 to get first pixels; (c) for Phase 7+ as the proper long-term implementation.
Q4: Upstream or fork?

Do we maintain patches against upstream WebKit (rebase on each release) or maintain a long-lived fork?

Analysis: A long-lived fork diverges rapidly — WebKit moves fast (~100 commits/day). Maintaining patches as a patchset and rebasing periodically is more sustainable. The GNUstep-specific code lives in gnustep/ subdirectories (per the v3 convention) — this minimizes conflicts with upstream changes that don't touch those directories. Recommendation: upstream patchset approach. Maintain a gnustep/ subdirectory convention. Attempt to upstream generic improvements (platform registration cmake, CFNotificationCenter stubs) to the WebKit project.
Q5: How to handle Apple SPI (hundreds of private API calls)?

WebKit uses hundreds of private/SPI calls from Apple frameworks. Options: Recommendation: combination approach. Stub headers for compile-time (quick, non-invasive). #if PLATFORM(GNUSTEP) guards for SPI that has semantically different behavior (Mach-specific, WindowServer-specific). Implement in GNUstep libs for SPI that's actually important for rendering quality (CTFontShapeGlyphs, CTFontCopyDefaultCascadeListForLanguages, CGStyleRef filters).
Q6: CFNetwork vs NSURLSession — which layer to target?

macOS WebKit has two network stack layers: CFNetwork (lower-level, used by WebCore's ResourceHandle) and NSURLSession (higher-level, used by WebKit's NetworkSession). GNUstep's NSURLSession is backed by libcurl and is the more complete implementation.

Recommendation: NSURLSession exclusively. It's the modern Apple API (Apple deprecated CFNetwork-direct usage), GNUstep implements it fully with libcurl as the backend, and WebKit already has complete NSURLSession code paths in NetworkProcess/cocoa/NetworkSessionCocoa.mm. Using NSURLSession also means HTTP/2, HTTP/3 (via curl's QUIC support), and cookie management work with minimal platform-specific code.