Skip to content

Commit

Permalink
gin: Implement ObjectTemplateBuilder::SetLazyDataProperty.
Browse files Browse the repository at this point in the history
This is useful for compatibility with APIs that previously exposed
data properties.

It is necessary to make the invocation infrastructure work on
v8::PropertyCallbackInfo, which is how this arrives to us from V8.
gin::Arguments is augmented so that it can be constructed from either
type of callback info. This does add some conditional branches, but
this complexity is confined to gin::Arguments.

Unit tests for the lazy data property logic are fairly basic because
most of the functionality lives inside V8 itself.

Bug: 927878

Change-Id: I83ad6088f8e508fb7b32fdefb1245dd2c5c1a2f1
Reviewed-on: https://chromium-review.googlesource.com/c/1450395
Commit-Queue: Jeremy Roman <jbroman@chromium.org>
Reviewed-by: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#628783}
  • Loading branch information
jeremyroman authored and Commit Bot committed Feb 4, 2019
1 parent 9cf2a99 commit 6a1242b
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 34 deletions.
43 changes: 27 additions & 16 deletions gin/arguments.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,49 @@
namespace gin {

Arguments::Arguments()
: isolate_(NULL),
info_(NULL),
next_(0),
insufficient_arguments_(false) {
}
: isolate_(nullptr), info_for_function_(nullptr), is_for_property_(false) {}

Arguments::Arguments(const v8::FunctionCallbackInfo<v8::Value>& info)
: isolate_(info.GetIsolate()),
info_(&info),
next_(0),
insufficient_arguments_(false) {
}
info_for_function_(&info),
is_for_property_(false) {}

Arguments::Arguments(const v8::PropertyCallbackInfo<v8::Value>& info)
: isolate_(info.GetIsolate()),
info_for_property_(&info),
is_for_property_(true) {}

Arguments::~Arguments() = default;

v8::Local<v8::Value> Arguments::PeekNext() const {
if (next_ >= info_->Length())
if (is_for_property_)
return v8::Local<v8::Value>();
return (*info_)[next_];
if (next_ >= info_for_function_->Length())
return v8::Local<v8::Value>();
return (*info_for_function_)[next_];
}

std::vector<v8::Local<v8::Value>> Arguments::GetAll() const {
std::vector<v8::Local<v8::Value>> result;
int length = info_->Length();
if (is_for_property_)
return result;

int length = info_for_function_->Length();
if (length == 0)
return result;

result.reserve(length);
for (int i = 0; i < length; ++i)
result.push_back((*info_)[i]);
result.push_back((*info_for_function_)[i]);

return result;
}

v8::Local<v8::Context> Arguments::GetHolderCreationContext() const {
return info_->Holder()->CreationContext();
v8::Local<v8::Object> holder = is_for_property_
? info_for_property_->Holder()
: info_for_function_->Holder();
return holder->CreationContext();
}

std::string V8TypeAsString(v8::Isolate* isolate, v8::Local<v8::Value> value) {
Expand All @@ -62,12 +69,16 @@ std::string V8TypeAsString(v8::Isolate* isolate, v8::Local<v8::Value> value) {
}

void Arguments::ThrowError() const {
if (is_for_property_)
return ThrowTypeError("Error processing property accessor arguments.");

if (insufficient_arguments_)
return ThrowTypeError("Insufficient number of arguments.");

v8::Local<v8::Value> value = (*info_for_function_)[next_ - 1];
return ThrowTypeError(base::StringPrintf(
"Error processing argument at index %d, conversion failure from %s",
next_ - 1, V8TypeAsString(isolate_, (*info_)[next_ - 1]).c_str()));
next_ - 1, V8TypeAsString(isolate_, value).c_str()));
}

void Arguments::ThrowTypeError(const std::string& message) const {
Expand All @@ -76,7 +87,7 @@ void Arguments::ThrowTypeError(const std::string& message) const {
}

bool Arguments::IsConstructCall() const {
return info_->IsConstructCall();
return !is_for_property_ && info_for_function_->IsConstructCall();
}

} // namespace gin
43 changes: 30 additions & 13 deletions gin/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,78 @@ namespace gin {
// Arguments is a wrapper around v8::FunctionCallbackInfo that integrates
// with Converter to make it easier to marshall arguments and return values
// between V8 and C++.
//
// If constructed instead with a v8::PropertyCallbackInfo, behaves as though a
// function with no arguments had been called.
class GIN_EXPORT Arguments {
public:
Arguments();
explicit Arguments(const v8::FunctionCallbackInfo<v8::Value>& info);
explicit Arguments(const v8::PropertyCallbackInfo<v8::Value>& info);
~Arguments();

template <typename T>
bool GetHolder(T* out) const {
return ConvertFromV8(isolate_, info_->Holder(), out);
v8::Local<v8::Object> holder = is_for_property_
? info_for_property_->Holder()
: info_for_function_->Holder();
return ConvertFromV8(isolate_, holder, out);
}

template<typename T>
bool GetData(T* out) {
return ConvertFromV8(isolate_, info_->Data(), out);
v8::Local<v8::Value> data = is_for_property_ ? info_for_property_->Data()
: info_for_function_->Data();
return ConvertFromV8(isolate_, data, out);
}

template<typename T>
bool GetNext(T* out) {
if (next_ >= info_->Length()) {
if (is_for_property_ || next_ >= info_for_function_->Length()) {
insufficient_arguments_ = true;
return false;
}
v8::Local<v8::Value> val = (*info_)[next_++];
v8::Local<v8::Value> val = (*info_for_function_)[next_++];
return ConvertFromV8(isolate_, val, out);
}

template<typename T>
bool GetRemaining(std::vector<T>* out) {
if (next_ >= info_->Length()) {
if (is_for_property_ || next_ >= info_for_function_->Length()) {
insufficient_arguments_ = true;
return false;
}
int remaining = info_->Length() - next_;
int remaining = info_for_function_->Length() - next_;
out->resize(remaining);
for (int i = 0; i < remaining; ++i) {
v8::Local<v8::Value> val = (*info_)[next_++];
v8::Local<v8::Value> val = (*info_for_function_)[next_++];
if (!ConvertFromV8(isolate_, val, &out->at(i)))
return false;
}
return true;
}

bool Skip() {
if (next_ >= info_->Length())
if (is_for_property_)
return false;
if (next_ >= info_for_function_->Length())
return false;
next_++;
return true;
}

int Length() const {
return info_->Length();
return is_for_property_ ? 0 : info_for_function_->Length();
}

template<typename T>
void Return(T val) {
v8::Local<v8::Value> v8_value;
if (!TryConvertToV8(isolate_, val, &v8_value))
return;
info_->GetReturnValue().Set(v8_value);
(is_for_property_ ? info_for_property_->GetReturnValue()
: info_for_function_->GetReturnValue())
.Set(v8_value);
}

// Returns the creation context of the Holder.
Expand All @@ -97,9 +110,13 @@ class GIN_EXPORT Arguments {

private:
v8::Isolate* isolate_;
const v8::FunctionCallbackInfo<v8::Value>* info_;
int next_;
bool insufficient_arguments_;
union {
const v8::FunctionCallbackInfo<v8::Value>* info_for_function_;
const v8::PropertyCallbackInfo<v8::Value>* info_for_property_;
};
int next_ = 0;
bool insufficient_arguments_ = false;
bool is_for_property_ = false;
};

} // namespace gin
Expand Down
42 changes: 37 additions & 5 deletions gin/function_template.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define GIN_FUNCTION_TEMPLATE_H_

#include <stddef.h>
#include <utility>

#include "base/callback.h"
#include "base/logging.h"
Expand Down Expand Up @@ -192,22 +193,33 @@ struct Dispatcher {};

template <typename ReturnType, typename... ArgTypes>
struct Dispatcher<ReturnType(ArgTypes...)> {
static void DispatchToCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
Arguments args(info);
static void DispatchToCallbackImpl(Arguments* args) {
v8::Local<v8::External> v8_holder;
CHECK(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 = std::index_sequence_for<ArgTypes...>;
Invoker<Indices, ArgTypes...> invoker(&args, holder->invoker_options);
Invoker<Indices, ArgTypes...> invoker(args, holder->invoker_options);
if (invoker.IsOK())
invoker.DispatchToCallback(holder->callback);
}

static void DispatchToCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
Arguments args(info);
DispatchToCallbackImpl(&args);
}

static void DispatchToCallbackForProperty(
v8::Local<v8::Name>,
const v8::PropertyCallbackInfo<v8::Value>& info) {
Arguments args(info);
DispatchToCallbackImpl(&args);
}
};

} // namespace internal
Expand Down Expand Up @@ -241,6 +253,26 @@ v8::Local<v8::FunctionTemplate> CreateFunctionTemplate(
return tmpl;
}

// 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 internal::CallbackHolder<Sig> HolderT;
HolderT* holder =
new HolderT(isolate, std::move(callback), std::move(invoker_options));
return {&internal::Dispatcher<Sig>::DispatchToCallbackForProperty,
ConvertToV8<v8::Local<v8::External>>(isolate,
holder->GetHandle(isolate))};
}

} // namespace gin

#endif // GIN_FUNCTION_TEMPLATE_H_
9 changes: 9 additions & 0 deletions gin/object_template_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ ObjectTemplateBuilder& ObjectTemplateBuilder::SetPropertyImpl(
return *this;
}

ObjectTemplateBuilder& ObjectTemplateBuilder::SetLazyDataPropertyImpl(
const base::StringPiece& name,
v8::AccessorNameGetterCallback callback,
v8::Local<v8::Value> data) {
template_->SetLazyDataProperty(StringToSymbol(isolate_, name), callback,
data);
return *this;
}

v8::Local<v8::ObjectTemplate> ObjectTemplateBuilder::Build() {
v8::Local<v8::ObjectTemplate> result = template_;
template_.Clear();
Expand Down
25 changes: 25 additions & 0 deletions gin/object_template_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#ifndef GIN_OBJECT_TEMPLATE_BUILDER_H_
#define GIN_OBJECT_TEMPLATE_BUILDER_H_

#include <tuple>
#include <type_traits>
#include <utility>

#include "base/bind.h"
#include "base/callback.h"
Expand Down Expand Up @@ -79,6 +81,25 @@ class GIN_EXPORT ObjectTemplateBuilder {
name, internal::CreateFunctionTemplate(isolate_, getter, type_name_),
internal::CreateFunctionTemplate(isolate_, setter, type_name_));
}

// Whereas SetProperty creates an accessor property, this creates what appears
// to be a data property but whose value is lazily computed the first time the
// [[Get]] operation occurs.
template <typename T>
ObjectTemplateBuilder& SetLazyDataProperty(const base::StringPiece& name,
const T& getter) {
InvokerOptions options;
if (std::is_member_function_pointer<T>::value) {
options.holder_is_first_argument = true;
options.holder_type = type_name_;
}
v8::AccessorNameGetterCallback callback;
v8::Local<v8::Value> data;
std::tie(callback, data) = CreateDataPropertyCallback(
isolate_, base::BindRepeating(getter), std::move(options));
return SetLazyDataPropertyImpl(name, callback, data);
}

ObjectTemplateBuilder& AddNamedPropertyInterceptor();
ObjectTemplateBuilder& AddIndexedPropertyInterceptor();

Expand All @@ -90,6 +111,10 @@ class GIN_EXPORT ObjectTemplateBuilder {
ObjectTemplateBuilder& SetPropertyImpl(
const base::StringPiece& name, v8::Local<v8::FunctionTemplate> getter,
v8::Local<v8::FunctionTemplate> setter);
ObjectTemplateBuilder& SetLazyDataPropertyImpl(
const base::StringPiece& name,
v8::AccessorNameGetterCallback callback,
v8::Local<v8::Value> data);

v8::Isolate* isolate_;

Expand Down
Loading

0 comments on commit 6a1242b

Please sign in to comment.