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.
gnustep/libs-corebase currently exposes 648 CF symbols, compared to Apple's 1302 in the macOS 14 SDK. That sounds like a large gap — but the vast majority of what WebKit actually calls is already present..h and one .c file. Total new code: approximately 600–850 lines.gnustep/libs-corebase to pkgdemon/libs-corebase, add these three files, wire them into the GNUstep-make build, and submit upstream PRs.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.
| 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 |
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. |
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) |
CFNotificationCenterGetLocalCenter() — by far the most common; used in nearly every observer registrationCFNotificationCenterAddObserver() — registers a C callback for a named notificationCFNotificationCenterRemoveObserver() — deregisters an observerCFNotificationCenterRemoveEveryObserver() — bulk removalCFNotificationCenterPostNotification() — fires a notification with optional userInfo dictionaryCFNotificationCenterGetDistributedCenter() — less critical; used for cross-process notifications (not required for the minimal build but trivial to add)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.
/* 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)
};
/* 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
);
#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];
}
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.
CFPreferencesCopyAppValue(key, appID) — read a value for the current app (most common)CFPreferencesGetAppBooleanValue(key, appID, keyExistsAndHasValidFormat) — typed boolean readCFPreferencesGetAppIntegerValue(key, appID, keyExistsAndHasValidFormat) — typed integer readCFPreferencesSetAppValue(key, value, appID) — write a valueCFPreferencesAppSynchronize(appID) — flush to diskCFPreferencesCopyValue(key, appID, user, host) — multi-domain read (less common)CFPreferencesSetValue(key, value, appID, user, host) — multi-domain writeCFPreferencesSynchronize(appID, user, host) — flush multi-domainCFPreferencesCopyKeyList(appID, user, host) — enumerate keysCFPreferencesCopyMultiple(keysToFetch, appID, user, host) — bulk readWrap NSUserDefaults. The mapping is direct and complete:
kCFPreferencesCurrentApplication → [NSUserDefaults standardUserDefaults]kCFPreferencesAnyApplication → the NSGlobalDomain (accessed via -persistentDomainForName:NSGlobalDomain)kCFPreferencesCurrentUser / kCFPreferencesAnyUser → irrelevant on non-macOS; map to the standard defaultskCFPreferencesCurrentHost / kCFPreferencesAnyHost → map to the standard defaults (host-level prefs are a macOS-specific concept)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].
/* 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;
/* 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
);
#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;
}
CFStringTokenizerCreate(allocator, string, range, unit, locale) — construct a tokenizer for a stringCFStringTokenizerSetString(tokenizer, string, range) — replace the string being tokenizedCFStringTokenizerAdvanceToNextToken(tokenizer) — move to the next token; returns token type flagsCFStringTokenizerGoToTokenAtIndex(tokenizer, index) — seek to the token at a character indexCFStringTokenizerGetCurrentTokenRange(tokenizer) — returns CFRange of the current tokenCFStringTokenizerCopyCurrentTokenAttribute(tokenizer, attribute) — returns language, script, or stem data for the tokenICU's UBreakIterator API, available in <unicode/ubrk.h>, implements exactly the same concept. The unit constants map as follows:
| CF unit constant | ICU UBreakIteratorType | Notes |
|---|---|---|
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.
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;
/* 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 */
);
#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;
}
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.
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. |
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.
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
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.
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})
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>
CFNotificationCenter.c and CFPreferences.c use Objective-C message sends and must be compiled as Objective-C. Two options:
.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..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.
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.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.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.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;
}