// 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/run_loop.h" #include "base/test/bind.h" #include "base/test/gmock_callback_support.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" using testing::_; using testing::Return; namespace content { namespace { 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 BindNewPipeAndPassRemote() { return receiver_.BindNewPipeAndPassRemote(); } // blink::mojom::SerialPortManagerClient MOCK_METHOD1(OnPortAdded, void(blink::mojom::SerialPortInfoPtr)); MOCK_METHOD1(OnPortRemoved, void(blink::mojom::SerialPortInfoPtr)); private: mojo::Receiver 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() 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_; ContentBrowserClient* original_client_ = nullptr; device::FakeSerialPortManager port_manager_; SerialDelegate::Observer* observer_ = nullptr; DISALLOW_COPY_AND_ASSIGN(SerialTest); }; } // namespace TEST_F(SerialTest, OpenAndClosePort) { NavigateAndCommit(GURL(kTestUrl)); mojo::Remote service; contents()->GetMainFrame()->BindSerialService( service.BindNewPipeAndPassReceiver()); auto token = base::UnguessableToken::Create(); auto port_info = device::mojom::SerialPortInfo::New(); port_info->token = token; port_manager()->AddPort(std::move(port_info)); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); mojo::Remote port; base::RunLoop loop; service->OpenPort( token, device::mojom::SerialConnectionOptions::New(), device::FakeSerialPortClient::Create(), base::BindLambdaForTesting( [&](mojo::PendingRemote pending_remote) { EXPECT_TRUE(pending_remote.is_valid()); port.Bind(std::move(pending_remote)); loop.Quit(); })); loop.Run(); EXPECT_TRUE(contents()->IsConnectedToSerialPort()); port.reset(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); } TEST_F(SerialTest, OpenFailure) { NavigateAndCommit(GURL(kTestUrl)); mojo::Remote service; contents()->GetMainFrame()->BindSerialService( service.BindNewPipeAndPassReceiver()); auto token = base::UnguessableToken::Create(); auto port_info = device::mojom::SerialPortInfo::New(); port_info->token = token; port_manager()->AddPort(std::move(port_info)); port_manager()->set_simulate_open_failure(true); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); base::RunLoop loop; service->OpenPort( token, device::mojom::SerialConnectionOptions::New(), device::FakeSerialPortClient::Create(), base::BindLambdaForTesting( [&](mojo::PendingRemote pending_remote) { EXPECT_FALSE(pending_remote.is_valid()); loop.Quit(); })); loop.Run(); // 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 service; contents()->GetMainFrame()->BindSerialService( service.BindNewPipeAndPassReceiver()); auto token = base::UnguessableToken::Create(); auto port_info = device::mojom::SerialPortInfo::New(); port_info->token = token; port_manager()->AddPort(std::move(port_info)); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); mojo::Remote port; base::RunLoop loop; service->OpenPort( token, device::mojom::SerialConnectionOptions::New(), device::FakeSerialPortClient::Create(), base::BindLambdaForTesting( [&](mojo::PendingRemote pending_remote) { EXPECT_TRUE(pending_remote.is_valid()); port.Bind(std::move(pending_remote)); loop.Quit(); })); loop.Run(); 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 service; contents()->GetMainFrame()->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 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 service; contents()->GetMainFrame()->BindSerialService( service.BindNewPipeAndPassReceiver()); auto token = base::UnguessableToken::Create(); auto port_info = device::mojom::SerialPortInfo::New(); port_info->token = token; port_manager()->AddPort(std::move(port_info)); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); mojo::Remote port; base::RunLoop loop; service->OpenPort( token, device::mojom::SerialConnectionOptions::New(), device::FakeSerialPortClient::Create(), base::BindLambdaForTesting( [&](mojo::PendingRemote pending_remote) { EXPECT_TRUE(pending_remote.is_valid()); port.Bind(std::move(pending_remote)); loop.Quit(); })); loop.Run(); EXPECT_TRUE(contents()->IsConnectedToSerialPort()); ASSERT_TRUE(observer()); observer()->OnPortManagerConnectionError(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(contents()->IsConnectedToSerialPort()); port.FlushForTesting(); EXPECT_FALSE(port.is_connected()); service.FlushForTesting(); EXPECT_FALSE(service.is_connected()); } } // namespace content