diff --git a/.gitmodules b/.gitmodules index 73b12931d77fb..970e215f723b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "vendor/breakpad"] path = vendor/breakpad url = https://github.com/electron/chromium-breakpad.git -[submodule "vendor/native_mate"] - path = vendor/native_mate - url = https://github.com/electron/native-mate.git [submodule "vendor/crashpad"] path = vendor/crashpad url = https://github.com/electron/crashpad.git diff --git a/docs/README.md b/docs/README.md index 99766c7d59a66..f7b956e8feb95 100644 --- a/docs/README.md +++ b/docs/README.md @@ -129,7 +129,7 @@ These individual tutorials expand on topics discussed in the guide above. * [Menu](api/menu.md) * [MenuItem](api/menu-item.md) * [net](api/net.md) -* [netLog](api/netLog.md) +* [netLog](api/net-log.md) * [powerMonitor](api/power-monitor.md) * [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) diff --git a/docs/api/breaking-changes.md b/docs/api/breaking-changes.md index 4f28b6838175a..e1e5923d21185 100644 --- a/docs/api/breaking-changes.md +++ b/docs/api/breaking-changes.md @@ -1,6 +1,6 @@ # API Contract -Breaking changes will be documented here, and deprecation warnings added to JS code where possible, at least [one major version](electron-versioning.md#semver) before the change is made. +Breaking changes will be documented here, and deprecation warnings added to JS code where possible, at least [one major version](../tutorial/electron-versioning.md#semver) before the change is made. # `FIXME` comments diff --git a/electron.gyp b/electron.gyp index a7de385735784..c0ba44980e6f4 100644 --- a/electron.gyp +++ b/electron.gyp @@ -10,7 +10,7 @@ 'includes': [ 'features.gypi', 'filenames.gypi', - 'vendor/native_mate/native_mate_files.gypi', + 'native_mate/native_mate_files.gypi', ], 'target_defaults': { 'defines': [ @@ -295,7 +295,7 @@ 'include_dirs': [ '.', 'chromium_src', - 'vendor/native_mate', + 'native_mate', # Include atom_natives.h. '<(SHARED_INTERMEDIATE_DIR)', # Include directories for uv and node. diff --git a/native_mate/LICENSE.chromium b/native_mate/LICENSE.chromium new file mode 100644 index 0000000000000..972bb2edb099e --- /dev/null +++ b/native_mate/LICENSE.chromium @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/native_mate/README.md b/native_mate/README.md new file mode 100644 index 0000000000000..e5d056bfed9fc --- /dev/null +++ b/native_mate/README.md @@ -0,0 +1,56 @@ +> A fork of Chromium's [gin library][chromium-gin-lib] that makes it easier to +> marshal types between C++ and JavaScript. + +# Overview + +`native-mate` was forked from `gin` so that it could be used in +[Electron][electron] without conflicting with Node's Environment. It has also +been extended to allow Electron to create classes in JavaScript. + +With the help of Chromium's `base` library, `native-mate` makes writing JS +bindings very easy, and most of the intricate details of converting V8 types +to C++ types and back are taken care of auto-magically. In most cases there's +no need to use the raw V8 API to implement an API binding. + +For example, here's an API binding that doesn't use `native-mate`: + +```c++ +// static +void Shell::OpenItem(const v8::FunctionCallbackInfo& args) { + base::FilePath file_path; + if (!FromV8Arguments(args, &file_path)) + return node::ThrowTypeError("Bad argument"); + + platform_util::OpenItem(file_path); +} + +// static +void Shell::Initialize(v8::Handle target) { + NODE_SET_METHOD(target, "openItem", OpenItem); +} +``` + +And here's the same API binding using `native-mate`: + +```c++ +void Initialize(v8::Handle exports) { + mate::Dictionary dict(v8::Isolate::GetCurrent(), exports); + dict.SetMethod("openItem", &platform_util::OpenItem); +} +``` + +# Code Structure + +* `converter.h` - Templatized JS<->C++ conversion routines for many common C++ + types. You can define your own by specializing `Converter`. +* `function_template.h` - Create JavaScript functions that dispatch to any C++ + function, member function pointer, or `base::Callback`. +* `object_template_builder.h` - A handy utility for creation of `v8::ObjectTemplate`. +* `wrappable.h` - Base class for C++ classes that want to be owned by the V8 GC. + Wrappable objects are automatically deleted when GC discovers that nothing in + the V8 heap refers to them. This is also an easy way to expose C++ objects to + JavaScript. + + +[chromium-gin-lib]: https://code.google.com/p/chromium/codesearch#chromium/src/gin/README.md&sq=package:chromium +[electron]: http://electron.atom.io/ diff --git a/native_mate/native_mate/arguments.cc b/native_mate/native_mate/arguments.cc new file mode 100644 index 0000000000000..a23e6523cac12 --- /dev/null +++ b/native_mate/native_mate/arguments.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/arguments.h" + +#include "base/strings/stringprintf.h" +#include "native_mate/converter.h" + +namespace mate { + +namespace { + +std::string V8TypeAsString(v8::Isolate* isolate, v8::Local value) { + if (value.IsEmpty()) + return ""; + v8::MaybeLocal details = + value->ToDetailString(isolate->GetCurrentContext()); + std::string result; + if (!details.IsEmpty()) + ConvertFromV8(isolate, details.ToLocalChecked(), &result); + return result; +} + +} // namespace + +Arguments::Arguments() + : isolate_(NULL), + info_(NULL), + next_(0), + insufficient_arguments_(false) { +} + +Arguments::Arguments(const v8::FunctionCallbackInfo& info) + : isolate_(info.GetIsolate()), + info_(&info), + next_(0), + insufficient_arguments_(false) { +} + +Arguments::~Arguments() { +} + +v8::Local Arguments::PeekNext() const { + if (next_ >= info_->Length()) + return v8::Local(); + return (*info_)[next_]; +} + +v8::Local Arguments::ThrowError() const { + if (insufficient_arguments_) + return ThrowTypeError("Insufficient number of arguments."); + + return ThrowTypeError(base::StringPrintf( + "Error processing argument at index %d, conversion failure from %s", + next_, V8TypeAsString(isolate_, (*info_)[next_]).c_str())); +} + +v8::Local Arguments::ThrowError(const std::string& message) const { + isolate_->ThrowException(v8::Exception::Error( + StringToV8(isolate_, message))); + return v8::Undefined(isolate_); +} + +v8::Local Arguments::ThrowTypeError( + const std::string& message) const { + isolate_->ThrowException(v8::Exception::TypeError( + StringToV8(isolate_, message))); + return v8::Undefined(isolate_); +} + +} // namespace mate diff --git a/native_mate/native_mate/arguments.h b/native_mate/native_mate/arguments.h new file mode 100644 index 0000000000000..9198f289d6e1e --- /dev/null +++ b/native_mate/native_mate/arguments.h @@ -0,0 +1,99 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_ARGUMENTS_H_ +#define NATIVE_MATE_ARGUMENTS_H_ + +#include "base/macros.h" +#include "native_mate/converter.h" + +namespace mate { + +// 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++. +class Arguments { + public: + Arguments(); + explicit Arguments(const v8::FunctionCallbackInfo& info); + ~Arguments(); + + v8::Local GetHolder() const { + return info_->Holder(); + } + + template + bool GetHolder(T* out) { + return ConvertFromV8(isolate_, info_->Holder(), out); + } + + template + bool GetData(T* out) { + return ConvertFromV8(isolate_, info_->Data(), out); + } + + template + bool GetNext(T* out) { + if (next_ >= info_->Length()) { + insufficient_arguments_ = true; + return false; + } + v8::Local val = (*info_)[next_]; + bool success = ConvertFromV8(isolate_, val, out); + if (success) + next_++; + return success; + } + + template + bool GetRemaining(std::vector* out) { + if (next_ >= info_->Length()) { + insufficient_arguments_ = true; + return false; + } + int remaining = info_->Length() - next_; + out->resize(remaining); + for (int i = 0; i < remaining; ++i) { + v8::Local val = (*info_)[next_++]; + if (!ConvertFromV8(isolate_, val, &out->at(i))) + return false; + } + return true; + } + + v8::Local GetThis() { + return info_->This(); + } + + bool IsConstructCall() const { + return info_->IsConstructCall(); + } + + int Length() const { + return info_->Length(); + } + + template + void Return(T val) { + info_->GetReturnValue().Set(ConvertToV8(isolate_, val)); + } + + v8::Local PeekNext() const; + + v8::Local ThrowError() const; + v8::Local ThrowError(const std::string& message) const; + v8::Local ThrowTypeError(const std::string& message) const; + + v8::Isolate* isolate() const { return isolate_; } + + private: + v8::Isolate* isolate_; + const v8::FunctionCallbackInfo* info_; + int next_; + bool insufficient_arguments_; +}; + +} // namespace mate + +#endif // NATIVE_MATE_ARGUMENTS_H_ diff --git a/native_mate/native_mate/constructor.h b/native_mate/native_mate/constructor.h new file mode 100644 index 0000000000000..8f165e629d888 --- /dev/null +++ b/native_mate/native_mate/constructor.h @@ -0,0 +1,148 @@ +// This file was GENERATED by command: +// pump.py constructor.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_WRAPPABLE_CLASS_H_ +#define NATIVE_MATE_WRAPPABLE_CLASS_H_ + +#include "base/bind.h" +#include "native_mate/function_template.h" + +namespace mate { + +namespace internal { + +// This set of templates invokes a base::Callback by converting the Arguments +// into native types. It relies on the function_template.h to provide helper +// templates. +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + return callback.Run(); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + if (!GetNextArgument(args, 0, true, &a1)) + return nullptr; + return callback.Run(a1); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + typename CallbackParamTraits::LocalType a2; + if (!GetNextArgument(args, 0, true, &a1) || + !GetNextArgument(args, 0, false, &a2)) + return nullptr; + return callback.Run(a1, a2); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + typename CallbackParamTraits::LocalType a2; + typename CallbackParamTraits::LocalType a3; + if (!GetNextArgument(args, 0, true, &a1) || + !GetNextArgument(args, 0, false, &a2) || + !GetNextArgument(args, 0, false, &a3)) + return nullptr; + return callback.Run(a1, a2, a3); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + typename CallbackParamTraits::LocalType a2; + typename CallbackParamTraits::LocalType a3; + typename CallbackParamTraits::LocalType a4; + if (!GetNextArgument(args, 0, true, &a1) || + !GetNextArgument(args, 0, false, &a2) || + !GetNextArgument(args, 0, false, &a3) || + !GetNextArgument(args, 0, false, &a4)) + return nullptr; + return callback.Run(a1, a2, a3, a4); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + typename CallbackParamTraits::LocalType a2; + typename CallbackParamTraits::LocalType a3; + typename CallbackParamTraits::LocalType a4; + typename CallbackParamTraits::LocalType a5; + if (!GetNextArgument(args, 0, true, &a1) || + !GetNextArgument(args, 0, false, &a2) || + !GetNextArgument(args, 0, false, &a3) || + !GetNextArgument(args, 0, false, &a4) || + !GetNextArgument(args, 0, false, &a5)) + return nullptr; + return callback.Run(a1, a2, a3, a4, a5); +}; + +template +inline WrappableBase* InvokeFactory( + Arguments* args, + const base::Callback& callback) { + typename CallbackParamTraits::LocalType a1; + typename CallbackParamTraits::LocalType a2; + typename CallbackParamTraits::LocalType a3; + typename CallbackParamTraits::LocalType a4; + typename CallbackParamTraits::LocalType a5; + typename CallbackParamTraits::LocalType a6; + if (!GetNextArgument(args, 0, true, &a1) || + !GetNextArgument(args, 0, false, &a2) || + !GetNextArgument(args, 0, false, &a3) || + !GetNextArgument(args, 0, false, &a4) || + !GetNextArgument(args, 0, false, &a5) || + !GetNextArgument(args, 0, false, &a6)) + return nullptr; + return callback.Run(a1, a2, a3, a4, a5, a6); +}; + +template +void InvokeNew(const base::Callback& factory, + v8::Isolate* isolate, Arguments* args) { + if (!args->IsConstructCall()) { + args->ThrowError("Requires constructor call"); + return; + } + + WrappableBase* object; + { + // Don't continue if the constructor throws an exception. + v8::TryCatch try_catch(isolate); + object = internal::InvokeFactory(args, factory); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } + } + + if (!object) + args->ThrowError(); + + return; +} + +} // namespace internal + +} // namespace mate + +#endif // NATIVE_MATE_WRAPPABLE_CLASS_H_ diff --git a/native_mate/native_mate/converter.cc b/native_mate/native_mate/converter.cc new file mode 100644 index 0000000000000..260bdf105f606 --- /dev/null +++ b/native_mate/native_mate/converter.cc @@ -0,0 +1,247 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/converter.h" + +#include "v8/include/v8.h" + +using v8::Array; +using v8::Boolean; +using v8::External; +using v8::Function; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +namespace mate { + +Local Converter::ToV8(Isolate* isolate, bool val) { + return v8::Boolean::New(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, bool* out) { + if (!val->IsBoolean()) + return false; + *out = val->BooleanValue(); + return true; +} + +#if !defined(OS_LINUX) && !defined(OS_FREEBSD) +Local Converter::ToV8(Isolate* isolate, + unsigned long val) { + return v8::Integer::New(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + unsigned long* out) { + if (!val->IsNumber()) + return false; + *out = val->IntegerValue(); + return true; +} +#endif + +Local Converter::ToV8(Isolate* isolate, int32_t val) { + return v8::Integer::New(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + int32_t* out) { + if (!val->IsInt32()) + return false; + *out = val->Int32Value(); + return true; +} + +Local Converter::ToV8(Isolate* isolate, uint32_t val) { + return v8::Integer::NewFromUnsigned(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + uint32_t* out) { + if (!val->IsUint32()) + return false; + *out = val->Uint32Value(); + return true; +} + +Local Converter::ToV8(Isolate* isolate, int64_t val) { + return v8::Number::New(isolate, static_cast(val)); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + int64_t* out) { + if (!val->IsNumber()) + return false; + // Even though IntegerValue returns int64_t, JavaScript cannot represent + // the full precision of int64_t, which means some rounding might occur. + *out = val->IntegerValue(); + return true; +} + +Local Converter::ToV8(Isolate* isolate, uint64_t val) { + return v8::Number::New(isolate, static_cast(val)); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + uint64_t* out) { + if (!val->IsNumber()) + return false; + *out = static_cast(val->IntegerValue()); + return true; +} + +Local Converter::ToV8(Isolate* isolate, float val) { + return v8::Number::New(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + float* out) { + if (!val->IsNumber()) + return false; + *out = static_cast(val->NumberValue()); + return true; +} + +Local Converter::ToV8(Isolate* isolate, double val) { + return v8::Number::New(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + double* out) { + if (!val->IsNumber()) + return false; + *out = val->NumberValue(); + return true; +} + +Local Converter::ToV8( + Isolate* isolate, const char* val) { + return v8::String::NewFromUtf8(isolate, val); +} + +Local Converter::ToV8( + Isolate* isolate, const base::StringPiece& val) { + return v8::String::NewFromUtf8(isolate, + val.data(), + v8::String::kNormalString, + static_cast(val.length())); +} + +Local Converter::ToV8(Isolate* isolate, + const std::string& val) { + return Converter::ToV8(isolate, val); +} + +bool Converter::FromV8(Isolate* isolate, Local val, + std::string* out) { + if (!val->IsString()) + return false; + Local str = Local::Cast(val); + int length = str->Utf8Length(); + out->resize(length); + str->WriteUtf8(&(*out)[0], length, NULL, String::NO_NULL_TERMINATION); + return true; +} + +Local Converter>::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, Local val, + Local* out) { + if (!val->IsFunction()) + return false; + *out = Local::Cast(val); + return true; +} + +Local Converter >::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, Local val, + Local* out) { + if (!val->IsObject()) + return false; + *out = Local::Cast(val); + return true; +} + +Local Converter >::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, Local val, + Local* out) { + if (!val->IsString()) + return false; + *out = Local::Cast(val); + return true; +} + +Local Converter >::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, + v8::Local val, + Local* out) { + if (!val->IsExternal()) + return false; + *out = Local::Cast(val); + return true; +} + +Local Converter >::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, + v8::Local val, + Local* out) { + if (!val->IsArray()) + return false; + *out = Local::Cast(val); + return true; +} + +Local Converter >::ToV8(Isolate* isolate, + Local val) { + return val; +} + +bool Converter >::FromV8(Isolate* isolate, Local val, + Local* out) { + *out = val; + return true; +} + +v8::Local StringToSymbol(v8::Isolate* isolate, + const base::StringPiece& val) { + return v8::String::NewFromUtf8(isolate, + val.data(), + v8::String::kInternalizedString, + static_cast(val.length())); +} + +std::string V8ToString(v8::Local value) { + if (value.IsEmpty()) + return std::string(); + std::string result; + if (!ConvertFromV8(NULL, value, &result)) + return std::string(); + return result; +} + +} // namespace mate diff --git a/native_mate/native_mate/converter.h b/native_mate/native_mate/converter.h new file mode 100644 index 0000000000000..ec3649c19bb7a --- /dev/null +++ b/native_mate/native_mate/converter.h @@ -0,0 +1,364 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_CONVERTER_H_ +#define NATIVE_MATE_CONVERTER_H_ + +#include +#include +#include +#include + +#include "base/strings/string_piece.h" +#include "v8/include/v8.h" + +namespace mate { + +template +bool SetProperty(v8::Isolate* isolate, + v8::Local object, + KeyType key, + v8::Local value) { + auto maybe = object->Set(isolate->GetCurrentContext(), key, value); + return !maybe.IsNothing() && maybe.FromJust(); +} + +template +struct ToV8ReturnsMaybe { + static const bool value = false; +}; + +template +struct Converter {}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, void* val) { + return v8::Undefined(isolate); + } +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, std::nullptr_t val) { + return v8::Null(isolate); + } +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + bool val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + bool* out); +}; + +#if !defined(OS_LINUX) && !defined(OS_FREEBSD) +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + unsigned long val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + unsigned long* out); +}; +#endif + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + int32_t val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + int32_t* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + uint32_t val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + uint32_t* out); +}; + +template<> +struct Converter { + // Warning: JavaScript cannot represent 64 integers precisely. + static v8::Local ToV8(v8::Isolate* isolate, + int64_t val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + int64_t* out); +}; + +template<> +struct Converter { + // Warning: JavaScript cannot represent 64 integers precisely. + static v8::Local ToV8(v8::Isolate* isolate, + uint64_t val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + uint64_t* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + float val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + float* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + double val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + double* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, const char* val); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const base::StringPiece& val); + // No conversion out is possible because StringPiece does not contain storage. +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const std::string& val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::string* out); +}; + +v8::Local StringToSymbol(v8::Isolate* isolate, + const base::StringPiece& input); + +std::string V8ToString(v8::Local value); + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template<> +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + v8::Local val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + v8::Local* out); +}; + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const std::vector& val) { + v8::Local result( + v8::Array::New(isolate, static_cast(val.size()))); + for (size_t i = 0; i < val.size(); ++i) { + result->Set(static_cast(i), Converter::ToV8(isolate, val[i])); + } + return result; + } + + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::vector* out) { + if (!val->IsArray()) + return false; + + std::vector result; + v8::Local array(v8::Local::Cast(val)); + uint32_t length = array->Length(); + for (uint32_t i = 0; i < length; ++i) { + T item; + if (!Converter::FromV8(isolate, array->Get(i), &item)) + return false; + result.push_back(item); + } + + out->swap(result); + return true; + } +}; + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const std::set& val) { + v8::Local result( + v8::Array::New(isolate, static_cast(val.size()))); + typename std::set::const_iterator it; + int i; + for (i = 0, it = val.begin(); it != val.end(); ++it, ++i) + result->Set(i, Converter::ToV8(isolate, *it)); + return result; + } + + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::set* out) { + if (!val->IsArray()) + return false; + + std::set result; + v8::Local array(v8::Local::Cast(val)); + uint32_t length = array->Length(); + for (uint32_t i = 0; i < length; ++i) { + T item; + if (!Converter::FromV8(isolate, array->Get(i), &item)) + return false; + result.insert(item); + } + + out->swap(result); + return true; + } +}; + +template +struct Converter > { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + std::map * out) { + if (!val->IsObject()) + return false; + + v8::Local dict = val->ToObject(); + v8::Local keys = dict->GetOwnPropertyNames(); + for (uint32_t i = 0; i < keys->Length(); ++i) { + v8::Local key = keys->Get(i); + T value; + if (Converter::FromV8(isolate, dict->Get(key), &value)) + (*out)[V8ToString(key)] = std::move(value); + } + return true; + } + static v8::Local ToV8(v8::Isolate* isolate, + const std::map& val) { + v8::Local result = v8::Object::New(isolate); + for (auto i = val.begin(); i != val.end(); i++) { + result->Set(Converter::ToV8(isolate, i->first), + Converter::ToV8(isolate, i->second)); + } + return result; + } +}; + +// Convenience functions that deduce T. +template +v8::Local ConvertToV8(v8::Isolate* isolate, const T& input) { + return Converter::ToV8(isolate, input); +} + +inline v8::Local ConvertToV8(v8::Isolate* isolate, + const char* input) { + return Converter::ToV8(isolate, input); +} + +template +v8::MaybeLocal ConvertToV8(v8::Local context, + const T& input) { + return Converter::ToV8(context, input); +} + +template::value> struct ToV8Traits; + +template +struct ToV8Traits { + static bool TryConvertToV8(v8::Isolate* isolate, + const T& input, + v8::Local* output) { + auto maybe = ConvertToV8(isolate->GetCurrentContext(), input); + if (maybe.IsEmpty()) + return false; + *output = maybe.ToLocalChecked(); + return true; + } +}; + +template +struct ToV8Traits { + static bool TryConvertToV8(v8::Isolate* isolate, + const T& input, + v8::Local* output) { + *output = ConvertToV8(isolate, input); + return true; + } +}; + +template +bool TryConvertToV8(v8::Isolate* isolate, + const T& input, + v8::Local* output) { + return ToV8Traits::TryConvertToV8(isolate, input, output); +} + +template +bool ConvertFromV8(v8::Isolate* isolate, v8::Local input, + T* result) { + return Converter::FromV8(isolate, input, result); +} + +inline v8::Local StringToV8( + v8::Isolate* isolate, + const base::StringPiece& input) { + return ConvertToV8(isolate, input).As(); +} + +} // namespace mate + +#endif // NATIVE_MATE_CONVERTER_H_ diff --git a/native_mate/native_mate/dictionary.cc b/native_mate/native_mate/dictionary.cc new file mode 100644 index 0000000000000..3caff11459744 --- /dev/null +++ b/native_mate/native_mate/dictionary.cc @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/dictionary.h" + +namespace mate { + +Dictionary::Dictionary() + : isolate_(NULL) { +} + +Dictionary::Dictionary(v8::Isolate* isolate, + v8::Local object) + : isolate_(isolate), + object_(object) { +} + +Dictionary::~Dictionary() { +} + +Dictionary Dictionary::CreateEmpty(v8::Isolate* isolate) { + return Dictionary(isolate, v8::Object::New(isolate)); +} + +v8::Local Dictionary::GetHandle() const { + return object_; +} + +v8::Local Converter::ToV8(v8::Isolate* isolate, + Dictionary val) { + return val.GetHandle(); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Local val, + Dictionary* out) { + if (!val->IsObject() || val->IsFunction()) + return false; + *out = Dictionary(isolate, v8::Local::Cast(val)); + return true; +} + +} // namespace mate diff --git a/native_mate/native_mate/dictionary.h b/native_mate/native_mate/dictionary.h new file mode 100644 index 0000000000000..9e80cb99acfdb --- /dev/null +++ b/native_mate/native_mate/dictionary.h @@ -0,0 +1,146 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_DICTIONARY_H_ +#define NATIVE_MATE_DICTIONARY_H_ + +#include "native_mate/converter.h" +#include "native_mate/object_template_builder.h" + +namespace mate { + +namespace internal { + +// Returns true if |maybe| is both a value, and that value is true. +inline bool IsTrue(v8::Maybe maybe) { + return maybe.IsJust() && maybe.FromJust(); +} + +} // namespace internal + +// Dictionary is useful when writing bindings for a function that either +// receives an arbitrary JavaScript object as an argument or returns an +// arbitrary JavaScript object as a result. For example, Dictionary is useful +// when you might use the |dictionary| type in WebIDL: +// +// http://heycam.github.io/webidl/#idl-dictionaries +// +// WARNING: You cannot retain a Dictionary object in the heap. The underlying +// storage for Dictionary is tied to the closest enclosing +// v8::HandleScope. Generally speaking, you should store a Dictionary +// on the stack. +// +class Dictionary { + public: + Dictionary(); + Dictionary(v8::Isolate* isolate, v8::Local object); + virtual ~Dictionary(); + + static Dictionary CreateEmpty(v8::Isolate* isolate); + + template + bool Get(const base::StringPiece& key, T* out) const { + // Check for existence before getting, otherwise this method will always + // returns true when T == v8::Local. + v8::Local context = isolate_->GetCurrentContext(); + v8::Local v8_key = StringToV8(isolate_, key); + if (!internal::IsTrue(GetHandle()->Has(context, v8_key))) + return false; + + v8::Local val; + if (!GetHandle()->Get(context, v8_key).ToLocal(&val)) + return false; + return ConvertFromV8(isolate_, val, out); + } + + template + bool GetHidden(const base::StringPiece& key, T* out) const { + v8::Local context = isolate_->GetCurrentContext(); + v8::Local privateKey = + v8::Private::ForApi(isolate_, StringToV8(isolate_, key)); + v8::Local value; + v8::Maybe result = + GetHandle()->HasPrivate(context, privateKey); + if (internal::IsTrue(result) && + GetHandle()->GetPrivate(context, privateKey).ToLocal(&value)) + return ConvertFromV8(isolate_, value, out); + return false; + } + + template + bool Set(const base::StringPiece& key, const T& val) { + v8::Local v8_value; + if (!TryConvertToV8(isolate_, val, &v8_value)) + return false; + v8::Maybe result = + GetHandle()->Set(isolate_->GetCurrentContext(), + StringToV8(isolate_, key), + v8_value); + return !result.IsNothing() && result.FromJust(); + } + + template + bool SetHidden(const base::StringPiece& key, T val) { + v8::Local v8_value; + if (!TryConvertToV8(isolate_, val, &v8_value)) + return false; + v8::Local context = isolate_->GetCurrentContext(); + v8::Local privateKey = + v8::Private::ForApi(isolate_, StringToV8(isolate_, key)); + v8::Maybe result = + GetHandle()->SetPrivate(context, privateKey, v8_value); + return !result.IsNothing() && result.FromJust(); + } + + template + bool SetReadOnly(const base::StringPiece& key, T val) { + v8::Local v8_value; + if (!TryConvertToV8(isolate_, val, &v8_value)) + return false; + v8::Maybe result = + GetHandle()->DefineOwnProperty(isolate_->GetCurrentContext(), + StringToV8(isolate_, key), + v8_value, + v8::ReadOnly); + return !result.IsNothing() && result.FromJust(); + } + + template + bool SetMethod(const base::StringPiece& key, const T& callback) { + return GetHandle()->Set( + StringToV8(isolate_, key), + CallbackTraits::CreateTemplate(isolate_, callback)->GetFunction()); + } + + bool Delete(const base::StringPiece& key) { + v8::Maybe result = GetHandle()->Delete(isolate_->GetCurrentContext(), + StringToV8(isolate_, key)); + return !result.IsNothing() && result.FromJust(); + } + + bool IsEmpty() const { return isolate() == NULL; } + + virtual v8::Local GetHandle() const; + + v8::Isolate* isolate() const { return isolate_; } + + protected: + v8::Isolate* isolate_; + + private: + v8::Local object_; +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + Dictionary val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + Dictionary* out); +}; + +} // namespace mate + +#endif // NATIVE_MATE_DICTIONARY_H_ diff --git a/native_mate/native_mate/function_template.cc b/native_mate/native_mate/function_template.cc new file mode 100644 index 0000000000000..a4bcb1141b743 --- /dev/null +++ b/native_mate/native_mate/function_template.cc @@ -0,0 +1,40 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/function_template.h" + +namespace mate { + +namespace internal { + +CallbackHolderBase::CallbackHolderBase(v8::Isolate* isolate) + : v8_ref_(isolate, v8::External::New(isolate, this)) { + v8_ref_.SetWeak(this, &CallbackHolderBase::FirstWeakCallback, + v8::WeakCallbackType::kParameter); +} + +CallbackHolderBase::~CallbackHolderBase() { + DCHECK(v8_ref_.IsEmpty()); +} + +v8::Local CallbackHolderBase::GetHandle(v8::Isolate* isolate) { + return v8::Local::New(isolate, v8_ref_); +} + +// static +void CallbackHolderBase::FirstWeakCallback( + const v8::WeakCallbackInfo& data) { + data.GetParameter()->v8_ref_.Reset(); + data.SetSecondPassCallback(SecondWeakCallback); +} + +// static +void CallbackHolderBase::SecondWeakCallback( + const v8::WeakCallbackInfo& data) { + delete data.GetParameter(); +} + +} // namespace internal + +} // namespace mate diff --git a/native_mate/native_mate/function_template.h b/native_mate/native_mate/function_template.h new file mode 100644 index 0000000000000..abbe7b5326e1e --- /dev/null +++ b/native_mate/native_mate/function_template.h @@ -0,0 +1,285 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_FUNCTION_TEMPLATE_H_ +#define NATIVE_MATE_FUNCTION_TEMPLATE_H_ + +#include "base/callback.h" +#include "base/logging.h" +#include "native_mate/arguments.h" +#include "native_mate/wrappable_base.h" +#include "v8/include/v8.h" + +namespace mate { + +enum CreateFunctionTemplateFlags { + HolderIsFirstArgument = 1 << 0, +}; + +namespace internal { + +struct Destroyable { + static void Destroy(Arguments* args) { + if (IsDestroyed(args)) + return; + + v8::Local holder = args->GetHolder(); + delete static_cast( + holder->GetAlignedPointerFromInternalField(0)); + holder->SetAlignedPointerInInternalField(0, nullptr); + } + static bool IsDestroyed(Arguments* args) { + v8::Local holder = args->GetHolder(); + return holder->InternalFieldCount() == 0 || + holder->GetAlignedPointerFromInternalField(0) == nullptr; + } +}; + +template +struct CallbackParamTraits { + typedef T LocalType; +}; +template +struct CallbackParamTraits { + typedef T LocalType; +}; +template +struct CallbackParamTraits { + typedef T* LocalType; +}; + + +// CallbackHolder and CallbackHolderBase are used to pass a base::Callback from +// CreateFunctionTemplate through v8 (via v8::FunctionTemplate) to +// DispatchToCallback, where it is invoked. + +// This simple base class is used so that we can share a single object template +// among every CallbackHolder instance. +class CallbackHolderBase { + public: + v8::Local GetHandle(v8::Isolate* isolate); + + protected: + explicit CallbackHolderBase(v8::Isolate* isolate); + virtual ~CallbackHolderBase(); + + private: + static void FirstWeakCallback( + const v8::WeakCallbackInfo& data); + static void SecondWeakCallback( + const v8::WeakCallbackInfo& data); + + v8::Global v8_ref_; + + DISALLOW_COPY_AND_ASSIGN(CallbackHolderBase); +}; + +template +class CallbackHolder : public CallbackHolderBase { + public: + CallbackHolder(v8::Isolate* isolate, + const base::Callback& callback, + int flags) + : CallbackHolderBase(isolate), callback(callback), flags(flags) {} + base::Callback callback; + int flags; + private: + virtual ~CallbackHolder() {} + + DISALLOW_COPY_AND_ASSIGN(CallbackHolder); +}; + +template +bool GetNextArgument(Arguments* args, int create_flags, bool is_first, + T* result) { + if (is_first && (create_flags & HolderIsFirstArgument) != 0) { + return args->GetHolder(result); + } else { + return args->GetNext(result); + } +} + +// For advanced use cases, we allow callers to request the unparsed Arguments +// object and poke around in it directly. +inline bool GetNextArgument(Arguments* args, int create_flags, bool is_first, + Arguments* result) { + *result = *args; + return true; +} +inline bool GetNextArgument(Arguments* args, int create_flags, bool is_first, + Arguments** result) { + *result = args; + return true; +} + +// It's common for clients to just need the isolate, so we make that easy. +inline bool GetNextArgument(Arguments* args, int create_flags, + bool is_first, v8::Isolate** result) { + *result = args->isolate(); + 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 +struct IndicesHolder {}; + +template +struct IndicesGenerator { + using type = typename IndicesGenerator::type; +}; +template +struct IndicesGenerator<0, indices...> { + using type = IndicesHolder; +}; + +// Class template for extracting and storing single argument for callback +// at position |index|. +template +struct ArgumentHolder { + using ArgLocalType = typename CallbackParamTraits::LocalType; + + ArgLocalType value; + bool ok; + + ArgumentHolder(Arguments* args, int create_flags) + : ok(false) { + if (index == 0 && + (create_flags & HolderIsFirstArgument) && + Destroyable::IsDestroyed(args)) { + args->ThrowError("Object has been destroyed"); + return; + } + ok = GetNextArgument(args, create_flags, 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(); + } + } +}; + +// Class template for converting arguments from JavaScript to C++ and running +// the callback with them. +template +class Invoker {}; + +template +class Invoker, ArgTypes...> + : public ArgumentHolder... { + 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(Arguments* args, int create_flags) + : ArgumentHolder(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/varible. + (void)create_flags; + } + + bool IsOK() { + return And(ArgumentHolder::ok...); + } + + template + void DispatchToCallback(base::Callback callback) { + v8::MicrotasksScope script_scope( + args_->isolate(), v8::MicrotasksScope::kRunMicrotasks); + args_->Return(callback.Run(ArgumentHolder::value...)); + } + + // In C++, you can declare the function foo(void), but you can't pass a void + // expression to foo. As a result, we must specialize the case of Callbacks + // that have the void return type. + void DispatchToCallback(base::Callback callback) { + v8::MicrotasksScope script_scope( + args_->isolate(), v8::MicrotasksScope::kRunMicrotasks); + callback.Run(ArgumentHolder::value...); + } + + private: + static bool And() { return true; } + template + static bool And(bool arg1, T... args) { + return arg1 && And(args...); + } + + Arguments* args_; +}; + +// DispatchToCallback converts all the JavaScript arguments to C++ types and +// invokes the base::Callback. +template +struct Dispatcher {}; + +template +struct Dispatcher { + static void DispatchToCallback( + const v8::FunctionCallbackInfo& info) { + Arguments args(info); + v8::Local v8_holder; + args.GetData(&v8_holder); + CallbackHolderBase* holder_base = reinterpret_cast( + v8_holder->Value()); + + typedef CallbackHolder HolderT; + HolderT* holder = static_cast(holder_base); + + using Indices = typename IndicesGenerator::type; + Invoker invoker(&args, holder->flags); + if (invoker.IsOK()) + invoker.DispatchToCallback(holder->callback); + } +}; + +} // namespace internal + + +// CreateFunctionTemplate creates a v8::FunctionTemplate that will create +// JavaScript functions that execute a provided C++ function or base::Callback. +// JavaScript arguments are automatically converted via gin::Converter, as is +// the return value of the C++ function, if any. +// +// 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. +template +v8::Local CreateFunctionTemplate( + v8::Isolate* isolate, const base::Callback callback, + int callback_flags = 0) { + typedef internal::CallbackHolder HolderT; + HolderT* holder = new HolderT(isolate, callback, callback_flags); + + return v8::FunctionTemplate::New( + isolate, + &internal::Dispatcher::DispatchToCallback, + ConvertToV8 >(isolate, + holder->GetHandle(isolate))); +} + +// CreateFunctionHandler installs a CallAsFunction handler on the given +// object template that forwards to a provided C++ function or base::Callback. +template +void CreateFunctionHandler(v8::Isolate* isolate, + v8::Local tmpl, + const base::Callback callback, + int callback_flags = 0) { + typedef internal::CallbackHolder HolderT; + HolderT* holder = new HolderT(isolate, callback, callback_flags); + tmpl->SetCallAsFunctionHandler(&internal::Dispatcher::DispatchToCallback, + ConvertToV8 >( + isolate, holder->GetHandle(isolate))); +} + +} // namespace mate + +#endif // NATIVE_MATE_FUNCTION_TEMPLATE_H_ diff --git a/native_mate/native_mate/handle.h b/native_mate/native_mate/handle.h new file mode 100644 index 0000000000000..60bd2348dd6c9 --- /dev/null +++ b/native_mate/native_mate/handle.h @@ -0,0 +1,72 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_HANDLE_H_ +#define NATIVE_MATE_HANDLE_H_ + +#include "native_mate/converter.h" + +namespace mate { + +// You can use mate::Handle on the stack to retain a mate::Wrappable object. +// Currently we don't have a mechanism for retaining a mate::Wrappable object +// in the C++ heap because strong references from C++ to V8 can cause memory +// leaks. +template +class Handle { + public: + Handle() : object_(NULL) {} + + Handle(v8::Local wrapper, T* object) + : wrapper_(wrapper), + object_(object) { + } + + bool IsEmpty() const { return !object_; } + + void Clear() { + wrapper_.Clear(); + object_ = NULL; + } + + T* operator->() const { return object_; } + v8::Local ToV8() const { return wrapper_; } + T* get() const { return object_; } + + private: + v8::Local wrapper_; + T* object_; +}; + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const mate::Handle& val) { + return val.ToV8(); + } + static bool FromV8(v8::Isolate* isolate, v8::Local val, + mate::Handle* out) { + T* object = NULL; + if (val->IsNull() || val->IsUndefined()) { + *out = mate::Handle(); + return true; + } + if (!Converter::FromV8(isolate, val, &object)) { + return false; + } + *out = mate::Handle(val->ToObject(), object); + return true; + } +}; + +// This function is a convenient way to create a handle from a raw pointer +// without having to write out the type of the object explicitly. +template +mate::Handle CreateHandle(v8::Isolate* isolate, T* object) { + return mate::Handle(object->GetWrapper(), object); +} + +} // namespace mate + +#endif // NATIVE_MATE_HANDLE_H_ diff --git a/native_mate/native_mate/object_template_builder.cc b/native_mate/native_mate/object_template_builder.cc new file mode 100644 index 0000000000000..c64e38fa1e2c6 --- /dev/null +++ b/native_mate/native_mate/object_template_builder.cc @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/object_template_builder.h" + +namespace mate { + +ObjectTemplateBuilder::ObjectTemplateBuilder( + v8::Isolate* isolate, + v8::Local templ) + : isolate_(isolate), template_(templ) { +} + +ObjectTemplateBuilder::~ObjectTemplateBuilder() { +} + +ObjectTemplateBuilder& ObjectTemplateBuilder::SetImpl( + const base::StringPiece& name, v8::Local val) { + template_->Set(StringToSymbol(isolate_, name), val); + return *this; +} + +ObjectTemplateBuilder& ObjectTemplateBuilder::SetPropertyImpl( + const base::StringPiece& name, v8::Local getter, + v8::Local setter) { + template_->SetAccessorProperty(StringToSymbol(isolate_, name), getter, + setter); + return *this; +} + +ObjectTemplateBuilder& ObjectTemplateBuilder::MakeDestroyable() { + SetMethod("destroy", base::Bind(internal::Destroyable::Destroy)); + SetMethod("isDestroyed", base::Bind(internal::Destroyable::IsDestroyed)); + return *this; +} + +v8::Local ObjectTemplateBuilder::Build() { + v8::Local result = template_; + template_.Clear(); + return result; +} + +} // namespace mate diff --git a/native_mate/native_mate/object_template_builder.h b/native_mate/native_mate/object_template_builder.h new file mode 100644 index 0000000000000..533576f98630d --- /dev/null +++ b/native_mate/native_mate/object_template_builder.h @@ -0,0 +1,131 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_OBJECT_TEMPLATE_BUILDER_H_ +#define NATIVE_MATE_OBJECT_TEMPLATE_BUILDER_H_ + +#include "base/bind.h" +#include "base/callback.h" +#include "base/strings/string_piece.h" +#include "native_mate/converter.h" +#include "native_mate/function_template.h" +#include "v8/include/v8.h" + +namespace mate { + +namespace { + +// Base template - used only for non-member function pointers. Other types +// either go to one of the below specializations, or go here and fail to compile +// because of base::Bind(). +template +struct CallbackTraits { + static v8::Local CreateTemplate( + v8::Isolate* isolate, T callback) { + return CreateFunctionTemplate(isolate, base::Bind(callback)); + } +}; + +// Specialization for base::Callback. +template +struct CallbackTraits > { + static v8::Local CreateTemplate( + v8::Isolate* isolate, const base::Callback& callback) { + return CreateFunctionTemplate(isolate, callback); + } +}; + +// Specialization for member function pointers. We need to handle this case +// specially because the first parameter for callbacks to MFP should typically +// come from the the JavaScript "this" object the function was called on, not +// from the first normal parameter. +template +struct CallbackTraits::value>::type> { + static v8::Local CreateTemplate( + v8::Isolate* isolate, T callback) { + int flags = HolderIsFirstArgument; + return CreateFunctionTemplate(isolate, base::Bind(callback), flags); + } +}; + +// This specialization allows people to construct function templates directly if +// they need to do fancier stuff. +template<> +struct CallbackTraits > { + static v8::Local CreateTemplate( + v8::Local templ) { + return templ; + } +}; + +} // namespace + + +// ObjectTemplateBuilder provides a handy interface to creating +// v8::ObjectTemplate instances with various sorts of properties. +class ObjectTemplateBuilder { + public: + explicit ObjectTemplateBuilder( + v8::Isolate* isolate, + v8::Local templ); + ~ObjectTemplateBuilder(); + + // It's against Google C++ style to return a non-const ref, but we take some + // poetic license here in order that all calls to Set() can be via the '.' + // operator and line up nicely. + template + ObjectTemplateBuilder& SetValue(const base::StringPiece& name, T val) { + return SetImpl(name, ConvertToV8(isolate_, val)); + } + + // In the following methods, T and U can be function pointer, member function + // pointer, base::Callback, or v8::FunctionTemplate. Most clients will want to + // use one of the first two options. Also see mate::CreateFunctionTemplate() + // for creating raw function templates. + template + ObjectTemplateBuilder& SetMethod(const base::StringPiece& name, + T callback) { + return SetImpl(name, + CallbackTraits::CreateTemplate(isolate_, callback)); + } + template + ObjectTemplateBuilder& SetProperty(const base::StringPiece& name, + T getter) { + return SetPropertyImpl( + name, + CallbackTraits::CreateTemplate(isolate_, getter), + v8::Local()); + } + template + ObjectTemplateBuilder& SetProperty(const base::StringPiece& name, + T getter, + U setter) { + return SetPropertyImpl( + name, + CallbackTraits::CreateTemplate(isolate_, getter), + CallbackTraits::CreateTemplate(isolate_, setter)); + } + + // Add "destroy" and "isDestroyed" methods. + ObjectTemplateBuilder& MakeDestroyable(); + + v8::Local Build(); + + private: + ObjectTemplateBuilder& SetImpl(const base::StringPiece& name, + v8::Local val); + ObjectTemplateBuilder& SetPropertyImpl( + const base::StringPiece& name, v8::Local getter, + v8::Local setter); + + v8::Isolate* isolate_; + + // ObjectTemplateBuilder should only be used on the stack. + v8::Local template_; +}; + +} // namespace mate + +#endif // NATIVE_MATE_OBJECT_TEMPLATE_BUILDER_H_ diff --git a/native_mate/native_mate/persistent_dictionary.cc b/native_mate/native_mate/persistent_dictionary.cc new file mode 100644 index 0000000000000..fd68cdced275e --- /dev/null +++ b/native_mate/native_mate/persistent_dictionary.cc @@ -0,0 +1,34 @@ +// Copyright 2014 Cheng Zhao. All rights reserved. +// Use of this source code is governed by MIT license that can be found in the +// LICENSE file. + +#include "native_mate/persistent_dictionary.h" + +namespace mate { + +PersistentDictionary::PersistentDictionary() { +} + +PersistentDictionary::PersistentDictionary(v8::Isolate* isolate, + v8::Local object) + : handle_(new RefCountedPersistent(isolate, object)) { + isolate_ = isolate; +} + +PersistentDictionary::~PersistentDictionary() { +} + +v8::Local PersistentDictionary::GetHandle() const { + return handle_->NewHandle(); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Local val, + PersistentDictionary* out) { + if (!val->IsObject()) + return false; + *out = PersistentDictionary(isolate, v8::Local::Cast(val)); + return true; +} + +} // namespace mate diff --git a/native_mate/native_mate/persistent_dictionary.h b/native_mate/native_mate/persistent_dictionary.h new file mode 100644 index 0000000000000..26c8998632bd8 --- /dev/null +++ b/native_mate/native_mate/persistent_dictionary.h @@ -0,0 +1,36 @@ +// Copyright 2014 Cheng Zhao. All rights reserved. +// Use of this source code is governed by MIT license that can be found in the +// LICENSE file. + +#ifndef NATIVE_MATE_PERSISTENT_DICTIONARY_H_ +#define NATIVE_MATE_PERSISTENT_DICTIONARY_H_ + +#include "native_mate/dictionary.h" +#include "native_mate/scoped_persistent.h" + +namespace mate { + +// Like Dictionary, but stores object in persistent handle so you can keep it +// safely on heap. +class PersistentDictionary : public Dictionary { + public: + PersistentDictionary(); + PersistentDictionary(v8::Isolate* isolate, v8::Local object); + virtual ~PersistentDictionary(); + + v8::Local GetHandle() const override; + + private: + scoped_refptr > handle_; +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + PersistentDictionary* out); +}; + +} // namespace mate + +#endif // NATIVE_MATE_PERSISTENT_DICTIONARY_H_ diff --git a/native_mate/native_mate/promise.cc b/native_mate/native_mate/promise.cc new file mode 100644 index 0000000000000..81b79441e72e2 --- /dev/null +++ b/native_mate/native_mate/promise.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "native_mate/promise.h" + +namespace mate { + +Promise::Promise() + : isolate_(NULL) { +} + +Promise::Promise(v8::Isolate* isolate) + : isolate_(isolate) { + resolver_ = v8::Promise::Resolver::New(isolate); +} + +Promise::~Promise() { +} + +Promise Promise::Create(v8::Isolate* isolate) { + return Promise(isolate); +} + +Promise Promise::Create() { + return Promise::Create(v8::Isolate::GetCurrent()); +} + +void Promise::RejectWithErrorMessage(const std::string& string) { + v8::Local error_message = + v8::String::NewFromUtf8(isolate(), string.c_str()); + v8::Local error = v8::Exception::Error(error_message); + resolver_->Reject(mate::ConvertToV8(isolate(), error)); +} + +v8::Local Promise::GetHandle() const { + return resolver_->GetPromise(); +} + +v8::Local Converter::ToV8(v8::Isolate* isolate, + Promise val) { + return val.GetHandle(); +} + +} // namespace mate diff --git a/native_mate/native_mate/promise.h b/native_mate/native_mate/promise.h new file mode 100644 index 0000000000000..225ac6d048f9f --- /dev/null +++ b/native_mate/native_mate/promise.h @@ -0,0 +1,57 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef NATIVE_MATE_PROMISE_H_ +#define NATIVE_MATE_PROMISE_H_ + +#include "native_mate/converter.h" + +namespace mate { + +class Promise { + public: + Promise(); + Promise(v8::Isolate* isolate); + virtual ~Promise(); + + static Promise Create(v8::Isolate* isolate); + static Promise Create(); + + v8::Isolate* isolate() const { return isolate_; } + + virtual v8::Local GetHandle() const; + + template + void Resolve(T* value) { + resolver_->Resolve(mate::ConvertToV8(isolate(), value)); + } + + template + void Reject(T* value) { + resolver_->Reject(mate::ConvertToV8(isolate(), value)); + } + + void RejectWithErrorMessage(const std::string& error); + + protected: + v8::Isolate* isolate_; + + private: + v8::Local resolver_; +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + Promise val); + // TODO(MarshallOfSound): Implement FromV8 to allow promise chaining + // in native land + // static bool FromV8(v8::Isolate* isolate, + // v8::Local val, + // Promise* out); +}; + +} // namespace mate + +#endif // NATIVE_MATE_PROMISE_H_ diff --git a/native_mate/native_mate/scoped_persistent.h b/native_mate/native_mate/scoped_persistent.h new file mode 100644 index 0000000000000..5d9c8fff423a4 --- /dev/null +++ b/native_mate/native_mate/scoped_persistent.h @@ -0,0 +1,111 @@ +// Copyright 2014 Cheng Zhao. All rights reserved. +// Use of this source code is governed by MIT license that can be found in the +// LICENSE file. + +#ifndef NATIVE_MATE_SCOPED_PERSISTENT_H_ +#define NATIVE_MATE_SCOPED_PERSISTENT_H_ + +#include "base/memory/ref_counted.h" +#include "native_mate/converter.h" +#include "v8/include/v8.h" + +namespace mate { + +// A v8::Persistent handle to a V8 value which destroys and clears the +// underlying handle on destruction. +template +class ScopedPersistent { + public: + ScopedPersistent() : isolate_(v8::Isolate::GetCurrent()) {} + + ScopedPersistent(v8::Isolate* isolate, v8::Local handle) + : isolate_(isolate) { + reset(isolate, v8::Local::Cast(handle)); + } + + ~ScopedPersistent() { + reset(); + } + + void reset(v8::Isolate* isolate, v8::Local handle) { + if (!handle.IsEmpty()) { + isolate_ = isolate; + handle_.Reset(isolate, handle); + } else { + reset(); + } + } + + void reset() { + handle_.Reset(); + } + + bool IsEmpty() const { + return handle_.IsEmpty(); + } + + v8::Local NewHandle() const { + return NewHandle(isolate_); + } + + v8::Local NewHandle(v8::Isolate* isolate) const { + if (handle_.IsEmpty()) + return v8::Local(); + return v8::Local::New(isolate, handle_); + } + + template + void SetWeak(P* parameter, C callback) { + handle_.SetWeak(parameter, callback); + } + + v8::Isolate* isolate() const { return isolate_; } + + private: + v8::Isolate* isolate_; + v8::Persistent handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPersistent); +}; + +template +class RefCountedPersistent : public ScopedPersistent, + public base::RefCounted> { + public: + RefCountedPersistent() {} + + RefCountedPersistent(v8::Isolate* isolate, v8::Local handle) + : ScopedPersistent(isolate, handle) { + } + + protected: + friend class base::RefCounted>; + + ~RefCountedPersistent() {} + + private: + DISALLOW_COPY_AND_ASSIGN(RefCountedPersistent); +}; + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const ScopedPersistent& val) { + return val.NewHandle(isolate); + } + + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + ScopedPersistent* out) { + v8::Local converted; + if (!Converter >::FromV8(isolate, val, &converted)) + return false; + + out->reset(isolate, converted); + return true; + } +}; + +} // namespace mate + +#endif // NATIVE_MATE_SCOPED_PERSISTENT_H_ diff --git a/native_mate/native_mate/wrappable.cc b/native_mate/native_mate/wrappable.cc new file mode 100644 index 0000000000000..3d5ce44ac0a83 --- /dev/null +++ b/native_mate/native_mate/wrappable.cc @@ -0,0 +1,75 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "native_mate/wrappable.h" + +#include "base/logging.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" + +namespace mate { + +WrappableBase::WrappableBase() : isolate_(nullptr) {} + +WrappableBase::~WrappableBase() { + if (wrapper_.IsEmpty()) + return; + + GetWrapper()->SetAlignedPointerInInternalField(0, nullptr); + wrapper_.ClearWeak(); + wrapper_.Reset(); +} + +v8::Local WrappableBase::GetWrapper() const { + if (!wrapper_.IsEmpty()) + return v8::Local::New(isolate_, wrapper_); + else + return v8::Local(); +} + +void WrappableBase::InitWith(v8::Isolate* isolate, + v8::Local wrapper) { + CHECK(wrapper_.IsEmpty()); + isolate_ = isolate; + wrapper->SetAlignedPointerInInternalField(0, this); + wrapper_.Reset(isolate, wrapper); + wrapper_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter); + + // Call object._init if we have one. + v8::Local init; + if (Dictionary(isolate, wrapper).Get("_init", &init)) + init->Call(wrapper, 0, nullptr); + + AfterInit(isolate); +} + +// static +void WrappableBase::FirstWeakCallback( + const v8::WeakCallbackInfo& data) { + WrappableBase* wrappable = data.GetParameter(); + wrappable->wrapper_.Reset(); + data.SetSecondPassCallback(SecondWeakCallback); +} + +// static +void WrappableBase::SecondWeakCallback( + const v8::WeakCallbackInfo& data) { + WrappableBase* wrappable = data.GetParameter(); + delete wrappable; +} + +namespace internal { + +void* FromV8Impl(v8::Isolate* isolate, v8::Local val) { + if (!val->IsObject()) + return nullptr; + v8::Local obj = v8::Local::Cast(val); + if (obj->InternalFieldCount() != 1) + return nullptr; + return obj->GetAlignedPointerFromInternalField(0); +} + +} // namespace internal + +} // namespace mate diff --git a/native_mate/native_mate/wrappable.h b/native_mate/native_mate/wrappable.h new file mode 100644 index 0000000000000..489c5817481b8 --- /dev/null +++ b/native_mate/native_mate/wrappable.h @@ -0,0 +1,99 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef NATIVE_MATE_WRAPPABLE_H_ +#define NATIVE_MATE_WRAPPABLE_H_ + +#include "base/bind.h" +#include "native_mate/converter.h" +#include "native_mate/constructor.h" +#include "gin/per_isolate_data.h" + +namespace mate { + +namespace internal { + +void* FromV8Impl(v8::Isolate* isolate, v8::Local val); + +} // namespace internal + +template +class Wrappable : public WrappableBase { + public: + Wrappable() {} + + template + static void SetConstructor(v8::Isolate* isolate, + const base::Callback& constructor) { + v8::Local templ = CreateFunctionTemplate( + isolate, base::Bind(&internal::InvokeNew, constructor)); + templ->InstanceTemplate()->SetInternalFieldCount(1); + T::BuildPrototype(isolate, templ); + gin::PerIsolateData::From(isolate)->SetFunctionTemplate( + &kWrapperInfo, templ); + } + + static v8::Local GetConstructor(v8::Isolate* isolate) { + // Fill the object template. + auto data = gin::PerIsolateData::From(isolate); + auto templ = data->GetFunctionTemplate(&kWrapperInfo); + if (templ.IsEmpty()) { + templ = v8::FunctionTemplate::New(isolate); + templ->InstanceTemplate()->SetInternalFieldCount(1); + T::BuildPrototype(isolate, templ); + data->SetFunctionTemplate(&kWrapperInfo, templ); + } + return templ; + } + + protected: + // Init the class with T::BuildPrototype. + void Init(v8::Isolate* isolate) { + v8::Local templ = GetConstructor(isolate); + + // |wrapper| may be empty in some extreme cases, e.g., when + // Object.prototype.constructor is overwritten. + v8::Local wrapper; + if (!templ->InstanceTemplate()->NewInstance( + isolate->GetCurrentContext()).ToLocal(&wrapper)) { + // The current wrappable object will be no longer managed by V8. Delete + // this now. + delete this; + return; + } + InitWith(isolate, wrapper); + } + + private: + static gin::WrapperInfo kWrapperInfo; + + DISALLOW_COPY_AND_ASSIGN(Wrappable); +}; + +// static +template +gin::WrapperInfo Wrappable::kWrapperInfo = { gin::kEmbedderNativeGin }; + +// This converter handles any subclass of Wrappable. +template +struct Converter::value>::type> { + static v8::Local ToV8(v8::Isolate* isolate, T* val) { + if (val) + return val->GetWrapper(); + else + return v8::Null(isolate); + } + + static bool FromV8(v8::Isolate* isolate, v8::Local val, T** out) { + *out = static_cast(static_cast( + internal::FromV8Impl(isolate, val))); + return *out != nullptr; + } +}; + +} // namespace mate + +#endif // NATIVE_MATE_WRAPPABLE_H_ diff --git a/native_mate/native_mate/wrappable_base.h b/native_mate/native_mate/wrappable_base.h new file mode 100644 index 0000000000000..1c489cc37af30 --- /dev/null +++ b/native_mate/native_mate/wrappable_base.h @@ -0,0 +1,61 @@ +#ifndef NATIVE_MATE_WRAPPABLE_BASE_H_ +#define NATIVE_MATE_WRAPPABLE_BASE_H_ + +namespace mate { + +namespace internal { +struct Destroyable; +} + +// Wrappable is a base class for C++ objects that have corresponding v8 wrapper +// objects. To retain a Wrappable object on the stack, use a gin::Handle. +// +// USAGE: +// // my_class.h +// class MyClass : Wrappable { +// public: +// ... +// }; +// +// Subclasses should also typically have private constructors and expose a +// static Create function that returns a mate::Handle. Forcing creators through +// this static Create function will enforce that clients actually create a +// wrapper for the object. If clients fail to create a wrapper for a wrappable +// object, the object will leak because we use the weak callback from the +// wrapper as the signal to delete the wrapped object. +class WrappableBase { + public: + WrappableBase(); + virtual ~WrappableBase(); + + // Retrieve the v8 wrapper object cooresponding to this object. + v8::Local GetWrapper() const; + + // Returns the Isolate this object is created in. + v8::Isolate* isolate() const { return isolate_; } + + protected: + // Called after the "_init" method gets called in JavaScript. + virtual void AfterInit(v8::Isolate* isolate) {} + + // Bind the C++ class to the JS wrapper. + // This method should only be called by classes using Constructor. + virtual void InitWith(v8::Isolate* isolate, v8::Local wrapper); + + private: + friend struct internal::Destroyable; + + static void FirstWeakCallback( + const v8::WeakCallbackInfo& data); + static void SecondWeakCallback( + const v8::WeakCallbackInfo& data); + + v8::Isolate* isolate_; + v8::Global wrapper_; // Weak + + DISALLOW_COPY_AND_ASSIGN(WrappableBase); +}; + +} // namespace mate + +#endif // NATIVE_MATE_WRAPPABLE_BASE_H_ diff --git a/native_mate/native_mate_files.gypi b/native_mate/native_mate_files.gypi new file mode 100644 index 0000000000000..d7e9374971cab --- /dev/null +++ b/native_mate/native_mate_files.gypi @@ -0,0 +1,26 @@ +{ + 'variables': { + 'native_mate_files': [ + 'native_mate/arguments.cc', + 'native_mate/arguments.h', + 'native_mate/constructor.h', + 'native_mate/converter.cc', + 'native_mate/converter.h', + 'native_mate/dictionary.cc', + 'native_mate/dictionary.h', + 'native_mate/function_template.cc', + 'native_mate/function_template.h', + 'native_mate/handle.h', + 'native_mate/object_template_builder.cc', + 'native_mate/object_template_builder.h', + 'native_mate/persistent_dictionary.cc', + 'native_mate/persistent_dictionary.h', + 'native_mate/scoped_persistent.h', + 'native_mate/wrappable.cc', + 'native_mate/wrappable.h', + 'native_mate/wrappable_base.h', + 'native_mate/promise.h', + 'native_mate/promise.cc', + ], + }, +} diff --git a/script/pump.py b/script/pump.py new file mode 100755 index 0000000000000..e7565b6b4f3a5 --- /dev/null +++ b/script/pump.py @@ -0,0 +1,855 @@ +#!/usr/bin/env python +# +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""pump v0.2.0 - Pretty Useful for Meta Programming. + +A tool for preprocessor meta programming. Useful for generating +repetitive boilerplate code. Especially useful for writing C++ +classes, functions, macros, and templates that need to work with +various number of arguments. + +USAGE: + pump.py SOURCE_FILE + +EXAMPLES: + pump.py foo.cc.pump + Converts foo.cc.pump to foo.cc. + +GRAMMAR: + CODE ::= ATOMIC_CODE* + ATOMIC_CODE ::= $var ID = EXPRESSION + | $var ID = [[ CODE ]] + | $range ID EXPRESSION..EXPRESSION + | $for ID SEPARATOR [[ CODE ]] + | $($) + | $ID + | $(EXPRESSION) + | $if EXPRESSION [[ CODE ]] ELSE_BRANCH + | [[ CODE ]] + | RAW_CODE + SEPARATOR ::= RAW_CODE | EMPTY + ELSE_BRANCH ::= $else [[ CODE ]] + | $elif EXPRESSION [[ CODE ]] ELSE_BRANCH + | EMPTY + EXPRESSION has Python syntax. +""" + +__author__ = 'wan@google.com (Zhanyong Wan)' + +import os +import re +import sys + + +TOKEN_TABLE = [ + (re.compile(r'\$var\s+'), '$var'), + (re.compile(r'\$elif\s+'), '$elif'), + (re.compile(r'\$else\s+'), '$else'), + (re.compile(r'\$for\s+'), '$for'), + (re.compile(r'\$if\s+'), '$if'), + (re.compile(r'\$range\s+'), '$range'), + (re.compile(r'\$[_A-Za-z]\w*'), '$id'), + (re.compile(r'\$\(\$\)'), '$($)'), + (re.compile(r'\$'), '$'), + (re.compile(r'\[\[\n?'), '[['), + (re.compile(r'\]\]\n?'), ']]'), + ] + + +class Cursor: + """Represents a position (line and column) in a text file.""" + + def __init__(self, line=-1, column=-1): + self.line = line + self.column = column + + def __eq__(self, rhs): + return self.line == rhs.line and self.column == rhs.column + + def __ne__(self, rhs): + return not self == rhs + + def __lt__(self, rhs): + return self.line < rhs.line or ( + self.line == rhs.line and self.column < rhs.column) + + def __le__(self, rhs): + return self < rhs or self == rhs + + def __gt__(self, rhs): + return rhs < self + + def __ge__(self, rhs): + return rhs <= self + + def __str__(self): + if self == Eof(): + return 'EOF' + else: + return '%s(%s)' % (self.line + 1, self.column) + + def __add__(self, offset): + return Cursor(self.line, self.column + offset) + + def __sub__(self, offset): + return Cursor(self.line, self.column - offset) + + def Clone(self): + """Returns a copy of self.""" + + return Cursor(self.line, self.column) + + +# Special cursor to indicate the end-of-file. +def Eof(): + """Returns the special cursor to denote the end-of-file.""" + return Cursor(-1, -1) + + +class Token: + """Represents a token in a Pump source file.""" + + def __init__(self, start=None, end=None, value=None, token_type=None): + if start is None: + self.start = Eof() + else: + self.start = start + if end is None: + self.end = Eof() + else: + self.end = end + self.value = value + self.token_type = token_type + + def __str__(self): + return 'Token @%s: \'%s\' type=%s' % ( + self.start, self.value, self.token_type) + + def Clone(self): + """Returns a copy of self.""" + + return Token(self.start.Clone(), self.end.Clone(), self.value, + self.token_type) + + +def StartsWith(lines, pos, string): + """Returns True iff the given position in lines starts with 'string'.""" + + return lines[pos.line][pos.column:].startswith(string) + + +def FindFirstInLine(line, token_table): + best_match_start = -1 + for (regex, token_type) in token_table: + m = regex.search(line) + if m: + # We found regex in lines + if best_match_start < 0 or m.start() < best_match_start: + best_match_start = m.start() + best_match_length = m.end() - m.start() + best_match_token_type = token_type + + if best_match_start < 0: + return None + + return (best_match_start, best_match_length, best_match_token_type) + + +def FindFirst(lines, token_table, cursor): + """Finds the first occurrence of any string in strings in lines.""" + + start = cursor.Clone() + cur_line_number = cursor.line + for line in lines[start.line:]: + if cur_line_number == start.line: + line = line[start.column:] + m = FindFirstInLine(line, token_table) + if m: + # We found a regex in line. + (start_column, length, token_type) = m + if cur_line_number == start.line: + start_column += start.column + found_start = Cursor(cur_line_number, start_column) + found_end = found_start + length + return MakeToken(lines, found_start, found_end, token_type) + cur_line_number += 1 + # We failed to find str in lines + return None + + +def SubString(lines, start, end): + """Returns a substring in lines.""" + + if end == Eof(): + end = Cursor(len(lines) - 1, len(lines[-1])) + + if start >= end: + return '' + + if start.line == end.line: + return lines[start.line][start.column:end.column] + + result_lines = ([lines[start.line][start.column:]] + + lines[start.line + 1:end.line] + + [lines[end.line][:end.column]]) + return ''.join(result_lines) + + +def StripMetaComments(_str): + """Strip meta comments from each line in the given string.""" + + # First, completely remove lines containing nothing but a meta + # comment, including the trailing \n. + _str = re.sub(r'^\s*\$\$.*\n', '', _str) + + # Then, remove meta comments from contentful lines. + return re.sub(r'\s*\$\$.*', '', _str) + + +def MakeToken(lines, start, end, token_type): + """Creates a new instance of Token.""" + + return Token(start, end, SubString(lines, start, end), token_type) + + +def ParseToken(lines, pos, regex, token_type): + line = lines[pos.line][pos.column:] + m = regex.search(line) + if m and not m.start(): + return MakeToken(lines, pos, pos + m.end(), token_type) + else: + print 'ERROR: %s expected at %s.' % (token_type, pos) + sys.exit(1) + + +ID_REGEX = re.compile(r'[_A-Za-z]\w*') +EQ_REGEX = re.compile(r'=') +REST_OF_LINE_REGEX = re.compile(r'.*?(?=$|\$\$)') +OPTIONAL_WHITE_SPACES_REGEX = re.compile(r'\s*') +WHITE_SPACE_REGEX = re.compile(r'\s') +DOT_DOT_REGEX = re.compile(r'\.\.') + + +def Skip(lines, pos, regex): + line = lines[pos.line][pos.column:] + m = re.search(regex, line) + if m and not m.start(): + return pos + m.end() + else: + return pos + + +def SkipUntil(lines, pos, regex, token_type): + line = lines[pos.line][pos.column:] + m = re.search(regex, line) + if m: + return pos + m.start() + else: + print ('ERROR: %s expected on line %s after column %s.' % + (token_type, pos.line + 1, pos.column)) + sys.exit(1) + + +def ParseExpTokenInParens(lines, pos): + def ParseInParens(pos): + pos = Skip(lines, pos, OPTIONAL_WHITE_SPACES_REGEX) + pos = Skip(lines, pos, r'\(') + pos = Parse(pos) + pos = Skip(lines, pos, r'\)') + return pos + + def Parse(pos): + pos = SkipUntil(lines, pos, r'\(|\)', ')') + if SubString(lines, pos, pos + 1) == '(': + pos = Parse(pos + 1) + pos = Skip(lines, pos, r'\)') + return Parse(pos) + else: + return pos + + start = pos.Clone() + pos = ParseInParens(pos) + return MakeToken(lines, start, pos, 'exp') + + +def RStripNewLineFromToken(token): + if token.value.endswith('\n'): + return Token(token.start, token.end, token.value[:-1], token.token_type) + else: + return token + + +def TokenizeLines(lines, pos): + while True: + found = FindFirst(lines, TOKEN_TABLE, pos) + if not found: + yield MakeToken(lines, pos, Eof(), 'code') + return + + if found.start == pos: + prev_token = None + prev_token_rstripped = None + else: + prev_token = MakeToken(lines, pos, found.start, 'code') + prev_token_rstripped = RStripNewLineFromToken(prev_token) + + if found.token_type == '$var': + if prev_token_rstripped: + yield prev_token_rstripped + yield found + id_token = ParseToken(lines, found.end, ID_REGEX, 'id') + yield id_token + pos = Skip(lines, id_token.end, OPTIONAL_WHITE_SPACES_REGEX) + + eq_token = ParseToken(lines, pos, EQ_REGEX, '=') + yield eq_token + pos = Skip(lines, eq_token.end, r'\s*') + + if SubString(lines, pos, pos + 2) != '[[': + exp_token = ParseToken(lines, pos, REST_OF_LINE_REGEX, 'exp') + yield exp_token + pos = Cursor(exp_token.end.line + 1, 0) + elif found.token_type == '$for': + if prev_token_rstripped: + yield prev_token_rstripped + yield found + id_token = ParseToken(lines, found.end, ID_REGEX, 'id') + yield id_token + pos = Skip(lines, id_token.end, WHITE_SPACE_REGEX) + elif found.token_type == '$range': + if prev_token_rstripped: + yield prev_token_rstripped + yield found + id_token = ParseToken(lines, found.end, ID_REGEX, 'id') + yield id_token + pos = Skip(lines, id_token.end, OPTIONAL_WHITE_SPACES_REGEX) + + dots_pos = SkipUntil(lines, pos, DOT_DOT_REGEX, '..') + yield MakeToken(lines, pos, dots_pos, 'exp') + yield MakeToken(lines, dots_pos, dots_pos + 2, '..') + pos = dots_pos + 2 + new_pos = Cursor(pos.line + 1, 0) + yield MakeToken(lines, pos, new_pos, 'exp') + pos = new_pos + elif found.token_type == '$': + if prev_token: + yield prev_token + yield found + exp_token = ParseExpTokenInParens(lines, found.end) + yield exp_token + pos = exp_token.end + elif (found.token_type == ']]' or found.token_type == '$if' or + found.token_type == '$elif' or found.token_type == '$else'): + if prev_token_rstripped: + yield prev_token_rstripped + yield found + pos = found.end + else: + if prev_token: + yield prev_token + yield found + pos = found.end + + +def Tokenize(s): + """A generator that yields the tokens in the given string.""" + if s != '': + lines = s.splitlines(True) + for token in TokenizeLines(lines, Cursor(0, 0)): + yield token + + +class CodeNode: + def __init__(self, atomic_code_list=None): + self.atomic_code = atomic_code_list + + +class VarNode: + def __init__(self, identifier=None, atomic_code=None): + self.identifier = identifier + self.atomic_code = atomic_code + + +class RangeNode: + def __init__(self, identifier=None, exp1=None, exp2=None): + self.identifier = identifier + self.exp1 = exp1 + self.exp2 = exp2 + + +class ForNode: + def __init__(self, identifier=None, sep=None, code=None): + self.identifier = identifier + self.sep = sep + self.code = code + + +class ElseNode: + def __init__(self, else_branch=None): + self.else_branch = else_branch + + +class IfNode: + def __init__(self, exp=None, then_branch=None, else_branch=None): + self.exp = exp + self.then_branch = then_branch + self.else_branch = else_branch + + +class RawCodeNode: + def __init__(self, token=None): + self.raw_code = token + + +class LiteralDollarNode: + def __init__(self, token): + self.token = token + + +class ExpNode: + def __init__(self, token, python_exp): + self.token = token + self.python_exp = python_exp + + +def PopFront(a_list): + head = a_list[0] + a_list[:1] = [] + return head + + +def PushFront(a_list, elem): + a_list[:0] = [elem] + + +def PopToken(a_list, token_type=None): + token = PopFront(a_list) + if token_type is not None and token.token_type != token_type: + print 'ERROR: %s expected at %s' % (token_type, token.start) + print 'ERROR: %s found instead' % (token,) + sys.exit(1) + + return token + + +def PeekToken(a_list): + if not a_list: + return None + + return a_list[0] + + +def ParseExpNode(token): + python_exp = re.sub(r'([_A-Za-z]\w*)', r'self.GetValue("\1")', token.value) + return ExpNode(token, python_exp) + + +def ParseElseNode(tokens): + def Pop(token_type=None): + return PopToken(tokens, token_type) + + _next = PeekToken(tokens) + if not next: + return None + if _next.token_type == '$else': + Pop('$else') + Pop('[[') + code_node = ParseCodeNode(tokens) + Pop(']]') + return code_node + elif _next.token_type == '$elif': + Pop('$elif') + exp = Pop('code') + Pop('[[') + code_node = ParseCodeNode(tokens) + Pop(']]') + inner_else_node = ParseElseNode(tokens) + return CodeNode([IfNode(ParseExpNode(exp), code_node, inner_else_node)]) + elif not _next.value.strip(): + Pop('code') + return ParseElseNode(tokens) + else: + return None + + +def ParseAtomicCodeNode(tokens): + def Pop(token_type=None): + return PopToken(tokens, token_type) + + head = PopFront(tokens) + t = head.token_type + if t == 'code': + return RawCodeNode(head) + elif t == '$var': + id_token = Pop('id') + Pop('=') + _next = PeekToken(tokens) + if _next.token_type == 'exp': + exp_token = Pop() + return VarNode(id_token, ParseExpNode(exp_token)) + Pop('[[') + code_node = ParseCodeNode(tokens) + Pop(']]') + return VarNode(id_token, code_node) + elif t == '$for': + id_token = Pop('id') + next_token = PeekToken(tokens) + if next_token.token_type == 'code': + sep_token = next_token + Pop('code') + else: + sep_token = None + Pop('[[') + code_node = ParseCodeNode(tokens) + Pop(']]') + return ForNode(id_token, sep_token, code_node) + elif t == '$if': + exp_token = Pop('code') + Pop('[[') + code_node = ParseCodeNode(tokens) + Pop(']]') + else_node = ParseElseNode(tokens) + return IfNode(ParseExpNode(exp_token), code_node, else_node) + elif t == '$range': + id_token = Pop('id') + exp1_token = Pop('exp') + Pop('..') + exp2_token = Pop('exp') + return RangeNode(id_token, ParseExpNode(exp1_token), + ParseExpNode(exp2_token)) + elif t == '$id': + return ParseExpNode(Token(head.start + 1, head.end, head.value[1:], 'id')) + elif t == '$($)': + return LiteralDollarNode(head) + elif t == '$': + exp_token = Pop('exp') + return ParseExpNode(exp_token) + elif t == '[[': + code_node = ParseCodeNode(tokens) + Pop(']]') + return code_node + else: + PushFront(tokens, head) + return None + + +def ParseCodeNode(tokens): + atomic_code_list = [] + while True: + if not tokens: + break + atomic_code_node = ParseAtomicCodeNode(tokens) + if atomic_code_node: + atomic_code_list.append(atomic_code_node) + else: + break + return CodeNode(atomic_code_list) + + +def ParseToAST(pump_src_text): + """Convert the given Pump source text into an AST.""" + tokens = list(Tokenize(pump_src_text)) + code_node = ParseCodeNode(tokens) + return code_node + + +class Env: + def __init__(self): + self.variables = [] + self.ranges = [] + + def Clone(self): + clone = Env() + clone.variables = self.variables[:] + clone.ranges = self.ranges[:] + return clone + + def PushVariable(self, var, value): + # If value looks like an int, store it as an int. + try: + int_value = int(value) + if ('%s' % int_value) == value: + value = int_value + except Exception: + pass + self.variables[:0] = [(var, value)] + + def PopVariable(self): + self.variables[:1] = [] + + def PushRange(self, var, lower, upper): + self.ranges[:0] = [(var, lower, upper)] + + def PopRange(self): + self.ranges[:1] = [] + + def GetValue(self, identifier): + for (var, value) in self.variables: + if identifier == var: + return value + + print 'ERROR: meta variable %s is undefined.' % (identifier,) + sys.exit(1) + + def EvalExp(self, exp): + try: + result = eval(exp.python_exp) + except Exception, e: + print 'ERROR: caught exception %s: %s' % (e.__class__.__name__, e) + print ('ERROR: failed to evaluate meta expression %s at %s' % + (exp.python_exp, exp.token.start)) + sys.exit(1) + return result + + def GetRange(self, identifier): + for (var, lower, upper) in self.ranges: + if identifier == var: + return (lower, upper) + + print 'ERROR: range %s is undefined.' % (identifier,) + sys.exit(1) + + +class Output: + def __init__(self): + self.string = '' + + def GetLastLine(self): + index = self.string.rfind('\n') + if index < 0: + return '' + + return self.string[index + 1:] + + def Append(self, s): + self.string += s + + +def RunAtomicCode(env, node, output): + if isinstance(node, VarNode): + identifier = node.identifier.value.strip() + result = Output() + RunAtomicCode(env.Clone(), node.atomic_code, result) + value = result.string + env.PushVariable(identifier, value) + elif isinstance(node, RangeNode): + identifier = node.identifier.value.strip() + lower = int(env.EvalExp(node.exp1)) + upper = int(env.EvalExp(node.exp2)) + env.PushRange(identifier, lower, upper) + elif isinstance(node, ForNode): + identifier = node.identifier.value.strip() + if node.sep is None: + sep = '' + else: + sep = node.sep.value + (lower, upper) = env.GetRange(identifier) + for i in range(lower, upper + 1): + new_env = env.Clone() + new_env.PushVariable(identifier, i) + RunCode(new_env, node.code, output) + if i != upper: + output.Append(sep) + elif isinstance(node, RawCodeNode): + output.Append(node.raw_code.value) + elif isinstance(node, IfNode): + cond = env.EvalExp(node.exp) + if cond: + RunCode(env.Clone(), node.then_branch, output) + elif node.else_branch is not None: + RunCode(env.Clone(), node.else_branch, output) + elif isinstance(node, ExpNode): + value = env.EvalExp(node) + output.Append('%s' % (value,)) + elif isinstance(node, LiteralDollarNode): + output.Append('$') + elif isinstance(node, CodeNode): + RunCode(env.Clone(), node, output) + else: + print 'BAD' + print node + sys.exit(1) + + +def RunCode(env, code_node, output): + for atomic_code in code_node.atomic_code: + RunAtomicCode(env, atomic_code, output) + + +def IsSingleLineComment(cur_line): + return '//' in cur_line + + +def IsInPreprocessorDirective(prev_lines, cur_line): + if cur_line.lstrip().startswith('#'): + return True + return prev_lines and prev_lines[-1].endswith('\\') + + +def WrapComment(line, output): + loc = line.find('//') + before_comment = line[:loc].rstrip() + if before_comment == '': + indent = loc + else: + output.append(before_comment) + indent = len(before_comment) - len(before_comment.lstrip()) + prefix = indent*' ' + '// ' + max_len = 80 - len(prefix) + comment = line[loc + 2:].strip() + segs = [seg for seg in re.split(r'(\w+\W*)', comment) if seg != ''] + cur_line = '' + for seg in segs: + if len((cur_line + seg).rstrip()) < max_len: + cur_line += seg + else: + if cur_line.strip() != '': + output.append(prefix + cur_line.rstrip()) + cur_line = seg.lstrip() + if cur_line.strip() != '': + output.append(prefix + cur_line.strip()) + + +def WrapCode(line, line_concat, output): + indent = len(line) - len(line.lstrip()) + prefix = indent*' ' # Prefix of the current line + max_len = 80 - indent - len(line_concat) # Maximum length of the current line + new_prefix = prefix + 4*' ' # Prefix of a continuation line + new_max_len = max_len - 4 # Maximum length of a continuation line + # Prefers to wrap a line after a ',' or ';'. + segs = [seg for seg in re.split(r'([^,;]+[,;]?)', line.strip()) if seg != ''] + cur_line = '' # The current line without leading spaces. + for seg in segs: + # If the line is still too long, wrap at a space. + while cur_line == '' and len(seg.strip()) > max_len: + seg = seg.lstrip() + split_at = seg.rfind(' ', 0, max_len) + output.append(prefix + seg[:split_at].strip() + line_concat) + seg = seg[split_at + 1:] + prefix = new_prefix + max_len = new_max_len + + if len((cur_line + seg).rstrip()) < max_len: + cur_line = (cur_line + seg).lstrip() + else: + output.append(prefix + cur_line.rstrip() + line_concat) + prefix = new_prefix + max_len = new_max_len + cur_line = seg.lstrip() + if cur_line.strip() != '': + output.append(prefix + cur_line.strip()) + + +def WrapPreprocessorDirective(line, output): + WrapCode(line, ' \\', output) + + +def WrapPlainCode(line, output): + WrapCode(line, '', output) + + +def IsMultiLineIWYUPragma(line): + return re.search(r'/\* IWYU pragma: ', line) + + +def IsHeaderGuardIncludeOrOneLineIWYUPragma(line): + return (re.match(r'^#(ifndef|define|endif\s*//)\s*[\w_]+\s*$', line) or + re.match(r'^#include\s', line) or + # Don't break IWYU pragmas, either; that causes iwyu.py problems. + re.search(r'// IWYU pragma: ', line)) + + +def WrapLongLine(line, output): + line = line.rstrip() + if len(line) <= 80: + output.append(line) + elif IsSingleLineComment(line): + if IsHeaderGuardIncludeOrOneLineIWYUPragma(line): + # The style guide made an exception to allow long header guard lines, + # includes and IWYU pragmas. + output.append(line) + else: + WrapComment(line, output) + elif IsInPreprocessorDirective(output, line): + if IsHeaderGuardIncludeOrOneLineIWYUPragma(line): + # The style guide made an exception to allow long header guard lines, + # includes and IWYU pragmas. + output.append(line) + else: + WrapPreprocessorDirective(line, output) + elif IsMultiLineIWYUPragma(line): + output.append(line) + else: + WrapPlainCode(line, output) + + +def BeautifyCode(string): + lines = string.splitlines() + output = [] + for line in lines: + WrapLongLine(line, output) + output2 = [line.rstrip() for line in output] + return '\n'.join(output2) + '\n' + + +def ConvertFromPumpSource(src_text): + """Return the text generated from the given Pump source text.""" + ast = ParseToAST(StripMetaComments(src_text)) + output = Output() + RunCode(Env(), ast, output) + return BeautifyCode(output.string) + + +def main(argv): + if len(argv) == 1: + print __doc__ + sys.exit(1) + + file_path = argv[-1] + output_str = ConvertFromPumpSource(file(file_path, 'r').read()) + if file_path.endswith('.pump'): + output_file_path = file_path[:-5] + else: + output_file_path = '-' + if output_file_path == '-': + print output_str, + else: + output_file = file(output_file_path, 'w') + output_file.write('// This file was GENERATED by command:\n') + output_file.write('// %s %s\n' % + (os.path.basename(__file__), os.path.basename(file_path))) + output_file.write('// DO NOT EDIT BY HAND!!!\n\n') + output_file.write(output_str) + output_file.close() + + +if __name__ == '__main__': + main(sys.argv) diff --git a/vendor/native_mate b/vendor/native_mate deleted file mode 160000 index 875706f66008e..0000000000000 --- a/vendor/native_mate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 875706f66008e03a0c7a699de16d7e2bde0efb90