blob: 5b4c3829bff7839f33c2214405d12a301177a635 [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 "chrome/browser/extensions/api/messaging/message_service.h"
#include <stdint.h>
#include <limits>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api/messaging/extension_message_port.h"
#include "chrome/browser/extensions/api/messaging/incognito_connectability.h"
#include "chrome/browser/extensions/api/messaging/native_message_port.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "components/guest_view/common/guest_view_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/child_process_host.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#include "extensions/browser/lazy_background_task_queue.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/externally_connectable.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "net/base/completion_callback.h"
#include "url/gurl.h"
using content::BrowserContext;
using content::BrowserThread;
using content::SiteInstance;
using content::WebContents;
// Since we have 2 ports for every channel, we just index channels by half the
// port ID.
#define GET_CHANNEL_ID(port_id) ((port_id) / 2)
#define GET_CHANNEL_OPENER_ID(channel_id) ((channel_id) * 2)
#define GET_CHANNEL_RECEIVERS_ID(channel_id) ((channel_id) * 2 + 1)
// Port1 is always even, port2 is always odd.
#define IS_OPENER_PORT_ID(port_id) (((port_id) & 1) == 0)
// Change even to odd and vice versa, to get the other side of a given channel.
#define GET_OPPOSITE_PORT_ID(source_port_id) ((source_port_id) ^ 1)
namespace extensions {
MessageService::PolicyPermission MessageService::IsNativeMessagingHostAllowed(
const PrefService* pref_service,
const std::string& native_host_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PolicyPermission allow_result = ALLOW_ALL;
if (pref_service->IsManagedPreference(
pref_names::kNativeMessagingUserLevelHosts)) {
if (!pref_service->GetBoolean(pref_names::kNativeMessagingUserLevelHosts))
allow_result = ALLOW_SYSTEM_ONLY;
}
// All native messaging hosts are allowed if there is no blacklist.
if (!pref_service->IsManagedPreference(pref_names::kNativeMessagingBlacklist))
return allow_result;
const base::ListValue* blacklist =
pref_service->GetList(pref_names::kNativeMessagingBlacklist);
if (!blacklist)
return allow_result;
// Check if the name or the wildcard is in the blacklist.
base::StringValue name_value(native_host_name);
base::StringValue wildcard_value("*");
if (blacklist->Find(name_value) == blacklist->end() &&
blacklist->Find(wildcard_value) == blacklist->end()) {
return allow_result;
}
// The native messaging host is blacklisted. Check the whitelist.
if (pref_service->IsManagedPreference(
pref_names::kNativeMessagingWhitelist)) {
const base::ListValue* whitelist =
pref_service->GetList(pref_names::kNativeMessagingWhitelist);
if (whitelist && whitelist->Find(name_value) != whitelist->end())
return allow_result;
}
return DISALLOW;
}
const char kReceivingEndDoesntExistError[] =
"Could not establish connection. Receiving end does not exist.";
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
const char kMissingPermissionError[] =
"Access to native messaging requires nativeMessaging permission.";
const char kProhibitedByPoliciesError[] =
"Access to the native messaging host was disabled by the system "
"administrator.";
#endif
struct MessageService::MessageChannel {
std::unique_ptr<MessagePort> opener;
std::unique_ptr<MessagePort> receiver;
};
struct MessageService::OpenChannelParams {
int source_process_id;
int source_routing_id;
std::unique_ptr<base::DictionaryValue> source_tab;
int source_frame_id;
std::unique_ptr<MessagePort> receiver;
int receiver_port_id;
std::string source_extension_id;
std::string target_extension_id;
GURL source_url;
std::string channel_name;
bool include_tls_channel_id;
std::string tls_channel_id;
bool include_guest_process_info;
// Takes ownership of receiver.
OpenChannelParams(int source_process_id,
int source_routing_id,
std::unique_ptr<base::DictionaryValue> source_tab,
int source_frame_id,
MessagePort* receiver,
int receiver_port_id,
const std::string& source_extension_id,
const std::string& target_extension_id,
const GURL& source_url,
const std::string& channel_name,
bool include_tls_channel_id,
bool include_guest_process_info)
: source_process_id(source_process_id),
source_routing_id(source_routing_id),
source_frame_id(source_frame_id),
receiver(receiver),
receiver_port_id(receiver_port_id),
source_extension_id(source_extension_id),
target_extension_id(target_extension_id),
source_url(source_url),
channel_name(channel_name),
include_tls_channel_id(include_tls_channel_id),
include_guest_process_info(include_guest_process_info) {
if (source_tab)
this->source_tab = std::move(source_tab);
}
private:
DISALLOW_COPY_AND_ASSIGN(OpenChannelParams);
};
namespace {
static base::StaticAtomicSequenceNumber g_next_channel_id;
static content::RenderProcessHost* GetExtensionProcess(
BrowserContext* context,
const std::string& extension_id) {
scoped_refptr<SiteInstance> site_instance =
ProcessManager::Get(context)->GetSiteInstanceForURL(
Extension::GetBaseURLFromExtensionId(extension_id));
return site_instance->HasProcess() ? site_instance->GetProcess() : NULL;
}
} // namespace
void MessageService::MessagePort::RemoveCommonFrames(const MessagePort& port) {}
bool MessageService::MessagePort::HasFrame(
content::RenderFrameHost* rfh) const {
return false;
}
// static
void MessageService::AllocatePortIdPair(int* port1, int* port2) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
unsigned channel_id = static_cast<unsigned>(g_next_channel_id.GetNext()) %
(std::numeric_limits<int32_t>::max() / 2);
unsigned port1_id = channel_id * 2;
unsigned port2_id = channel_id * 2 + 1;
// Sanity checks to make sure our channel<->port converters are correct.
DCHECK(IS_OPENER_PORT_ID(port1_id));
DCHECK_EQ(GET_OPPOSITE_PORT_ID(port1_id), port2_id);
DCHECK_EQ(GET_OPPOSITE_PORT_ID(port2_id), port1_id);
DCHECK_EQ(GET_CHANNEL_ID(port1_id), GET_CHANNEL_ID(port2_id));
DCHECK_EQ(GET_CHANNEL_ID(port1_id), channel_id);
DCHECK_EQ(GET_CHANNEL_OPENER_ID(channel_id), port1_id);
DCHECK_EQ(GET_CHANNEL_RECEIVERS_ID(channel_id), port2_id);
*port1 = port1_id;
*port2 = port2_id;
}
MessageService::MessageService(BrowserContext* context)
: lazy_background_task_queue_(
LazyBackgroundTaskQueue::Get(context)),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
MessageService::~MessageService() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
channels_.clear();
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<MessageService> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<MessageService>*
MessageService::GetFactoryInstance() {
return g_factory.Pointer();
}
// static
MessageService* MessageService::Get(BrowserContext* context) {
return BrowserContextKeyedAPIFactory<MessageService>::Get(context);
}
void MessageService::OpenChannelToExtension(
int source_process_id, int source_routing_id, int receiver_port_id,
const std::string& source_extension_id,
const std::string& target_extension_id,
const GURL& source_url,
const std::string& channel_name,
bool include_tls_channel_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(source_process_id, source_routing_id);
if (!source)
return;
BrowserContext* context = source->GetProcess()->GetBrowserContext();
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
const Extension* target_extension =
registry->enabled_extensions().GetByID(target_extension_id);
if (!target_extension) {
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
bool is_web_connection = false;
if (source_extension_id != target_extension_id) {
// It's an external connection. Check the externally_connectable manifest
// key if it's present. If it's not, we allow connection from any extension
// but not webpages.
ExternallyConnectableInfo* externally_connectable =
static_cast<ExternallyConnectableInfo*>(
target_extension->GetManifestData(
manifest_keys::kExternallyConnectable));
bool is_externally_connectable = false;
if (externally_connectable) {
if (source_extension_id.empty()) {
// No source extension ID so the source was a web page. Check that the
// URL matches.
is_web_connection = true;
is_externally_connectable =
externally_connectable->matches.MatchesURL(source_url);
// Only include the TLS channel ID for externally connected web pages.
include_tls_channel_id &=
is_externally_connectable &&
externally_connectable->accepts_tls_channel_id;
} else {
// Source extension ID so the source was an extension. Check that the
// extension matches.
is_externally_connectable =
externally_connectable->IdCanConnect(source_extension_id);
}
} else {
// Default behaviour. Any extension, no webpages.
is_externally_connectable = !source_extension_id.empty();
}
if (!is_externally_connectable) {
// Important: use kReceivingEndDoesntExistError here so that we don't
// leak information about this extension to callers. This way it's
// indistinguishable from the extension just not existing.
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
}
WebContents* source_contents = tab_util::GetWebContentsByFrameID(
source_process_id, source_routing_id);
bool include_guest_process_info = false;
// Include info about the opener's tab (if it was a tab).
std::unique_ptr<base::DictionaryValue> source_tab;
int source_frame_id = -1;
if (source_contents && ExtensionTabUtil::GetTabId(source_contents) >= 0) {
// Only the tab id is useful to platform apps for internal use. The
// unnecessary bits will be stripped out in
// MessagingBindings::DispatchOnConnect().
source_tab.reset(ExtensionTabUtil::CreateTabObject(source_contents)
->ToValue()
.release());
content::RenderFrameHost* rfh =
content::RenderFrameHost::FromID(source_process_id, source_routing_id);
if (rfh)
source_frame_id = ExtensionApiFrameIdMap::GetFrameId(rfh);
} else {
// Check to see if it was a WebView making the request.
// Sending messages from WebViews to extensions breaks webview isolation,
// so only allow component extensions to receive messages from WebViews.
bool is_web_view = !!WebViewGuest::FromWebContents(source_contents);
if (is_web_view && extensions::Manifest::IsComponentLocation(
target_extension->location())) {
include_guest_process_info = true;
}
}
std::unique_ptr<OpenChannelParams> params(new OpenChannelParams(
source_process_id, source_routing_id, std::move(source_tab),
source_frame_id, nullptr, receiver_port_id, source_extension_id,
target_extension_id, source_url, channel_name, include_tls_channel_id,
include_guest_process_info));
pending_incognito_channels_[GET_CHANNEL_ID(params->receiver_port_id)] =
PendingMessagesQueue();
if (context->IsOffTheRecord() &&
!util::IsIncognitoEnabled(target_extension_id, context)) {
// Give the user a chance to accept an incognito connection from the web if
// they haven't already, with the conditions:
// - Only for spanning-mode incognito. We don't want the complication of
// spinning up an additional process here which might need to do some
// setup that we're not expecting.
// - Only for extensions that can't normally be enabled in incognito, since
// that surface (e.g. chrome://extensions) should be the only one for
// enabling in incognito. In practice this means platform apps only.
if (!is_web_connection || IncognitoInfo::IsSplitMode(target_extension) ||
util::CanBeIncognitoEnabled(target_extension)) {
OnOpenChannelAllowed(std::move(params), false);
return;
}
// If the target extension isn't even listening for connect/message events,
// there is no need to go any further and the connection should be
// rejected without showing a prompt. See http://crbug.com/442497
EventRouter* event_router = EventRouter::Get(context);
const char* const events[] = {"runtime.onConnectExternal",
"runtime.onMessageExternal",
"extension.onRequestExternal",
nullptr};
bool has_event_listener = false;
for (const char* const* event = events; *event; event++) {
has_event_listener |=
event_router->ExtensionHasEventListener(target_extension_id, *event);
}
if (!has_event_listener) {
OnOpenChannelAllowed(std::move(params), false);
return;
}
// This check may show a dialog.
IncognitoConnectability::Get(context)
->Query(target_extension, source_contents, source_url,
base::Bind(&MessageService::OnOpenChannelAllowed,
weak_factory_.GetWeakPtr(), base::Passed(&params)));
return;
}
OnOpenChannelAllowed(std::move(params), true);
}
void MessageService::OpenChannelToNativeApp(
int source_process_id,
int source_routing_id,
int receiver_port_id,
const std::string& native_app_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(source_process_id, source_routing_id);
if (!source)
return;
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(source);
ExtensionWebContentsObserver* extension_web_contents_observer =
web_contents ?
ExtensionWebContentsObserver::GetForWebContents(web_contents) :
nullptr;
const Extension* extension =
extension_web_contents_observer ?
extension_web_contents_observer->GetExtensionFromFrame(source, true) :
nullptr;
bool has_permission = extension &&
extension->permissions_data()->HasAPIPermission(
APIPermission::kNativeMessaging);
if (!has_permission) {
DispatchOnDisconnect(source, receiver_port_id, kMissingPermissionError);
return;
}
PrefService* pref_service =
Profile::FromBrowserContext(source->GetProcess()->GetBrowserContext())->
GetPrefs();
// Verify that the host is not blocked by policies.
PolicyPermission policy_permission =
IsNativeMessagingHostAllowed(pref_service, native_app_name);
if (policy_permission == DISALLOW) {
DispatchOnDisconnect(source, receiver_port_id, kProhibitedByPoliciesError);
return;
}
std::unique_ptr<MessageChannel> channel(new MessageChannel());
channel->opener.reset(
new ExtensionMessagePort(weak_factory_.GetWeakPtr(),
GET_OPPOSITE_PORT_ID(receiver_port_id),
extension->id(), source, false));
if (!channel->opener->IsValidPort())
return;
channel->opener->OpenPort(source_process_id, source_routing_id);
// Get handle of the native view and pass it to the native messaging host.
gfx::NativeView native_view = source ? source->GetNativeView() : nullptr;
std::string error = kReceivingEndDoesntExistError;
std::unique_ptr<NativeMessageHost> native_host = NativeMessageHost::Create(
native_view, extension->id(), native_app_name,
policy_permission == ALLOW_ALL, &error);
// Abandon the channel.
if (!native_host.get()) {
LOG(ERROR) << "Failed to create native process.";
DispatchOnDisconnect(source, receiver_port_id, error);
return;
}
channel->receiver.reset(new NativeMessagePort(
weak_factory_.GetWeakPtr(), receiver_port_id, std::move(native_host)));
// Keep the opener alive until the channel is closed.
channel->opener->IncrementLazyKeepaliveCount();
AddChannel(std::move(channel), receiver_port_id);
#else // !(defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX))
const char kNativeMessagingNotSupportedError[] =
"Native Messaging is not supported on this platform.";
DispatchOnDisconnect(
source, receiver_port_id, kNativeMessagingNotSupportedError);
#endif // !(defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX))
}
void MessageService::OpenChannelToTab(int source_process_id,
int source_routing_id,
int receiver_port_id,
int tab_id,
int frame_id,
const std::string& extension_id,
const std::string& channel_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_GE(frame_id, -1);
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(source_process_id, source_routing_id);
if (!source)
return;
Profile* profile =
Profile::FromBrowserContext(source->GetProcess()->GetBrowserContext());
WebContents* contents = NULL;
std::unique_ptr<MessagePort> receiver;
if (!ExtensionTabUtil::GetTabById(tab_id, profile, true, NULL, NULL,
&contents, NULL) ||
contents->GetController().NeedsReload()) {
// The tab isn't loaded yet. Don't attempt to connect.
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
// Frame ID -1 is every frame in the tab.
bool include_child_frames = frame_id == -1;
content::RenderFrameHost* receiver_rfh =
include_child_frames
? contents->GetMainFrame()
: ExtensionApiFrameIdMap::GetRenderFrameHostById(contents, frame_id);
if (!receiver_rfh) {
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
receiver.reset(
new ExtensionMessagePort(weak_factory_.GetWeakPtr(),
receiver_port_id, extension_id, receiver_rfh,
include_child_frames));
const Extension* extension = nullptr;
if (!extension_id.empty()) {
// Source extension == target extension so the extension must exist, or
// where did the IPC come from?
extension = ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
extension_id);
DCHECK(extension);
}
std::unique_ptr<OpenChannelParams> params(new OpenChannelParams(
source_process_id, source_routing_id,
std::unique_ptr<base::DictionaryValue>(), // Source tab doesn't make
// sense
// for opening to tabs.
-1, // If there is no tab, then there is no frame either.
receiver.release(), receiver_port_id, extension_id, extension_id,
GURL(), // Source URL doesn't make sense for opening to tabs.
channel_name,
false, // Connections to tabs don't get TLS channel IDs.
false)); // Connections to tabs aren't webview guests.
OpenChannelImpl(contents->GetBrowserContext(), std::move(params), extension,
false /* did_enqueue */);
}
void MessageService::OpenChannelImpl(BrowserContext* browser_context,
std::unique_ptr<OpenChannelParams> params,
const Extension* target_extension,
bool did_enqueue) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(target_extension != nullptr, !params->target_extension_id.empty());
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(params->source_process_id,
params->source_routing_id);
if (!source)
return; // Closed while in flight.
if (!params->receiver || !params->receiver->IsValidPort()) {
DispatchOnDisconnect(source, params->receiver_port_id,
kReceivingEndDoesntExistError);
return;
}
std::unique_ptr<ExtensionMessagePort> opener(
new ExtensionMessagePort(weak_factory_.GetWeakPtr(),
GET_OPPOSITE_PORT_ID(params->receiver_port_id),
params->source_extension_id, source, false));
if (!opener->IsValidPort())
return;
opener->OpenPort(params->source_process_id, params->source_routing_id);
opener->RevalidatePort();
params->receiver->RemoveCommonFrames(*opener);
if (!params->receiver->IsValidPort()) {
opener->DispatchOnDisconnect(kReceivingEndDoesntExistError);
return;
}
std::unique_ptr<MessageChannel> channel_ptr =
base::MakeUnique<MessageChannel>();
MessageChannel* channel = channel_ptr.get();
channel->opener.reset(opener.release());
channel->receiver.reset(params->receiver.release());
AddChannel(std::move(channel_ptr), params->receiver_port_id);
// TODO(robwu): Could |guest_process_id| and |guest_render_frame_routing_id|
// be removed? In the past extension message routing was process-based, but
// now that extensions are routed from a specific RFH, the special casing for
// guest views seems no longer necessary, because the ExtensionMessagePort can
// simply obtain the source process & frame ID directly from the RFH.
int guest_process_id = content::ChildProcessHost::kInvalidUniqueID;
int guest_render_frame_routing_id = MSG_ROUTING_NONE;
if (params->include_guest_process_info) {
guest_process_id = params->source_process_id;
guest_render_frame_routing_id = params->source_routing_id;
DCHECK(WebViewGuest::FromWebContents(
WebContents::FromRenderFrameHost(source)));
}
// Send the connect event to the receiver. Give it the opener's port ID (the
// opener has the opposite port ID).
channel->receiver->DispatchOnConnect(
params->channel_name, std::move(params->source_tab),
params->source_frame_id, guest_process_id, guest_render_frame_routing_id,
params->source_extension_id, params->target_extension_id,
params->source_url, params->tls_channel_id);
// Report the event to the event router, if the target is an extension.
//
// First, determine what event this will be (runtime.onConnect vs
// runtime.onMessage etc), and what the event target is (view vs background
// page etc).
//
// Yes - even though this is opening a channel, they may actually be
// runtime.onRequest/onMessage events because those single-use events are
// built using the connect framework (see messaging.js).
//
// Likewise, if you're wondering about native messaging events, these are
// only initiated *by* the extension, so aren't really events, just the
// endpoint of a communication channel.
if (target_extension) {
events::HistogramValue histogram_value = events::UNKNOWN;
bool is_external =
params->source_extension_id != params->target_extension_id;
if (params->channel_name == "chrome.runtime.onRequest") {
histogram_value = is_external ? events::RUNTIME_ON_REQUEST_EXTERNAL
: events::RUNTIME_ON_REQUEST;
} else if (params->channel_name == "chrome.runtime.onMessage") {
histogram_value = is_external ? events::RUNTIME_ON_MESSAGE_EXTERNAL
: events::RUNTIME_ON_MESSAGE;
} else {
histogram_value = is_external ? events::RUNTIME_ON_CONNECT_EXTERNAL
: events::RUNTIME_ON_CONNECT;
}
EventRouter::Get(browser_context)
->ReportEvent(histogram_value, target_extension, did_enqueue);
}
// Keep both ends of the channel alive until the channel is closed.
channel->opener->IncrementLazyKeepaliveCount();
channel->receiver->IncrementLazyKeepaliveCount();
}
void MessageService::AddChannel(std::unique_ptr<MessageChannel> channel,
int receiver_port_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int channel_id = GET_CHANNEL_ID(receiver_port_id);
CHECK(channels_.find(channel_id) == channels_.end());
channels_[channel_id] = std::move(channel);
pending_lazy_background_page_channels_.erase(channel_id);
}
void MessageService::OpenPort(int port_id, int process_id, int routing_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!IS_OPENER_PORT_ID(port_id));
int channel_id = GET_CHANNEL_ID(port_id);
MessageChannelMap::iterator it = channels_.find(channel_id);
if (it == channels_.end())
return;
it->second->receiver->OpenPort(process_id, routing_id);
}
void MessageService::ClosePort(
int port_id, int process_id, int routing_id, bool force_close) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ClosePortImpl(port_id, process_id, routing_id, force_close, std::string());
}
void MessageService::CloseChannel(int port_id,
const std::string& error_message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ClosePortImpl(port_id, content::ChildProcessHost::kInvalidUniqueID,
MSG_ROUTING_NONE, true, error_message);
}
void MessageService::ClosePortImpl(int port_id,
int process_id,
int routing_id,
bool force_close,
const std::string& error_message) {
// Note: The channel might be gone already, if the other side closed first.
int channel_id = GET_CHANNEL_ID(port_id);
MessageChannelMap::iterator it = channels_.find(channel_id);
if (it == channels_.end()) {
PendingLazyBackgroundPageChannelMap::iterator pending =
pending_lazy_background_page_channels_.find(channel_id);
if (pending != pending_lazy_background_page_channels_.end()) {
lazy_background_task_queue_->AddPendingTask(
pending->second.first, pending->second.second,
base::Bind(&MessageService::PendingLazyBackgroundPageClosePort,
weak_factory_.GetWeakPtr(), port_id, process_id,
routing_id, force_close, error_message));
}
return;
}
// The difference between closing a channel and port is that closing a port
// does not necessarily have to destroy the channel if there are multiple
// receivers, whereas closing a channel always forces all ports to be closed.
if (force_close) {
CloseChannelImpl(it, port_id, error_message, true);
} else if (IS_OPENER_PORT_ID(port_id)) {
it->second->opener->ClosePort(process_id, routing_id);
} else {
it->second->receiver->ClosePort(process_id, routing_id);
}
}
void MessageService::CloseChannelImpl(
MessageChannelMap::iterator channel_iter,
int closing_port_id,
const std::string& error_message,
bool notify_other_port) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::unique_ptr<MessageChannel> channel = std::move(channel_iter->second);
// Remove from map to make sure that it is impossible for CloseChannelImpl to
// run twice for the same channel.
channels_.erase(channel_iter);
// Notify the other side.
if (notify_other_port) {
MessagePort* port = IS_OPENER_PORT_ID(closing_port_id) ?
channel->receiver.get() : channel->opener.get();
port->DispatchOnDisconnect(error_message);
}
// Balance the IncrementLazyKeepaliveCount() in OpenChannelImpl.
channel->opener->DecrementLazyKeepaliveCount();
channel->receiver->DecrementLazyKeepaliveCount();
}
void MessageService::PostMessage(int source_port_id, const Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int channel_id = GET_CHANNEL_ID(source_port_id);
MessageChannelMap::iterator iter = channels_.find(channel_id);
if (iter == channels_.end()) {
// If this channel is pending, queue up the PostMessage to run once
// the channel opens.
EnqueuePendingMessage(source_port_id, channel_id, message);
return;
}
DispatchMessage(source_port_id, iter->second.get(), message);
}
void MessageService::EnqueuePendingMessage(int source_port_id,
int channel_id,
const Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PendingChannelMap::iterator pending_for_incognito =
pending_incognito_channels_.find(channel_id);
if (pending_for_incognito != pending_incognito_channels_.end()) {
pending_for_incognito->second.push_back(
PendingMessage(source_port_id, message));
// A channel should only be holding pending messages because it is in one
// of these states.
DCHECK(!base::ContainsKey(pending_tls_channel_id_channels_, channel_id));
DCHECK(
!base::ContainsKey(pending_lazy_background_page_channels_, channel_id));
return;
}
PendingChannelMap::iterator pending_for_tls_channel_id =
pending_tls_channel_id_channels_.find(channel_id);
if (pending_for_tls_channel_id != pending_tls_channel_id_channels_.end()) {
pending_for_tls_channel_id->second.push_back(
PendingMessage(source_port_id, message));
// A channel should only be holding pending messages because it is in one
// of these states.
DCHECK(
!base::ContainsKey(pending_lazy_background_page_channels_, channel_id));
return;
}
EnqueuePendingMessageForLazyBackgroundLoad(source_port_id,
channel_id,
message);
}
void MessageService::EnqueuePendingMessageForLazyBackgroundLoad(
int source_port_id,
int channel_id,
const Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PendingLazyBackgroundPageChannelMap::iterator pending =
pending_lazy_background_page_channels_.find(channel_id);
if (pending != pending_lazy_background_page_channels_.end()) {
lazy_background_task_queue_->AddPendingTask(
pending->second.first, pending->second.second,
base::Bind(&MessageService::PendingLazyBackgroundPagePostMessage,
weak_factory_.GetWeakPtr(), source_port_id, message));
}
}
void MessageService::DispatchMessage(int source_port_id,
MessageChannel* channel,
const Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Figure out which port the ID corresponds to.
int dest_port_id = GET_OPPOSITE_PORT_ID(source_port_id);
MessagePort* port = IS_OPENER_PORT_ID(dest_port_id) ?
channel->opener.get() : channel->receiver.get();
port->DispatchOnMessage(message);
}
bool MessageService::MaybeAddPendingLazyBackgroundPageOpenChannelTask(
BrowserContext* context,
const Extension* extension,
std::unique_ptr<OpenChannelParams>* params,
const PendingMessagesQueue& pending_messages) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!BackgroundInfo::HasLazyBackgroundPage(extension))
return false;
// If the extension uses spanning incognito mode, make sure we're always
// using the original profile since that is what the extension process
// will use.
if (!IncognitoInfo::IsSplitMode(extension))
context = ExtensionsBrowserClient::Get()->GetOriginalContext(context);
if (!lazy_background_task_queue_->ShouldEnqueueTask(context, extension))
return false;
int channel_id = GET_CHANNEL_ID((*params)->receiver_port_id);
pending_lazy_background_page_channels_[channel_id] =
PendingLazyBackgroundPageChannel(context, extension->id());
int source_id = (*params)->source_process_id;
lazy_background_task_queue_->AddPendingTask(
context, extension->id(),
base::Bind(&MessageService::PendingLazyBackgroundPageOpenChannel,
weak_factory_.GetWeakPtr(), base::Passed(params), source_id));
for (const PendingMessage& message : pending_messages) {
EnqueuePendingMessageForLazyBackgroundLoad(message.first, channel_id,
message.second);
}
return true;
}
void MessageService::OnOpenChannelAllowed(
std::unique_ptr<OpenChannelParams> params,
bool allowed) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int channel_id = GET_CHANNEL_ID(params->receiver_port_id);
PendingChannelMap::iterator pending_for_incognito =
pending_incognito_channels_.find(channel_id);
if (pending_for_incognito == pending_incognito_channels_.end()) {
NOTREACHED();
return;
}
PendingMessagesQueue pending_messages;
pending_messages.swap(pending_for_incognito->second);
pending_incognito_channels_.erase(pending_for_incognito);
// Re-lookup the source process since it may no longer be valid.
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(params->source_process_id,
params->source_routing_id);
if (!source) {
return;
}
if (!allowed) {
DispatchOnDisconnect(source, params->receiver_port_id,
kReceivingEndDoesntExistError);
return;
}
BrowserContext* context = source->GetProcess()->GetBrowserContext();
// Note: we use the source's profile here. If the source is an incognito
// process, we will use the incognito EPM to find the right extension process,
// which depends on whether the extension uses spanning or split mode.
if (content::RenderProcessHost* extension_process =
GetExtensionProcess(context, params->target_extension_id)) {
params->receiver.reset(
new ExtensionMessagePort(
weak_factory_.GetWeakPtr(), params->receiver_port_id,
params->target_extension_id, extension_process));
} else {
params->receiver.reset();
}
// If the target requests the TLS channel id, begin the lookup for it.
// The target might also be a lazy background page, checked next, but the
// loading of lazy background pages continues asynchronously, so enqueue
// messages awaiting TLS channel ID first.
if (params->include_tls_channel_id) {
// Transfer pending messages to the next pending channel list.
pending_tls_channel_id_channels_[channel_id].swap(pending_messages);
// Capture this reference before params is invalidated by base::Passed().
const GURL& source_url = params->source_url;
property_provider_.GetChannelID(
Profile::FromBrowserContext(context), source_url,
base::Bind(&MessageService::GotChannelID, weak_factory_.GetWeakPtr(),
base::Passed(&params)));
return;
}
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
const Extension* target_extension =
registry->enabled_extensions().GetByID(params->target_extension_id);
if (!target_extension) {
DispatchOnDisconnect(source, params->receiver_port_id,
kReceivingEndDoesntExistError);
return;
}
// The target might be a lazy background page. In that case, we have to check
// if it is loaded and ready, and if not, queue up the task and load the
// page.
if (!MaybeAddPendingLazyBackgroundPageOpenChannelTask(
context, target_extension, &params, pending_messages)) {
OpenChannelImpl(context, std::move(params), target_extension,
false /* did_enqueue */);
DispatchPendingMessages(pending_messages, channel_id);
}
}
void MessageService::GotChannelID(std::unique_ptr<OpenChannelParams> params,
const std::string& tls_channel_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
params->tls_channel_id.assign(tls_channel_id);
int channel_id = GET_CHANNEL_ID(params->receiver_port_id);
PendingChannelMap::iterator pending_for_tls_channel_id =
pending_tls_channel_id_channels_.find(channel_id);
if (pending_for_tls_channel_id == pending_tls_channel_id_channels_.end()) {
NOTREACHED();
return;
}
PendingMessagesQueue pending_messages;
pending_messages.swap(pending_for_tls_channel_id->second);
pending_tls_channel_id_channels_.erase(pending_for_tls_channel_id);
// Re-lookup the source process since it may no longer be valid.
content::RenderFrameHost* source =
content::RenderFrameHost::FromID(params->source_process_id,
params->source_routing_id);
if (!source) {
return;
}
BrowserContext* context = source->GetProcess()->GetBrowserContext();
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
const Extension* target_extension =
registry->enabled_extensions().GetByID(params->target_extension_id);
if (!target_extension) {
DispatchOnDisconnect(source, params->receiver_port_id,
kReceivingEndDoesntExistError);
return;
}
if (!MaybeAddPendingLazyBackgroundPageOpenChannelTask(
context, target_extension, &params, pending_messages)) {
OpenChannelImpl(context, std::move(params), target_extension,
false /* did_enqueue */);
DispatchPendingMessages(pending_messages, channel_id);
}
}
void MessageService::PendingLazyBackgroundPageOpenChannel(
std::unique_ptr<OpenChannelParams> params,
int source_process_id,
ExtensionHost* host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!host)
return; // TODO(mpcomplete): notify source of disconnect?
params->receiver.reset(
new ExtensionMessagePort(
weak_factory_.GetWeakPtr(), params->receiver_port_id,
params->target_extension_id, host->render_process_host()));
OpenChannelImpl(host->browser_context(), std::move(params), host->extension(),
true /* did_enqueue */);
}
void MessageService::DispatchOnDisconnect(content::RenderFrameHost* source,
int port_id,
const std::string& error_message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ExtensionMessagePort port(weak_factory_.GetWeakPtr(),
GET_OPPOSITE_PORT_ID(port_id), "", source, false);
if (!port.IsValidPort())
return;
port.DispatchOnDisconnect(error_message);
}
void MessageService::DispatchPendingMessages(const PendingMessagesQueue& queue,
int channel_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
MessageChannelMap::iterator channel_iter = channels_.find(channel_id);
if (channel_iter != channels_.end()) {
for (const PendingMessage& message : queue) {
DispatchMessage(message.first, channel_iter->second.get(),
message.second);
}
}
}
} // namespace extensions