Skip to content

feat(runtime): requestAnimationFrame and Android-parity frame callbacks - #446

Merged
NathanWalker merged 1 commit into
mainfrom
feat/raf
Aug 24, 2026
Merged

feat(runtime): requestAnimationFrame and Android-parity frame callbacks#446
NathanWalker merged 1 commit into
mainfrom
feat/raf

Conversation

@edusperoni

@edusperoni edusperoni commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a CADisplayLink-backed AnimationFrame module exposing two frame-callback surfaces on every global (workers included):

  • requestAnimationFrame(fn) / cancelAnimationFrame(handle) — spec semantics: every request registers its own one-shot entry and returns a numeric handle; cancellation is by handle (and can only touch rAF entries); fn receives a single DOMHighResTimeStamp on the isolate's performance timeline.
  • __postFrameCallback(fn[, delayMillis]) / __removeFrameCallback(fn) — the Android runtime's compatibility contract (NativeScript/android@d1fc925f): one-shot, deduped by function identity via a _postFrameCallbackId private value (re-posting a pending callback is a no-op), fn(frameTimeNanos, performanceMillis), TypeError on non-function arguments, and delayed entries firing on the first frame after the delay elapses.

How

  • One CADisplayLink per isolate, created paused during isolate init on the home thread and attached to that thread's runloop in the common modes (same modes as the EventLoop's timers, so ticks keep arriving during scroll tracking). Posting unpauses it; an empty registry pauses it again, so an idle isolate never wakes per frame.
  • CADisplayLink.timestamp shares the mach_absolute_time base with the V8 platform clock, so the vsync instant maps onto the performance timeline through Runtime::TimeOriginMonotonicSeconds() — callbacks observe the frame time, not dispatch time, and every callback in a batch sees the same timestamps.
  • Each tick dispatches only entries scheduled before it, so a callback that re-posts runs next frame, never twice in one batch. Callbacks are held by strong persistents until fired or removed, then released (covered by GC tests).
  • State is per-isolate and home-thread-only (registered through Caches::registerCacheBoundObject, torn down before Isolate::Dispose like TimerState), so unlike the Android implementation's process-global map, no locking is needed.
  • Links QuartzCore into the NativeScript target (it autolinks nothing, and UIKit alone does not resolve CADisplayLink under explicit -framework linking).

Tests

TestRunner/app/tests/AnimationFrameTests.js ports the Android testPostFrameCallback.js suite (existence, argument TypeErrors, single-fire, dedup on double-post, cancel/cancel-then-repost, self-reschedule, GC retention/release) and adds rAF specs (handle + single timestamp arg, once-per-request for the same function, targeted cancellation, batch timestamp equality, frame chaining, dedupe isolation from __postFrameCallback), a delay spec, and a frame-time/performance-timestamp spec. Full suite green locally on the iOS simulator.

Summary by CodeRabbit

  • New Features

    • Added support for standard requestAnimationFrame and cancelAnimationFrame APIs on iOS.
    • Added compatibility support for __postFrameCallback and __removeFrameCallback.
    • Improved animation-frame scheduling with cancellation, delays, batching, timestamps, and chained frames.
  • Tests

    • Added comprehensive coverage for animation-frame behavior, timing, validation, cancellation, and API interoperability.

Add a CADisplayLink-backed AnimationFrame module registering two
surfaces on every global (workers included):

- requestAnimationFrame/cancelAnimationFrame with spec semantics: one
  one-shot entry per request, a returned handle, cancellation by handle,
  and a single performance-timeline timestamp argument.
- __postFrameCallback(fn[, delayMillis])/__removeFrameCallback(fn),
  matching the Android runtime's contract: dedup by function identity
  via a private value, fn(frameTimeNanos, performanceMillis), and
  delayed entries firing on the first frame after the delay elapses.

The per-isolate display link is created paused on the isolate's home
runloop in the common modes and only runs while entries are pending, so
an idle isolate never wakes per frame. Frame timestamps map
CADisplayLink.timestamp (mach_absolute_time base, shared with the V8
platform clock) onto the performance timeline through the runtime's
monotonic time origin, so every callback in a batch observes the vsync
instant rather than dispatch time. QuartzCore is now linked into the
NativeScript target, which autolinks nothing.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ece8b68-d187-4e19-883e-ee607bd89727

📥 Commits

Reviewing files that changed from the base of the PR and between 9399899 and 36da24f.

📒 Files selected for processing (6)
  • NativeScript/runtime/AnimationFrame.hpp
  • NativeScript/runtime/AnimationFrame.mm
  • NativeScript/runtime/ModuleBinding.hpp
  • TestRunner/app/tests/AnimationFrameTests.js
  • TestRunner/app/tests/index.js
  • v8ios.xcodeproj/project.pbxproj

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Adds iOS animation-frame support for each V8 isolate. It exposes standard and compatibility callback APIs, schedules callbacks through CADisplayLink, integrates the runtime and Xcode project, and adds JavaScript contract tests.

Changes

Animation frame runtime

Layer / File(s) Summary
Animation-frame state and contract
NativeScript/runtime/AnimationFrame.hpp, NativeScript/runtime/AnimationFrame.mm
Defines tns::AnimationFrame and per-isolate callback state backed by a paused CADisplayLink.
Callback scheduling and dispatch
NativeScript/runtime/AnimationFrame.mm
Implements registration, cancellation, validation, delays, batching, timestamps, re-posting, cleanup, and display-link pausing.
Runtime and build integration
NativeScript/runtime/ModuleBinding.hpp, v8ios.xcodeproj/project.pbxproj
Registers the animationframe module, adds runtime files to the Xcode target, and links QuartzCore in Debug and Release.
JavaScript contract coverage
TestRunner/app/tests/AnimationFrameTests.js, TestRunner/app/tests/index.js
Tests both callback APIs and registers the animation-frame test suite with the runner.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 36da2

The PR adds frame-callback APIs with runtime, test, and build integration; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant JavaScript
  participant AnimationFrame
  participant CADisplayLink
  participant V8Isolate
  JavaScript->>AnimationFrame: requestAnimationFrame(callback)
  AnimationFrame->>CADisplayLink: schedule display-link callback
  CADisplayLink->>AnimationFrame: provide frame timestamp
  AnimationFrame->>V8Isolate: enter isolate and dispatch callbacks
  V8Isolate->>JavaScript: invoke callback with timestamp
Loading

Suggested reviewers: nathanwalker

Poem

A rabbit taps frames in a bright little line,
CADisplayLink keeps each heartbeat in time.
Callbacks hop, cancel, and queue,
Timestamps arrive fresh and true.
Tests twitch their noses: all paths shine!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding requestAnimationFrame and Android-parity frame callback APIs.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@NathanWalker
NathanWalker merged commit e9ce46e into main Aug 24, 2026
9 checks passed
@NathanWalker
NathanWalker deleted the feat/raf branch August 24, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants