blob: 2d8e527c598cdd320c247c2db30c42ebbb91a8d3 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "components/embedder_support/switches.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_host_registry.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/api/runtime.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/test/base/ui_test_utils.h"
#endif
namespace extensions {
namespace {
class MessageSender : public ExtensionHostRegistry::Observer {
public:
explicit MessageSender(content::BrowserContext* browser_context) {
host_registry_observation_.Observe(
ExtensionHostRegistry::Get(browser_context));
}
private:
static base::Value::List BuildEventArguments(const bool last_message,
const std::string& data) {
return base::Value::List().Append(
base::Value::Dict().Set("lastMessage", last_message).Set("data", data));
}
static std::unique_ptr<Event> BuildEvent(
base::Value::List event_args,
content::BrowserContext* browser_context,
GURL event_url) {
auto event =
std::make_unique<Event>(events::TEST_ON_MESSAGE, "test.onMessage",
std::move(event_args), browser_context);
event->event_url = std::move(event_url);
return event;
}
// ExtensionHostRegistry::Observer:
void OnExtensionHostCompletedFirstLoad(
content::BrowserContext* browser_context,
ExtensionHost* extension_host) override {
EventRouter* event_router = EventRouter::Get(browser_context);
// Sends four messages to the extension. All but the third message sent
// from the origin http://b.com/ are supposed to arrive.
event_router->BroadcastEvent(BuildEvent(
BuildEventArguments(false, "no restriction"), browser_context, GURL()));
event_router->BroadcastEvent(
BuildEvent(BuildEventArguments(false, "http://a.com/"), browser_context,
GURL("http://a.com/")));
event_router->BroadcastEvent(
BuildEvent(BuildEventArguments(false, "http://b.com/"), browser_context,
GURL("http://b.com/")));
event_router->BroadcastEvent(BuildEvent(
BuildEventArguments(true, "last message"), browser_context, GURL()));
}
base::ScopedObservation<ExtensionHostRegistry,
ExtensionHostRegistry::Observer>
host_registry_observation_{this};
};
class MessagingApiTest : public ExtensionApiTest {
public:
explicit MessagingApiTest(bool enable_back_forward_cache = true) {
if (!enable_back_forward_cache) {
feature_list_.InitWithFeaturesAndParameters(
{}, {features::kBackForwardCache});
return;
}
feature_list_.InitWithFeaturesAndParameters(
content::GetBasicBackForwardCacheFeatureForTesting(),
content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}
MessagingApiTest(const MessagingApiTest&) = delete;
MessagingApiTest& operator=(const MessagingApiTest&) = delete;
~MessagingApiTest() override = default;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(StartEmbeddedTestServer());
}
private:
base::test::ScopedFeatureList feature_list_;
};
class MessagingApiWithBackForwardCacheTest : public MessagingApiTest {
public:
MessagingApiWithBackForwardCacheTest()
: MessagingApiTest(
/*enable_back_forward_cache=*/true) {}
};
class MessagingApiWithoutBackForwardCacheTest : public MessagingApiTest {
public:
MessagingApiWithoutBackForwardCacheTest()
: MessagingApiTest(/*enable_back_forward_cache=*/false) {}
};
#if BUILDFLAG(ENABLE_EXTENSIONS)
IN_PROC_BROWSER_TEST_F(MessagingApiTest, Messaging) {
ASSERT_TRUE(RunExtensionTest("messaging/connect", {.custom_arg = "bfcache"}))
<< message_;
}
IN_PROC_BROWSER_TEST_F(MessagingApiWithoutBackForwardCacheTest, Messaging) {
ASSERT_TRUE(RunExtensionTest("messaging/connect")) << message_;
}
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingCrash) {
ExtensionTestMessageListener ready_to_crash("ready_to_crash");
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("messaging/connect_crash")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
content::WebContents* tab = GetActiveWebContents();
EXPECT_TRUE(ready_to_crash.WaitUntilSatisfied());
ResultCatcher catcher;
CrashTab(tab);
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(), {}, {}));
}
#endif
// Tests that message passing from one extension to another works.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingExternal) {
ASSERT_TRUE(LoadExtension(
shared_test_data_dir().AppendASCII("messaging").AppendASCII("receiver")));
ASSERT_TRUE(RunExtensionTest("messaging/connect_external",
{.use_extensions_root_dir = true}))
<< message_;
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests that a content script can exchange messages with a tab even if there is
// no background page.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingNoBackground) {
ASSERT_TRUE(RunExtensionTest("messaging/connect_nobackground",
{.extension_url = "page_in_main_frame.html"}))
<< message_;
}
#endif
// Tests that messages with event_urls are only passed to extensions with
// appropriate permissions.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingEventURL) {
MessageSender sender(profile());
ASSERT_TRUE(RunExtensionTest("messaging/event_url")) << message_;
}
// Tests that messages cannot be received from the same frame.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingBackgroundOnly) {
ASSERT_TRUE(RunExtensionTest("messaging/background_only")) << message_;
}
// TODO(kalman): Most web messaging tests disabled on windows due to extreme
// flakiness. See http://crbug.com/350517.
#if !BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingUserGesture) {
const char kManifest[] = "{"
" \"name\": \"user_gesture\","
" \"version\": \"1.0\","
" \"background\": {"
" \"scripts\": [\"background.js\"]"
" },"
" \"manifest_version\": 2"
"}";
TestExtensionDir receiver_dir;
receiver_dir.WriteManifest(kManifest);
receiver_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
"chrome.runtime.onMessageExternal.addListener(\n"
" function(msg, sender, reply) {\n"
" reply({result:chrome.test.isProcessingUserGesture()});\n"
" });");
const Extension* receiver = LoadExtension(receiver_dir.UnpackedPath());
ASSERT_TRUE(receiver);
TestExtensionDir sender_dir;
sender_dir.WriteManifest(kManifest);
sender_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
const Extension* sender = LoadExtension(sender_dir.UnpackedPath());
ASSERT_TRUE(sender);
EXPECT_EQ(
"false",
ExecuteScriptInBackgroundPage(
sender->id(),
base::StringPrintf(
"if (chrome.test.isProcessingUserGesture()) {\n"
" chrome.test.sendScriptResult("
" 'Error: unexpected user gesture');\n"
"} else {\n"
" chrome.runtime.sendMessage('%s', {}, function(response) {\n"
" chrome.test.sendScriptResult('' + response.result);\n"
" });\n"
"}",
receiver->id().c_str()),
extensions::browsertest_util::ScriptUserActivation::kDontActivate));
EXPECT_EQ(
"true",
ExecuteScriptInBackgroundPage(
sender->id(),
base::StringPrintf(
"chrome.test.runWithUserGesture(function() {\n"
" chrome.runtime.sendMessage('%s', {}, function(response) {\n"
" chrome.test.sendScriptResult('' + response.result);\n"
" });\n"
"});",
receiver->id().c_str())));
}
IN_PROC_BROWSER_TEST_F(MessagingApiTest, UserGestureFromContentScript) {
static constexpr char kBackground[] = R"(
chrome.runtime.onMessage.addListener(function() {
chrome.test.assertTrue(chrome.test.isProcessingUserGesture());
chrome.test.notifyPass();
});
)";
static constexpr char kContentScript[] = R"(
chrome.test.runWithUserGesture(function() {
chrome.runtime.sendMessage('');
});
)";
static constexpr char kManifest[] = R"(
{
"name": "Test user gesture from content script.",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["*://example.com/*"],
"js": ["content_script.js"]
}]
}
)";
TestExtensionDir test_dir;
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackground);
test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"), kContentScript);
test_dir.WriteManifest(kManifest);
GURL url = embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(),
{.page_url = url.spec().c_str()}, {}))
<< message_;
}
IN_PROC_BROWSER_TEST_F(MessagingApiTest, UserGestureFromExtensionPage) {
static constexpr char kBackground[] = R"(
chrome.runtime.onMessage.addListener(function() {
chrome.test.assertTrue(chrome.test.isProcessingUserGesture());
chrome.test.notifyPass();
});
)";
static constexpr char kPage[] = R"(
<script src='page.js'></script>
)";
static constexpr char kScript[] = R"(
chrome.test.runWithUserGesture(function() {
chrome.runtime.sendMessage('');
});
)";
static constexpr char kManifest[] = R"(
{
"name": "Test user gesture from extension page.",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
)";
TestExtensionDir test_dir;
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackground);
test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPage);
test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kScript);
test_dir.WriteManifest(kManifest);
ASSERT_TRUE(RunExtensionTest(test_dir.UnpackedPath(),
{.extension_url = "page.html"}, {}))
<< message_;
}
IN_PROC_BROWSER_TEST_F(MessagingApiTest,
RestrictedActivationTriggerBetweenExtensions) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
embedder_support::kDisablePopupBlocking);
static constexpr char kManifest[] = R"({
"name": "activation_state_thru_send_reply",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
})";
// The receiver replies back with its transient activation state after a
// delay.
TestExtensionDir receiver_dir;
receiver_dir.WriteManifest(kManifest);
receiver_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
R"(
chrome.runtime.onMessageExternal.addListener(
(msg, sender, callback) => {
setTimeout(() =>
callback({active:navigator.userActivation.isActive}), 200);
});
)");
const Extension* receiver = LoadExtension(receiver_dir.UnpackedPath());
ASSERT_TRUE(receiver);
TestExtensionDir sender_dir;
sender_dir.WriteManifest(kManifest);
sender_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
const Extension* sender = LoadExtension(sender_dir.UnpackedPath());
ASSERT_TRUE(sender);
static constexpr char send_script_template[] = R"(
log = [];
log.push('sender-initial:' + navigator.userActivation.isActive);
chrome.runtime.sendMessage('%s', {}, response => {
log.push('receiver:' + response.active);
log.push('sender-received:' + navigator.userActivation.isActive);
chrome.test.sendScriptResult(log.toString());
});
log.push('sender-sent:' + navigator.userActivation.isActive);
)";
std::string send_script =
base::StringPrintf(send_script_template, receiver->id().c_str());
// Without any user activation, neither the sender nor the receiver should be
// in active state at any moment.
EXPECT_EQ(
"sender-initial:false,sender-sent:false,receiver:false,"
"sender-received:false",
ExecuteScriptInBackgroundPage(
sender->id(), send_script,
extensions::browsertest_util::ScriptUserActivation::kDontActivate));
// With user activation before sending, the sender should be in active state
// all the time, and the receiver should be in active state.
//
// TODO(crbug.com/40094773): The receiver should be inactive here.
EXPECT_EQ(
"sender-initial:true,sender-sent:true,receiver:true,"
"sender-received:true",
ExecuteScriptInBackgroundPage(
sender->id(), send_script,
extensions::browsertest_util::ScriptUserActivation::kActivate));
std::string send_and_consume_script = send_script + R"(
setTimeout(() => {
open().close();
log.push('sender-consumed:' + navigator.userActivation.isActive);
}, 0);
)";
// With user activation consumed right after sending, the sender should be in
// active state until consumption, and the receiver should be in active state.
//
// TODO(crbug.com/40094773): The receiver should be inactive here.
EXPECT_EQ(
"sender-initial:true,sender-sent:true,sender-consumed:false,"
"receiver:true,sender-received:false",
ExecuteScriptInBackgroundPage(
sender->id(), send_and_consume_script,
extensions::browsertest_util::ScriptUserActivation::kActivate));
}
#endif // !BUILDFLAG(IS_WIN)
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests that messages sent in the pagehide handler of a window arrive.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessagingOnPagehide) {
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("messaging/on_pagehide"));
ExtensionTestMessageListener listener("listening");
ASSERT_TRUE(extension);
// Open a new tab to example.com. Since we'll be closing it later, we need
// to make sure there's still a tab around to extend the life of the
// browser.
NavigateToURLInNewTab(
embedded_test_server()->GetURL("example.com", "/empty.html"));
EXPECT_TRUE(listener.WaitUntilSatisfied());
ExtensionHost* background_host =
ProcessManager::Get(profile())->GetBackgroundHostForExtension(
extension->id());
ASSERT_TRUE(background_host);
content::WebContents* background_contents = background_host->host_contents();
ASSERT_TRUE(background_contents);
// There shouldn't be any messages yet.
EXPECT_EQ(0, content::EvalJs(background_contents, "window.messageCount;"));
content::WebContentsDestroyedWatcher destroyed_watcher(
GetActiveWebContents());
chrome::CloseTab(browser());
destroyed_watcher.Wait();
base::RunLoop().RunUntilIdle();
// The extension should have sent a message from its pagehide handler.
EXPECT_EQ(1, content::EvalJs(background_contents, "window.messageCount;"));
}
// Tests that messages over a certain size are not sent.
// https://crbug.com/766713.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, LargeMessages) {
ASSERT_TRUE(RunExtensionTest("messaging/large_messages"));
}
// Tests that the channel name used in runtime.connect() cannot redirect the
// message to another event (like onMessage).
// See https://crbug.com/1430999.
IN_PROC_BROWSER_TEST_F(MessagingApiTest, MessageChannelName) {
static constexpr char kManifest[] =
R"({
"name": "Ext",
"manifest_version": 3,
"version": "0.1"
})";
static constexpr char kConnectorJs[] =
R"(chrome.test.runTests([
async function portWithSendMessageName() {
let port = chrome.runtime.connect(
{name: 'chrome.runtime.sendMessage'});
chrome.test.assertEq('chrome.runtime.sendMessage', port.name);
port.onMessage.addListener((msg) => {
chrome.test.assertEq('pong', msg);
chrome.test.succeed();
});
port.postMessage('ping');
}
]);)";
static constexpr char kConnecteeJs[] =
R"(chrome.runtime.onConnect.addListener((port) => {
self.port = port;
port.onMessage.addListener((msg) => {
chrome.test.assertEq(port.name, 'chrome.runtime.sendMessage');
chrome.test.assertEq(msg, 'ping');
port.postMessage('pong');
});
});
chrome.runtime.onMessage.addListener((msg) => {
// We don't expect anything to hit the `onMessage` listener.
// See https://crbug.com/1430999.
chrome.test.fail(`Unexpected onMessage received: ${msg}`);
});)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("connector.html"),
R"(<html><script src="connector.js"></script></html>)");
test_dir.WriteFile(FILE_PATH_LITERAL("connector.js"), kConnectorJs);
test_dir.WriteFile(FILE_PATH_LITERAL("connectee.html"),
R"(<html><script src="connectee.js"></script></html>)");
test_dir.WriteFile(FILE_PATH_LITERAL("connectee.js"), kConnecteeJs);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
ResultCatcher result_catcher;
NavigateToURLInNewTab(extension->GetResourceURL("connectee.html"));
NavigateToURLInNewTab(extension->GetResourceURL("connector.html"));
ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
class OnMessagePromiseReturnMessagingApiTest : public MessagingApiTest {
public:
OnMessagePromiseReturnMessagingApiTest() {
scoped_feature_list_.InitAndEnableFeature(
extensions_features::kRuntimeOnMessagePromiseReturnSupport);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Runs multiple test scenarios for runtime.OnMessage() listeners returning
// promises.
IN_PROC_BROWSER_TEST_F(OnMessagePromiseReturnMessagingApiTest,
OnMessagePromiseReturnResolvesBehavior) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/on_message_promise_resolve")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script sender gets response with the expected value.
{
SCOPED_TRACE(
"waiting for content script message sender to receive response from "
"background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
IN_PROC_BROWSER_TEST_F(OnMessagePromiseReturnMessagingApiTest,
OnMessagePromiseReturnRejectsBehavior) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/on_message_promise_reject")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script sender gets response with the expected value.
{
SCOPED_TRACE(
"waiting for content script message sender to receive response from "
"background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
using PolyfillSupportMessagingApiTest = MessagingApiTest;
// Tests that runtime.sendMessage() promise version behavior matches the
// mozilla/webextension-polyfill
// (https://github.com/mozilla/webextension-polyfill). The polyfill doesn't
// support callbacks so we do not test the sendMessage() callback version
// (https://github.com/mozilla/webextension-polyfill/issues/102). The test is
// split into sync and async versions due to needing to stop the worker to
// elicit the response for some test cases.
IN_PROC_BROWSER_TEST_F(PolyfillSupportMessagingApiTest,
SendMessageListenerBehavior_Synchronous) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/send_message_promise_polyfill_sync")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script sender gets response with the expected value.
{
SCOPED_TRACE(
"waiting for content script message sender to receive response from "
"background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
// See above.
IN_PROC_BROWSER_TEST_F(PolyfillSupportMessagingApiTest,
SendMessageListenerBehavior_Asynchronous) {
const Extension* extension = LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/send_message_promise_polyfill_async"));
ASSERT_TRUE(extension);
ExtensionTestMessageListener message_processed_listener(
"listener_processed_message");
ExtensionTestMessageListener worker_shutdown_listener(
"shutdown_worker", ReplyBehavior::kWillReply);
auto OnShutdownMessage = [&](const std::string& message) {
// Wait for the worker listener to process the message so we don't shutdown
// the worker so quickly that the sender's message never gets to the
// listener.
ASSERT_TRUE(message_processed_listener.WaitUntilSatisfied(
base::RunLoop::Type::kNestableTasksAllowed));
message_processed_listener.Reset();
// Shut down the worker to start garbage collection of the sendResponse in
// the listener context. This elicits the browser to respond on behalf of
// the listener.
browsertest_util::StopServiceWorkerForExtensionGlobalScope(
profile(), extension->id(), base::RunLoop::Type::kNestableTasksAllowed);
// Notify the test cases to proceed.
worker_shutdown_listener.Reply("");
worker_shutdown_listener.Reset();
};
worker_shutdown_listener.SetOnRepeatedlySatisfied(
base::BindLambdaForTesting(OnShutdownMessage));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script sender gets response with the expected value.
{
SCOPED_TRACE(
"waiting for content script message sender to receive response from "
"background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
class UnserializableOneTimeMessageResponseMessagingApiTest
: public MessagingApiTest {
public:
UnserializableOneTimeMessageResponseMessagingApiTest() {
scoped_feature_list_.InitAndEnableFeature(
extensions_features::
kOneTimeMessageUnserializableResponseClosesChannel);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests similar behavior to PolyfillSupportMessagingApiTest, but specifically
// when the message listener attempts to send unserializable data back to the
// sender. In this case we close the channel and return an error. It is closer
// to the behavior of mozilla/webextension-polyfill
// (https://github.com/mozilla/webextension-polyfill), but in that an error is
// returned.
IN_PROC_BROWSER_TEST_F(UnserializableOneTimeMessageResponseMessagingApiTest,
UnserializableResponseClosesChannel) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/send_message_promise_polyfill_unserializable")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script sender gets response with the expected value.
{
SCOPED_TRACE(
"waiting for content script message sender to receive response from "
"background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
// Helps in testing that
// extensions_features::kRuntimeOnMessagePromiseReturnSupport doesn't regress
// asynchronous listener behavior when multiple listeners can return for a
// single message.
class OnMessageMultiListenerMessagingApiTest
: public MessagingApiTest,
public testing::WithParamInterface<bool> {
public:
OnMessageMultiListenerMessagingApiTest() {
if (GetParam()) {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{extensions_features::
kRuntimeOnMessagePromiseReturnSupport},
/*disabled_features=*/{});
} else {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{}, /*disabled_features=*/{
extensions_features::kRuntimeOnMessagePromiseReturnSupport});
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that, when a synchronous onMessage listener is registered first (it's
// return value is examined first) and an asynchronous listener is registered
// second, it doesn't prevent the asynchronous listeners sendResponse() call
// from getting to the message sender. Regression test for crbug.com/424560420.
IN_PROC_BROWSER_TEST_P(OnMessageMultiListenerMessagingApiTest,
OnMessageSyncListenerReturnsFirst) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/on_message_multi_listener/sync_listener_called_first")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script response callback function is called with the
// expected value.
{
SCOPED_TRACE(
"waiting for content script message sender response callback to "
"receive response from background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
// Tests that, when a asynchronous onMessage listener is registered first (it's
// return value is examined first) and a synchronous listener is registered
// second, it doesn't prevent the asynchronous listeners sendResponse() call
// from getting to the message sender. Regression test for crbug.com/424560420.
IN_PROC_BROWSER_TEST_P(OnMessageMultiListenerMessagingApiTest,
OnMessageAsyncListenerReturnsFirst) {
ASSERT_TRUE(LoadExtension(shared_test_data_dir().AppendASCII(
"messaging/on_message_multi_listener/async_listener_called_first")));
// Open example.com where content script is injected and runtime.sendMessage()
// is called.
ResultCatcher result_catcher;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.html")));
// Confirm content script response callback function is called with the
// expected value.
{
SCOPED_TRACE(
"waiting for content script message sender response callback to "
"receive response from background message listener");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
}
INSTANTIATE_TEST_SUITE_P(All,
OnMessageMultiListenerMessagingApiTest,
// extensions_features::kUserScriptUserExtensionToggle
testing::Bool());
class ServiceWorkerMessagingApiTest : public MessagingApiTest {
protected:
~ServiceWorkerMessagingApiTest() override = default;
size_t GetWorkerRefCount(const blink::StorageKey& key) {
content::ServiceWorkerContext* sw_context =
browser()
->profile()
->GetDefaultStoragePartition()
->GetServiceWorkerContext();
return sw_context->CountExternalRequestsForTest(key);
}
};
// After sending message from extension and got response back, there should be
// no in-flight request hanging.
// TODO(crbug.com/40257364): Disabled due to flakiness.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingApiTest,
DISABLED_InflightCountAfterSendMessage) {
constexpr char kManifest[] =
R"({
"name": "Test Extension",
"manifest_version": 3,
"version": "0.1",
"background": {
"service_worker": "script.js",
"type": "module"
}
})";
constexpr char kScript[] =
R"(
import { openTab } from '/_test_resources/test_util/tabs_util.js';
self.addEventListener('activate', async (event) => {
await openTab('page.html');
sendMessage();
});
function sendMessage() {
chrome.runtime.sendMessage({ greeting: 'hello' }, (response) => {
chrome.test.notifyPass();
console.log('pass');
});
}
)";
constexpr char kPageHtml[] =
R"(
<title>Page Title</title>
<html>
<body>
<p>Test page</p>
<script src="page.js"></script>
</body>
</html>
)";
constexpr char kPageJs[] =
R"(
function onMessage(request, sender, sendResponse) {
sendResponse({ greeting: 'there' });
}
chrome.runtime.onMessage.addListener(onMessage);
)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("script.js"), kScript);
test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPageHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kPageJs);
ResultCatcher catcher;
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
ASSERT_TRUE(catcher.GetNextResult());
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(web_contents);
EXPECT_EQ(extension->origin(),
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
// This is a hack to make sure messaging IPCs are finished. Since IPCs
// are sent synchronously, anything started prior to this method will finish
// before this method returns (as content::ExecJs() blocks until
// completion).
ASSERT_TRUE(content::ExecJs(web_contents, "1 == 1;"));
content::RunAllTasksUntilIdle();
url::Origin extension_origin = url::Origin::Create(extension->url());
const blink::StorageKey extension_key =
blink::StorageKey::CreateFirstParty(extension_origin);
EXPECT_EQ(0u, GetWorkerRefCount(extension_key));
}
class MessagingApiFencedFrameTest : public MessagingApiTest {
protected:
MessagingApiFencedFrameTest() {
feature_list_.InitWithFeaturesAndParameters(
{{blink::features::kFencedFrames, {}},
{blink::features::kFencedFramesAPIChanges, {}},
{blink::features::kFencedFramesDefaultMode, {}},
{features::kPrivacySandboxAdsAPIsOverride, {}}},
{/* disabled_features */});
}
~MessagingApiFencedFrameTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(MessagingApiFencedFrameTest, Load) {
ASSERT_TRUE(RunExtensionTest("messaging/connect_fenced_frames", {}))
<< message_;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace
} // namespace extensions