blob: f64b2048232a2d57d9e1eba1cfc018005d308378 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/renderer/api/messaging/native_renderer_messaging_service.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/supports_user_data.h"
#include "content/public/common/content_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/api/messaging/port_id.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/manifest_handlers/externally_connectable.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/common/mojom/message_port.mojom-shared.h"
#include "extensions/common/utils/extension_utils.h"
#include "extensions/renderer/api/messaging/message_target.h"
#include "extensions/renderer/api/messaging/messaging_util.h"
#include "extensions/renderer/api_activity_logger.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/bindings/get_per_context_data.h"
#include "extensions/renderer/extension_interaction_provider.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 "extensions/renderer/script_context_set.h"
#include "extensions/renderer/script_context_set_iterable.h"
#include "gin/data_object_builder.h"
#include "gin/handle.h"
#include "gin/per_context_data.h"
#include "ipc/ipc_mojo_bootstrap.h"
#include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_scoped_window_focus_allowed_indicator.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-local-handle.h"
#include "v8/include/v8-persistent-handle.h"
using blink::mojom::UserActivationNotificationType;
namespace extensions {
namespace {
struct MessagingPerContextData : public base::SupportsUserData::Data {
static constexpr char kPerContextDataKey[] =
"extension_messaging_per_context_data";
// All the port objects that exist in this context.
std::map<PortId, v8::Global<v8::Object>> ports;
// The next available context-specific port id.
int next_port_id = 0;
};
constexpr char MessagingPerContextData::kPerContextDataKey[];
bool ScriptContextIsValid(ScriptContext* script_context) {
// TODO(devlin): This is in lieu of a similar check in the JS bindings that
// null-checks ScriptContext::GetRenderFrame(). This is good because it
// removes the reliance on RenderFrames (which make testing very difficult),
// but is it fully analogous? It should be close enough, since the browser
// has to deal with frames potentially disappearing before the IPC arrives
// anyway.
return script_context->is_valid();
}
} // namespace
// This class implements the mojo messaging hooks. Since the
// NativeRendererMessagingService is shared by everything on the same thread
// we want a separate object so the mojo channels are organized based on
// their "scope", whether that be a Worker or a Frame.
class NativeRendererMessagingService::MessagePortScope
: public mojom::MessagePort {
public:
explicit MessagePortScope(
base::SafeRef<NativeRendererMessagingService> messaging_service)
: messaging_service_(messaging_service) {}
~MessagePortScope() override = default;
// MessagePort overrides:
void DispatchDisconnect(const std::string& error) override {
auto target_port_id = message_port_dispatchers_.current_context();
messaging_service_->DispatchOnDisconnect(
messaging_service_->bindings_system_->delegate()->GetScriptContextSet(),
target_port_id, error,
/*restrict_to_render_frame=*/GetRestrictToRenderFrame());
}
void DeliverMessage(extensions::Message message) override {
auto target_port_id = message_port_dispatchers_.current_context();
messaging_service_->DeliverMessage(
messaging_service_->bindings_system_->delegate()->GetScriptContextSet(),
target_port_id, message,
/*restrict_to_render_frame=*/GetRestrictToRenderFrame());
}
mojo::ReceiverId AddPort(
const PortId& target_port_id,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePortHost>
port_host) {
auto receiver_id =
message_port_dispatchers_.Add(this, std::move(port), target_port_id);
CHECK(!base::Contains(message_port_hosts_, target_port_id));
auto& bound_port_host = message_port_hosts_[target_port_id] =
mojo::AssociatedRemote(std::move(port_host));
bound_port_host.set_disconnect_handler(
base::BindOnce(&MessagePortScope::PortDisconnected,
base::Unretained(this), target_port_id));
return receiver_id;
}
void BindNewMessagePort(
const PortId& port_id,
mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
message_port_host_receiver) {
mojo::PendingAssociatedReceiver<mojom::MessagePort> message_port_receiver;
mojo::PendingAssociatedRemote<mojom::MessagePortHost>
message_port_host_remote;
message_port_remote =
message_port_receiver.InitWithNewEndpointAndPassRemote();
message_port_host_receiver =
message_port_host_remote.InitWithNewEndpointAndPassReceiver();
message_port_dispatchers_.Add(this, std::move(message_port_receiver),
port_id);
CHECK(!base::Contains(message_port_hosts_, port_id));
auto& bound_port_host = message_port_hosts_[port_id] =
mojo::AssociatedRemote(std::move(message_port_host_remote));
bound_port_host.set_disconnect_handler(base::BindOnce(
&MessagePortScope::PortDisconnected, base::Unretained(this), port_id));
}
void Remove(mojo::ReceiverId receiver_id, const PortId& target_port_id) {
message_port_dispatchers_.Remove(receiver_id);
PortDisconnected(target_port_id);
}
void PortDisconnected(const PortId& port_id) {
message_port_hosts_.erase(port_id);
}
size_t GetNumHosts() { return message_port_hosts_.size(); }
base::WeakPtr<MessagePortScope> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
virtual content::RenderFrame* GetRestrictToRenderFrame() { return nullptr; }
void CloseMessagePort(const PortId& port_id, bool close_channel) {
// BFCache can disconnect the mojo pipe but leave the GinPort thinking
// it is open.
if (!HasPort(port_id)) {
return;
}
GetMessagePortHost(port_id)->ClosePort(close_channel);
message_port_hosts_.erase(port_id);
}
bool HasPort(const PortId& port_id) {
return base::Contains(message_port_hosts_, port_id);
}
mojom::MessagePortHost* GetMessagePortHost(const PortId& port_id) {
auto* result = GetMessagePortHostIfExists(port_id);
CHECK(result);
return result;
}
mojom::MessagePortHost* GetMessagePortHostIfExists(const PortId& port_id) {
auto it = message_port_hosts_.find(port_id);
if (it == message_port_hosts_.end()) {
return nullptr;
}
return it->second.get();
}
private:
base::SafeRef<NativeRendererMessagingService> messaging_service_;
std::map<PortId, mojo::AssociatedRemote<mojom::MessagePortHost>>
message_port_hosts_;
mojo::AssociatedReceiverSet<mojom::MessagePort, PortId>
message_port_dispatchers_;
base::WeakPtrFactory<MessagePortScope> weak_ptr_factory_{this};
};
const void* const kRenderFrameMessagePortsKey = &kRenderFrameMessagePortsKey;
// This class extensions MessagePortScope but associates the storage of
// the class with UserData on each RenderFrame.
class NativeRendererMessagingService::RenderFrameMessagePorts
: public base::SupportsUserData::Data,
public NativeRendererMessagingService::MessagePortScope {
public:
RenderFrameMessagePorts(
base::SafeRef<NativeRendererMessagingService> messaging_service,
content::RenderFrame* render_frame)
: MessagePortScope(std::move(messaging_service)),
render_frame_(render_frame) {}
~RenderFrameMessagePorts() override = default;
static RenderFrameMessagePorts* GetOrCreate(
NativeRendererMessagingService* messaging_service,
content::RenderFrame* render_frame) {
RenderFrameMessagePorts* per_frame_data =
static_cast<RenderFrameMessagePorts*>(
render_frame->GetUserData(kRenderFrameMessagePortsKey));
if (!per_frame_data) {
auto created_data = std::make_unique<RenderFrameMessagePorts>(
messaging_service->AsSafeRef(), render_frame);
per_frame_data = created_data.get();
render_frame->SetUserData(kRenderFrameMessagePortsKey,
std::move(created_data));
}
return per_frame_data;
}
content::RenderFrame* GetRestrictToRenderFrame() override {
return render_frame_;
}
private:
// Safe raw ptr since this object is UserData owned by RenderFrame itself.
raw_ptr<content::RenderFrame> render_frame_;
};
NativeRendererMessagingService::NativeRendererMessagingService(
NativeExtensionBindingsSystem* bindings_system)
: bindings_system_(bindings_system),
one_time_message_handler_(bindings_system) {
default_scope_ = std::make_unique<MessagePortScope>(AsSafeRef());
}
NativeRendererMessagingService::~NativeRendererMessagingService() = default;
void NativeRendererMessagingService::DispatchOnConnect(
ScriptContextSetIterable* context_set,
const PortId& target_port_id,
mojom::ChannelType channel_type,
const std::string& channel_name,
const TabConnectionInfo& source,
const ExternalConnectionInfo& info,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePortHost> port_host,
content::RenderFrame* restrict_to_render_frame,
ConnectCallback callback) {
DCHECK(!target_port_id.is_opener);
auto scope = GetMessagePortScope(restrict_to_render_frame)->GetWeakPtr();
auto receiver_id =
scope->AddPort(target_port_id, std::move(port), std::move(port_host));
bool port_created = false;
context_set->ForEach(
GenerateHostIdFromExtensionId(info.target_id), restrict_to_render_frame,
base::BindRepeating(
&NativeRendererMessagingService::DispatchOnConnectToScriptContext,
base::Unretained(this), target_port_id, channel_type, channel_name,
std::ref(source), std::ref(info), &port_created));
// Note: |restrict_to_render_frame| may have been deleted at this point!
if (!port_created && scope) {
scope->Remove(receiver_id, target_port_id);
}
// Note: |restrict_to_render_frame| may have been deleted at this point!
std::move(callback).Run(port_created);
}
void NativeRendererMessagingService::DeliverMessage(
ScriptContextSetIterable* context_set,
const PortId& target_port_id,
const Message& message,
content::RenderFrame* restrict_to_render_frame) {
context_set->ForEach(
restrict_to_render_frame,
base::BindRepeating(
&NativeRendererMessagingService::DeliverMessageToScriptContext,
base::Unretained(this), message, target_port_id));
}
void NativeRendererMessagingService::DispatchOnDisconnect(
ScriptContextSetIterable* context_set,
const PortId& port_id,
const std::string& error_message,
content::RenderFrame* restrict_to_render_frame) {
context_set->ForEach(
restrict_to_render_frame,
base::BindRepeating(
&NativeRendererMessagingService::DispatchOnDisconnectToScriptContext,
base::Unretained(this), port_id, error_message));
}
gin::Handle<GinPort> NativeRendererMessagingService::Connect(
ScriptContext* script_context,
const MessageTarget& target,
const std::string& channel_name,
mojom::SerializationFormat format) {
if (!ScriptContextIsValid(script_context))
return gin::Handle<GinPort>();
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
script_context->v8_context(), kCreateIfMissing);
if (!data)
return gin::Handle<GinPort>();
MessagePortScope* scope =
GetMessagePortScope(script_context->GetRenderFrame());
bool is_opener = true;
gin::Handle<GinPort> port =
CreatePort(script_context, channel_name,
PortId(script_context->context_id(), data->next_port_id++,
is_opener, format));
mojo::PendingAssociatedRemote<mojom::MessagePort> messsage_port;
mojo::PendingAssociatedReceiver<mojom::MessagePortHost> messsage_port_host;
scope->BindNewMessagePort(port->port_id(), messsage_port, messsage_port_host);
mojom::ChannelType channel_type = target.type == MessageTarget::NATIVE_APP
? mojom::ChannelType::kNative
: mojom::ChannelType::kConnect;
bindings_system_->GetIPCMessageSender()->SendOpenMessageChannel(
script_context, port->port_id(), target, channel_type, channel_name,
std::move(messsage_port), std::move(messsage_port_host));
return port;
}
v8::Local<v8::Promise> NativeRendererMessagingService::SendOneTimeMessage(
ScriptContext* script_context,
const MessageTarget& target,
mojom::ChannelType channel_type,
const Message& message,
binding::AsyncResponseType async_type,
v8::Local<v8::Function> response_callback) {
if (!ScriptContextIsValid(script_context))
return v8::Local<v8::Promise>();
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
script_context->v8_context(), kCreateIfMissing);
MessagePortScope* scope =
GetMessagePortScope(script_context->GetRenderFrame());
bool is_opener = true;
// TODO(crbug.com/248548): Instead of inferring the mojom::SerializationFormat
// from Message, it'd be better to have the clients pass it directly. This is
// because, in case of `kStructuredCloned` to `kJson` fallback, the format for
// the ports will also be `kJson`. This is inconsistent with what we do for
// ports for long-lived channels where the port's `mojom::SerializationFormat`
// is always the same as that passed by messaging clients and is independent
// of any fallback behavior.
PortId port_id(script_context->context_id(), data->next_port_id++, is_opener,
message.format);
mojo::PendingAssociatedRemote<mojom::MessagePort> message_port;
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>
message_port_host_receiver;
mojom::MessagePortHost* message_port_host = nullptr;
scope->BindNewMessagePort(port_id, message_port, message_port_host_receiver);
message_port_host = scope->GetMessagePortHost(port_id);
return one_time_message_handler_.SendMessage(
script_context, port_id, target, channel_type, message, async_type,
response_callback, message_port_host, std::move(message_port),
std::move(message_port_host_receiver));
}
void NativeRendererMessagingService::PostMessageToPort(
v8::Local<v8::Context> context,
const PortId& port_id,
std::unique_ptr<Message> message) {
ScriptContext* script_context = GetScriptContextFromV8Context(context);
CHECK(script_context);
if (!ScriptContextIsValid(script_context))
return;
auto* scope = GetMessagePortScope(script_context->GetRenderFrame());
// BFCache can disconnect the mojo pipe but leave the GinPort thinking
// it is open.
if (!scope->HasPort(port_id)) {
return;
}
scope->GetMessagePortHost(port_id)->PostMessage(*message);
}
void NativeRendererMessagingService::ClosePort(v8::Local<v8::Context> context,
const PortId& port_id) {
ScriptContext* script_context = GetScriptContextFromV8Context(context);
CHECK(script_context);
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
script_context->v8_context(), kDontCreateIfMissing);
if (!data)
return;
size_t erased = data->ports.erase(port_id);
DCHECK_GT(erased, 0u);
if (!ScriptContextIsValid(script_context))
return;
CloseMessagePort(script_context, port_id, /*close_channel=*/true);
}
gin::Handle<GinPort> NativeRendererMessagingService::CreatePortForTesting(
ScriptContext* script_context,
const std::string& channel_name,
const PortId& port_id,
mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
message_port_host_receiver) {
BindPortForTesting(script_context, port_id, message_port_remote, // IN-TEST
message_port_host_receiver);
return CreatePort(script_context, channel_name, port_id);
}
gin::Handle<GinPort> NativeRendererMessagingService::GetPortForTesting(
ScriptContext* script_context,
const PortId& port_id) {
return GetPort(script_context, port_id);
}
bool NativeRendererMessagingService::HasPortForTesting(
ScriptContext* script_context,
const PortId& port_id) {
return ContextHasMessagePort(script_context, port_id);
}
void NativeRendererMessagingService::BindPortForTesting(
ScriptContext* script_context,
const PortId& port_id,
mojo::PendingAssociatedRemote<mojom::MessagePort>& message_port_remote,
mojo::PendingAssociatedReceiver<mojom::MessagePortHost>&
message_port_host_receiver) {
auto* scope = GetMessagePortScope(script_context->GetRenderFrame());
scope->BindNewMessagePort(port_id, message_port_remote,
message_port_host_receiver);
}
void NativeRendererMessagingService::DispatchOnConnectToScriptContext(
const PortId& target_port_id,
mojom::ChannelType channel_type,
const std::string& channel_name,
const TabConnectionInfo& source,
const ExternalConnectionInfo& info,
bool* port_created,
ScriptContext* script_context) {
// If the channel was opened by this same context, ignore it. This should only
// happen when messages are sent to an entire process (rather than a single
// frame) as an optimization; otherwise the browser process filters this out.
if (script_context->context_id() == target_port_id.context_id)
return;
// First, determine the event we'll use to connect.
std::string target_extension_id = script_context->GetExtensionID();
std::string event_name = messaging_util::GetEventForChannel(
info.source_endpoint, target_extension_id, channel_type);
// If there are no listeners for the given event, then we know the port won't
// be used in this context.
if (!bindings_system_->HasEventListenerInContext(event_name,
script_context)) {
return;
}
*port_created = true;
DispatchOnConnectToListeners(script_context, target_port_id,
target_extension_id, channel_type, channel_name,
source, info, event_name);
}
void NativeRendererMessagingService::DeliverMessageToScriptContext(
const Message& message,
const PortId& target_port_id,
ScriptContext* script_context) {
if (!ContextHasMessagePort(script_context, target_port_id)) {
return;
}
if (script_context->IsForServiceWorker()) {
DeliverMessageToWorker(message, target_port_id, script_context);
} else {
DeliverMessageToBackgroundPage(message, target_port_id, script_context);
}
}
void NativeRendererMessagingService::DeliverMessageToWorker(
const Message& message,
const PortId& target_port_id,
ScriptContext* script_context) {
// Note |scoped_extension_interaction| requires a HandleScope.
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
std::unique_ptr<InteractionProvider::Scope> scoped_extension_interaction;
if (message.user_gesture) {
// TODO(crbug.com/41467311): Add logging for privilege level for
// sender and receiver and decide if want to allow unprivileged to
// privileged support.
scoped_extension_interaction =
ExtensionInteractionProvider::Scope::ForWorker(
script_context->v8_context());
}
DispatchOnMessageToListeners(script_context, message, target_port_id);
}
void NativeRendererMessagingService::DeliverMessageToBackgroundPage(
const Message& message,
const PortId& target_port_id,
ScriptContext* script_context) {
std::unique_ptr<blink::WebScopedWindowFocusAllowedIndicator>
allow_window_focus;
if (message.user_gesture && script_context->web_frame()) {
bool sender_is_privileged = message.from_privileged_context;
bool receiver_is_privileged =
script_context->context_type() ==
extensions::mojom::ContextType::kPrivilegedExtension;
UserActivationNotificationType notification_type;
if (sender_is_privileged && receiver_is_privileged) {
notification_type =
UserActivationNotificationType::kExtensionMessagingBothPrivileged;
} else if (sender_is_privileged && !receiver_is_privileged) {
notification_type =
UserActivationNotificationType::kExtensionMessagingSenderPrivileged;
} else if (!sender_is_privileged && receiver_is_privileged) {
notification_type =
UserActivationNotificationType::kExtensionMessagingReceiverPrivileged;
} else /* !sender_is_privileged && !receiver_is_privileged */ {
notification_type =
UserActivationNotificationType::kExtensionMessagingNeitherPrivileged;
}
script_context->web_frame()->NotifyUserActivation(notification_type);
blink::WebDocument document = script_context->web_frame()->GetDocument();
allow_window_focus =
std::make_unique<blink::WebScopedWindowFocusAllowedIndicator>(
&document);
}
DispatchOnMessageToListeners(script_context, message, target_port_id);
}
void NativeRendererMessagingService::DispatchOnDisconnectToScriptContext(
const PortId& port_id,
const std::string& error_message,
ScriptContext* script_context) {
if (!ContextHasMessagePort(script_context, port_id))
return;
DispatchOnDisconnectToListeners(script_context, port_id, error_message);
}
bool NativeRendererMessagingService::ContextHasMessagePort(
ScriptContext* script_context,
const PortId& port_id) {
if (one_time_message_handler_.HasPort(script_context, port_id))
return true;
v8::HandleScope handle_scope(script_context->isolate());
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
script_context->v8_context(), kDontCreateIfMissing);
return data && base::Contains(data->ports, port_id);
}
void NativeRendererMessagingService::DispatchOnConnectToListeners(
ScriptContext* script_context,
const PortId& target_port_id,
const ExtensionId& target_extension_id,
mojom::ChannelType channel_type,
const std::string& channel_name,
const TabConnectionInfo& source,
const ExternalConnectionInfo& info,
const std::string& event_name) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = script_context->v8_context();
v8::Context::Scope context_scope(v8_context);
gin::DataObjectBuilder sender_builder(isolate);
if (info.source_endpoint.extension_id)
sender_builder.Set("id", *info.source_endpoint.extension_id);
if (info.source_endpoint.native_app_name) {
sender_builder.Set("nativeApplication",
*info.source_endpoint.native_app_name);
}
if (!info.source_url.is_empty())
sender_builder.Set("url", info.source_url.spec());
if (info.source_origin)
sender_builder.Set("origin", info.source_origin->Serialize());
if (source.frame_id >= 0) {
sender_builder.Set("frameId", source.frame_id);
}
if (!source.document_id.empty()) {
sender_builder.Set("documentId", source.document_id);
}
if (!source.document_lifecycle.empty()) {
sender_builder.Set("documentLifecycle", source.document_lifecycle);
}
const Extension* extension = script_context->extension();
if (extension) {
if (!source.tab.empty() && !extension->is_platform_app()) {
sender_builder.Set("tab", content::V8ValueConverter::Create()->ToV8Value(
source.tab, v8_context));
}
ExternallyConnectableInfo* externally_connectable =
ExternallyConnectableInfo::Get(extension);
if (externally_connectable &&
externally_connectable->accepts_tls_channel_id) {
sender_builder.Set("tlsChannelId", std::string());
}
if (info.guest_process_id != content::kInvalidChildProcessUniqueId) {
CHECK(Manifest::IsComponentLocation(extension->location()))
<< "GuestProcessId can only be exposed to component extensions.";
sender_builder.Set("guestProcessId", info.guest_process_id)
.Set("guestRenderFrameRoutingId", info.guest_render_frame_routing_id);
}
}
v8::Local<v8::Object> sender = sender_builder.Build();
if (channel_type == mojom::ChannelType::kSendRequest ||
channel_type == mojom::ChannelType::kSendMessage) {
one_time_message_handler_.AddReceiver(script_context, target_port_id,
sender, event_name);
} else {
CHECK(channel_type == mojom::ChannelType::kConnect ||
channel_type == mojom::ChannelType::kNative);
gin::Handle<GinPort> port =
CreatePort(script_context, channel_name, target_port_id);
port->SetSender(v8_context, sender);
v8::LocalVector<v8::Value> args(isolate, {port.ToV8()});
bindings_system_->api_system()->event_handler()->FireEventInContext(
event_name, v8_context, &args, nullptr, JSRunner::ResultCallback());
}
// Note: Arbitrary JS may have run; the context may now be deleted.
if (binding::IsContextValid(v8_context) &&
APIActivityLogger::IsLoggingEnabled()) {
base::Value::List list;
list.reserve(2u);
if (info.source_endpoint.extension_id)
list.Append(*info.source_endpoint.extension_id);
else if (info.source_endpoint.native_app_name)
list.Append(*info.source_endpoint.native_app_name);
else
list.Append(base::Value());
if (!info.source_url.is_empty())
list.Append(info.source_url.spec());
else
list.Append(base::Value());
APIActivityLogger::LogEvent(bindings_system_->GetIPCMessageSender(),
script_context, event_name, std::move(list));
}
}
void NativeRendererMessagingService::DispatchOnMessageToListeners(
ScriptContext* script_context,
const Message& message,
const PortId& target_port_id) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(script_context->v8_context());
if (one_time_message_handler_.DeliverMessage(script_context, message,
target_port_id)) {
return;
}
gin::Handle<GinPort> port = GetPort(script_context, target_port_id);
DCHECK(!port.IsEmpty());
port->DispatchOnMessage(script_context->v8_context(), message);
// Note: Arbitrary JS may have run; the context may now be deleted.
}
void NativeRendererMessagingService::DispatchOnDisconnectToListeners(
ScriptContext* script_context,
const PortId& port_id,
const std::string& error_message) {
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = script_context->v8_context();
v8::Context::Scope context_scope(v8_context);
if (one_time_message_handler_.Disconnect(script_context, port_id,
error_message)) {
return;
}
gin::Handle<GinPort> port = GetPort(script_context, port_id);
DCHECK(!port.IsEmpty());
if (!error_message.empty()) {
// TODO(devlin): Subtle: If the JS event to disconnect the port happens
// asynchronously because JS is suspended, this last error won't be
// correctly set for listeners. Given this exceedingly rare, and shouldn't
// behave too strangely, this is somewhat low priority.
bindings_system_->api_system()->request_handler()->last_error()->SetError(
v8_context, error_message);
}
port->DispatchOnDisconnect(v8_context);
// Note: Arbitrary JS may have run; the context may now be deleted.
if (!binding::IsContextValid(v8_context))
return;
if (!error_message.empty()) {
bindings_system_->api_system()->request_handler()->last_error()->ClearError(
v8_context, true);
}
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
v8_context, kDontCreateIfMissing);
DCHECK(data);
data->ports.erase(port_id);
}
gin::Handle<GinPort> NativeRendererMessagingService::CreatePort(
ScriptContext* script_context,
const std::string& channel_name,
const PortId& port_id) {
// Note: no HandleScope because it would invalidate the gin::Handle::wrapper_.
v8::Isolate* isolate = script_context->isolate();
v8::Local<v8::Context> context = script_context->v8_context();
// Note: needed because gin::CreateHandle infers the context from the active
// context on the isolate.
v8::Context::Scope context_scope(context);
// If this port is an opener, then it should have been created in this
// context. Otherwise, it should have been created in another context, because
// we don't support intra-context message passing.
if (port_id.is_opener)
DCHECK_EQ(port_id.context_id, script_context->context_id());
else
DCHECK_NE(port_id.context_id, script_context->context_id());
MessagingPerContextData* data =
GetPerContextData<MessagingPerContextData>(context, kCreateIfMissing);
DCHECK(data);
DCHECK(!base::Contains(data->ports, port_id));
gin::Handle<GinPort> port_handle = gin::CreateHandle(
isolate,
new GinPort(context, port_id, channel_name,
bindings_system_->api_system()->event_handler(), this));
v8::Local<v8::Object> port_object = port_handle.ToV8().As<v8::Object>();
data->ports[port_id].Reset(isolate, port_object);
return port_handle;
}
gin::Handle<GinPort> NativeRendererMessagingService::GetPort(
ScriptContext* script_context,
const PortId& port_id) {
// Note: no HandleScope because it would invalidate the gin::Handle::wrapper_.
v8::Isolate* isolate = script_context->isolate();
v8::Local<v8::Context> context = script_context->v8_context();
MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
script_context->v8_context(), kDontCreateIfMissing);
DCHECK(data);
DCHECK(base::Contains(data->ports, port_id));
GinPort* port = nullptr;
gin::Converter<GinPort*>::FromV8(context->GetIsolate(),
data->ports[port_id].Get(isolate), &port);
CHECK(port);
return gin::CreateHandle(isolate, port);
}
void NativeRendererMessagingService::CloseMessagePort(
ScriptContext* script_context,
const PortId& port_id,
bool close_channel) {
auto* scope = GetMessagePortScope(script_context->GetRenderFrame());
scope->CloseMessagePort(port_id, close_channel);
}
NativeRendererMessagingService::MessagePortScope*
NativeRendererMessagingService::GetMessagePortScope(
content::RenderFrame* render_frame) {
if (render_frame) {
return RenderFrameMessagePorts::GetOrCreate(this, render_frame);
}
return default_scope_.get();
}
mojom::MessagePortHost* NativeRendererMessagingService::GetMessagePortHost(
ScriptContext* script_context,
const PortId& port_id) {
return GetMessagePortScope(script_context->GetRenderFrame())
->GetMessagePortHost(port_id);
}
mojom::MessagePortHost*
NativeRendererMessagingService::GetMessagePortHostIfExists(
ScriptContext* script_context,
const PortId& port_id) {
return GetMessagePortScope(script_context->GetRenderFrame())
->GetMessagePortHostIfExists(port_id);
}
base::SafeRef<NativeRendererMessagingService>
NativeRendererMessagingService::AsSafeRef() {
return weak_ptr_factory_.GetSafeRef();
}
} // namespace extensions