summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/message_service.cc
diff options
context:
space:
mode:
authorrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-25 17:40:49 +0000
committerrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-25 17:40:49 +0000
commit40404bcddf2a1d19906ab9beb3a4cb1fa4d90f87 (patch)
tree62d5a5d8073366c52975bdf3c54b0cef0729e807 /chrome/browser/extensions/message_service.cc
parentc4185460ea68a358320cca15cbe836e9dfb55138 (diff)
downloadchromium_src-40404bcddf2a1d19906ab9beb3a4cb1fa4d90f87.zip
chromium_src-40404bcddf2a1d19906ab9beb3a4cb1fa4d90f87.tar.gz
chromium_src-40404bcddf2a1d19906ab9beb3a4cb1fa4d90f87.tar.bz2
Moved ExtensionMessage* into extensions namespace
Moved ExtensionMessageBundle, ExtensionMessageService, and ExtensionMessageHandler into extensions namespace. Renamed appropriately. BUG=137298, 117261 Review URL: https://chromiumcodereview.appspot.com/10787002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148354 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions/message_service.cc')
-rw-r--r--chrome/browser/extensions/message_service.cc471
1 files changed, 471 insertions, 0 deletions
diff --git a/chrome/browser/extensions/message_service.cc b/chrome/browser/extensions/message_service.cc
new file mode 100644
index 0000000..8758c13
--- /dev/null
+++ b/chrome/browser/extensions/message_service.cc
@@ -0,0 +1,471 @@
+// 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/message_service.h"
+
+#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/lazy_background_task_queue.h"
+#include "chrome/browser/extensions/process_map.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/tab_contents/tab_util.h"
+#include "chrome/browser/ui/tab_contents/tab_contents.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/view_type.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+
+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 {
+
+struct MessageService::MessagePort {
+ content::RenderProcessHost* process;
+ int routing_id;
+ std::string extension_id;
+ void* background_host_ptr; // used in IncrementLazyKeepaliveCount
+
+ MessagePort()
+ : process(NULL),
+ routing_id(MSG_ROUTING_CONTROL),
+ background_host_ptr(NULL) {}
+ MessagePort(content::RenderProcessHost* process,
+ int routing_id,
+ const std::string& extension_id)
+ : process(process),
+ routing_id(routing_id),
+ extension_id(extension_id),
+ background_host_ptr(NULL) {}
+};
+
+struct MessageService::MessageChannel {
+ MessageService::MessagePort opener;
+ MessageService::MessagePort receiver;
+};
+
+struct MessageService::OpenChannelParams {
+ content::RenderProcessHost* source;
+ std::string tab_json;
+ MessagePort receiver;
+ int receiver_port_id;
+ std::string source_extension_id;
+ std::string target_extension_id;
+ std::string channel_name;
+
+ OpenChannelParams(content::RenderProcessHost* source,
+ const std::string& tab_json,
+ const MessagePort& receiver,
+ int receiver_port_id,
+ const std::string& source_extension_id,
+ const std::string& target_extension_id,
+ const std::string& channel_name)
+ : source(source),
+ tab_json(tab_json),
+ receiver(receiver),
+ receiver_port_id(receiver_port_id),
+ source_extension_id(source_extension_id),
+ target_extension_id(target_extension_id),
+ channel_name(channel_name) {}
+};
+
+namespace {
+
+static base::StaticAtomicSequenceNumber g_next_channel_id;
+
+static void DispatchOnConnect(const MessageService::MessagePort& port,
+ int dest_port_id,
+ const std::string& channel_name,
+ const std::string& tab_json,
+ const std::string& source_extension_id,
+ const std::string& target_extension_id) {
+ port.process->Send(new ExtensionMsg_DispatchOnConnect(
+ port.routing_id, dest_port_id, channel_name,
+ tab_json, source_extension_id, target_extension_id));
+}
+
+static void DispatchOnDisconnect(const MessageService::MessagePort& port,
+ int source_port_id,
+ bool connection_error) {
+ port.process->Send(new ExtensionMsg_DispatchOnDisconnect(
+ port.routing_id, source_port_id, connection_error));
+}
+
+static void DispatchOnMessage(const MessageService::MessagePort& port,
+ const std::string& message,
+ int target_port_id) {
+ port.process->Send(new ExtensionMsg_DeliverMessage(
+ port.routing_id, target_port_id, message));
+}
+
+static content::RenderProcessHost* GetExtensionProcess(
+ Profile* profile, const std::string& extension_id) {
+ SiteInstance* site_instance =
+ profile->GetExtensionProcessManager()->GetSiteInstanceForURL(
+ Extension::GetBaseURLFromExtensionId(extension_id));
+
+ if (!site_instance->HasProcess())
+ return NULL;
+
+ return site_instance->GetProcess();
+}
+
+static void IncrementLazyKeepaliveCount(MessageService::MessagePort* port) {
+ Profile* profile =
+ Profile::FromBrowserContext(port->process->GetBrowserContext());
+ ExtensionProcessManager* pm =
+ ExtensionSystem::Get(profile)->process_manager();
+ ExtensionHost* host = pm->GetBackgroundHostForExtension(port->extension_id);
+ if (host && host->extension()->has_lazy_background_page())
+ pm->IncrementLazyKeepaliveCount(host->extension());
+
+ // Keep track of the background host, so when we decrement, we only do so if
+ // the host hasn't reloaded.
+ port->background_host_ptr = host;
+}
+
+static void DecrementLazyKeepaliveCount(MessageService::MessagePort* port) {
+ Profile* profile =
+ Profile::FromBrowserContext(port->process->GetBrowserContext());
+ ExtensionProcessManager* pm =
+ ExtensionSystem::Get(profile)->process_manager();
+ ExtensionHost* host = pm->GetBackgroundHostForExtension(port->extension_id);
+ if (host && host == port->background_host_ptr)
+ pm->DecrementLazyKeepaliveCount(host->extension());
+}
+
+} // namespace
+
+// static
+void MessageService::AllocatePortIdPair(int* port1, int* port2) {
+ int channel_id = g_next_channel_id.GetNext();
+ int port1_id = channel_id * 2;
+ int 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(GET_OPPOSITE_PORT_ID(port1_id) == port2_id);
+ DCHECK(GET_OPPOSITE_PORT_ID(port2_id) == port1_id);
+ DCHECK(GET_CHANNEL_ID(port1_id) == GET_CHANNEL_ID(port2_id));
+ DCHECK(GET_CHANNEL_ID(port1_id) == channel_id);
+ DCHECK(GET_CHANNEL_OPENER_ID(channel_id) == port1_id);
+ DCHECK(GET_CHANNEL_RECEIVERS_ID(channel_id) == port2_id);
+
+ *port1 = port1_id;
+ *port2 = port2_id;
+}
+
+MessageService::MessageService(
+ LazyBackgroundTaskQueue* queue)
+ : lazy_background_task_queue_(queue) {
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
+ content::NotificationService::AllBrowserContextsAndSources());
+}
+
+MessageService::~MessageService() {
+ STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
+ channels_.clear();
+}
+
+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 std::string& channel_name) {
+ content::RenderProcessHost* source =
+ content::RenderProcessHost::FromID(source_process_id);
+ if (!source)
+ return;
+ Profile* profile = Profile::FromBrowserContext(source->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.
+ MessagePort receiver(GetExtensionProcess(profile, target_extension_id),
+ MSG_ROUTING_CONTROL,
+ target_extension_id);
+ WebContents* source_contents = tab_util::GetWebContentsByID(
+ source_process_id, source_routing_id);
+
+ // Include info about the opener's tab (if it was a tab).
+ std::string tab_json = "null";
+ if (source_contents) {
+ scoped_ptr<DictionaryValue> tab_value(
+ ExtensionTabUtil::CreateTabValue(source_contents));
+ base::JSONWriter::Write(tab_value.get(), &tab_json);
+ }
+
+ OpenChannelParams params(source, tab_json, receiver, receiver_port_id,
+ source_extension_id, target_extension_id,
+ channel_name);
+
+ // 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 (MaybeAddPendingOpenChannelTask(profile, params))
+ return;
+
+ OpenChannelImpl(params);
+}
+
+void MessageService::OpenChannelToTab(
+ int source_process_id, int source_routing_id, int receiver_port_id,
+ int tab_id, const std::string& extension_id,
+ const std::string& channel_name) {
+ content::RenderProcessHost* source =
+ content::RenderProcessHost::FromID(source_process_id);
+ if (!source)
+ return;
+ Profile* profile = Profile::FromBrowserContext(source->GetBrowserContext());
+
+ TabContents* contents = NULL;
+ MessagePort receiver;
+ if (ExtensionTabUtil::GetTabById(tab_id, profile, true,
+ NULL, NULL, &contents, NULL)) {
+ receiver.process = contents->web_contents()->GetRenderProcessHost();
+ receiver.routing_id =
+ contents->web_contents()->GetRenderViewHost()->GetRoutingID();
+ receiver.extension_id = extension_id;
+ }
+
+ if (contents && contents->web_contents()->GetController().NeedsReload()) {
+ // The tab isn't loaded yet. Don't attempt to connect. Treat this as a
+ // disconnect.
+ DispatchOnDisconnect(MessagePort(source, MSG_ROUTING_CONTROL, extension_id),
+ GET_OPPOSITE_PORT_ID(receiver_port_id), true);
+ return;
+ }
+
+ WebContents* source_contents = tab_util::GetWebContentsByID(
+ source_process_id, source_routing_id);
+
+ // Include info about the opener's tab (if it was a tab).
+ std::string tab_json = "null";
+ if (source_contents) {
+ scoped_ptr<DictionaryValue> tab_value(
+ ExtensionTabUtil::CreateTabValue(source_contents));
+ base::JSONWriter::Write(tab_value.get(), &tab_json);
+ }
+
+ OpenChannelParams params(source, tab_json, receiver, receiver_port_id,
+ extension_id, extension_id, channel_name);
+ OpenChannelImpl(params);
+}
+
+bool MessageService::OpenChannelImpl(const OpenChannelParams& params) {
+ if (!params.source)
+ return false; // Closed while in flight.
+
+ if (!params.receiver.process) {
+ // Treat it as a disconnect.
+ DispatchOnDisconnect(MessagePort(params.source, MSG_ROUTING_CONTROL, ""),
+ GET_OPPOSITE_PORT_ID(params.receiver_port_id), true);
+ return false;
+ }
+
+ // Add extra paranoid CHECKs, since we have crash reports of this being NULL.
+ // http://code.google.com/p/chromium/issues/detail?id=19067
+ CHECK(params.receiver.process);
+
+ MessageChannel* channel(new MessageChannel);
+ channel->opener = MessagePort(params.source, MSG_ROUTING_CONTROL,
+ params.source_extension_id);
+ channel->receiver = params.receiver;
+
+ CHECK(params.receiver.process);
+
+ int channel_id = GET_CHANNEL_ID(params.receiver_port_id);
+ CHECK(channels_.find(channel_id) == channels_.end());
+ channels_[channel_id] = channel;
+ pending_channels_.erase(channel_id);
+
+ CHECK(params.receiver.process);
+
+ // Send the connect event to the receiver. Give it the opener's port ID (the
+ // opener has the opposite port ID).
+ DispatchOnConnect(params.receiver, params.receiver_port_id,
+ params.channel_name, params.tab_json,
+ params.source_extension_id, params.target_extension_id);
+
+ // Keep both ends of the channel alive until the channel is closed.
+ IncrementLazyKeepaliveCount(&channel->opener);
+ IncrementLazyKeepaliveCount(&channel->receiver);
+ return true;
+}
+
+void MessageService::CloseChannel(int port_id, bool connection_error) {
+ // 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()) {
+ PendingChannelMap::iterator pending = pending_channels_.find(channel_id);
+ if (pending != pending_channels_.end()) {
+ lazy_background_task_queue_->AddPendingTask(
+ pending->second.first, pending->second.second,
+ base::Bind(&MessageService::PendingCloseChannel,
+ base::Unretained(this), port_id, connection_error));
+ }
+ return;
+ }
+ CloseChannelImpl(it, port_id, connection_error, true);
+}
+
+void MessageService::CloseChannelImpl(
+ MessageChannelMap::iterator channel_iter, int closing_port_id,
+ bool connection_error, bool notify_other_port) {
+ MessageChannel* channel = channel_iter->second;
+
+ // Notify the other side.
+ if (notify_other_port) {
+ const MessagePort& port = IS_OPENER_PORT_ID(closing_port_id) ?
+ channel->receiver : channel->opener;
+ DispatchOnDisconnect(port, GET_OPPOSITE_PORT_ID(closing_port_id),
+ connection_error);
+ }
+
+ // Balance the addrefs in OpenChannelImpl.
+ DecrementLazyKeepaliveCount(&channel->opener);
+ DecrementLazyKeepaliveCount(&channel->receiver);
+
+ delete channel_iter->second;
+ channels_.erase(channel_iter);
+}
+
+void MessageService::PostMessageFromRenderer(
+ int source_port_id, const std::string& message) {
+ 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.
+ PendingChannelMap::iterator pending = pending_channels_.find(channel_id);
+ if (pending != pending_channels_.end()) {
+ lazy_background_task_queue_->AddPendingTask(
+ pending->second.first, pending->second.second,
+ base::Bind(&MessageService::PendingPostMessage,
+ base::Unretained(this), source_port_id, message));
+ }
+ return;
+ }
+
+ // Figure out which port the ID corresponds to.
+ int dest_port_id = GET_OPPOSITE_PORT_ID(source_port_id);
+ const MessagePort& port = IS_OPENER_PORT_ID(dest_port_id) ?
+ iter->second->opener : iter->second->receiver;
+
+ DispatchOnMessage(port, message, dest_port_id);
+}
+
+void MessageService::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
+ case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: {
+ content::RenderProcessHost* renderer =
+ content::Source<content::RenderProcessHost>(source).ptr();
+ OnProcessClosed(renderer);
+ break;
+ }
+ default:
+ NOTREACHED();
+ return;
+ }
+}
+
+void MessageService::OnProcessClosed(content::RenderProcessHost* process) {
+ // Close any channels that share this renderer. We notify the opposite
+ // port that his pair has closed.
+ for (MessageChannelMap::iterator it = channels_.begin();
+ it != channels_.end(); ) {
+ MessageChannelMap::iterator current = it++;
+ // If both sides are the same renderer, and it is closing, there is no
+ // "other" port, so there's no need to notify it.
+ bool notify_other_port =
+ current->second->opener.process != current->second->receiver.process;
+
+ if (current->second->opener.process == process) {
+ CloseChannelImpl(current, GET_CHANNEL_OPENER_ID(current->first),
+ false, notify_other_port);
+ } else if (current->second->receiver.process == process) {
+ CloseChannelImpl(current, GET_CHANNEL_RECEIVERS_ID(current->first),
+ false, notify_other_port);
+ }
+ }
+}
+
+bool MessageService::MaybeAddPendingOpenChannelTask(
+ Profile* profile,
+ const OpenChannelParams& params) {
+ ExtensionService* service = profile->GetExtensionService();
+ const std::string& extension_id = params.target_extension_id;
+ const Extension* extension = service->extensions()->GetByID(
+ extension_id);
+ if (extension && extension->has_lazy_background_page()) {
+ // 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 (!extension->incognito_split_mode())
+ profile = profile->GetOriginalProfile();
+
+ if (lazy_background_task_queue_->ShouldEnqueueTask(profile, extension)) {
+ lazy_background_task_queue_->AddPendingTask(profile, extension_id,
+ base::Bind(&MessageService::PendingOpenChannel,
+ base::Unretained(this), params, params.source->GetID()));
+ pending_channels_[GET_CHANNEL_ID(params.receiver_port_id)] =
+ PendingChannel(profile, extension_id);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void MessageService::PendingOpenChannel(const OpenChannelParams& params_in,
+ int source_process_id,
+ ExtensionHost* host) {
+ if (!host)
+ return; // TODO(mpcomplete): notify source of disconnect?
+
+ // Re-lookup the source process since it may no longer be valid.
+ OpenChannelParams params = params_in;
+ params.source = content::RenderProcessHost::FromID(source_process_id);
+ if (!params.source)
+ return;
+
+ params.receiver = MessagePort(host->render_process_host(),
+ MSG_ROUTING_CONTROL,
+ params.target_extension_id);
+ OpenChannelImpl(params);
+}
+
+} // namespace extensions