summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc')
-rw-r--r--chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc308
1 files changed, 308 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc b/chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc
new file mode 100644
index 00000000000..abb3c4766ae
--- /dev/null
+++ b/chromium/third_party/blink/renderer/modules/remote_objects/remote_object.cc
@@ -0,0 +1,308 @@
+// Copyright 2019 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 "third_party/blink/renderer/modules/remote_objects/remote_object.h"
+#include "gin/converter.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
+#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
+
+namespace blink {
+
+gin::WrapperInfo RemoteObject::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+namespace {
+const char kMethodInvocationAsConstructorDisallowed[] =
+ "Java bridge method can't be invoked as a constructor";
+const char kMethodInvocationNonexistentMethod[] =
+ "Java bridge method does not exist for this object";
+const char kMethodInvocationOnNonInjectedObjectDisallowed[] =
+ "Java bridge method can't be invoked on a non-injected object";
+const char kMethodInvocationErrorMessage[] =
+ "Java bridge method invocation error";
+
+String RemoteInvocationErrorToString(
+ mojom::blink::RemoteInvocationError value) {
+ switch (value) {
+ case mojom::blink::RemoteInvocationError::METHOD_NOT_FOUND:
+ return "method not found";
+ case mojom::blink::RemoteInvocationError::OBJECT_GET_CLASS_BLOCKED:
+ return "invoking Object.getClass() is not permitted";
+ case mojom::blink::RemoteInvocationError::EXCEPTION_THROWN:
+ return "an exception was thrown";
+ default:
+ return String::Format("unknown RemoteInvocationError value: %d", value);
+ }
+}
+
+v8::Local<v8::Object> GetMethodCache(v8::Isolate* isolate,
+ v8::Local<v8::Object> object) {
+ static const V8PrivateProperty::SymbolKey kMethodCacheKey;
+ V8PrivateProperty::Symbol method_cache_symbol =
+ V8PrivateProperty::GetSymbol(isolate, kMethodCacheKey);
+ v8::Local<v8::Value> result;
+ if (!method_cache_symbol.GetOrUndefined(object).ToLocal(&result))
+ return v8::Local<v8::Object>();
+
+ if (result->IsUndefined()) {
+ result = v8::Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0);
+ ignore_result(method_cache_symbol.Set(object, result));
+ }
+
+ DCHECK(result->IsObject());
+ return result.As<v8::Object>();
+}
+
+mojom::blink::RemoteInvocationArgumentPtr JSValueToMojom(
+ const v8::Local<v8::Value>& js_value,
+ v8::Isolate* isolate) {
+ if (js_value->IsNumber()) {
+ return mojom::blink::RemoteInvocationArgument::NewNumberValue(
+ js_value->NumberValue(isolate->GetCurrentContext()).ToChecked());
+ }
+
+ if (js_value->IsBoolean()) {
+ return mojom::blink::RemoteInvocationArgument::NewBooleanValue(
+ js_value->BooleanValue(isolate));
+ }
+
+ if (js_value->IsString()) {
+ return mojom::blink::RemoteInvocationArgument::NewStringValue(
+ ToCoreString(js_value.As<v8::String>()));
+ }
+
+ if (js_value->IsNull()) {
+ return mojom::blink::RemoteInvocationArgument::NewSingletonValue(
+ mojom::blink::SingletonJavaScriptValue::kNull);
+ }
+
+ if (js_value->IsUndefined()) {
+ return mojom::blink::RemoteInvocationArgument::NewSingletonValue(
+ mojom::blink::SingletonJavaScriptValue::kUndefined);
+ }
+
+ if (js_value->IsArray()) {
+ auto array = js_value.As<v8::Array>();
+ WTF::Vector<mojom::blink::RemoteInvocationArgumentPtr> nested_arguments;
+ for (uint32_t i = 0; i < array->Length(); ++i) {
+ v8::Local<v8::Value> element_v8;
+
+ if (!array->Get(isolate->GetCurrentContext(), i).ToLocal(&element_v8))
+ return nullptr;
+
+ // The array length might change during iteration. Set the output array
+ // elements to null for nonexistent input array elements.
+ if (!array->HasRealIndexedProperty(isolate->GetCurrentContext(), i)
+ .FromMaybe(false)) {
+ nested_arguments.push_back(
+ mojom::blink::RemoteInvocationArgument::NewSingletonValue(
+ mojom::blink::SingletonJavaScriptValue::kNull));
+ } else {
+ mojom::blink::RemoteInvocationArgumentPtr nested_argument;
+
+ // This code prevents infinite recursion on the sender side.
+ // Null value is sent according to the Java-side conversion rules for
+ // expected parameter types:
+ // - multi-dimensional and object arrays are not allowed and are
+ // converted to nulls;
+ // - for primitive arrays, the null value will be converted to primitive
+ // zero;
+ // - for string arrays, the null value will be converted to a null
+ // string. See RemoteObjectImpl.convertArgument() in
+ // content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectImpl.java
+ if (element_v8->IsObject()) {
+ nested_argument =
+ mojom::blink::RemoteInvocationArgument::NewSingletonValue(
+ mojom::blink::SingletonJavaScriptValue::kNull);
+ } else {
+ nested_argument = JSValueToMojom(element_v8, isolate);
+ }
+
+ if (!nested_argument)
+ return nullptr;
+
+ nested_arguments.push_back(std::move(nested_argument));
+ }
+ }
+
+ return mojom::blink::RemoteInvocationArgument::NewArrayValue(
+ std::move(nested_arguments));
+ }
+
+ return nullptr;
+}
+
+v8::Local<v8::Value> MojomToJSValue(
+ const mojom::blink::RemoteInvocationResultValuePtr& result_value,
+ v8::Isolate* isolate) {
+ if (result_value->is_number_value()) {
+ return v8::Number::New(isolate, result_value->get_number_value());
+ }
+
+ if (result_value->is_boolean_value()) {
+ return v8::Boolean::New(isolate, result_value->get_boolean_value());
+ }
+
+ if (result_value->is_string_value()) {
+ return V8String(isolate, result_value->get_string_value());
+ }
+
+ switch (result_value->get_singleton_value()) {
+ case mojom::blink::SingletonJavaScriptValue::kNull:
+ return v8::Null(isolate);
+ case mojom::blink::SingletonJavaScriptValue::kUndefined:
+ return v8::Undefined(isolate);
+ }
+
+ return v8::Local<v8::Value>();
+}
+} // namespace
+
+RemoteObject::RemoteObject(v8::Isolate* isolate,
+ RemoteObjectGatewayImpl* gateway,
+ int32_t object_id)
+ : gin::NamedPropertyInterceptor(isolate, this),
+ gateway_(gateway),
+ object_id_(object_id) {}
+
+gin::ObjectTemplateBuilder RemoteObject::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return gin::Wrappable<RemoteObject>::GetObjectTemplateBuilder(isolate)
+ .AddNamedPropertyInterceptor();
+}
+
+void RemoteObject::RemoteObjectInvokeCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& info) {
+ v8::Isolate* isolate = info.GetIsolate();
+ if (info.IsConstructCall()) {
+ // This is not a constructor. Throw and return.
+ isolate->ThrowException(v8::Exception::Error(
+ V8String(isolate, kMethodInvocationAsConstructorDisallowed)));
+ return;
+ }
+
+ RemoteObject* remote_object;
+ if (!gin::ConvertFromV8(isolate, info.Holder(), &remote_object)) {
+ // Someone messed with the |this| pointer. Throw and return.
+ isolate->ThrowException(v8::Exception::Error(
+ V8String(isolate, kMethodInvocationOnNonInjectedObjectDisallowed)));
+ return;
+ }
+
+ String method_name = ToCoreString(info.Data().As<v8::String>());
+
+ v8::Local<v8::Object> method_cache = GetMethodCache(
+ isolate, remote_object->GetWrapper(isolate).ToLocalChecked());
+ if (method_cache.IsEmpty())
+ return;
+
+ v8::Local<v8::Value> cached_method =
+ method_cache
+ ->Get(isolate->GetCurrentContext(), info.Data().As<v8::String>())
+ .ToLocalChecked();
+
+ if (cached_method->IsUndefined()) {
+ isolate->ThrowException(v8::Exception::Error(
+ V8String(isolate, kMethodInvocationNonexistentMethod)));
+ return;
+ }
+
+ WTF::Vector<mojom::blink::RemoteInvocationArgumentPtr> arguments;
+ arguments.ReserveInitialCapacity(info.Length());
+
+ for (int i = 0; i < info.Length(); i++) {
+ auto argument = JSValueToMojom(info[i], isolate);
+ if (!argument)
+ return;
+
+ arguments.push_back(std::move(argument));
+ }
+
+ remote_object->EnsureRemoteIsBound();
+ mojom::blink::RemoteInvocationResultPtr result;
+ remote_object->object_->InvokeMethod(method_name, std::move(arguments),
+ &result);
+
+ if (result->error != mojom::blink::RemoteInvocationError::OK) {
+ String message = String::Format("%s : ", kMethodInvocationErrorMessage) +
+ RemoteInvocationErrorToString(result->error);
+ isolate->ThrowException(v8::Exception::Error(V8String(isolate, message)));
+ return;
+ }
+
+ if (!result->value)
+ return;
+
+ if (result->value->is_object_id()) {
+ // TODO(crbug.com/794320): need to check whether an object with this id has
+ // already been injected
+ RemoteObject* object_result =
+ new RemoteObject(info.GetIsolate(), remote_object->gateway_,
+ result->value->get_object_id());
+ gin::Handle<RemoteObject> controller =
+ gin::CreateHandle(isolate, object_result);
+ if (controller.IsEmpty())
+ info.GetReturnValue().SetUndefined();
+ else
+ info.GetReturnValue().Set(controller.ToV8());
+ } else {
+ info.GetReturnValue().Set(MojomToJSValue(result->value, isolate));
+ }
+}
+
+void RemoteObject::EnsureRemoteIsBound() {
+ if (!object_.is_bound()) {
+ gateway_->BindRemoteObjectReceiver(object_id_,
+ object_.BindNewPipeAndPassReceiver());
+ }
+}
+
+v8::Local<v8::Value> RemoteObject::GetNamedProperty(
+ v8::Isolate* isolate,
+ const std::string& property) {
+ auto wtf_property = WTF::String::FromUTF8(property);
+
+ v8::Local<v8::String> v8_property = V8AtomicString(isolate, wtf_property);
+ v8::Local<v8::Object> method_cache =
+ GetMethodCache(isolate, GetWrapper(isolate).ToLocalChecked());
+ if (method_cache.IsEmpty())
+ return v8::Local<v8::Value>();
+
+ v8::Local<v8::Value> cached_method =
+ method_cache->Get(isolate->GetCurrentContext(), v8_property)
+ .ToLocalChecked();
+
+ if (!cached_method->IsUndefined())
+ return cached_method;
+
+ // if not in the cache, ask the browser
+ EnsureRemoteIsBound();
+ bool method_exists = false;
+ object_->HasMethod(wtf_property, &method_exists);
+
+ if (!method_exists) {
+ return v8::Local<v8::Value>();
+ }
+
+ auto function = v8::Function::New(isolate->GetCurrentContext(),
+ RemoteObjectInvokeCallback, v8_property)
+ .ToLocalChecked();
+
+ ignore_result(method_cache->CreateDataProperty(isolate->GetCurrentContext(),
+ v8_property, function));
+ return function;
+}
+
+std::vector<std::string> RemoteObject::EnumerateNamedProperties(
+ v8::Isolate* isolate) {
+ EnsureRemoteIsBound();
+ WTF::Vector<WTF::String> methods;
+ object_->GetMethods(&methods);
+ std::vector<std::string> result;
+ for (const auto& method : methods)
+ result.push_back(method.Utf8());
+ return result;
+}
+
+} // namespace blink