blob: 262c45745ad045663b33b94c07855695c972490b [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 "base/barrier_closure.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/test_future.h"
#include "content/browser/serial/serial_test_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/cpp/test/fake_serial_port_client.h"
#include "services/device/public/cpp/test/fake_serial_port_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace content {
namespace {
using ::base::test::TestFuture;
using ::testing::_;
using ::testing::Return;
const char kTestUrl[] = "https://www.google.com";
const char kCrossOriginTestUrl[] = "https://www.chromium.org";
class MockSerialServiceClient : public blink::mojom::SerialServiceClient {
public:
MockSerialServiceClient() = default;
MockSerialServiceClient(const MockSerialServiceClient&) = delete;
MockSerialServiceClient& operator=(const MockSerialServiceClient&) = delete;
~MockSerialServiceClient() override {
// Flush the pipe to make sure there aren't any lingering events.
receiver_.FlushForTesting();
}
mojo::PendingRemote<blink::mojom::SerialServiceClient>
BindNewPipeAndPassRemote() {
return receiver_.BindNewPipeAndPassRemote();
}
// blink::mojom::SerialPortManagerClient
MOCK_METHOD1(OnPortAdded, void(blink::mojom::SerialPortInfoPtr));
MOCK_METHOD1(OnPortRemoved, void(blink::mojom::SerialPortInfoPtr));
private:
mojo::Receiver<blink::mojom::SerialServiceClient> receiver_{this};
};
class SerialTest : public RenderViewHostImplTestHarness {
public:
SerialTest() {
ON_CALL(delegate(), GetPortManager).WillByDefault(Return(&port_manager_));
ON_CALL(delegate(), AddObserver)
.WillByDefault(testing::SaveArg<1>(&observer_));
}
SerialTest(const SerialTest&) = delete;
SerialTest& operator=(const SerialTest&) = delete;
~SerialTest() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
original_client_ = SetBrowserClientForTesting(&test_client_);
RenderViewHostTestHarness::SetUp();
}
void TearDown() override {
RenderViewHostTestHarness::TearDown();
if (original_client_)
SetBrowserClientForTesting(original_client_);
}
MockSerialDelegate& delegate() { return test_client_.delegate(); }
device::FakeSerialPortManager* port_manager() { return &port_manager_; }
SerialDelegate::Observer* observer() { return observer_; }
private:
SerialTestContentBrowserClient test_client_;
raw_ptr<ContentBrowserClient> original_client_ = nullptr;
device::FakeSerialPortManager port_manager_;
SerialDelegate::Observer* observer_ = nullptr;
};
} // namespace
TEST_F(SerialTest, OpenAndClosePort) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
auto port = future.Take();
EXPECT_TRUE(port.is_valid());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
port.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
}
TEST_F(SerialTest, OpenWithoutPermission) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(false));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
auto port = future.Take();
EXPECT_FALSE(port.is_valid());
// Allow extra time for the watcher connection failure to propagate.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
}
TEST_F(SerialTest, OpenFailure) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
port_manager()->set_simulate_open_failure(true);
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
auto port = future.Take();
EXPECT_FALSE(port.is_valid());
// Allow extra time for the watcher connection failure to propagate.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
}
TEST_F(SerialTest, OpenAndNavigateCrossOrigin) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
mojo::Remote<device::mojom::SerialPort> port(future.Take());
EXPECT_TRUE(port.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
NavigateAndCommit(GURL(kCrossOriginTestUrl));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
port.FlushForTesting();
EXPECT_FALSE(port.is_connected());
}
TEST_F(SerialTest, AddAndRemovePorts) {
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
MockSerialServiceClient client;
service->SetClient(client.BindNewPipeAndPassRemote());
service.FlushForTesting();
ASSERT_TRUE(observer());
// Three ports will be added and then removed. Only the 1st and 3rd will have
// permission granted.
std::vector<device::mojom::SerialPortInfoPtr> ports;
for (size_t i = 0; i < 3; i++) {
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
ports.push_back(std::move(port));
}
EXPECT_CALL(delegate(), HasPortPermission(_, _))
.WillOnce(Return(true))
.WillOnce(Return(false))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(false))
.WillOnce(Return(true));
{
base::RunLoop run_loop;
auto closure = base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(client, OnPortAdded(_))
.Times(2)
.WillRepeatedly(base::test::RunClosure(closure));
for (const auto& port : ports)
observer()->OnPortAdded(*port);
run_loop.Run();
}
{
base::RunLoop run_loop;
auto closure = base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(client, OnPortRemoved(_))
.Times(2)
.WillRepeatedly(base::test::RunClosure(closure));
for (const auto& port : ports)
observer()->OnPortRemoved(*port);
run_loop.Run();
}
}
TEST_F(SerialTest, OpenAndClosePortManagerConnection) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
mojo::Remote<device::mojom::SerialPort> port(future.Take());
EXPECT_TRUE(port.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
ASSERT_TRUE(observer());
observer()->OnPortManagerConnectionError();
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
port.FlushForTesting();
EXPECT_FALSE(port.is_connected());
service.FlushForTesting();
EXPECT_FALSE(service.is_connected());
}
TEST_F(SerialTest, OpenAndRevokePermission) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
mojo::Remote<device::mojom::SerialPort> port(future.Take());
EXPECT_TRUE(port.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(false));
ASSERT_TRUE(observer());
url::Origin origin = url::Origin::Create(GURL(kTestUrl));
observer()->OnPermissionRevoked(origin);
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
port.FlushForTesting();
EXPECT_FALSE(port.is_connected());
service.FlushForTesting();
EXPECT_TRUE(service.is_connected());
}
TEST_F(SerialTest, OpenAndRevokePermissionOnDifferentOrigin) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token = base::UnguessableToken::Create();
auto port_info = device::mojom::SerialPortInfo::New();
port_info->token = token;
port_manager()->AddPort(port_info->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future;
service->OpenPort(token, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future.GetCallback());
mojo::Remote<device::mojom::SerialPort> port(future.Take());
EXPECT_TRUE(port.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
ASSERT_TRUE(observer());
url::Origin different_origin =
url::Origin::Create(GURL("http://different-origin.com"));
observer()->OnPermissionRevoked(different_origin);
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
port.FlushForTesting();
EXPECT_TRUE(port.is_connected());
service.FlushForTesting();
EXPECT_TRUE(service.is_connected());
}
TEST_F(SerialTest, OpenTwoPortsAndRevokePermission) {
NavigateAndCommit(GURL(kTestUrl));
mojo::Remote<blink::mojom::SerialService> service;
contents()->GetPrimaryMainFrame()->BindSerialService(
service.BindNewPipeAndPassReceiver());
auto token1 = base::UnguessableToken::Create();
auto port_info1 = device::mojom::SerialPortInfo::New();
port_info1->token = token1;
port_manager()->AddPort(port_info1->Clone());
auto token2 = base::UnguessableToken::Create();
auto port_info2 = device::mojom::SerialPortInfo::New();
port_info2->token = token2;
port_manager()->AddPort(port_info2->Clone());
EXPECT_FALSE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info1.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future1;
service->OpenPort(token1, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future1.GetCallback());
mojo::Remote<device::mojom::SerialPort> port1(future1.Take());
EXPECT_TRUE(port1.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, _)).WillOnce(Return(port_info2.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
TestFuture<mojo::PendingRemote<device::mojom::SerialPort>> future2;
service->OpenPort(token2, device::mojom::SerialConnectionOptions::New(),
device::FakeSerialPortClient::Create(),
future2.GetCallback());
mojo::Remote<device::mojom::SerialPort> port2(future2.Take());
EXPECT_TRUE(port2.is_connected());
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
EXPECT_CALL(delegate(), GetPortInfo(_, token1))
.WillOnce(Return(port_info1.get()));
EXPECT_CALL(delegate(), GetPortInfo(_, token2))
.WillOnce(Return(port_info2.get()));
EXPECT_CALL(delegate(), HasPortPermission(_, _))
.Times(2)
.WillRepeatedly(testing::Invoke([&](auto rfh, auto port_info) {
if (port_info.token == port_info1->token) {
return false;
} else {
return true;
}
}));
ASSERT_TRUE(observer());
url::Origin origin = url::Origin::Create(GURL(kTestUrl));
observer()->OnPermissionRevoked(origin);
EXPECT_TRUE(contents()->IsConnectedToSerialPort());
port1.FlushForTesting();
EXPECT_FALSE(port1.is_connected());
port2.FlushForTesting();
EXPECT_TRUE(port2.is_connected());
service.FlushForTesting();
EXPECT_TRUE(service.is_connected());
}
} // namespace content