Commit 0b875676 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions Bindings] Add a OneTimeMessagingHandler

Add a OneTimeMessagingHandler to handle sending one-time messages (such
as those from chrome.runtime.sendMessage) with native bindings. These
messages open a message channel, similar to the long-lived connections
of chrome.runtime.connect(), but the channel is only open for a single
message-and-response cycle.

The OneTimeMessagingHandler is responsible for creating and maintaining
these message channels, and cleaning them up when complete.

Add tests for the same.

Note: currently, these constructs are still only used in tests. A later
CL will hook them up to the bindings system.

Bug: 653596
Change-Id: Ibeae3bda73b66306a85b13749f1cab1a1edfb33b
Reviewed-on: https://chromium-review.googlesource.com/675305
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarIstiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#506932}
parent 7e123266
...@@ -144,6 +144,8 @@ source_set("renderer") { ...@@ -144,6 +144,8 @@ source_set("renderer") {
"logging_native_handler.h", "logging_native_handler.h",
"messaging_bindings.cc", "messaging_bindings.cc",
"messaging_bindings.h", "messaging_bindings.h",
"messaging_util.cc",
"messaging_util.h",
"module_system.cc", "module_system.cc",
"module_system.h", "module_system.h",
"native_extension_bindings_system.cc", "native_extension_bindings_system.cc",
...@@ -154,6 +156,8 @@ source_set("renderer") { ...@@ -154,6 +156,8 @@ source_set("renderer") {
"native_renderer_messaging_service.h", "native_renderer_messaging_service.h",
"object_backed_native_handler.cc", "object_backed_native_handler.cc",
"object_backed_native_handler.h", "object_backed_native_handler.h",
"one_time_message_handler.cc",
"one_time_message_handler.h",
"process_info_native_handler.cc", "process_info_native_handler.cc",
"process_info_native_handler.h", "process_info_native_handler.h",
"programmatic_script_injector.cc", "programmatic_script_injector.cc",
...@@ -368,6 +372,7 @@ source_set("unit_tests") { ...@@ -368,6 +372,7 @@ source_set("unit_tests") {
"native_extension_bindings_system_unittest.cc", "native_extension_bindings_system_unittest.cc",
"native_extension_bindings_system_unittest.h", "native_extension_bindings_system_unittest.h",
"native_renderer_messaging_service_unittest.cc", "native_renderer_messaging_service_unittest.cc",
"one_time_message_handler_unittest.cc",
"safe_builtins_unittest.cc", "safe_builtins_unittest.cc",
"scoped_web_frame.cc", "scoped_web_frame.cc",
"scoped_web_frame.h", "scoped_web_frame.h",
......
...@@ -123,8 +123,7 @@ void APIRequestHandler::CompleteRequest(int request_id, ...@@ -123,8 +123,7 @@ void APIRequestHandler::CompleteRequest(int request_id,
if (iter == pending_requests_.end()) if (iter == pending_requests_.end())
return; return;
PendingRequest pending_request = std::move(iter->second); PendingRequest& pending_request = iter->second;
pending_requests_.erase(iter);
v8::Isolate* isolate = pending_request.isolate; v8::Isolate* isolate = pending_request.isolate;
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
...@@ -132,13 +131,40 @@ void APIRequestHandler::CompleteRequest(int request_id, ...@@ -132,13 +131,40 @@ void APIRequestHandler::CompleteRequest(int request_id,
v8::Context::Scope context_scope(context); v8::Context::Scope context_scope(context);
std::unique_ptr<content::V8ValueConverter> converter = std::unique_ptr<content::V8ValueConverter> converter =
content::V8ValueConverter::Create(); content::V8ValueConverter::Create();
std::vector<v8::Local<v8::Value>> v8_args;
v8_args.reserve(response_args.GetSize());
for (const auto& arg : response_args)
v8_args.push_back(converter->ToV8Value(&arg, context));
// NOTE(devlin): This results in a double lookup of the pending request and an
// extra Handle/Context-Scope, but that should be pretty cheap.
CompleteRequest(request_id, v8_args, error);
}
void APIRequestHandler::CompleteRequest(
int request_id,
const std::vector<v8::Local<v8::Value>>& response_args,
const std::string& error) {
auto iter = pending_requests_.find(request_id);
// The request may have been removed if the context was invalidated before a
// response is ready.
if (iter == pending_requests_.end())
return;
PendingRequest pending_request = std::move(iter->second);
pending_requests_.erase(iter);
v8::Isolate* isolate = pending_request.isolate;
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = pending_request.context.Get(isolate);
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args; std::vector<v8::Local<v8::Value>> args;
args.reserve(response_args.GetSize() + args.reserve(response_args.size() +
pending_request.callback_arguments.size()); pending_request.callback_arguments.size());
for (const auto& arg : pending_request.callback_arguments) for (const auto& arg : pending_request.callback_arguments)
args.push_back(arg.Get(isolate)); args.push_back(arg.Get(isolate));
for (const auto& arg : response_args) for (const auto& arg : response_args)
args.push_back(converter->ToV8Value(&arg, context)); args.push_back(arg);
blink::WebScopedUserGesture user_gesture(pending_request.user_gesture_token); blink::WebScopedUserGesture user_gesture(pending_request.user_gesture_token);
if (!error.empty()) if (!error.empty())
......
...@@ -82,6 +82,9 @@ class APIRequestHandler { ...@@ -82,6 +82,9 @@ class APIRequestHandler {
void CompleteRequest(int request_id, void CompleteRequest(int request_id,
const base::ListValue& response, const base::ListValue& response,
const std::string& error); const std::string& error);
void CompleteRequest(int request_id,
const std::vector<v8::Local<v8::Value>>& response,
const std::string& error);
// Invalidates any requests that are associated with |context|. // Invalidates any requests that are associated with |context|.
void InvalidateContext(v8::Local<v8::Context> context); void InvalidateContext(v8::Local<v8::Context> context);
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
#include "extensions/common/api/messaging/message.h" #include "extensions/common/api/messaging/message.h"
#include "extensions/renderer/bindings/api_event_handler.h" #include "extensions/renderer/bindings/api_event_handler.h"
#include "extensions/renderer/bindings/event_emitter.h" #include "extensions/renderer/bindings/event_emitter.h"
#include "extensions/renderer/messaging_util.h"
#include "gin/arguments.h" #include "gin/arguments.h"
#include "gin/converter.h" #include "gin/converter.h"
#include "gin/object_template_builder.h" #include "gin/object_template_builder.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
namespace extensions { namespace extensions {
...@@ -57,16 +57,13 @@ void GinPort::DispatchOnMessage(v8::Local<v8::Context> context, ...@@ -57,16 +57,13 @@ void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context); v8::Context::Scope context_scope(context);
v8::Local<v8::String> v8_message_string = v8::Local<v8::Value> parsed_message =
gin::StringToV8(isolate, message.data); messaging_util::MessageToV8(context, message);
v8::Local<v8::Value> parsed_message; if (parsed_message.IsEmpty()) {
{ NOTREACHED();
v8::TryCatch try_catch(isolate); return;
if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
NOTREACHED();
return;
}
} }
v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked(); v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
std::vector<v8::Local<v8::Value>> args = {parsed_message, self}; std::vector<v8::Local<v8::Value>> args = {parsed_message, self};
DispatchEvent(context, &args, kOnMessageEvent); DispatchEvent(context, &args, kOnMessageEvent);
...@@ -117,44 +114,15 @@ void GinPort::PostMessageHandler(gin::Arguments* arguments, ...@@ -117,44 +114,15 @@ void GinPort::PostMessageHandler(gin::Arguments* arguments,
return; return;
} }
// TODO(devlin): For some reason, we don't use the signature for std::unique_ptr<Message> message =
// Port.postMessage when evaluating the parameters. We probably should, but messaging_util::MessageFromV8(context, v8_message);
// we don't know how many extensions that may break. It would be good to if (!message) {
// investigate, and, ideally, use the signature.
if (v8_message->IsUndefined()) {
// JSON.stringify won't serialized undefined (it returns undefined), but it
// will serialized null. We've always converted undefined to null in JS
// bindings, so preserve this behavior for now.
v8_message = v8::Null(isolate);
}
bool success = false;
v8::Local<v8::String> stringified;
{
v8::TryCatch try_catch(isolate);
success = v8::JSON::Stringify(context, v8_message).ToLocal(&stringified);
}
std::string message;
if (success) {
message = gin::V8ToString(stringified);
// JSON.stringify can either fail (with unserializable objects) or can
// return undefined. If it returns undefined, the v8 API then coerces it to
// the string value "undefined". Throw an error if we were passed
// unserializable objects.
success = message != "undefined";
}
if (!success) {
ThrowError(isolate, "Illegal argument to Port.postMessage"); ThrowError(isolate, "Illegal argument to Port.postMessage");
return; return;
} }
delegate_->PostMessageToPort( delegate_->PostMessageToPort(context, port_id_, routing_id_,
context, port_id_, routing_id_, std::move(message));
std::make_unique<Message>(
message, blink::WebUserGestureIndicator::IsProcessingUserGesture()));
} }
std::string GinPort::GetName() { std::string GinPort::GetName() {
......
// Copyright 2017 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/renderer/messaging_util.h"
#include <string>
#include "base/logging.h"
#include "extensions/common/api/messaging/message.h"
#include "gin/converter.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
namespace extensions {
namespace messaging_util {
std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
v8::Local<v8::Value> value) {
DCHECK(!value.IsEmpty());
v8::Isolate* isolate = context->GetIsolate();
v8::Context::Scope context_scope(context);
// TODO(devlin): For some reason, we don't use the signature for
// Port.postMessage when evaluating the parameters. We probably should, but
// we don't know how many extensions that may break. It would be good to
// investigate, and, ideally, use the signature.
if (value->IsUndefined()) {
// JSON.stringify won't serialized undefined (it returns undefined), but it
// will serialized null. We've always converted undefined to null in JS
// bindings, so preserve this behavior for now.
value = v8::Null(isolate);
}
bool success = false;
v8::Local<v8::String> stringified;
{
v8::TryCatch try_catch(isolate);
success = v8::JSON::Stringify(context, value).ToLocal(&stringified);
}
std::string message;
if (success) {
message = gin::V8ToString(stringified);
// JSON.stringify can fail to produce a string value in one of two ways: it
// can throw an exception (as with unserializable objects), or it can return
// `undefined` (as with e.g. passing a function). If JSON.stringify returns
// `undefined`, the v8 API then coerces it to the string value "undefined".
// Check for this, and consider it a failure (since we didn't properly
// serialize a value).
success = message != "undefined";
}
if (!success)
return nullptr;
return std::make_unique<Message>(
message, blink::WebUserGestureIndicator::IsProcessingUserGesture());
}
v8::Local<v8::Value> MessageToV8(v8::Local<v8::Context> context,
const Message& message) {
v8::Isolate* isolate = context->GetIsolate();
v8::Context::Scope context_scope(context);
v8::Local<v8::String> v8_message_string =
gin::StringToV8(isolate, message.data);
v8::Local<v8::Value> parsed_message;
v8::TryCatch try_catch(isolate);
if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
NOTREACHED();
return v8::Local<v8::Value>();
}
return parsed_message;
}
} // namespace messaging_util
} // namespace extensions
// Copyright 2017 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.
#ifndef EXTENSIONS_RENDERER_MESSAGING_UTIL_H_
#define EXTENSIONS_RENDERER_MESSAGING_UTIL_H_
#include <memory>
#include "v8/include/v8.h"
namespace extensions {
struct Message;
namespace messaging_util {
// Parses the message from a v8 value, returning null on failure.
std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
v8::Local<v8::Value> value);
// Converts a message to a v8 value. This is expected not to fail, since it
// should only be used for messages that have been validated.
v8::Local<v8::Value> MessageToV8(v8::Local<v8::Context> context,
const Message& message);
} // namespace messaging_util
} // namespace extensions
#endif // EXTENSIONS_RENDERER_MESSAGING_UTIL_H_
...@@ -42,6 +42,12 @@ bool PropertyExists(v8::Local<v8::Context> context, ...@@ -42,6 +42,12 @@ bool PropertyExists(v8::Local<v8::Context> context,
TestIPCMessageSender::TestIPCMessageSender() {} TestIPCMessageSender::TestIPCMessageSender() {}
TestIPCMessageSender::~TestIPCMessageSender() {} TestIPCMessageSender::~TestIPCMessageSender() {}
void TestIPCMessageSender::SendRequestIPC(
ScriptContext* context,
std::unique_ptr<ExtensionHostMsg_Request_Params> params,
binding::RequestThread thread) {
last_params_ = std::move(params);
}
NativeExtensionBindingsSystemUnittest::NativeExtensionBindingsSystemUnittest() { NativeExtensionBindingsSystemUnittest::NativeExtensionBindingsSystemUnittest() {
} }
......
...@@ -47,9 +47,7 @@ class TestIPCMessageSender : public IPCMessageSender { ...@@ -47,9 +47,7 @@ class TestIPCMessageSender : public IPCMessageSender {
// IPCMessageSender: // IPCMessageSender:
void SendRequestIPC(ScriptContext* context, void SendRequestIPC(ScriptContext* context,
std::unique_ptr<ExtensionHostMsg_Request_Params> params, std::unique_ptr<ExtensionHostMsg_Request_Params> params,
binding::RequestThread thread) override { binding::RequestThread thread) override;
last_params_ = std::move(params);
}
void SendOnRequestResponseReceivedIPC(int request_id) override {} void SendOnRequestResponseReceivedIPC(int request_id) override {}
// The event listener methods are less of a pain to mock (since they don't // The event listener methods are less of a pain to mock (since they don't
// have complex parameters like ExtensionHostMsg_Request_Params). // have complex parameters like ExtensionHostMsg_Request_Params).
......
...@@ -72,7 +72,8 @@ bool ScriptContextIsValid(ScriptContext* script_context) { ...@@ -72,7 +72,8 @@ bool ScriptContextIsValid(ScriptContext* script_context) {
NativeRendererMessagingService::NativeRendererMessagingService( NativeRendererMessagingService::NativeRendererMessagingService(
NativeExtensionBindingsSystem* bindings_system) NativeExtensionBindingsSystem* bindings_system)
: RendererMessagingService(bindings_system), : RendererMessagingService(bindings_system),
bindings_system_(bindings_system) {} bindings_system_(bindings_system),
one_time_message_handler_(bindings_system) {}
NativeRendererMessagingService::~NativeRendererMessagingService() {} NativeRendererMessagingService::~NativeRendererMessagingService() {}
gin::Handle<GinPort> NativeRendererMessagingService::Connect( gin::Handle<GinPort> NativeRendererMessagingService::Connect(
...@@ -99,6 +100,27 @@ gin::Handle<GinPort> NativeRendererMessagingService::Connect( ...@@ -99,6 +100,27 @@ gin::Handle<GinPort> NativeRendererMessagingService::Connect(
return port; return port;
} }
void NativeRendererMessagingService::SendOneTimeMessage(
ScriptContext* script_context,
const std::string& target_id,
const std::string& method_name,
bool include_tls_channel_id,
const Message& message,
v8::Local<v8::Function> response_callback) {
if (!ScriptContextIsValid(script_context))
return;
MessagingPerContextData* data =
GetPerContextData(script_context->v8_context(), true);
bool is_opener = true;
PortId port_id(script_context->context_id(), data->next_port_id++, is_opener);
one_time_message_handler_.SendMessage(script_context, port_id, target_id,
method_name, include_tls_channel_id,
message, response_callback);
}
void NativeRendererMessagingService::PostMessageToPort( void NativeRendererMessagingService::PostMessageToPort(
v8::Local<v8::Context> context, v8::Local<v8::Context> context,
const PortId& port_id, const PortId& port_id,
...@@ -159,6 +181,8 @@ bool NativeRendererMessagingService::HasPortForTesting( ...@@ -159,6 +181,8 @@ bool NativeRendererMessagingService::HasPortForTesting(
bool NativeRendererMessagingService::ContextHasMessagePort( bool NativeRendererMessagingService::ContextHasMessagePort(
ScriptContext* script_context, ScriptContext* script_context,
const PortId& port_id) { const PortId& port_id) {
if (one_time_message_handler_.HasPort(script_context, port_id))
return true;
MessagingPerContextData* data = MessagingPerContextData* data =
GetPerContextData(script_context->v8_context(), false); GetPerContextData(script_context->v8_context(), false);
return data && base::ContainsKey(data->ports, port_id); return data && base::ContainsKey(data->ports, port_id);
...@@ -177,12 +201,6 @@ void NativeRendererMessagingService::DispatchOnConnectToListeners( ...@@ -177,12 +201,6 @@ void NativeRendererMessagingService::DispatchOnConnectToListeners(
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = script_context->v8_context(); v8::Local<v8::Context> v8_context = script_context->v8_context();
if (channel_name == "chrome.extension.sendRequest" ||
channel_name == "chrome.runtime.sendMessage") {
// TODO(devlin): Handle sendMessage/sendRequest.
return;
}
gin::DataObjectBuilder sender_builder(isolate); gin::DataObjectBuilder sender_builder(isolate);
if (!info.source_id.empty()) if (!info.source_id.empty())
sender_builder.Set("id", info.source_id); sender_builder.Set("id", info.source_id);
...@@ -212,7 +230,19 @@ void NativeRendererMessagingService::DispatchOnConnectToListeners( ...@@ -212,7 +230,19 @@ void NativeRendererMessagingService::DispatchOnConnectToListeners(
} }
} }
v8::Local<v8::Value> sender = sender_builder.Build(); v8::Local<v8::Object> sender = sender_builder.Build();
if (channel_name == "chrome.extension.sendRequest" ||
channel_name == "chrome.runtime.sendMessage") {
OneTimeMessageHandler::Event event =
channel_name == "chrome.extension.sendRequest"
? OneTimeMessageHandler::Event::ON_REQUEST
: OneTimeMessageHandler::Event::ON_MESSAGE;
one_time_message_handler_.AddReceiver(script_context, target_port_id,
sender, event);
return;
}
gin::Handle<GinPort> port = gin::Handle<GinPort> port =
CreatePort(script_context, channel_name, target_port_id); CreatePort(script_context, channel_name, target_port_id);
port->SetSender(v8_context, sender); port->SetSender(v8_context, sender);
...@@ -228,8 +258,11 @@ void NativeRendererMessagingService::DispatchOnMessageToListeners( ...@@ -228,8 +258,11 @@ void NativeRendererMessagingService::DispatchOnMessageToListeners(
v8::Isolate* isolate = script_context->isolate(); v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
// TODO(devlin): Handle special casing for the sendMessage/sendRequest if (one_time_message_handler_.DeliverMessage(script_context, message,
// versions. target_port_id)) {
return;
}
gin::Handle<GinPort> port = GetPort(script_context, target_port_id); gin::Handle<GinPort> port = GetPort(script_context, target_port_id);
DCHECK(!port.IsEmpty()); DCHECK(!port.IsEmpty());
...@@ -243,8 +276,11 @@ void NativeRendererMessagingService::DispatchOnDisconnectToListeners( ...@@ -243,8 +276,11 @@ void NativeRendererMessagingService::DispatchOnDisconnectToListeners(
v8::Isolate* isolate = script_context->isolate(); v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
// TODO(devlin): Handle special casing for the sendMessage/sendRequest if (one_time_message_handler_.Disconnect(script_context, port_id,
// versions. error_message)) {
return;
}
v8::Local<v8::Context> context = script_context->v8_context(); v8::Local<v8::Context> context = script_context->v8_context();
gin::Handle<GinPort> port = GetPort(script_context, port_id); gin::Handle<GinPort> port = GetPort(script_context, port_id);
DCHECK(!port.IsEmpty()); DCHECK(!port.IsEmpty());
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include <string> #include <string>
#include "base/macros.h" #include "base/macros.h"
#include "extensions/common/extension_id.h"
#include "extensions/renderer/gin_port.h" #include "extensions/renderer/gin_port.h"
#include "extensions/renderer/one_time_message_handler.h"
#include "extensions/renderer/renderer_messaging_service.h" #include "extensions/renderer/renderer_messaging_service.h"
#include "gin/handle.h" #include "gin/handle.h"
...@@ -69,6 +71,14 @@ class NativeRendererMessagingService : public RendererMessagingService, ...@@ -69,6 +71,14 @@ class NativeRendererMessagingService : public RendererMessagingService,
const std::string& name, const std::string& name,
bool include_tls_channel_id); bool include_tls_channel_id);
// Sends a one-time message, as is used by runtime.sendMessage.
void SendOneTimeMessage(ScriptContext* script_context,
const ExtensionId& target_id,
const std::string& channel_name,
bool include_tls_channel_id,
const Message& message,
v8::Local<v8::Function> response_callback);
// GinPort::Delegate: // GinPort::Delegate:
void PostMessageToPort(v8::Local<v8::Context> context, void PostMessageToPort(v8::Local<v8::Context> context,
const PortId& port_id, const PortId& port_id,
...@@ -119,6 +129,8 @@ class NativeRendererMessagingService : public RendererMessagingService, ...@@ -119,6 +129,8 @@ class NativeRendererMessagingService : public RendererMessagingService,
// The associated bindings system; guaranteed to outlive this object. // The associated bindings system; guaranteed to outlive this object.
NativeExtensionBindingsSystem* const bindings_system_; NativeExtensionBindingsSystem* const bindings_system_;
OneTimeMessageHandler one_time_message_handler_;
DISALLOW_COPY_AND_ASSIGN(NativeRendererMessagingService); DISALLOW_COPY_AND_ASSIGN(NativeRendererMessagingService);
}; };
......
...@@ -328,4 +328,109 @@ TEST_F(NativeRendererMessagingServiceTest, Connect) { ...@@ -328,4 +328,109 @@ TEST_F(NativeRendererMessagingServiceTest, Connect) {
EXPECT_FALSE(new_port->is_closed()); EXPECT_FALSE(new_port->is_closed());
} }
// Tests sending a one-time message through the messaging service. Note that
// this is more thoroughly tested in the OneTimeMessageHandler tests; this is
// just to ensure NativeRendererMessagingService correctly forwards the calls.
TEST_F(NativeRendererMessagingServiceTest, SendOneTimeMessage) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
const std::string kChannel = "chrome.runtime.sendMessage";
PortId port_id(script_context()->context_id(), 0, true);
const char kEchoArgs[] =
"(function() { this.replyArgs = Array.from(arguments); })";
v8::Local<v8::Function> response_callback =
FunctionFromString(context, kEchoArgs);
// Send a message and expect a reply. A new port should be created, and should
// remain open (waiting for the response).
const Message message("\"hi\"", false);
bool include_tls_channel_id = false;
EXPECT_CALL(
*ipc_message_sender(),
SendOpenChannelToExtension(script_context(), port_id, extension()->id(),
kChannel, include_tls_channel_id));
EXPECT_CALL(*ipc_message_sender(),
SendPostMessageToPort(MSG_ROUTING_NONE, port_id, message));
messaging_service()->SendOneTimeMessage(script_context(), extension()->id(),
kChannel, include_tls_channel_id,
message, response_callback);
::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
EXPECT_TRUE(
messaging_service()->HasPortForTesting(script_context(), port_id));
// Respond to the message. The response callback should be triggered, and the
// port should be closed.
EXPECT_CALL(*ipc_message_sender(),
SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
messaging_service()->DeliverMessage(*script_context_set(), port_id,
Message("\"reply\"", false), nullptr);
::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
EXPECT_EQ("[\"reply\"]", GetStringPropertyFromObject(context->Global(),
context, "replyArgs"));
EXPECT_FALSE(
messaging_service()->HasPortForTesting(script_context(), port_id));
}
// Tests receiving a one-time message through the messaging service. Note that
// this is more thoroughly tested in the OneTimeMessageHandler tests; this is
// just to ensure NativeRendererMessagingService correctly forwards the calls.
TEST_F(NativeRendererMessagingServiceTest, ReceiveOneTimeMessage) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
constexpr char kRegisterListener[] =
"(function() {\n"
" chrome.runtime.onMessage.addListener(\n"
" function(message, sender, reply) {\n"
" this.eventMessage = message;\n"
" reply({data: 'hi'});\n"
" });\n"
"})";
v8::Local<v8::Function> add_listener =
FunctionFromString(context, kRegisterListener);
RunFunctionOnGlobal(add_listener, context, 0, nullptr);
const std::string kChannel = "chrome.runtime.sendMessage";
base::UnguessableToken other_context_id = base::UnguessableToken::Create();
const PortId port_id(other_context_id, 0, false);
ExtensionMsg_TabConnectionInfo tab_connection_info;
tab_connection_info.frame_id = 0;
const int tab_id = 10;
GURL source_url("http://example.com");
tab_connection_info.tab.Swap(
DictionaryBuilder().Set("tabId", tab_id).Build().get());
ExtensionMsg_ExternalConnectionInfo external_connection_info;
external_connection_info.target_id = extension()->id();
external_connection_info.source_id = extension()->id();
external_connection_info.source_url = source_url;
external_connection_info.guest_process_id =
content::ChildProcessHost::kInvalidUniqueID;
external_connection_info.guest_render_frame_routing_id = 0;
// Open a receiver for the message.
EXPECT_CALL(*ipc_message_sender(),
SendOpenMessagePort(MSG_ROUTING_NONE, port_id));
messaging_service()->DispatchOnConnect(
*script_context_set(), port_id, kChannel, tab_connection_info,
external_connection_info, std::string(), nullptr);
::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
EXPECT_TRUE(
messaging_service()->HasPortForTesting(script_context(), port_id));
// Post the message to the receiver. The receiver should respond, and the
// port should close.
EXPECT_CALL(*ipc_message_sender(),
SendPostMessageToPort(MSG_ROUTING_NONE, port_id,
Message(R"({"data":"hi"})", false)));
EXPECT_CALL(*ipc_message_sender(),
SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
messaging_service()->DeliverMessage(*script_context_set(), port_id,
Message("\"message\"", false), nullptr);
::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
EXPECT_FALSE(
messaging_service()->HasPortForTesting(script_context(), port_id));
}
} // namespace extensions } // namespace extensions
This diff is collapsed.
// Copyright 2017 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.
#ifndef EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_
#define EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "v8/include/v8.h"
namespace gin {
class Arguments;
}
namespace extensions {
class NativeExtensionBindingsSystem;
class ScriptContext;
struct Message;
struct PortId;
// A class for handling one-time message communication, including
// runtime.sendMessage and extension.sendRequest. These methods use the same
// underlying architecture as long-lived port-based communications (like
// runtime.connect), but are exposed through a simpler API.
// A basic flow will be from an "opener" (the original sender) and a "receiver"
// (the event listener), which will be in two separate contexts (and potentially
// renderer processes). The flow is outlined below:
//
// chrome.runtime.sendMessage( // initiates the sendMessage flow, triggering
// // SendMessage().
// {foo: bar}, // The data sent with SendMessage().
// function() { ... }); // The response callback in SendMessage().
//
// This creates a new opener port in the context, and posts a message to it
// with the data. The browser then dispatches this to other renderers.
//
// In another context, we have:
// chrome.runtime.onMessage.addListener(function(message, sender, reply) {
// ...
// reply(...);
// });
//
// When the renderer receives the connection message, we will create a
// new receiver port in this context via AddReceiver().
// When the message comes in, we reply with DeliverMessage() to the receiver's
// port ID.
// If the receiver replies via the reply callback, it will send a new message
// back along the port to the browser. The browser then sends this message back
// to the opener's renderer, where it is delivered via DeliverMessage().
//
// This concludes the one-time message flow.
class OneTimeMessageHandler {
public:
explicit OneTimeMessageHandler(
NativeExtensionBindingsSystem* bindings_system);
~OneTimeMessageHandler();
// The event corresponding with the message.
enum class Event {
ON_MESSAGE, // For runtime.sendMessage and extension.sendMessage.
ON_REQUEST, // For the deprecated extension.sendRequest.
};
// Returns true if the given context has a port with the specified id.
bool HasPort(ScriptContext* script_context, const PortId& port_id);
// Initiates a flow to send a message from the given |script_context|.
void SendMessage(ScriptContext* script_context,
const PortId& new_port_id,
const std::string& target_id,
const std::string& method_name,
bool include_tls_channel_id,
const Message& message,
v8::Local<v8::Function> response_callback);
// Adds a receiving port port to the given |script_context| in preparation
// for receiving a message to post to the onMessage event.
void AddReceiver(ScriptContext* script_context,
const PortId& target_port_id,
v8::Local<v8::Object> sender,
Event event);
// Delivers a message to the port, either the event listener or in response
// to the sender, if one exists with the specified |target_port_id|. Returns
// true if a message was delivered (i.e., an open channel existed), and false
// otherwise.
bool DeliverMessage(ScriptContext* script_context,
const Message& message,
const PortId& target_port_id);
// Disconnects the port in the context, if one exists with the specified
// |target_port_id|. Returns true if a port was disconnected (i.e., an open
// channel existed), and false otherwise.
bool Disconnect(ScriptContext* script_context,
const PortId& port_id,
const std::string& error_message);
private:
// Helper methods to deliver a message to an opener/receiver.
bool DeliverMessageToReceiver(ScriptContext* script_context,
const Message& message,
const PortId& target_port_id);
bool DeliverReplyToOpener(ScriptContext* script_context,
const Message& message,
const PortId& target_port_id);
// Helper methods to disconnect an opener/receiver.
bool DisconnectReceiver(ScriptContext* script_context, const PortId& port_id);
bool DisconnectOpener(ScriptContext* script_context,
const PortId& port_id,
const std::string& error_message);
// Triggered when a receiver responds to a message.
void OnOneTimeMessageResponse(const PortId& port_id,
gin::Arguments* arguments);
// The associated bindings system. Outlives this object.
NativeExtensionBindingsSystem* const bindings_system_;
base::WeakPtrFactory<OneTimeMessageHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(OneTimeMessageHandler);
};
} // namespace extensions
#endif // EXTENSIONS_RENDERER_ONE_TIME_MESSAGE_HANDLER_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment