0

V8's ObjectTemplate provides us two ways to attach a so-called accessor property to the object under instantiation. The first one is ObjectTemplate::SetAccessor:

/**
   * Sets an accessor on the object template.
   *
   * Whenever the property with the given name is accessed on objects
   * created from this ObjectTemplate the getter and setter callbacks
   * are called instead of getting and setting the property directly
   * on the JavaScript object.
   *
   * \param name The name of the property for which an accessor is added.
   * \param getter The callback to invoke when getting the property.
   * \param setter The callback to invoke when setting the property.
   * \param data A piece of data that will be passed to the getter and setter
   *   callbacks whenever they are invoked.
   * \param settings Access control settings for the accessor. This is a bit
   *   field consisting of one of more of
   *   DEFAULT = 0, ALL_CAN_READ = 1, or ALL_CAN_WRITE = 2.
   *   The default is to not allow cross-context access.
   *   ALL_CAN_READ means that all cross-context reads are allowed.
   *   ALL_CAN_WRITE means that all cross-context writes are allowed.
   *   The combination ALL_CAN_READ | ALL_CAN_WRITE can be used to allow all
   *   cross-context access.
   * \param attribute The attributes of the property for which an accessor
   *   is added.
   * \param signature The signature describes valid receivers for the accessor
   *   and is used to perform implicit instance checks against them. If the
   *   receiver is incompatible (i.e. is not an instance of the constructor as
   *   defined by FunctionTemplate::HasInstance()), an implicit TypeError is
   *   thrown and no callback is invoked.
   */
  void SetAccessor(
      Local<String> name, AccessorGetterCallback getter,
      AccessorSetterCallback setter = nullptr,
      Local<Value> data = Local<Value>(), AccessControl settings = DEFAULT, // note the data is on the template level.
      PropertyAttribute attribute = None, // not on the instance level.
      Local<AccessorSignature> signature = Local<AccessorSignature>(),
      SideEffectType getter_side_effect_type = SideEffectType::kHasSideEffect,
      SideEffectType setter_side_effect_type = SideEffectType::kHasSideEffect);

and the second one is Template::SetAccessorProperty:

void SetAccessorProperty(
     Local<Name> name, // The latter one is called data property.
     Local<FunctionTemplate> getter = Local<FunctionTemplate>(),
     Local<FunctionTemplate> setter = Local<FunctionTemplate>(),
     PropertyAttribute attribute = None,
     AccessControl settings = DEFAULT);

Having two very similar APIs confuses me a lot. Unfortunately there is no docs describing their differences, so I have to do experiment on my own. I found that the combination of Holder() and SetAccessor() will break, while other combinations of Holder() or This() and SetAccessor() or SetAccessorProperty() work fine. On a previous post I ran into this failing combination and was fooled to believe what's went wrong is Holder() or This(). But after the experiment I now believe that it is SetAccessor went wrong.

My question is, whether SetAccessor is a deprecated API? If so, all we have to do is stop using it. If not so, please explain a little bit their differences, esspecially in causing this failure. Many thanks to the warm-hearted and experienced V8 developers!

cgsdfc
  • 538
  • 4
  • 19

1 Answers1

4

I agree that the names can be a bit confusing; neither is deprecated. The difference is as follows:

SetAccessor creates a "magic" data property: it looks like a data property to JavaScript (getOwnPropertyDescriptor returns {value: ..., ...}), but when reading/writing the property, the C++ callbacks you specified will be called. For a built-in example, think of Array.prototype.length (where in particular the setter has to do additional work when you use a .length assignment to shorten an array).

SetAccessorProperty creates a regular accessor property, i.e. getOwnPropertyDescriptor returns {get: ..., set: ..., ...}. A built-in example would be Int32Array.prototype.__proto__.byteLength. The name "SetAccessorProperty" reflects the fact that the JS spec calls these properties "accessor properties".

Chances are that in many cases, this difference doesn't matter: interfacing JavaScript code can read and/or write properties either way. But sometimes you might have a reason to care about the distinction, and in that case V8's API gives you that flexibility.

jmrk
  • 34,271
  • 7
  • 59
  • 74