#include "extensions/renderer/api/messaging/one_time_message_handler.h"
#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/supports_user_data.h"
#include "content/public/renderer/render_frame.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/api/messaging/port_id.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/common/mojom/message_port.mojom-shared.h"
#include "extensions/renderer/api/messaging/message_target.h"
#include "extensions/renderer/api/messaging/messaging_util.h"
#include "extensions/renderer/bindings/api_binding_types.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/bindings/api_bindings_system.h"
#include "extensions/renderer/bindings/api_event_handler.h"
#include "extensions/renderer/bindings/api_request_handler.h"
#include "extensions/renderer/bindings/get_per_context_data.h"
#include "extensions/renderer/console.h"
#include "extensions/renderer/gc_callback.h"
#include "extensions/renderer/get_script_context.h"
#include "extensions/renderer/ipc_message_sender.h"
#include "extensions/renderer/native_extension_bindings_system.h"
#include "extensions/renderer/script_context.h"
#include "gin/arguments.h"
#include "gin/data_object_builder.h"
#include "gin/dictionary.h"
#include "gin/handle.h"
#include "gin/per_context_data.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-external.h"
#include "v8/include/v8-function-callback.h"
#include "v8/include/v8-function.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-persistent-handle.h"
#include "v8/include/v8-primitive.h"
namespace extensions {
namespace {
struct OneTimeOpener {
int request_id = -1;
binding::AsyncResponseType async_type = binding::AsyncResponseType::kNone;
mojom::ChannelType channel_type;
};
struct OneTimeReceiver {
std::string event_name;
v8::Global<v8::Object> sender;
v8::Global<v8::Function> message_response_function;
};
struct OneTimeMessageContextData : public base::SupportsUserData::Data {
static constexpr char kPerContextDataKey[] =
"extension_one_time_message_context_data";
std::map<PortId, OneTimeOpener> openers;
std::map<PortId, OneTimeReceiver> receivers;
using OneTimePortCallbacks =
std::map<OneTimeMessageHandler::CallbackID,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>>;
std::map<PortId, OneTimePortCallbacks> pending_receiver_callbacks;
};
constexpr char OneTimeMessageContextData::kPerContextDataKey[];
bool IsMessagePolyfillSupportEnabled() {
return base::FeatureList::IsEnabled(
extensions_features::kRuntimeOnMessageWebExtensionPolyfillSupport);
}
v8::Local<v8::Array> GetListenerResultArray(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Value> result,
const char* property_name) {
if (result->IsUndefined()) {
return v8::Local<v8::Array>();
}
if (!result->IsObject()) {
return v8::Local<v8::Array>();
}
v8::Local<v8::Object> result_object = result.As<v8::Object>();
v8::Local<v8::Value> array_value;
if (!result_object->Get(context, gin::StringToSymbol(isolate, property_name))
.ToLocal(&array_value) ||
!array_value->IsArray()) {
return v8::Local<v8::Array>();
}
return array_value.As<v8::Array>();
}
void DelayedOneTimeMessageCallbackHelper(
const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(info.Data()->IsString());
gin::Arguments arguments(info);
v8::Isolate* isolate = arguments.isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data)
return;
v8::Local<v8::String> callback_id_v8_string = info.Data().As<v8::String>();
std::string callback_id_string;
if (!gin::Converter<std::string>::FromV8(isolate, callback_id_v8_string,
&callback_id_string)) {
return;
}
std::optional<OneTimeMessageHandler::CallbackID> callback_id =
OneTimeMessageHandler::CallbackID::DeserializeFromString(
callback_id_string);
if (!callback_id) {
return;
}
OneTimeMessageContextData::OneTimePortCallbacks* port_callbacks = nullptr;
OneTimeMessageContextData::OneTimePortCallbacks::iterator port_callback_iter;
for (auto& port_entry : data->pending_receiver_callbacks) {
auto callback_entry = port_entry.second.find(*callback_id);
if (callback_entry == port_entry.second.end()) {
continue;
}
port_callbacks = &port_entry.second;
port_callback_iter = callback_entry;
break;
}
if (!port_callbacks) {
return;
}
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback =
std::move(port_callback_iter->second);
port_callbacks->erase(port_callback_iter);
std::move(*callback).Run(&arguments);
}
}
class OneTimeMessageHandler::OneTimeMessageCallbackManager {
public:
explicit OneTimeMessageCallbackManager(
OneTimeMessageHandler& owning_message_handler);
~OneTimeMessageCallbackManager();
OneTimeMessageCallbackManager(const OneTimeMessageCallbackManager&) = delete;
OneTimeMessageCallbackManager& operator=(
const OneTimeMessageCallbackManager&) = delete;
v8::Local<v8::Function> CreateRespondingFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback);
v8::Local<v8::Function> CreateEventDispatchFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback);
v8::Local<v8::Function> CreateListenerThrowsErrorFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback,
const CallbackID& callback_id);
void ClearCallbackDataForPortId(ScriptContext* script_context,
const PortId& port_id);
void DeleteCallbackDataForCallbackId(
ScriptContext* script_context,
const PortId& port_id,
const OneTimeMessageHandler::CallbackID& callback_id);
int GetPendingCallbackCountForTest(ScriptContext* script_context,
const PortId& port_id);
private:
v8::Local<v8::Function> CreateDelayedOneTimeMessageCallback(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback,
bool cleanup_if_function_unused);
v8::Local<v8::Function> CreateDelayedOneTimeMessageCallback(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback,
bool cleanup_if_function_unused,
std::optional<CallbackID> optional_callback_id);
void OnDelayedOneTimeMessageCallbackCollected(
ScriptContext* script_context,
const PortId& port_id,
OneTimeMessageHandler::CallbackID callback_id);
const raw_ref<OneTimeMessageHandler> message_handler_;
base::WeakPtrFactory<OneTimeMessageHandler::OneTimeMessageCallbackManager>
weak_factory_{this};
};
OneTimeMessageHandler::OneTimeMessageCallbackManager::
OneTimeMessageCallbackManager(OneTimeMessageHandler& owning_message_handler)
: message_handler_(owning_message_handler) {}
OneTimeMessageHandler::OneTimeMessageCallbackManager::
~OneTimeMessageCallbackManager() = default;
v8::Local<v8::Function>
OneTimeMessageHandler::OneTimeMessageCallbackManager::CreateRespondingFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback) {
return CreateDelayedOneTimeMessageCallback(
script_context, port_id, std::move(callback),
true);
}
v8::Local<v8::Function> OneTimeMessageHandler::OneTimeMessageCallbackManager::
CreateEventDispatchFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>
callback) {
return CreateDelayedOneTimeMessageCallback(
script_context, port_id, std::move(callback),
false);
}
v8::Local<v8::Function> OneTimeMessageHandler::OneTimeMessageCallbackManager::
CreateListenerThrowsErrorFunction(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback> callback,
const CallbackID& callback_id) {
return CreateDelayedOneTimeMessageCallback(
script_context, port_id, std::move(callback),
false, callback_id);
}
void OneTimeMessageHandler::OneTimeMessageCallbackManager::
ClearCallbackDataForPortId(ScriptContext* script_context,
const PortId& port_id) {
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data) {
return;
}
data->pending_receiver_callbacks.erase(port_id);
}
void OneTimeMessageHandler::OneTimeMessageCallbackManager::
DeleteCallbackDataForCallbackId(
ScriptContext* script_context,
const PortId& port_id,
const OneTimeMessageHandler::CallbackID& callback_id) {
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data) {
return;
}
if (auto port_iter = data->pending_receiver_callbacks.find(port_id);
port_iter != data->pending_receiver_callbacks.end()) {
port_iter->second.erase(callback_id);
}
}
int OneTimeMessageHandler::OneTimeMessageCallbackManager::
GetPendingCallbackCountForTest(ScriptContext* script_context,
const PortId& port_id) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data) {
return 0;
}
if (auto port_iter = data->pending_receiver_callbacks.find(port_id);
port_iter != data->pending_receiver_callbacks.end()) {
return port_iter->second.size();
}
return 0;
}
OneTimeMessageHandler::OneTimeMessageHandler(
NativeExtensionBindingsSystem* bindings_system)
: bindings_system_(bindings_system),
callback_manager_(
std::make_unique<
OneTimeMessageHandler::OneTimeMessageCallbackManager>(*this)) {}
OneTimeMessageHandler::~OneTimeMessageHandler() = default;
bool OneTimeMessageHandler::HasPort(ScriptContext* script_context,
const PortId& port_id) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data)
return false;
return port_id.is_opener ? base::Contains(data->openers, port_id)
: base::Contains(data->receivers, port_id);
}
v8::Local<v8::Promise> OneTimeMessageHandler::SendMessage(
ScriptContext* script_context,
const PortId& new_port_id,
const MessageTarget& target,
mojom::ChannelType channel_type,
const Message& message,
binding::AsyncResponseType async_type,
v8::Local<v8::Function> response_callback,
mojom::MessagePortHost* message_port_host,
mojo::PendingAssociatedRemote<mojom::MessagePort> message_port,
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>
message_port_host_receiver) {
v8::Isolate* isolate = script_context->isolate();
v8::EscapableHandleScope handle_scope(isolate);
DCHECK(new_port_id.is_opener);
DCHECK_EQ(script_context->context_id(), new_port_id.context_id);
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kCreateIfMissing);
DCHECK(data);
v8::Local<v8::Promise> promise;
bool wants_response = async_type != binding::AsyncResponseType::kNone;
if (wants_response) {
if (async_type == binding::AsyncResponseType::kPromise)
DCHECK(response_callback.IsEmpty());
APIRequestHandler::RequestDetails details =
bindings_system_->api_system()->request_handler()->AddPendingRequest(
script_context->v8_context(), async_type, response_callback,
binding::ResultModifierFunction());
OneTimeOpener& port = data->openers[new_port_id];
port.request_id = details.request_id;
port.async_type = async_type;
port.channel_type = channel_type;
promise = details.promise;
DCHECK_EQ(async_type == binding::AsyncResponseType::kPromise,
!promise.IsEmpty());
}
IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender();
std::string channel_name;
switch (channel_type) {
case mojom::ChannelType::kSendRequest:
channel_name = messaging_util::kSendRequestChannel;
break;
case mojom::ChannelType::kSendMessage:
channel_name = messaging_util::kSendMessageChannel;
break;
case mojom::ChannelType::kNative:
break;
case mojom::ChannelType::kConnect:
NOTREACHED();
}
ipc_sender->SendOpenMessageChannel(
script_context, new_port_id, target, channel_type, channel_name,
std::move(message_port), std::move(message_port_host_receiver));
message_port_host->PostMessage(message);
if (!wants_response && target.type != MessageTarget::NATIVE_APP) {
message_port_host->ClosePort(true,
std::nullopt);
}
return handle_scope.Escape(promise);
}
void OneTimeMessageHandler::AddReceiver(ScriptContext* script_context,
const PortId& target_port_id,
v8::Local<v8::Object> sender,
const std::string& event_name) {
DCHECK(!target_port_id.is_opener);
DCHECK_NE(script_context->context_id(), target_port_id.context_id);
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = script_context->v8_context();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context, kCreateIfMissing);
DCHECK(data);
DCHECK(!base::Contains(data->receivers, target_port_id));
OneTimeReceiver& receiver = data->receivers[target_port_id];
receiver.sender.Reset(isolate, sender);
receiver.event_name = event_name;
}
void OneTimeMessageHandler::AddReceiverForTesting(
ScriptContext* script_context,
const PortId& target_port_id,
v8::Local<v8::Object> sender,
const std::string& event_name,
mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
message_port_host_receiver) {
AddReceiver(script_context, target_port_id, sender, event_name);
messaging_service()->BindPortForTesting(
script_context, target_port_id, message_port_remote,
message_port_host_receiver);
}
bool OneTimeMessageHandler::DeliverMessage(ScriptContext* script_context,
const Message& message,
const PortId& target_port_id) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
return target_port_id.is_opener
? DeliverReplyToOpener(script_context, message, target_port_id)
: DeliverMessageToReceiver(script_context, message,
target_port_id);
}
bool OneTimeMessageHandler::Disconnect(ScriptContext* script_context,
const PortId& port_id,
const std::string& error_message) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
return port_id.is_opener
? DisconnectOpener(script_context, port_id, error_message)
: DisconnectReceiver(script_context, port_id);
}
int OneTimeMessageHandler::GetPendingCallbackCountForTest(
ScriptContext* script_context,
const PortId& port_id) {
return callback_manager_->GetPendingCallbackCountForTest(
script_context, port_id);
}
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>
OneTimeMessageHandler::CreateMessageResponseCallback(const PortId& port_id) {
return std::make_unique<OneTimeMessageHandler::OneTimeMessageCallback>(
base::BindOnce(&OneTimeMessageHandler::OnOneTimeMessageResponse,
weak_factory_.GetWeakPtr(), port_id));
}
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>
OneTimeMessageHandler::CreatePromiseRejectedCallback(const PortId& port_id) {
return std::make_unique<OneTimeMessageHandler::OneTimeMessageCallback>(
base::BindOnce(&OneTimeMessageHandler::OnPromiseRejectedResponse,
weak_factory_.GetWeakPtr(), port_id));
}
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>
OneTimeMessageHandler::CreateEventDispatchCallback(
const PortId& port_id,
std::optional<CallbackID> listener_error_callback_id) {
return std::make_unique<OneTimeMessageHandler::OneTimeMessageCallback>(
base::BindOnce(&OneTimeMessageHandler::OnEventFired,
weak_factory_.GetWeakPtr(), port_id,
listener_error_callback_id));
}
std::unique_ptr<OneTimeMessageHandler::OneTimeMessageCallback>
OneTimeMessageHandler::CreateListenerErrorCallback(const PortId& port_id) {
return std::make_unique<OneTimeMessageHandler::OneTimeMessageCallback>(
base::BindOnce(&OneTimeMessageHandler::OnListenerThrowsError,
weak_factory_.GetWeakPtr(), port_id));
}
void OneTimeMessageHandler::OnAllCallbacksCollected(
ScriptContext* script_context,
v8::Local<v8::Context> context,
const PortId& port_id) {
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data) {
return;
}
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end()) {
return;
}
data->receivers.erase(port_id);
CloseReceiverMessagePortOrChannel(script_context, port_id,
false,
std::nullopt);
}
bool OneTimeMessageHandler::DeliverMessageToReceiver(
ScriptContext* script_context,
const Message& message,
const PortId& target_port_id) {
DCHECK(!target_port_id.is_opener);
v8::Isolate* isolate = script_context->isolate();
v8::Local<v8::Context> context = script_context->v8_context();
bool handled = false;
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data)
return handled;
auto iter = data->receivers.find(target_port_id);
if (iter == data->receivers.end())
return handled;
handled = true;
OneTimeReceiver& port = iter->second;
auto message_response_callback =
CreateMessageResponseCallback(target_port_id);
v8::Local<v8::Function> message_response_function =
callback_manager_->CreateRespondingFunction(
*script_context, target_port_id,
std::move(message_response_callback));
if (IsMessagePolyfillSupportEnabled()) {
port.message_response_function =
v8::Global<v8::Function>(isolate, message_response_function);
}
v8::HandleScope handle_scope(isolate);
std::string error;
v8::Local<v8::Value> v8_message = messaging_util::MessageToV8(
context, message,
port.event_name == messaging_util::kOnConnectNativeEvent, &error);
if (error.empty()) {
v8::Local<v8::Object> v8_sender = port.sender.Get(isolate);
v8::LocalVector<v8::Value> args(
isolate, {v8_message, v8_sender, message_response_function});
v8::Local<v8::Function> message_dispatched_function;
v8::Local<v8::Function> listener_throws_error_function;
if (port.event_name == messaging_util::kOnMessageEvent) {
CallbackID listener_throws_error_callback_id;
if (IsMessagePolyfillSupportEnabled()) {
auto listener_throws_error_callback =
CreateListenerErrorCallback(target_port_id);
listener_throws_error_callback_id = CallbackID::Create();
listener_throws_error_function =
callback_manager_->CreateListenerThrowsErrorFunction(
*script_context, target_port_id,
std::move(listener_throws_error_callback),
listener_throws_error_callback_id);
}
auto message_dispatched_callback = CreateEventDispatchCallback(
target_port_id, listener_throws_error_callback_id);
message_dispatched_function =
callback_manager_->CreateEventDispatchFunction(
*script_context, target_port_id,
std::move(message_dispatched_callback));
}
bindings_system_->api_system()->event_handler()->FireEventInContext(
port.event_name, context, &args, nullptr,
message_dispatched_function, listener_throws_error_function);
} else {
console::AddMessage(script_context,
blink::mojom::ConsoleMessageLevel::kError, error);
}
return handled;
}
bool OneTimeMessageHandler::DeliverReplyToOpener(ScriptContext* script_context,
const Message& message,
const PortId& target_port_id) {
DCHECK(target_port_id.is_opener);
v8::Local<v8::Context> v8_context = script_context->v8_context();
bool handled = false;
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(v8_context,
kDontCreateIfMissing);
if (!data)
return handled;
auto iter = data->openers.find(target_port_id);
if (iter == data->openers.end())
return handled;
handled = true;
const OneTimeOpener port = iter->second;
DCHECK_NE(-1, port.request_id);
data->openers.erase(iter);
v8::Isolate* isolate = script_context->isolate();
std::string error;
v8::Local<v8::Value> v8_message = messaging_util::MessageToV8(
v8_context, message, port.channel_type == mojom::ChannelType::kNative,
&error);
if (v8_message.IsEmpty()) {
v8_message = v8::Undefined(isolate);
}
v8::LocalVector<v8::Value> args(isolate, {v8_message});
bindings_system_->api_system()->request_handler()->CompleteRequest(
port.request_id, args, error);
bindings_system_->messaging_service()->CloseMessagePort(
script_context, target_port_id, true);
return handled;
}
bool OneTimeMessageHandler::DisconnectReceiver(ScriptContext* script_context,
const PortId& port_id) {
v8::Local<v8::Context> context = script_context->v8_context();
bool handled = false;
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data)
return handled;
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end())
return handled;
handled = true;
data->receivers.erase(iter);
callback_manager_->ClearCallbackDataForPortId(script_context, port_id);
return handled;
}
bool OneTimeMessageHandler::DisconnectOpener(ScriptContext* script_context,
const PortId& port_id,
const std::string& error_message) {
bool handled = false;
v8::Local<v8::Context> v8_context = script_context->v8_context();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(v8_context,
kDontCreateIfMissing);
if (!data)
return handled;
auto iter = data->openers.find(port_id);
if (iter == data->openers.end())
return handled;
handled = true;
const OneTimeOpener opener = iter->second;
DCHECK_NE(-1, opener.request_id);
data->openers.erase(iter);
std::string error;
if (!error_message.empty()) {
error = error_message;
} else if (opener.async_type == binding::AsyncResponseType::kCallback) {
error = "The message port closed before a response was received.";
}
bindings_system_->api_system()->request_handler()->CompleteRequest(
opener.request_id, v8::LocalVector<v8::Value>(v8::Isolate::GetCurrent()),
error);
return handled;
}
void OneTimeMessageHandler::CloseReceiverMessagePortOrChannel(
ScriptContext* script_context,
const PortId& port_id,
bool close_channel,
std::optional<std::string> error) {
callback_manager_->ClearCallbackDataForPortId(script_context, port_id);
if (close_channel && error) {
messaging_service()->CloseMessagePort(script_context, port_id,
close_channel, *error);
return;
}
messaging_service()->CloseMessagePort(script_context, port_id, close_channel);
}
void OneTimeMessageHandler::OnOneTimeMessageResponse(
const PortId& port_id,
gin::Arguments* arguments) {
v8::Isolate* isolate = arguments->isolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data)
return;
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end())
return;
data->receivers.erase(port_id);
v8::Local<v8::Value> value;
if (arguments->Length() > 0)
CHECK(arguments->GetNext(&value));
else
value = v8::Undefined(isolate);
ScriptContext* script_context = GetScriptContextFromV8Context(context);
std::string message_creation_error;
std::unique_ptr<Message> message = messaging_util::MessageFromV8(
context, value, port_id.serialization_format, &message_creation_error);
if (!message) {
arguments->ThrowTypeError(message_creation_error);
if (IsMessagePolyfillSupportEnabled()) {
CloseReceiverMessagePortOrChannel(script_context, port_id,
true,
message_creation_error);
}
return;
}
if (auto* message_port_host = messaging_service()->GetMessagePortHostIfExists(
script_context, port_id)) {
message_port_host->PostMessage(*message);
CloseReceiverMessagePortOrChannel(script_context, port_id,
true,
std::nullopt);
}
callback_manager_->ClearCallbackDataForPortId(script_context, port_id);
}
v8::Local<v8::Function> OneTimeMessageHandler::OneTimeMessageCallbackManager::
CreateDelayedOneTimeMessageCallback(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageCallback> callback,
bool cleanup_if_function_unused) {
return CreateDelayedOneTimeMessageCallback(
script_context, port_id, std::move(callback), cleanup_if_function_unused,
std::nullopt);
}
v8::Local<v8::Function> OneTimeMessageHandler::OneTimeMessageCallbackManager::
CreateDelayedOneTimeMessageCallback(
ScriptContext& script_context,
const PortId& port_id,
std::unique_ptr<OneTimeMessageCallback> callback,
bool cleanup_if_function_unused,
std::optional<CallbackID> optional_callback_id) {
CHECK(callback);
v8::Isolate* isolate = script_context.isolate();
v8::Local<v8::Context> context = script_context.v8_context();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(
context, CreatePerContextData::kDontCreateIfMissing);
CHECK(data);
CallbackID callback_id;
if (optional_callback_id) {
callback_id = *optional_callback_id;
} else {
callback_id = OneTimeMessageHandler::CallbackID::Create();
}
v8::Local<v8::String> callback_id_v8_string =
gin::StringToV8(isolate, callback_id.ToString());
v8::Local<v8::Function> function;
if (!v8::Function::New(context, &DelayedOneTimeMessageCallbackHelper,
callback_id_v8_string)
.ToLocal(&function)) {
NOTREACHED();
}
auto& port_callbacks = data->pending_receiver_callbacks[port_id];
const auto& [callback_id_iter, callback_id_inserted] =
port_callbacks.try_emplace(callback_id, std::move(callback));
CHECK(callback_id_inserted);
if (cleanup_if_function_unused) {
new GCCallback(
&script_context, function,
base::BindOnce(&OneTimeMessageHandler::OneTimeMessageCallbackManager::
OnDelayedOneTimeMessageCallbackCollected,
weak_factory_.GetWeakPtr(), &script_context, port_id,
callback_id),
base::OnceClosure());
}
return function;
}
void OneTimeMessageHandler::OneTimeMessageCallbackManager::
OnDelayedOneTimeMessageCallbackCollected(ScriptContext* script_context,
const PortId& port_id,
CallbackID callback_id) {
v8::HandleScope handle_scope(script_context->isolate());
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
kDontCreateIfMissing);
if (!data)
return;
if (auto port_id_iter = data->pending_receiver_callbacks.find(port_id);
port_id_iter != data->pending_receiver_callbacks.end()) {
auto& callbacks = port_id_iter->second;
callbacks.erase(callback_id);
if (!callbacks.empty()) {
DCHECK(IsMessagePolyfillSupportEnabled());
return;
}
data->pending_receiver_callbacks.erase(port_id_iter);
}
message_handler_->OnAllCallbacksCollected(
script_context, script_context->v8_context(), port_id);
}
std::optional<std::string> OneTimeMessageHandler::GetErrorMessageFromValue(
v8::Isolate* isolate,
v8::Local<v8::Value> possible_error_value) {
if (!possible_error_value->IsNativeError()) {
return std::nullopt;
}
v8::Local<v8::Message> error_message =
v8::Exception::CreateMessage(isolate, possible_error_value);
std::string error_message_from_v8;
bool error_message_string_convert_success =
gin::Converter<std::string>::FromV8(isolate,
error_message->Get().As<v8::Value>(),
&error_message_from_v8);
if (!error_message_string_convert_success || error_message_from_v8.empty()) {
return std::nullopt;
}
return error_message_from_v8;
}
void OneTimeMessageHandler::OnPromiseRejectedResponse(
const PortId& port_id,
gin::Arguments* arguments) {
CHECK(IsMessagePolyfillSupportEnabled());
CHECK(arguments);
v8::Isolate* isolate = arguments->isolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data) {
return;
}
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end()) {
return;
}
data->receivers.erase(port_id);
v8::Local<v8::Value> promise_reject_value;
CHECK(arguments->Length() > 0);
CHECK(arguments->GetNext(&promise_reject_value));
std::optional<std::string> error_message_from_value =
GetErrorMessageFromValue(isolate, promise_reject_value);
std::string error_message =
error_message_from_value
? *error_message_from_value
: "A runtime.onMessage listener's promise rejected without an Error";
ScriptContext* script_context = GetScriptContextFromV8Context(context);
CloseReceiverMessagePortOrChannel(script_context, port_id,
true, error_message);
}
void OneTimeMessageHandler::OnListenerThrowsError(const PortId& port_id,
gin::Arguments* arguments) {
CHECK(IsMessagePolyfillSupportEnabled());
v8::Isolate* isolate = arguments->isolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data) {
return;
}
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end()) {
return;
}
data->receivers.erase(port_id);
v8::Local<v8::Value> listener_thrown_value;
CHECK(arguments->Length() > 0);
CHECK(arguments->GetNext(&listener_thrown_value));
std::optional<std::string> error_message_from_value =
GetErrorMessageFromValue(isolate, listener_thrown_value);
std::string error_message =
error_message_from_value
? *error_message_from_value
: "Error message from listener couldn't be parsed or was empty.";
ScriptContext* script_context = GetScriptContextFromV8Context(context);
CloseReceiverMessagePortOrChannel(script_context, port_id,
true, error_message);
}
bool OneTimeMessageHandler::CheckAndHandleAsyncListenerReply(
v8::Isolate* isolate,
v8::Local<v8::Context> context,
ScriptContext& script_context,
v8::Local<v8::Value> result,
const PortId& port_id,
v8::Local<v8::Function> promise_resolved_function) {
v8::Local<v8::Array> results_array =
GetListenerResultArray(isolate, context, result, "results");
if (results_array.IsEmpty()) {
return false;
}
bool will_reply_async = false;
for (uint32_t i = 0; i < results_array->Length(); ++i) {
v8::MaybeLocal<v8::Value> maybe_result = results_array->Get(context, i);
v8::Local<v8::Value> listener_return;
if (!maybe_result.ToLocal(&listener_return)) {
continue;
}
if (listener_return->IsBoolean() &&
listener_return.As<v8::Boolean>()->Value()) {
will_reply_async = true;
}
if (!IsMessagePolyfillSupportEnabled() && will_reply_async) {
return true;
}
if (IsMessagePolyfillSupportEnabled() && listener_return->IsPromise()) {
auto promise_rejected_response_callback =
CreatePromiseRejectedCallback(port_id);
v8::Local<v8::Function> promise_rejected_function =
callback_manager_->CreateRespondingFunction(
script_context, port_id,
std::move(promise_rejected_response_callback));
std::ignore = listener_return.As<v8::Promise>()->Then(
context, promise_resolved_function, promise_rejected_function);
will_reply_async = true;
}
}
return will_reply_async;
}
void OneTimeMessageHandler::OnEventFired(
const PortId& port_id,
std::optional<CallbackID> listener_error_callback_id,
gin::Arguments* arguments) {
v8::Isolate* isolate = arguments->isolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Value> result;
if (arguments->Length() > 0) {
CHECK(arguments->GetNext(&result));
} else {
result = v8::Undefined(isolate);
}
OneTimeMessageContextData* data =
GetPerContextData<OneTimeMessageContextData>(context,
kDontCreateIfMissing);
if (!data)
return;
ScriptContext* script_context = GetScriptContextFromV8Context(context);
DCHECK(script_context)
<< "script context was destroyed before runtime.onMessage listener "
"results could be processed.";
if (IsMessagePolyfillSupportEnabled() && listener_error_callback_id) {
callback_manager_->DeleteCallbackDataForCallbackId(
script_context, port_id, *listener_error_callback_id);
}
auto iter = data->receivers.find(port_id);
if (iter == data->receivers.end()) {
return;
}
OneTimeReceiver& port = iter->second;
v8::Local<v8::Function> promise_resolved_function;
if (IsMessagePolyfillSupportEnabled()) {
promise_resolved_function = port.message_response_function.Get(isolate);
port.message_response_function.SetWeak();
}
if (CheckAndHandleAsyncListenerReply(isolate, context, *script_context,
result, port_id,
promise_resolved_function)) {
if (auto* message_port_host =
messaging_service()->GetMessagePortHostIfExists(script_context,
port_id)) {
message_port_host->ResponsePending();
}
return;
}
data->receivers.erase(port_id);
CloseReceiverMessagePortOrChannel(script_context, port_id,
false,
std::nullopt);
}
NativeRendererMessagingService* OneTimeMessageHandler::messaging_service() {
return bindings_system_->messaging_service();
}
}