123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- // 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/one_time_message_handler.h"
- #include <memory>
- #include "base/run_loop.h"
- #include "base/strings/stringprintf.h"
- #include "extensions/common/api/messaging/message.h"
- #include "extensions/common/api/messaging/port_id.h"
- #include "extensions/common/api/messaging/serialization_format.h"
- #include "extensions/common/extension.h"
- #include "extensions/common/extension_builder.h"
- #include "extensions/renderer/bindings/api_binding_test_util.h"
- #include "extensions/renderer/bindings/api_binding_types.h"
- #include "extensions/renderer/bindings/api_bindings_system.h"
- #include "extensions/renderer/bindings/api_request_handler.h"
- #include "extensions/renderer/message_target.h"
- #include "extensions/renderer/messaging_util.h"
- #include "extensions/renderer/native_extension_bindings_system.h"
- #include "extensions/renderer/native_extension_bindings_system_test_base.h"
- #include "extensions/renderer/script_context.h"
- #include "gin/data_object_builder.h"
- #include "ipc/ipc_message.h"
- namespace extensions {
- namespace {
- constexpr char kEchoArgsAndError[] =
- "(function() {\n"
- " this.replyArgs = Array.from(arguments);\n"
- " this.lastError =\n"
- " chrome.runtime.lastError ?\n"
- " chrome.runtime.lastError.message : undefined;\n"
- "})";
- } // namespace
- class OneTimeMessageHandlerTest : public NativeExtensionBindingsSystemUnittest {
- public:
- OneTimeMessageHandlerTest() {}
- OneTimeMessageHandlerTest(const OneTimeMessageHandlerTest&) = delete;
- OneTimeMessageHandlerTest& operator=(const OneTimeMessageHandlerTest&) =
- delete;
- ~OneTimeMessageHandlerTest() override {}
- void SetUp() override {
- NativeExtensionBindingsSystemUnittest::SetUp();
- message_handler_ =
- std::make_unique<OneTimeMessageHandler>(bindings_system());
- extension_ = ExtensionBuilder("foo").Build();
- RegisterExtension(extension_);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- script_context_ = CreateScriptContext(context, extension_.get(),
- Feature::BLESSED_EXTENSION_CONTEXT);
- script_context_->set_url(extension_->url());
- bindings_system()->UpdateBindingsForContext(script_context_);
- }
- void TearDown() override {
- script_context_ = nullptr;
- extension_ = nullptr;
- message_handler_.reset();
- NativeExtensionBindingsSystemUnittest::TearDown();
- }
- bool UseStrictIPCMessageSender() override { return true; }
- std::string GetGlobalProperty(v8::Local<v8::Context> context,
- base::StringPiece property) {
- return GetStringPropertyFromObject(context->Global(), context, property);
- }
- OneTimeMessageHandler* message_handler() { return message_handler_.get(); }
- ScriptContext* script_context() { return script_context_; }
- const Extension* extension() { return extension_.get(); }
- private:
- std::unique_ptr<OneTimeMessageHandler> message_handler_;
- ScriptContext* script_context_ = nullptr;
- scoped_refptr<const Extension> extension_;
- };
- // Tests sending a message without expecting a reply, as in
- // chrome.runtime.sendMessage({foo: 'bar'});
- TEST_F(OneTimeMessageHandlerTest, SendMessageAndDontExpectReply) {
- const PortId port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message message("\"Hello\"", SerializationFormat::kJson, false);
- v8::HandleScope handle_scope(isolate());
- // We should open a message port, send a message, and then close it
- // immediately.
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(), SendPostMessageToPort(port_id, message));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
- message_handler()->SendMessage(
- script_context(), port_id, target, messaging_util::kSendMessageChannel,
- message, binding::AsyncResponseType::kNone, v8::Local<v8::Function>());
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- }
- // Tests sending a message and expecting a callback reply, as in
- // chrome.runtime.sendMessage({foo: 'bar'}, function(reply) { ... });
- TEST_F(OneTimeMessageHandlerTest, SendMessageAndExpectCallbackReply) {
- const PortId port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message message("\"Hello\"", SerializationFormat::kJson, false);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- v8::Local<v8::Function> callback =
- FunctionFromString(context, kEchoArgsAndError);
- APIRequestHandler* request_handler =
- bindings_system()->api_system()->request_handler();
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- // We should open a message port and send a message, and the message port
- // should remain open (to allow for a reply).
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(), SendPostMessageToPort(port_id, message));
- message_handler()->SendMessage(
- script_context(), port_id, target, messaging_util::kSendMessageChannel,
- message, binding::AsyncResponseType::kCallback, callback);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- // We should have added a pending request to the APIRequestHandler, but
- // shouldn't yet have triggered the reply callback.
- EXPECT_FALSE(request_handler->GetPendingRequestIdsForTesting().empty());
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "replyArgs"));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "lastError"));
- // Deliver the reply; the message port should close.
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
- const Message reply("\"Hi\"", SerializationFormat::kJson, false);
- message_handler()->DeliverMessage(script_context(), reply, port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- // And the callback should have been triggered, completing the request.
- EXPECT_EQ("[\"Hi\"]", GetGlobalProperty(context, "replyArgs"));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "lastError"));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- }
- // Tests sending a message and expecting a promise reply, as in
- // promise = chrome.runtime.sendMessage({foo: 'bar'});
- TEST_F(OneTimeMessageHandlerTest, SendMessageAndExpectPromiseReply) {
- const PortId port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message message("\"Hello\"", SerializationFormat::kJson, false);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- APIRequestHandler* request_handler =
- bindings_system()->api_system()->request_handler();
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- // We should open a message port and send a message, and the message port
- // should remain open (to allow for a reply).
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(), SendPostMessageToPort(port_id, message));
- v8::Local<v8::Promise> promise = message_handler()->SendMessage(
- script_context(), port_id, target, messaging_util::kSendMessageChannel,
- message, binding::AsyncResponseType::kPromise, v8::Local<v8::Function>());
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- ASSERT_FALSE(promise.IsEmpty());
- // We should have added a pending request to the APIRequestHandler, but
- // shouldn't yet have fulfilled the related promise.
- EXPECT_FALSE(request_handler->GetPendingRequestIdsForTesting().empty());
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- // Deliver the reply; the message port should close.
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
- const Message reply("\"Hi\"", SerializationFormat::kJson, false);
- message_handler()->DeliverMessage(script_context(), reply, port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- // And the callback should have been triggered, completing the request.
- EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
- EXPECT_EQ("\"Hi\"", V8ToString(promise->Result(), context));
- EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
- }
- // Tests disconnecting an opener (initiator of a sendMessage() call) when using
- // callbacks. This can happen when no receiving end exists (i.e., no listener to
- // runtime.onMessage).
- TEST_F(OneTimeMessageHandlerTest, DisconnectOpenerCallback) {
- const PortId port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message message("\"Hello\"", SerializationFormat::kJson, false);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- v8::Local<v8::Function> callback =
- FunctionFromString(context, kEchoArgsAndError);
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(), SendPostMessageToPort(port_id, message));
- message_handler()->SendMessage(
- script_context(), port_id, target, messaging_util::kSendMessageChannel,
- message, binding::AsyncResponseType::kCallback, callback);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_EQ("undefined", GetGlobalProperty(context, "replyArgs"));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "lastError"));
- // Disconnect the opener with an error. The callback should be triggered, and
- // the port should be removed. chrome.runtime.lastError should have been
- // populated.
- message_handler()->Disconnect(script_context(), port_id, "No receiving end");
- EXPECT_EQ("[]", GetGlobalProperty(context, "replyArgs"));
- EXPECT_EQ("\"No receiving end\"", GetGlobalProperty(context, "lastError"));
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- }
- // Tests disconnecting an opener (initiator of a sendMessage() call) when using
- // promises. This can happen when no receiving end exists (i.e., no listener to
- // runtime.onMessage).
- TEST_F(OneTimeMessageHandlerTest, DisconnectOpenerPromise) {
- const PortId port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message message("\"Hello\"", SerializationFormat::kJson, false);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(), SendPostMessageToPort(port_id, message));
- v8::Local<v8::Promise> promise = message_handler()->SendMessage(
- script_context(), port_id, target, messaging_util::kSendMessageChannel,
- message, binding::AsyncResponseType::kPromise, v8::Local<v8::Function>());
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- // Disconnect the opener with an error. The promise should be rejected with
- // the error and the port should be removed.
- message_handler()->Disconnect(script_context(), port_id, "No receiving end");
- EXPECT_EQ(v8::Promise::kRejected, promise->State());
- ASSERT_TRUE(promise->Result()->IsNativeError());
- EXPECT_EQ("\"No receiving end\"",
- GetStringPropertyFromObject(promise->Result().As<v8::Object>(),
- context, "message"));
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- }
- // Tests delivering a message to a receiver and not replying, as in
- // chrome.runtime.onMessage.addListener(function(message, sender, reply) {
- // ...
- // });
- TEST_F(OneTimeMessageHandlerTest, DeliverMessageToReceiverWithNoReply) {
- 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"
- " this.eventSender = sender;\n"
- " return true; // Reply later\n"
- " });\n"
- "})";
- v8::Local<v8::Function> add_listener =
- FunctionFromString(context, kRegisterListener);
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- EXPECT_EQ("undefined", GetGlobalProperty(context, "eventMessage"));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "eventSender"));
- base::UnguessableToken other_context_id = base::UnguessableToken::Create();
- const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- v8::Local<v8::Object> sender =
- gin::DataObjectBuilder(isolate())
- .Set("origin", std::string("https://example.com"))
- .Build();
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "eventMessage"));
- EXPECT_EQ("undefined", GetGlobalProperty(context, "eventSender"));
- EXPECT_CALL(*ipc_message_sender(),
- SendMessageResponsePending(MSG_ROUTING_NONE, port_id));
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- message_handler()->DeliverMessage(script_context(), message, port_id);
- EXPECT_EQ("\"Hi\"", GetGlobalProperty(context, "eventMessage"));
- EXPECT_EQ(R"({"origin":"https://example.com"})",
- GetGlobalProperty(context, "eventSender"));
- // TODO(devlin): Right now, the port lives eternally. In JS bindings, we have
- // two ways of dealing with this:
- // - monitoring the lifetime of the reply object
- // - requiring the extension to return true from an onMessage handler
- // We should implement these and test lifetime.
- }
- // Tests delivering a message to a receiver and replying, as in
- // chrome.runtime.onMessage.addListener(function(message, sender, reply) {
- // reply('foo');
- // });
- TEST_F(OneTimeMessageHandlerTest, DeliverMessageToReceiverAndReply) {
- 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"
- " reply({data: 'hey'});\n"
- " });\n"
- "})";
- v8::Local<v8::Function> add_listener =
- FunctionFromString(context, kRegisterListener);
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- base::UnguessableToken other_context_id = base::UnguessableToken::Create();
- const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- v8::Local<v8::Object> sender = v8::Object::New(isolate());
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- // When the listener replies, we should post the reply to the message port and
- // close the channel.
- EXPECT_CALL(*ipc_message_sender(),
- SendPostMessageToPort(
- port_id, Message(R"({"data":"hey"})",
- SerializationFormat::kJson, false)));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
- message_handler()->DeliverMessage(script_context(), message, port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- }
- // Tests that nothing breaks when trying to call the reply callback multiple
- // times.
- TEST_F(OneTimeMessageHandlerTest, TryReplyingMultipleTimes) {
- 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.sendReply = reply;\n"
- " return true; // Reply later\n"
- " });\n"
- "})";
- v8::Local<v8::Function> add_listener =
- FunctionFromString(context, kRegisterListener);
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- base::UnguessableToken other_context_id = base::UnguessableToken::Create();
- const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
- v8::Local<v8::Object> sender = v8::Object::New(isolate());
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- EXPECT_CALL(*ipc_message_sender(),
- SendMessageResponsePending(MSG_ROUTING_NONE, port_id));
- message_handler()->DeliverMessage(script_context(), message, port_id);
- v8::Local<v8::Value> reply =
- GetPropertyFromObject(context->Global(), context, "sendReply");
- ASSERT_FALSE(reply.IsEmpty());
- ASSERT_TRUE(reply->IsFunction());
- v8::Local<v8::Value> reply_arg = V8ValueFromScriptSource(context, "'hi'");
- v8::Local<v8::Value> args[] = {reply_arg};
- EXPECT_CALL(
- *ipc_message_sender(),
- SendPostMessageToPort(
- port_id, Message("\"hi\"", SerializationFormat::kJson, false)));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
- RunFunction(reply.As<v8::Function>(), context, std::size(args), args);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- // Running the reply function a second time shouldn't do anything.
- // TODO(devlin): Add an error message.
- RunFunction(reply.As<v8::Function>(), context, std::size(args), args);
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- }
- // Test starting a new sendMessage call from a sendMessage listener.
- TEST_F(OneTimeMessageHandlerTest, SendMessageInListener) {
- 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"
- " chrome.runtime.sendMessage('foo', function() {});\n"
- " });\n"
- "})";
- v8::Local<v8::Function> add_listener =
- FunctionFromString(context, kRegisterListener);
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- base::UnguessableToken sender_context_id = base::UnguessableToken::Create();
- const PortId original_port_id(sender_context_id, 0, false,
- SerializationFormat::kJson);
- v8::Local<v8::Object> sender = v8::Object::New(isolate());
- message_handler()->AddReceiver(script_context(), original_port_id, sender,
- messaging_util::kOnMessageEvent);
- // On delivering the message, we expect the listener to open a new message
- // channel by using sendMessage(). The original message channel will be
- // closed.
- const PortId listener_created_port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message listener_sent_message("\"foo\"", SerializationFormat::kJson,
- false);
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(
- *ipc_message_sender(),
- SendOpenMessageChannel(script_context(), listener_created_port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(
- *ipc_message_sender(),
- SendPostMessageToPort(listener_created_port_id, listener_sent_message));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, original_port_id, false));
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- message_handler()->DeliverMessage(script_context(), message,
- original_port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- }
- // Test using sendMessage from the reply to a sendMessage call.
- TEST_F(OneTimeMessageHandlerTest, SendMessageInCallback) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- constexpr char kSendMessage[] =
- "(function() {\n"
- " chrome.runtime.sendMessage(\n"
- " 'foo',\n"
- " function(reply) {\n"
- " chrome.runtime.sendMessage('bar', function() {});\n"
- " });\n"
- "})";
- v8::Local<v8::Function> send_message =
- FunctionFromString(context, kSendMessage);
- // Running the function should send one message ('foo'), which will wait for
- // a reply.
- const PortId original_port_id(script_context()->context_id(), 0, true,
- SerializationFormat::kJson);
- const Message original_message("\"foo\"", SerializationFormat::kJson, false);
- MessageTarget target(MessageTarget::ForExtension(extension()->id()));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), original_port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(*ipc_message_sender(),
- SendPostMessageToPort(original_port_id, original_message));
- RunFunctionOnGlobal(send_message, context, 0, nullptr);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- // Upon delivering the reply to the sender, it should send a second message
- // ('bar'). The original message channel should be closed.
- const PortId new_port_id(script_context()->context_id(), 1, true,
- SerializationFormat::kJson);
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), new_port_id, target,
- messaging_util::kSendMessageChannel));
- EXPECT_CALL(
- *ipc_message_sender(),
- SendPostMessageToPort(
- new_port_id, Message("\"bar\"", SerializationFormat::kJson, false)));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, original_port_id, true));
- const Message reply("\"reply\"", SerializationFormat::kJson, false);
- message_handler()->DeliverMessage(script_context(), reply, original_port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- }
- TEST_F(OneTimeMessageHandlerTest, ResponseCallbackGarbageCollected) {
- 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"
- " return true; // Reply later\n"
- " });\n"
- "})";
- v8::Local<v8::Function> add_listener =
- FunctionFromString(context, kRegisterListener);
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- base::UnguessableToken other_context_id = base::UnguessableToken::Create();
- const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
- v8::Local<v8::Object> sender = v8::Object::New(isolate());
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- EXPECT_CALL(*ipc_message_sender(),
- SendMessageResponsePending(MSG_ROUTING_NONE, port_id));
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, false));
- message_handler()->DeliverMessage(script_context(), message, port_id);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_EQ(
- 1, message_handler()->GetPendingCallbackCountForTest(script_context()));
- // The listener didn't retain the reply callback, so it should be garbage
- // collected and the related pending callback should have been cleared.
- RunGarbageCollection();
- base::RunLoop().RunUntilIdle();
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_EQ(
- 0, message_handler()->GetPendingCallbackCountForTest(script_context()));
- }
- // runtime.onMessage requires that a listener return `true` if they intend to
- // respond to the message asynchronously. Verify that we close the port if no
- // listener does so.
- TEST_F(OneTimeMessageHandlerTest, ChannelClosedIfTrueNotReturned) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- auto register_listener = [context](const char* listener) {
- constexpr char kRegisterListenerTemplate[] =
- "(function() { chrome.runtime.onMessage.addListener(%s); })";
- v8::Local<v8::Function> add_listener = FunctionFromString(
- context, base::StringPrintf(kRegisterListenerTemplate, listener));
- RunFunctionOnGlobal(add_listener, context, 0, nullptr);
- };
- register_listener("function(message, reply, sender) { }");
- // Add a listener that returns a truthy value, but not `true`.
- register_listener("function(message, reply, sender) { return {}; }");
- // Add a listener that throws an error.
- register_listener(
- "function(message, reply, sender) { throw new Error('hi!'); }");
- base::UnguessableToken other_context_id = base::UnguessableToken::Create();
- const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
- v8::Local<v8::Object> sender = v8::Object::New(isolate());
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- TestJSRunner::AllowErrors allow_errors;
- // Dispatch the message. Since none of these listeners return `true`, the port
- // should close.
- const Message message("\"Hi\"", SerializationFormat::kJson, false);
- EXPECT_CALL(*ipc_message_sender(),
- SendCloseMessagePort(MSG_ROUTING_NONE, port_id, false));
- message_handler()->DeliverMessage(script_context(), message, port_id);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
- // If any of the listeners return `true`, the channel should be left open.
- register_listener("function(message, reply, sender) { return true; }");
- message_handler()->AddReceiver(script_context(), port_id, sender,
- messaging_util::kOnMessageEvent);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- EXPECT_CALL(*ipc_message_sender(),
- SendMessageResponsePending(MSG_ROUTING_NONE, port_id));
- message_handler()->DeliverMessage(script_context(), message, port_id);
- EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
- }
- } // namespace extensions
|