diff options
author | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-27 18:10:06 +0000 |
---|---|---|
committer | mnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-27 18:10:06 +0000 |
commit | b8b6708119105a8c2e14f6de22d02cf320f6b76d (patch) | |
tree | 944931b848ba371ae953c7c5fc2657f0e7aae423 | |
parent | 9c50a6cb09ac89e3dfe70dd8f8468b9cea5eab7d (diff) | |
download | chromium_src-b8b6708119105a8c2e14f6de22d02cf320f6b76d.zip chromium_src-b8b6708119105a8c2e14f6de22d02cf320f6b76d.tar.gz chromium_src-b8b6708119105a8c2e14f6de22d02cf320f6b76d.tar.bz2 |
[Android] Implement renderer side of Gin Java Bridge
This change adds GinJavaBridgeObject and GinJavaBridgeDispatcher classes.
GinJavaBridgeDispatcher is a per-RenderFrame manager of injected
objects. It interacts with the browser side.
GinJavaBridgeObject is a Gin-based wrapper injected into page's context
that exposes methods of the corresponding Java object and receives
method invocation requests from JavaScript.
BUG=355644
R=jochen@chromium.org, thakis@chromium.org
Review URL: https://codereview.chromium.org/259033002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272997 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/id_map.h | 4 | ||||
-rw-r--r-- | content/content_renderer.gypi | 12 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | content/public/renderer/v8_value_converter.h | 3 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_dispatcher.cc | 125 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_dispatcher.h | 72 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_object.cc | 165 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_object.h | 70 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_value_converter.cc | 163 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_value_converter.h | 46 | ||||
-rw-r--r-- | content/renderer/java/gin_java_bridge_value_converter_unittest.cc | 138 | ||||
-rw-r--r-- | content/renderer/v8_value_converter_impl.cc | 10 | ||||
-rw-r--r-- | content/renderer/v8_value_converter_impl.h | 3 | ||||
-rw-r--r-- | content/renderer/v8_value_converter_impl_unittest.cc | 6 |
14 files changed, 809 insertions, 9 deletions
diff --git a/base/id_map.h b/base/id_map.h index 27098d2..9cbc1f8 100644 --- a/base/id_map.h +++ b/base/id_map.h @@ -32,8 +32,10 @@ enum IDMapOwnershipSemantics { // ownership semantics are set to own because pointers will leak. template<typename T, IDMapOwnershipSemantics OS = IDMapExternalPointer> class IDMap : public base::NonThreadSafe { - private: + public: typedef int32 KeyType; + + private: typedef base::hash_map<KeyType, T*> HashTable; public: diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index cc549dd..370a94a 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -191,6 +191,12 @@ 'renderer/input/input_handler_wrapper.h', 'renderer/internal_document_state_data.cc', 'renderer/internal_document_state_data.h', + 'renderer/java/gin_java_bridge_dispatcher.cc', + 'renderer/java/gin_java_bridge_dispatcher.h', + 'renderer/java/gin_java_bridge_object.cc', + 'renderer/java/gin_java_bridge_object.h', + 'renderer/java/gin_java_bridge_value_converter.cc', + 'renderer/java/gin_java_bridge_value_converter.h', 'renderer/java/java_bridge_channel.cc', 'renderer/java/java_bridge_channel.h', 'renderer/java/java_bridge_dispatcher.cc', @@ -607,6 +613,12 @@ ], }, { 'sources!': [ + 'renderer/java/gin_java_bridge_dispatcher.cc', + 'renderer/java/gin_java_bridge_dispatcher.h', + 'renderer/java/gin_java_bridge_object.cc', + 'renderer/java/gin_java_bridge_object.h', + 'renderer/java/gin_java_bridge_value_converter.cc', + 'renderer/java/gin_java_bridge_value_converter.h', 'renderer/java/java_bridge_channel.cc', 'renderer/java/java_bridge_channel.h', 'renderer/java/java_bridge_dispatcher.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 1db6c19..b52985a 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -875,6 +875,7 @@ ['OS == "android"', { 'sources': [ 'browser/renderer_host/java/jni_helper_unittest.cc', + 'renderer/java/gin_java_bridge_value_converter_unittest.cc', ], 'sources!': [ 'browser/geolocation/network_location_provider_unittest.cc', diff --git a/content/public/renderer/v8_value_converter.h b/content/public/renderer/v8_value_converter.h index 725980e..f3fd126 100644 --- a/content/public/renderer/v8_value_converter.h +++ b/content/public/renderer/v8_value_converter.h @@ -55,7 +55,8 @@ class CONTENT_EXPORT V8ValueConverter { // behavior. v8::Object is passed as ArrayBuffer and ArrayBufferView // classes are siblings. virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, - base::Value** out) const; + base::Value** out, + v8::Isolate* isolate) const; // If false is returned, V8ValueConverter proceeds with the default // behavior. This allows to intercept "non-finite" values and do something diff --git a/content/renderer/java/gin_java_bridge_dispatcher.cc b/content/renderer/java/gin_java_bridge_dispatcher.cc new file mode 100644 index 0000000..bacc23c --- /dev/null +++ b/content/renderer/java/gin_java_bridge_dispatcher.cc @@ -0,0 +1,125 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/java/gin_java_bridge_dispatcher.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "content/common/gin_java_bridge_messages.h" +#include "content/public/renderer/render_frame.h" +#include "content/renderer/java/gin_java_bridge_object.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +namespace content { + +GinJavaBridgeDispatcher::GinJavaBridgeDispatcher(RenderFrame* render_frame) + : RenderFrameObserver(render_frame) { +} + +GinJavaBridgeDispatcher::~GinJavaBridgeDispatcher() { +} + +bool GinJavaBridgeDispatcher::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(GinJavaBridgeDispatcher, msg) + IPC_MESSAGE_HANDLER(GinJavaBridgeMsg_AddNamedObject, OnAddNamedObject) + IPC_MESSAGE_HANDLER(GinJavaBridgeMsg_RemoveNamedObject, OnRemoveNamedObject) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void GinJavaBridgeDispatcher::DidClearWindowObject() { + for (NamedObjectMap::const_iterator iter = named_objects_.begin(); + iter != named_objects_.end(); ++iter) { + // Always create a new GinJavaBridgeObject, so we don't pull any of the V8 + // wrapper's custom properties into the context of the page we have + // navigated to. The old GinJavaBridgeObject will be automatically + // deleted after its wrapper will be collected. + // On the browser side, we ignore wrapper deletion events for named objects, + // as they are only removed upon embedder's request (RemoveNamedObject). + if (objects_.Lookup(iter->second)) + objects_.Remove(iter->second); + GinJavaBridgeObject* object = GinJavaBridgeObject::InjectNamed( + render_frame()->GetWebFrame(), AsWeakPtr(), iter->first, iter->second); + if (object) { + objects_.AddWithID(object, iter->second); + } else { + // Inform the host about wrapper creation failure. + render_frame()->Send(new GinJavaBridgeHostMsg_ObjectWrapperDeleted( + routing_id(), iter->second)); + } + } +} + +void GinJavaBridgeDispatcher::OnAddNamedObject( + const std::string& name, + ObjectID object_id) { + // Added objects only become available after page reload, so here they + // are only added into the internal map. + named_objects_.insert(std::make_pair(name, object_id)); +} + +void GinJavaBridgeDispatcher::OnRemoveNamedObject(const std::string& name) { + // Removal becomes in effect on next reload. We simply removing the entry + // from the map here. + NamedObjectMap::iterator iter = named_objects_.find(name); + DCHECK(iter != named_objects_.end()); + named_objects_.erase(iter); +} + +void GinJavaBridgeDispatcher::GetJavaMethods( + ObjectID object_id, + std::set<std::string>* methods) { + render_frame()->Send(new GinJavaBridgeHostMsg_GetMethods( + routing_id(), object_id, methods)); +} + +bool GinJavaBridgeDispatcher::HasJavaMethod(ObjectID object_id, + const std::string& method_name) { + bool result; + render_frame()->Send(new GinJavaBridgeHostMsg_HasMethod( + routing_id(), object_id, method_name, &result)); + return result; +} + +scoped_ptr<base::Value> GinJavaBridgeDispatcher::InvokeJavaMethod( + ObjectID object_id, + const std::string& method_name, + const base::ListValue& arguments) { + base::ListValue result_wrapper; + render_frame()->Send( + new GinJavaBridgeHostMsg_InvokeMethod(routing_id(), + object_id, + method_name, + arguments, + &result_wrapper)); + base::Value* result; + if (result_wrapper.Get(0, &result)) { + return scoped_ptr<base::Value>(result->DeepCopy()); + } else { + return scoped_ptr<base::Value>(); + } +} + +GinJavaBridgeObject* GinJavaBridgeDispatcher::GetObject(ObjectID object_id) { + GinJavaBridgeObject* result = objects_.Lookup(object_id); + if (!result) { + result = GinJavaBridgeObject::InjectAnonymous(AsWeakPtr(), object_id); + if (result) + objects_.AddWithID(result, object_id); + } + return result; +} + +void GinJavaBridgeDispatcher::OnGinJavaBridgeObjectDeleted(ObjectID object_id) { + if (!objects_.Lookup(object_id)) + return; + objects_.Remove(object_id); + render_frame()->Send( + new GinJavaBridgeHostMsg_ObjectWrapperDeleted(routing_id(), object_id)); +} + +} // namespace content diff --git a/content/renderer/java/gin_java_bridge_dispatcher.h b/content/renderer/java/gin_java_bridge_dispatcher.h new file mode 100644 index 0000000..c5348b97 --- /dev/null +++ b/content/renderer/java/gin_java_bridge_dispatcher.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_H_ +#define CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_H_ + +#include <map> +#include <set> + +#include "base/id_map.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/public/renderer/render_frame_observer.h" + +namespace blink { +class WebFrame; +} + +namespace content { + +class GinJavaBridgeObject; + +// This class handles injecting Java objects into the main frame of a +// RenderView. The 'add' and 'remove' messages received from the browser +// process modify the entries in a map of 'pending' objects. These objects are +// bound to the window object of the main frame when that window object is next +// cleared. These objects remain bound until the window object is cleared +// again. +class GinJavaBridgeDispatcher + : public base::SupportsWeakPtr<GinJavaBridgeDispatcher>, + public RenderFrameObserver { + public: + // GinJavaBridgeObjects are managed by gin. An object gets destroyed + // when it is no more referenced from JS. As GinJavaBridgeObject reports + // deletion of self to GinJavaBridgeDispatcher, we would not have stale + // pointers here. + typedef IDMap<GinJavaBridgeObject, IDMapExternalPointer> ObjectMap; + typedef ObjectMap::KeyType ObjectID; + + explicit GinJavaBridgeDispatcher(RenderFrame* render_frame); + virtual ~GinJavaBridgeDispatcher(); + + // RenderFrameObserver override: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void DidClearWindowObject() OVERRIDE; + + void GetJavaMethods(ObjectID object_id, std::set<std::string>* methods); + bool HasJavaMethod(ObjectID object_id, const std::string& method_name); + scoped_ptr<base::Value> InvokeJavaMethod(ObjectID object_id, + const std::string& method_name, + const base::ListValue& arguments); + GinJavaBridgeObject* GetObject(ObjectID object_id); + void OnGinJavaBridgeObjectDeleted(ObjectID object_id); + + private: + void OnAddNamedObject(const std::string& name, + ObjectID object_id); + void OnRemoveNamedObject(const std::string& name); + void OnSetAllowObjectContentsInspection(bool allow); + + typedef std::map<std::string, ObjectID> NamedObjectMap; + NamedObjectMap named_objects_; + ObjectMap objects_; + + DISALLOW_COPY_AND_ASSIGN(GinJavaBridgeDispatcher); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_DISPATCHER_H_ diff --git a/content/renderer/java/gin_java_bridge_object.cc b/content/renderer/java/gin_java_bridge_object.cc new file mode 100644 index 0000000..a153f2a --- /dev/null +++ b/content/renderer/java/gin_java_bridge_object.cc @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/java/gin_java_bridge_object.h" + +#include "base/strings/utf_string_conversions.h" +#include "content/common/android/gin_java_bridge_value.h" +#include "content/public/renderer/v8_value_converter.h" +#include "content/renderer/java/gin_java_bridge_value_converter.h" +#include "gin/function_template.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebKit.h" + +namespace content { + +namespace { + +const char kMethodInvocationErrorMessage[] = + "Java bridge method invocation error"; + +} // namespace + + +// static +GinJavaBridgeObject* GinJavaBridgeObject::InjectNamed( + blink::WebFrame* frame, + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + const std::string& object_name, + GinJavaBridgeDispatcher::ObjectID object_id) { + v8::Isolate* isolate = blink::mainThreadIsolate(); + v8::HandleScope handle_scope(isolate); + v8::Handle<v8::Context> context = frame->mainWorldScriptContext(); + if (context.IsEmpty()) + return NULL; + + GinJavaBridgeObject* object = + new GinJavaBridgeObject(isolate, dispatcher, object_id); + + v8::Context::Scope context_scope(context); + v8::Handle<v8::Object> global = context->Global(); + gin::Handle<GinJavaBridgeObject> controller = + gin::CreateHandle(isolate, object); + // WrappableBase instance deletes itself in case of a wrapper + // creation failure, thus there is no need to delete |object|. + if (controller.IsEmpty()) + return NULL; + + global->Set(gin::StringToV8(isolate, object_name), controller.ToV8()); + return object; +} + +// static +GinJavaBridgeObject* GinJavaBridgeObject::InjectAnonymous( + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + GinJavaBridgeDispatcher::ObjectID object_id) { + return new GinJavaBridgeObject( + blink::mainThreadIsolate(), dispatcher, object_id); +} + +GinJavaBridgeObject::GinJavaBridgeObject( + v8::Isolate* isolate, + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + GinJavaBridgeDispatcher::ObjectID object_id) + : gin::NamedPropertyInterceptor(isolate, this), + dispatcher_(dispatcher), + object_id_(object_id), + converter_(new GinJavaBridgeValueConverter()) { +} + +GinJavaBridgeObject::~GinJavaBridgeObject() { + if (dispatcher_) + dispatcher_->OnGinJavaBridgeObjectDeleted(object_id_); +} + +gin::ObjectTemplateBuilder GinJavaBridgeObject::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin::Wrappable<GinJavaBridgeObject>::GetObjectTemplateBuilder(isolate) + .AddNamedPropertyInterceptor(); +} + +v8::Local<v8::Value> GinJavaBridgeObject::GetNamedProperty( + v8::Isolate* isolate, + const std::string& property) { + if (dispatcher_ && dispatcher_->HasJavaMethod(object_id_, property)) { + return gin::CreateFunctionTemplate( + isolate, + base::Bind(&GinJavaBridgeObject::InvokeMethod, + base::Unretained(this), + property))->GetFunction(); + } else { + return v8::Local<v8::Value>(); + } +} + +std::vector<std::string> GinJavaBridgeObject::EnumerateNamedProperties( + v8::Isolate* isolate) { + std::set<std::string> method_names; + if (dispatcher_) + dispatcher_->GetJavaMethods(object_id_, &method_names); + return std::vector<std::string> (method_names.begin(), method_names.end()); +} + +v8::Handle<v8::Value> GinJavaBridgeObject::InvokeMethod( + const std::string& name, + gin::Arguments* args) { + if (!dispatcher_) { + args->isolate()->ThrowException(v8::Exception::Error(gin::StringToV8( + args->isolate(), kMethodInvocationErrorMessage))); + return v8::Undefined(args->isolate()); + } + + base::ListValue arguments; + { + v8::HandleScope handle_scope(args->isolate()); + v8::Handle<v8::Context> context = args->isolate()->GetCurrentContext(); + v8::Handle<v8::Value> val; + while (args->GetNext(&val)) { + scoped_ptr<base::Value> arg(converter_->FromV8Value(val, context)); + if (arg.get()) { + arguments.Append(arg.release()); + } else { + arguments.Append(base::Value::CreateNullValue()); + } + } + } + + scoped_ptr<base::Value> result = + dispatcher_->InvokeJavaMethod(object_id_, name, arguments); + if (!result.get()) { + args->isolate()->ThrowException(v8::Exception::Error(gin::StringToV8( + args->isolate(), kMethodInvocationErrorMessage))); + return v8::Undefined(args->isolate()); + } + if (!result->IsType(base::Value::TYPE_BINARY)) { + return converter_->ToV8Value(result.get(), + args->isolate()->GetCurrentContext()); + } + + scoped_ptr<const GinJavaBridgeValue> gin_value = + GinJavaBridgeValue::FromValue(result.get()); + if (gin_value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID)) { + GinJavaBridgeObject* result = NULL; + GinJavaBridgeDispatcher::ObjectID object_id; + if (gin_value->GetAsObjectID(&object_id)) { + result = dispatcher_->GetObject(object_id); + } + if (result) { + gin::Handle<GinJavaBridgeObject> controller = + gin::CreateHandle(args->isolate(), result); + if (controller.IsEmpty()) + return v8::Undefined(args->isolate()); + return controller.ToV8(); + } + } else if (gin_value->IsType(GinJavaBridgeValue::TYPE_NONFINITE)) { + float float_value; + gin_value->GetAsNonFinite(&float_value); + return v8::Number::New(args->isolate(), float_value); + } + return v8::Undefined(args->isolate()); +} + +gin::WrapperInfo GinJavaBridgeObject::kWrapperInfo = {gin::kEmbedderNativeGin}; + +} // namespace content diff --git a/content/renderer/java/gin_java_bridge_object.h b/content/renderer/java/gin_java_bridge_object.h new file mode 100644 index 0000000..97b74c5 --- /dev/null +++ b/content/renderer/java/gin_java_bridge_object.h @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_OBJECT_H_ +#define CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_OBJECT_H_ + +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/renderer/java/gin_java_bridge_dispatcher.h" +#include "gin/handle.h" +#include "gin/interceptor.h" +#include "gin/object_template_builder.h" +#include "gin/wrappable.h" + +namespace blink { +class WebFrame; +} + +namespace content { + +class GinJavaBridgeValueConverter; + +class GinJavaBridgeObject : public gin::Wrappable<GinJavaBridgeObject>, + public gin::NamedPropertyInterceptor { + public: + static gin::WrapperInfo kWrapperInfo; + + GinJavaBridgeDispatcher::ObjectID object_id() const { return object_id_; } + + // gin::Wrappable. + virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) OVERRIDE; + + // gin::NamedPropertyInterceptor + virtual v8::Local<v8::Value> GetNamedProperty( + v8::Isolate* isolate, const std::string& property) OVERRIDE; + virtual std::vector<std::string> EnumerateNamedProperties( + v8::Isolate* isolate) OVERRIDE; + + static GinJavaBridgeObject* InjectNamed( + blink::WebFrame* frame, + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + const std::string& object_name, + GinJavaBridgeDispatcher::ObjectID object_id); + static GinJavaBridgeObject* InjectAnonymous( + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + GinJavaBridgeDispatcher::ObjectID object_id); + + private: + GinJavaBridgeObject(v8::Isolate* isolate, + const base::WeakPtr<GinJavaBridgeDispatcher>& dispatcher, + GinJavaBridgeDispatcher::ObjectID object_id); + virtual ~GinJavaBridgeObject(); + + v8::Handle<v8::Value> InvokeMethod(const std::string& name, + gin::Arguments* args); + + base::WeakPtr<GinJavaBridgeDispatcher> dispatcher_; + GinJavaBridgeDispatcher::ObjectID object_id_; + scoped_ptr<GinJavaBridgeValueConverter> converter_; + + DISALLOW_COPY_AND_ASSIGN(GinJavaBridgeObject); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_OBJECT_H_ diff --git a/content/renderer/java/gin_java_bridge_value_converter.cc b/content/renderer/java/gin_java_bridge_value_converter.cc new file mode 100644 index 0000000..ad8fee9 --- /dev/null +++ b/content/renderer/java/gin_java_bridge_value_converter.cc @@ -0,0 +1,163 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/java/gin_java_bridge_value_converter.h" + +#include "base/float_util.h" +#include "base/values.h" +#include "content/common/android/gin_java_bridge_value.h" +#include "content/renderer/java/gin_java_bridge_object.h" +#include "gin/array_buffer.h" + +namespace content { + +GinJavaBridgeValueConverter::GinJavaBridgeValueConverter() + : converter_(V8ValueConverter::create()) { + converter_->SetDateAllowed(false); + converter_->SetRegExpAllowed(false); + converter_->SetFunctionAllowed(true); + converter_->SetStrategy(this); +} + +GinJavaBridgeValueConverter::~GinJavaBridgeValueConverter() { +} + +v8::Handle<v8::Value> GinJavaBridgeValueConverter::ToV8Value( + const base::Value* value, + v8::Handle<v8::Context> context) const { + return converter_->ToV8Value(value, context); +} + +scoped_ptr<base::Value> GinJavaBridgeValueConverter::FromV8Value( + v8::Handle<v8::Value> value, + v8::Handle<v8::Context> context) const { + return make_scoped_ptr(converter_->FromV8Value(value, context)); +} + +bool GinJavaBridgeValueConverter::FromV8Object( + v8::Handle<v8::Object> value, + base::Value** out, + v8::Isolate* isolate, + const FromV8ValueCallback& callback) const { + GinJavaBridgeObject* unwrapped; + if (!gin::ConvertFromV8(isolate, value, &unwrapped)) { + return false; + } + *out = + GinJavaBridgeValue::CreateObjectIDValue(unwrapped->object_id()).release(); + return true; +} + +namespace { + +class TypedArraySerializer { + public: + virtual ~TypedArraySerializer() {} + static scoped_ptr<TypedArraySerializer> Create( + v8::Handle<v8::TypedArray> typed_array); + virtual void serializeTo(char* data, + size_t data_length, + base::ListValue* out) = 0; + protected: + TypedArraySerializer() {} +}; + +template <typename ElementType, typename ListType> +class TypedArraySerializerImpl : public TypedArraySerializer { + public: + static scoped_ptr<TypedArraySerializer> Create( + v8::Handle<v8::TypedArray> typed_array) { + scoped_ptr<TypedArraySerializerImpl<ElementType, ListType> > result( + new TypedArraySerializerImpl<ElementType, ListType>(typed_array)); + return result.template PassAs<TypedArraySerializer>(); + } + + virtual void serializeTo(char* data, + size_t data_length, + base::ListValue* out) OVERRIDE { + DCHECK_EQ(data_length, typed_array_->Length() * sizeof(ElementType)); + for (ElementType *element = reinterpret_cast<ElementType*>(data), + *end = element + typed_array_->Length(); + element != end; + ++element) { + const ListType list_value = *element; + out->Append(new base::FundamentalValue(list_value)); + } + } + + private: + explicit TypedArraySerializerImpl(v8::Handle<v8::TypedArray> typed_array) + : typed_array_(typed_array) {} + + v8::Handle<v8::TypedArray> typed_array_; + + DISALLOW_COPY_AND_ASSIGN(TypedArraySerializerImpl); +}; + +// static +scoped_ptr<TypedArraySerializer> TypedArraySerializer::Create( + v8::Handle<v8::TypedArray> typed_array) { + if (typed_array->IsInt8Array() || + typed_array->IsUint8Array() || + typed_array->IsUint8ClampedArray()) { + return TypedArraySerializerImpl<char, int>::Create(typed_array).Pass(); + } else if (typed_array->IsInt16Array() || typed_array->IsUint16Array()) { + return TypedArraySerializerImpl<int16_t, int>::Create(typed_array).Pass(); + } else if (typed_array->IsInt32Array() || typed_array->IsUint32Array()) { + return TypedArraySerializerImpl<int32_t, int>::Create(typed_array).Pass(); + } else if (typed_array->IsFloat32Array()) { + return TypedArraySerializerImpl<float, double>::Create(typed_array).Pass(); + } else if (typed_array->IsFloat64Array()) { + return TypedArraySerializerImpl<double, double>::Create(typed_array).Pass(); + } + NOTREACHED(); + return scoped_ptr<TypedArraySerializer>(); +} + +} // namespace + +bool GinJavaBridgeValueConverter::FromV8ArrayBuffer( + v8::Handle<v8::Object> value, + base::Value** out, + v8::Isolate* isolate) const { + if (!value->IsTypedArray()) { + *out = GinJavaBridgeValue::CreateUndefinedValue().release(); + return true; + } + + char* data = NULL; + size_t data_length = 0; + gin::ArrayBufferView view; + if (ConvertFromV8(isolate, value.As<v8::ArrayBufferView>(), &view)) { + data = reinterpret_cast<char*>(view.bytes()); + data_length = view.num_bytes(); + } + if (!data) { + *out = GinJavaBridgeValue::CreateUndefinedValue().release(); + return true; + } + + base::ListValue* result = new base::ListValue(); + *out = result; + scoped_ptr<TypedArraySerializer> serializer( + TypedArraySerializer::Create(value.As<v8::TypedArray>())); + serializer->serializeTo(data, data_length, result); + return true; +} + +bool GinJavaBridgeValueConverter::FromV8Number(v8::Handle<v8::Number> value, + base::Value** out) const { + double double_value = value->Value(); + if (base::IsFinite(double_value)) + return false; + *out = GinJavaBridgeValue::CreateNonFiniteValue(double_value).release(); + return true; +} + +bool GinJavaBridgeValueConverter::FromV8Undefined(base::Value** out) const { + *out = GinJavaBridgeValue::CreateUndefinedValue().release(); + return true; +} + +} // namespace content diff --git a/content/renderer/java/gin_java_bridge_value_converter.h b/content/renderer/java/gin_java_bridge_value_converter.h new file mode 100644 index 0000000..5b834ea --- /dev/null +++ b/content/renderer/java/gin_java_bridge_value_converter.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_VALUE_CONVERTER_H_ +#define CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_VALUE_CONVERTER_H_ + +#include "base/memory/scoped_ptr.h" +#include "content/common/content_export.h" +#include "content/public/renderer/v8_value_converter.h" + +namespace content { + +class GinJavaBridgeValueConverter : public content::V8ValueConverter::Strategy { + public: + CONTENT_EXPORT GinJavaBridgeValueConverter(); + CONTENT_EXPORT virtual ~GinJavaBridgeValueConverter(); + + CONTENT_EXPORT v8::Handle<v8::Value> ToV8Value( + const base::Value* value, + v8::Handle<v8::Context> context) const; + CONTENT_EXPORT scoped_ptr<base::Value> FromV8Value( + v8::Handle<v8::Value> value, + v8::Handle<v8::Context> context) const; + + // content::V8ValueConverter::Strategy + virtual bool FromV8Object(v8::Handle<v8::Object> value, + base::Value** out, + v8::Isolate* isolate, + const FromV8ValueCallback& callback) const OVERRIDE; + virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, + base::Value** out, + v8::Isolate* isolate) const OVERRIDE; + virtual bool FromV8Number(v8::Handle<v8::Number> value, + base::Value** out) const OVERRIDE; + virtual bool FromV8Undefined(base::Value** out) const OVERRIDE; + + private: + scoped_ptr<V8ValueConverter> converter_; + + DISALLOW_COPY_AND_ASSIGN(GinJavaBridgeValueConverter); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_JAVA_GIN_JAVA_BRIDGE_VALUE_CONVERTER_H_ diff --git a/content/renderer/java/gin_java_bridge_value_converter_unittest.cc b/content/renderer/java/gin_java_bridge_value_converter_unittest.cc new file mode 100644 index 0000000..1a0812b --- /dev/null +++ b/content/renderer/java/gin_java_bridge_value_converter_unittest.cc @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/float_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/stringprintf.h" +#include "content/common/android/gin_java_bridge_value.h" +#include "content/renderer/java/gin_java_bridge_value_converter.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "v8/include/v8.h" + +namespace content { + +class GinJavaBridgeValueConverterTest : public testing::Test { + public: + GinJavaBridgeValueConverterTest() + : isolate_(v8::Isolate::GetCurrent()) { + } + + protected: + virtual void SetUp() { + v8::HandleScope handle_scope(isolate_); + v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_); + context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global)); + } + + virtual void TearDown() { + context_.Reset(); + } + + v8::Isolate* isolate_; + + // Context for the JavaScript in the test. + v8::Persistent<v8::Context> context_; +}; + +TEST_F(GinJavaBridgeValueConverterTest, BasicValues) { + v8::HandleScope handle_scope(isolate_); + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, context_); + v8::Context::Scope context_scope(context); + + scoped_ptr<GinJavaBridgeValueConverter> converter( + new GinJavaBridgeValueConverter()); + + v8::Handle<v8::Primitive> v8_undefined(v8::Undefined(isolate_)); + scoped_ptr<base::Value> undefined( + converter->FromV8Value(v8_undefined, context)); + ASSERT_TRUE(undefined.get()); + EXPECT_TRUE(GinJavaBridgeValue::ContainsGinJavaBridgeValue(undefined.get())); + scoped_ptr<const GinJavaBridgeValue> undefined_value( + GinJavaBridgeValue::FromValue(undefined.get())); + ASSERT_TRUE(undefined_value.get()); + EXPECT_TRUE(undefined_value->IsType(GinJavaBridgeValue::TYPE_UNDEFINED)); + + v8::Handle<v8::Number> v8_infinity( + v8::Number::New(isolate_, std::numeric_limits<double>::infinity())); + scoped_ptr<base::Value> infinity( + converter->FromV8Value(v8_infinity, context)); + ASSERT_TRUE(infinity.get()); + EXPECT_TRUE( + GinJavaBridgeValue::ContainsGinJavaBridgeValue(infinity.get())); + scoped_ptr<const GinJavaBridgeValue> infinity_value( + GinJavaBridgeValue::FromValue(infinity.get())); + ASSERT_TRUE(infinity_value.get()); + float native_float; + EXPECT_TRUE( + infinity_value->IsType(GinJavaBridgeValue::TYPE_NONFINITE)); + EXPECT_TRUE(infinity_value->GetAsNonFinite(&native_float)); + EXPECT_FALSE(base::IsFinite(native_float)); + EXPECT_FALSE(base::IsNaN(native_float)); +} + +TEST_F(GinJavaBridgeValueConverterTest, ArrayBuffer) { + v8::HandleScope handle_scope(isolate_); + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, context_); + v8::Context::Scope context_scope(context); + + scoped_ptr<GinJavaBridgeValueConverter> converter( + new GinJavaBridgeValueConverter()); + + v8::Handle<v8::ArrayBuffer> v8_array_buffer( + v8::ArrayBuffer::New(isolate_, 0)); + scoped_ptr<base::Value> undefined( + converter->FromV8Value(v8_array_buffer, context)); + ASSERT_TRUE(undefined.get()); + EXPECT_TRUE(GinJavaBridgeValue::ContainsGinJavaBridgeValue(undefined.get())); + scoped_ptr<const GinJavaBridgeValue> undefined_value( + GinJavaBridgeValue::FromValue(undefined.get())); + ASSERT_TRUE(undefined_value.get()); + EXPECT_TRUE(undefined_value->IsType(GinJavaBridgeValue::TYPE_UNDEFINED)); +} + +TEST_F(GinJavaBridgeValueConverterTest, TypedArrays) { + v8::HandleScope handle_scope(isolate_); + v8::Local<v8::Context> context = + v8::Local<v8::Context>::New(isolate_, context_); + v8::Context::Scope context_scope(context); + + scoped_ptr<GinJavaBridgeValueConverter> converter( + new GinJavaBridgeValueConverter()); + + const char* source_template = "(function() {" + "var array_buffer = new ArrayBuffer(%s);" + "var array_view = new %s(array_buffer);" + "array_view[0] = 42;" + "return array_view;" + "})();"; + const char* array_types[] = { + "1", "Int8Array", "1", "Uint8Array", "1", "Uint8ClampedArray", + "2", "Int16Array", "2", "Uint16Array", + "4", "Int32Array", "4", "Uint32Array", + "4", "Float32Array", "8", "Float64Array" + }; + for (size_t i = 0; i < arraysize(array_types); i += 2) { + const char* typed_array_type = array_types[i + 1]; + v8::Handle<v8::Script> script(v8::Script::Compile(v8::String::NewFromUtf8( + isolate_, + base::StringPrintf( + source_template, array_types[i], typed_array_type).c_str()))); + v8::Handle<v8::Value> v8_typed_array = script->Run(); + scoped_ptr<base::Value> list_value( + converter->FromV8Value(v8_typed_array, context)); + ASSERT_TRUE(list_value.get()) << typed_array_type; + EXPECT_TRUE(list_value->IsType(base::Value::TYPE_LIST)) << typed_array_type; + base::ListValue* list; + ASSERT_TRUE(list_value->GetAsList(&list)) << typed_array_type; + EXPECT_EQ(1u, list->GetSize()) << typed_array_type; + double first_element; + ASSERT_TRUE(list->GetDouble(0, &first_element)) << typed_array_type; + EXPECT_EQ(42.0, first_element) << typed_array_type; + } +} + +} // namespace content diff --git a/content/renderer/v8_value_converter_impl.cc b/content/renderer/v8_value_converter_impl.cc index b321f55..d12b26b 100644 --- a/content/renderer/v8_value_converter_impl.cc +++ b/content/renderer/v8_value_converter_impl.cc @@ -38,7 +38,8 @@ bool V8ValueConverter::Strategy::FromV8Array( } bool V8ValueConverter::Strategy::FromV8ArrayBuffer(v8::Handle<v8::Object> value, - base::Value** out) const { + base::Value** out, + v8::Isolate* isolate) const { return false; } @@ -342,7 +343,7 @@ base::Value* V8ValueConverterImpl::FromV8ValueImpl( } if (val->IsArrayBuffer() || val->IsArrayBufferView()) - return FromV8ArrayBuffer(val->ToObject()); + return FromV8ArrayBuffer(val->ToObject(), isolate); if (val->IsObject()) return FromV8Object(val->ToObject(), state, isolate); @@ -405,10 +406,11 @@ base::Value* V8ValueConverterImpl::FromV8Array( } base::Value* V8ValueConverterImpl::FromV8ArrayBuffer( - v8::Handle<v8::Object> val) const { + v8::Handle<v8::Object> val, + v8::Isolate* isolate) const { if (strategy_) { base::Value* out = NULL; - if (strategy_->FromV8ArrayBuffer(val, &out)) + if (strategy_->FromV8ArrayBuffer(val, &out, isolate)) return out; } diff --git a/content/renderer/v8_value_converter_impl.h b/content/renderer/v8_value_converter_impl.h index d026a18..a5288ae 100644 --- a/content/renderer/v8_value_converter_impl.h +++ b/content/renderer/v8_value_converter_impl.h @@ -61,7 +61,8 @@ class CONTENT_EXPORT V8ValueConverterImpl : public V8ValueConverter { // This will convert objects of type ArrayBuffer or any of the // ArrayBufferView subclasses. - base::Value* FromV8ArrayBuffer(v8::Handle<v8::Object> val) const; + base::Value* FromV8ArrayBuffer(v8::Handle<v8::Object> val, + v8::Isolate* isolate) const; base::Value* FromV8Object(v8::Handle<v8::Object> object, FromV8ValueState* state, diff --git a/content/renderer/v8_value_converter_impl_unittest.cc b/content/renderer/v8_value_converter_impl_unittest.cc index f6f252d..6f1302d 100644 --- a/content/renderer/v8_value_converter_impl_unittest.cc +++ b/content/renderer/v8_value_converter_impl_unittest.cc @@ -732,7 +732,8 @@ class V8ValueConverterOverridingStrategyForTesting return true; } virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, - base::Value** out) const OVERRIDE { + base::Value** out, + v8::Isolate* isolate) const OVERRIDE { *out = NewReferenceValue(); return true; } @@ -822,7 +823,8 @@ class V8ValueConverterBypassStrategyForTesting return false; } virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, - base::Value** out) const OVERRIDE { + base::Value** out, + v8::Isolate* isolate) const OVERRIDE { return false; } virtual bool FromV8Number(v8::Handle<v8::Number> value, |