libs-corebase — CoreFoundation gaps for WebKit Sub-plan

What must be added to gnustep/libs-corebase to reach 100% CoreFoundation coverage for the minimal WebKit v5 build. Parent plan: WebKit v5 Cocoa-native porting plan.

TL;DR

Three files away from full CF coverage

Post-merge audit — 30 DTW-Thalion PRs merged Done

30 PRs from DTW-Thalion were merged into the pkgdemon/libs-corebase fork. These were exclusively correctness and stability fixes across existing APIs — no new CF subsystems were added. The 3 critical gaps (CFNotificationCenter, CFPreferences, CFStringTokenizer) remain at 0% — they must be implemented from scratch.

What the 30 PRs fixed

Area Fix WebKit Impact
CFString Buffer overflow in CFStringGetCString/AppendCString Direct — DOM text encoding, URL construction
CFRunLoop Observer marked valid on creation; timer fire date correction; crash fix removing source from common modes Direct — WebKit's event loop
CFAttributedString Multi-run attribute storage, substring, attribute removal/replacement Direct — text rendering pipeline
CFDate/CFCalendar Month calculation fix, week-of-year, unit difference, ordinality, min/max range swap Direct — JavaScript Date object
CFTimeZone TZif header validation (prevent OOB reads), GMT offset naming Stability
CFTree Retain/release context info, prepend child, out-of-range index safety Stability
CFURL Absolute URL path scan, empty URL safety, percent-escape buffer sizing Direct — URL handling
CFUUID Complete rewrite of string parsing Stability
CFPropertyList Deep copy fix (was copying empty destination, not source) Direct — plist/preferences
CFSocket Register type, correct address accessors Networking
CFXMLNode Type code, value comparison, entity escaping XML parsing
CFStream Memory stream status and error reporting I/O
GSUnicode UTF-8 BOM, UTF-32 byte order, non-lossy ASCII, printf digit parsing String encoding
CFDictionary/CFSet Fast enumeration fix (was creating new enumerator each call, never terminating) Direct — all dictionary iteration

Remaining gaps (unchanged)

The 3 critical APIs needed for the minimal WebKit build are still completely absent — no headers, no source, no stubs:
  1. CFNotificationCenter (~70 calls in WebKit)
  2. CFPreferences (~46 calls in WebKit)
  3. CFStringTokenizer (~27 calls in WebKit)
These must be implemented from scratch as documented in sections 3a–3c below.

Updated coverage

libs-corebase coverage is now ~80% of the CF type surface (up from ~75% pre-merge due to correctness fixes making existing implementations actually usable). For the minimal WebKit build, the 3 missing APIs are the only remaining blockers.

Current coverage

The table below lists CF subsystems that libs-corebase already implements and that WebKit exercises. All are available and functional for the minimal build target. Symbol counts are approximate and based on the libs-corebase source tree as of mid-2026.

CF subsystem Key types / functions In libs-corebase? Notes
CFString CFStringRef, CFMutableStringRef, CFStringCreateWithCString, CFStringGetCString, CFStringCompare, CFStringFind, CFStringAppend, … Yes Most-used CF type in WebKit. Well-implemented.
CFArray CFArrayRef, CFMutableArrayRef, CFArrayCreate, CFArrayGetValueAtIndex, CFArrayAppendValue, CFArraySortValues, … Yes
CFDictionary CFDictionaryRef, CFMutableDictionaryRef, CFDictionaryCreate, CFDictionaryGetValue, CFDictionarySetValue, CFDictionaryApplyFunction, … Yes
CFSet CFSetRef, CFMutableSetRef, CFSetCreate, CFSetAddValue, CFSetContainsValue, … Yes
CFData CFDataRef, CFMutableDataRef, CFDataCreate, CFDataGetBytePtr, CFDataGetLength, CFDataAppendBytes, … Yes
CFNumber CFNumberRef, CFNumberCreate, CFNumberGetValue, CFNumberCompare, kCFNumberIntType, kCFNumberDoubleType, … Yes
CFRunLoop CFRunLoopRef, CFRunLoopGetMain, CFRunLoopGetCurrent, CFRunLoopRun, CFRunLoopStop, CFRunLoopWakeUp, CFRunLoopAddSource, CFRunLoopAddTimer, … Yes Critical for WebKit's event processing.
CFRunLoopSource CFRunLoopSourceRef, CFRunLoopSourceCreate, CFRunLoopSourceSignal, CFRunLoopSourceInvalidate, … Yes
CFRunLoopTimer CFRunLoopTimerRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerGetNextFireDate, … Yes
CFURL CFURLRef, CFURLCreateWithString, CFURLCreateWithFileSystemPath, CFURLGetString, CFURLCopyFileSystemPath, CFURLCopyPathExtension, … Yes
CFBundle CFBundleRef, CFBundleGetMainBundle, CFBundleCopyResourceURL, CFBundleGetIdentifier, CFBundleCopyLocalizedString, … Yes
CFPropertyList CFPropertyListRef, CFPropertyListCreateWithData, CFPropertyListCreateData, CFPropertyListIsValid, … Yes
CFDate CFDateRef, CFDateCreate, CFDateGetAbsoluteTime, CFDateCompare, CFAbsoluteTimeGetCurrent, … Yes
CFCalendar CFCalendarRef, CFCalendarCopyCurrent, CFCalendarAddComponents, CFCalendarGetComponentDifference, … Yes
CFTimeZone CFTimeZoneRef, CFTimeZoneCopyDefault, CFTimeZoneCreateWithName, CFTimeZoneGetSecondsFromGMT, … Yes
CFLocale CFLocaleRef, CFLocaleCopyCurrent, CFLocaleCreateWithLocaleIdentifier, CFLocaleGetIdentifier, CFLocaleGetValue, … Yes
CFError CFErrorRef, CFErrorCreate, CFErrorGetCode, CFErrorGetDomain, CFErrorCopyDescription, … Yes
CFSocket CFSocketRef, CFSocketCreate, CFSocketCreateWithNative, CFSocketSetAddress, CFSocketConnectToAddress, CFSocketCreateRunLoopSource, … Yes
CFStream CFReadStreamRef, CFWriteStreamRef, CFReadStreamCreateWithFile, CFWriteStreamCreateWithFile, CFStreamCreatePairWithSocket, … Yes
CFUUID CFUUIDRef, CFUUIDCreate, CFUUIDCreateString, CFUUIDCreateFromString, … Yes
CFBag CFBagRef, CFMutableBagRef, CFBagCreate, CFBagGetCount, CFBagAddValue, … Yes
CFBinaryHeap CFBinaryHeapRef, CFBinaryHeapCreate, CFBinaryHeapAddValue, CFBinaryHeapGetMinimum, … Yes
CFBitVector CFBitVectorRef, CFMutableBitVectorRef, CFBitVectorCreate, CFBitVectorGetBitAtIndex, … Yes
CFTree CFTreeRef, CFTreeCreate, CFTreeAppendChild, CFTreeGetParent, CFTreeApplyFunctionToChildren, … Yes
CFAttributedString CFAttributedStringRef, CFMutableAttributedStringRef, CFAttributedStringCreate, CFAttributedStringGetAttribute, … Yes
CFCharacterSet CFCharacterSetRef, CFMutableCharacterSetRef, CFCharacterSetGetPredefined, CFCharacterSetIsCharacterMember, … Yes
CFDateFormatter CFDateFormatterRef, CFDateFormatterCreate, CFDateFormatterCreateStringWithDate, CFDateFormatterCreateDateFromString, … Yes
CFNumberFormatter CFNumberFormatterRef, CFNumberFormatterCreate, CFNumberFormatterCreateStringWithNumber, CFNumberFormatterCreateNumberFromString, … Yes
CFByteOrder CFByteOrderGetCurrent, CFSwapInt16BigToHost, CFSwapInt32HostToBig, CFConvertFloat32HostToSwapped, … Yes Inline functions; no compilation unit needed.
CFRuntime CFTypeID, CFGetTypeID, CFRetain, CFRelease, CFEqual, CFHash, CFCopyDescription, CFAllocatorRef, … Yes Foundation of the entire CF type system.
CFXMLNode / CFXMLParser CFXMLNodeRef, CFXMLParserRef, CFXMLParserCreate, CFXMLParserParse, … Yes Deprecated on Apple but present in libs-corebase.
Result: 31 CF subsystems confirmed present. The three missing subsystems are detailed in the next section. No other CF APIs appear in WebKit's minimal build call graph.

Missing APIs — must add

These three API families are absent from libs-corebase and are called directly by WebKit source. They are not optional — the minimal build will fail to link without them. Each is straightforwardly implementable by wrapping existing OS-level APIs that are already available on GNUstep/NextBSD.

API WebKit call sites Implementation wraps Est. LOC New files
CFNotificationCenter ~70 NSNotificationCenter (gnustep-base) 200–300 2 (.h + .c)
CFPreferences ~46 NSUserDefaults (gnustep-base) 150–200 2 (.h + .c)
CFStringTokenizer ~27 ICU UBreakIterator (libicu) 250–350 2 (.h + .c)

3a. CFNotificationCenter

Overview

Header: CFNotificationCenter.h Source: CFNotificationCenter.c WebKit call sites: ~70 Estimated LOC: 200–300 Depends on: gnustep-base (NSNotificationCenter, NSDistributedNotificationCenter)

WebKit functions called

Implementation strategy

Wrap NSNotificationCenter from gnustep-base. The CF notification center is a thin veneer over the Objective-C notification system. The local center maps 1:1 to [NSNotificationCenter defaultCenter]. The distributed center maps to [NSDistributedNotificationCenter defaultCenter], which gnustep-base already implements.

The tricky part is adapting the C callback signature. CF observers use the signature:

typedef void (*CFNotificationCallback)(
    CFNotificationCenterRef center,
    void *observer,
    CFNotificationName name,
    const void *object,
    CFDictionaryRef userInfo
);

Each registered observer must be wrapped in an Objective-C block or NSObject shim that bridges NSNotification to the CF callback. The shim holds the CF callback pointer, the observer pointer, and the suspension behavior. Use a global CFMutableDictionaryRef (keyed by observer+name+center triplet) to track shims for removal.

Key constants to define

/* Suspension behavior when app is in background */
typedef CF_ENUM(CFIndex, CFNotificationSuspensionBehavior) {
    CFNotificationSuspensionBehaviorDrop                = 1,
    CFNotificationSuspensionBehaviorCoalesce            = 2,
    CFNotificationSuspensionBehaviorHold                = 3,
    CFNotificationSuspensionBehaviorDeliverImmediately  = 4
};

/* Options for CFNotificationCenterPostNotification */
typedef CF_OPTIONS(CFOptionFlags, CFNotificationPostOptions) {
    kCFNotificationPostToAllSessions = (1 << 0)
};

Complete function signatures to implement

/* CFNotificationCenter.h */

CF_EXPORT CFTypeID CFNotificationCenterGetTypeID(void);

CF_EXPORT CFNotificationCenterRef CFNotificationCenterGetLocalCenter(void);
CF_EXPORT CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void);
CF_EXPORT CFNotificationCenterRef CFNotificationCenterGetDarwinNotifyCenter(void);

CF_EXPORT void CFNotificationCenterAddObserver(
    CFNotificationCenterRef center,
    const void *observer,
    CFNotificationCallback callBack,
    CFStringRef name,
    const void *object,
    CFNotificationSuspensionBehavior suspensionBehavior
);

CF_EXPORT void CFNotificationCenterRemoveObserver(
    CFNotificationCenterRef center,
    const void *observer,
    CFNotificationName name,
    const void *object
);

CF_EXPORT void CFNotificationCenterRemoveEveryObserver(
    CFNotificationCenterRef center,
    const void *observer
);

CF_EXPORT void CFNotificationCenterPostNotification(
    CFNotificationCenterRef center,
    CFNotificationName name,
    const void *object,
    CFDictionaryRef userInfo,
    Boolean deliverImmediately
);

CF_EXPORT void CFNotificationCenterPostNotificationWithOptions(
    CFNotificationCenterRef center,
    CFNotificationName name,
    const void *object,
    CFDictionaryRef userInfo,
    CFOptionFlags options
);

Key implementation sketch (CFNotificationCenter.c)

#import <Foundation/NSNotificationCenter.h>
#import <Foundation/NSDistributedNotificationCenter.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#include "CFNotificationCenter.h"
#include "CFInternal.h"

/* Observer shim: bridges NSNotification -> CF callback */
@interface _CFNotificationObserver : NSObject {
@public
    CFNotificationCallback  _callback;
    void                   *_observer;
    CFNotificationCenterRef _center;
}
- (void)handleNotification:(NSNotification *)note;
@end

static CFNotificationCenterRef __localCenter    = NULL;
static CFNotificationCenterRef __distCenter     = NULL;
static pthread_mutex_t         __centerLock     = PTHREAD_MUTEX_INITIALIZER;

/* Global map: (observer ptr, name string) -> _CFNotificationObserver */
static CFMutableDictionaryRef  __observerMap    = NULL;

CFNotificationCenterRef CFNotificationCenterGetLocalCenter(void)
{
    pthread_mutex_lock(&__centerLock);
    if (!__localCenter) {
        /* Allocate a CF runtime instance wrapping [NSNotificationCenter defaultCenter] */
        __localCenter = _CFNotificationCenterCreate(kCFNotificationCenterLocal);
    }
    pthread_mutex_unlock(&__centerLock);
    return __localCenter;
}

CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void)
{
    pthread_mutex_lock(&__centerLock);
    if (!__distCenter) {
        __distCenter = _CFNotificationCenterCreate(kCFNotificationCenterDistributed);
    }
    pthread_mutex_unlock(&__centerLock);
    return __distCenter;
}

void CFNotificationCenterAddObserver(
    CFNotificationCenterRef center,
    const void *observer,
    CFNotificationCallback callBack,
    CFStringRef name,
    const void *object,
    CFNotificationSuspensionBehavior suspensionBehavior)
{
    _CFNotificationObserver *shim = [[_CFNotificationObserver alloc] init];
    shim->_callback = callBack;
    shim->_observer = (void *)observer;
    shim->_center   = center;

    NSString *nsName = name ? (NSString *)name : nil;
    NSNotificationCenter *nc = _CFNotificationCenterGetNSCenter(center);
    [nc addObserver:shim
           selector:@selector(handleNotification:)
               name:nsName
             object:nil];

    /* Store shim for later removal */
    _CFNotificationMapStore(center, observer, name, shim);
}

void CFNotificationCenterPostNotification(
    CFNotificationCenterRef center,
    CFNotificationName name,
    const void *object,
    CFDictionaryRef userInfo,
    Boolean deliverImmediately)
{
    NSNotificationCenter *nc = _CFNotificationCenterGetNSCenter(center);
    NSString *nsName = (NSString *)name;
    NSDictionary *nsInfo = (NSDictionary *)userInfo;
    [nc postNotificationName:nsName object:nil userInfo:nsInfo];
}
Note on Darwin notify center: CFNotificationCenterGetDarwinNotifyCenter() uses the private notify_post() / notify_register_dispatch() BSD API (part of Apple's libnotify). On GNUstep/NextBSD, stub this to return NULL or a no-op center — WebKit's Darwin-notify usage is limited to system-level power/sleep notifications that are irrelevant for the minimal build.

3b. CFPreferences

Overview

Header: CFPreferences.h Source: CFPreferences.c WebKit call sites: ~46 Estimated LOC: 150–200 Depends on: gnustep-base (NSUserDefaults)

WebKit functions called

Implementation strategy

Wrap NSUserDefaults. The mapping is direct and complete:

The appID parameter in most calls is a bundle identifier string (kCFPreferencesCurrentApplication is a sentinel). When the sentinel is passed, use [NSUserDefaults standardUserDefaults] directly. When a real bundle ID is passed, use [[NSUserDefaults alloc] initWithSuiteName:bundleID].

Key constants to define

/* Sentinel strings — defined as extern CFStringRef, initialized to static values */
CF_EXPORT const CFStringRef kCFPreferencesAnyApplication;
CF_EXPORT const CFStringRef kCFPreferencesCurrentApplication;
CF_EXPORT const CFStringRef kCFPreferencesAnyHost;
CF_EXPORT const CFStringRef kCFPreferencesCurrentHost;
CF_EXPORT const CFStringRef kCFPreferencesAnyUser;
CF_EXPORT const CFStringRef kCFPreferencesCurrentUser;

Complete function signatures to implement

/* CFPreferences.h */

/* Per-app read/write (most common in WebKit) */
CF_EXPORT CFTypeRef CFPreferencesCopyAppValue(
    CFStringRef key,
    CFStringRef applicationID
);

CF_EXPORT Boolean CFPreferencesGetAppBooleanValue(
    CFStringRef key,
    CFStringRef applicationID,
    Boolean *keyExistsAndHasValidFormat   /* may be NULL */
);

CF_EXPORT CFIndex CFPreferencesGetAppIntegerValue(
    CFStringRef key,
    CFStringRef applicationID,
    Boolean *keyExistsAndHasValidFormat   /* may be NULL */
);

CF_EXPORT void CFPreferencesSetAppValue(
    CFStringRef key,
    CFTypeRef value,                      /* pass NULL to remove */
    CFStringRef applicationID
);

CF_EXPORT Boolean CFPreferencesAppSynchronize(
    CFStringRef applicationID
);

CF_EXPORT Boolean CFPreferencesAppValueIsForced(
    CFStringRef key,
    CFStringRef applicationID
);

/* Multi-domain read/write */
CF_EXPORT CFTypeRef CFPreferencesCopyValue(
    CFStringRef key,
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT void CFPreferencesSetValue(
    CFStringRef key,
    CFTypeRef value,
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT Boolean CFPreferencesSynchronize(
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT CFArrayRef CFPreferencesCopyApplicationList(
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT CFDictionaryRef CFPreferencesCopyMultiple(
    CFArrayRef keysToFetch,               /* pass NULL for all keys */
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT void CFPreferencesSetMultiple(
    CFDictionaryRef keysToSet,
    CFArrayRef keysToRemove,
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

CF_EXPORT CFArrayRef CFPreferencesCopyKeyList(
    CFStringRef applicationID,
    CFStringRef userName,
    CFStringRef hostName
);

Key implementation sketch (CFPreferences.c)

#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSString.h>
#import <Foundation/NSNumber.h>
#include "CFPreferences.h"
#include "CFString.h"

const CFStringRef kCFPreferencesCurrentApplication =
    (CFStringRef)@"kCFPreferencesCurrentApplication";
const CFStringRef kCFPreferencesAnyApplication =
    (CFStringRef)@"kCFPreferencesAnyApplication";
const CFStringRef kCFPreferencesCurrentUser =
    (CFStringRef)@"kCFPreferencesCurrentUser";
const CFStringRef kCFPreferencesAnyUser =
    (CFStringRef)@"kCFPreferencesAnyUser";
const CFStringRef kCFPreferencesCurrentHost =
    (CFStringRef)@"kCFPreferencesCurrentHost";
const CFStringRef kCFPreferencesAnyHost =
    (CFStringRef)@"kCFPreferencesAnyHost";

static NSUserDefaults *_defaultsForAppID(CFStringRef applicationID)
{
    if (applicationID == kCFPreferencesCurrentApplication
        || [(NSString *)applicationID isEqualToString:
               (NSString *)kCFPreferencesCurrentApplication])
        return [NSUserDefaults standardUserDefaults];

    /* Named suite — note: NSUserDefaults initWithSuiteName: is available
       in gnustep-base and behaves like a per-bundle defaults domain. */
    return [[[NSUserDefaults alloc]
               initWithSuiteName:(NSString *)applicationID] autorelease];
}

CFTypeRef CFPreferencesCopyAppValue(CFStringRef key, CFStringRef applicationID)
{
    NSUserDefaults *ud = _defaultsForAppID(applicationID);
    id value = [ud objectForKey:(NSString *)key];
    if (!value) return NULL;
    CFRetain((CFTypeRef)value);
    return (CFTypeRef)value;
}

Boolean CFPreferencesGetAppBooleanValue(
    CFStringRef key, CFStringRef applicationID, Boolean *keyExists)
{
    NSUserDefaults *ud = _defaultsForAppID(applicationID);
    id value = [ud objectForKey:(NSString *)key];
    if (!value) {
        if (keyExists) *keyExists = false;
        return false;
    }
    if (keyExists) *keyExists = true;
    return [ud boolForKey:(NSString *)key];
}

CFIndex CFPreferencesGetAppIntegerValue(
    CFStringRef key, CFStringRef applicationID, Boolean *keyExists)
{
    NSUserDefaults *ud = _defaultsForAppID(applicationID);
    id value = [ud objectForKey:(NSString *)key];
    if (!value) {
        if (keyExists) *keyExists = false;
        return 0;
    }
    if (keyExists) *keyExists = true;
    return (CFIndex)[ud integerForKey:(NSString *)key];
}

void CFPreferencesSetAppValue(
    CFStringRef key, CFTypeRef value, CFStringRef applicationID)
{
    NSUserDefaults *ud = _defaultsForAppID(applicationID);
    if (value)
        [ud setObject:(id)value forKey:(NSString *)key];
    else
        [ud removeObjectForKey:(NSString *)key];
}

Boolean CFPreferencesAppSynchronize(CFStringRef applicationID)
{
    NSUserDefaults *ud = _defaultsForAppID(applicationID);
    [ud synchronize];
    return true;
}

3c. CFStringTokenizer

Overview

Header: CFStringTokenizer.h Source: CFStringTokenizer.c WebKit call sites: ~27 Estimated LOC: 250–350 Depends on: ICU (libicu — already a WebKit dependency)

WebKit functions called

ICU mapping

ICU's UBreakIterator API, available in <unicode/ubrk.h>, implements exactly the same concept. The unit constants map as follows:

CF unit constantICU UBreakIteratorTypeNotes
kCFStringTokenizerUnitWord UBRK_WORD Standard word boundaries
kCFStringTokenizerUnitSentence UBRK_SENTENCE Sentence boundaries
kCFStringTokenizerUnitParagraph UBRK_LINE Closest ICU analog to paragraph; CF paragraphs = line breaks
kCFStringTokenizerUnitLineBreak UBRK_LINE ICU line-break opportunities
kCFStringTokenizerUnitWordBoundary UBRK_WORD Same as word but returns all tokens including whitespace

ICU uses UTF-16 internally, matching CoreFoundation's native encoding. The string passed to the tokenizer must be extracted as a UTF-16 buffer (CFStringGetCharactersPtr or CFStringGetCharacters) and held for the lifetime of the iterator.

Key constants and types to define

typedef CF_OPTIONS(CFOptionFlags, CFStringTokenizerTokenType) {
    kCFStringTokenizerTokenNone                 = 0,
    kCFStringTokenizerTokenNormal               = 1UL << 0,
    kCFStringTokenizerTokenHasSubTokensMask     = 1UL << 1,
    kCFStringTokenizerTokenHasDerivedSubTokensMask = 1UL << 2,
    kCFStringTokenizerTokenHasHasNumbersMask    = 1UL << 3,
    kCFStringTokenizerTokenHasNonLettersMask    = 1UL << 4,
    kCFStringTokenizerTokenIsCJWordMask         = 1UL << 5
};

/* Unit options for CFStringTokenizerCreate */
typedef CF_OPTIONS(CFOptionFlags, CFStringTokenizerUnit) {
    kCFStringTokenizerUnitWord          = 0,
    kCFStringTokenizerUnitSentence      = 1,
    kCFStringTokenizerUnitParagraph     = 2,
    kCFStringTokenizerUnitLineBreak     = 3,
    kCFStringTokenizerUnitWordBoundary  = (1UL << 4) | kCFStringTokenizerUnitWord
};

/* Attribute keys for CFStringTokenizerCopyCurrentTokenAttribute */
CF_EXPORT const CFStringRef kCFStringTokenizerAttributeLatinTranscription;
CF_EXPORT const CFStringRef kCFStringTokenizerAttributeLanguage;

Complete function signatures to implement

/* CFStringTokenizer.h */

CF_EXPORT CFTypeID CFStringTokenizerGetTypeID(void);

CF_EXPORT CFStringTokenizerRef CFStringTokenizerCreate(
    CFAllocatorRef alloc,
    CFStringRef string,
    CFRange range,
    CFOptionFlags unit,
    CFLocaleRef locale
);

CF_EXPORT void CFStringTokenizerSetString(
    CFStringTokenizerRef tokenizer,
    CFStringRef string,
    CFRange range
);

CF_EXPORT CFStringTokenizerTokenType CFStringTokenizerAdvanceToNextToken(
    CFStringTokenizerRef tokenizer
);

CF_EXPORT CFStringTokenizerTokenType CFStringTokenizerGoToTokenAtIndex(
    CFStringTokenizerRef tokenizer,
    CFIndex index
);

CF_EXPORT CFRange CFStringTokenizerGetCurrentTokenRange(
    CFStringTokenizerRef tokenizer
);

CF_EXPORT CFTypeRef CFStringTokenizerCopyCurrentTokenAttribute(
    CFStringTokenizerRef tokenizer,
    CFOptionFlags attribute
);

CF_EXPORT CFIndex CFStringTokenizerGetCurrentSubTokens(
    CFStringTokenizerRef tokenizer,
    CFRange *ranges,
    CFIndex maxRangeLength,
    CFMutableArrayRef derivedSubTokens   /* may be NULL */
);

Key implementation sketch (CFStringTokenizer.c)

#include <unicode/ubrk.h>
#include <unicode/uloc.h>
#include "CFStringTokenizer.h"
#include "CFRuntime.h"
#include "CFString.h"
#include "CFLocale.h"

typedef struct __CFStringTokenizer {
    CFRuntimeBase      _base;
    UBreakIterator    *_brk;          /* ICU break iterator          */
    UniChar           *_buf;          /* UTF-16 copy of the string   */
    CFIndex            _bufLen;
    CFRange            _range;        /* subrange within the string  */
    CFOptionFlags      _unit;
    int32_t            _current;      /* current break position      */
    int32_t            _tokenEnd;     /* end of current token        */
} __CFStringTokenizer;

static UBreakIteratorType _unitToICU(CFOptionFlags unit)
{
    switch (unit & 0xF) {
        case kCFStringTokenizerUnitSentence:    return UBRK_SENTENCE;
        case kCFStringTokenizerUnitParagraph:   return UBRK_LINE;
        case kCFStringTokenizerUnitLineBreak:   return UBRK_LINE;
        default:                                return UBRK_WORD;
    }
}

CFStringTokenizerRef CFStringTokenizerCreate(
    CFAllocatorRef alloc, CFStringRef string,
    CFRange range, CFOptionFlags unit, CFLocaleRef locale)
{
    __CFStringTokenizer *t = (__CFStringTokenizer *)_CFRuntimeCreateInstance(
        alloc, CFStringTokenizerGetTypeID(),
        sizeof(__CFStringTokenizer) - sizeof(CFRuntimeBase), NULL);
    if (!t) return NULL;

    /* Extract UTF-16 buffer */
    CFIndex len = range.length;
    t->_buf    = (UniChar *)CFAllocatorAllocate(alloc, len * sizeof(UniChar), 0);
    t->_bufLen = len;
    CFStringGetCharacters(string, range, t->_buf);

    /* Locale string for ICU */
    const char *locStr = "en";
    if (locale) {
        CFStringRef locID = CFLocaleGetIdentifier(locale);
        char tmp[64];
        if (CFStringGetCString(locID, tmp, sizeof(tmp), kCFStringEncodingUTF8))
            locStr = tmp;  /* safe: ICU copies it */
    }

    UErrorCode err = U_ZERO_ERROR;
    t->_brk = ubrk_openUChars(_unitToICU(unit),
                               locStr,
                               (UChar *)t->_buf, (int32_t)len,
                               &err);
    t->_unit    = unit;
    t->_range   = range;
    t->_current = 0;
    t->_tokenEnd = 0;
    return (CFStringTokenizerRef)t;
}

CFStringTokenizerTokenType CFStringTokenizerAdvanceToNextToken(
    CFStringTokenizerRef tokenizer)
{
    __CFStringTokenizer *t = (__CFStringTokenizer *)tokenizer;
    if (!t->_brk) return kCFStringTokenizerTokenNone;

    int32_t start = t->_tokenEnd;
    int32_t end   = ubrk_nextBoundary(t->_brk);
    if (end == UBRK_DONE) {
        t->_current  = (int32_t)t->_bufLen;
        t->_tokenEnd = (int32_t)t->_bufLen;
        return kCFStringTokenizerTokenNone;
    }
    t->_current  = start;
    t->_tokenEnd = end;
    return kCFStringTokenizerTokenNormal;
}

CFRange CFStringTokenizerGetCurrentTokenRange(CFStringTokenizerRef tokenizer)
{
    __CFStringTokenizer *t = (__CFStringTokenizer *)tokenizer;
    if (t->_current == t->_tokenEnd)
        return CFRangeMake(kCFNotFound, 0);
    return CFRangeMake(
        t->_range.location + t->_current,
        t->_tokenEnd - t->_current);
}

CFStringTokenizerTokenType CFStringTokenizerGoToTokenAtIndex(
    CFStringTokenizerRef tokenizer, CFIndex index)
{
    __CFStringTokenizer *t = (__CFStringTokenizer *)tokenizer;
    if (!t->_brk) return kCFStringTokenizerTokenNone;

    /* Seek ICU iterator */
    int32_t pos = (int32_t)(index - t->_range.location);
    if (pos < 0 || pos >= (int32_t)t->_bufLen)
        return kCFStringTokenizerTokenNone;

    ubrk_isBoundary(t->_brk, pos);   /* positions the iterator */
    int32_t start = ubrk_previous(t->_brk);
    if (start == UBRK_DONE) start = 0;
    int32_t end   = ubrk_nextBoundary(t->_brk);
    if (end == UBRK_DONE) return kCFStringTokenizerTokenNone;

    t->_current  = start;
    t->_tokenEnd = end;
    return kCFStringTokenizerTokenNormal;
}
Note on CFStringTokenizerCopyCurrentTokenAttribute: The kCFStringTokenizerAttributeLanguage attribute (identifies language of the current token using ICU's language detection) is optional for the minimal WebKit build. Implement it as a stub returning NULL initially; WebKit's text selection and editing code uses it for CJK input but it is not exercised in the HTML/CSS/JS minimal build.

Lower-priority APIs

These CF APIs are absent from libs-corebase and are also absent from Apple's CoreFoundation public headers, or are not exercised in the minimal WebKit build. They are documented here for completeness but are not blocking the minimal build target.

CF API Needed for minimal build? Reason / notes
CFMessagePort No WebKit's IPC layer uses Unix sockets (via the platform abstraction in Source/WebKit/Platform/IPC/). CFMessagePort is only used in legacy code paths that are guarded by #if PLATFORM(MAC) && !TARGET_OS_MACCATALYST checks that we will not enable. CFMessagePort is also Mach-message-based on Darwin — implementing it without Mach would require a completely different backend.
CFMachPort No Mach-specific. Wraps a Mach port receive right. On FreeBSD/GNUstep there is no Mach port primitive. WebKit uses CFMachPort only in Source/WebKit/Platform/IPC/mac/ which is excluded from the GNUstep port. Not implementable without a Mach kernel (or a userspace Mach emulation layer, which is a separate effort).
CFFileDescriptor No Not called in the minimal build. Where WebKit monitors file descriptors for I/O readiness, it goes through CFSocket (already present) or dispatch sources. Can be deferred; if needed later, implement as a CFRunLoopSource wrapper around kevent(2) / kqueue(2).
CFPlugIn / CFPlugInInstance No WebKit does not use the CF plug-in system. This is a legacy COM-like component model from pre-Cocoa days. Not called anywhere in WebKit trunk. Skip entirely.
CFUserNotification No Mach-specific UI mechanism: displays a dialog by contacting the WindowServer via a Mach message. Irrelevant outside macOS. WebKit does not call this API.
CFURLAccess No Deprecated since macOS 10.9. Not used in WebKit trunk. Successor APIs (CFURLCreateDataAndPropertiesFromResource, etc.) are the old API surface. WebKit uses NSURL / NSURLSession for all network access.
CFURLEnumerator No Directory enumeration API. Not called in WebKit. Implement later if needed by app-level code.
CFWindowsNamedPipe No Windows-only. Not applicable.
CFNetwork (CFHTTPMessage, CFHTTPStream, …) No Part of CFNetwork.framework on macOS, not CoreFoundation.framework. WebKit's network layer has already been redirected to NSURLSession. CFNetwork symbols are in a separate framework; libs-corebase does not and should not implement them.

Build integration

libs-corebase uses GNUstep-make as its primary build system, with a GNUmakefile in the source root. There is also a nascent CMake integration in some forks. Both need to be updated.

GNUmakefile changes

The main library target in GNUmakefile collects sources via the libCoreFoundation_OBJC_FILES and libCoreFoundation_C_FILES variables. Add the three new .c files to the latter:

# In GNUmakefile, find the block that lists C source files:
libCoreFoundation_C_FILES = \
    CFArray.c \
    CFAttributedString.c \
    CFBag.c \
    CFBinaryHeap.c \
    CFBitVector.c \
    CFBundle.c \
    CFCalendar.c \
    CFCharacterSet.c \
    CFData.c \
    CFDate.c \
    CFDateFormatter.c \
    CFDictionary.c \
    CFError.c \
    CFLocale.c \
    CFNumber.c \
    CFNumberFormatter.c \
    CFPropertyList.c \
    CFRunLoop.c \
    CFRuntime.c \
    CFSet.c \
    CFSocket.c \
    CFStream.c \
    CFString.c \
    CFStringEncodingConverter.c \
    CFTimeZone.c \
    CFTree.c \
    CFURL.c \
    CFXMLNode.c \
    CFXMLParser.c \
    CFUUID.c \
    CFNotificationCenter.c \     # <-- ADD
    CFPreferences.c \             # <-- ADD
    CFStringTokenizer.c           # <-- ADD

The headers go into the existing Headers/CoreFoundation/ directory and are listed in the libCoreFoundation_HEADER_FILES variable (or its equivalent). Confirm with:

grep -n HEADER_FILES GNUmakefile

Linker flags for ICU (CFStringTokenizer only)

CFStringTokenizer.c links against ICU's break-iterator library. Add the ICU flags to the library's ADDITIONAL_LDFLAGS:

# In GNUmakefile:
libCoreFoundation_LIBRARIES_DEPEND_UPON += \
    $(shell icu-config --ldflags-libsonly)
# Or, more portably:
libCoreFoundation_LIBRARIES_DEPEND_UPON += -licui18n -licuuc -licudata

If the build already links ICU for CFString (likely), no extra linker flag is needed — confirm by checking the existing flags for -licuuc.

CMake changes (for the pkgdemon fork)

If the fork uses or adds a CMakeLists.txt, add the new files to the target source list:

target_sources(CoreFoundation PRIVATE
    # ... existing sources ...
    CFNotificationCenter.c
    CFPreferences.c
    CFStringTokenizer.c
)

# ICU dependency (already present for CFString, but make it explicit)
find_package(ICU REQUIRED COMPONENTS uc i18n data)
target_link_libraries(CoreFoundation PRIVATE ICU::uc ICU::i18n)

# gnustep-base dependency (for NSNotificationCenter, NSUserDefaults)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GNUSTEP_BASE REQUIRED gnustep-base)
target_include_directories(CoreFoundation PRIVATE ${GNUSTEP_BASE_INCLUDE_DIRS})
target_link_libraries(CoreFoundation PRIVATE ${GNUSTEP_BASE_LIBRARIES})

Header install paths

On a standard GNUstep layout, public headers are installed to:

${GNUSTEP_LOCAL_ROOT}/Library/Headers/CoreFoundation/CFNotificationCenter.h
${GNUSTEP_LOCAL_ROOT}/Library/Headers/CoreFoundation/CFPreferences.h
${GNUSTEP_LOCAL_ROOT}/Library/Headers/CoreFoundation/CFStringTokenizer.h

The umbrella header CoreFoundation.h in the source tree must also be updated to include the three new headers:

/* Add to CoreFoundation.h */
#include <CoreFoundation/CFNotificationCenter.h>
#include <CoreFoundation/CFPreferences.h>
#include <CoreFoundation/CFStringTokenizer.h>

Objective-C compilation flags

CFNotificationCenter.c and CFPreferences.c use Objective-C message sends and must be compiled as Objective-C. Two options:

  1. Rename to .m and add to libCoreFoundation_OBJC_FILES instead of _C_FILES. This is the cleanest approach and what other ObjC-backed CF files in libs-corebase (if any) do.
  2. Keep as .c and pass -x objective-c in a per-file CFLAGS override. Less clean but avoids renaming.

Recommendation: rename to .m. CFStringTokenizer.c is pure C + ICU and stays as .c.

Testing

Each new API should have a standalone test program that exercises the core functionality and validates correctness against the expected semantics. Compile and run these on the target GNUstep installation before wiring WebKit.

Test: CFNotificationCenter

/* test_cfnotificationcenter.m
   Compile: cc -x objective-c -framework CoreFoundation -lgnustep-base \
               -I${GNUSTEP_HEADERS} test_cfnotificationcenter.m -o test_cfnc
*/
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>

static int g_received = 0;

static void notificationCallback(
    CFNotificationCenterRef center,
    void *observer,
    CFNotificationName name,
    const void *object,
    CFDictionaryRef userInfo)
{
    printf("Received notification: %s\n",
           CFStringGetCStringPtr(name, kCFStringEncodingUTF8));
    g_received++;

    /* Verify userInfo */
    if (userInfo) {
        CFTypeRef val = CFDictionaryGetValue(userInfo, CFSTR("key"));
        if (val) {
            char buf[64];
            CFStringGetCString((CFStringRef)val, buf, sizeof(buf),
                               kCFStringEncodingUTF8);
            printf("  userInfo[key] = %s\n", buf);
        }
    }
}

int main(void)
{
    CFNotificationCenterRef center = CFNotificationCenterGetLocalCenter();
    if (!center) { fprintf(stderr, "FAIL: GetLocalCenter returned NULL\n"); return 1; }
    printf("PASS: CFNotificationCenterGetLocalCenter\n");

    /* Add observer */
    CFNotificationCenterAddObserver(
        center, (void *)0xDEAD, notificationCallback,
        CFSTR("TestNotification"), NULL,
        CFNotificationSuspensionBehaviorDeliverImmediately);

    /* Post notification with userInfo */
    CFStringRef keys[]   = { CFSTR("key") };
    CFStringRef values[] = { CFSTR("hello") };
    CFDictionaryRef info = CFDictionaryCreate(
        NULL, (const void **)keys, (const void **)values, 1,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);

    CFNotificationCenterPostNotification(
        center, CFSTR("TestNotification"), NULL, info, true);
    CFRelease(info);

    if (g_received != 1) {
        fprintf(stderr, "FAIL: expected 1 notification, got %d\n", g_received);
        return 1;
    }
    printf("PASS: notification delivered\n");

    /* Remove observer */
    CFNotificationCenterRemoveObserver(
        center, (void *)0xDEAD, CFSTR("TestNotification"), NULL);

    /* Post again — should not be received */
    CFNotificationCenterPostNotification(
        center, CFSTR("TestNotification"), NULL, NULL, true);
    if (g_received != 1) {
        fprintf(stderr, "FAIL: observer was not removed\n");
        return 1;
    }
    printf("PASS: observer removal works\n");

    /* RemoveEveryObserver */
    CFNotificationCenterAddObserver(
        center, (void *)0xBEEF, notificationCallback,
        CFSTR("Another"), NULL,
        CFNotificationSuspensionBehaviorDeliverImmediately);
    CFNotificationCenterRemoveEveryObserver(center, (void *)0xBEEF);
    CFNotificationCenterPostNotification(
        center, CFSTR("Another"), NULL, NULL, true);
    if (g_received != 1) {
        fprintf(stderr, "FAIL: RemoveEveryObserver did not work\n");
        return 1;
    }
    printf("PASS: CFNotificationCenterRemoveEveryObserver\n");

    printf("\nAll CFNotificationCenter tests passed.\n");
    return 0;
}

Test: CFPreferences

/* test_cfpreferences.m
   Compile: cc -x objective-c -framework CoreFoundation -lgnustep-base \
               -I${GNUSTEP_HEADERS} test_cfpreferences.m -o test_cfprefs
*/
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>

int main(void)
{
    CFStringRef appID = kCFPreferencesCurrentApplication;

    /* Write a string value */
    CFPreferencesSetAppValue(CFSTR("TestKey"), CFSTR("TestValue"), appID);
    CFPreferencesAppSynchronize(appID);

    /* Read it back */
    CFTypeRef val = CFPreferencesCopyAppValue(CFSTR("TestKey"), appID);
    if (!val) {
        fprintf(stderr, "FAIL: CFPreferencesCopyAppValue returned NULL\n");
        return 1;
    }
    char buf[64];
    CFStringGetCString((CFStringRef)val, buf, sizeof(buf), kCFStringEncodingUTF8);
    printf("Read back: %s\n", buf);
    if (strcmp(buf, "TestValue") != 0) {
        fprintf(stderr, "FAIL: expected 'TestValue', got '%s'\n", buf);
        return 1;
    }
    CFRelease(val);
    printf("PASS: CFPreferencesCopyAppValue\n");

    /* Write and read boolean */
    CFPreferencesSetAppValue(CFSTR("BoolKey"),
        kCFBooleanTrue, appID);
    Boolean exists = false;
    Boolean bval = CFPreferencesGetAppBooleanValue(
        CFSTR("BoolKey"), appID, &exists);
    if (!exists || !bval) {
        fprintf(stderr, "FAIL: boolean preference not readable\n");
        return 1;
    }
    printf("PASS: CFPreferencesGetAppBooleanValue\n");

    /* Write and read integer */
    SInt32 ival_in = 42;
    CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt32Type, &ival_in);
    CFPreferencesSetAppValue(CFSTR("IntKey"), num, appID);
    CFRelease(num);
    CFIndex ival_out = CFPreferencesGetAppIntegerValue(
        CFSTR("IntKey"), appID, &exists);
    if (!exists || ival_out != 42) {
        fprintf(stderr, "FAIL: integer preference not readable (got %ld)\n",
                (long)ival_out);
        return 1;
    }
    printf("PASS: CFPreferencesGetAppIntegerValue\n");

    /* Remove a key */
    CFPreferencesSetAppValue(CFSTR("TestKey"), NULL, appID);
    CFPreferencesAppSynchronize(appID);
    val = CFPreferencesCopyAppValue(CFSTR("TestKey"), appID);
    if (val != NULL) {
        fprintf(stderr, "FAIL: key should have been removed\n");
        return 1;
    }
    printf("PASS: key removal via NULL value\n");

    printf("\nAll CFPreferences tests passed.\n");
    return 0;
}

Test: CFStringTokenizer

/* test_cfstringtokenizer.c
   Compile: cc -framework CoreFoundation \
               $(icu-config --cflags --ldflags) \
               test_cfstringtokenizer.c -o test_cftok
*/
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>

static void printToken(CFStringRef str, CFRange r)
{
    char buf[256] = {0};
    CFStringRef sub = CFStringCreateWithSubstring(NULL, str, r);
    CFStringGetCString(sub, buf, sizeof(buf), kCFStringEncodingUTF8);
    CFRelease(sub);
    printf("  [%ld,%ld] '%s'\n", (long)r.location, (long)r.length, buf);
}

int main(void)
{
    /* --- Word tokenization --- */
    CFStringRef text = CFSTR("Hello, world. This is a test.");
    CFRange     full = CFRangeMake(0, CFStringGetLength(text));
    CFLocaleRef locale = CFLocaleCopyCurrent();

    CFStringTokenizerRef tok = CFStringTokenizerCreate(
        NULL, text, full, kCFStringTokenizerUnitWord, locale);
    if (!tok) {
        fprintf(stderr, "FAIL: CFStringTokenizerCreate returned NULL\n");
        return 1;
    }
    printf("Word tokens for '%s':\n",
           CFStringGetCStringPtr(text, kCFStringEncodingUTF8));

    int wordCount = 0;
    CFStringTokenizerTokenType type;
    while ((type = CFStringTokenizerAdvanceToNextToken(tok))
           != kCFStringTokenizerTokenNone) {
        CFRange r = CFStringTokenizerGetCurrentTokenRange(tok);
        if (r.location != kCFNotFound) {
            printToken(text, r);
            wordCount++;
        }
    }
    printf("Total tokens: %d\n", wordCount);
    /* "Hello, world. This is a test." has 6 words */
    if (wordCount < 5) {
        fprintf(stderr, "FAIL: expected at least 5 word tokens\n");
        return 1;
    }
    printf("PASS: CFStringTokenizerAdvanceToNextToken (word)\n");

    /* --- GoToTokenAtIndex --- */
    /* Index 7 is inside "world" */
    type = CFStringTokenizerGoToTokenAtIndex(tok, 7);
    if (type == kCFStringTokenizerTokenNone) {
        fprintf(stderr, "FAIL: GoToTokenAtIndex returned None\n");
        return 1;
    }
    CFRange r = CFStringTokenizerGetCurrentTokenRange(tok);
    printf("Token at index 7: [%ld,%ld]\n", (long)r.location, (long)r.length);
    if (r.location != 7 || r.length != 5) {   /* "world" starts at 7, length 5 */
        fprintf(stderr, "FAIL: expected 'world' at [7,5], got [%ld,%ld]\n",
                (long)r.location, (long)r.length);
        return 1;
    }
    printf("PASS: CFStringTokenizerGoToTokenAtIndex\n");

    CFRelease(tok);

    /* --- Sentence tokenization --- */
    CFStringRef sentences = CFSTR("First sentence. Second sentence. Third.");
    CFRange     sfull     = CFRangeMake(0, CFStringGetLength(sentences));
    CFStringTokenizerRef stok = CFStringTokenizerCreate(
        NULL, sentences, sfull, kCFStringTokenizerUnitSentence, locale);
    if (!stok) {
        fprintf(stderr, "FAIL: sentence tokenizer creation failed\n");
        return 1;
    }
    printf("\nSentence tokens:\n");
    int sentCount = 0;
    while ((type = CFStringTokenizerAdvanceToNextToken(stok))
           != kCFStringTokenizerTokenNone) {
        r = CFStringTokenizerGetCurrentTokenRange(stok);
        if (r.location != kCFNotFound) { printToken(sentences, r); sentCount++; }
    }
    if (sentCount != 3) {
        fprintf(stderr, "FAIL: expected 3 sentences, got %d\n", sentCount);
        return 1;
    }
    printf("PASS: CFStringTokenizerUnitSentence\n");

    /* --- SetString (reuse tokenizer) --- */
    CFStringRef text2 = CFSTR("One two three.");
    CFStringTokenizerSetString(stok, text2,
        CFRangeMake(0, CFStringGetLength(text2)));
    int count2 = 0;
    while (CFStringTokenizerAdvanceToNextToken(stok) != kCFStringTokenizerTokenNone)
        count2++;
    printf("After SetString, sentence count: %d\n", count2);
    printf("PASS: CFStringTokenizerSetString\n");

    CFRelease(stok);
    CFRelease(locale);

    printf("\nAll CFStringTokenizer tests passed.\n");
    return 0;
}
Testing recommendation: Run these three test programs as part of the pkgdemon/libs-corebase CI pipeline before any WebKit build attempt. A failing test here will manifest as a mysterious crash or incorrect behavior deep inside WebKit's layout engine, which is very hard to diagnose at that level.