[Extension] Fix sendMessage emitting unhelpful errors for promises
This CL changes how errors are handled when message ports for
OneTimeMessageHandler listeners are closed before they are able to
reply, when they had indicated they intended to response
asynchronously. What this means for extensions is sendMessage() calls
will more accurately report errors for the receivers not returning an
expected delayed response vs not receiving an expected immediate
response. This also resolves a problem where calling sendMessage with
no callback argument would report an error if the listener didn't
respond, due to how expected responses were being treated for promise
based calls.
These changes are achieved by having the receiver's renderer send a
newly introduced IPC to the browser if one of the receivers indicated
that it intends to respond asynchronously. Then if the associated port
closes unexpectedly, an explicit error message describing this is sent
back to the opener's renderer.
Additionally the opener keeps track if the message port is being used
for a promise based call, as the default behavior for the port being
closed before a non-asynchronous response is received differs slightly
from the callback based case and does not need to report a default
error.
Tests have also been added to more thoroughly document and test these
behaviors.
Bug: 1304272
Change-Id: If507fdb364e6a1929defea3a6a8fcd3bb1cc4636
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3526376
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Reviewed-by: Greg Kerr <kerrnel@chromium.org>
Commit-Queue: Tim <tjudkins@chromium.org>
Cr-Commit-Position: refs/heads/main@{#991650}
diff --git a/chrome/browser/extensions/api/messaging/messaging_apitest.cc b/chrome/browser/extensions/api/messaging/messaging_apitest.cc
index 558a349..aa7ceb0 100644
--- a/chrome/browser/extensions/api/messaging/messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/messaging_apitest.cc
@@ -189,6 +189,75 @@
EXPECT_TRUE(catcher.GetNextResult());
}
+// Tests sendMessage cases where the listener gets disconnected before it is
+// able to reply with a message it said it would send. This is achieved by
+// closing the page the listener is registered on.
+IN_PROC_BROWSER_TEST_F(MessagingApiTest, SendMessageDisconnect) {
+ static constexpr char kManifest[] = R"(
+ {
+ "name": "sendMessageDisconnect",
+ "version": "1.0",
+ "manifest_version": 3,
+ "background": {
+ "service_worker": "test.js",
+ "type": "module"
+ }
+ })";
+
+ static constexpr char kListenerPage[] = R"(
+ <script src="listener.js"></script>
+ )";
+ static constexpr char kListenerJS[] = R"(
+ var sendResponseCallback;
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ // Store the callback and return true to indicate we intend to respond
+ // with it later. We store the callback because the port would be closed
+ // automatically if it is garbage collected.
+ sendResponseCallback = sendResponse;
+
+ // Have the page close itself after a short delay to trigger the
+ // disconnect.
+ setTimeout(window.close, 0);
+ return true;
+ });
+ )";
+ static constexpr char kTestJS[] = R"(
+ import {openTab} from '/_test_resources/test_util/tabs_util.js';
+ let expectedError = 'A listener indicated an asynchronous response by ' +
+ 'returning true, but the message channel closed before a response ' +
+ 'was received';
+ chrome.test.runTests([
+ async function sendMessageWithCallbackExpectingUnsentAsyncResponse() {
+ // Open the page which has the listener.
+ let tab = await openTab(chrome.runtime.getURL('listener.html'));
+ chrome.tabs.sendMessage(tab.id, 'async_true', (response) => {
+ chrome.test.assertLastError(expectedError);
+ chrome.test.succeed();
+ });
+ },
+
+ async function sendMessageWithPromiseExpectingUnsentAsyncResponse() {
+ // Open the page which has the listener.
+ let tab = await openTab(chrome.runtime.getURL('listener.html'));
+ chrome.runtime.sendMessage('async_true').then(() => {
+ chrome.test.fail('Message unexpectedly succeeded');
+ }).catch((error) => {
+ chrome.test.assertEq(expectedError, error.message);
+ chrome.test.succeed();
+ });
+ },
+ ]);
+ )";
+
+ TestExtensionDir dir;
+ dir.WriteManifest(kManifest);
+ dir.WriteFile(FILE_PATH_LITERAL("listener.html"), kListenerPage);
+ dir.WriteFile(FILE_PATH_LITERAL("listener.js"), kListenerJS);
+ dir.WriteFile(FILE_PATH_LITERAL("test.js"), kTestJS);
+
+ ASSERT_TRUE(RunExtensionTest(dir.UnpackedPath(), {}, {}));
+}
+
// Tests that message passing from one extension to another works.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingExternal) {
ASSERT_TRUE(LoadExtension(
diff --git a/chrome/test/data/extensions/api_test/messaging/connect_crash/test.js b/chrome/test/data/extensions/api_test/messaging/connect_crash/test.js
index 409c7b12..890ef83 100644
--- a/chrome/test/data/extensions/api_test/messaging/connect_crash/test.js
+++ b/chrome/test/data/extensions/api_test/messaging/connect_crash/test.js
@@ -22,7 +22,8 @@
chrome.tabs.sendMessage(port.sender.tab.id, 'Rob says hi', function() {
chrome.test.log('tab.sendMessage\'s response callback was invoked');
chrome.test.assertLastError(
- 'The message port closed before a response was received.');
+ 'A listener indicated an asynchronous response by returning true, ' +
+ 'but the message channel closed before a response was received');
succeed2();
});
});
diff --git a/chrome/test/data/extensions/api_test/runtime/send_message/test.js b/chrome/test/data/extensions/api_test/runtime/send_message/test.js
index 4e6e1798..fa0b7d35 100644
--- a/chrome/test/data/extensions/api_test/runtime/send_message/test.js
+++ b/chrome/test/data/extensions/api_test/runtime/send_message/test.js
@@ -11,9 +11,43 @@
});
},
+ function sendMessageWithCallbackAndNoResponse() {
+ chrome.runtime.sendMessage('no_response', (response) => {
+ chrome.test.assertLastError(
+ 'The message port closed before a response was received.');
+ chrome.test.succeed();
+ });
+ },
+
+ function sendMessageWithCallbackExpectingAsyncReply() {
+ chrome.runtime.sendMessage('async_true', (response) => {
+ chrome.test.assertEq('async_reply', response);
+ chrome.test.assertNoLastError();
+ chrome.test.succeed();
+ });
+ // After a short delay, send another message which should cause the stored
+ // response for the previous sendMessage call to be called.
+ setTimeout(chrome.runtime.sendMessage, 0, 'send_async_reply');
+ },
+
async function sendMessageWithPromise() {
const response = await chrome.runtime.sendMessage('ping');
chrome.test.assertEq('pong', response);
chrome.test.succeed();
- }
+ },
+
+ async function sendMessageWithPromiseAndNoResponse() {
+ await chrome.runtime.sendMessage('no_response');
+ chrome.test.succeed();
+ },
+
+ async function sendMessageWithPromiseExpectingAsyncReply() {
+ chrome.runtime.sendMessage('async_true').then((response) => {
+ chrome.test.assertEq('async_reply', response);
+ chrome.test.succeed();
+ });
+ // After a short delay, send another message which should cause the stored
+ // response for the previous sendMessage call to be called.
+ setTimeout(chrome.runtime.sendMessage, 0, 'send_async_reply');
+ },
]);
diff --git a/chrome/test/data/extensions/api_test/runtime/send_message/worker.js b/chrome/test/data/extensions/api_test/runtime/send_message/worker.js
index a3d17a8..d5240ec 100644
--- a/chrome/test/data/extensions/api_test/runtime/send_message/worker.js
+++ b/chrome/test/data/extensions/api_test/runtime/send_message/worker.js
@@ -2,10 +2,29 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+var asyncResponseCallback;
+
// A simple onMessage listener we can send a "ping" message to and get a "pong"
// message back.
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
- const response =
- request == 'ping' ? 'pong' : 'Unexpected message: ${request}';
- sendResponse(response);
+ switch (request) {
+ case 'ping':
+ // Simple case of receiving a message and replying with a response.
+ sendResponse('pong');
+ break;
+ case 'no_response':
+ // Case for receiving a message and not replying or indicating there will
+ // be an asynchronous response.
+ break;
+ case 'async_true':
+ // Case where true is returned to indicate we intend to respond
+ // asynchronously.
+ asyncResponseCallback = sendResponse;
+ return true;
+ case 'send_async_reply':
+ asyncResponseCallback('async_reply');
+ break;
+ default:
+ sendResponse('Unexpected message: ${request}');
+ }
});
diff --git a/extensions/browser/api/messaging/extension_message_port.cc b/extensions/browser/api/messaging/extension_message_port.cc
index e1c9ac3..e60ad834 100644
--- a/extensions/browser/api/messaging/extension_message_port.cc
+++ b/extensions/browser/api/messaging/extension_message_port.cc
@@ -43,6 +43,9 @@
const char kReceivingEndDoesntExistError[] =
// TODO(lazyboy): Test these in service worker implementation.
"Could not establish connection. Receiving end does not exist.";
+const char kClosedWhileResponsePendingError[] =
+ "A listener indicated an asynchronous response by returning true, but the "
+ "message channel closed before a response was received";
} // namespace
@@ -310,6 +313,9 @@
}
void ExtensionMessagePort::DispatchOnMessage(const Message& message) {
+ // Since we are now receicing a message, we can mark any asynchronous reply
+ // that may have been pending for this port as no longer pending.
+ asynchronous_reply_pending_ = false;
SendToPort(base::BindRepeating(&ExtensionMessagePort::BuildDeliverMessageIPC,
// Called synchronously.
base::Unretained(this), message));
@@ -360,6 +366,10 @@
}
}
+void ExtensionMessagePort::NotifyResponsePending() {
+ asynchronous_reply_pending_ = true;
+}
+
void ExtensionMessagePort::OpenPort(int process_id,
const PortContext& port_context) {
DCHECK((port_context.is_for_render_frame() &&
@@ -368,7 +378,7 @@
port_context.worker->thread_id != kMainThreadId) ||
for_all_extension_contexts_);
- did_create_port_ = true;
+ port_was_created_ = true;
}
void ExtensionMessagePort::ClosePort(int process_id,
@@ -386,8 +396,12 @@
}
void ExtensionMessagePort::CloseChannel() {
- std::string error_message = did_create_port_ ? std::string() :
- kReceivingEndDoesntExistError;
+ std::string error_message;
+ if (!port_was_created_)
+ error_message = kReceivingEndDoesntExistError;
+ else if (asynchronous_reply_pending_)
+ error_message = kClosedWhileResponsePendingError;
+
if (weak_channel_delegate_)
weak_channel_delegate_->CloseChannel(port_id_, error_message);
}
diff --git a/extensions/browser/api/messaging/extension_message_port.h b/extensions/browser/api/messaging/extension_message_port.h
index 5631880..f3c37ebe 100644
--- a/extensions/browser/api/messaging/extension_message_port.h
+++ b/extensions/browser/api/messaging/extension_message_port.h
@@ -98,6 +98,7 @@
void DecrementLazyKeepaliveCount() override;
void OpenPort(int process_id, const PortContext& port_context) override;
void ClosePort(int process_id, int routing_id, int worker_thread_id) override;
+ void NotifyResponsePending() override;
private:
class FrameTracker;
@@ -176,7 +177,17 @@
// Whether the renderer acknowledged creation of the port. This is used to
// distinguish abnormal port closure (e.g. no receivers) from explicit port
// closure (e.g. by the port.disconnect() JavaScript method in the renderer).
- bool did_create_port_ = false;
+ bool port_was_created_ = false;
+
+ // Whether one of the receivers has indicated that it will respond later and
+ // the opener should be expecting that response. Used to determine if we
+ // should notify the opener of a message port being closed before an expected
+ // response was received. By default this is assumed to be false until one of
+ // the receivers notifies us otherwise.
+ // Note: this is currently only relevant for messaging using
+ // OneTimeMessageHandlers, where the receivers are able to indicate they are
+ // going to respond asynchronously.
+ bool asynchronous_reply_pending_ = false;
// Used in IncrementLazyKeepaliveCount
raw_ptr<ExtensionHost> background_host_ptr_ = nullptr;
diff --git a/extensions/browser/api/messaging/message_port.cc b/extensions/browser/api/messaging/message_port.cc
index d62f2f4e..f39c701 100644
--- a/extensions/browser/api/messaging/message_port.cc
+++ b/extensions/browser/api/messaging/message_port.cc
@@ -42,4 +42,6 @@
void MessagePort::DecrementLazyKeepaliveCount() {}
+void MessagePort::NotifyResponsePending() {}
+
} // namespace extensions
diff --git a/extensions/browser/api/messaging/message_port.h b/extensions/browser/api/messaging/message_port.h
index 96f7ef1..25dba800 100644
--- a/extensions/browser/api/messaging/message_port.h
+++ b/extensions/browser/api/messaging/message_port.h
@@ -94,6 +94,10 @@
virtual void IncrementLazyKeepaliveCount(bool is_for_native_message_connect);
virtual void DecrementLazyKeepaliveCount();
+ // Notifies the message port that one of the receivers intents to respond
+ // later.
+ virtual void NotifyResponsePending();
+
protected:
MessagePort();
};
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 33f084b6..0e7956d 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -870,6 +870,20 @@
dest_port->DispatchOnMessage(message);
}
+void MessageService::NotifyResponsePending(const PortId& port_id,
+ int process_id,
+ const PortContext& port_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!port_id.is_opener);
+
+ ChannelId channel_id = port_id.GetChannelId();
+ auto it = channels_.find(channel_id);
+ if (it == channels_.end())
+ return;
+
+ it->second->receiver->NotifyResponsePending();
+}
+
bool MessageService::MaybeAddPendingLazyContextOpenChannelTask(
BrowserContext* context,
const Extension* extension,
diff --git a/extensions/browser/api/messaging/message_service.h b/extensions/browser/api/messaging/message_service.h
index 3f2001c1..7e8720c 100644
--- a/extensions/browser/api/messaging/message_service.h
+++ b/extensions/browser/api/messaging/message_service.h
@@ -122,6 +122,12 @@
const PortContext& port_context,
bool force_close);
+ // Notifies the port that one of the receivers of a message indicated that
+ // they plan to respond to the message later.
+ void NotifyResponsePending(const PortId& port_id,
+ int process_id,
+ const PortContext& port_context);
+
// Returns the number of open channels for test.
size_t GetChannelCountForTest() { return channels_.size(); }
diff --git a/extensions/browser/api/messaging/messaging_api_message_filter.cc b/extensions/browser/api/messaging/messaging_api_message_filter.cc
index abd3f6b..5469f5e 100644
--- a/extensions/browser/api/messaging/messaging_api_message_filter.cc
+++ b/extensions/browser/api/messaging/messaging_api_message_filter.cc
@@ -237,6 +237,7 @@
case ExtensionHostMsg_OpenMessagePort::ID:
case ExtensionHostMsg_CloseMessagePort::ID:
case ExtensionHostMsg_PostMessage::ID:
+ case ExtensionHostMsg_ResponsePending::ID:
*thread = BrowserThread::UI;
break;
default:
@@ -259,6 +260,7 @@
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenMessagePort, OnOpenMessagePort)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_CloseMessagePort, OnCloseMessagePort)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_PostMessage, OnPostMessage)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_ResponsePending, OnResponsePending)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -366,4 +368,15 @@
MessageService::Get(browser_context_)->PostMessage(port_id, message);
}
+void MessagingAPIMessageFilter::OnResponsePending(
+ const PortContext& port_context,
+ const PortId& port_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ MessageService::Get(browser_context_)
+ ->NotifyResponsePending(port_id, render_process_id_, port_context);
+}
+
} // namespace extensions
diff --git a/extensions/browser/api/messaging/messaging_api_message_filter.h b/extensions/browser/api/messaging/messaging_api_message_filter.h
index 58ccd3e..6a0ccd6 100644
--- a/extensions/browser/api/messaging/messaging_api_message_filter.h
+++ b/extensions/browser/api/messaging/messaging_api_message_filter.h
@@ -65,6 +65,8 @@
bool force_close);
void OnPostMessage(const extensions::PortId& port_id,
const extensions::Message& message);
+ void OnResponsePending(const PortContext& port_context,
+ const PortId& port_id);
const int render_process_id_;
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index a0f81060..5791dd8 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -364,6 +364,13 @@
extensions::PortId /* port_id */,
extensions::Message)
+// Send a message to tell the browser that one of the listeners for a message
+// indicated they are intending to reply later. The handle is the value returned
+// by ExtensionHostMsg_OpenChannelTo*.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_ResponsePending,
+ extensions::PortContext /* port_context */,
+ extensions::PortId /*port_id */)
+
// Used to get the extension message bundle.
IPC_SYNC_MESSAGE_CONTROL1_1(
ExtensionHostMsg_GetMessageBundle,
diff --git a/extensions/renderer/ipc_message_sender.cc b/extensions/renderer/ipc_message_sender.cc
index d2e1f57e..7c8c794 100644
--- a/extensions/renderer/ipc_message_sender.cc
+++ b/extensions/renderer/ipc_message_sender.cc
@@ -199,6 +199,12 @@
render_thread_->Send(new ExtensionHostMsg_PostMessage(port_id, message));
}
+ void SendMessageResponsePending(int routing_id,
+ const PortId& port_id) override {
+ render_thread_->Send(new ExtensionHostMsg_ResponsePending(
+ PortContext::ForFrame(routing_id), port_id));
+ }
+
void SendActivityLogIPC(
const ExtensionId& extension_id,
ActivityLogCallType call_type,
@@ -421,6 +427,13 @@
dispatcher_->Send(new ExtensionHostMsg_PostMessage(port_id, message));
}
+ void SendMessageResponsePending(int routing_id,
+ const PortId& port_id) override {
+ DCHECK_EQ(MSG_ROUTING_NONE, routing_id);
+ dispatcher_->Send(new ExtensionHostMsg_ResponsePending(
+ PortContextForCurrentWorker(), port_id));
+ }
+
void SendActivityLogIPC(
const ExtensionId& extension_id,
ActivityLogCallType call_type,
diff --git a/extensions/renderer/ipc_message_sender.h b/extensions/renderer/ipc_message_sender.h
index 17814a6..a851fd3 100644
--- a/extensions/renderer/ipc_message_sender.h
+++ b/extensions/renderer/ipc_message_sender.h
@@ -88,6 +88,11 @@
virtual void SendPostMessageToPort(const PortId& port_id,
const Message& message) = 0;
+ // Sends a message indicating that a receiver of a message indicated that it
+ // plans to send a response later.
+ virtual void SendMessageResponsePending(int routing_id,
+ const PortId& port_id) = 0;
+
// Sends activityLog IPC to the browser process.
virtual void SendActivityLogIPC(
const ExtensionId& extension_id,
diff --git a/extensions/renderer/native_extension_bindings_system_test_base.h b/extensions/renderer/native_extension_bindings_system_test_base.h
index 59ba329..95a7733 100644
--- a/extensions/renderer/native_extension_bindings_system_test_base.h
+++ b/extensions/renderer/native_extension_bindings_system_test_base.h
@@ -93,6 +93,8 @@
void(int routing_id, const PortId& port_id, bool close_channel));
MOCK_METHOD2(SendPostMessageToPort,
void(const PortId& port_id, const Message& message));
+ MOCK_METHOD2(SendMessageResponsePending,
+ void(int routing_id, const PortId& port_id));
MOCK_METHOD3(SendActivityLogIPC,
void(const ExtensionId& extension_id,
IPCMessageSender::ActivityLogCallType call_type,
diff --git a/extensions/renderer/native_renderer_messaging_service_unittest.cc b/extensions/renderer/native_renderer_messaging_service_unittest.cc
index 37728ae..16622c4c 100644
--- a/extensions/renderer/native_renderer_messaging_service_unittest.cc
+++ b/extensions/renderer/native_renderer_messaging_service_unittest.cc
@@ -557,6 +557,8 @@
crx_file::id_util::GenerateId("different");
open_port(on_message_external_port_id, other_extension);
+ EXPECT_CALL(*ipc_message_sender(),
+ SendMessageResponsePending(MSG_ROUTING_NONE, on_message_port_id));
messaging_service()->DeliverMessage(
script_context_set(), on_message_port_id,
Message("\"onMessage\"", SerializationFormat::kJson, false), nullptr);
diff --git a/extensions/renderer/one_time_message_handler.cc b/extensions/renderer/one_time_message_handler.cc
index bb7d145..939d4eb 100644
--- a/extensions/renderer/one_time_message_handler.cc
+++ b/extensions/renderer/one_time_message_handler.cc
@@ -49,6 +49,7 @@
struct OneTimeOpener {
int request_id = -1;
int routing_id = MSG_ROUTING_NONE;
+ binding::AsyncResponseType async_type = binding::AsyncResponseType::kNone;
};
// A receiver port in the context; i.e., a listener to runtime.onMessage.
@@ -204,6 +205,7 @@
OneTimeOpener& port = data->openers[new_port_id];
port.request_id = details.request_id;
port.routing_id = routing_id;
+ port.async_type = async_type;
promise = details.promise;
DCHECK_EQ(async_type == binding::AsyncResponseType::kPromise,
!promise.IsEmpty());
@@ -432,21 +434,34 @@
handled = true;
// Note: make a copy of port, since we're about to free it.
- const OneTimeOpener port = iter->second;
- DCHECK_NE(-1, port.request_id);
+ const OneTimeOpener opener = iter->second;
+ DCHECK_NE(-1, opener.request_id);
// We erase the opener now, since delivering the reply can cause JS to run,
// which could either invalidate the context or modify the |openers|
// collection (e.g., by sending another message).
data->openers.erase(iter);
+ std::string error;
+ // Set the error for the message port. If the browser supplies an error, we
+ // always use that. Otherwise, the behavior is different for promise-based vs
+ // callback-based channels.
+ // For a promise-based channel, not receiving a response is fine (assuming the
+ // listener didn't indicate it would send one) - the extension may simply be
+ // waiting for confirmation that the message sent.
+ // In the callback-based scenario, we use the presence of the callback as an
+ // indication that the extension expected a specific response. This is an
+ // unfortunate behavior difference that we keep for backwards-compatibility in
+ // callback-based API calls.
+ if (!error_message.empty()) {
+ // If the browser supplied us with an error message, use that.
+ error = error_message;
+ } else if (opener.async_type == binding::AsyncResponseType::kCallback) {
+ error = "The message port closed before a response was received.";
+ }
+
bindings_system_->api_system()->request_handler()->CompleteRequest(
- port.request_id, std::vector<v8::Local<v8::Value>>(),
- // If the browser doesn't supply an error message, we supply a generic
- // one.
- error_message.empty()
- ? "The message port closed before a response was received."
- : error_message);
+ opener.request_id, std::vector<v8::Local<v8::Value>>(), error);
// Note: The context could be invalidated at this point!
@@ -540,21 +555,26 @@
if (!data)
return;
- if (WillListenerReplyAsync(context, result))
- return; // The listener will reply later; leave the channel open.
-
auto iter = data->receivers.find(port_id);
// The channel may already be closed (if the listener replied).
if (iter == data->receivers.end())
return;
int routing_id = iter->second.routing_id;
+ IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender();
+
+ if (WillListenerReplyAsync(context, result)) {
+ // Inform the browser that one of the listeners said they would be replying
+ // later and leave the channel open.
+ ipc_sender->SendMessageResponsePending(routing_id, port_id);
+ return;
+ }
+
data->receivers.erase(iter);
// The listener did not reply and did not return `true` from any of its
// listeners. Close the message port. Don't close the channel because another
// listener (in a separate context) may reply.
- IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender();
bool close_channel = false;
ipc_sender->SendCloseMessagePort(routing_id, port_id, close_channel);
}
diff --git a/extensions/renderer/one_time_message_handler_unittest.cc b/extensions/renderer/one_time_message_handler_unittest.cc
index 7544347..3ed5ce6e 100644
--- a/extensions/renderer/one_time_message_handler_unittest.cc
+++ b/extensions/renderer/one_time_message_handler_unittest.cc
@@ -322,6 +322,8 @@
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);
@@ -404,6 +406,8 @@
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 =
@@ -550,6 +554,8 @@
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));
@@ -610,6 +616,8 @@
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));
}