What must be added to gnustep/libs-quartzcore for WebKit v5. Includes 6 open PRs from DTW-Thalion that should be merged first. For the minimal build (ENABLE_GPU_PROCESS=OFF), most gaps are bypassed by software rendering. Parent plan: WebKit v5 Cocoa-native porting plan.
CATransform3DInvert was computing the adjugate matrix instead of the true inverse (missing divide by determinant). This corrupts hit-testing and projection math. Merge immediately.ENABLE_GPU_PROCESS=OFF), the rendering path is CGBitmapContext → NSBitmapImageRep → NSView drawRect:. This bypasses CALayer compositing entirely. Most QuartzCore gaps therefore don't block Phase 1–4.CALayer.frame — WebKit reads and writes this property constantly; without it layer geometry is broken.CALayer geometry conversion methods (convertPoint:fromLayer:, etc.) — needed for hit testing.CATransaction.completionBlock — currently a TODO; WebKit uses this for animation teardown callbacks.CADisplayLink — needed for animation frame timing in Phase 5+.CATiledLayer, CATextLayer, CAGradientLayer, CALayer.mask — Phase 6+.CAContext (multi-process layer hosting) — Phase 7+ only if GPU compositing across processes is required.6 PRs from DTW-Thalion were merged into pkgdemon/libs-quartzcore. All 6 verified as correctly implemented.
| PR | What | Verified |
|---|---|---|
| #13 | CATransform3DInvert — now divides by determinant |
Confirmed — full cofactor-matrix inversion with epsilon check |
| #16 | CATransform3DMakeAffineTransform, CATransform3DIsAffine, CATransform3DGetAffineTransform |
Confirmed — all 3 functions present with correct implementation |
| #15 | CAMediaTimingFunction.h imports CGBase.h for CGFloat |
Confirmed — unconditional import |
| #11 | Heap-allocate pixel buffer in CAGLTexture _writeToPNG |
Confirmed — uses malloc() with NULL guard |
| #12 | CATransform3D test suite (289 lines) |
Confirmed — covers identity, translate, scale, rotate, concat, invert |
| #14 | CAMediaTimingFunction test suite (229 lines) |
Confirmed — covers all 5 named functions + custom + solveForInput |
These 3 items are still missing and needed before WebKit can use CALayer:
@property, implemented via @synthesize only. This creates a simple ivar-backed getter/setter, but Apple's CALayer.frame is a computed property derived from position, bounds, and anchorPoint. Setting frame should update position and bounds. The current implementation reads/writes _frame independently — semantically wrong for WebKit.convertPoint:fromLayer:, convertPoint:toLayer:, convertRect:fromLayer:, convertRect:toLayer: are declared in the header but inside #if 0 in CALayer.m (lines 1229–1237). Zero implementation.kCATransactionCompletionBlock). No ivar, no property, no invocation in commit.Note: convertTime:fromLayer: and convertTime:toLayer: ARE implemented (the time variants work, the spatial variants don't).
CADisplayLink (header-only stub), CATiledLayer, CATextLayer, CAGradientLayer (partial stubs), CALayer.mask (not declared). These are bypassed by software rendering in the minimal build.
Seven PRs are open against gnustep/libs-quartzcore as of July 2026. Six are from DTW-Thalion and are in good shape. One (#6, ethanc8) has stalled and needs rework before merging. The pkgdemon fork should cherry-pick or merge all DTW-Thalion PRs immediately — they fix real bugs and improve build quality with no known regressions.
| PR | Author | What it does | WebKit impact | Priority |
|---|---|---|---|---|
| #16 | DTW-Thalion | Add CATransform3DMakeAffineTransform, CATransform3DIsAffine, CATransform3DGetAffineTransform — the bridge between 2D CGAffineTransform and the 4×4 CATransform3D matrix |
Direct. WebKit frequently bridges CG and CA transforms, e.g. when mapping a layer's geometry into a CGAffineTransform for hit-testing or when setting layer.affineTransform. Without this bridge, any code path involving CATransform3DIsAffine will crash or produce garbage. |
Merge now |
| #15 | DTW-Thalion | Make CAMediaTimingFunction.h self-contained — removes a hidden transitive include dependency so the header can be included in isolation |
Build quality. WebKit includes QuartzCore headers in piecemeal order; an include cycle or missing transitive header causes hard-to-diagnose build failures. Makes the header safe to include from any translation unit. | Merge now |
| #14 | DTW-Thalion | CAMediaTimingFunction tests — unit tests for the timing function interpolation logic (linear, ease, ease-in, ease-out, ease-in-out, custom cubic Bézier control points) |
Test coverage. Catches regressions in animation timing interpolation, which affects CSS transitions and animation-timing-function behavior in WebKit. |
Merge now |
| #13 | DTW-Thalion | Fix CATransform3DInvert — the existing implementation computed the adjugate (cofactor transpose) matrix but omitted the final division by the determinant. The result is a matrix scaled by det(M), not the true inverse M−1. |
Critical bug. Any code using CATransform3DInvert gets a wrong matrix. In WebKit this corrupts: (1) hit-testing when layers have non-identity transforms (the point-in-layer conversion uses the inverse of the layer's accumulated transform), (2) animation keyframe interpolation, (3) convertPoint:toLayer:/convertPoint:fromLayer: which walk the transform tree in both directions. Every transformed layer on-screen has broken input handling until this is fixed. |
Merge now — critical |
| #12 | DTW-Thalion | CATransform3D tests — comprehensive test coverage for all CATransform3D math functions (concatenation, inversion, rotation, scale, translation, affine conversion) |
Test coverage. Guards against future regressions in transform math. Also serves as executable documentation of expected behavior. | Merge now |
| #11 | DTW-Thalion | Heap-allocate the pixel buffer in _writeToPNG — previously the buffer was stack-allocated with a fixed size, causing a stack overflow for large textures (e.g. a retina-density web page tile) |
Crash fix. WebKit renders at contentsScale 2.0 on HiDPI displays. A 1024×1024 tile at 2× scale = 2048×2048 RGBA = 16 MB — far beyond any reasonable stack limit. Without this fix, any PNG-capture code path (used for debugging, screenshots, and potentially for software compositing transfer) will stack-overflow silently. |
Merge now |
| #6 | ethanc8 | Build fixes — corrects various compile errors found when building libs-quartzcore on non-macOS systems | Build quality. Stalled — the PR has unresolved review comments and may conflict with the DTW-Thalion changes. Needs a rebase and a fresh review pass before merging. | Review — needs rework |
pkgdemon/libs-quartzcore forked from gnustep/libs-quartzcore. Apply PR #11, #12, #13, #14, #15, #16 as a merge or cherry-pick series (in that order, so tests run against the fixed code). Hold #6 until rebased. Build against the WebKit source tree and run the new test suite before tagging a release.
libs-quartzcore is approximately 50% of the full QuartzCore surface area that macOS exposes to WebKit. For the minimal build the effective gap is much smaller because ENABLE_GPU_PROCESS=OFF routes rendering through CGBitmapContext, bypassing most CALayer compositing. The table below reflects the state after merging all DTW-Thalion PRs.
| Class / API | Status | Notes |
|---|---|---|
CALayer — basic properties |
Working | bounds, position, anchorPoint, opacity, backgroundColor, hidden, sublayers, superlayer, zPosition, transform, sublayerTransform, contents, contentsRect, delegate, shouldRasterize, contentsScale, masksToBounds, cornerRadius, borderWidth, borderColor, shadowColor, shadowOffset, shadowOpacity, shadowRadius |
CALayer.frame |
Missing | Derived property (computed from bounds/position/anchorPoint/transform). WebKit uses this constantly. See §5a. |
CALayer.mask |
Missing | Alpha-channel masking. Used in CSS clip-path and mask compositing. |
CALayer — geometry conversion |
Missing | convertPoint:fromLayer:, convertPoint:toLayer:, convertRect:fromLayer:, convertRect:toLayer:, convertTime:fromLayer:, convertTime:toLayer:. See §5b. |
CALayer — animations dict |
Working | addAnimation:forKey:, removeAnimationForKey:, removeAllAnimations, animationForKey:, animationKeys |
CALayer — presentation/model |
Working | presentationLayer, modelLayer |
CALayer — hit testing |
Partial | hitTest: exists but depends on CATransform3DInvert (broken until PR #13 merges) and missing geometry conversion methods. |
CABasicAnimation |
Working | Full: fromValue, toValue, byValue, interpolation |
CAKeyframeAnimation |
Working | Full: values, keyTimes, timingFunctions, calculationMode (linear/discrete/paced) |
CASpringAnimation |
Working | Full: mass, stiffness, damping, initialVelocity, settlingDuration |
CATransition |
Partial | MoveIn type implemented; Push, Reveal, Fade not implemented. WebKit uses fade transitions. |
CAPropertyAnimation |
Working | Base class for Basic/Keyframe; keyPath, additive, cumulative |
CAMediaTiming protocol |
Working | beginTime, duration, speed, timeOffset, repeatCount, repeatDuration, autoreverses, fillMode |
CAMediaTimingFunction |
Working | After PR #15: self-contained header. Cubic Bézier interpolation, named presets (linear, easeIn, easeOut, easeInEaseOut, default). PR #14 adds tests. |
CATransaction |
Partial | begin, commit, flush, lock, unlock, animationDuration, setAnimationDuration:, disableActions, setDisableActions:, animationTimingFunction. Missing: completionBlock (see §5c). |
CATransform3D — math |
Working | After PR #13: all functions correct. Concatenation, rotation, scale, translation, inversion, equality, identity check. After PR #16: CATransform3DMakeAffineTransform, CATransform3DIsAffine, CATransform3DGetAffineTransform. PR #12 adds tests. |
CAShapeLayer |
Working | Full: path, fillColor, strokeColor, lineWidth, lineDashPattern, strokeStart, strokeEnd, fillRule, lineCap, lineJoin |
CARenderer |
Working | OpenGL-backed compositor. rendererWithCGLContext:options:, setLayer:, beginFrameAtTime:timeStamp:, render, endFrame. Only used when ENABLE_GPU_PROCESS=ON. |
CAAction protocol |
Working | runActionForKey:object:arguments: |
CAFilter |
Constants only | Filter name constants declared; no actual image-filter processing. Used by WebKit for blur/saturate filters on layers. |
CAValueFunction |
Constants only | Function name constants declared; no evaluation logic. Used for animating transform components individually (rotate.x, scale.y, etc.). |
CAGradientLayer |
Header only | Declares colors, locations, startPoint, endPoint, type. No drawInContext: implementation. |
CATextLayer |
Header only | Declares string, font, fontSize, foregroundColor, alignmentMode, wrapped. No rendering. |
CADisplayLink |
Header only | Class declared; no implementation. See §6a. |
CAScrollLayer |
Header only | Declares scrollToPoint:, scrollToRect:, scrollMode. No implementation. |
CAReplicatorLayer |
Header only | Declares instanceCount, instanceDelay, instanceTransform, instanceColor. No implementation. |
CATiledLayer |
Header only | Declares tileSize, levelsOfDetail, levelsOfDetailBias. No implementation. See §6b. |
CATransformLayer |
24-line stub | Subclasses CALayer; no 3D-pass-through compositing. |
CAOpenGLLayer |
Minimal stub | Not required for ENABLE_GPU_PROCESS=OFF. |
CAContext |
Not present | Apple SPI for cross-process layer hosting. See §7a. |
These are the QuartzCore gaps that must be resolved before WebKit can use CALayer at all, even in the software-rendering path. They are not blocked on GPU compositing — they are fundamental layer-tree operations that WebKit's layout and rendering engines call unconditionally.
CALayer.frame propertyCALayer currently exposes only bounds and position. WebKit reads and writes layer.frame in dozens of places — e.g. RenderLayerBacking::updateGeometry(), GraphicsLayerCA::setPosition(), GraphicsLayerCA::setSize().
frame is a derived property. On macOS it is defined as:
bounds rectangle after applying transform and repositioning around position/anchorPoint. When transform is the identity matrix, frame.origin = position − anchorPoint × bounds.size and frame.size = bounds.size. When the transform is non-identity the getter must compute the four corners of the transformed bounds and return their bounding box.bounds.size from the frame size (no transform applied — setting frame with a non-identity transform is undefined behavior on macOS too) and position from frame.origin + anchorPoint × frame.size.Implementation location: Source/CALayer.m
/* Getter — simplified (identity transform fast path) */
- (CGRect)frame
{
CGRect bounds = self.bounds;
CGPoint anchor = self.anchorPoint; /* default {0.5, 0.5} */
CGPoint pos = self.position;
CATransform3D t = self.transform;
if (CATransform3DIsIdentity(t)) {
return CGRectMake(pos.x - anchor.x * bounds.size.width,
pos.y - anchor.y * bounds.size.height,
bounds.size.width,
bounds.size.height);
}
/* Non-identity: transform all four corners, return AABB */
CGFloat x[4], y[4];
CGFloat w = bounds.size.width, h = bounds.size.height;
CGFloat ox = anchor.x * w, oy = anchor.y * h;
/* ... apply 3D transform to each corner, collect min/max ... */
return CGRectMake(/* min x */, /* min y */, /* max-min x */, /* max-min y */);
}
/* Setter */
- (void)setFrame:(CGRect)frame
{
self.bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
CGPoint anchor = self.anchorPoint;
self.position = CGPointMake(frame.origin.x + anchor.x * frame.size.width,
frame.origin.y + anchor.y * frame.size.height);
}
Estimated LOC: 40–60 in CALayer.m. The non-identity-transform corner case adds ~20 lines.
Blocking: Phase 2. WebKit cannot use CALayer without this.
CALayer geometry conversion methodsconvertPoint:fromLayer:, convertPoint:toLayer:, convertRect:fromLayer:, convertRect:toLayer:, convertTime:fromLayer:, convertTime:toLayer:.
These are used extensively in WebKit for hit testing (GraphicsLayerCA::pointInLayerRect) and for mapping coordinates between the web-layer hierarchy and the native view hierarchy.
Algorithm: To convert a point from layer A to layer B:
transform scaled by contentsScale and offset by position/anchorPoint. This gives transform TA→root.Finding the common ancestor: standard lowest-common-ancestor walk using the superlayer chain. If there is no common ancestor (different layer trees), return an undefined value — macOS documents this as undefined behavior.
Estimated LOC: 100–150 in CALayer.m. The LCA walk is ~40 lines; the transform accumulation is ~40 lines; the point/rect variants share most code.
Blocking: Phase 5 (hit testing), but implementing now avoids a second edit pass.
CATransaction.completionBlockCATransaction has a completionBlock property marked TODO in the implementation. WebKit uses this in GraphicsLayerCA::flushCompositingState and in animation completion teardown paths.
On macOS, completionBlock is called on the main thread after all animations in the transaction have finished (i.e., after the last animation's CAAnimationDelegate animationDidStop:finished: has been called and the layer tree has been committed to the render server).
Implementation plan:
_completionBlock ivar (dispatch_block_t / void (^)(void)) to the CATransaction stack frame.+completionBlock and +setCompletionBlock: class methods that get/set the block on the current transaction stack frame (same pattern as animationDuration).+commit, after all animations for this transaction have been applied and the layer tree has been flushed, invoke the completion block on the main thread.ENABLE_GPU_PROCESS=OFF), animations resolve synchronously during commit, so the completion block can be called immediately after commit returns.Estimated LOC: 30–50 in CATransaction.m.
Blocking: Phase 2 if any WebKit code path unconditionally sets a completion block before committing. Phase 5 otherwise.
These gaps are not blocking for the minimal software-rendering build but are required for Phase 5 (interactive compositing, smooth animations, 60 Hz rendering) and Phase 6 (full CSS visual effects).
CADisplayLinkCADisplayLink for vsync-aligned animation frame scheduling in DisplayRefreshMonitorCocoa and WKDisplayLinkRunLoopSource. macOS added CADisplayLink as a public API in macOS 14 (Sonoma); it was previously iOS-only. WebKit on macOS 14+ uses it for main-thread animation timing.
On macOS, CADisplayLink calls its target/selector once per display refresh, at the hardware vsync interval, delivered on a specified NSRunLoop. It exposes timestamp (time of the last frame), targetTimestamp (time of the next frame), duration (frame interval), and preferredFramesPerSecond.
Implementation plan for GNUstep (X11 environment):
XRRGetScreenInfo() to get the current monitor's SizeID and then XRRConfigCurrentRate(). Fall back to 60 Hz if RandR is unavailable or returns 0. Cache the result at CADisplayLink creation time; re-query on display configuration change notifications.dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()) with dispatch_source_set_timer(source, DISPATCH_TIME_NOW, interval_ns, leeway_ns) where interval_ns = 1,000,000,000 / refresh_rate and leeway_ns = interval_ns / 10.NSRunLoop, schedule the GCD source on a queue associated with that run loop. For the main run loop this is dispatch_get_main_queue().CACurrentMediaTime() (which calls mach_absolute_time() or clock_gettime(CLOCK_MONOTONIC) on GNUstep) to fill timestamp and targetTimestamp = timestamp + duration.CVDisplayLink (from libs-corevideo). If libs-corevideo implements CVDisplayLinkSetOutputCallback, CADisplayLink can be a thin wrapper. For now, implement CADisplayLink standalone and leave the CVDisplayLink path for later.@implementation CADisplayLink {
dispatch_source_t _timer;
id _target;
SEL _selector;
CFTimeInterval _duration;
CFTimeInterval _timestamp;
CFTimeInterval _targetTimestamp;
BOOL _paused;
}
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel
{
CADisplayLink *dl = [[self alloc] init];
dl->_target = target;
dl->_selector = sel;
dl->_duration = 1.0 / [self _queryRefreshRate];
return dl;
}
- (void)addToRunLoop:(NSRunLoop *)rl forMode:(NSRunLoopMode)mode
{
NSTimeInterval iv = _duration;
uint64_t ns = (uint64_t)(iv * 1e9);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_main_queue());
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, ns, ns / 10);
__weak CADisplayLink *weakSelf = self;
dispatch_source_set_event_handler(_timer, ^{
[weakSelf _fire];
});
dispatch_resume(_timer);
}
- (void)_fire
{
_timestamp = CACurrentMediaTime();
_targetTimestamp = _timestamp + _duration;
[_target performSelector:_selector withObject:self];
}
@end
Estimated LOC: 150–200 in Source/CADisplayLink.m + header updates.
Blocking: Phase 5. Without this, animation frame timing falls back to a fixed timer in WebKit, which is imprecise and not vsync-aligned.
CATiledLayerCATiledLayer manages this tile grid and calls the delegate or drawInContext: override for each tile as it becomes visible.
macOS CATiledLayer behavior:
tileSize (default 256×256 points).levelsOfDetail controls how many zoom levels are maintained in the tile cache.levelsOfDetailBias controls asymmetry (more detail levels above vs. below the current scale).CGContext set up by CATiledLayer; the caller draws into it via drawInContext: or the layer delegate.Implementation plan:
CALayer in Source/CATiledLayer.m.display or a custom render hook, determine which tiles intersect the current visibleRect (derived from the superlayer hierarchy).CGBitmapContext creation + drawInContext: call to a background queue (use dispatch_async with a concurrent queue). Set the CTM to position the context over the tile's rect before calling drawInContext:.contents on a sublayer tile (or composite it into the parent layer's backing store) on the main queue.levelsOfDetail * (visible_tiles + margin) tiles in memory.Estimated LOC: 300–500 in Source/CATiledLayer.m.
Blocking: Phase 6+ (performance optimization for large pages).
CATextLayerCATextLayer for certain text overlays and in some subframe rendering paths.
Implementation plan:
CALayer; override drawInContext:.string property accepts either an NSString or an NSAttributedString. If plain string, construct an NSAttributedString applying font, fontSize, foregroundColor, alignmentMode as paragraph/character attributes.CTFramesetter from the attributed string, create a CGPath from bounds, create a CTFrame, call CTFrameDraw(frame, ctx).wrapped = NO, truncate at the layer width using a CTLine with kCTLineTruncationEnd.truncationMode: none, start, middle, end.Estimated LOC: 200–300 in Source/CATextLayer.m.
Blocking: Phase 6+.
CAGradientLayerbackground: linear-gradient() and radial-gradient() in composited rendering paths.
Implementation plan:
CALayer; override drawInContext:.CGGradient from the colors array (array of CGColorRef) and locations array (array of CGFloat). If locations is nil, distribute stops evenly.type = kCAGradientLayerAxial (linear, the default): call CGContextDrawLinearGradient(ctx, gradient, startPt, endPt, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation). startPoint and endPoint are in unit coordinate space (0,0 = bottom-left, 1,1 = top-right — note: CA uses flipped Y for gradient unit space on macOS). Map to pixel coordinates by multiplying by bounds.size.type = kCAGradientLayerRadial: call CGContextDrawRadialGradient with the center derived from startPoint.type = kCAGradientLayerConic (macOS 12+): not in WebKit's minimal path; stub with linear fallback.Estimated LOC: 100–150 in Source/CAGradientLayer.m.
Blocking: Phase 6+.
CALayer.maskmask property sets another CALayer as the alpha-channel mask for the receiver. Used in CSS clip-path, mask, and -webkit-mask properties in composited rendering paths.
Implementation plan:
_mask ivar (CALayer *) to CALayer. Implement -mask / -setMask: accessors.CARenderer or CALayer's renderInContext:), after rendering the receiver's subtree into an off-screen buffer:
CGContextClipToMask(ctx, bounds, maskImage).position, bounds, and transform are applied relative to the masked layer's coordinate space.Estimated LOC: 100–200 across CALayer.m and the render path.
Blocking: Phase 6+.
CAContext (Apple SPI)CAContext is private Apple SPI that hosts a CALayer tree across process boundaries using Mach ports. The UIProcess holds a context ID; the WebProcess renders into the matching context; the display compositor merges them without a shared-memory pixel copy.
Why it matters for WebKit: When ENABLE_GPU_PROCESS=ON, the GPU Process renders layer trees and the UIProcess displays them. On macOS this handoff is via CAContext + Mach ports + IOSurface. Without CAContext, the GPU Process path cannot work.
Alternative approaches for GNUstep (in ascending complexity):
| Approach | Mechanism | GPU needed? | Phase |
|---|---|---|---|
| Software pixel blit | WebProcess renders into a CGBitmapContext backed by a POSIX shared memory region (shm_open / memfd_create). UIProcess maps the same region and blits it to the screen via NSBitmapImageRep + drawInRect:. This is v4's architecture — proven correct in principle. |
No | Phase 3–4 (current target) |
| DMA-BUF sharing | WebProcess renders into a DRM/KMS DMA-BUF surface (Linux kernel primitve). UIProcess imports the same DMA-BUF as an OpenGL texture and composites it. Zero pixel copy. Requires a DRM device and EGL. | Yes (EGL) | Phase 7+ |
| CAContext over Unix sockets + shm | Implement CAContext as a GNUstep extension using a Unix domain socket for control messages and POSIX shm for layer-tree serialization. The compositor in the UIProcess deserializes the tree and renders it. Closest to the macOS architecture; highest implementation cost. |
Optional | Phase 8+ |
Recommended approach: For Phase 1–6, keep ENABLE_GPU_PROCESS=OFF and use the software pixel blit path. This entirely sidesteps CAContext. Implement CAContext stubs (enough to compile, panic at runtime) to allow the source tree to build with ENABLE_GPU_PROCESS=ON for future work.
Stub implementation:
@interface CAContext : NSObject
+ (CAContext *)contextWithCGSConnection:(uint32_t)cid options:(NSDictionary *)opts;
@property (readonly) uint32_t contextId;
@property (retain) CALayer *layer;
@end
@implementation CAContext
+ (CAContext *)contextWithCGSConnection:(uint32_t)cid options:(NSDictionary *)opts
{
[NSException raise:NSInternalInconsistencyException
format:@"CAContext: multi-process compositing not implemented on GNUstep"];
return nil;
}
@end
CABackdropLayer, CAPresentationModifier, CARenderServer (Apple SPI)CABackdropLayer for backdrop-filter blur, CAPresentationModifier for in-flight animation modification, CARenderServer for direct render server communication).
These are guarded by #if HAVE(CA_BACKDROP_LAYER), #if HAVE(CA_PRESENTATION_MODIFIER), and similar feature-detection macros in WebKit. OptionsGNUstep.cmake should define these as OFF:
set(HAVE_CA_BACKDROP_LAYER OFF CACHE BOOL "" FORCE) set(HAVE_CA_PRESENTATION_MODIFIER OFF CACHE BOOL "" FORCE) set(HAVE_CA_RENDER_SERVER OFF CACHE BOOL "" FORCE)
With these flags off, WebKit's preprocessor guards will exclude all code that references these SPI classes. No stub implementations are needed — the feature-disable path is the right approach. Priority: Phase 7+ (and even then, only if backdrop-filter CSS is needed).
libs-quartzcore has an existing test infrastructure in the Tests/ directory. The test programs are visual X11 applications that open windows and render animations — useful for interactive verification but not directly CI-runnable without a display.
CATransform3D tests: Non-visual unit tests covering all math functions: identity, concatenation (left vs. right multiply), rotation around each axis, scale, translation, inversion (validates that M × M−1 = I — the test that exposed the PR #13 bug), equality comparison, CATransform3DIsIdentity, CATransform3DIsAffine (after PR #16 lands).CAMediaTimingFunction tests: Tests for all six named presets (linear, easeIn, easeOut, easeInOut, default, and custom control points). Validates that interpolation at t = 0 returns 0 and at t = 1 returns 1. Tests the cubic Bézier solver (Newton-Raphson iteration) for accuracy at mid-points.PR #11 switches the pixel buffer from stack to heap — a side effect is that the PNG capture path becomes usable for automated image comparison tests. The strategy:
EGL_EXT_platform_surfaceless (no X11 required). Mesa's software renderer (llvmpipe or softpipe) supports this on any Linux CI host.CARenderer against the EGL context.CARenderer.glReadPixels._writeToPNG path and compare against a reference image using a pixel-diff threshold.This pattern gives visual regression tests that run on CI without a real GPU or X11 server. Extend the existing CARendererTest program to support a --headless flag that activates this path.
| Component | Test type | Coverage after PRs |
|---|---|---|
CATransform3D math |
Unit (non-visual) | Full — PR #12 |
CATransform3DInvert |
Unit (algebraic identity check) | Full — PR #12 + PR #13 |
CATransform3D affine bridge |
Unit (round-trip CGAffineTransform) | Full — PR #12 + PR #16 |
CAMediaTimingFunction |
Unit (interpolation accuracy) | Full — PR #14 |
CALayer compositing |
Visual (X11 / EGL headless) | Partial — existing visual tests |
CALayer.frame |
Unit (geometry) | Not yet — must add with implementation |
| Geometry conversion methods | Unit (coordinate mapping) | Not yet — must add with implementation |
CATransaction.completionBlock |
Unit (callback fires) | Not yet — must add with implementation |
CADisplayLink |
Integration (timer fires at ~60 Hz) | Not yet |
make check target to libs-quartzcore that runs the non-visual unit tests (CATransform3D, CAMediaTimingFunction, CALayer geometry) and the EGL headless visual tests as a single command. These should pass on any Linux host with Mesa installed (pkg install mesa-dri on NextBSD). Visual X11 tests remain opt-in via make check-visual.