CVE-2026-118334
Overview
Background
- ImageCapture
- A Blink Oilpan
GarbageCollectedobject (third_party/blink/renderer/modules/imagecapture/image_capture.{h,cc}) exposing theImageCaptureweb API. It owns the helper that actually grabs frames. - ImageCaptureFrameGrabber
- A non-Oilpan helper (subclasses
MediaStreamVideoSink) owned byImageCapture. It is created/used/destroyed on the main thread and relies on~MediaStreamVideoSink()to disconnect from the track. Before the patch it was held bystd::unique_ptrinImageCapture::frame_grabber_. - SingleShotFrameHandler
- Inner handler whose
OnVideoFrameOnIOThread()runs on the IO thread; itPostCrossThreadTasksImageCaptureFrameGrabber::OnVideoFrame()to the main-thread task runner, bound with abase::WeakPtr<ImageCaptureFrameGrabber>. - base::WeakPtr
- Validates the pointer only at callback entry; it does not keep the pointee alive while the callback is executing on the stack.
- EstablishGpuChannelSync()
- Called transitively from
OnVideoFrame()viaGetRasterContextProvider(); it can synchronously run queued GPU-channel callbacks inline on the main thread, which can allocate on the Oilpan heap and drive lazy sweeping.
Root Cause Analysis
ImageCapture::grabFrame() starts a frame grab without any Persistent<ImageCapture> keeping the Oilpan owner reachable, so if JavaScript drops its reference to the ImageCapture wrapper, ImageCapture becomes garbage-collectable even while an OnVideoFrame() task is pending/executing. OnVideoFrame() was posted bound to a base::WeakPtr<ImageCaptureFrameGrabber>, which only checks liveness at entry. The lifetime invariant violated is that frame_grabber_ (owned via unique_ptr by ImageCapture) must outlive the running OnVideoFrame() call. Inside OnVideoFrame() (image_capture_frame_grabber.cc), GetRasterContextProvider() → SharedGpuContext::CreateContextProviderIfNeeded() → viz::Gpu::EstablishGpuChannelSync() can synchronously flush previously queued EstablishGpuChannel callbacks (e.g. from a prior navigator.gpu.requestAdapter). Such a callback resolves/rejects a promise, allocates on the Oilpan heap, and triggers synchronous lazy sweeping, which runs ~ImageCapture() inline — deleting the unique_ptr-owned frame_grabber_ while OnVideoFrame() is still on the stack. When GetRasterContextProvider() returns, this is dangling: a same-thread, re-entrant use-after-free.
The patch converts ImageCaptureFrameGrabber into a RefCounted<ImageCaptureFrameGrabber> (adding the base class, a friend class RefCounted<>, and moving ~ImageCaptureFrameGrabber() to private), changes ImageCapture::frame_grabber_ from std::unique_ptr to scoped_refptr, allocates it with base::MakeRefCounted, and — crucially — adds scoped_refptr<ImageCaptureFrameGrabber> keep_alive(this); at the top of OnVideoFrame(). That stack reference pins the grabber alive across the GPU-context call even if ~ImageCapture() runs mid-method and drops frame_grabber_. The commit explains why the simpler fixes do not work: WrapPersistent cannot be applied to a non-Oilpan class, and routing through ImageCapture with a CrossThreadHandle would break cancellation on ContextDestroyed(), which depends on resetting frame_grabber_ and invalidating its WeakPtr.
WeakPtr-bound main-thread callback (OnVideoFrame()) made a nested call that could synchronously garbage-collect its own non-Oilpan owner, deleting this mid-execution — because WeakPtr guarantees liveness only at entry, not for the duration of the call.Attack Path
- Prime GPU callback
Script issues a
navigator.gpu.requestAdapter()(or similar) so anEstablishGpuChannelcallback is queued, ready to run inline later. - Start frame grab
Script calls
ImageCapture.grabFrame()on a live track;GrabFrame()connects aSingleShotFrameHandlerto the media stream track. NoPersistent<ImageCapture>is held. - Drop the owner
Script drops all references to the
ImageCapturewrapper soImageCaptureis unreachable in Oilpan (but not yet swept). - Frame delivery
A video frame arrives on the IO thread;
OnVideoFrameOnIOThread()postsOnVideoFrame()to the main thread.WeakPtris still valid at entry. - Synchronous GC mid-call
OnVideoFrame()→GetRasterContextProvider()→EstablishGpuChannelSync()flushes the queued GPU callback, which resolves a promise, allocates on the Oilpan heap, and triggers lazy sweeping →~ImageCapture()freesframe_grabber_. - Primitive
Control returns into
OnVideoFrame()withthispointing at freed memory: a controllable same-thread UAF in the renderer (further escalation to RCE inferred).
Impact Assessment
ImageCapture.grabFrame() API combined with WebGPU adapter requests to seed a synchronously-flushable GPU-channel callback. No elevated privilege is required beyond camera/track access typical for the ImageCapture API. The diff substantiates a precise same-thread re-entrant UAF of the ImageCaptureFrameGrabber while OnVideoFrame() executes; the strength is a genuine UAF (not merely a crash), and heap grooming of the freed object could yield memory corruption and potential code execution (inferred). The advisory’s “Use after free in ImageCapture” matches the diff exactly.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/imagecapture/image_capture.cc |
modified | |
SkImagethird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h |
modified | |
ImageCaptureFrameGrabberthird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h |
modified | |
ScopedPromiseResolverthird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h |
modified | |
SingleShotFrameHandlerthird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h |
modified | |
TEST_Fthird_party/blink/renderer/modules/imagecapture/image_capture_test.cc |
modified | |
ifthird_party/blink/renderer/modules/imagecapture/image_capture_test.cc |
modified |
Files Changed
third_party/blink/renderer/modules/imagecapture/image_capture_test.ccthird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.hthird_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.ccthird_party/blink/renderer/modules/imagecapture/image_capture.ccthird_party/blink/renderer/modules/imagecapture/image_capture.h
Audit Directions
- WeakPtr-bound cross-thread callbacks into GPU/context setupLook for other main-thread callbacks bound by
base::WeakPtrthat callGetRasterContextProvider()/SharedGpuContext/EstablishGpuChannelSync(); any that can synchronously run promise resolution risk re-entrant self-destruction. - Non-Oilpan helpers owned by GarbageCollected objectsAudit other Blink modules where a
GarbageCollectedobject owns a non-Oilpan helper byunique_ptrand posts tasks referencing the helper;WeakPtrentry-only checks do not protect against sweeping the owner mid-call. - Synchronous lazy sweeping triggersEnumerate code paths that allocate on the Oilpan heap while another object is on the stack and unreachable, especially where GPU-channel callbacks resolve promises inline.
- ContextDestroyed cancellation assumptionsVerify any refactor to
CrossThreadHandle/HeapMojoRemoterouting preserves theContextDestroyed()-based cancellation this class relies on.
Patch
From adb8ef84f8fe2ae3fe8b3495d3e84626d17acf8a Mon Sep 17 00:00:00 2001
From: Colin Blundell <blundell@chromium.org>
Date: Mon, 19 Oct 2026 13:21:25 -0700
Subject: [PATCH] blink: Fix ImageCaptureFrameGrabber UAF in OnVideoFrame
ImageCapture is an Oilpan GarbageCollected object that owns a
non-Oilpan ImageCaptureFrameGrabber helper [1], which is constructed,
used, and destroyed exclusively on the main thread. In grabFrame() [2],
ImageCaptureFrameGrabber::GrabFrame() connects a SingleShotFrameHandler
to the media stream track [3]. When a frame arrives on the IO thread,
SingleShotFrameHandler::OnVideoFrameOnIOThread() posts
ImageCaptureFrameGrabber::OnVideoFrame() back to the main thread task
runner bound with a WeakPtr<ImageCaptureFrameGrabber> [4] (so the
WeakPtr is only ever dereferenced and invalidated on the main thread).
Because nothing holds a Persistent<ImageCapture> while grabFrame() is
pending, ImageCapture becomes unreachable in Oilpan if JavaScript drops
its reference to the ImageCapture wrapper.
This leads to a same-thread re-entrant UAF on the main thread:
1. ImageCaptureFrameGrabber::OnVideoFrame() starts on the main thread
[5] before Oilpan has swept the dead ImageCapture. base::WeakPtr
only checks validity at callback entry; it does not keep
ImageCaptureFrameGrabber alive while OnVideoFrame() is on the stack.
2. OnVideoFrame() calls GetRasterContextProvider() [5] ->
SharedGpuContext::ContextProviderWrapper() ->
SharedGpuContext::CreateContextProviderIfNeeded() [6] ->
RendererBlinkPlatformImpl::
CreateSharedOffscreenGraphicsContext3DProvider() ->
viz::Gpu::EstablishGpuChannelSync() [7] on the main thread.
3. EstablishGpuChannelSync() calls EstablishRequest::FinishOnMain() ->
Gpu::OnEstablishedGpuChannel() -> Gpu::RunEstablishCallbacks() [8],
which synchronously runs any queued EstablishGpuChannel callbacks
inline on the main thread before returning.
4. A queued callback (e.g. from an earlier navigator.gpu.requestAdapter
call [9]) runs inline on the main thread and resolves/rejects its
promise, which allocates on the Oilpan heap and triggers Oilpan lazy
sweeping synchronously on the main thread.
5. Lazy sweeping runs ~ImageCapture() inline on the main thread,
deleting frame_grabber_ while OnVideoFrame() is still on the stack.
When GetRasterContextProvider() returns to OnVideoFrame(), "this" is
dangling [5].
We cannot simply bind OnVideoFrame() with WrapPersistent like
getPhotoCapabilities, getPhotoSettings, and takePhoto do:
- ImageCaptureFrameGrabber is not an Oilpan GarbageCollected class (it
subclasses MediaStreamVideoSink and relies on ~MediaStreamVideoSink()
to disconnect from the track), so WrapPersistent cannot be used on it.
- Routing OnVideoFrame() through ImageCapture with a CrossThreadHandle
would break cancellation on ContextDestroyed(): whereas
HeapMojoRemote automatically drops pending callbacks on
ContextDestroyed(), OnVideoFrame() is posted from the IO thread via
PostCrossThreadTask to a raw SingleThreadTaskRunner and relies on
ImageCapture::ContextDestroyed() resetting frame_grabber_ (and
invalidating its WeakPtr) to cancel any pending or already-posted
frame grab.
Instead, make ImageCaptureFrameGrabber RefCounted (single-threaded on
the main thread, shared only between ImageCapture and an actively
executing OnVideoFrame() call) and hold a local
scoped_refptr<ImageCaptureFrameGrabber> keep_alive on the stack across
OnVideoFrame().
Note: The bug report's suggested patch also included a test for
ImageCapture::ContextDestroyed() running synchronously during
OnVideoFrame(), but auditing the EstablishGpuChannel() callers shows
that none of them can synchronously run JavaScript or detach a frame
(promise resolution uses V8DoNotRunMicrotasksScope), so synchronous
execution context destruction during OnVideoFrame() is not possible.
[1] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/imagecapture/image_capture.h;l=180
[2] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/imagecapture/image_capture.cc;l=1689-1692
[3] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc;l=156-165
[4] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc;l=111-116
[5] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc;l=167-208
[6] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc;l=154
[7] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:services/viz/public/cpp/gpu/gpu.cc;l=392-394
[8] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:services/viz/public/cpp/gpu/gpu.cc;l=462-488
[9] https://source.chromium.org/chromium/chromium/src/+/cbf97aa00afa4f6dedcababb7a71efd999986e4b:third_party/blink/renderer/modules/webgpu/gpu.cc;l=299-302
Patch and regression test authored by OpenAI Codex Security (kylebot).
Bug: 574027204
Change-Id: Ib937010017d6601e64d38838f62f14b3aa1b4497
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8588402
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Colin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1722274}
---
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.cc b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
index 4d35b56..d193a30 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
@@ -1677,7 +1677,7 @@
// Create |m_frameGrabber| the first time.
if (!frame_grabber_) {
- frame_grabber_ = std::make_unique<ImageCaptureFrameGrabber>();
+ frame_grabber_ = base::MakeRefCounted<ImageCaptureFrameGrabber>();
}
if (!frame_grabber_) {
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.h b/third_party/blink/renderer/modules/imagecapture/image_capture.h
index e477820e..2ce25e4 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.h
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.h
@@ -5,9 +5,9 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_IMAGECAPTURE_IMAGE_CAPTURE_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_IMAGECAPTURE_IMAGE_CAPTURE_H_
-#include <memory>
#include <optional>
+#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "media/capture/mojom/image_capture.mojom-blink.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
@@ -177,7 +177,7 @@
MediaTrackConstraintSetType) const;
Member<MediaStreamTrack> stream_track_;
- std::unique_ptr<ImageCaptureFrameGrabber> frame_grabber_;
+ scoped_refptr<ImageCaptureFrameGrabber> frame_grabber_;
HeapMojoRemote<media::mojom::blink::ImageCapture> service_;
// Whether the user has granted permission for the user to control camera PTZ.
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc b/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc
index 7a28460a..22974cf8 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.cc
@@ -169,6 +169,13 @@
ScriptPromiseResolver<ImageBitmap>* resolver) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ // Hold a reference across `OnVideoFrame()` (`WeakPtr` only checks validity at
+ // callback entry). Creating or accessing the GPU context below can
+ // synchronously flush pending GPU channel callbacks, which may allocate on
+ // the Oilpan heap and trigger lazy sweeping of an unreachable `ImageCapture`
+ // owner (`~ImageCapture()`), dropping `ImageCapture::frame_grabber_`.
+ scoped_refptr<ImageCaptureFrameGrabber> keep_alive(this);
+
auto required_provider_info = CreateSnapshotProviderInfoForVideoFrame(*frame);
if (!cached_draw_info_ ||
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h b/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h
index 5185908..23c5eee5 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h
@@ -17,6 +17,7 @@
#include "third_party/blink/renderer/platform/graphics/canvas_non_2d_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/canvas_snapshot_info.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
+#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
class SkImage;
@@ -30,7 +31,9 @@
// called. This method receives an incoming media::VideoFrame on a background
// thread and converts it into the appropriate SkBitmap which is sent back to
// OnSkBitmap(). This class is single threaded throughout.
-class ImageCaptureFrameGrabber final : public MediaStreamVideoSink {
+class ImageCaptureFrameGrabber final
+ : public RefCounted<ImageCaptureFrameGrabber>,
+ public MediaStreamVideoSink {
public:
// Helper class to ensure an ImageBitmap promise is always resolved.
class ScopedPromiseResolver;
@@ -40,14 +43,14 @@
ImageCaptureFrameGrabber(const ImageCaptureFrameGrabber&) = delete;
ImageCaptureFrameGrabber& operator=(const ImageCaptureFrameGrabber&) = delete;
- ~ImageCaptureFrameGrabber() override;
-
void GrabFrame(MediaStreamComponent* component,
ScriptPromiseResolver<ImageBitmap>* resolver,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
base::TimeDelta timeout);
private:
+ friend class RefCounted<ImageCaptureFrameGrabber>;
+ ~ImageCaptureFrameGrabber() override;
// Internal class to receive, convert and forward one frame.
class SingleShotFrameHandler;
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc b/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
index 6e22618..061a541a 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
@@ -5,11 +5,13 @@
#include "third_party/blink/renderer/modules/imagecapture/image_capture.h"
#include "base/functional/callback_helpers.h"
+#include "base/test/bind.h"
#include "base/time/time.h"
#include "media/base/video_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_sink.h"
+#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
#include "third_party/blink/public/web/web_heap.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -36,6 +38,8 @@
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_video_capturer_source.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
@@ -1611,6 +1615,38 @@
EXPECT_TRUE(tester.IsFulfilled());
}
+TEST_F(ImageCaptureTest, GrabFrameSurvivesOwnerCollectionDuringConversion) {
+ V8TestingScope scope;
+ SetupTrackMocks(scope);
+ track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
+ track_->setEnabled(true);
+ track_->SetMuted(false);
+
+ WeakPersistent<ImageCapture> weak_image_capture(image_capture_.Get());
+ SharedGpuContext::Reset();
+ base::ScopedClosureRunner reset_context(
+ base::BindOnce(&SharedGpuContext::Reset));
+ SharedGpuContext::SetContextProviderFactoryForTesting(
+ base::BindLambdaForTesting(
+ [&]() -> std::unique_ptr<WebGraphicsContext3DProvider> {
+ // GPU channel initialization can run callbacks which trigger GC.
+ if (image_capture_) {
+ image_capture_.Clear();
+ WebHeap::CollectAllGarbageForTesting();
+ EXPECT_FALSE(weak_image_capture);
+ }
+ return nullptr;
+ }));
+
+ auto result = image_capture_->grabFrame(scope.GetScriptState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), result);
+ tester.WaitUntilSettled();
+ EXPECT_FALSE(weak_image_capture);
+ EXPECT_TRUE(tester.IsFulfilled());
+ EXPECT_EQ(0u, platform_track_->CountSinks());
+}
+
TEST_F(ImageCaptureTest, GrabFrameOfMutedTrackIsFulfilled) {
V8TestingScope scope;
SetupTrackMocks(scope);
Regression Test / PoC
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc b/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
index 6e22618..061a541a 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture_test.cc
@@ -5,11 +5,13 @@
#include "third_party/blink/renderer/modules/imagecapture/image_capture.h"
#include "base/functional/callback_helpers.h"
+#include "base/test/bind.h"
#include "base/time/time.h"
#include "media/base/video_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_sink.h"
+#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
#include "third_party/blink/public/web/web_heap.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -36,6 +38,8 @@
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_video_capturer_source.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
@@ -1611,6 +1615,38 @@
EXPECT_TRUE(tester.IsFulfilled());
}
+TEST_F(ImageCaptureTest, GrabFrameSurvivesOwnerCollectionDuringConversion) {
+ V8TestingScope scope;
+ SetupTrackMocks(scope);
+ track_->SetReadyState(V8MediaStreamTrackState::Enum::kLive);
+ track_->setEnabled(true);
+ track_->SetMuted(false);
+
+ WeakPersistent<ImageCapture> weak_image_capture(image_capture_.Get());
+ SharedGpuContext::Reset();
+ base::ScopedClosureRunner reset_context(
+ base::BindOnce(&SharedGpuContext::Reset));
+ SharedGpuContext::SetContextProviderFactoryForTesting(
+ base::BindLambdaForTesting(
+ [&]() -> std::unique_ptr<WebGraphicsContext3DProvider> {
+ // GPU channel initialization can run callbacks which trigger GC.
+ if (image_capture_) {
+ image_capture_.Clear();
+ WebHeap::CollectAllGarbageForTesting();
+ EXPECT_FALSE(weak_image_capture);
+ }
+ return nullptr;
+ }));
+
+ auto result = image_capture_->grabFrame(scope.GetScriptState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), result);
+ tester.WaitUntilSettled();
+ EXPECT_FALSE(weak_image_capture);
+ EXPECT_TRUE(tester.IsFulfilled());
+ EXPECT_EQ(0u, platform_track_->CountSinks());
+}
+
TEST_F(ImageCaptureTest, GrabFrameOfMutedTrackIsFulfilled) {
V8TestingScope scope;
SetupTrackMocks(scope);