| // 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/ipc_message_sender.h" |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "content/public/renderer/worker_thread.h" |
| #include "extensions/common/api/messaging/messaging_endpoint.h" |
| #include "extensions/common/api/messaging/port_context.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/features/feature.h" |
| #include "extensions/common/mojom/automation_registry.mojom.h" |
| #include "extensions/common/mojom/context_type.mojom.h" |
| #include "extensions/common/mojom/event_router.mojom.h" |
| #include "extensions/common/mojom/frame.mojom.h" |
| #include "extensions/common/mojom/message_port.mojom-shared.h" |
| #include "extensions/common/mojom/renderer_host.mojom.h" |
| #include "extensions/common/trace_util.h" |
| #include "extensions/renderer/api/messaging/message_target.h" |
| #include "extensions/renderer/dispatcher.h" |
| #include "extensions/renderer/extension_frame_helper.h" |
| #include "extensions/renderer/extensions_renderer_client.h" |
| #include "extensions/renderer/native_extension_bindings_system.h" |
| #include "extensions/renderer/script_context.h" |
| #include "extensions/renderer/service_worker_data.h" |
| #include "extensions/renderer/trace_util.h" |
| #include "extensions/renderer/worker_thread_dispatcher.h" |
| #include "ipc/ipc_sync_channel.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h" |
| #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| class MainThreadIPCMessageSender : public IPCMessageSender { |
| public: |
| MainThreadIPCMessageSender() : render_thread_(content::RenderThread::Get()) {} |
| |
| MainThreadIPCMessageSender(const MainThreadIPCMessageSender&) = delete; |
| MainThreadIPCMessageSender& operator=(const MainThreadIPCMessageSender&) = |
| delete; |
| |
| ~MainThreadIPCMessageSender() override {} |
| |
| void SendRequestIPC(ScriptContext* context, |
| mojom::RequestParamsPtr params) override { |
| content::RenderFrame* frame = context->GetRenderFrame(); |
| if (!frame) |
| return; |
| |
| int request_id = params->request_id; |
| ExtensionFrameHelper::Get(frame)->GetLocalFrameHost()->Request( |
| std::move(params), |
| base::BindOnce(&MainThreadIPCMessageSender::OnResponse, |
| weak_ptr_factory_.GetWeakPtr(), request_id)); |
| } |
| |
| void SendResponseAckIPC(ScriptContext* context, |
| const base::Uuid& request_uuid) override { |
| CHECK(!context->IsForServiceWorker()); |
| CHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| content::RenderFrame* frame = context->GetRenderFrame(); |
| CHECK(frame); |
| |
| ExtensionFrameHelper::Get(frame)->GetLocalFrameHost()->ResponseAck( |
| request_uuid); |
| } |
| |
| mojom::EventListenerOwnerPtr GetEventListenerOwner(ScriptContext* context) { |
| return !context->GetExtensionID().empty() |
| ? mojom::EventListenerOwner::NewExtensionId( |
| context->GetExtensionID()) |
| : mojom::EventListenerOwner::NewListenerUrl(context->url()); |
| } |
| |
| void SendAddUnfilteredEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->AddListenerForMainThread(mojom::EventListener::New( |
| GetEventListenerOwner(context), event_name, nullptr, std::nullopt)); |
| } |
| |
| void SendRemoveUnfilteredEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->RemoveListenerForMainThread( |
| mojom::EventListener::New(GetEventListenerOwner(context), event_name, |
| nullptr, std::nullopt)); |
| } |
| |
| void SendAddUnfilteredLazyEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->AddLazyListenerForMainThread( |
| context->GetExtensionID(), event_name); |
| } |
| |
| void SendRemoveUnfilteredLazyEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->RemoveLazyListenerForMainThread( |
| context->GetExtensionID(), event_name); |
| } |
| |
| void SendAddFilteredEventListenerIPC(ScriptContext* context, |
| const std::string& event_name, |
| const base::Value::Dict& filter, |
| bool is_lazy) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->AddFilteredListenerForMainThread( |
| GetEventListenerOwner(context), event_name, filter.Clone(), is_lazy); |
| } |
| |
| void SendRemoveFilteredEventListenerIPC(ScriptContext* context, |
| const std::string& event_name, |
| const base::Value::Dict& filter, |
| bool remove_lazy_listener) override { |
| DCHECK(!context->IsForServiceWorker()); |
| DCHECK_EQ(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| GetEventRouter(context)->RemoveFilteredListenerForMainThread( |
| GetEventListenerOwner(context), event_name, filter.Clone(), |
| remove_lazy_listener); |
| } |
| |
| void SendBindAutomationIPC( |
| ScriptContext* context, |
| mojo::PendingAssociatedRemote<ax::mojom::Automation> pending_remote) |
| override { |
| CHECK(!context->IsForServiceWorker()); |
| |
| GetRendererAutomationRegistry(context)->BindAutomation( |
| std::move(pending_remote)); |
| } |
| |
| void SendOpenMessageChannel( |
| ScriptContext* script_context, |
| const PortId& port_id, |
| const MessageTarget& target, |
| mojom::ChannelType channel_type, |
| const std::string& channel_name, |
| mojo::PendingAssociatedRemote<mojom::MessagePort> port, |
| mojo::PendingAssociatedReceiver<mojom::MessagePortHost> port_host) |
| override { |
| content::RenderFrame* render_frame = script_context->GetRenderFrame(); |
| DCHECK(render_frame); |
| const Extension* extension = script_context->extension(); |
| |
| // TODO(crbug.com/40263335): We should just avoid passing a |
| // channel name in at all for non-connect messages; we no longer need to. |
| std::string channel_name_to_use = |
| channel_type == mojom::ChannelType::kConnect ? channel_name |
| : std::string(); |
| switch (target.type) { |
| case MessageTarget::EXTENSION: { |
| auto info = mojom::ExternalConnectionInfo::New(); |
| if (extension && !extension->is_hosted_app()) { |
| switch (script_context->context_type()) { |
| case mojom::ContextType::kPrivilegedExtension: |
| case mojom::ContextType::kUnprivilegedExtension: |
| case mojom::ContextType::kOffscreenExtension: |
| info->source_endpoint = |
| MessagingEndpoint::ForExtension(extension->id()); |
| break; |
| case mojom::ContextType::kContentScript: |
| info->source_endpoint = |
| MessagingEndpoint::ForContentScript(extension->id()); |
| break; |
| case mojom::ContextType::kUserScript: |
| info->source_endpoint = |
| MessagingEndpoint::ForUserScript(extension->id()); |
| break; |
| case mojom::ContextType::kUnspecified: |
| case mojom::ContextType::kWebPage: |
| case mojom::ContextType::kPrivilegedWebPage: |
| case mojom::ContextType::kWebUi: |
| case mojom::ContextType::kUntrustedWebUi: |
| NOTREACHED() << "Unexpected Context Encountered: " |
| << script_context->GetDebugString(); |
| } |
| } else { |
| info->source_endpoint = MessagingEndpoint::ForWebPage(); |
| } |
| info->target_id = *target.extension_id; |
| info->source_url = script_context->url(); |
| |
| TRACE_RENDERER_EXTENSION_EVENT( |
| "MainThreadIPCMessageSender::SendOpenMessageChannel/extension", |
| *target.extension_id); |
| ExtensionFrameHelper::Get(render_frame) |
| ->GetLocalFrameHost() |
| ->OpenChannelToExtension(std::move(info), channel_type, |
| channel_name_to_use, port_id, |
| std::move(port), std::move(port_host)); |
| break; |
| } |
| case MessageTarget::TAB: { |
| DCHECK(extension); |
| DCHECK_NE(script_context->context_type(), |
| mojom::ContextType::kContentScript); |
| ExtensionFrameHelper::Get(render_frame) |
| ->GetLocalFrameHost() |
| ->OpenChannelToTab(*target.tab_id, *target.frame_id, |
| target.document_id, channel_type, |
| channel_name_to_use, port_id, std::move(port), |
| std::move(port_host)); |
| break; |
| } |
| case MessageTarget::NATIVE_APP: |
| CHECK_EQ(mojom::ChannelType::kNative, channel_type); |
| ExtensionFrameHelper::Get(render_frame) |
| ->GetLocalFrameHost() |
| ->OpenChannelToNativeApp(*target.native_application_name, port_id, |
| std::move(port), std::move(port_host)); |
| break; |
| } |
| } |
| |
| void SendActivityLogIPC(ScriptContext* context, |
| const ExtensionId& extension_id, |
| ActivityLogCallType call_type, |
| const std::string& call_name, |
| base::Value::List args, |
| const std::string& extra) override { |
| std::optional<ExtensionId> optional_extension_id; |
| if (!extension_id.empty()) { |
| optional_extension_id = extension_id; |
| } |
| switch (call_type) { |
| case ActivityLogCallType::APICALL: |
| GetRendererHost(context)->AddAPIActionToActivityLog( |
| optional_extension_id, call_name, std::move(args), extra); |
| break; |
| case ActivityLogCallType::EVENT: |
| GetRendererHost(context)->AddEventToActivityLog( |
| optional_extension_id, call_name, std::move(args), extra); |
| break; |
| } |
| } |
| |
| private: |
| void OnResponse(int request_id, |
| bool success, |
| base::Value::List response, |
| const std::string& error, |
| mojom::ExtraResponseDataPtr response_data) { |
| ExtensionsRendererClient::Get() |
| ->dispatcher() |
| ->bindings_system() |
| ->HandleResponse(request_id, success, std::move(response), error, |
| std::move(response_data)); |
| } |
| |
| mojom::EventRouter* GetEventRouter(ScriptContext* context) { |
| content::RenderFrame* frame = context->GetRenderFrame(); |
| CHECK(frame); |
| return ExtensionFrameHelper::Get(frame)->GetEventRouter(); |
| } |
| |
| mojom::RendererAutomationRegistry* GetRendererAutomationRegistry( |
| ScriptContext* context) { |
| content::RenderFrame* frame = context->GetRenderFrame(); |
| CHECK(frame); |
| return ExtensionFrameHelper::Get(frame)->GetRendererAutomationRegistry(); |
| } |
| |
| mojom::RendererHost* GetRendererHost(ScriptContext* context) { |
| content::RenderFrame* frame = context->GetRenderFrame(); |
| CHECK(frame); |
| return ExtensionFrameHelper::Get(frame)->GetRendererHost(); |
| } |
| |
| const raw_ptr<content::RenderThread, DanglingUntriaged> render_thread_; |
| mojo::AssociatedRemote<mojom::RendererHost> renderer_host_; |
| |
| base::WeakPtrFactory<MainThreadIPCMessageSender> weak_ptr_factory_{this}; |
| }; |
| |
| class WorkerThreadIPCMessageSender : public IPCMessageSender { |
| public: |
| WorkerThreadIPCMessageSender( |
| WorkerThreadDispatcher* dispatcher, |
| blink::WebServiceWorkerContextProxy* context_proxy, |
| int64_t service_worker_version_id) |
| : dispatcher_(dispatcher), |
| service_worker_version_id_(service_worker_version_id) {} |
| |
| WorkerThreadIPCMessageSender(const WorkerThreadIPCMessageSender&) = delete; |
| WorkerThreadIPCMessageSender& operator=(const WorkerThreadIPCMessageSender&) = |
| delete; |
| |
| ~WorkerThreadIPCMessageSender() override {} |
| |
| void SendRequestIPC(ScriptContext* context, |
| mojom::RequestParamsPtr params) override { |
| DCHECK(!context->GetRenderFrame()); |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| int worker_thread_id = content::WorkerThread::GetCurrentId(); |
| params->worker_thread_id = worker_thread_id; |
| params->service_worker_version_id = service_worker_version_id_; |
| |
| const int request_id = params->request_id; |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetServiceWorkerHost() |
| ->RequestWorker(std::move(params), |
| base::BindOnce( |
| [](int request_id, bool success, |
| base::Value::List args, const std::string& error, |
| mojom::ExtraResponseDataPtr extra_data) { |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->bindings_system() |
| ->HandleResponse(request_id, success, |
| std::move(args), error, |
| std::move(extra_data)); |
| }, |
| request_id)); |
| } |
| |
| void SendResponseAckIPC(ScriptContext* context, |
| const base::Uuid& request_uuid) override { |
| CHECK(!context->GetRenderFrame()); |
| CHECK(context->IsForServiceWorker()); |
| CHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetServiceWorkerHost() |
| ->WorkerResponseAck(request_uuid); |
| } |
| |
| void SendAddUnfilteredEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, |
| context->service_worker_version_id()); |
| |
| auto event_listener = mojom::EventListener::New( |
| mojom::EventListenerOwner::NewExtensionId(context->GetExtensionID()), |
| event_name, |
| mojom::ServiceWorkerContext::New(context->service_worker_scope(), |
| context->service_worker_version_id(), |
| content::WorkerThread::GetCurrentId()), |
| /*event_filter=*/std::nullopt); |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->AddListenerForServiceWorker(std::move(event_listener)); |
| } |
| |
| void SendRemoveUnfilteredEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, |
| context->service_worker_version_id()); |
| |
| auto event_listener = mojom::EventListener::New( |
| mojom::EventListenerOwner::NewExtensionId(context->GetExtensionID()), |
| event_name, |
| mojom::ServiceWorkerContext::New(context->service_worker_scope(), |
| context->service_worker_version_id(), |
| content::WorkerThread::GetCurrentId()), |
| /*event_filter=*/std::nullopt); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->RemoveListenerForServiceWorker(std::move(event_listener)); |
| } |
| |
| void SendAddUnfilteredLazyEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->AddLazyListenerForServiceWorker(context->GetExtensionID(), |
| context->service_worker_scope(), |
| event_name); |
| } |
| |
| void SendRemoveUnfilteredLazyEventListenerIPC( |
| ScriptContext* context, |
| const std::string& event_name) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->RemoveLazyListenerForServiceWorker(context->GetExtensionID(), |
| context->service_worker_scope(), |
| event_name); |
| } |
| |
| void SendAddFilteredEventListenerIPC(ScriptContext* context, |
| const std::string& event_name, |
| const base::Value::Dict& filter, |
| bool is_lazy) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, |
| context->service_worker_version_id()); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->AddFilteredListenerForServiceWorker( |
| context->GetExtensionID(), event_name, |
| mojom::ServiceWorkerContext::New( |
| context->service_worker_scope(), |
| context->service_worker_version_id(), |
| content::WorkerThread::GetCurrentId()), |
| filter.Clone(), is_lazy); |
| } |
| |
| void SendRemoveFilteredEventListenerIPC(ScriptContext* context, |
| const std::string& event_name, |
| const base::Value::Dict& filter, |
| bool remove_lazy_listener) override { |
| DCHECK(context->IsForServiceWorker()); |
| DCHECK_NE(kMainThreadId, content::WorkerThread::GetCurrentId()); |
| DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, |
| context->service_worker_version_id()); |
| |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetEventRouter() |
| ->RemoveFilteredListenerForServiceWorker( |
| context->GetExtensionID(), event_name, |
| mojom::ServiceWorkerContext::New( |
| context->service_worker_scope(), |
| context->service_worker_version_id(), |
| content::WorkerThread::GetCurrentId()), |
| filter.Clone(), remove_lazy_listener); |
| } |
| |
| void SendBindAutomationIPC( |
| ScriptContext* context, |
| mojo::PendingAssociatedRemote<ax::mojom::Automation> pending_remote) |
| override { |
| CHECK(context->IsForServiceWorker()); |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetAutomationRegistry() |
| ->BindAutomation(std::move(pending_remote)); |
| } |
| |
| void SendOpenMessageChannel( |
| ScriptContext* script_context, |
| const PortId& port_id, |
| const MessageTarget& target, |
| mojom::ChannelType channel_type, |
| const std::string& channel_name, |
| mojo::PendingAssociatedRemote<mojom::MessagePort> port, |
| mojo::PendingAssociatedReceiver<mojom::MessagePortHost> port_host) |
| override { |
| DCHECK(!script_context->GetRenderFrame()); |
| DCHECK(script_context->IsForServiceWorker()); |
| const Extension* extension = script_context->extension(); |
| |
| // TODO(crbug.com/40263335): We should just avoid passing a |
| // channel name in at all for non-connect messages; we no longer need to. |
| std::string channel_name_to_use = |
| channel_type == mojom::ChannelType::kConnect ? channel_name |
| : std::string(); |
| |
| switch (target.type) { |
| case MessageTarget::EXTENSION: { |
| auto info = mojom::ExternalConnectionInfo::New(); |
| if (extension && !extension->is_hosted_app()) { |
| info->source_endpoint = |
| MessagingEndpoint::ForExtension(extension->id()); |
| } |
| info->target_id = *target.extension_id; |
| info->source_url = script_context->url(); |
| TRACE_RENDERER_EXTENSION_EVENT( |
| "WorkerThreadIPCMessageSender::SendOpenMessageChannel/extension", |
| *target.extension_id); |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetServiceWorkerHost() |
| ->OpenChannelToExtension(std::move(info), channel_type, |
| channel_name_to_use, port_id, |
| std::move(port), std::move(port_host)); |
| break; |
| } |
| case MessageTarget::TAB: { |
| DCHECK(extension); |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetServiceWorkerHost() |
| ->OpenChannelToTab(*target.tab_id, *target.frame_id, |
| target.document_id, channel_type, |
| channel_name_to_use, port_id, std::move(port), |
| std::move(port_host)); |
| break; |
| } |
| case MessageTarget::NATIVE_APP: |
| CHECK_EQ(mojom::ChannelType::kNative, channel_type); |
| WorkerThreadDispatcher::GetServiceWorkerData() |
| ->GetServiceWorkerHost() |
| ->OpenChannelToNativeApp(*target.native_application_name, port_id, |
| std::move(port), std::move(port_host)); |
| break; |
| } |
| } |
| |
| void SendActivityLogIPC(ScriptContext* context, |
| const ExtensionId& extension_id, |
| ActivityLogCallType call_type, |
| const std::string& call_name, |
| base::Value::List args, |
| const std::string& extra) override { |
| std::optional<ExtensionId> optional_extension_id; |
| if (!extension_id.empty()) { |
| optional_extension_id = extension_id; |
| } |
| switch (call_type) { |
| case ActivityLogCallType::APICALL: |
| GetRendererHost()->AddAPIActionToActivityLog( |
| optional_extension_id, call_name, std::move(args), extra); |
| break; |
| case ActivityLogCallType::EVENT: |
| GetRendererHost()->AddEventToActivityLog( |
| optional_extension_id, call_name, std::move(args), extra); |
| break; |
| } |
| } |
| |
| private: |
| const ExtensionId& GetExtensionId() { |
| if (!extension_id_) |
| extension_id_ = dispatcher_->GetScriptContext()->extension()->id(); |
| return *extension_id_; |
| } |
| |
| mojom::RendererHost* GetRendererHost() { |
| return WorkerThreadDispatcher::GetServiceWorkerData()->GetRendererHost(); |
| } |
| |
| const raw_ptr<WorkerThreadDispatcher> dispatcher_; |
| const int64_t service_worker_version_id_; |
| std::optional<ExtensionId> extension_id_; |
| }; |
| |
| } // namespace |
| |
| IPCMessageSender::IPCMessageSender() = default; |
| |
| IPCMessageSender::~IPCMessageSender() = default; |
| |
| // static |
| std::unique_ptr<IPCMessageSender> |
| IPCMessageSender::CreateMainThreadIPCMessageSender() { |
| return std::make_unique<MainThreadIPCMessageSender>(); |
| } |
| |
| // static |
| std::unique_ptr<IPCMessageSender> |
| IPCMessageSender::CreateWorkerThreadIPCMessageSender( |
| WorkerThreadDispatcher* dispatcher, |
| blink::WebServiceWorkerContextProxy* context_proxy, |
| int64_t service_worker_version_id) { |
| return std::make_unique<WorkerThreadIPCMessageSender>( |
| dispatcher, context_proxy, service_worker_version_id); |
| } |
| |
| } // namespace extensions |