CVE-2026-79181
Overview
Background
- `requestAnimationFrame` (rAF)
- the web-exposed API that registers a callback to run before the next paint, backed in Blink by
FrameRequestCallbackCollection. - `FrameRequestCallbackCollection`
- holds registered frame callbacks in
frame_callbacks_, assigns each an id fromnext_callback_id_, and exposesRegisterFrameCallback/CancelFrameCallback/ExecuteFrameCallbacks. - Internal rAF users
- engine components (e.g.
DocumentMarkerController’s Glic marker animation,HTMLMarqueeElement,PaintTiming) callDocument::RequestAnimationFrameinternally, sharing the same collection as webrequestAnimationFrame. - `FrameCallbackType`
- new enum (
kWebExposed,kInternal) introduced by the patch to distinguish web-page callbacks from engine-internal callbacks.
Root Cause Analysis
Before the patch, both web requestAnimationFrame and engine-internal rAF registrations funneled into a single frame_callbacks_ list with ids drawn from one shared counter next_callback_id_, and CancelFrameCallback searched that single list by id. This conflated two trust domains: the id returned to a web page depended on how many internal callbacks had been registered, and a page’s cancelAnimationFrame(id) could match and cancel an internal callback that happened to share the id. That created an observable discrepancy — a web page could infer the presence/activity of internal animations (such as the Glic marker animation registered from DocumentMarkerController) from id allocation or cancellation side effects.
The fix adds a FrameCallbackType parameter threaded through Document, ScriptedAnimationController, WorkerAnimationFrameProvider, and FrameRequestCallbackCollection, giving internal callbacks their own list internal_frame_callbacks_ and their own id counter next_internal_callback_id_, and making RegisterFrameCallback/CancelFrameCallback operate only on the list matching the caller’s type. Web entry points (LocalDOMWindow) pass kWebExposed while internal callers (DocumentMarkerController, HTMLMarqueeElement, PaintTiming, etc.) pass kInternal, so the two pools no longer share ids or cancellation scope. ExecuteFrameCallbacks now snapshots and runs both lists via the shared ExecuteFrameCallbacksImpl so behavior for scheduling is preserved.
FrameCallbackType.Attack Path
- Trigger an internal rAF user
The attacker page induces an engine feature that schedules internal rAF callbacks (e.g. conditions that start the Glic marker animation in
DocumentMarkerController, or a marquee). - Register/observe web callbacks
The page calls
requestAnimationFrameand inspects the returned ids, which pre-patch are drawn from the samenext_callback_id_counter shared with internal registrations. - Detect the discrepancy
By correlating gaps/values in the returned ids (or by calling
cancelAnimationFrameon candidate ids and observing effects) the page infers whether internal callbacks are registered, leaking otherwise-unobservable engine state (the reported observable discrepancy in Glic). - Cross-domain cancellation (inferred)
Because
CancelFrameCallbackscanned the single shared list, a webcancelAnimationFrame(id)could cancel an internal callback with a colliding id, perturbing internal animation state.
Impact Assessment
requestAnimationFrame/cancelAnimationFrame. The advisory classifies this as an observable discrepancy / information disclosure in Glic; no exploited-in-the-wild status or higher escalation is stated.Changed Functions
| Function | Change | Notes |
|---|---|---|
RegisterFrameCallbackthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified | |
ifthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified | |
forthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified | |
CORE_EXPORTthird_party/blink/renderer/core/dom/frame_request_callback_collection.h |
modified | |
RegisterFrameCallbackthird_party/blink/renderer/core/dom/scripted_animation_controller.cc |
modified | |
ifthird_party/blink/renderer/core/html/html_marquee_element.cc |
modified | |
ifthird_party/blink/renderer/core/paint/timing/paint_timing.cc |
modified |
Files Changed
third_party/blink/renderer/core/editing/markers/document_marker_controller.ccthird_party/blink/renderer/core/dom/scripted_animation_controller.ccthird_party/blink/renderer/modules/xr/xr_frame_provider.ccthird_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.ccthird_party/blink/renderer/core/dom/frame_request_callback_collection.ccthird_party/blink/renderer/core/dom/frame_request_callback_collection.hthird_party/blink/renderer/modules/media_controls/elements/media_control_playback_speed_list_element.ccthird_party/blink/renderer/core/dom/document.hthird_party/blink/renderer/core/dom/scripted_animation_controller_test.ccthird_party/blink/renderer/core/paint/timing/paint_timing.ccthird_party/blink/renderer/core/html/html_marquee_element.ccthird_party/blink/renderer/core/dom/document.ccthird_party/blink/renderer/core/frame/document_loading_rendering_test.ccthird_party/blink/renderer/core/dom/scripted_animation_controller.hthird_party/blink/renderer/core/frame/local_dom_window.cc
Audit Directions
- Shared id/handle spaces across trust boundariesLook for other collections where web-exposed handles and internal handles are drawn from one counter or one container, enabling id-based inference or cross-cancellation.
- Internal reuse of web-exposed APIsAudit engine components that call web-facing scheduling primitives (
RequestAnimationFrame, timers, IDs) directly, and verify their state is not observable or cancellable from script. - Cancellation by id without ownership checkReview
Cancel*/Remove*methods that match solely on a caller-supplied id to ensure they cannot reach objects registered by a different origin/subsystem.
Patch
From 6937cb76b634b8f44877a3e9443b30baddf28c5f Mon Sep 17 00:00:00 2001 From: William Liu <[email protected]> Date: Mon, 27 Jul 2026 11:02:16 -0700 Subject: [PATCH] [blink] Distinguish between Web and internal requestAnimationFrame Fixed: b:506539337 Change-Id: I891083a789417a1d9317b95223091d3c65f30bc1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8144040 Reviewed-by: Adithya Srinivasan <[email protected]> Commit-Queue: William Liu <[email protected]> Reviewed-by: Philip Rogers <[email protected]> Cr-Commit-Position: refs/heads/main@{#1668807} --- diff --git a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc index 7a646f03..2a9b35f 100644 --- a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc +++ b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc @@ -32,13 +32,14 @@ } FrameRequestCallbackCollection::CallbackId id = - callback_collection_.RegisterFrameCallback(callback); + callback_collection_.RegisterFrameCallback( + callback, FrameCallbackType::kWebExposed); begin_frame_provider_->RequestBeginFrame(); return id; } void WorkerAnimationFrameProvider::CancelCallback(int id) { - callback_collection_.CancelFrameCallback(id); + callback_collection_.CancelFrameCallback(id, FrameCallbackType::kWebExposed); } void WorkerAnimationFrameProvider::BeginFrame(const viz::BeginFrameArgs& args) { diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index 460ac2eb..e3961a3 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc @@ -8849,12 +8849,13 @@ return *scripted_animation_controller_; } -int Document::RequestAnimationFrame(FrameCallback* callback) { - return scripted_animation_controller_->RegisterFrameCallback(callback); +int Document::RequestAnimationFrame(FrameCallback* callback, + FrameCallbackType type) { + return scripted_animation_controller_->RegisterFrameCallback(callback, type); } -void Document::CancelAnimationFrame(int id) { - scripted_animation_controller_->CancelFrameCallback(id); +void Document::CancelAnimationFrame(int id, FrameCallbackType type) { + scripted_animation_controller_->CancelFrameCallback(id, type); } DocumentLoader* Document::Loader() const { diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 6e735c85..a6c0c2e0 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h @@ -75,6 +75,7 @@ #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/events/event_path.h" #include "third_party/blink/renderer/core/dom/focus_params.h" +#include "third_party/blink/renderer/core/dom/frame_request_callback_collection.h" #include "third_party/blink/renderer/core/dom/live_node_list_registry.h" #include "third_party/blink/renderer/core/dom/node_list_invalidation_type.h" #include "third_party/blink/renderer/core/dom/qualified_name.h" @@ -1660,8 +1661,8 @@ IsInOutermostMainFrame(); } - int RequestAnimationFrame(FrameCallback*); - void CancelAnimationFrame(int id); + int RequestAnimationFrame(FrameCallback*, FrameCallbackType type); + void CancelAnimationFrame(int id, FrameCallbackType type); ScriptedAnimationController& GetScriptedAnimationController(); diff --git a/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc b/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc index d20b374..1b6f2302 100644 --- a/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc +++ b/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc @@ -16,12 +16,18 @@ : context_(context) {} FrameRequestCallbackCollection::CallbackId -FrameRequestCallbackCollection::RegisterFrameCallback(FrameCallback* callback) { - FrameRequestCallbackCollection::CallbackId id = ++next_callback_id_; +FrameRequestCallbackCollection::RegisterFrameCallback(FrameCallback* callback, + FrameCallbackType type) { + FrameRequestCallbackCollection::CallbackId id = + (type == FrameCallbackType::kInternal) ? ++next_internal_callback_id_ + : ++next_callback_id_; callback->SetIsCancelled(false); callback->SetId(id); - frame_callbacks_.push_back(callback); - + if (type == FrameCallbackType::kInternal) { + internal_frame_callbacks_.push_back(callback); + } else { + frame_callbacks_.push_back(callback); + } DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT("RequestAnimationFrame", inspector_animation_frame_event::Data, context_, id); @@ -30,19 +36,28 @@ return id; } -void FrameRequestCallbackCollection::CancelFrameCallback(CallbackId id) { - for (wtf_size_t i = 0; i < frame_callbacks_.size(); ++i) { - if (frame_callbacks_[i]->Id() == id) { - frame_callbacks_[i]->async_task_context()->Cancel(); +void FrameRequestCallbackCollection::CancelFrameCallback( + CallbackId id, + FrameCallbackType type) { + auto& callbacks = (type == FrameCallbackType::kInternal) + ? internal_frame_callbacks_ + : frame_callbacks_; + auto& callbacks_to_invoke = (type == FrameCallbackType::kInternal) + ? internal_callbacks_to_invoke_ + : callbacks_to_invoke_; + + for (wtf_size_t i = 0; i < callbacks.size(); ++i) { + if (callbacks[i]->Id() == id) { + callbacks[i]->async_task_context()->Cancel(); probe::BreakableLocation(context_, "cancelAnimationFrame"); - frame_callbacks_.EraseAt(i); + callbacks.EraseAt(i); DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT( "CancelAnimationFrame", inspector_animation_frame_event::Data, context_.Get(), id); return; } } - for (const auto& callback : callbacks_to_invoke_) { + for (const auto& callback : callbacks_to_invoke) { if (callback->Id() == id) { callback->async_task_context()->Cancel(); probe::BreakableLocation(context_, "cancelAnimationFrame"); @@ -64,19 +79,32 @@ ExecutionContext::ScopedRequestAnimationFrameStatus scoped_raf_status( context_); - // First, generate a list of callbacks to consider. Callbacks registered from - // this point on are considered only for the "next" frame, not this one. + // First, snapshot both lists. Callbacks registered from this point on are + // considered only for the "next" frame, not this one. DCHECK(callbacks_to_invoke_.empty()); + DCHECK(internal_callbacks_to_invoke_.empty()); swap(callbacks_to_invoke_, frame_callbacks_); + swap(internal_callbacks_to_invoke_, internal_frame_callbacks_); - for (const auto& callback : callbacks_to_invoke_) { + ExecuteFrameCallbacksImpl(callbacks_to_invoke_, high_res_now_ms, + high_res_now_ms_legacy); + ExecuteFrameCallbacksImpl(internal_callbacks_to_invoke_, high_res_now_ms, + high_res_now_ms_legacy); +} + +void FrameRequestCallbackCollection::ExecuteFrameCallbacksImpl( + CallbackList& callbacks_to_invoke, + double high_res_now_ms, + double high_res_now_ms_legacy) { + for (const auto& callback : callbacks_to_invoke) { // When the ExecutionContext is destroyed (e.g. an iframe is detached), // there is no path to perform wrapper tracing for the callbacks. In such a // case, the callback functions may already have been collected by V8 GC. // Since it's possible that a callback function being invoked detaches an // iframe, we need to check the condition for each callback. - if (context_->IsContextDestroyed()) + if (context_->IsContextDestroyed()) { break; + } if (callback->IsCancelled()) { // Another requestAnimationFrame callback already cancelled this one UseCounter::Count(context_, @@ -89,18 +117,21 @@ probe::AsyncTask async_task(context_, callback->async_task_context()); probe::UserCallback probe(context_, "requestAnimationFrame", AtomicString(), true); - if (callback->GetUseLegacyTimeBase()) + if (callback->GetUseLegacyTimeBase()) { callback->Invoke(high_res_now_ms_legacy); - else + } else { callback->Invoke(high_res_now_ms); + } } - callbacks_to_invoke_.clear(); + callbacks_to_invoke.clear(); } void FrameRequestCallbackCollection::Trace(Visitor* visitor) const { visitor->Trace(frame_callbacks_); visitor->Trace(callbacks_to_invoke_); + visitor->Trace(internal_frame_callbacks_); + visitor->Trace(internal_callbacks_to_invoke_); visitor->Trace(context_); } diff --git a/third_party/blink/renderer/core/dom/frame_request_callback_collection.h b/third_party/blink/renderer/core/dom/frame_request_callback_collection.h index 2120faa7..2a42b02 100644 --- a/third_party/blink/renderer/core/dom/frame_request_callback_collection.h +++ b/third_party/blink/renderer/core/dom/frame_request_callback_collection.h @@ -66,6 +66,11 @@ Member<V8FrameRequestCallback> callback_; }; +enum class FrameCallbackType { + kWebExposed, + kInternal, +}; + class CORE_EXPORT FrameRequestCallbackCollection final : public NameClient { DISALLOW_NEW(); @@ -74,13 +79,14 @@ using CallbackId = int; - CallbackId RegisterFrameCallback(FrameCallback*); - void CancelFrameCallback(CallbackId); + CallbackId RegisterFrameCallback(FrameCallback*, FrameCallbackType type); + void CancelFrameCallback(CallbackId, FrameCallbackType type); void ExecuteFrameCallbacks(double high_res_now_ms, double high_res_now_ms_legacy); - bool HasFrameCallback() const { return frame_callbacks_.size(); } - bool IsEmpty() const { return !HasFrameCallback(); } + bool HasFrameCallback() const { + return frame_callbacks_.size() || internal_frame_callbacks_.size(); + } void Trace(Visitor*) const; const char* GetHumanReadableName() const override { @@ -90,12 +96,21 @@ private: using CallbackList = HeapVector<Member<FrameCallback>>; + void ExecuteFrameCallbacksImpl(CallbackList& callbacks_to_invoke, + double high_res_now_ms, + double high_res_now_ms_legacy); + CallbackList frame_callbacks_; // only non-empty while inside ExecuteCallbacks. CallbackList callbacks_to_invoke_; CallbackId next_callback_id_ = 0; + CallbackList internal_frame_callbacks_; + CallbackList internal_callbacks_to_invoke_; + + CallbackId next_internal_callback_id_ = 0; + Member<ExecutionContext> context_; }; diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc index 3a152ad..3eaee73 100644 --- a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc +++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc @@ -98,18 +98,20 @@ } ScriptedAnimationController::CallbackId -ScriptedAnimationController::RegisterFrameCallback(FrameCallback* callback) { +ScriptedAnimationController::RegisterFrameCallback(FrameCallback* callback, + FrameCallbackType type) { // If we no longer have a context, there is no need to register the callback. if (!GetExecutionContext()) { return 0; } - CallbackId id = callback_collection_.RegisterFrameCallback(callback); + CallbackId id = callback_collection_.RegisterFrameCallback(callback, type); ScheduleAnimationIfNeeded(cc::BeginMainFrameReason::kRAF); return id; } -void ScriptedAnimationController::CancelFrameCallback(CallbackId id) { - callback_collection_.CancelFrameCallback(id); +void ScriptedAnimationController::CancelFrameCallback(CallbackId id, + FrameCallbackType type) { + callback_collection_.CancelFrameCallback(id, type); } bool ScriptedAnimationController::HasFrameCallback() const { diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.h b/third_party/blink/renderer/core/dom/scripted_animation_controller.h index 4264fe3..ce1fdec 100644 --- a/third_party/blink/renderer/core/dom/scripted_animation_controller.h +++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.h @@ -71,8 +71,8 @@ // Animation frame callbacks are used for requestAnimationFrame(). typedef int CallbackId; - CallbackId RegisterFrameCallback(FrameCallback*); - void CancelFrameCallback(CallbackId); + CallbackId RegisterFrameCallback(FrameCallback*, FrameCallbackType type); + void CancelFrameCallback(CallbackId, FrameCallbackType type); // Returns true if any callback is currently registered. bool HasFrameCallback() const; diff --git a/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc b/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc index 4d85b26..91c38c3 100644 --- a/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc +++ b/third_party/blink/renderer/core/editing/markers/document_marker_controller.cc @@ -1464,7 +1464,7 @@ glic_marker_animation_start_ = std::nullopt; glic_animation_state_ = GlicAnimationState::kRunning; auto* callback = MakeGarbageCollected<RequestAnimationFrameCallback>(this); - document_->RequestAnimationFrame(callback); + document_->RequestAnimationFrame(callback, FrameCallbackType::kInternal); } void DocumentMarkerController::ContinueGlicMarkerAnimation( @@ -1495,7 +1495,7 @@ } auto* callback = MakeGarbageCollected<RequestAnimationFrameCallback>(this); - document_->RequestAnimationFrame(callback); + document_->RequestAnimationFrame(callback, FrameCallbackType::kInternal); } bool DocumentMarkerController::UpdateGlicMarkerOpacity( diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc index 0125fed7de..511fce6 100644 --- a/third_party/blink/renderer/core/frame/local_dom_window.cc +++ b/third_party/blink/renderer/core/frame/local_dom_window.cc @@ -197,7 +197,8 @@ auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback); frame_callback->SetUseLegacyTimeBase(legacy); - return document->RequestAnimationFrame(frame_callback); + return document->RequestAnimationFrame(frame_callback, + FrameCallbackType::kWebExposed); } } // namespace @@ -2217,7 +2218,7 @@ } void LocalDOMWindow::cancelAnimationFrame(int id) { - document()->CancelAnimationFrame(id); + document()->CancelAnimationFrame(id, FrameCallbackType::kWebExposed); } bool LocalDOMWindow::originAgentCluster() const { diff --git a/third_party/blink/renderer/core/html/html_marquee_element.cc b/third_party/blink/renderer/core/html/html_marquee_element.cc index f0f96c36..3af928d 100644 --- a/third_party/blink/renderer/core/html/html_marquee_element.cc +++ b/third_party/blink/renderer/core/html/html_marquee_element.cc @@ -195,12 +195,14 @@ RequestAnimationFrameCallback* callback = MakeGarbageCollected<RequestAnimationFrameCallback>(this); - continue_callback_request_id_ = GetDocument().RequestAnimationFrame(callback); + continue_callback_request_id_ = GetDocument().RequestAnimationFrame( + callback, FrameCallbackType::kInternal); } void HTMLMarqueeElement::stop() { if (continue_callback_request_id_) { - GetDocument().CancelAnimationFrame(continue_callback_request_id_); + GetDocument().CancelAnimationFrame(continue_callback_request_id_, + FrameCallbackType::kInternal); continue_callback_request_id_ = 0; return; } diff --git a/third_party/blink/renderer/core/paint/timing/paint_timing.cc b/third_party/blink/renderer/core/paint/timing/paint_timing.cc index 9612805a..5405f037 100644 --- a/third_party/blink/renderer/core/paint/timing/paint_timing.cc +++ b/third_party/blink/renderer/core/paint/timing/paint_timing.cc @@ -93,7 +93,7 @@ if (auto* frame = paint_timing_->GetFrame()) { if (auto* document = frame->GetDocument()) { - document->RequestAnimationFrame(this); + document->RequestAnimationFrame(this, FrameCallbackType::kInternal); } } } @@ -772,7 +772,8 @@ // Cancel if there is already a registered callback. if (raf_after_bfcache_restore_measurement_callback_id_) { document->CancelAnimationFrame( - raf_after_bfcache_restore_measurement_callback_id_); + raf_after_bfcache_restore_measurement_callback_id_, + FrameCallbackType::kInternal); raf_after_bfcache_restore_measurement_callback_id_ = 0; } @@ -780,7 +781,8 @@ document->RequestAnimationFrame( MakeGarbageCollected< RecodingTimeAfterBackForwardCacheRestoreFrameCallback>(this, - index)); + index), + FrameCallbackType::kInternal); ... (file diff truncated)
Regression Test / PoC
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
index c4001c15..b5473fe 100644
--- a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
+++ b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
@@ -184,7 +184,8 @@
event->SetTarget(&GetDocument());
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
Controller().EnqueueTask(observer.CreateTask(2));
EXPECT_EQ(0u, observer.Order().size());
@@ -199,19 +200,22 @@
TaskOrderObserver observer;
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
- Controller().CancelFrameCallback(1);
+ Controller().CancelFrameCallback(1, FrameCallbackType::kWebExposed);
EXPECT_FALSE(Controller().HasFrameCallback());
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
- Controller().CancelFrameCallback(1);
+ Controller().CancelFrameCallback(1, FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
// Servicing the scripted animations should call the remaining callback and
@@ -233,7 +237,8 @@
*ran_callback = true;
},
WrapPersistent(&Controller()),
- blink::subtle::UnretainedException(&ran_callback))));
+ blink::subtle::UnretainedException(&ran_callback))),
+ FrameCallbackType::kWebExposed);
PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
{{Controller(), false}});
@@ -242,4 +247,52 @@
EXPECT_FALSE(Controller().GetExecutionContext()->IsInRequestAnimationFrame());
}
+TEST_F(ScriptedAnimationControllerTest, TestInternalCallbackIsolation) {
+ TaskOrderObserver observer;
+
+ // Web-exposed callback registration gets ID 1 in web pool.
+ int web_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
+ EXPECT_EQ(1, web_id);
+
+ // Internal callback registration gets independent ID 1 in internal pool.
+ int internal_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kInternal);
+ EXPECT_EQ(1, internal_id);
+
+ // Canceling internal callback with FrameCallbackType::kInternal should cancel
+ // internal without affecting web callback.
+ Controller().CancelFrameCallback(internal_id, FrameCallbackType::kInternal);
+
+ // Servicing scripted animations should run only the web callback.
+ PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
+ {{Controller(), false}});
+ EXPECT_EQ(1u, observer.Order().size());
+ EXPECT_EQ(1, observer.Order()[0]);
+}
+
+TEST_F(ScriptedAnimationControllerTest,
+ TestCancelWebCallbackDoesNotCancelInternalCallback) {
+ TaskOrderObserver observer;
+
+ int web_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
+ Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kInternal);
+
+ // CancelFrameCallback with kWebExposed cancels web callback without
+ // affecting internal callback.
+ Controller().CancelFrameCallback(web_id, FrameCallbackType::kWebExposed);
+
+ // Servicing scripted animations should run only the internal callback.
+ PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
+ {{Controller(), false}});
+ EXPECT_EQ(1u, observer.Order().size());
+ EXPECT_EQ(2, observer.Order()[0]);
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc b/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
index 786c949..223d8dc4 100644
--- a/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
+++ b/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
@@ -343,7 +343,8 @@
// Frame while the child frame still has pending sheets.
auto* frame1_callback = MakeGarbageCollected<CheckRafCallback>();
- child_frame->contentDocument()->RequestAnimationFrame(frame1_callback);
+ child_frame->contentDocument()->RequestAnimationFrame(
+ frame1_callback, FrameCallbackType::kWebExposed);
auto frame1 = Compositor().BeginFrame();
EXPECT_FALSE(frame1_callback->WasCalled());
EXPECT_TRUE(frame1.Contains(SimCanvas::kRect, "red"));
@@ -356,7 +357,8 @@
// Frame with all lifecycle updates enabled.
auto* frame2_callback = MakeGarbageCollected<CheckRafCallback>();
- child_frame->contentDocument()->RequestAnimationFrame(frame2_callback);
+ child_frame->contentDocument()->RequestAnimationFrame(
+ frame2_callback, FrameCallbackType::kWebExposed);
auto frame2 = Compositor().BeginFrame();
EXPECT_TRUE(frame1_callback->WasCalled());
EXPECT_TRUE(frame2_callback->WasCalled());