| // Copyright 2022 The Chromium Authors |
| // 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 <memory> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "content/public/android/content_jni_headers/AppWebMessagePort_jni.h" |
| #include "content/public/browser/android/message_payload.h" |
| #include "content/public/browser/android/message_port_helper.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/system/message_pipe.h" |
| #include "third_party/blink/public/common/messaging/message_port_channel.h" |
| #include "third_party/blink/public/common/messaging/message_port_descriptor.h" |
| #include "third_party/blink/public/common/messaging/string_message_codec.h" |
| #include "third_party/blink/public/common/messaging/transferable_message.h" |
| #include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h" |
| #include "third_party/blink/public/common/messaging/web_message_port.h" |
| #include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h" |
| |
| namespace content::android { |
| |
| base::android::ScopedJavaLocalRef<jobjectArray> CreateJavaMessagePort( |
| std::vector<blink::MessagePortDescriptor> descriptors) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::vector<base::android::ScopedJavaLocalRef<jobject>> j_descriptors; |
| j_descriptors.reserve(descriptors.size()); |
| for (auto& descriptor : descriptors) { |
| j_descriptors.push_back(AppWebMessagePort::Create(std::move(descriptor))); |
| } |
| |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return base::android::ToTypedJavaArrayOfObjects( |
| env, base::make_span(j_descriptors), |
| org_chromium_content_browser_AppWebMessagePort_clazz(env)); |
| } |
| |
| // static |
| base::android::ScopedJavaLocalRef<jobject> AppWebMessagePort::Create( |
| blink::MessagePortDescriptor&& descriptor) { |
| auto app_web_message_port = |
| base::WrapUnique(new AppWebMessagePort(std::move(descriptor))); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| auto* app_web_messge_port_ptr = app_web_message_port.get(); |
| auto j_obj = Java_AppWebMessagePort_Constructor( |
| env, reinterpret_cast<intptr_t>(app_web_message_port.release())); |
| app_web_messge_port_ptr->j_obj_ = JavaObjectWeakGlobalRef(env, j_obj); |
| return j_obj; |
| } |
| |
| // static |
| std::vector<blink::MessagePortDescriptor> AppWebMessagePort::Release( |
| JNIEnv* env, |
| const base::android::JavaRef<jobjectArray>& jports) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::vector<blink::MessagePortDescriptor> ports; |
| if (!jports.is_null()) { |
| for (auto jport : jports.ReadElements<jobject>()) { |
| jlong port_ptr = Java_AppWebMessagePort_getNativeObj(env, jport); |
| // Ports are heap allocated native objects. Since we are taking ownership |
| // of the object from the Java code we are responsible for cleaning it up. |
| std::unique_ptr<AppWebMessagePort> port = |
| base::WrapUnique(reinterpret_cast<AppWebMessagePort*>(port_ptr)); |
| ports.emplace_back(port->PassPort()); |
| } |
| } |
| return ports; |
| } |
| |
| AppWebMessagePort::AppWebMessagePort(blink::MessagePortDescriptor&& descriptor) |
| : runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), |
| descriptor_(std::move(descriptor)) { |
| // AppWebMessagePort can only be created on main thread. |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| connector_ = std::make_unique<mojo::Connector>( |
| descriptor_.TakeHandleToEntangleWithEmbedder(), |
| mojo::Connector::SINGLE_THREADED_SEND); |
| connector_->set_connection_error_handler( |
| base::BindOnce(&AppWebMessagePort::OnPipeError, base::Unretained(this))); |
| } |
| |
| AppWebMessagePort::~AppWebMessagePort() { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| GiveDisentangledHandleIfNeeded(); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_AppWebMessagePort_nativeDestroyed(env, GetJavaObj(env)); |
| } |
| |
| // JNI |
| void AppWebMessagePort::PostMessage( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& j_message_payload, |
| const base::android::JavaParamRef<jobjectArray>& j_ports) { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| DCHECK(descriptor_.IsValid()); |
| DCHECK(connector_); |
| if (connector_->encountered_error()) { |
| LOG(ERROR) |
| << "Failed to send message to renderer, connector encountered error."; |
| return; |
| } |
| blink::TransferableMessage transferable_message = |
| blink::EncodeWebMessagePayload(ConvertToWebMessagePayloadFromJava( |
| base::android::ScopedJavaLocalRef<jobject>(j_message_payload))); |
| transferable_message.ports = |
| blink::MessagePortChannel::CreateFromHandles(Release(env, j_ports)); |
| // As the message is posted from an Android app and not from another renderer, |
| // set the agent cluster ID to the embedder's, and nullify its parent task ID. |
| transferable_message.sender_agent_cluster_id = |
| blink::WebMessagePort::GetEmbedderAgentClusterID(); |
| transferable_message.parent_task_id = std::nullopt; |
| |
| mojo::Message mojo_message = |
| blink::mojom::TransferableMessage::SerializeAsMessage( |
| &transferable_message); |
| bool send_result = connector_->Accept(&mojo_message); |
| DCHECK(send_result); |
| } |
| |
| void AppWebMessagePort::SetShouldReceiveMessages(JNIEnv* env, |
| bool should_receive_message) { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| DCHECK(connector_); |
| if (!should_receive_message) { |
| connector_->set_incoming_receiver(nullptr); |
| j_strong_obj_.Reset(); |
| } else { |
| connector_->set_incoming_receiver(this); |
| if (!connector_errored_) { |
| j_strong_obj_ = j_obj_.get(env); |
| } |
| if (!is_watching_) { |
| is_watching_ = true; |
| connector_->StartReceiving(runner_); |
| } |
| } |
| } |
| |
| void AppWebMessagePort::CloseAndDestroy(JNIEnv* env) { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| DCHECK(connector_); |
| delete this; |
| } |
| |
| // mojo::MessageReceiver: |
| bool AppWebMessagePort::Accept(mojo::Message* message) { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| blink::TransferableMessage transferable_message; |
| if (!blink::mojom::TransferableMessage::DeserializeFromMessage( |
| std::move(*message), &transferable_message)) { |
| // Decode mojo message failed. |
| return false; |
| } |
| auto ports = std::move(transferable_message.ports); |
| auto optional_payload = |
| blink::DecodeToWebMessagePayload(std::move(transferable_message)); |
| if (!optional_payload) { |
| // Unsupported or invalid payload. |
| return true; |
| } |
| const auto& payload = optional_payload.value(); |
| |
| auto j_ports = |
| CreateJavaMessagePort(blink::MessagePortChannel::ReleaseHandles(ports)); |
| base::android::ScopedJavaLocalRef<jobject> j_message = |
| ConvertWebMessagePayloadToJava(payload); |
| DCHECK(j_message); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_AppWebMessagePort_onMessage(env, GetJavaObj(env), j_message, j_ports); |
| return true; |
| } |
| |
| blink::MessagePortDescriptor AppWebMessagePort::PassPort() { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_AppWebMessagePort_setTransferred(env, GetJavaObj(env)); |
| GiveDisentangledHandleIfNeeded(); |
| return std::move(descriptor_); |
| } |
| |
| void AppWebMessagePort::OnPipeError() { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| connector_errored_ = true; |
| j_strong_obj_.Reset(); |
| } |
| |
| void AppWebMessagePort::GiveDisentangledHandleIfNeeded() { |
| DCHECK(runner_->BelongsToCurrentThread()); |
| if (!connector_ || !descriptor_.IsValid()) { |
| return; |
| } |
| if (connector_errored_) { |
| // Return an empty message pipe. |
| descriptor_.GiveDisentangledHandle(mojo::ScopedMessagePipeHandle()); |
| } else { |
| descriptor_.GiveDisentangledHandle(connector_->PassMessagePipe()); |
| } |
| connector_.reset(); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobjectArray> |
| JNI_AppWebMessagePort_CreatePair(JNIEnv* env) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| blink::MessagePortDescriptorPair port_pair; |
| std::vector<blink::MessagePortDescriptor> descriptors; |
| descriptors.emplace_back(port_pair.TakePort0()); |
| descriptors.emplace_back(port_pair.TakePort1()); |
| return CreateJavaMessagePort(std::move(descriptors)); |
| } |
| |
| } // namespace content::android |