Browse Source

refactor: update `gin_helper/function_template` (#41534)

* refactor: update gin_helper/function_template

* fix: crash in Node.js Worker threads
Shelley Vohr 1 year ago
parent
commit
8c427253b3

+ 3 - 2
shell/common/gin_helper/callback.h

@@ -132,8 +132,9 @@ template <typename ReturnType, typename... ArgTypes>
 struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {
   static void Go(base::RepeatingCallback<ReturnType(ArgTypes...)> val,
                  gin::Arguments* args) {
-    using Indices = typename IndicesGenerator<sizeof...(ArgTypes)>::type;
-    Invoker<Indices, ArgTypes...> invoker(args, 0);
+    using Indices = std::index_sequence_for<ArgTypes...>;
+    Invoker<Indices, ArgTypes...> invoker(args,
+                                          {.holder_is_first_argument = false});
     if (invoker.IsOK())
       invoker.DispatchToCallback(val);
   }

+ 42 - 21
shell/common/gin_helper/constructor.h

@@ -26,7 +26,8 @@ inline WrappableBase* InvokeFactory(
     gin::Arguments* args,
     const base::RepeatingCallback<WrappableBase*(P1)>& callback) {
   typename CallbackParamTraits<P1>::LocalType a1;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1))
     return nullptr;
   return callback.Run(a1);
 }
@@ -37,8 +38,10 @@ inline WrappableBase* InvokeFactory(
     const base::RepeatingCallback<WrappableBase*(P1, P2)>& callback) {
   typename CallbackParamTraits<P1>::LocalType a1;
   typename CallbackParamTraits<P2>::LocalType a2;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a2))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a2))
     return nullptr;
   return callback.Run(a1, a2);
 }
@@ -50,9 +53,12 @@ inline WrappableBase* InvokeFactory(
   typename CallbackParamTraits<P1>::LocalType a1;
   typename CallbackParamTraits<P2>::LocalType a2;
   typename CallbackParamTraits<P3>::LocalType a3;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a2) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a3))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a2) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a3))
     return nullptr;
   return callback.Run(a1, a2, a3);
 }
@@ -65,10 +71,14 @@ inline WrappableBase* InvokeFactory(
   typename CallbackParamTraits<P2>::LocalType a2;
   typename CallbackParamTraits<P3>::LocalType a3;
   typename CallbackParamTraits<P4>::LocalType a4;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a2) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a3) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a4))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a2) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a3) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a4))
     return nullptr;
   return callback.Run(a1, a2, a3, a4);
 }
@@ -83,11 +93,16 @@ inline WrappableBase* InvokeFactory(
   typename CallbackParamTraits<P3>::LocalType a3;
   typename CallbackParamTraits<P4>::LocalType a4;
   typename CallbackParamTraits<P5>::LocalType a5;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a2) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a3) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a4) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a5))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a2) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a3) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a4) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a5))
     return nullptr;
   return callback.Run(a1, a2, a3, a4, a5);
 }
@@ -108,12 +123,18 @@ inline WrappableBase* InvokeFactory(
   typename CallbackParamTraits<P4>::LocalType a4;
   typename CallbackParamTraits<P5>::LocalType a5;
   typename CallbackParamTraits<P6>::LocalType a6;
-  if (!gin_helper::GetNextArgument(args, 0, true, &a1) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a2) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a3) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a4) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a5) ||
-      !gin_helper::GetNextArgument(args, 0, false, &a6))
+  if (!gin_helper::GetNextArgument(args, {.holder_is_first_argument = true}, 0,
+                                   &a1) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a2) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a3) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a4) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a5) ||
+      !gin_helper::GetNextArgument(args, {.holder_is_first_argument = false}, 0,
+                                   &a6))
     return nullptr;
   return callback.Run(a1, a2, a3, a4, a5, a6);
 }

+ 49 - 1
shell/common/gin_helper/function_template.cc

@@ -4,10 +4,34 @@
 
 #include "shell/common/gin_helper/function_template.h"
 
+#include "base/observer_list.h"
+#include "base/strings/strcat.h"
+
 namespace gin_helper {
 
+CallbackHolderBase::DisposeObserver::DisposeObserver(
+    gin::PerIsolateData* per_isolate_data,
+    CallbackHolderBase* holder)
+    : per_isolate_data_(per_isolate_data), holder_(*holder) {
+  if (per_isolate_data_)
+    per_isolate_data_->AddDisposeObserver(this);
+}
+CallbackHolderBase::DisposeObserver::~DisposeObserver() {
+  if (per_isolate_data_)
+    per_isolate_data_->RemoveDisposeObserver(this);
+}
+void CallbackHolderBase::DisposeObserver::OnBeforeDispose(
+    v8::Isolate* isolate) {
+  holder_->v8_ref_.Reset();
+}
+void CallbackHolderBase::DisposeObserver::OnDisposed() {
+  // The holder contains the observer, so the observer is destroyed here also.
+  delete &holder_.get();
+}
+
 CallbackHolderBase::CallbackHolderBase(v8::Isolate* isolate)
-    : v8_ref_(isolate, v8::External::New(isolate, this)) {
+    : v8_ref_(isolate, v8::External::New(isolate, this)),
+      dispose_observer_(gin::PerIsolateData::From(isolate), this) {
   v8_ref_.SetWeak(this, &CallbackHolderBase::FirstWeakCallback,
                   v8::WeakCallbackType::kParameter);
 }
@@ -33,4 +57,28 @@ void CallbackHolderBase::SecondWeakCallback(
   delete data.GetParameter();
 }
 
+void ThrowConversionError(gin::Arguments* args,
+                          const InvokerOptions& invoker_options,
+                          size_t index) {
+  if (index == 0 && invoker_options.holder_is_first_argument) {
+    // Failed to get the appropriate `this` object. This can happen if a
+    // method is invoked using Function.prototype.[call|apply] and passed an
+    // invalid (or null) `this` argument.
+    std::string error =
+        invoker_options.holder_type
+            ? base::StrCat({"Illegal invocation: Function must be "
+                            "called on an object of type ",
+                            invoker_options.holder_type})
+            : "Illegal invocation";
+    args->ThrowTypeError(error);
+  } else {
+    // Otherwise, this failed parsing on a different argument.
+    // Arguments::ThrowError() will try to include appropriate information.
+    // Ideally we would include the expected c++ type in the error message
+    // here, too (which we can access via typeid(ArgType).name()), however we
+    // compile with no-rtti, which disables typeid.
+    args->ThrowError();
+  }
+}
+
 }  // namespace gin_helper

+ 173 - 86
shell/common/gin_helper/function_template.h

@@ -12,6 +12,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "gin/arguments.h"
+#include "gin/per_isolate_data.h"
 #include "shell/common/gin_helper/arguments.h"
 #include "shell/common/gin_helper/destroyable.h"
 #include "shell/common/gin_helper/error_thrower.h"
@@ -25,8 +26,9 @@
 
 namespace gin_helper {
 
-enum CreateFunctionTemplateFlags {
-  HolderIsFirstArgument = 1 << 0,
+struct InvokerOptions {
+  bool holder_is_first_argument = false;
+  const char* holder_type = nullptr;  // Null if unknown or not applicable.
 };
 
 template <typename T>
@@ -43,64 +45,91 @@ struct CallbackParamTraits<const T*> {
 };
 
 // CallbackHolder and CallbackHolderBase are used to pass a
-// base::RepeatingCallback from
-// CreateFunctionTemplate through v8 (via v8::FunctionTemplate) to
-// DispatchToCallback, where it is invoked.
+// base::RepeatingCallback from CreateFunctionTemplate through v8 (via
+// v8::FunctionTemplate) to DispatchToCallback, where it is invoked.
+
+// CallbackHolder will clean up the callback in two different scenarios:
+// - If the garbage collector finds that it's garbage and collects it. (But note
+//   that even _if_ we become garbage, we might never get collected!)
+// - If the isolate gets disposed.
+//
+// TODO(crbug.com/1285119): When gin::Wrappable gets migrated over to using
+//   cppgc, this class should also be considered for migration.
 
 // This simple base class is used so that we can share a single object template
 // among every CallbackHolder instance.
 class CallbackHolderBase {
  public:
-  v8::Local<v8::External> GetHandle(v8::Isolate* isolate);
-
-  // disable copy
   CallbackHolderBase(const CallbackHolderBase&) = delete;
   CallbackHolderBase& operator=(const CallbackHolderBase&) = delete;
 
+  v8::Local<v8::External> GetHandle(v8::Isolate* isolate);
+
  protected:
   explicit CallbackHolderBase(v8::Isolate* isolate);
   virtual ~CallbackHolderBase();
 
  private:
+  class DisposeObserver : gin::PerIsolateData::DisposeObserver {
+   public:
+    DisposeObserver(gin::PerIsolateData* per_isolate_data,
+                    CallbackHolderBase* holder);
+    ~DisposeObserver() override;
+    void OnBeforeDispose(v8::Isolate* isolate) override;
+    void OnDisposed() override;
+
+   private:
+    // Unlike in Chromium, it's possible for PerIsolateData to be null
+    // for a given isolate - e.g. in a Node.js Worker. Thus this
+    // needs to be a raw_ptr instead of a raw_ref.
+    const raw_ptr<gin::PerIsolateData> per_isolate_data_;
+    const raw_ref<CallbackHolderBase> holder_;
+  };
+
   static void FirstWeakCallback(
       const v8::WeakCallbackInfo<CallbackHolderBase>& data);
   static void SecondWeakCallback(
       const v8::WeakCallbackInfo<CallbackHolderBase>& data);
 
   v8::Global<v8::External> v8_ref_;
+  DisposeObserver dispose_observer_;
 };
 
 template <typename Sig>
 class CallbackHolder : public CallbackHolderBase {
  public:
   CallbackHolder(v8::Isolate* isolate,
-                 const base::RepeatingCallback<Sig>& callback,
-                 int flags)
-      : CallbackHolderBase(isolate), callback(callback), flags(flags) {}
+                 base::RepeatingCallback<Sig> callback,
+                 InvokerOptions invoker_options)
+      : CallbackHolderBase(isolate),
+        callback(std::move(callback)),
+        invoker_options(std::move(invoker_options)) {}
+  CallbackHolder(const CallbackHolder&) = delete;
+  CallbackHolder& operator=(const CallbackHolder&) = delete;
+
   base::RepeatingCallback<Sig> callback;
-  int flags = 0;
+  InvokerOptions invoker_options;
 
  private:
-  virtual ~CallbackHolder() = default;
+  ~CallbackHolder() override = default;
 };
 
 template <typename T>
 bool GetNextArgument(gin::Arguments* args,
-                     int create_flags,
+                     const InvokerOptions& invoker_options,
                      bool is_first,
                      T* result) {
-  if (is_first && (create_flags & HolderIsFirstArgument) != 0) {
+  if (is_first && invoker_options.holder_is_first_argument) {
     return args->GetHolder(result);
   } else {
     return args->GetNext(result);
   }
 }
 
-// Support std::optional as output, which would be empty and do not throw error
-// when conversion to T fails.
+// Electron-specific GetNextArgument that supports std::optional.
 template <typename T>
 bool GetNextArgument(gin::Arguments* args,
-                     int create_flags,
+                     const InvokerOptions& invoker_options,
                      bool is_first,
                      std::optional<T>* result) {
   T converted;
@@ -110,83 +139,103 @@ bool GetNextArgument(gin::Arguments* args,
   return true;
 }
 
-// For advanced use cases, we allow callers to request the unparsed Arguments
-// object and poke around in it directly.
+// Electron-specific GetNextArgument that supports ErrorThrower.
 inline bool GetNextArgument(gin::Arguments* args,
-                            int create_flags,
+                            const InvokerOptions& invoker_options,
                             bool is_first,
-                            gin::Arguments** result) {
-  *result = args;
+                            ErrorThrower* result) {
+  *result = ErrorThrower(args->isolate());
   return true;
 }
 
-// It's common for clients to just need the isolate, so we make that easy.
+// Electron-specific GetNextArgument that supports the gin_helper::Arguments.
 inline bool GetNextArgument(gin::Arguments* args,
-                            int create_flags,
+                            const InvokerOptions& invoker_options,
                             bool is_first,
-                            v8::Isolate** result) {
-  *result = args->isolate();
+                            gin_helper::Arguments** result) {
+  *result = static_cast<gin_helper::Arguments*>(args);
   return true;
 }
 
-// Allow clients to pass a util::Error to throw errors if they
-// don't need the full gin::Arguments
+// For advanced use cases, we allow callers to request the unparsed Arguments
+// object and poke around in it directly.
 inline bool GetNextArgument(gin::Arguments* args,
-                            int create_flags,
+                            const InvokerOptions& invoker_options,
                             bool is_first,
-                            ErrorThrower* result) {
-  *result = ErrorThrower(args->isolate());
+                            gin::Arguments* result) {
+  *result = *args;
   return true;
 }
 
-// Supports the gin_helper::Arguments.
 inline bool GetNextArgument(gin::Arguments* args,
-                            int create_flags,
+                            const InvokerOptions& invoker_options,
                             bool is_first,
-                            gin_helper::Arguments** result) {
-  *result = static_cast<gin_helper::Arguments*>(args);
+                            gin::Arguments** result) {
+  *result = args;
   return true;
 }
 
-// Classes for generating and storing an argument pack of integer indices
-// (based on well-known "indices trick", see: http://goo.gl/bKKojn):
-template <size_t... indices>
-struct IndicesHolder {};
+// It's common for clients to just need the isolate, so we make that easy.
+inline bool GetNextArgument(gin::Arguments* args,
+                            const InvokerOptions& invoker_options,
+                            bool is_first,
+                            v8::Isolate** result) {
+  *result = args->isolate();
+  return true;
+}
 
-template <size_t requested_index, size_t... indices>
-struct IndicesGenerator {
-  using type = typename IndicesGenerator<requested_index - 1,
-                                         requested_index - 1,
-                                         indices...>::type;
-};
-template <size_t... indices>
-struct IndicesGenerator<0, indices...> {
-  using type = IndicesHolder<indices...>;
-};
+// Throws an error indicating conversion failure.
+void ThrowConversionError(gin::Arguments* args,
+                          const InvokerOptions& invoker_options,
+                          size_t index);
 
 // Class template for extracting and storing single argument for callback
 // at position |index|.
-template <size_t index, typename ArgType>
+template <size_t index, typename ArgType, typename = void>
 struct ArgumentHolder {
   using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
 
   ArgLocalType value;
   bool ok = false;
 
-  ArgumentHolder(gin::Arguments* args, int create_flags) {
+  ArgumentHolder(gin::Arguments* args, const InvokerOptions& invoker_options) {
     v8::Local<v8::Object> holder;
-    if (index == 0 && (create_flags & HolderIsFirstArgument) &&
+    if (index == 0 && invoker_options.holder_is_first_argument &&
         args->GetHolder(&holder) &&
         gin_helper::Destroyable::IsDestroyed(holder)) {
       args->ThrowTypeError("Object has been destroyed");
       return;
     }
-    ok = GetNextArgument(args, create_flags, index == 0, &value);
+
+    ok = GetNextArgument(args, invoker_options, index == 0, &value);
+    if (!ok) {
+      ThrowConversionError(args, invoker_options, index);
+    }
+  }
+};
+
+// This is required for types such as v8::LocalVector<T>, which don't have
+// a default constructor. To create an element of such a type, the isolate
+// has to be provided.
+template <size_t index, typename ArgType>
+struct ArgumentHolder<
+    index,
+    ArgType,
+    std::enable_if_t<!std::is_default_constructible_v<
+                         typename CallbackParamTraits<ArgType>::LocalType> &&
+                     std::is_constructible_v<
+                         typename CallbackParamTraits<ArgType>::LocalType,
+                         v8::Isolate*>>> {
+  using ArgLocalType = typename CallbackParamTraits<ArgType>::LocalType;
+
+  ArgLocalType value;
+  bool ok;
+
+  ArgumentHolder(gin::Arguments* args, const InvokerOptions& invoker_options)
+      : value(args->isolate()),
+        ok(GetNextArgument(args, invoker_options, index == 0, &value)) {
     if (!ok) {
-      // Ideally we would include the expected c++ type in the error
-      // message which we can access via typeid(ArgType).name()
-      // however we compile with no-rtti, which disables typeid.
-      args->ThrowError();
+      ThrowConversionError(args, invoker_options, index);
     }
   }
 };
@@ -194,24 +243,19 @@ struct ArgumentHolder {
 // Class template for converting arguments from JavaScript to C++ and running
 // the callback with them.
 template <typename IndicesType, typename... ArgTypes>
-class Invoker {};
+class Invoker;
 
 template <size_t... indices, typename... ArgTypes>
-class Invoker<IndicesHolder<indices...>, ArgTypes...>
+class Invoker<std::index_sequence<indices...>, ArgTypes...>
     : public ArgumentHolder<indices, ArgTypes>... {
  public:
   // Invoker<> inherits from ArgumentHolder<> for each argument.
   // C++ has always been strict about the class initialization order,
   // so it is guaranteed ArgumentHolders will be initialized (and thus, will
   // extract arguments from Arguments) in the right order.
-  Invoker(gin::Arguments* args, int create_flags)
-      : ArgumentHolder<indices, ArgTypes>(args, create_flags)..., args_(args) {
-    // GCC thinks that create_flags is going unused, even though the
-    // expansion above clearly makes use of it. Per jyasskin@, casting
-    // to void is the commonly accepted way to convince the compiler
-    // that you're actually using a parameter/variable.
-    (void)create_flags;
-  }
+  Invoker(gin::Arguments* args, const InvokerOptions& invoker_options)
+      : ArgumentHolder<indices, ArgTypes>(args, invoker_options)...,
+        args_(args) {}
 
   bool IsOK() { return And(ArgumentHolder<indices, ArgTypes>::ok...); }
 
@@ -252,46 +296,89 @@ struct Dispatcher {};
 
 template <typename ReturnType, typename... ArgTypes>
 struct Dispatcher<ReturnType(ArgTypes...)> {
-  static void DispatchToCallback(
-      const v8::FunctionCallbackInfo<v8::Value>& info) {
-    gin::Arguments args(info);
+  static void DispatchToCallbackImpl(gin::Arguments* args) {
     v8::Local<v8::External> v8_holder;
-    args.GetData(&v8_holder);
+    CHECK(args->GetData(&v8_holder));
     CallbackHolderBase* holder_base =
         reinterpret_cast<CallbackHolderBase*>(v8_holder->Value());
 
     typedef CallbackHolder<ReturnType(ArgTypes...)> HolderT;
     HolderT* holder = static_cast<HolderT*>(holder_base);
 
-    using Indices = typename IndicesGenerator<sizeof...(ArgTypes)>::type;
-    Invoker<Indices, ArgTypes...> invoker(&args, holder->flags);
+    using Indices = std::index_sequence_for<ArgTypes...>;
+    Invoker<Indices, ArgTypes...> invoker(args, holder->invoker_options);
     if (invoker.IsOK())
       invoker.DispatchToCallback(holder->callback);
   }
+
+  static void DispatchToCallback(
+      const v8::FunctionCallbackInfo<v8::Value>& info) {
+    gin::Arguments args(info);
+    DispatchToCallbackImpl(&args);
+  }
+
+  static void DispatchToCallbackForProperty(
+      v8::Local<v8::Name>,
+      const v8::PropertyCallbackInfo<v8::Value>& info) {
+    gin::Arguments args(info);
+    DispatchToCallbackImpl(&args);
+  }
 };
 
 // CreateFunctionTemplate creates a v8::FunctionTemplate that will create
 // JavaScript functions that execute a provided C++ function or
-// base::RepeatingCallback.
-// JavaScript arguments are automatically converted via gin::Converter, as is
-// the return value of the C++ function, if any.
+// base::RepeatingCallback. JavaScript arguments are automatically converted via
+// gin::Converter, as is the return value of the C++ function, if any.
+// |invoker_options| contains additional parameters. If it contains a
+// holder_type, it will be used to provide a useful conversion error if the
+// holder is the first argument. If not provided, a generic invocation error
+// will be used.
 //
 // NOTE: V8 caches FunctionTemplates for a lifetime of a web page for its own
 // internal reasons, thus it is generally a good idea to cache the template
 // returned by this function.  Otherwise, repeated method invocations from JS
 // will create substantial memory leaks. See http://crbug.com/463487.
+//
+// The callback will be destroyed if either the function template gets garbage
+// collected or _after_ the isolate is disposed. Garbage collection can never be
+// relied upon. As such, any destructors for objects bound to the callback must
+// not depend on the isolate being alive at the point they are called. The order
+// in which callbacks are destroyed is not guaranteed.
 template <typename Sig>
 v8::Local<v8::FunctionTemplate> CreateFunctionTemplate(
     v8::Isolate* isolate,
-    const base::RepeatingCallback<Sig> callback,
-    int callback_flags = 0) {
+    base::RepeatingCallback<Sig> callback,
+    InvokerOptions invoker_options = {}) {
   typedef CallbackHolder<Sig> HolderT;
-  HolderT* holder = new HolderT(isolate, callback, callback_flags);
+  HolderT* holder =
+      new HolderT(isolate, std::move(callback), std::move(invoker_options));
+
+  v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
+      isolate, &Dispatcher<Sig>::DispatchToCallback,
+      gin::ConvertToV8<v8::Local<v8::External>>(isolate,
+                                                holder->GetHandle(isolate)),
+      v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow);
+  return tmpl;
+}
 
-  return v8::FunctionTemplate::New(isolate,
-                                   &Dispatcher<Sig>::DispatchToCallback,
-                                   gin::ConvertToV8<v8::Local<v8::External>>(
-                                       isolate, holder->GetHandle(isolate)));
+// CreateDataPropertyCallback creates a v8::AccessorNameGetterCallback and
+// corresponding data value that will hold and execute the provided
+// base::RepeatingCallback, using automatic conversions similar to
+// |CreateFunctionTemplate|.
+//
+// It is expected that these will be passed to v8::Template::SetLazyDataProperty
+// or another similar function.
+template <typename Sig>
+std::pair<v8::AccessorNameGetterCallback, v8::Local<v8::Value>>
+CreateDataPropertyCallback(v8::Isolate* isolate,
+                           base::RepeatingCallback<Sig> callback,
+                           InvokerOptions invoker_options = {}) {
+  typedef CallbackHolder<Sig> HolderT;
+  HolderT* holder =
+      new HolderT(isolate, std::move(callback), std::move(invoker_options));
+  return {&Dispatcher<Sig>::DispatchToCallbackForProperty,
+          gin::ConvertToV8<v8::Local<v8::External>>(
+              isolate, holder->GetHandle(isolate))};
 }
 
 // Base template - used only for non-member function pointers. Other types
@@ -326,9 +413,9 @@ struct CallbackTraits<
     typename std::enable_if<std::is_member_function_pointer<T>::value>::type> {
   static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate,
                                                         T callback) {
-    int flags = HolderIsFirstArgument;
+    InvokerOptions invoker_options = {.holder_is_first_argument = true};
     return gin_helper::CreateFunctionTemplate(
-        isolate, base::BindRepeating(callback), flags);
+        isolate, base::BindRepeating(callback), std::move(invoker_options));
   }
 };