Joone Blog

Fixing the issue where sourceCharPosition always returns -1 for resolve-promise invoker type in LoAF script entries

Posted on Mar 1, 2026

In Long Animation Frames (LoAF) script entries, the sourceCharPosition value for the resolve-promise invoker type is consistently -1 when the promise originates from APIs like fetch or scheduler.yield. This issue limits precise attribution of the source location for these promises, making it difficult to debug performance problems involving such APIs.

Chromium Issues

This was not actually a bug, but a missing implementation: getting sourceCharPosition is an expensive operation that requires looking into call stacks in V8. When I implemented this missing feature, I had to add a new interface to V8.

Change List

The patches listed below resolved this issue.

However, the feature remained behind the Blink runtime flag (LongAnimationFrameSourceCharPosition) for over a year because the LoAF API owner had concerns about the potential performance impact. In particular, V8 performance regressions observed in early Finch tests prompted caution and required multiple rounds of optimization work.

Performance Optimization

Implement lazy conversion for SourceLocation::Url()

Previously, the URL string for a SourceLocation captured from the V8 stack was immediately converted from a v8::String to a Blink String in CapturePartialSourceLocationFromStack. This conversion involves allocation and copying, which may be unnecessary if the SourceLocation::Url() method is never called.

This change modifies SourceLocation to perform this conversion lazily, improving the overall performance of getting a source URL for the resolve-promise invoker type in LoAF script entries.

Optimize retrieval of script URL and source location in CapturePartialSourceLocationFromStack

This change streamlines the retrieval of the script URL and source offset by leveraging v8::StackFrame directly, removing the dependency on StackTrace. This optimization reduces redundant stack traversal and improves performance.

Convert SourceLocation to an Oilpan GC class

SourceLocation was previously managed via std::unique_ptr, while ScriptPromiseResolver is managed by the V8 garbage collector. Making SourceLocation a GC-managed class can potentially improve allocation performance by using the GC's infrastructure. More importantly, a GC-collected SourceLocation can more easily hold onto V8 objects (such as v8::Local<v8::StackFrame> or v8::Local<v8::String>) needed for the lazy approach, as the GC helps manage the V8 handles' lifetimes correctly.

Why This Matters

Previously, many LoAF entries — especially those triggered by promise-based invokers such as resolve-promise, fetch(), and scheduler.yield() — reported sourceCharPosition = -1, making it extremely difficult to understand what actually caused a long animation frame.

With this feature now enabled, LoAF entries provide accurate script attribution, making it much easier to diagnose main-thread congestion and identify the true root causes of long animation frames.

Feature Rollout

Finch is Google's infrastructure for running experiments and rolling out features gradually to Chrome users. It allows developers to enable a change for a small percentage of the user base (e.g., 1% of Chrome Beta users) and monitor its effects. Due to all the performance optimization efforts, no performance regression was observed with this change, and Google finally decided to enable this feature by default.