Repository: electron/electron
Pull request: fix: out-of-scope Local handle in node::CallbackScope

#### Description of Change

- Fixes a minor bug in `gin_helper::internal::CallMethodWithArgs()` that created a `node::CallbackScope` with an out-of-scope resource handle. This comes from [this suggestion](https://github.com/electron/electron/pull/40576#discussion_r1400808904) to add a `HandleScope` before creating the `CallbackScope` in an early draft of #40576 when the `CallbackScope` had a shorter lifespan; but in the version that got merged, the callback scope outlives the handle scope.

- Small refinement to #42297: since we unconditionally return false if there's no Node environment, the CallbackScope doesn't need to be heap-allocated anymore.

- I also wrapped everything in an EscapeableHandleScope to ensure that we don't leak either the CallbackScope resource object or any side handles created by `MakeCallback()`, since its docs say it "may create handles on [its] own, so run [it] inside a HandleScope".

CC @codebytere, @deepak1556 

#### Checklist

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [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: none.

Changed files:
filename: shell/common/gin_helper/event_emitter_caller.cc
patch: @@ -13,15 +13,14 @@ v8::Local<v8::Value> CallMethodWithArgs(v8::Isolate* isolate,
                                         v8::Local<v8::Object> obj,
                                         const char* method,
                                         ValueVector* args) {
-  // An active node::Environment is required for node::MakeCallback.
-  std::unique_ptr<node::CallbackScope> callback_scope;
-  if (node::Environment::GetCurrent(isolate)) {
-    v8::HandleScope handle_scope(isolate);
-    callback_scope = std::make_unique<node::CallbackScope>(
-        isolate, v8::Object::New(isolate), node::async_context{0, 0});
-  } else {
-    return v8::Boolean::New(isolate, false);
-  }
+  v8::EscapableHandleScope handle_scope{isolate};
+
+  // CallbackScope and MakeCallback both require an active node::Environment
+  if (!node::Environment::GetCurrent(isolate))
+    return handle_scope.Escape(v8::Boolean::New(isolate, false));
+
+  node::CallbackScope callback_scope{isolate, v8::Object::New(isolate),
+                                     node::async_context{0, 0}};
 
   // Perform microtask checkpoint after running JavaScript.
   gin_helper::MicrotasksScope microtasks_scope{
@@ -36,11 +35,10 @@ v8::Local<v8::Value> CallMethodWithArgs(v8::Isolate* isolate,
   // of MakeCallback will be empty and therefore ToLocal will be false, in this
   // case we need to return "false" as that indicates that the event emitter did
   // not handle the event
-  v8::Local<v8::Value> localRet;
-  if (ret.ToLocal(&localRet))
-    return localRet;
+  if (v8::Local<v8::Value> localRet; ret.ToLocal(&localRet))
+    return handle_scope.Escape(localRet);
 
-  return v8::Boolean::New(isolate, false);
+  return handle_scope.Escape(v8::Boolean::New(isolate, false));
 }
 
 }  // namespace gin_helper::internal