Repository: electron/electron
Pull request: fix: add missing HandleScope in contentTracing.getTraceBufferUsage()

#### Description of Change

The `OnTraceBufferUsageAvailable` callback creates V8 handles via `Dictionary::CreateEmpty()` before `promise.Resolve()` enters its `SettleScope` (which provides a `HandleScope`). When the callback fires asynchronously from a Mojo response (i.e., when a trace session is active), there is no `HandleScope` on the stack, causing a fatal V8 error: "Cannot create a handle without a HandleScope".

Add an explicit `v8::HandleScope` at the top of the callback, matching the pattern used by the other contentTracing APIs, which resolve their promises through `SettleScope` or the static `ResolvePromise` helper.

#### Checklist

- [x] PR description included
- [x] I have built and tested this PR
- [x] `npm test` passes
- [x] tests are [added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed a crash when calling `contentTracing.getTraceBufferUsage()` while a trace session is active.


Changed files:
filename: shell/browser/api/electron_api_content_tracing.cc
patch: @@ -151,7 +151,10 @@ void OnTraceBufferUsageAvailable(
     gin_helper::Promise<gin_helper::Dictionary> promise,
     float percent_full,
     size_t approximate_count) {
-  auto dict = gin_helper::Dictionary::CreateEmpty(promise.isolate());
+  v8::Isolate* isolate = promise.isolate();
+  v8::HandleScope handle_scope(isolate);
+
+  auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
   dict.Set("percentage", percent_full);
   dict.Set("value", approximate_count);
 

filename: spec/api-content-tracing-spec.ts
patch: @@ -132,6 +132,36 @@ ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== '
     });
   });
 
+  describe('getTraceBufferUsage', function () {
+    this.timeout(10e3);
+
+    it('does not crash and returns valid usage data', async () => {
+      await app.whenReady();
+      await contentTracing.startRecording({
+        categoryFilter: '*',
+        traceOptions: 'record-until-full'
+      });
+
+      // Yield to the event loop so the JS HandleScope from this tick is gone.
+      // When the Mojo response arrives it fires OnTraceBufferUsageAvailable
+      // as a plain Chromium task — if that callback lacks its own HandleScope
+      // the process will crash with "Cannot create a handle without a HandleScope".
+      const result = await contentTracing.getTraceBufferUsage();
+
+      expect(result).to.have.property('percentage').that.is.a('number');
+      expect(result).to.have.property('value').that.is.a('number');
+
+      await contentTracing.stopRecording();
+    });
+
+    it('returns zero usage when no trace is active', async () => {
+      await app.whenReady();
+      const result = await contentTracing.getTraceBufferUsage();
+      expect(result).to.have.property('percentage').that.is.a('number');
+      expect(result.percentage).to.equal(0);
+    });
+  });
+
   describe('captured events', () => {
     it('include V8 samples from the main process', async function () {
       this.timeout(60000);