blob: ce49d3e51d62a0663ae2ba4bea9b5056e37d195b [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "extensions/browser/service_worker/service_worker_test_utils.h"
#include "extensions/common/scoped_worker_based_extensions_channel.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"
namespace extensions {
namespace {
base::FilePath WriteExtensionWithMessagePortToDir(TestExtensionDir* test_dir) {
test_dir->WriteManifest(R"(
{
"name": "Content script disconnect on worker stop test",
"description": "Tests worker shutdown behavior for messaging",
"version": "0.1",
"manifest_version": 2,
"background": {"scripts": ["background.js"]},
"content_scripts": [{
"matches": ["*://example.com:*/*"],
"js": ["content_script.js"]
}]
})");
test_dir->WriteFile(FILE_PATH_LITERAL("background.js"), R"(
chrome.runtime.onConnect.addListener(port => {
console.log('background: runtime.onConnect');
port.onMessage.addListener(msg => {
if (msg == 'foo') {
chrome.test.notifyPass();
} else
chrome.test.notifyFail('FAILED');
});
});)");
test_dir->WriteFile(FILE_PATH_LITERAL("content_script.js"), R"(
var port = chrome.runtime.connect({name:"bar"});
port.postMessage('foo');)");
return test_dir->UnpackedPath();
}
base::FilePath WriteServiceWorkerExtensionToDir(TestExtensionDir* test_dir) {
test_dir->WriteManifest(R"(
{
"name": "Test Extension",
"manifest_version": 2,
"version": "1.0",
"background": {"service_worker": "service_worker_background.js"}
})");
test_dir->WriteFile(FILE_PATH_LITERAL("service_worker_background.js"),
R"(chrome.test.sendMessage('worker_running');)");
return test_dir->UnpackedPath();
}
} // namespace
class ServiceWorkerMessagingTest : public ExtensionApiTest {
public:
ServiceWorkerMessagingTest() = default;
~ServiceWorkerMessagingTest() override = default;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
protected:
// TODO(lazyboy): Move this to a common place so it can be shared with other
// tests.
void StopServiceWorker(const Extension& extension) {
content::StoragePartition* storage_partition =
content::BrowserContext::GetDefaultStoragePartition(
browser()->profile());
content::ServiceWorkerContext* context =
storage_partition->GetServiceWorkerContext();
base::RunLoop run_loop;
// The service worker is registered at the root scope.
content::StopServiceWorkerForScope(context, extension.url(),
run_loop.QuitClosure());
run_loop.Run();
}
extensions::ScopedTestNativeMessagingHost test_host_;
private:
ScopedWorkerBasedExtensionsChannel current_channel_;
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerMessagingTest);
};
// Tests one-way message from content script to SW extension using
// chrome.runtime.sendMessage.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, TabToWorkerOneWay) {
ExtensionTestMessageListener worker_listener("WORKER_RUNNING", false);
const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/send_message_tab_to_worker_one_way"));
ASSERT_TRUE(extension);
EXPECT_TRUE(worker_listener.WaitUntilSatisfied());
ExtensionTestMessageListener test_listener("WORKER_RECEIVED_MESSAGE", false);
test_listener.set_failure_message("FAILURE");
{
ASSERT_TRUE(StartEmbeddedTestServer());
const GURL url =
embedded_test_server()->GetURL("/extensions/test_file.html");
content::WebContents* new_web_contents =
browsertest_util::AddTab(browser(), url);
EXPECT_TRUE(new_web_contents);
}
EXPECT_TRUE(test_listener.WaitUntilSatisfied());
}
// Tests chrome.runtime.sendMessage from content script to SW extension.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, TabToWorker) {
ExtensionTestMessageListener worker_listener("WORKER_RUNNING", false);
const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/send_message_tab_to_worker"));
ASSERT_TRUE(extension);
EXPECT_TRUE(worker_listener.WaitUntilSatisfied());
ExtensionTestMessageListener reply_listener("CONTENT_SCRIPT_RECEIVED_REPLY",
false);
reply_listener.set_failure_message("FAILURE");
{
ASSERT_TRUE(StartEmbeddedTestServer());
const GURL url =
embedded_test_server()->GetURL("/extensions/test_file.html");
content::WebContents* new_web_contents =
browsertest_util::AddTab(browser(), url);
EXPECT_TRUE(new_web_contents);
}
EXPECT_TRUE(reply_listener.WaitUntilSatisfied());
}
// Tests that a message port disconnects if the extension SW is forcefully
// stopped.
// Regression test for https://crbug.com/1033783.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
TabToWorker_StopWorkerDisconnects) {
ASSERT_TRUE(StartEmbeddedTestServer());
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Content script disconnect on worker stop test",
"description": "Tests worker shutdown behavior for messaging",
"version": "0.1",
"manifest_version": 2,
"background": {"service_worker": "service_worker_background.js"},
"content_scripts": [{
"matches": ["*://example.com:*/*"],
"js": ["content_script.js"]
}]
})");
test_dir.WriteFile(FILE_PATH_LITERAL("service_worker_background.js"),
R"(chrome.runtime.onConnect.addListener((port) => {
console.log('background: runtime.onConnect');
chrome.test.assertNoLastError();
chrome.test.notifyPass();
});
chrome.test.notifyPass();
)");
test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
R"(var port = chrome.runtime.connect({name:"foo"});
port.onDisconnect.addListener(() => {
console.log('content script: port.onDisconnect');
chrome.test.assertNoLastError();
chrome.test.notifyPass();
});
)");
ResultCatcher catcher;
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Wait for the extension to register runtime.onConnect listener.
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
GURL url =
embedded_test_server()->GetURL("example.com", "/extensions/body1.html");
ui_test_utils::NavigateToURL(browser(), url);
// Wait for the content script to connect to the worker's port.
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
// Stop the service worker, this will disconnect the port.
StopServiceWorker(*extension);
// Wait for the port to disconnect in the content script.
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// Tests chrome.runtime.sendNativeMessage from SW extension to a native
// messaging host.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, NativeMessagingBasic) {
ASSERT_NO_FATAL_FAILURE(test_host_.RegisterTestHost(false));
ASSERT_TRUE(RunExtensionTest("service_worker/messaging/send_native_message"))
<< message_;
}
// Tests chrome.runtime.connectNative from SW extension to a native messaging
// host.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, ConnectNative) {
ASSERT_NO_FATAL_FAILURE(test_host_.RegisterTestHost(false));
ASSERT_TRUE(RunExtensionTest("service_worker/messaging/connect_native"))
<< message_;
}
// Tests chrome.tabs.sendMessage from SW extension to content script.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, WorkerToTab) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(
RunExtensionTest("service_worker/messaging/send_message_worker_to_tab"))
<< message_;
}
// Tests port creation (chrome.runtime.connect) from content script to an
// extension SW and disconnecting the port.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
TabToWorker_ConnectAndDisconnect) {
// Load an extension that will inject content script to |new_web_contents|.
const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/connect_to_worker/connect_and_disconnect"));
ASSERT_TRUE(extension);
// Load the tab with content script to open a Port to |extension|.
// Test concludes when extension gets notified about port being disconnected.
ResultCatcher catcher;
{
ASSERT_TRUE(StartEmbeddedTestServer());
content::WebContents* new_web_contents = browsertest_util::AddTab(
browser(),
embedded_test_server()->GetURL("/extensions/test_file.html"));
EXPECT_TRUE(new_web_contents);
}
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// Tests port creation (chrome.runtime.connect) from content script to an
// extension and sending message through the port.
// TODO(lazyboy): Refactor common parts with TabToWorker_ConnectAndDisconnect.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
TabToWorker_ConnectAndPostMessage) {
// Load an extension that will inject content script to |new_web_contents|.
const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/connect_to_worker/post_message"));
ASSERT_TRUE(extension);
// Load the tab with content script to send message to |extension| via port.
// Test concludes when the content script receives a reply.
ResultCatcher catcher;
{
ASSERT_TRUE(StartEmbeddedTestServer());
content::WebContents* new_web_contents = browsertest_util::AddTab(
browser(),
embedded_test_server()->GetURL("/extensions/test_file.html"));
EXPECT_TRUE(new_web_contents);
}
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// Tests chrome.runtime.onMessageExternal between two Service Worker based
// extensions.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, ExternalMessageToWorker) {
const std::string kTargetExtensionId = "pkplfbidichfdicaijlchgnapepdginl";
// Load the receiver extension first.
const Extension* target_extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/send_message_external/target"));
ASSERT_TRUE(target_extension);
EXPECT_EQ(kTargetExtensionId, target_extension->id());
// Then run the test from initiator extension.
ASSERT_TRUE(RunExtensionTest(
"service_worker/messaging/send_message_external/initiator"))
<< message_;
}
// Tests chrome.runtime.onConnectExternal between two Service Worker based
// extensions.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, ConnectExternalToWorker) {
const std::string kTargetExtensionId = "pkplfbidichfdicaijlchgnapepdginl";
// Load the receiver extension first.
const Extension* target_extension = LoadExtension(test_data_dir_.AppendASCII(
"service_worker/messaging/connect_external/target"));
ASSERT_TRUE(target_extension);
EXPECT_EQ(kTargetExtensionId, target_extension->id());
// Then run the test from initiator extension.
ASSERT_TRUE(
RunExtensionTest("service_worker/messaging/connect_external/initiator"))
<< message_;
}
// Tests that an extension's message port isn't affected by an unrelated
// extension's service worker.
//
// This test opens a message port in an extension (message_port_extension) and
// then loads another extension that is service worker based. This test ensures
// that stopping the service worker based extension doesn't DCHECK in
// message_port_extension's message port.
//
// Regression test for https://crbug.com/1075751.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
UnrelatedPortsArentAffectedByServiceWorker) {
ASSERT_TRUE(StartEmbeddedTestServer());
// Step 1/2: Load an extension that creates an ExtensionMessagePort from a
// content script and connects to its background script.
TestExtensionDir message_port_extension_dir;
ResultCatcher content_script_connected_catcher;
const Extension* message_port_extension = LoadExtension(
WriteExtensionWithMessagePortToDir(&message_port_extension_dir));
ASSERT_TRUE(message_port_extension);
// Load the content script for |message_port_extension|, and wait for the
// content script to connect to its background's port.
GURL url =
embedded_test_server()->GetURL("example.com", "/extensions/body1.html");
ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(content_script_connected_catcher.GetNextResult())
<< content_script_connected_catcher.message();
// Step 2/2: Load an extension with service worker background, verify that
// stopping the service worker doesn't cause message port in
// |message_port_extension| to crash.
ExtensionTestMessageListener worker_running_listener("worker_running", false);
content::ServiceWorkerContext* service_worker_context =
content::BrowserContext::GetDefaultStoragePartition(browser()->profile())
->GetServiceWorkerContext();
service_worker_test_utils::TestRegistrationObserver registration_observer(
service_worker_context);
TestExtensionDir worker_extension_dir;
const Extension* service_worker_extension =
LoadExtension(WriteServiceWorkerExtensionToDir(&worker_extension_dir));
const ExtensionId worker_extension_id = service_worker_extension->id();
ASSERT_TRUE(service_worker_extension);
// Wait for the extension service worker to settle before moving to next step.
EXPECT_TRUE(worker_running_listener.WaitUntilSatisfied());
registration_observer.WaitForRegistrationStored();
{
// Stop the worker, and ensure its completion.
service_worker_test_utils::UnregisterWorkerObserver
unregister_worker_observer(ProcessManager::Get(profile()),
worker_extension_id);
StopServiceWorker(*service_worker_extension);
unregister_worker_observer.WaitForUnregister();
}
}
} // namespace extensions