blob: 4f79971ca1647eaf3b983e2c8138aa00e7a7fad9 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/messaging/extension_message_port.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/api/messaging/channel_endpoint.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/manifest_handlers/background_info.h"
namespace {
std::string PortIdToString(const extensions::PortId& port_id) {
return base::StrCat({port_id.GetChannelId().first.ToString(), ":",
base::NumberToString(port_id.GetChannelId().second)});
}
} // namespace
namespace extensions {
const char kReceivingEndDoesntExistError[] =
// TODO(lazyboy): Test these in service worker implementation.
"Could not establish connection. Receiving end does not exist.";
// Helper class to detect when frames are destroyed.
class ExtensionMessagePort::FrameTracker : public content::WebContentsObserver,
public ProcessManagerObserver {
public:
explicit FrameTracker(ExtensionMessagePort* port) : port_(port) {}
~FrameTracker() override {}
void TrackExtensionProcessFrames() {
pm_observation_.Observe(ProcessManager::Get(port_->browser_context_));
}
void TrackTabFrames(content::WebContents* tab) {
Observe(tab);
}
private:
// content::WebContentsObserver overrides:
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host)
override {
port_->UnregisterFrame(render_frame_host);
}
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
if (navigation_handle->HasCommitted() &&
!navigation_handle->IsSameDocument()) {
port_->UnregisterFrame(navigation_handle->GetRenderFrameHost());
}
}
// extensions::ProcessManagerObserver overrides:
void OnExtensionFrameUnregistered(
const std::string& extension_id,
content::RenderFrameHost* render_frame_host) override {
if (extension_id == port_->extension_id_)
port_->UnregisterFrame(render_frame_host);
}
void OnServiceWorkerUnregistered(const WorkerId& worker_id) override {
port_->UnregisterWorker(worker_id);
}
base::ScopedObservation<ProcessManager, ProcessManagerObserver>
pm_observation_{this};
ExtensionMessagePort* port_; // Owns this FrameTracker.
DISALLOW_COPY_AND_ASSIGN(FrameTracker);
};
// Represents target of an IPC (render frame, ServiceWorker or render process).
struct ExtensionMessagePort::IPCTarget {
content::RenderProcessHost* render_process_host;
content::RenderFrameHost* render_frame_host;
int worker_thread_id;
};
ExtensionMessagePort::ExtensionMessagePort(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const std::string& extension_id,
content::RenderProcessHost* extension_process)
: weak_channel_delegate_(channel_delegate),
port_id_(port_id),
extension_id_(extension_id),
browser_context_(extension_process->GetBrowserContext()),
extension_process_(extension_process),
did_create_port_(false),
background_host_ptr_(nullptr),
frame_tracker_(new FrameTracker(this)) {
auto all_hosts = ProcessManager::Get(browser_context_)
->GetRenderFrameHostsForExtension(extension_id);
for (content::RenderFrameHost* rfh : all_hosts)
RegisterFrame(rfh);
std::vector<WorkerId> running_workers_in_process =
ProcessManager::Get(browser_context_)
->GetServiceWorkers(extension_id_, extension_process_->GetID());
for (const WorkerId& running_worker_id : running_workers_in_process)
RegisterWorker(running_worker_id);
frame_tracker_->TrackExtensionProcessFrames();
}
ExtensionMessagePort::ExtensionMessagePort(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const std::string& extension_id,
content::RenderFrameHost* rfh,
bool include_child_frames)
: weak_channel_delegate_(channel_delegate),
port_id_(port_id),
extension_id_(extension_id),
browser_context_(rfh->GetProcess()->GetBrowserContext()),
extension_process_(nullptr),
did_create_port_(false),
background_host_ptr_(nullptr),
frame_tracker_(new FrameTracker(this)) {
content::WebContents* tab = content::WebContents::FromRenderFrameHost(rfh);
CHECK(tab);
frame_tracker_->TrackTabFrames(tab);
if (include_child_frames) {
tab->ForEachFrame(base::BindRepeating(&ExtensionMessagePort::RegisterFrame,
base::Unretained(this)));
} else {
RegisterFrame(rfh);
}
}
ExtensionMessagePort::ExtensionMessagePort(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const ExtensionId& extension_id,
content::BrowserContext* browser_context)
: weak_channel_delegate_(channel_delegate),
port_id_(port_id),
extension_id_(extension_id),
browser_context_(browser_context) {}
// static
std::unique_ptr<ExtensionMessagePort> ExtensionMessagePort::CreateForEndpoint(
base::WeakPtr<ChannelDelegate> channel_delegate,
const PortId& port_id,
const std::string& extension_id,
const ChannelEndpoint& endpoint,
bool include_child_frames) {
if (endpoint.is_for_render_frame()) {
return std::make_unique<ExtensionMessagePort>(
channel_delegate, port_id, extension_id, endpoint.GetRenderFrameHost(),
include_child_frames);
}
DCHECK(!include_child_frames);
// NOTE: We don't want all the workers within the extension, so we cannot
// reuse other constructor from above.
std::unique_ptr<ExtensionMessagePort> port(new ExtensionMessagePort(
channel_delegate, port_id, extension_id, endpoint.browser_context()));
port->frame_tracker_ = std::make_unique<FrameTracker>(port.get());
port->frame_tracker_->TrackExtensionProcessFrames();
port->RegisterWorker(endpoint.GetWorkerId());
return port;
}
ExtensionMessagePort::~ExtensionMessagePort() {}
void ExtensionMessagePort::RemoveCommonFrames(const MessagePort& port) {
// Avoid overlap in the set of frames to make sure that it does not matter
// when UnregisterFrame is called.
for (auto it = frames_.begin(); it != frames_.end();) {
if (port.HasFrame(*it)) {
frames_.erase(it++);
} else {
++it;
}
}
}
bool ExtensionMessagePort::HasFrame(content::RenderFrameHost* rfh) const {
return frames_.find(rfh) != frames_.end();
}
bool ExtensionMessagePort::IsValidPort() {
return !frames_.empty() || !service_workers_.empty();
}
void ExtensionMessagePort::RevalidatePort() {
// Checks whether the frames to which this port is tied at its construction
// are still aware of this port's existence. Frames that don't know about
// the port are removed from the set of frames. This should be used for opener
// ports because the frame may be navigated before the port was initialized.
// Only opener ports need to be revalidated, because these are created in the
// renderer before the browser knows about them.
if (service_workers_.empty())
DCHECK(!extension_process_);
DCHECK_LE(frames_.size() + service_workers_.size(), 1U);
DCHECK(frames_.empty() ^ service_workers_.empty())
<< "Either frame or Service Worker should be present.";
// If the port is unknown, the renderer will respond by closing the port.
// NOTE: There is only one opener target.
if (!frames_.empty()) {
SendToIPCTarget({nullptr, *frames_.begin(), kMainThreadId},
std::make_unique<ExtensionMsg_ValidateMessagePort>(
MSG_ROUTING_NONE, kMainThreadId, port_id_));
return;
}
if (!service_workers_.empty()) {
const WorkerId& service_worker = *service_workers_.begin();
SendToIPCTarget(
{content::RenderProcessHost::FromID(service_worker.render_process_id),
nullptr, service_worker.thread_id},
std::make_unique<ExtensionMsg_ValidateMessagePort>(
MSG_ROUTING_NONE, service_worker.thread_id, port_id_));
}
}
void ExtensionMessagePort::DispatchOnConnect(
const std::string& channel_name,
std::unique_ptr<base::DictionaryValue> source_tab,
int source_frame_id,
int guest_process_id,
int guest_render_frame_routing_id,
const MessagingEndpoint& source_endpoint,
const std::string& target_extension_id,
const GURL& source_url,
base::Optional<url::Origin> source_origin) {
SendToPort(base::BindRepeating(
&ExtensionMessagePort::BuildDispatchOnConnectIPC,
// Called synchronously.
base::Unretained(this), channel_name, source_tab.get(), source_frame_id,
guest_process_id, guest_render_frame_routing_id, source_endpoint,
target_extension_id, source_url, source_origin));
}
void ExtensionMessagePort::DispatchOnDisconnect(
const std::string& error_message) {
SendToPort(
base::BindRepeating(&ExtensionMessagePort::BuildDispatchOnDisconnectIPC,
base::Unretained(this), error_message));
}
void ExtensionMessagePort::DispatchOnMessage(const Message& message) {
SendToPort(base::BindRepeating(&ExtensionMessagePort::BuildDeliverMessageIPC,
// Called synchronously.
base::Unretained(this), message));
}
void ExtensionMessagePort::IncrementLazyKeepaliveCount() {
ProcessManager* pm = ProcessManager::Get(browser_context_);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_);
if (host && BackgroundInfo::HasLazyBackgroundPage(host->extension())) {
pm->IncrementLazyKeepaliveCount(host->extension(), Activity::MESSAGE_PORT,
PortIdToString(port_id_));
}
for (const auto& worker_id : service_workers_) {
std::string request_uuid = pm->IncrementServiceWorkerKeepaliveCount(
worker_id, Activity::MESSAGE_PORT, PortIdToString(port_id_));
if (!request_uuid.empty())
pending_keepalive_uuids_[worker_id].push_back(request_uuid);
}
// Keep track of the background host, so when we decrement, we only do so if
// the host hasn't reloaded.
background_host_ptr_ = host;
}
void ExtensionMessagePort::DecrementLazyKeepaliveCount() {
ProcessManager* pm = ProcessManager::Get(browser_context_);
ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_);
if (host && host == background_host_ptr_) {
pm->DecrementLazyKeepaliveCount(host->extension(), Activity::MESSAGE_PORT,
PortIdToString(port_id_));
return;
}
for (const auto& worker_id : service_workers_) {
auto& uuids = pending_keepalive_uuids_[worker_id];
DCHECK(!uuids.empty());
std::string request_uuid = std::move(uuids.back());
uuids.pop_back();
pm->DecrementServiceWorkerKeepaliveCount(worker_id, request_uuid,
Activity::MESSAGE_PORT,
PortIdToString(port_id_));
}
}
void ExtensionMessagePort::OpenPort(int process_id,
const PortContext& port_context) {
DCHECK((port_context.is_for_render_frame() &&
port_context.frame->routing_id != MSG_ROUTING_NONE) ||
(port_context.is_for_service_worker() &&
port_context.worker->thread_id != kMainThreadId) ||
extension_process_);
did_create_port_ = true;
}
void ExtensionMessagePort::ClosePort(int process_id,
int routing_id,
int worker_thread_id) {
const bool is_for_service_worker = worker_thread_id != kMainThreadId;
if (!is_for_service_worker && routing_id == MSG_ROUTING_NONE) {
// The only non-frame-specific message is the response to an unhandled
// onConnect event in the extension process.
DCHECK(extension_process_);
frames_.clear();
if (!HasReceivers())
CloseChannel();
return;
}
if (is_for_service_worker) {
UnregisterWorker(process_id, worker_thread_id);
} else {
DCHECK_NE(MSG_ROUTING_NONE, routing_id);
if (auto* rfh = content::RenderFrameHost::FromID(process_id, routing_id))
UnregisterFrame(rfh);
}
}
void ExtensionMessagePort::CloseChannel() {
std::string error_message = did_create_port_ ? std::string() :
kReceivingEndDoesntExistError;
if (weak_channel_delegate_)
weak_channel_delegate_->CloseChannel(port_id_, error_message);
}
void ExtensionMessagePort::RegisterFrame(content::RenderFrameHost* rfh) {
// Only register a RenderFrameHost whose RenderFrame has been created, to
// ensure that we are notified of frame destruction. Without this check,
// |frames_| can eventually contain a stale pointer because RenderFrameDeleted
// is not triggered for |rfh|.
if (rfh->IsRenderFrameLive())
frames_.insert(rfh);
}
void ExtensionMessagePort::UnregisterFrame(content::RenderFrameHost* rfh) {
if (frames_.erase(rfh) != 0 && !HasReceivers())
CloseChannel();
}
bool ExtensionMessagePort::HasReceivers() const {
return !frames_.empty() || !service_workers_.empty();
}
void ExtensionMessagePort::RegisterWorker(const WorkerId& worker_id) {
DCHECK(!worker_id.extension_id.empty());
service_workers_.insert(worker_id);
}
void ExtensionMessagePort::UnregisterWorker(const WorkerId& worker_id) {
if (extension_id_ != worker_id.extension_id)
return;
if (service_workers_.erase(worker_id) == 0)
return;
if (!HasReceivers())
CloseChannel();
}
void ExtensionMessagePort::UnregisterWorker(int render_process_id,
int worker_thread_id) {
DCHECK_NE(kMainThreadId, worker_thread_id);
// Note: We iterate through *each* workers belonging to this port to find the
// worker we are interested in. Since there will only be a handful of such
// workers, this is OK.
for (auto iter = service_workers_.begin(); iter != service_workers_.end();) {
if (iter->render_process_id == render_process_id &&
iter->thread_id == worker_thread_id) {
service_workers_.erase(iter);
break;
} else {
++iter;
}
}
if (!HasReceivers())
CloseChannel();
}
void ExtensionMessagePort::SendToPort(IPCBuilderCallback ipc_builder) {
std::vector<IPCTarget> targets;
{
// Build the list of targets.
if (extension_process_) {
// All extension frames reside in the same process, so we can just send a
// single IPC message to the extension process as an optimization if
// there are not Service Worker recipient for this port.
// The frame tracking is then only used to make sure that the port gets
// closed when all frames have closed / reloaded.
targets.push_back({extension_process_, nullptr, kMainThreadId});
} else {
for (content::RenderFrameHost* frame : frames_)
targets.push_back({nullptr, frame, kMainThreadId});
}
for (const auto& running_worker : service_workers_) {
targets.push_back(
{content::RenderProcessHost::FromID(running_worker.render_process_id),
nullptr, running_worker.thread_id});
}
}
for (const IPCTarget& target : targets) {
std::unique_ptr<IPC::Message> ipc_message = ipc_builder.Run(target);
SendToIPCTarget(target, std::move(ipc_message));
}
}
void ExtensionMessagePort::SendToIPCTarget(const IPCTarget& target,
std::unique_ptr<IPC::Message> msg) {
if (target.render_frame_host) {
msg->set_routing_id(target.render_frame_host->GetRoutingID());
target.render_frame_host->Send(msg.release());
return;
}
if (target.render_process_host) {
msg->set_routing_id(MSG_ROUTING_CONTROL);
target.render_process_host->Send(msg.release());
return;
}
DCHECK(extension_process_);
msg->set_routing_id(MSG_ROUTING_CONTROL);
extension_process_->Send(msg.release());
}
std::unique_ptr<IPC::Message> ExtensionMessagePort::BuildDispatchOnConnectIPC(
const std::string& channel_name,
const base::DictionaryValue* source_tab,
int source_frame_id,
int guest_process_id,
int guest_render_frame_routing_id,
const MessagingEndpoint& source_endpoint,
const std::string& target_extension_id,
const GURL& source_url,
base::Optional<url::Origin> source_origin,
const IPCTarget& target) {
ExtensionMsg_TabConnectionInfo source;
if (source_tab) {
std::unique_ptr<base::Value> source_tab_value =
base::Value::ToUniquePtrValue(source_tab->Clone());
// TODO(lazyboy): Make ExtensionMsg_TabConnectionInfo.tab a base::Value and
// remove this cast.
source.tab.Swap(
static_cast<base::DictionaryValue*>(source_tab_value.get()));
}
source.frame_id = source_frame_id;
ExtensionMsg_ExternalConnectionInfo info;
info.target_id = target_extension_id;
info.source_endpoint = source_endpoint;
info.source_url = source_url;
info.source_origin = std::move(source_origin);
info.guest_process_id = guest_process_id;
info.guest_render_frame_routing_id = guest_render_frame_routing_id;
return std::make_unique<ExtensionMsg_DispatchOnConnect>(
MSG_ROUTING_NONE, target.worker_thread_id, port_id_, channel_name, source,
info);
}
std::unique_ptr<IPC::Message>
ExtensionMessagePort::BuildDispatchOnDisconnectIPC(
const std::string& error_message,
const IPCTarget& target) {
return std::make_unique<ExtensionMsg_DispatchOnDisconnect>(
MSG_ROUTING_NONE, target.worker_thread_id, port_id_, error_message);
}
std::unique_ptr<IPC::Message> ExtensionMessagePort::BuildDeliverMessageIPC(
const Message& message,
const IPCTarget& target) {
return std::make_unique<ExtensionMsg_DeliverMessage>(
MSG_ROUTING_NONE, target.worker_thread_id, port_id_, message);
}
} // namespace extensions