electron/shell/common/gin_helper/callback.h
Calvin 37639b5400
refactor: replace gin_helper::MicrotasksScope with v8::MicrotasksScope (#46963)
* Remove microtasks_scope.h and microtasks_scope.cc

* Use v8::MicrotasksScope when ignoring browser checkpoint

These call always skip the browser checkpoint, so they are equivalent to using v8::MicrotasksScope directly (modulo the optional wrapper behavior).

* Remove MicrotasksScope from node_bindings.cc

This code seems contradictory: it explicitly specifies "do not run microtasks" yet runs a microtask checkpoint in the browser process.

Looking at its history, it [was introduced][1] with the intention to not run microtasks, but a [subtle C++ language behavior][2] caused it to do the opposite later in the same roll. Since the original intention was to not run microtasks, and since that is also the simplest explanation, we can assume `ignore_browser_checkpoint` should be true and migrate this to `v8::MicrotasksScope` as it is equivalent (modulo the optional wrapper behavior).

[1]: a4ea80dd47 (diff-efe58cf03c97028f37f801db044d396a5f428686da6595d2c692f1c052bbd09c)
[2]: https://github.com/electron/electron/pull/43185

* Migrate gin_helper/promise.h and gin_helper/promise.cc to v8::MicrotasksScope

Restores the [original][1] behavior of running the microtask checkpoint at destruction, but preserves the behavior of running microtasks in the browser process. This had last changed in the migration to gin_helper::MicroTasks.

[1]: https://github.com/electron/electron/pull/16401
2025-05-07 14:10:34 -05:00

156 lines
6 KiB
C++

// Copyright (c) 2019 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_CALLBACK_H_
#define ELECTRON_SHELL_COMMON_GIN_HELPER_CALLBACK_H_
#include <array>
#include <utility>
#include "base/functional/bind.h"
#include "shell/common/gin_converters/std_converter.h"
#include "shell/common/gin_helper/function_template.h"
#include "shell/common/gin_helper/locker.h"
#include "v8/include/v8-function.h"
#include "v8/include/v8-microtask-queue.h"
// Implements safe conversions between JS functions and base::RepeatingCallback.
namespace gin_helper {
template <typename T>
class RefCountedGlobal;
// Manages the V8 function with RAII.
class SafeV8Function {
public:
SafeV8Function(v8::Isolate* isolate, v8::Local<v8::Value> value);
SafeV8Function(const SafeV8Function& other);
~SafeV8Function();
bool IsAlive() const;
v8::Local<v8::Function> NewHandle(v8::Isolate* isolate) const;
private:
scoped_refptr<RefCountedGlobal<v8::Function>> v8_function_;
};
// Helper to invoke a V8 function with C++ parameters.
template <typename Sig>
struct V8FunctionInvoker {};
template <typename... ArgTypes>
struct V8FunctionInvoker<v8::Local<v8::Value>(ArgTypes...)> {
static v8::Local<v8::Value> Go(v8::Isolate* isolate,
const SafeV8Function& function,
ArgTypes... raw) {
gin_helper::Locker locker(isolate);
v8::EscapableHandleScope handle_scope(isolate);
if (!function.IsAlive())
return v8::Null(isolate);
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->GetCreationContextChecked();
v8::MicrotasksScope microtasks_scope(context,
v8::MicrotasksScope::kRunMicrotasks);
v8::Context::Scope context_scope(context);
std::array<v8::Local<v8::Value>, sizeof...(raw)> args{
gin::ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::MaybeLocal<v8::Value> ret = holder->Call(
context, holder, args.size(), args.empty() ? nullptr : &args.front());
if (ret.IsEmpty())
return v8::Undefined(isolate);
else
return handle_scope.Escape(ret.ToLocalChecked());
}
};
template <typename... ArgTypes>
struct V8FunctionInvoker<void(ArgTypes...)> {
static void Go(v8::Isolate* isolate,
const SafeV8Function& function,
ArgTypes... raw) {
gin_helper::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
if (!function.IsAlive())
return;
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->GetCreationContextChecked();
v8::MicrotasksScope microtasks_scope(context,
v8::MicrotasksScope::kRunMicrotasks);
v8::Context::Scope context_scope(context);
std::array<v8::Local<v8::Value>, sizeof...(raw)> args{
gin::ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
holder
->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front())
.IsEmpty();
}
};
template <typename ReturnType, typename... ArgTypes>
struct V8FunctionInvoker<ReturnType(ArgTypes...)> {
static ReturnType Go(v8::Isolate* isolate,
const SafeV8Function& function,
ArgTypes... raw) {
gin_helper::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
ReturnType ret = ReturnType();
if (!function.IsAlive())
return ret;
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->GetCreationContextChecked();
v8::MicrotasksScope microtasks_scope(context,
v8::MicrotasksScope::kRunMicrotasks);
v8::Context::Scope context_scope(context);
std::array<v8::Local<v8::Value>, sizeof...(raw)> args{
gin::ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::Local<v8::Value> result;
auto maybe_result = holder->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front());
if (maybe_result.ToLocal(&result))
gin::Converter<ReturnType>::FromV8(isolate, result, &ret);
return ret;
}
};
// Helper to pass a C++ function to JavaScript.
using Translator = base::RepeatingCallback<void(gin::Arguments* args)>;
v8::Local<v8::Value> CreateFunctionFromTranslator(v8::Isolate* isolate,
const Translator& translator,
bool one_time);
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2);
// Calls callback with Arguments.
template <typename Sig>
struct NativeFunctionInvoker {};
template <typename ReturnType, typename... ArgTypes>
struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {
static void Go(base::RepeatingCallback<ReturnType(ArgTypes...)> val,
gin::Arguments* args) {
using Indices = std::index_sequence_for<ArgTypes...>;
Invoker<Indices, ArgTypes...> invoker(args,
{.holder_is_first_argument = false});
if (invoker.IsOK())
invoker.DispatchToCallback(val);
}
};
// Convert a callback to V8 without the call number limitation, this can easily
// cause memory leaks so use it with caution.
template <typename Sig>
v8::Local<v8::Value> CallbackToV8Leaked(
v8::Isolate* isolate,
const base::RepeatingCallback<Sig>& val) {
Translator translator =
base::BindRepeating(&NativeFunctionInvoker<Sig>::Go, val);
return CreateFunctionFromTranslator(isolate, translator, false);
}
} // namespace gin_helper
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_CALLBACK_H_