blob: 72ac857957eba83372213808f9d0b59854f3e5a9 [file] [log] [blame]
/*
* Copyright (C) 2013 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.
*/
#include "config.h"
#include "bindings/core/dart/DartJsInterop.h"
#include "bindings/core/dart/DartDOMWrapper.h"
#include "bindings/core/dart/DartHandleProxy.h"
#include "bindings/core/dart/DartJsInteropData.h"
#include "bindings/core/dart/DartPersistentValue.h"
#include "bindings/core/dart/DartUtilities.h"
#include "bindings/core/dart/V8Converter.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8Element.h"
#include "bindings/core/v8/V8RecursionScope.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "core/dom/Element.h"
#include "wtf/CurrentTime.h"
#include "wtf/StdLibExtras.h"
#include <dart_api.h>
#include <limits>
namespace blink {
const int JsObject::dartClassId = _JsObjectClassId;
const int JsFunction::dartClassId = _JsFunctionClassId;
const int JsArray::dartClassId = _JsArrayClassId;
const int JSFunction::dartClassId = _JSFunctionClassId;
const int JSArray::dartClassId = _JSArrayClassId;
// TODO(jacobr): v8::String::NewFromUtf8 usages should be cached for constant
// strings to improve performance.
/**
* Polyfill script to make Dart List objects look like JavaScript arrays when
* passed to JavaScript via JavaScript interop. Handling the edge cases
* requires patching the JavaScript Array prototype for a couple methods
* and implementing all of the JavaScript Array methods on the JavaScript
* proxy object for the Dart List. Some of the JavaScript methods are
* implemented in Dart and C++ code, some are implemented in this JavaScript
* polyfill script.
*/
const char* dartArrayPolyfill =
"(function() {"
// If filter has already been defined on the DartList prototype,
// another DOM isolate has beaten us to polyfilling.
" if ($DartList.prototype.hasOwnProperty('filter')) return;"
// This hack is required so that someDartList instanceof Array == true.
" $DartList.prototype.__proto__ = Array.prototype;"
" $DartList.__proto__ = Array;"
// This hack is required for libraries such as jQuery that detect arrays
// by looking at the constructor property treat $DartList as Array.
" $DartList.prototype.constructor = Array;"
" var isArray = Array.isArray;"
" var concat = Array.prototype.concat;"
" var blob = Blob;"
" function makeSafeArg(arg) {"
" return (arg instanceof $DartList) ? arg.$toJsArray() : arg;"
" };"
" function makeSafeArgs(args) {"
" var len = args.length;"
" for (var i = 0; i < len; ++i) {"
" var arg = args[i];"
" if (arg instanceof $DartList) {"
" args[i] = arg.$toJsArray();"
" }"
" }"
" };"
// If performance becomes an issue, we could implement these
// methods in Dart instead of creating a shallow copy JavaScript
// array containing the elements of the Dart List and calling
// the JavaScript method.
// Handle methods that take a callback with value, index, and thisArg
// parameters. The trick is we need to make thisArg reference the
// underlying Dart List rather than the JavaScript copy we create for
// implementation convenience.
" ['filter', 'forEach', 'some', 'every', 'map'].forEach(function(name) {"
" Object.defineProperty($DartList.prototype, name, {enumerable: false, value: function(callback, thisArg) {"
" var dartList = this;"
" return this.$toJsArray()[name](function(value, index) {"
" return callback.call(thisArg, value, index, dartList);"
" });"
" }});"
" });"
" ['slice', 'indexOf', 'lastIndexOf'].forEach(function(name) {"
" Object.defineProperty($DartList.prototype, name, {enumerable: false, value: function() {"
" var jsArray = this.$toJsArray();"
" return jsArray[name].apply(jsArray, arguments);"
" }});"
" });"
" ['reduce', 'reduceRight'].forEach(function(name) {"
" Object.defineProperty($DartList.prototype, name, {enumerable: false, value: function(callback, thisArg) {"
" var dartList = this;"
" return this.$toJsArray()[name](function(previousValue, currentValue, index) {"
" return callback.call(thisArg, previousValue, currentValue, index, dartList);"
" });"
" }});"
" });"
// Arguments to concat that are Arrays are treated differently.
// Warning: this will slow down general JavaScript array concat performance in Dartium.
" Array.prototype.concat = function() {"
" makeSafeArgs(arguments);"
" return concat.apply(this, arguments);"
" };"
// The Blob constructor cannot handle our psuedo Arrays.
// Warning: this will slow down general Blob constructor performance in Dartium.
// TODO(jacobr): this will also pose a problem for JavaScript instanceof Blob checks.
" Blob = function(array, options) {"
" if (arguments.length < 2) return new blob(makeSafeArg(array));"
" else return new blob(makeSafeArg(array), options);"
" };"
" Blob.prototype = blob.prototype;"
""
// Need to make sure that Array.isArray returns true for Dart lists.
" Array.isArray = function(arr) {"
" return isArray(arr) || (arr instanceof $DartList);"
" };"
"})();";
static v8::Local<v8::FunctionTemplate> dartFunctionTemplate(v8::Isolate*);
static v8::Local<v8::FunctionTemplate> dartObjectTemplate(v8::Isolate*);
static v8::Local<v8::FunctionTemplate> dartListTemplate(v8::Isolate*);
// TODO(jacobr): we should really be using this method everywhere instead of
// sticking interop methods in the dart:html _Utils class.
static Dart_Handle invokeTopLevelJsInteropMethod(DartDOMData* domData, const char* methodName, int argCount, Dart_Handle* args)
{
Dart_PersistentHandle library = domData->jsLibrary();
ASSERT(!Dart_IsError(library));
return Dart_Invoke(library, Dart_NewStringFromCString(methodName), argCount, args);
}
template<typename CallbackInfo>
void setJsReturnValue(DartDOMData* domData, CallbackInfo info, Dart_Handle result)
{
auto v8Isolate = domData->v8Isolate();
if (Dart_IsError(result)) {
V8ThrowException::throwException(v8::String::NewFromUtf8(v8Isolate, Dart_GetError(result)), v8Isolate);
} else {
Dart_Handle exception = 0;
v8::Local<v8::Value> ret = JsInterop::fromDart(domData, result, exception);
if (exception) {
V8ThrowException::throwException(V8Converter::stringToV8(Dart_ToString(exception)), v8Isolate);
return;
}
v8SetReturnValue(info, ret);
}
}
template<typename CallbackInfo>
void setJsReturnValueTyped(DartDOMData* domData, CallbackInfo info, Dart_Handle result)
{
auto v8Isolate = domData->v8Isolate();
if (Dart_IsError(result)) {
V8ThrowException::throwException(v8::String::NewFromUtf8(v8Isolate, Dart_GetError(result)), v8Isolate);
} else {
Dart_Handle exception = 0;
v8::Local<v8::Value> ret = JsInterop::fromDartTyped(domData, result, exception);
if (exception) {
V8ThrowException::throwException(V8Converter::stringToV8(Dart_ToString(exception)), v8Isolate);
return;
}
v8SetReturnValue(info, ret);
}
}
// Function invocation callback that exactly matches the old dart:js interop semantics.
static void functionInvocationCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
{
DartScopes scopes(args.Holder());
Dart_Handle handle = scopes.handle;
DartDOMData* domData = DartDOMData::current();
ASSERT(domData);
ASSERT(DartUtilities::isFunction(domData, handle));
Vector<Dart_Handle> dartFunctionArgs;
ASSERT(args.Length() == 1 || args.Length() == 2);
// If there is 1 argument, we assume it is a v8:Array or arguments, if
// there are 2 arguments, the first argument is "this" and the second
// argument is an array of arguments.
if (args.Length() > 1) {
dartFunctionArgs.append(JsInterop::toDart(args[0], domData, true));
}
v8::Local<v8::Array> argsList = args[args.Length()-1].As<v8::Array>();
uint32_t argsListLength = argsList->Length();
for (uint32_t i = 0; i < argsListLength; i++) {
dartFunctionArgs.append(JsInterop::toDart(argsList->Get(i), domData, true));
}
setJsReturnValue(domData, args, Dart_InvokeClosure(handle, dartFunctionArgs.size(), dartFunctionArgs.data()));
}
// Function invocation callback that matches the package:js typed interop semantics
// and dart:html callback passing semantics.
static void typedFunctionInvocationCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
{
DartScopes scopes(args.Holder());
Dart_Handle handle = scopes.handle;
DartDOMData* domData = DartDOMData::current();
ASSERT(domData);
ASSERT(DartUtilities::isFunction(domData, handle));
Vector<Dart_Handle> dartFunctionArgs;
ASSERT(args.Length() == 1 || args.Length() == 2);
// If there is 1 argument, we assume it is a v8:Array or arguments, if
// there are 2 arguments, the first argument is "this" and the second
// argument is an array of arguments.
if (args.Length() > 1) {
dartFunctionArgs.append(JsInterop::toDartTyped(args[0], domData));
}
v8::Local<v8::Array> argsList = args[args.Length()-1].As<v8::Array>();
uint32_t argsListLength = argsList->Length();
for (uint32_t i = 0; i < argsListLength; i++) {
dartFunctionArgs.append(JsInterop::toDartTyped(argsList->Get(i), domData));
}
setJsReturnValueTyped(domData, args, Dart_InvokeClosure(handle, dartFunctionArgs.size(), dartFunctionArgs.data()));
}
static v8::Local<v8::ObjectTemplate> setupInstanceTemplate(v8::Local<v8::FunctionTemplate> proxyTemplate)
{
v8::Local<v8::ObjectTemplate> instanceTemplate = proxyTemplate->InstanceTemplate();
instanceTemplate->SetInternalFieldCount(1);
return instanceTemplate;
}
static v8::Local<v8::FunctionTemplate> dartFunctionTemplate(v8::Isolate* v8Isolate)
{
DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, proxyTemplate, ());
v8::Local<v8::FunctionTemplate> proxyTemplateLocal;
if (proxyTemplate.IsEmpty()) {
proxyTemplate.Reset(v8Isolate, v8::FunctionTemplate::New(v8Isolate));
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
v8::Local<v8::ObjectTemplate> instanceTemplate = setupInstanceTemplate(proxyTemplateLocal);
instanceTemplate->SetCallAsFunctionHandler(&functionInvocationCallback);
} else {
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
}
return proxyTemplateLocal;
}
static v8::Local<v8::FunctionTemplate> typedDartFunctionTemplate(v8::Isolate* v8Isolate)
{
DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, proxyTemplate, ());
v8::Local<v8::FunctionTemplate> proxyTemplateLocal;
if (proxyTemplate.IsEmpty()) {
proxyTemplate.Reset(v8Isolate, v8::FunctionTemplate::New(v8Isolate));
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
v8::Local<v8::ObjectTemplate> instanceTemplate = setupInstanceTemplate(proxyTemplateLocal);
instanceTemplate->SetCallAsFunctionHandler(&typedFunctionInvocationCallback);
} else {
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
}
return proxyTemplateLocal;
}
/**
* Helper class to manage scopes needed for JSInterop code.
*/
class JsInteropScopes {
public:
Dart_NativeArguments args;
v8::Context::Scope v8Scope;
v8::TryCatch tryCatch;
JsInteropScopes(Dart_NativeArguments args, v8::Local<v8::Context> context)
: args(args)
, v8Scope(context)
{
ASSERT(v8::Isolate::GetCurrent());
}
~JsInteropScopes()
{
// The user is expected to call handleJsException before the scope is
// closed so that V8 exceptions are properly sent back to Dart.
ASSERT(!tryCatch.HasCaught());
}
bool handleJsException(Dart_Handle* exception)
{
if (!tryCatch.HasCaught())
return false;
// FIXME: terminate v8 if tryCatch.CanContinue() is false.
ASSERT(tryCatch.CanContinue());
ASSERT(exception);
v8::Handle<v8::Value> ex(tryCatch.Exception()->ToString());
if (ex.IsEmpty()) {
*exception = Dart_NewStringFromCString("Empty JavaScript exception");
} else {
*exception = V8Converter::stringToDart(ex);
}
tryCatch.Reset();
return true;
}
void setReturnValue(Dart_Handle ret)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetReturnValue(args, ret);
}
// Here and elsewhere, legacy indicates that we need to match the painful
// to support legacy dart:js semantics where EventTarget and a few other
// classes are passed as dart:html objects while other classes are passed
// as JsObject, JsArray, etc.
void setReturnValue(v8::Local<v8::Value> ret, DartDOMData* data, bool legacy)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetReturnValue(args, JsInterop::toDart(ret, data, legacy));
ASSERT(!tryCatch.HasCaught());
}
void setReturnValueTyped(v8::Local<v8::Object> ret, InterceptorData* interceptorData, DartDOMData* data)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetReturnValue(args, JsObject::toDartTyped(ret, data, interceptorData));
ASSERT(!tryCatch.HasCaught());
}
void setReturnValueTyped(v8::Local<v8::Value> ret, DartDOMData* data)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetReturnValue(args, JsInterop::toDartTyped(ret, data));
ASSERT(!tryCatch.HasCaught());
}
void setReturnValueProbablyNative(v8::Local<v8::Object> ret)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetReturnValue(args, JsInterop::toDartProbablyNative(ret));
ASSERT(!tryCatch.HasCaught());
}
void setReturnValueInteger(int64_t ret)
{
ASSERT(!tryCatch.HasCaught());
Dart_SetIntegerReturnValue(args, ret);
}
void setReturnValueString(const AtomicString& ret)
{
ASSERT(!tryCatch.HasCaught());
DartUtilities::setDartStringReturnValue(args, ret);
}
};
PassRefPtr<JsObject> JsObject::create(v8::Local<v8::Object> v8Handle, v8::Isolate* v8Isolate)
{
return adoptRef(new JsObject(v8Handle, v8Isolate));
}
v8::Local<v8::Function> toTypedDartFunction(Dart_Handle function, DartDOMData* domData, v8::Isolate* v8Isolate) {
ASSERT(DartUtilities::isFunction(domData, function));
void* peer = 0;
Dart_Handle result = Dart_GetPeer(function, &peer);
ALLOW_UNUSED_LOCAL(result);
ASSERT(!Dart_IsError(result));
v8::Persistent<v8::Function>* existingFunction = static_cast<v8::Persistent<v8::Function>*>(peer);
if (existingFunction) {
return v8::Local<v8::Function>::New(v8Isolate, *existingFunction);
}
v8::Local<v8::Object> functionProxy = dartFunctionTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
DartHandleProxy::writePointerToProxy(functionProxy, function);
// The raw functionProxy doesn't behave enough like a true JS function
// so we wrap it in a true JS function.
v8::Local<v8::Function> object = domData->jsInteropData()->wrapDartFunction(v8Isolate)->Call(functionProxy, 0, 0).As<v8::Function>();
// TODO(jacobr): it is really ugly but required as we can't use the DartHandleProxy directly.
Dart_PersistentHandle persistent = Dart_NewPersistentHandle(function);
InteropDartHandles* cache = domData->m_dartHandles.get(object, v8Isolate);
cache->m_typedInterop = persistent;
cache->m_rawInterop = persistent;
cache->m_legacyInteropMode = TypedInterop; // Doesn't matter.
v8::Local<v8::Object> proxy = typedDartFunctionTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
DartHandleProxy::writePointerToProxy(proxy, function);
v8::Local<v8::Function> ret = v8::Local<v8::Function>::Cast(domData->jsInteropData()->wrapDartFunction(v8Isolate)->Call(proxy, 0, 0));
existingFunction = new v8::Persistent<v8::Function>();
existingFunction->Reset(v8Isolate, ret);
Dart_SetPeer(function, (void*)existingFunction);
return ret;
}
v8::Local<v8::Value> fromDartHelper(DartDOMData* domData, Dart_Handle handle, Dart_Handle& exception, bool typed)
{
auto v8Isolate = domData->v8Isolate();
v8::Handle<v8::Value> value = V8Converter::toV8IfPrimitive(domData, handle, exception);
if (!value.IsEmpty() || exception)
return value;
value = V8Converter::toV8IfBrowserNative(domData, handle, exception);
if (!value.IsEmpty() || exception)
return value;
if (DartDOMWrapper::subtypeOf(handle, JsObject::dartClassId)) {
JsObject* object = DartDOMWrapper::unwrapDartWrapper<JsObject>(domData, handle, exception);
if (exception)
return v8::Local<v8::Value>();
return object->localV8Object(v8Isolate);
}
if (DartUtilities::isFunction(domData, handle)) {
if (typed) {
return toTypedDartFunction(handle, domData, v8Isolate);
} else {
v8::Local<v8::Object> functionProxy = dartFunctionTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
DartHandleProxy::writePointerToProxy(functionProxy, handle);
// The raw functionProxy doesn't behave enough like a true JS function
// so we wrap it in a true JS function.
v8::Local<v8::Function> object = domData->jsInteropData()->wrapDartFunction(v8Isolate)->Call(functionProxy, 0, 0).As<v8::Function>();
// TODO(jacobr): it is really ugly but required as we can't use the DartHandleProxy directly.
InteropDartHandles* cache = domData->m_dartHandles.get(object, v8Isolate);
Dart_PersistentHandle persistent = Dart_NewPersistentHandle(handle);
cache->m_typedInterop = persistent;
cache->m_rawInterop = persistent;
cache->m_legacyInteropMode = RawInterop;
return object;
}
}
v8::Local<v8::Object> proxy;
ASSERT(Dart_IsInstance(handle));
// Simulate the behavior of the Dart dev compiler where new List() is
// equivalent to a JavaScript array. We accomplish this by creating a
// JavaScript object that fakes that it is a JavaScript array but is
// actually backed by a Dart list. This is not a breaking change as
// existing Dart-JS interop passed arrays as opaque Dart handles.
// The jsify method can still be called if you wish to create a copy
// of a json like Dart data structure.
if (Dart_IsList(handle)) {
proxy = dartListTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
} else {
proxy = dartObjectTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
}
DartHandleProxy::writePointerToProxy(proxy, handle);
proxy->SetHiddenValue(v8::String::NewFromUtf8(v8Isolate, "dartProxy"), v8::Boolean::New(v8Isolate, true));
return proxy;
}
v8::Local<v8::Value> JsInterop::fromDartTyped(DartDOMData* domData, Dart_Handle handle, Dart_Handle& exception)
{
return fromDartHelper(domData, handle, exception, true);
}
v8::Local<v8::Value> JsInterop::fromDart(DartDOMData* domData, Dart_Handle handle, Dart_Handle& exception)
{
return fromDartHelper(domData, handle, exception, false);
}
JsObject::JsObject(v8::Local<v8::Object> v8Handle, v8::Isolate* isolate)
{
v8::Persistent<v8::Object> persistentHandle;
v8Object.Reset(isolate, v8Handle);
m_cachedNode = 0;
}
v8::Local<v8::Object> JsObject::localV8Object(v8::Isolate* v8Isolate)
{
return v8::Local<v8::Object>::New(v8Isolate, v8Object);
}
Node* JsObject::toNode(v8::Isolate* v8Isolate) {
if (m_cachedNode) return m_cachedNode;
m_cachedNode = V8Node::toImplWithTypeCheck(v8Isolate, localV8Object(v8Isolate));
// TODO(jacobr): we don't optimize for cases where the type check fails.
return m_cachedNode;
}
Element* JsObject::toElement(v8::Isolate* v8Isolate) {
Node* node = toNode(v8Isolate);
return node && node->isElementNode() ? (Element*)m_cachedNode : 0;
}
Dart_Handle JsInterop::toDart(v8::Local<v8::Value> v8Handle, DartDOMData* domData, bool legacy)
{
Dart_Handle handle = V8Converter::toDartIfPrimitive(v8Handle);
if (handle)
return handle;
ASSERT(v8Handle->IsObject());
v8::Handle<v8::Object> object = v8Handle.As<v8::Object>();
// TODO(terry): START of uncommented block by Jacob, I've re-enabled for clamped arrays...
Dart_Handle exception = 0;
handle = V8Converter::toDartIfBrowserNative(object, object->CreationContext()->GetIsolate(), exception);
ASSERT(!exception);
if (handle)
return handle;
// TODO(terry): END of uncommented block by Jacob.
// Unwrap objects passed from Dart to JS that are being passed back to
// Dart. FIXME: we do not yet handle unwrapping JS functions passed
// from Dart to JS as we have to wrap them with true JS Function objects.
// If this use case is important we can support it at the cost of hanging
// an extra expando off the JS function wrapping the Dart function.
if (DartHandleProxy::isDartProxy(v8Handle)) {
DartPersistentValue* scriptValue = DartHandleProxy::readPointerFromProxy(v8Handle);
ASSERT(scriptValue->isIsolateAlive());
// If the isolate does not match we fall back to using the existing JS
// wrapper for the Dart object so that simple cases that would work in
// Dart2Js work. We could alternately throw an exception here.
if (scriptValue->isolate() == Dart_CurrentIsolate()) {
return scriptValue->value();
}
}
return JsObject::toDart(object, domData, legacy);
}
Dart_Handle JsInterop::toDartTyped(v8::Local<v8::Value> v8Handle, DartDOMData* domData)
{
Dart_Handle handle = V8Converter::toDartIfPrimitive(v8Handle);
if (handle)
return handle;
ASSERT(v8Handle->IsObject());
v8::Handle<v8::Object> object = v8Handle.As<v8::Object>();
Dart_Handle exception = 0;
handle = V8Converter::toDartIfBrowserNative(object, domData->v8Isolate(), exception);
ASSERT(!exception);
if (handle)
return handle;
// Unwrap objects passed from Dart to JS that are being passed back to
// Dart. FIXME: we do not yet handle unwrapping JS functions passed
// from Dart to JS as we have to wrap them with true JS Function objects.
// If this use case is important we can support it at the cost of hanging
// an extra expando off the JS function wrapping the Dart function.
if (DartHandleProxy::isDartProxy(v8Handle)) {
DartPersistentValue* scriptValue = DartHandleProxy::readPointerFromProxy(v8Handle);
ASSERT(scriptValue->isIsolateAlive());
// If the isolate does not match we fall back to using the existing JS
// wrapper for the Dart object so that simple cases that would work in
// Dart2Js work. We could alternately throw an exception here.
if (scriptValue->isolate() == Dart_CurrentIsolate()) {
return scriptValue->value();
}
}
return JsObject::toDartTyped(object, domData);
}
Dart_Handle JsObject::toDart(v8::Local<v8::Object> object, DartDOMData* domData, bool legacy)
{
return toDart(object, domData, domData->m_dartHandles.get(object, domData->v8Isolate()), legacy);
}
// Contract for this method is it assumes any implicit conversions such as DateTime have already been performed.
Dart_Handle JsObject::toDart(v8::Local<v8::Object> object, DartDOMData* domData, InteropDartHandles* cache, bool legacy)
{
Dart_Handle wrapper;
auto v8Isolate = domData->v8Isolate();
if (object->IsFunction()) {
if (cache->m_rawInterop) return cache->m_rawInterop;
RefPtr<JsFunction> jsFunction = JsFunction::create(object.As<v8::Function>(), v8Isolate);
wrapper = JsFunction::toDart(jsFunction);
cache->m_legacyInteropMode = RawInterop;
} else if (object->IsArray()
// Check for Dart List objects from different Dart isolates.
// In dart2js the List from a different isolate would just be a regular
// JS Array so it can be treated as a JS Array.
|| dartListTemplate(v8Isolate)->HasInstance(object)) {
if (cache->m_rawInterop) return cache->m_rawInterop;
RefPtr<JsArray> jsArray = JsArray::create(object, v8Isolate);
wrapper = JsArray::toDart(jsArray);
cache->m_legacyInteropMode = RawInterop;
} else {
if (legacy) {
InterceptorData* interceptor = 0;
if (cache->m_legacyInteropMode == UnknownLegacyMode) {
interceptor = computeInterceptor(object, domData, cache);
}
if (cache->m_legacyInteropMode == TypedInterop) {
return toDartTyped(object, domData, interceptor, cache);
}
}
if (cache->m_rawInterop) return cache->m_rawInterop;
RefPtr<JsObject> jsObject = JsObject::create(object, v8Isolate);
wrapper = JsObject::toDart(jsObject, domData);
}
v8::Local<v8::Object> proxy;
ASSERT(Dart_IsInstance(wrapper));
// Simulate the behavior of the Dart dev compiler where new List() is
// equivalent to a JavaScript array. We accomplish this by creating a
// JavaScript object that fakes that it is a JavaScript array but is
// actually backed by a Dart list. This is not a breaking change as
// existing Dart-JS interop passed arrays as opaque Dart handles.
// The jsify method can still be called if you wish to create a copy
// of a json like Dart data structure.
cache->m_rawInterop = Dart_NewPersistentHandle(wrapper);
return wrapper;
}
Dart_Handle JsObject::toDartTyped(v8::Local<v8::Object> object, DartDOMData* domData)
{
InteropDartHandles* cache = domData->m_dartHandles.get(object, domData->v8Isolate());
return toDartTyped(object, domData, cache);
}
Dart_Handle JsObject::toDartTyped(v8::Local<v8::Object> object, DartDOMData* domData, InteropDartHandles* cache) {
return toDartTyped(object, domData, 0, cache);
}
InterceptorData* JsObject::computeInterceptor(v8::Local<v8::Object> object, DartDOMData* domData, InteropDartHandles* cache) {
v8::Local<v8::Value> proto = object->GetPrototype();
auto v8Isolate = domData->v8Isolate();
bool cachedInterceptor = proto->IsObject();
InterceptorData* interceptor = cachedInterceptor ?
domData->m_interceptors.get(proto.As<v8::Object>(), v8Isolate) :
// We can't actually cache the interceptor for this case.
// We currently cache on the instance but that is pretty pointless.
// TODO(jacobr): we should track to make sure this case isn't common
domData->m_interceptors.get(object, v8Isolate);
if (interceptor->isNotEmpty()) {
cache->m_legacyInteropMode = interceptor->m_legacyInteropMode;
return interceptor;
}
if (object->IsFunction() || object->IsArray() || dartListTemplate(v8Isolate)->HasInstance(object)) {
cache->m_legacyInteropMode = RawInterop;
interceptor->m_legacyInteropMode = RawInterop;
} else {
// Need to determine the Dart type before we can create the right wrapper.
// This is really inefficient.... We should really just pass the proto or something
// and for this case we really don't need to be leaking tons of memory... oh well.
Dart_Handle rawWrapper = JsObject::toDart(object, domData, cache, false);
bool isCrossFrame = !object->CreationContext()->Global()->StrictEquals(domData->v8Global());
Element* element = V8Element::toImplWithTypeCheck(v8Isolate, object);
bool isElement = element && element->executionContext();
Dart_Handle args[3] = { rawWrapper, Dart_NewBoolean(isCrossFrame), Dart_NewBoolean(isElement) };
Dart_Handle ret = invokeTopLevelJsInteropMethod(DartDOMData::current(), "_lookupType", 3, args);
ASSERT(Dart_IsList(ret));
Dart_Handle type = Dart_ListGetAt(ret, 0);
ASSERT(Dart_IsType(type));
ASSERT(!Dart_IsError(type));
Dart_Handle legacyInteropConvertToNativeHandle = Dart_ListGetAt(ret, 1);
ASSERT(Dart_IsBoolean(legacyInteropConvertToNativeHandle));
bool legacyInteropConvertToNative = false;
Dart_BooleanValue(legacyInteropConvertToNativeHandle, &legacyInteropConvertToNative);
interceptor->m_legacyInteropMode = legacyInteropConvertToNative ? TypedInterop : RawInterop;
cache->m_legacyInteropMode = interceptor->m_legacyInteropMode;
interceptor->m_type = Dart_NewPersistentHandle(type);
}
return interceptor;
}
// Only call this method if you are confident that the v8 Object does not have an existing
// Dart wrapper. For example, the result of calling document.createElement, etc.
Dart_Handle JsObject::toDartTyped(v8::Local<v8::Object> object, DartDOMData* domData, InterceptorData* interceptor, InteropDartHandles* cache)
{
Dart_Handle wrapper;
if (cache->m_typedInterop) return cache->m_typedInterop;
// Depending on the frequency of Function and Array we should check them
// first.
auto v8Isolate = domData->v8Isolate();
if (!interceptor) {
interceptor = computeInterceptor(object, domData, cache);
}
if (interceptor->m_type) {
return createTyped(object, domData, interceptor, cache);
}
// TODO(jacobr): these checks for IsFunction and IsArray are really unfortunate.
// figure out how to remove them.
if (object->IsFunction()) {
cache->m_legacyInteropMode = RawInterop;
RefPtr<JSFunction> jsFunction = JSFunction::create(object.As<v8::Function>(), v8Isolate);
wrapper = JSFunction::toDart(jsFunction);
} else {
ASSERT(object->IsArray()
// Check for Dart List objects from different Dart isolates.
// In dart2js the List from a different isolate would just be a regular
// JS Array so it can be treated as a JS Array.
|| dartListTemplate(v8Isolate)->HasInstance(object));
cache->m_legacyInteropMode = RawInterop;
RefPtr<JSArray> jsArray = JSArray::create(object, v8Isolate);
wrapper = JSArray::toDart(jsArray);
}
ASSERT(Dart_IsInstance(wrapper));
// Simulate the behavior of the Dart dev compiler where new List() is
// equivalent to a JavaScript array. We accomplish this by creating a
// JavaScript object that fakes that it is a JavaScript array but is
// actually backed by a Dart list. This is not a breaking change as
// existing Dart-JS interop passed arrays as opaque Dart handles.
// The jsify method can still be called if you wish to create a copy
// of a json like Dart data structure.
cache->m_typedInterop = Dart_NewPersistentHandle(wrapper);
return wrapper;
}
Dart_Handle JsObject::toDartTyped(v8::Local<v8::Object> object, DartDOMData* domData, InterceptorData* interceptorData)
{
ASSERT(interceptorData && interceptorData->m_type);
auto v8Isolate = domData->v8Isolate();
InteropDartHandles* cache = domData->m_dartHandles.get(object, v8Isolate);
if (cache->m_typedInterop) return cache->m_typedInterop;
return JsObject::createTyped(object, domData, interceptorData, cache);
}
Dart_Handle JsObject::createTyped(v8::Local<v8::Object> object, DartDOMData* domData, InterceptorData* interceptorData, InteropDartHandles* cache)
{
auto v8Isolate = domData->v8Isolate();
RefPtr<JsObject> jsObject = JsObject::create(object, v8Isolate);
ASSERT(interceptorData->m_type);
Dart_Handle wrapper = DartDOMWrapper::createWrapperForType<JsObject>(domData, jsObject.get(), JsObject::dartClassId, interceptorData->m_type);
if (interceptorData->m_isCustomElement) {
Dart_Handle ret = Dart_InvokeConstructor(wrapper, Dart_NewStringFromCString("created"), 0, 0);
if (Dart_IsError(ret)) {
DartUtilities::reportProblem(domData->scriptExecutionContext(), ret);
if (!domData->htmlElementType()) {
Dart_Handle htmlElementType = Dart_GetType(domData->htmlLibrary(), Dart_NewStringFromCString("HtmlElementImpl"), 0, 0);
RELEASE_ASSERT(!Dart_IsError(htmlElementType));
domData->setHtmlElementType(Dart_NewPersistentHandle(htmlElementType));
}
// We need a fresh JsObject to create a new DOM wrapper.
jsObject = JsObject::create(object, v8Isolate);
wrapper = DartDOMWrapper::createWrapperForType<JsObject>(domData, jsObject.get(), JsObject::dartClassId, domData->htmlElementType());
}
}
ASSERT(Dart_IsInstance(wrapper));
// Simulate the behavior of the Dart dev compiler where new List() is
// equivalent to a JavaScript array. We accomplish this by creating a
// JavaScript object that fakes that it is a JavaScript array but is
// actually backed by a Dart list. This is not a breaking change as
// existing Dart-JS interop passed arrays as opaque Dart handles.
// The jsify method can still be called if you wish to create a copy
// of a json like Dart data structure.
cache->m_typedInterop = Dart_NewPersistentHandle(wrapper);
return wrapper;
}
void JsInterop::buildInteropPatchFiles(DartDOMData* domData, Vector<InteropPatchFile>* patches, Dart_Handle& exception)
{
// TODO(terry): startTime used for elapsed time perf debugging.
double startTime = currentTimeMS();
ALLOW_UNUSED_LOCAL(startTime);
Dart_Handle libraries = Dart_NewList(0);
// Build patch files implementing all external methods specified with new
// style JS interop and JsObjectImpl, JsFunctionImpl, and JsArrayImpl
// classes that implement all Dart types annoted with @Js.
// The sole purpose of these classes is to ensure that checked mode
// allows casting a JsObject to all types implemented by a JsObject.
Dart_Handle externals = invokeTopLevelJsInteropMethod(domData, "_generateInteropPatchFiles", 1, &libraries);
if (Dart_IsError(externals)) {
exception = externals;
return;
}
// TODO(terry): Elapsed time perf debugging.
double endTime = currentTimeMS();
ALLOW_UNUSED_LOCAL(endTime);
ASSERT(Dart_IsList(externals));
intptr_t externalsLength = 0;
Dart_ListLength(externals, &externalsLength);
ASSERT(externalsLength % 3 == 0);
#if 0
// TODO(terry): Debug logging reports speed of patch files generation.
// Log speed of generating the patch files.
intptr_t librariesLength = 0;
// Number of total libraries handled statistics.
libraries = Dart_GetLibraryIds();
ASSERT(Dart_IsList(libraries));
Dart_ListLength(libraries, &librariesLength);
fprintf(stderr, "\nGenerated %li interop patch files from %li libraries in %f ms.\n\n",
externalsLength / 3, librariesLength, endTime - startTime);
#endif
for (intptr_t i = 0; i < externalsLength; i += 3) {
InteropPatchFile patch;
Dart_Handle libraryUri = Dart_ListGetAt(externals, i);
Dart_Handle patchFileUri = Dart_ListGetAt(externals, i + 1);
Dart_Handle source = Dart_ListGetAt(externals, i + 2);
ASSERT(Dart_IsString(libraryUri));
ASSERT(Dart_IsString(patchFileUri));
ASSERT(Dart_IsString(source));
patch.libraryUri = DartUtilities::toString(libraryUri);
patch.patchFileUri = DartUtilities::toString(patchFileUri);
patch.source = DartUtilities::toString(source);
patches->append(patch);
#if 0
// TODO(terry): Debug logging to see each patch file generated.
// Log the patch files.
CString libraryName = CString(reinterpret_cast<const char*>(patch.libraryUri.characters8()), patch.libraryUri.length());
fprintf(stderr, " Patch %li %s\n", i / 3, libraryName.data());
#endif
}
}
void JsInterop::initializeJsInterop(DartDOMData* domData, const Vector<InteropPatchFile>& patches, Dart_Handle& exception)
{
DartJsInteropData* interopData = domData->jsInteropData();
// Skip if the JSObjectImpl class has already been defined.
if (interopData->jsObjectImplDefined()) {
return;
}
// Helper method that generates boilerplate source code for
// JsObjectImpl, JsFunctionImpl, and JsArrayImpl classes that implement
// all Dart types that have been passed to the dart:js registerJsInterfaces
// method. The sole purpose of these classes is to ensure that checked mode
// allows casting a JsObject to all types implemented by a JsObject.
Dart_Handle ret;
for (size_t i = 0; i < patches.size(); ++i) {
const InteropPatchFile& patch = patches[i];
Dart_Handle library = Dart_LookupLibrary(DartUtilities::safeStringToDartString(patch.libraryUri));
ASSERT(Dart_IsLibrary(library));
Dart_Handle patchFileUri = DartUtilities::safeStringToDartString(patch.patchFileUri);
Dart_Handle source = DartUtilities::safeStringToDartString(patch.source);
ret = Dart_LibraryLoadPatch(library, patchFileUri, source);
if (Dart_IsError(ret)) {
exception = ret;
return;
}
}
ret = Dart_FinalizeLoading(false);
if (Dart_IsError(ret)) {
exception = ret;
return;
}
interopData->setJsObjectImplDefined();
if (domData->isDOMEnabled()) {
// Start of polyfill work to make Dart List proxies behave like JavaScript
// Arrays by monkey patching JavaScript Array and the List JavaScript
// proxies as needed.
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Function> dartArrayConstructor = dartListTemplate(v8Isolate)->GetFunction();
domData->v8Global()->Set(v8::String::NewFromUtf8(v8Isolate, "$DartList"),
dartArrayConstructor);
V8ScriptRunner::compileAndRunInternalScript(v8::String::NewFromUtf8(v8Isolate, dartArrayPolyfill), v8Isolate);
ret = Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString("_registerAllJsInterfaces"), 0, 0);
if (Dart_IsError(ret)) {
exception = ret;
return;
}
}
}
Dart_Handle JsObject::toDart(PassRefPtr<JsObject> jsObject, DartDOMData* domData)
{
return DartDOMWrapper::createWrapper<JsObject>(domData, jsObject.get(), JsObject::dartClassId);
}
JsObject::~JsObject()
{
v8Object.Reset();
}
// JsFunction
Dart_Handle JsFunction::toDart(PassRefPtr<JsFunction> jsFunction)
{
DartDOMData* domData = DartDOMData::current();
return DartDOMWrapper::createWrapper<JsFunction>(domData, jsFunction.get(), JsFunction::dartClassId);
}
JsFunction::JsFunction(v8::Local<v8::Function> v8Handle, v8::Isolate* v8Isolate) : JsObject(v8Handle, v8Isolate) { }
PassRefPtr<JsFunction> JsFunction::create(v8::Local<v8::Function> v8Handle, v8::Isolate* v8Isolate)
{
return adoptRef(new JsFunction(v8Handle, v8Isolate));
}
// JSFunction
Dart_Handle JSFunction::toDart(PassRefPtr<JSFunction> jsFunction)
{
DartDOMData* domData = DartDOMData::current();
return DartDOMWrapper::createWrapper<JSFunction>(domData, jsFunction.get(), JSFunction::dartClassId);
}
JSFunction::JSFunction(v8::Local<v8::Function> v8Handle, v8::Isolate* v8Isolate) : JsObject(v8Handle, v8Isolate) { }
PassRefPtr<JSFunction> JSFunction::create(v8::Local<v8::Function> v8Handle, v8::Isolate* v8Isolate)
{
return adoptRef(new JSFunction(v8Handle, v8Isolate));
}
// JsArray
Dart_Handle JsArray::toDart(PassRefPtr<JsArray> jsArray)
{
DartDOMData* domData = DartDOMData::current();
return DartDOMWrapper::createWrapper<JsArray>(domData, jsArray.get(), JsArray::dartClassId);
}
JsArray::JsArray(v8::Local<v8::Object> v8Handle, v8::Isolate* v8Isolate) : JsObject(v8Handle, v8Isolate) { }
PassRefPtr<JsArray> JsArray::create(v8::Local<v8::Object> v8Handle, v8::Isolate* v8Isolate)
{
return adoptRef(new JsArray(v8Handle, v8Isolate));
}
// JSArray
Dart_Handle JSArray::toDart(PassRefPtr<JSArray> jsArray)
{
DartDOMData* domData = DartDOMData::current();
return DartDOMWrapper::createWrapper<JSArray>(domData, jsArray.get(), JSArray::dartClassId);
}
JSArray::JSArray(v8::Local<v8::Object> v8Handle, v8::Isolate* v8Isolate) : JsObject(v8Handle, v8Isolate) { }
PassRefPtr<JSArray> JSArray::create(v8::Local<v8::Object> v8Handle, v8::Isolate* v8Isolate)
{
return adoptRef(new JSArray(v8Handle, v8Isolate));
}
namespace JsInteropInternal {
JsObject* toJsObject(Dart_NativeArguments args, int index, Dart_Handle& exception)
{
JsObject* result = DartDOMWrapper::unwrapDartWrapper<JsObject>(args, index, exception);
if (result) return result;
exception = Dart_NewStringFromCString("Not a valid JS object");
return 0;
}
typedef HashMap<Dart_Handle, v8::Handle<v8::Value> > DartHandleToV8Map;
v8::Handle<v8::Value> jsifyHelper(DartDOMData*, Dart_Handle value, DartHandleToV8Map&, Dart_Handle& exception, bool typed);
void argsListToV8(DartDOMData* domData, Dart_Handle args, Vector<v8::Local<v8::Value> >* v8Args, Dart_Handle& exception)
{
if (Dart_IsNull(args))
return;
if (!Dart_IsList(args)) {
exception = Dart_NewStringFromCString("args not type list");
return;
}
intptr_t argsLength = 0;
Dart_ListLength(args, &argsLength);
for (intptr_t i = 0; i < argsLength; i++) {
v8Args->append(JsInterop::fromDart(domData, Dart_ListGetAt(args, i), exception));
if (exception)
return;
}
}
void argsListToV8Typed(DartDOMData* domData, Dart_Handle args, Vector<v8::Local<v8::Value> >* v8Args, Dart_Handle& exception)
{
if (Dart_IsNull(args))
return;
if (!Dart_IsList(args)) {
exception = Dart_NewStringFromCString("args not type list");
return;
}
intptr_t argsLength = 0;
Dart_ListLength(args, &argsLength);
for (intptr_t i = 0; i < argsLength; i++) {
v8Args->append(JsInterop::fromDartTyped(domData, Dart_ListGetAt(args, i), exception));
if (exception)
return;
}
}
void argsListToV8DebuggerOnly(DartDOMData* domData, Dart_Handle args, Vector<v8::Local<v8::Value> >* v8Args, Dart_Handle& exception)
{
if (Dart_IsNull(args))
return;
if (!Dart_IsList(args)) {
exception = Dart_NewStringFromCString("args not type list");
return;
}
intptr_t argsLength = 0;
Dart_ListLength(args, &argsLength);
for (intptr_t i = 0; i < argsLength; i++) {
v8Args->append(DartHandleProxy::create(Dart_ListGetAt(args, i)));
}
}
static void jsObjectConstructorCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
v8::Local<v8::Value> constructorArg = JsInterop::fromDart(domData, Dart_GetNativeArgument(args, 0), exception);
if (exception)
goto fail;
if (!constructorArg->IsFunction()) {
exception = Dart_NewStringFromCString("constructor not a function");
goto fail;
}
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8(domData, Dart_GetNativeArgument(args, 1), &v8Args, exception);
v8::Local<v8::Value> ret = constructorArg.As<v8::Function>()->CallAsConstructor(v8Args.size(), v8Args.data());
crashIfV8IsDead();
if (scopes.handleJsException(&exception))
goto fail;
// Intentionally skip auto-conversion in this case as the user expects
// a JSObject. FIXME: evaluate if this is the right solution.
// Alternately, we could throw an exception.
scopes.setReturnValue(JsObject::toDart(ret.As<v8::Object>(), domData, false));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void identityEqualityCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
v8::Local<v8::Value> a = JsInterop::fromDart(domData, Dart_GetNativeArgument(args, 0), exception);
if (exception)
goto fail;
v8::Local<v8::Value> b = JsInterop::fromDart(domData, Dart_GetNativeArgument(args, 1), exception);
if (exception)
goto fail;
bool strictEquals = a->StrictEquals(b);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(DartUtilities::boolToDart(strictEquals));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void getterCallbackHelper(Dart_NativeArguments args, bool legacy)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
uint64_t intIndex = 0;
v8::Local<v8::Value> ret;
if (Dart_IsInteger(index)) {
bool isUint64 = false;
Dart_IntegerFitsIntoUint64(index, &isUint64);
if (isUint64) {
Dart_Handle result = Dart_IntegerToUint64(index, &intIndex);
ALLOW_UNUSED_LOCAL(result);
if (intIndex <= std::numeric_limits<uint32_t>::max()) {
ASSERT(!Dart_IsError(result));
ret = v8Receiver->Get((uint32_t)intIndex);
} else {
ret = v8Receiver->Get(V8Converter::numberToV8(index));
}
} else {
ret = v8Receiver->Get(V8Converter::numberToV8(index));
}
} else if (Dart_IsString(index)) {
ret = v8Receiver->Get(V8Converter::stringToV8(index));
} else if (Dart_IsNumber(index)) {
ret = v8Receiver->Get(V8Converter::numberToV8(index));
} else {
ret = v8Receiver->Get(V8Converter::stringToV8(Dart_ToString(index)));
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(ret, domData, legacy);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void getterCallback(Dart_NativeArguments args)
{
return getterCallbackHelper(args, false);
}
static void getterLegacyCallback(Dart_NativeArguments args)
{
return getterCallbackHelper(args, true);
}
static void typedGetterCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
v8::Local<v8::Value> ret;
if (!Dart_IsString(index)) goto fail;
ret = v8Receiver->Get(V8Converter::stringToV8(index));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedIndexGetter(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
v8::Local<v8::Value> ret;
if (!Dart_IsNumber(index)) goto fail;
ret = v8Receiver->Get(V8Converter::numberToV8(index));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void hasPropertyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle property = Dart_GetNativeArgument(args, 1);
if (!Dart_IsString(property))
property = Dart_ToString(property);
bool hasProperty = v8Receiver->Has(V8Converter::stringToV8(property));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(DartUtilities::boolToDart(hasProperty));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void deletePropertyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle property = Dart_GetNativeArgument(args, 1);
if (!Dart_IsString(property))
property = Dart_ToString(property);
v8Receiver->Delete(V8Converter::stringToV8(property));
if (scopes.handleJsException(&exception))
goto fail;
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void instanceofCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
v8::Local<v8::Value> type = JsInterop::fromDart(domData, Dart_GetNativeArgument(args, 1), exception);
v8::Local<v8::Value> ret = domData->jsInteropData()->instanceofFunction(v8Isolate)->Call(v8Receiver, 1, &type);
if (scopes.handleJsException(&exception))
goto fail;
ASSERT(ret->IsBoolean());
Dart_SetBooleanReturnValue(args, ret->IsTrue());
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void setterCallbackHelper(Dart_NativeArguments args, bool legacy)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
v8::Local<v8::Value> value = JsInterop::fromDart(domData, Dart_GetNativeArgument(args, 2), exception);
if (exception)
goto fail;
uint64_t intIndex = 0;
bool ret = false;
if (Dart_IsInteger(index)) {
bool isUint64 = false;
Dart_IntegerFitsIntoUint64(index, &isUint64);
if (isUint64) {
Dart_Handle result = Dart_IntegerToUint64(index, &intIndex);
ALLOW_UNUSED_LOCAL(result);
if (intIndex <= std::numeric_limits<uint32_t>::max()) {
ASSERT(!Dart_IsError(result));
ret = v8Receiver->Set((uint32_t)intIndex, value);
} else {
ret = v8Receiver->Set(V8Converter::numberToV8(index), value);
}
} else {
ret = v8Receiver->Set(V8Converter::numberToV8(index), value);
}
} else if (Dart_IsString(index)) {
ret = v8Receiver->Set(V8Converter::stringToV8(index), value);
} else if (Dart_IsNumber(index)) {
ret = v8Receiver->Set(V8Converter::numberToV8(index), value);
} else {
ret = v8Receiver->Set(V8Converter::stringToV8(Dart_ToString(index)), value);
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(DartUtilities::boolToDart(ret));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void setterCallback(Dart_NativeArguments args)
{
return setterCallbackHelper(args, false);
}
static void setterLegacyCallback(Dart_NativeArguments args)
{
return setterCallbackHelper(args, true);
}
static void typedSetterCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
v8::Local<v8::Value> value = JsInterop::fromDartTyped(domData, Dart_GetNativeArgument(args, 2), exception);
if (exception)
goto fail;
bool ret = false;
if (!Dart_IsString(index)) goto fail;
ret = v8Receiver->Set(V8Converter::stringToV8(index), value);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(DartUtilities::boolToDart(ret));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedIndexSetter(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle index = Dart_GetNativeArgument(args, 1);
v8::Local<v8::Value> value = JsInterop::fromDartTyped(domData, Dart_GetNativeArgument(args, 2), exception);
if (exception)
goto fail;
bool ret = false;
if (!Dart_IsNumber(index)) goto fail;
ret = v8Receiver->Set(V8Converter::numberToV8(index), value);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(DartUtilities::boolToDart(ret));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void hashCodeCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
int hashCode = receiver->localV8Object(v8Isolate)->GetIdentityHash();
// FIXME: salt the v8 hashcode so we don't leak information about v8
// memory allocation.
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueInteger(hashCode);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
// TODO(jacobr): is there any reason to keep this method separate from hashCodeCallback?
static void typedHashCodeCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
int hashCode = receiver->localV8Object(v8Isolate)->GetIdentityHash();
// FIXME: salt the v8 hashcode so we don't leak information about v8
// memory allocation.
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueInteger(hashCode);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void defineInterceptorCustomElementCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsObject* jsObject = toJsObject(args, 0, exception);
if (exception)
goto fail;
v8::Local<v8::Object> v8Object = jsObject->localV8Object(v8Isolate);
Dart_Handle type = Dart_GetNativeArgument(args, 1);
ASSERT(Dart_IsType(type));
// v8::Local<v8::Value> proto = v8Object->GetPrototype();
InterceptorData* interceptor;
if (v8Object->IsObject()) {
interceptor = domData->m_interceptors.get(v8Object, v8Isolate);
interceptor->m_type = Dart_NewPersistentHandle(type);
// ASSUME this is only required for element upgrades so this is correct by default
interceptor->m_legacyInteropMode = TypedInterop;
interceptor->m_isCustomElement = true;
} else {
exception = Dart_NewStringFromCString("Internal Error. No Prototype to attach interceptor to");
goto fail;
}
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void callConstructor0Callback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
JsObject* jsConstructor = toJsObject(args, 0, exception);
if (exception)
goto fail;
v8::Local<v8::Object> constructorArg = jsConstructor->localV8Object(v8Isolate);
if (!constructorArg->IsFunction()) {
exception = Dart_NewStringFromCString("constructor not a function");
goto fail;
}
v8::Local<v8::Value> ret = constructorArg->CallAsConstructor(0, 0);
crashIfV8IsDead();
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void callConstructorCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
JsObject* jsConstructor = toJsObject(args, 0, exception);
if (exception)
goto fail;
v8::Local<v8::Object> constructorArg = jsConstructor->localV8Object(v8Isolate);
if (!constructorArg->IsFunction()) {
exception = Dart_NewStringFromCString("constructor not a function");
goto fail;
}
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8Typed(domData, Dart_GetNativeArgument(args, 1), &v8Args, exception);
v8::Local<v8::Value> ret = constructorArg->CallAsConstructor(v8Args.size(), v8Args.data());
crashIfV8IsDead();
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();}
static void toTypedObjectCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Context> context = domData->v8Context();
JsInteropScopes scopes(args, context);
JsObject* jsObject = toJsObject(args, 0, exception);
if (exception)
goto fail;
v8::Local<v8::Object> v8Object = jsObject->localV8Object(v8Isolate);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(v8Object, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
// Callback to force an instance to adopt a specific interceptor.
static void setInstanceInterceptorCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Context> context = domData->v8Context();
JsInteropScopes scopes(args, context);
JsObject* jsObject = toJsObject(args, 0, exception);
Dart_Handle type = Dart_GetNativeArgument(args, 1);
bool isCustomElement = DartUtilities::dartToBool(args, 2, exception);
if (exception)
goto fail;
v8::Local<v8::Object> v8Object = jsObject->localV8Object(v8Isolate);
if (scopes.handleJsException(&exception))
goto fail;
// Define a temporary interceptor record.
InterceptorData tempInterceptor;
tempInterceptor.m_type = Dart_PersistentHandle(type);
tempInterceptor.m_isCustomElement = isCustomElement;
auto cache = domData->m_dartHandles.get(v8Object, v8Isolate);
cache->m_typedInterop = 0; // Clear existing handle.
scopes.setReturnValue(JsObject::createTyped(v8Object, domData, &tempInterceptor, cache));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void callMethodCallbackHelper(Dart_NativeArguments args, bool legacy)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle name = Dart_GetNativeArgument(args, 1);
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8(domData, Dart_GetNativeArgument(args, 2), &v8Args, exception);
if (exception)
goto fail;
if (!Dart_IsString(name))
name = Dart_ToString(name);
v8::Local<v8::Value> value = v8Receiver->Get(V8Converter::stringToV8(name));
v8::Local<v8::Value> ret;
if (value->IsFunction()) {
v8::Local<v8::Value> result;
if (!V8ScriptRunner::callFunction(value.As<v8::Function>(),
domData->scriptExecutionContext(), receiver->localV8Object(v8Isolate), v8Args.size(), v8Args.data(), v8Isolate).ToLocal(&ret)) {
if (!scopes.handleJsException(&exception))
exception = Dart_NewStringFromCString("V8 Exception");
goto fail;
}
} else {
// FIXME: we currently convert this exception to a NoSuchMethod
// exception in the Dart code that wraps this native method.
// Consider throwing a NoSuchMethod exception directly instead.
exception = Dart_NewStringFromCString("property is not a function");
goto fail;
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(ret, domData, legacy);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void callMethodCallback(Dart_NativeArguments args) {
callMethodCallbackHelper(args, false);
}
static void callMethodLegacyCallback(Dart_NativeArguments args) {
callMethodCallbackHelper(args, true);
}
static void typedCallMethodCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Receiver = receiver->localV8Object(v8Isolate);
Dart_Handle name = Dart_GetNativeArgument(args, 1);
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8Typed(domData, Dart_GetNativeArgument(args, 2), &v8Args, exception);
if (exception || !Dart_IsString(name))
goto fail;
v8::Local<v8::Value> value = v8Receiver->Get(V8Converter::stringToV8(name));
v8::Local<v8::Value> ret;
if (value->IsFunction()) {
v8::Local<v8::Value> result;
if (!V8ScriptRunner::callFunction(value.As<v8::Function>(),
domData->scriptExecutionContext(), receiver->localV8Object(v8Isolate), v8Args.size(), v8Args.data(), v8Isolate).ToLocal(&ret)) {
if (!scopes.handleJsException(&exception))
exception = Dart_NewStringFromCString("V8 Exception");
goto fail;
}
} else {
// FIXME: we currently convert this exception to a NoSuchMethod
// exception in the Dart code that wraps this native method.
// Consider throwing a NoSuchMethod exception directly instead.
exception = Dart_NewStringFromCString("property is not a function");
goto fail;
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void newJsArrayCallback(Dart_NativeArguments args)
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
// We don't need to use legacy mode for this return value as this is known
// to be an Array not a DOM type.
scopes.setReturnValue(JsObject::toDart(v8::Array::New(v8Isolate), domData, false));
return;
}
static void newJsArrayFromSafeListCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
Dart_Handle list = Dart_GetNativeArgument(args, 0);
// Code on the Dart side insures this arg is a native Dart list.
ASSERT(Dart_IsList(list));
intptr_t length = 0;
Dart_Handle result = Dart_ListLength(list, &length);
ASSERT(!Dart_IsError(result));
v8::Local<v8::Array> array = v8::Array::New(v8Isolate, length);
for (intptr_t i = 0; i < length; ++i) {
result = Dart_ListGetAt(list, i);
ASSERT(!Dart_IsError(result));
v8::Handle<v8::Value> v8value = JsInterop::fromDart(domData, result, exception);
if (exception)
goto fail;
array->Set(i, v8value);
}
scopes.setReturnValue(array, domData, false);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void jsArrayLengthCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Value> length = receiver->localV8Object(v8Isolate)->Get(v8::String::NewFromUtf8(v8Isolate, "length"));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueInteger(length->IntegerValue());
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void fromBrowserObjectCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
v8::Local<v8::Context> context = domData->v8Context();
JsInteropScopes scopes(args, context);
JsObject* jsObject = toJsObject(args, 0, exception);
if (exception)
goto fail;
v8::Local<v8::Object> v8Object = jsObject->localV8Object(v8Isolate);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(v8Object, domData, false);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void applyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8(domData, Dart_GetNativeArgument(args, 1), &v8Args, exception);
if (exception)
goto fail;
v8::Local<v8::Value> thisArg;
Dart_Handle thisArgDart = Dart_GetNativeArgument(args, 2);
if (Dart_IsNull(thisArgDart)) {
// Use the global v8 object if no Dart thisArg was passed in.
thisArg = domData->v8Global();
} else {
thisArg = JsInterop::fromDart(domData, thisArgDart, exception);
if (exception)
goto fail;
if (!thisArg->IsObject()) {
exception = Dart_NewStringFromCString("thisArg is not an object");
goto fail;
}
}
v8::Local<v8::Value> ret;
if (!V8ScriptRunner::callFunction(receiver->localV8Object(v8Isolate).As<v8::Function>(),
domData->scriptExecutionContext(),
thisArg.As<v8::Object>(),
v8Args.size(),
v8Args.data(),
v8Isolate).ToLocal(&ret)) {
exception = Dart_NewStringFromCString("V8 Exception");
goto fail;
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(ret, domData, true);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedApplyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
// TODO(jacobr): this is crazily inefficient if the JSFunction happens
// to really be a Dart function. Consider optimizing that case.
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8Typed(domData, Dart_GetNativeArgument(args, 1), &v8Args, exception);
if (exception)
goto fail;
v8::Local<v8::Value> thisArg;
Dart_Handle thisArgDart = Dart_GetNativeArgument(args, 2);
if (Dart_IsNull(thisArgDart)) {
// Use the global v8 object if no Dart thisArg was passed in.
thisArg = domData->v8Global();
} else {
thisArg = JsInterop::fromDartTyped(domData, thisArgDart, exception);
if (exception)
goto fail;
if (!thisArg->IsObject()) {
exception = Dart_NewStringFromCString("thisArg is not an object");
goto fail;
}
}
v8::Local<v8::Value> ret;
if (!V8ScriptRunner::callFunction(receiver->localV8Object(v8Isolate).As<v8::Function>(),
domData->scriptExecutionContext(),
thisArg.As<v8::Object>(),
v8Args.size(),
v8Args.data(),
v8Isolate).ToLocal(&ret)) {
exception = Dart_NewStringFromCString("V8 Exception");
goto fail;
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void applyDebuggerOnlyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
Vector<v8::Local<v8::Value> > v8Args;
argsListToV8DebuggerOnly(domData, Dart_GetNativeArgument(args, 1), &v8Args, exception);
if (exception)
goto fail;
v8::Local<v8::Value> thisArg;
Dart_Handle thisArgDart = Dart_GetNativeArgument(args, 2);
if (Dart_IsNull(thisArgDart)) {
// Use the global v8 object if no Dart thisArg was passed in.
thisArg = domData->v8Global();
} else {
thisArg = JsInterop::fromDartTyped(domData, thisArgDart, exception);
if (exception)
goto fail;
if (!thisArg->IsObject()) {
exception = Dart_NewStringFromCString("thisArg is not an object");
goto fail;
}
}
v8::Local<v8::Value> ret;
if (!V8ScriptRunner::callFunction(receiver->localV8Object(v8Isolate).As<v8::Function>(),
domData->scriptExecutionContext(),
thisArg.As<v8::Object>(),
v8Args.size(),
v8Args.data(),
v8Isolate).ToLocal(&ret)) {
exception = Dart_NewStringFromCString("V8 Exception");
goto fail;
}
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void toStringCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
auto v8Isolate = domData->v8Isolate();
JsObject* receiver = DartDOMWrapper::receiver<JsObject>(args);
v8::Local<v8::Object> v8Object = receiver->localV8Object(v8Isolate);
if (scopes.handleJsException(&exception))
goto fail;
if (v8Object.IsEmpty()) {
exception = Dart_NewStringFromCString("Invalid v8 handle");
goto fail;
}
v8::Local<v8::String> v8String = v8Object->ToString();
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(V8Converter::stringToDart(v8String));
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void contextCallback(Dart_NativeArguments args)
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
v8::Context::Scope scope(domData->v8Context());
Dart_SetReturnValue(args, JsObject::toDart(domData->v8Global(), domData, false));
}
static void finalizeJsInterfacesCallback(Dart_NativeArguments args)
{
// Obsolete.
}
static void interfacesFinalizedCallback(Dart_NativeArguments args)
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
DartJsInteropData* interopData = domData->jsInteropData();
// Skip if the JSObjectImpl class has already been defined.
Dart_SetBooleanReturnValue(args, interopData->jsObjectImplDefined());
}
v8::Handle<v8::Value> mapToV8(DartDOMData* domData, Dart_Handle value, DartHandleToV8Map& map, Dart_Handle& exception, bool typed)
{
auto v8Isolate = domData->v8Isolate();
Dart_Handle asList = DartUtilities::invokeUtilsMethod("convertMapToList", 1, &value);
if (!DartUtilities::checkResult(asList, exception))
return v8::Handle<v8::Value>();
ASSERT(Dart_IsList(asList));
// Now we have a list [key, value, key, value, ....], create a v8 object and set necesary
// properties on it.
v8::Handle<v8::Object> object = v8::Object::New(v8Isolate);
map.set(value, object);
// We converted to internal Dart list, methods shouldn't throw exceptions now.
intptr_t length = 0;
Dart_Handle result = Dart_ListLength(asList, &length);
ALLOW_UNUSED_LOCAL(result);
ASSERT(!Dart_IsError(result));
ASSERT(!(length % 2));
for (intptr_t i = 0; i < length; i += 2) {
v8::Handle<v8::Value> key = jsifyHelper(domData, Dart_ListGetAt(asList, i), map, exception, typed);
if (exception)
return v8::Handle<v8::Value>();
v8::Handle<v8::Value> value = jsifyHelper(domData, Dart_ListGetAt(asList, i + 1), map, exception, typed);
if (exception)
return v8::Handle<v8::Value>();
object->Set(key, value);
}
return object;
}
v8::Handle<v8::Value> listToV8(DartDOMData* domData, Dart_Handle value, DartHandleToV8Map& map, Dart_Handle& exception, bool typed)
{
auto v8Isolate = domData->v8Isolate();
ASSERT(Dart_IsList(value));
intptr_t length = 0;
Dart_Handle result = Dart_ListLength(value, &length);
if (!DartUtilities::checkResult(result, exception))
return v8::Handle<v8::Value>();
v8::Local<v8::Array> array = v8::Array::New(v8Isolate, length);
map.set(value, array);
for (intptr_t i = 0; i < length; ++i) {
result = Dart_ListGetAt(value, i);
if (!DartUtilities::checkResult(result, exception))
return v8::Handle<v8::Value>();
v8::Handle<v8::Value> v8value = jsifyHelper(domData, result, map, exception, typed);
if (exception)
return v8::Handle<v8::Value>();
array->Set(i, v8value);
}
return array;
}
v8::Handle<v8::Value> jsifyHelper(DartDOMData* domData, Dart_Handle value, DartHandleToV8Map& map, Dart_Handle& exception, bool typed)
{
DartHandleToV8Map::iterator iter = map.find(value);
if (iter != map.end())
return iter->value;
if (Dart_IsList(value))
return listToV8(domData, value, map, exception, typed);
bool isMap = DartUtilities::dartToBool(DartUtilities::invokeUtilsMethod("isMap", 1, &value), exception);
ASSERT(!exception);
if (isMap)
return mapToV8(domData, value, map, exception, typed);
Dart_Handle maybeList = DartUtilities::invokeUtilsMethod("toListIfIterable", 1, &value);
if (Dart_IsList(maybeList))
return listToV8(domData, maybeList, map, exception, typed);
v8::Handle<v8::Value> ret = typed ? JsInterop::fromDartTyped(domData, value, exception) : JsInterop::fromDart(domData, value, exception);
map.set(value, ret);
return ret;
}
static void jsifyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
Dart_Handle value = Dart_GetNativeArgument(args, 0);
DartHandleToV8Map map;
v8::Local<v8::Value> ret = jsifyHelper(domData, value, map, exception, false);
if (exception)
goto fail;
if (scopes.handleJsException(&exception))
goto fail;
// Intentionally skip legacy auto-conversion in this case as the user
// expects a JsObject.
scopes.setReturnValue(ret, domData, false);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedJsifyCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
JsInteropScopes scopes(args, domData->v8Context());
Dart_Handle value = Dart_GetNativeArgument(args, 0);
DartHandleToV8Map map;
v8::Local<v8::Value> ret = jsifyHelper(domData, value, map, exception, true);
if (exception)
goto fail;
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void newObjectCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context()); // TODO(jacobr): this line isn't needed.
v8::Local<v8::Object> ret = v8::Object::New(v8Isolate);
if (exception)
goto fail;
if (scopes.handleJsException(&exception))
goto fail;
// TODO(jacobr): we could hard code the interceptor in this case.
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void newArrayCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context()); // TODO(jacobr): this line isn't needed.
v8::Local<v8::Object> ret = v8::Array::New(v8Isolate);
if (exception)
goto fail;
if (scopes.handleJsException(&exception))
goto fail;
// TODO(jacobr): we could hard code the interceptor in this case.
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void withThisCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
Dart_Handle function = Dart_GetNativeArgument(args, 0);
ASSERT(DartUtilities::isFunction(domData, function));
v8::Local<v8::Object> proxy = dartFunctionTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
DartHandleProxy::writePointerToProxy(proxy, function);
v8::Local<v8::Function> ret = v8::Local<v8::Function>::Cast(domData->jsInteropData()->captureThisFunction(v8Isolate)->Call(proxy, 0, 0));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValue(ret, domData, true);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedFunctionCreateWithThisCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
Dart_Handle function = Dart_GetNativeArgument(args, 0);
ASSERT(DartUtilities::isFunction(domData, function));
v8::Local<v8::Object> proxy = typedDartFunctionTemplate(v8Isolate)->InstanceTemplate()->NewInstance();
DartHandleProxy::writePointerToProxy(proxy, function);
v8::Local<v8::Function> ret = v8::Local<v8::Function>::Cast(domData->jsInteropData()->captureThisFunction(v8Isolate)->Call(proxy, 0, 0));
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
static void typedFunctionCreateCallback(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
DartDOMData* domData = static_cast<DartDOMData*>(Dart_GetNativeIsolateData(args));
auto v8Isolate = domData->v8Isolate();
JsInteropScopes scopes(args, domData->v8Context());
Dart_Handle function = Dart_GetNativeArgument(args, 0);
ASSERT(DartUtilities::isFunction(domData, function));
v8::Local<v8::Function> ret = toTypedDartFunction(function, domData, v8Isolate);
if (scopes.handleJsException(&exception))
goto fail;
scopes.setReturnValueTyped(ret, domData);
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
void initializeCustomElement(Dart_NativeArguments args)
{
Dart_Handle exception = 0;
{
Dart_Handle elementWrapper = Dart_GetNativeArgument(args, 0);
if (!DartDOMWrapper::subtypeOf(elementWrapper, JsObject::dartClassId)) {
exception = Dart_NewStringFromCString("created called outside of custom element creation.");
goto fail;
}
// We don't actually perform any real initialization work.
return;
}
fail:
Dart_ThrowException(exception);
ASSERT_NOT_REACHED();
}
}
static DartNativeEntry nativeEntries[] = {
{ JsInteropInternal::jsObjectConstructorCallback, 2, "JsObject_constructorCallback" },
{ JsInteropInternal::contextCallback, 0, "Js_context_Callback" },
{ JsInteropInternal::finalizeJsInterfacesCallback, 0, "Js_finalizeJsInterfaces" },
{ JsInteropInternal::interfacesFinalizedCallback, 0, "Js_interfacesFinalized_Callback" },
{ JsInteropInternal::jsifyCallback, 1, "JsObject_jsify" },
{ JsInteropInternal::typedJsifyCallback, 1, "JSObject_jsify" },
{ JsInteropInternal::newObjectCallback, 0, "JSObject_newObject" },
{ JsInteropInternal::newArrayCallback, 0, "JSObject_newArray" },
{ JsInteropInternal::withThisCallback, 1, "JsFunction_withThis" },
{ JsInteropInternal::getterCallback, 2, "JsObject_[]" },
{ JsInteropInternal::getterLegacyCallback, 2, "JsObject_[]Legacy" },
{ JsInteropInternal::setterCallback, 3, "JsObject_[]=" },
{ JsInteropInternal::setterLegacyCallback, 3, "JsObject_[]=Legacy" },
{ JsInteropInternal::hashCodeCallback, 1, "JsObject_hashCode" },
{ JsInteropInternal::callMethodCallback, 3, "JsObject_callMethod" },
{ JsInteropInternal::callMethodLegacyCallback, 3, "JsObject_callMethodLegacy" },
// Begin new interop methods
{ JsInteropInternal::typedGetterCallback, 2, "JSObject_[]" },
{ JsInteropInternal::typedSetterCallback, 3, "JSObject_[]=" },
{ JsInteropInternal::typedHashCodeCallback, 1, "JSObject_hashCode" },
{ JsInteropInternal::typedCallMethodCallback, 3, "JSObject_callMethod" },
{ JsInteropInternal::typedIndexGetter, 2, "JSArray_indexed_getter" },
{ JsInteropInternal::typedIndexSetter, 3, "JSArray_indexed_setter" },
{ JsInteropInternal::callConstructor0Callback, 1, "JSNative_callConstructor0" },
{ JsInteropInternal::callConstructorCallback, 2, "JSNative_callConstructor" },
{ JsInteropInternal::defineInterceptorCustomElementCallback, 2, "Utils_defineInterceptorCustomElement" },
{ JsInteropInternal::setInstanceInterceptorCallback, 3, "Utils_setInstanceInterceptor" },
{ JsInteropInternal::initializeCustomElement, 1, "Utils_initializeCustomElement" },
{ JsInteropInternal::toTypedObjectCallback, 1, "JSNative_toTypedObject" },
{ JsInteropInternal::typedApplyCallback, 3, "JSFunction_apply" },
{ JsInteropInternal::typedFunctionCreateWithThisCallback, 1, "JSFunction_createWithThis" },
{ JsInteropInternal::typedFunctionCreateCallback, 1, "JSFunction_create" },
// TODO(jacobr): do we want to do anything differently for JSObject
// toString relative to JsObject.toString?
{ JsInteropInternal::toStringCallback, 1, "JSObject_toString" },
// End new interop methods
{ JsInteropInternal::toStringCallback, 1, "JsObject_toString" },
{ JsInteropInternal::identityEqualityCallback, 2, "JsObject_identityEquality" },
{ JsInteropInternal::hasPropertyCallback, 2, "JsObject_hasProperty" },
{ JsInteropInternal::deletePropertyCallback, 2, "JsObject_deleteProperty" },
{ JsInteropInternal::instanceofCallback, 2, "JsObject_instanceof" },
{ JsInteropInternal::applyCallback, 3, "JsFunction_apply" },
{ JsInteropInternal::applyDebuggerOnlyCallback, 3, "JsFunction_applyDebuggerOnly" },
{ JsInteropInternal::newJsArrayCallback, 0, "JsArray_newJsArray" },
{ JsInteropInternal::newJsArrayFromSafeListCallback, 1, "JsArray_newJsArrayFromSafeList" },
{ JsInteropInternal::jsArrayLengthCallback, 1, "JsArray_length" },
{ JsInteropInternal::fromBrowserObjectCallback, 1, "JsObject_fromBrowserObject" },
{ 0, 0, 0 },
};
Dart_NativeFunction JsInterop::resolver(Dart_Handle nameHandle, int argumentCount, bool* autoSetupScope)
{
ASSERT(autoSetupScope);
*autoSetupScope = true;
String name = DartUtilities::toString(nameHandle);
for (intptr_t i = 0; nativeEntries[i].nativeFunction != 0; i++) {
if (argumentCount == nativeEntries[i].argumentCount && name == nativeEntries[i].name) {
return nativeEntries[i].nativeFunction;
}
}
return 0;
}
const uint8_t* JsInterop::symbolizer(Dart_NativeFunction nf)
{
for (intptr_t i = 0; nativeEntries[i].nativeFunction != 0; i++) {
if (nf == nativeEntries[i].nativeFunction) {
return reinterpret_cast<const uint8_t*>(nativeEntries[i].name);
}
}
return 0;
}
// Methods enabling a Dart List to emulate a JS Array.
static void indexedGetterArray(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info)
{
DartScopes scopes(info.Holder());
Dart_Handle handle = scopes.handle;
DartDOMData* domData = DartDOMData::current();
Dart_Handle ret = 0;
ASSERT(Dart_IsList(handle));
ret = Dart_ListGetAt(handle, index);
if (Dart_IsError(ret)) {
// Return undefined if the index is invalid to match JS semantics.
// TODO(jacobr): we should log to the console warning of bad use of a
// Dart List.
return;
}
setJsReturnValueTyped(domData, info, ret);
}
static void indexedSetterArray(uint32_t index, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info)
{
DartScopes scopes(info.Holder());
Dart_Handle handle = scopes.handle;
DartDOMData* domData = DartDOMData::current();
Dart_Handle ret = 0;
ASSERT(Dart_IsList(handle));
intptr_t length = 0;
ret = Dart_ListLength(handle, &length);
ASSERT(!Dart_IsError(ret));
if (index >= static_cast<uint32_t>(length)) {
// Approximate JS semantics by filling the list with nulls if we
// attempt to add an element past the end of the list.
// TODO(jacobr): ideally we would match JS semantics exactly and fill
// the list with undefined.
Dart_Handle args[2] = { handle, DartUtilities::unsignedToDart(index + 1)};
ret = Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString("_arrayExtend"), 2, args);
ASSERT(!Dart_IsError(ret));
}
ret = Dart_ListSetAt(handle, index, DartHandleProxy::unwrapValue(value));
if (Dart_IsError(ret)) {
// Return undefined if the index is invalid to match JS semantics.
// TODO(jacobr): we should log to the console warning of bad use of a
// Dart list or add a JS expando to the object ala JS.
}
setJsReturnValueTyped(domData, info, ret);
}
static void indexedEnumeratorArray(const v8::PropertyCallbackInfo<v8::Array>& info)
{
DartScopes scopes(info.Holder());
Dart_Handle handle = scopes.handle;
intptr_t length = 0;
ASSERT(Dart_IsList(handle));
Dart_ListLength(handle, &length);
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Array> indexes = v8::Array::New(v8Isolate, length);
for (int i = 0; i < length; i++)
indexes->Set(i, v8::Integer::New(v8Isolate, i));
v8SetReturnValue(info, indexes);
}
v8::Handle<v8::Array> shallowListToV8(Dart_Handle value, DartDOMData* domData, Dart_Handle& exception)
{
ASSERT(Dart_IsList(value));
auto v8Isolate = domData->v8Isolate();
intptr_t length = 0;
Dart_Handle result = Dart_ListLength(value, &length);
if (!DartUtilities::checkResult(result, exception))
return v8::Handle<v8::Array>();
v8::Local<v8::Array> array = v8::Array::New(v8Isolate, length);
for (intptr_t i = 0; i < length; ++i) {
result = Dart_ListGetAt(value, i);
v8::Handle<v8::Value> v8value = JsInterop::fromDart(domData, result, exception);
// TODO(jacobr): is there a better way to handle this error case?
if (exception)
return v8::Handle<v8::Array>();
array->Set(i, v8value);
}
return array;
}
// If one of the methods we added to the Dart List proxy to make it mascarade
// as JavaScript Array happens to get called on a JavaScript object instead of
// a Dart List proxy object we use the corresponding regular JavaScript Array
// method which will do something plausible in most cases even if the
// JavaScript object isn't actually a JavaScript array. The chrome devtools
// appear to go down exactly this code path as I got crashes in the devtools
// due to info.Holder not being a DartHandleProxy. I haven't investigated
// whether devtools would still function properly if we threw an exception
// which would match the behavior of DOM classes when a method is called on the
// wrong object. The current behavior while strange matches the behavior
// observed when an method from Array is called on an arbitrary JS object.
bool handleNonDartProxyThis(const v8::FunctionCallbackInfo<v8::Value>& info, const char* jsMethodName)
{
if (DartHandleProxy::isDartProxy(info.Holder())) {
return false;
}
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
// Get the method on JS array in an inefficient way.
v8::Local<v8::Function> method = v8::Array::New(v8Isolate)->Get(v8::String::NewFromUtf8(v8Isolate, jsMethodName)).As<v8::Function>();
ASSERT(!method.IsEmpty());
int length = info.Length();
Vector<v8::Local<v8::Value> > v8Args(length);
for (int i = 0; i < length; ++i) {
v8Args[i] = info[i];
}
v8SetReturnValue(info, method->Call(info.Holder(), length, v8Args.data()));
return true;
}
void arrayHelper(const v8::FunctionCallbackInfo<v8::Value>& info, const char* methodName, const char* jsMethodName)
{
if (handleNonDartProxyThis(info, jsMethodName)) {
return;
}
DartScopes scopes(info.Holder());
DartDOMData* domData = DartDOMData::current();
Dart_Handle handle = scopes.handle;
Dart_Handle ret = Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString(methodName), 1, &handle);
setJsReturnValueTyped(domData, info, ret);
}
void arrayHelper1Arg(const v8::FunctionCallbackInfo<v8::Value>& info, const char* methodName, const char* jsMethodName)
{
if (handleNonDartProxyThis(info, jsMethodName)) {
return;
}
DartScopes scopes(info.Holder());
DartDOMData* domData = DartDOMData::current();
Dart_Handle handle = scopes.handle;
Dart_Handle e;
if (info.Length() == 0) {
e = Dart_Null();
} else {
e = JsInterop::toDartTyped(info[0], domData);
}
Dart_Handle args[2] = { handle, e };
Dart_Handle ret = Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString(methodName), 2, args);
setJsReturnValueTyped(domData, info, ret);
}
void arrayHelperWithArgsAsList(const v8::FunctionCallbackInfo<v8::Value>& info, const char* methodName, const char* jsMethodName)
{
if (handleNonDartProxyThis(info, jsMethodName)) {
return;
}
DartScopes scopes(info.Holder());
DartDOMData* domData = DartDOMData::current();
Dart_Handle handle = scopes.handle;
int length = info.Length();
Dart_Handle argsList = Dart_NewList(length);
for (int i = 0; i < length; ++i) {
Dart_ListSetAt(argsList, i, JsInterop::toDartTyped(info[i], domData));
}
// Note: this is also just info.Holder().
Dart_Handle args[2] = { handle, argsList };
setJsReturnValueTyped(domData, info, Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString(methodName), 2, args));
}
static void arrayNamedPropertyGetter(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
{
if (!DartHandleProxy::isDartProxy(info.Holder())) {
// I don't think this case can occur but avoid crashing if there is an
// exotic way to trigger it.
return;
}
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
if (!name->Equals(v8::String::NewFromUtf8(v8Isolate, "length")))
return;
DartScopes scopes(info.Holder());
Dart_Handle handle = scopes.handle;
intptr_t length = 0;
Dart_ListLength(handle, &length);
v8SetReturnValueInt(info, length);
}
static void arrayNamedPropertySetter(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info)
{
if (!DartHandleProxy::isDartProxy(info.Holder())) {
// I don't think this case can occur but avoid crashing if there is an
// exotic way to trigger it.
return;
}
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
if (!property->Equals(v8::String::NewFromUtf8(v8Isolate, "length"))) {
return;
}
DartScopes scopes(info.Holder());
Dart_Handle handle = scopes.handle;
DartDOMData* domData = DartDOMData::current();
Dart_Handle args[2] = { handle, JsInterop::toDartTyped(value, domData) };
Dart_Handle ret = Dart_Invoke(domData->jsLibrary(), Dart_NewStringFromCString("_setListLength"), 2, args);
setJsReturnValueTyped(domData, info, ret);
}
static void arrayQueryProperty(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Integer>& info)
{
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
if (name->Equals(v8::String::NewFromUtf8(v8Isolate, "length"))) {
v8SetReturnValueInt(info, v8::DontEnum | v8::DontDelete);
}
}
static void arrayDeleteProperty(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Boolean>& info)
{
v8::Isolate* v8Isolate = v8::Isolate::GetCurrent();
if (name->Equals(v8::String::NewFromUtf8(v8Isolate, "length"))) {
v8SetReturnValueBool(info, false);
}
}
void arrayToStringCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelper(info, "_arrayToString", "toString");
}
void arrayJoinCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelper1Arg(info, "_arrayJoin", "join");
}
void arrayPushCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelperWithArgsAsList(info, "_arrayPush", "push");
}
void arrayPopCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelper(info, "_arrayPop", "pop");
}
void concatCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelperWithArgsAsList(info, "_arrayConcat", "concat");
}
void arrayReverseCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelper(info, "_arrayReverse", "reverse");
}
void arrayShiftCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelper(info, "_arrayShift", "shift");
}
void arrayUnshiftCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelperWithArgsAsList(info, "_arrayUnshift", "unshift");
}
void arraySpliceCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
arrayHelperWithArgsAsList(info, "_arraySplice", "splice");
}
void toJsArrayCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
// This isn't a method on the JavaScript Array class so it is fine to
// just return the existing hopefully Array like JS object if not called on
// a Dart List.
if (!DartHandleProxy::isDartProxy(info.Holder())) {
v8SetReturnValue(info, info.Holder());
}
DartScopes scopes(info.Holder());
DartDOMData* domData = DartDOMData::current();
Dart_Handle handle = scopes.handle;
Dart_Handle exception = 0;
v8::Local<v8::Array> v8Array = shallowListToV8(handle, domData, exception);
ASSERT(!exception);
v8SetReturnValue(info, v8Array);
}
void arraySortCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
// TODO(jacobr): consider using the JavaScript sort method instead.
arrayHelper1Arg(info, "_arraySort", "sort");
}
v8::Local<v8::FunctionTemplate> dartListTemplate(v8::Isolate* v8Isolate)
{
DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, proxyTemplate, ());
v8::Local<v8::FunctionTemplate> proxyTemplateLocal;
if (proxyTemplate.IsEmpty()) {
proxyTemplate.Reset(v8Isolate, v8::FunctionTemplate::New(v8Isolate));
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
// Set to Array because we want these instances to appear to be
// JavaScript arrays as far as user code is concerned.
proxyTemplateLocal->SetClassName(v8::String::NewFromUtf8(v8Isolate, "Array"));
// Hack to set the prototype to be the prototype of an actual JavaScript Array.
v8::Local<v8::ObjectTemplate> protoTemplate = proxyTemplateLocal->PrototypeTemplate();
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "toString"), v8::FunctionTemplate::New(v8Isolate, arrayToStringCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "join"), v8::FunctionTemplate::New(v8Isolate, arrayJoinCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "push"), v8::FunctionTemplate::New(v8Isolate, arrayPushCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "pop"), v8::FunctionTemplate::New(v8Isolate, arrayPopCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "concat"), v8::FunctionTemplate::New(v8Isolate, concatCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "reverse"), v8::FunctionTemplate::New(v8Isolate, arrayReverseCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "shift"), v8::FunctionTemplate::New(v8Isolate, arrayShiftCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "unshift"), v8::FunctionTemplate::New(v8Isolate, arrayUnshiftCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "splice"), v8::FunctionTemplate::New(v8Isolate, arraySpliceCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "sort"), v8::FunctionTemplate::New(v8Isolate, arraySortCallback), v8::DontEnum);
protoTemplate ->Set(v8::String::NewFromUtf8(v8Isolate, "$toJsArray"), v8::FunctionTemplate::New(v8Isolate, toJsArrayCallback), v8::DontEnum);
// ES6 experimental properties not currently supported that we could support if needed.
// These would require building separate live proxy objects.
// "entries",
// "values",
// "keys"
v8::Local<v8::ObjectTemplate> instanceTemplate = setupInstanceTemplate(proxyTemplateLocal);
instanceTemplate->SetIndexedPropertyHandler(&indexedGetterArray, &indexedSetterArray, 0, 0, &indexedEnumeratorArray);
instanceTemplate->SetNamedPropertyHandler(&arrayNamedPropertyGetter, &arrayNamedPropertySetter, &arrayQueryProperty, &arrayDeleteProperty, 0);
} else {
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
}
return proxyTemplateLocal;
}
void dartObjectToStringCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
if (handleNonDartProxyThis(info, "toString")) {
return;
}
DartScopes scopes(info.Holder());
DartDOMData* domData = DartDOMData::current();
Dart_Handle handle = scopes.handle;
setJsReturnValueTyped(domData, info, Dart_ToString(handle));
}
static v8::Local<v8::FunctionTemplate> dartObjectTemplate(v8::Isolate* v8Isolate)
{
DEFINE_STATIC_LOCAL(v8::Persistent<v8::FunctionTemplate>, proxyTemplate, ());
v8::Local<v8::FunctionTemplate> proxyTemplateLocal;
if (proxyTemplate.IsEmpty()) {
proxyTemplate.Reset(v8Isolate, v8::FunctionTemplate::New(v8Isolate));
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
v8::Local<v8::ObjectTemplate> protoTemplate = proxyTemplateLocal->PrototypeTemplate();
protoTemplate->Set(v8::String::NewFromUtf8(v8Isolate, "toString"), v8::FunctionTemplate::New(v8Isolate, dartObjectToStringCallback));
proxyTemplateLocal->SetClassName(v8::String::NewFromUtf8(v8Isolate, "DartObject"));
setupInstanceTemplate(proxyTemplateLocal);
} else {
proxyTemplateLocal = v8::Local<v8::FunctionTemplate>::New(v8Isolate, proxyTemplate);
}
return proxyTemplateLocal;
}
}