Chromium Code Reviews| Index: content/browser/android/app_web_message_port.cc |
| diff --git a/content/browser/android/app_web_message_port.cc b/content/browser/android/app_web_message_port.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..85b80258b91414538b459dbfe04eac572ce212dd |
| --- /dev/null |
| +++ b/content/browser/android/app_web_message_port.cc |
| @@ -0,0 +1,265 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
|
jam
2017/01/23 18:08:56
I don't remember who worked on the initial impleme
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/android/app_web_message_port.h" |
| + |
| +#include "base/android/jni_android.h" |
| +#include "base/android/jni_string.h" |
| +#include "base/bind.h" |
| +#include "base/logging.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "jni/AppWebMessagePort_jni.h" |
| + |
| +namespace content { |
| +namespace { |
| + |
| +// To avoid setting up a V8 context here, we just hard code the format for |
| +// string type messages. /me ducks! |
| + |
| +const uint32_t kVarIntShift = 7; |
| +const uint32_t kVarIntMask = (1 << kVarIntShift) - 1; |
| + |
| +const uint8_t kVersionTag = 0xFF; |
| +const uint8_t kStringTag = 'S'; |
| +const uint8_t kStringUCharTag = 'c'; |
| + |
| +const uint32_t kVersion = 9; |
| + |
| +void WriteUint8(uint8_t value, std::vector<uint8_t>* buffer) { |
| + buffer->push_back(value); |
| +} |
| + |
| +void WriteUint32(uint32_t value, std::vector<uint8_t>* buffer) { |
| + for (;;) { |
| + uint8_t b = (value & kVarIntMask); |
| + value >>= kVarIntShift; |
| + if (!value) { |
| + WriteUint8(b, buffer); |
| + break; |
| + } |
| + WriteUint8(b | (1 << kVarIntShift), buffer); |
| + } |
| +} |
| + |
| +void WriteBytes(const char* bytes, size_t num_bytes, |
| + std::vector<uint8_t>* buffer) { |
| + buffer->insert(buffer->end(), bytes, bytes + num_bytes); |
| +} |
| + |
| +base::string16 EncodeStringMessage(const base::string16& data) { |
| + std::string data_utf8(base::UTF16ToUTF8(data)); |
| + |
| + std::vector<uint8_t> buffer; |
| + WriteUint8(kVersionTag, &buffer); |
| + WriteUint32(kVersion, &buffer); |
| + |
| + WriteUint8(kStringTag, &buffer); |
| + WriteUint32(static_cast<uint32_t>(data_utf8.size()), &buffer); |
| + WriteBytes(data_utf8.data(), data_utf8.size(), &buffer); |
| + |
| + base::string16 result; |
| + size_t result_num_bytes = (buffer.size() + 1) & ~1; |
| + result.resize(result_num_bytes / 2); |
| + uint8_t* destination = reinterpret_cast<uint8_t*>(&result[0]); |
| + memcpy(destination, &buffer[0], buffer.size()); |
| + if (result_num_bytes > buffer.size()) |
|
jbroman
2017/01/24 22:41:51
nit: std::basic_string::resize promises to zero-in
darin (slow to review)
2017/02/04 01:43:02
Thanks
|
| + destination[result_num_bytes - 1] = '\0'; |
| + |
| + return result; |
| +} |
| + |
| +bool ReadUint8(const uint8_t** ptr, const uint8_t* end, uint8_t* value) { |
| + if (*ptr >= end) |
| + return false; |
| + *value = *(*ptr)++; |
| + return true; |
| +} |
| + |
| +bool ReadUint32(const uint8_t** ptr, const uint8_t* end, uint32_t* value) { |
| + *value = 0; |
| + uint8_t current_byte; |
| + int shift = 0; |
| + do { |
| + if (*ptr >= end) |
| + return false; |
| + current_byte = *(*ptr)++; |
| + *value |= (static_cast<uint32_t>(current_byte & kVarIntMask) << shift); |
|
jbroman
2017/01/24 22:41:51
nit: shifts of >=32 bits are undefined behavior.
|
| + shift += kVarIntShift; |
| + } while (current_byte & (1 << kVarIntShift)); |
| + return true; |
| +} |
| + |
| +bool DecodeStringMessage(const base::string16& encoded_data, |
| + base::string16* result) { |
| + size_t num_bytes = encoded_data.size() * 2; |
| + |
| + const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&encoded_data[0]); |
| + const uint8_t* end = ptr + num_bytes; |
| + |
| + uint8_t tag; |
| + if (!ReadUint8(&ptr, end, &tag) || tag != kVersionTag) |
| + return false; |
| + |
| + uint32_t version; |
| + if (!ReadUint32(&ptr, end, &version)) |
| + return false; |
| + |
| + if (!ReadUint8(&ptr, end, &tag)) |
| + return false; |
| + |
| + switch (tag) { |
|
jbroman
2017/01/24 22:41:51
You also need to be able to handle the padding tag
|
| + case kStringTag: { |
| + uint32_t num_utf8_bytes; |
| + if (!ReadUint32(&ptr, end, &num_utf8_bytes)) |
| + return false; |
| + const char* utf8_start = reinterpret_cast<const char*>(ptr); |
| + return base::UTF8ToUTF16(utf8_start, num_utf8_bytes, result); |
| + } |
| + case kStringUCharTag: { |
| + uint32_t num_uchar_bytes; |
| + if (!ReadUint32(&ptr, end, &num_uchar_bytes)) |
| + return false; |
| + const base::char16* uchar_start = |
| + reinterpret_cast<const base::char16*>(ptr); |
| + result->assign(uchar_start, num_uchar_bytes / 2); |
| + return true; |
| + } |
| + } |
| + |
| + LOG(WARNING) << "Unexpected tag: " << tag; |
| + return false; |
| +} |
| + |
| +} // namespace |
| + |
| +// static |
| +void AppWebMessagePort::CreateAndBindToJavaObject( |
| + JNIEnv* env, |
| + mojo::ScopedMessagePipeHandle handle, |
| + const base::android::JavaRef<jobject>& jobject) { |
| + AppWebMessagePort* instance = |
| + new AppWebMessagePort(env, std::move(handle), jobject); |
| + Java_AppWebMessagePort_setNativeAppWebMessagePort( |
| + env, jobject, reinterpret_cast<jlong>(instance)); |
| +} |
| + |
| +// static |
| +std::vector<MessagePort> AppWebMessagePort::UnwrapJavaArray( |
| + JNIEnv* env, |
| + const base::android::JavaRef<jobjectArray>& jports) { |
| + std::vector<MessagePort> ports; |
| + if (!env->IsSameObject(jports.obj(), nullptr)) { |
| + jsize num_ports = env->GetArrayLength(jports.obj()); |
| + for (jsize i = 0; i < num_ports; ++i) { |
| + base::android::ScopedJavaLocalRef<jobject> jport( |
| + env, env->GetObjectArrayElement(jports.obj(), i)); |
| + jlong native_port = |
| + Java_AppWebMessagePort_releaseNativePortForTransfer(env, jport); |
| + // Uninitialized ports should be trapper earlier at the Java layer. |
| + DCHECK(native_port != -1); |
| + AppWebMessagePort* instance = |
| + reinterpret_cast<AppWebMessagePort*>(native_port); |
| + ports.push_back(instance->port_); |
| + } |
| + } |
| + return ports; |
| +} |
| + |
| +void AppWebMessagePort::CloseMessagePort( |
| + JNIEnv* env, |
| + const base::android::JavaParamRef<jobject>& jcaller) { |
| + // Explicitly reset the port here to ensure that OnMessagesAvailable has |
| + // finished before we destroy this. |
| + port_ = MessagePort(); |
| + |
| + delete this; |
| +} |
| + |
| +void AppWebMessagePort::PostMessage( |
| + JNIEnv* env, |
| + const base::android::JavaParamRef<jobject>& jcaller, |
| + const base::android::JavaParamRef<jstring>& jmessage, |
| + const base::android::JavaParamRef<jobjectArray>& jports) { |
| + port_.PostMessage( |
| + EncodeStringMessage(base::android::ConvertJavaStringToUTF16(jmessage)), |
| + UnwrapJavaArray(env, jports)); |
| +} |
| + |
| +void AppWebMessagePort::DispatchReceivedMessages( |
| + JNIEnv* env, |
| + const base::android::JavaParamRef<jobject>& jcaller) { |
| + jmethodID app_web_message_port_constructor = |
| + base::android::MethodID::Get<base::android::MethodID::TYPE_INSTANCE>( |
| + env, AppWebMessagePort_clazz(env), "<init>", "()V"); |
| + |
| + // Consume all of the available messages. |
| + // TODO(darin): Consider breaking this up across multiple PostTask calls. |
| + for (;;) { |
| + base::string16 encoded_message; |
| + std::vector<MessagePort> ports; |
| + if (!port_.GetMessage(&encoded_message, &ports)) |
| + return; |
| + |
| + base::string16 message; |
| + if (!DecodeStringMessage(encoded_message, &message)) |
| + return; |
| + |
| + base::android::ScopedJavaLocalRef<jstring> jmessage = |
| + base::android::ConvertUTF16ToJavaString(env, message); |
| + |
| + base::android::ScopedJavaLocalRef<jobjectArray> jports; |
| + if (ports.size() > 0) { |
| + jports = base::android::ScopedJavaLocalRef<jobjectArray>( |
| + env, |
| + env->NewObjectArray(ports.size(), |
| + AppWebMessagePort_clazz(env), |
| + nullptr)); |
| + |
| + // Instantiate the Java and C++ wrappers for the transferred ports. |
| + for (size_t i = 0; i < ports.size(); ++i) { |
| + base::android::ScopedJavaLocalRef<jobject> jport( |
| + env, |
| + env->NewObject(AppWebMessagePort_clazz(env), |
| + app_web_message_port_constructor)); |
| + CreateAndBindToJavaObject(env, ports[i].ReleaseHandle(), jport); |
| + |
| + env->SetObjectArrayElement(jports.obj(), i, jport.obj()); |
| + } |
| + } |
| + |
| + Java_AppWebMessagePort_onReceivedMessage( |
| + env, java_ref_.get(env), jmessage, jports); |
| + } |
| +} |
| + |
| +void AppWebMessagePort::StartReceivingMessages( |
| + JNIEnv* env, |
| + const base::android::JavaParamRef<jobject>& jcaller) { |
| + port_.SetCallback( |
| + base::Bind(&AppWebMessagePort::OnMessagesAvailable, |
| + base::Unretained(this))); |
| +} |
| + |
| +AppWebMessagePort::AppWebMessagePort( |
| + JNIEnv* env, |
| + mojo::ScopedMessagePipeHandle handle, |
| + const base::android::JavaRef<jobject>& jobject) |
| + : port_(std::move(handle)), |
| + java_ref_(env, jobject) { |
| +} |
| + |
| +AppWebMessagePort::~AppWebMessagePort() { |
| +} |
| + |
| +void AppWebMessagePort::OnMessagesAvailable() { |
| + // Called on the IO thread. |
| + JNIEnv* env = base::android::AttachCurrentThread(); |
| + Java_AppWebMessagePort_onMessagesAvailable(env, java_ref_.get(env)); |
| +} |
| + |
| +bool RegisterAppWebMessagePort(JNIEnv* env) { |
| + return RegisterNativesImpl(env); |
| +} |
| + |
| +} // namespace content |