| // Copyright (c) 2010 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/automation/extension_port_container.h" |
| |
| #include "base/logging.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/values.h" |
| #include "chrome/browser/automation/automation_provider.h" |
| #include "chrome/browser/automation/extension_automation_constants.h" |
| #include "chrome/browser/chrome_thread.h" |
| #include "chrome/browser/extensions/extension_message_service.h" |
| #include "chrome/browser/profile.h" |
| #include "chrome/browser/renderer_host/render_process_host.h" |
| #include "chrome/browser/renderer_host/render_view_host.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/test/automation/automation_messages.h" |
| |
| // TODO(siggi): Find a more structured way to read and write JSON messages. |
| |
| namespace ext = extension_automation_constants; |
| |
| ExtensionPortContainer::ExtensionPortContainer(AutomationProvider* automation, |
| int tab_handle) : |
| automation_(automation), service_(NULL), port_id_(-1), |
| tab_handle_(tab_handle) { |
| service_ = automation_->profile()->GetExtensionMessageService(); |
| DCHECK(service_); |
| } |
| |
| ExtensionPortContainer::~ExtensionPortContainer() { |
| DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| |
| if (port_id_ != -1) |
| service_->CloseChannel(port_id_); |
| |
| NotificationService::current()->Notify( |
| NotificationType::EXTENSION_PORT_DELETED_DEBUG, |
| Source<IPC::Message::Sender>(this), |
| NotificationService::NoDetails()); |
| } |
| |
| bool ExtensionPortContainer::PostResponseToExternalPort( |
| const std::string& message) { |
| return automation_->Send( |
| new AutomationMsg_ForwardMessageToExternalHost( |
| 0, tab_handle_, message, ext::kAutomationOrigin, |
| ext::kAutomationPortResponseTarget)); |
| } |
| |
| bool ExtensionPortContainer::PostMessageToExternalPort( |
| const std::string& message) { |
| return automation_->Send( |
| new AutomationMsg_ForwardMessageToExternalHost( |
| 0, tab_handle_, message, |
| ext::kAutomationOrigin, |
| ext::kAutomationPortRequestTarget)); |
| } |
| |
| void ExtensionPortContainer::PostMessageFromExternalPort( |
| const std::string &message) { |
| service_->PostMessageFromRenderer(port_id_, message); |
| } |
| |
| bool ExtensionPortContainer::Connect(const std::string &extension_id, |
| int process_id, |
| int routing_id, |
| int connection_id, |
| const std::string& channel_name, |
| const std::string& tab_json) { |
| DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| |
| port_id_ = service_->OpenSpecialChannelToExtension( |
| extension_id, channel_name, tab_json, this); |
| if (port_id_ == -1) { |
| // In this case a disconnect message has been dispatched. |
| return false; |
| } |
| |
| SendConnectionResponse(connection_id, port_id_); |
| return true; |
| } |
| |
| void ExtensionPortContainer::SendConnectionResponse(int connection_id, |
| int port_id) { |
| // Compose the reply message. |
| scoped_ptr<DictionaryValue> msg_dict(new DictionaryValue()); |
| msg_dict->SetInteger(ext::kAutomationRequestIdKey, ext::CHANNEL_OPENED); |
| msg_dict->SetInteger(ext::kAutomationConnectionIdKey, connection_id); |
| msg_dict->SetInteger(ext::kAutomationPortIdKey, port_id); |
| |
| std::string msg_json; |
| base::JSONWriter::Write(msg_dict.get(), false, &msg_json); |
| |
| PostResponseToExternalPort(msg_json); |
| } |
| |
| bool ExtensionPortContainer::Send(IPC::Message *message) { |
| DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| |
| IPC_BEGIN_MESSAGE_MAP(ExtensionPortContainer, *message) |
| IPC_MESSAGE_HANDLER(ViewMsg_ExtensionMessageInvoke, |
| OnExtensionMessageInvoke) |
| IPC_MESSAGE_UNHANDLED_ERROR() |
| IPC_END_MESSAGE_MAP() |
| |
| delete message; |
| return true; |
| } |
| |
| void ExtensionPortContainer::OnExtensionMessageInvoke( |
| const std::string& function_name, |
| const ListValue& args, |
| bool requires_incognito_access, |
| const GURL& event_url) { |
| if (function_name == ExtensionMessageService::kDispatchOnMessage) { |
| DCHECK_EQ(args.GetSize(), 2u); |
| |
| std::string message; |
| int source_port_id; |
| if (args.GetString(0, &message) && args.GetInteger(1, &source_port_id)) |
| OnExtensionHandleMessage(message, source_port_id); |
| } else if (function_name == ExtensionMessageService::kDispatchOnDisconnect) { |
| DCHECK_EQ(args.GetSize(), 1u); |
| int port_id; |
| if (args.GetInteger(0, &port_id)) |
| OnExtensionPortDisconnected(port_id); |
| } else if (function_name == ExtensionMessageService::kDispatchOnConnect) { |
| // Do nothing. |
| // TODO(siggi): implement |
| } else { |
| NOTREACHED() << function_name << " shouldn't be called."; |
| } |
| } |
| |
| void ExtensionPortContainer::OnExtensionHandleMessage( |
| const std::string& message, int source_port_id) { |
| // Compose the reply message and fire it away. |
| DictionaryValue msg_dict; |
| msg_dict.SetInteger(ext::kAutomationRequestIdKey, ext::POST_MESSAGE); |
| msg_dict.SetInteger(ext::kAutomationPortIdKey, port_id_); |
| msg_dict.SetString(ext::kAutomationMessageDataKey, message); |
| |
| std::string msg_json; |
| base::JSONWriter::Write(&msg_dict, false, &msg_json); |
| |
| PostMessageToExternalPort(msg_json); |
| } |
| |
| void ExtensionPortContainer::OnExtensionPortDisconnected(int source_port_id) { |
| // Compose the disconnect message and fire it away. |
| DictionaryValue msg_dict; |
| msg_dict.SetInteger(ext::kAutomationRequestIdKey, ext::CHANNEL_CLOSED); |
| msg_dict.SetInteger(ext::kAutomationPortIdKey, port_id_); |
| |
| std::string msg_json; |
| base::JSONWriter::Write(&msg_dict, false, &msg_json); |
| |
| PostMessageToExternalPort(msg_json); |
| } |
| |
| bool ExtensionPortContainer::InterceptMessageFromExternalHost( |
| const std::string& message, const std::string& origin, |
| const std::string& target, AutomationProvider* automation, |
| RenderViewHost *view_host, int tab_handle) { |
| if (target != ext::kAutomationPortRequestTarget) |
| return false; |
| |
| if (origin != ext::kAutomationOrigin) { |
| // TODO(siggi): Should we block the message on wrong origin? |
| LOG(WARNING) << "Wrong origin on automation port message " << origin; |
| } |
| |
| scoped_ptr<Value> message_value(base::JSONReader::Read(message, false)); |
| DCHECK(message_value->IsType(Value::TYPE_DICTIONARY)); |
| if (!message_value->IsType(Value::TYPE_DICTIONARY)) |
| return true; |
| |
| DictionaryValue* message_dict = |
| reinterpret_cast<DictionaryValue*>(message_value.get()); |
| |
| int command = -1; |
| bool got_value = message_dict->GetInteger(ext::kAutomationRequestIdKey, |
| &command); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| if (command == ext::OPEN_CHANNEL) { |
| // Extract the "extension_id" and "connection_id" parameters. |
| std::string extension_id; |
| got_value = message_dict->GetString(ext::kAutomationExtensionIdKey, |
| &extension_id); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| int connection_id; |
| got_value = message_dict->GetInteger(ext::kAutomationConnectionIdKey, |
| &connection_id); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| std::string channel_name; |
| // Channel name is optional. |
| message_dict->GetString(ext::kAutomationChannelNameKey, &channel_name); |
| |
| // Tab information is optional, try to retrieve it |
| // and re-flatten it to a string. |
| std::string tab_json("null"); |
| DictionaryValue* tab = NULL; |
| if (message_dict->GetDictionary(ext::kAutomationTabJsonKey, &tab)) |
| base::JSONWriter::Write(tab, false, &tab_json); |
| |
| int routing_id = view_host->routing_id(); |
| // Create the extension port and connect it. |
| scoped_ptr<ExtensionPortContainer> port( |
| new ExtensionPortContainer(automation, tab_handle)); |
| |
| int process_id = view_host->process()->id(); |
| if (port->Connect(extension_id, process_id, routing_id, connection_id, |
| channel_name, tab_json)) { |
| // We have a successful connection. |
| automation->AddPortContainer(port.release()); |
| } |
| } else if (command == ext::POST_MESSAGE) { |
| int port_id = -1; |
| got_value = message_dict->GetInteger(ext::kAutomationPortIdKey, &port_id); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| std::string data; |
| got_value = message_dict->GetString(ext::kAutomationMessageDataKey, &data); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| ExtensionPortContainer* port = automation->GetPortContainer(port_id); |
| DCHECK(port); |
| if (port) |
| port->PostMessageFromExternalPort(data); |
| } else if (command == ext::CHANNEL_CLOSED) { |
| int port_id = -1; |
| got_value = message_dict->GetInteger(ext::kAutomationPortIdKey, &port_id); |
| DCHECK(got_value); |
| if (!got_value) |
| return true; |
| |
| ExtensionPortContainer* port = automation->GetPortContainer(port_id); |
| DCHECK(port); |
| if (port) { |
| // This will delete the port and notify the other end of the disconnect. |
| automation->RemovePortContainer(port); |
| } |
| } else { |
| // We don't expect other messages here. |
| NOTREACHED(); |
| } |
| |
| return true; |
| } |