910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// 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/repeating_test_future.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/public/test/navigation_simulator.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.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 "third_party/blink/public/mojom/serial/serial.mojom.h"
#include "url/origin.h"

namespace content {

namespace {

using ::base::test::InvokeFuture;
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(OnPortConnectedStateChanged,
               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_));
    ON_CALL(delegate(), RemoveObserver)
        .WillByDefault([&](RenderFrameHost*, SerialDelegate::Observer*) {
          observer_ = nullptr;
        });
  }

  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_;
  raw_ptr<SerialDelegate::Observer> observer_ = nullptr;
};

}  // namespace

TEST_F(SerialTest, GetPortsForAllDeviceTypes) {
  NavigateAndCommit(GURL(kTestUrl));

  mojo::Remote<blink::mojom::SerialService> service;
  contents()->GetPrimaryMainFrame()->BindSerialService(
      service.BindNewPipeAndPassReceiver());

  MockSerialServiceClient client;
  service->SetClient(client.BindNewPipeAndPassRemote());
  service.FlushForTesting();

  // Platform Serial port
  auto platform_token = base::UnguessableToken::Create();
  auto platform_port_info = device::mojom::SerialPortInfo::New();
  platform_port_info->token = platform_token;
  port_manager()->AddPort(std::move(platform_port_info));

  // USB Serial port
  auto usb_token = base::UnguessableToken::Create();
  auto usb_port_info = device::mojom::SerialPortInfo::New();
  usb_port_info->token = usb_token;
  usb_port_info->has_vendor_id = true;
  usb_port_info->vendor_id = 0x1111;
  usb_port_info->has_product_id = true;
  usb_port_info->product_id = 0x2222;
  port_manager()->AddPort(std::move(usb_port_info));

  // Bluetooth Serial port
  const device::BluetoothUUID kServiceClassId(
      "ac822b69-d7e9-4bab-8fa6-ce40c87e1ac4");
  auto bluetooth_token = base::UnguessableToken::Create();
  auto bluetooth_port_info = device::mojom::SerialPortInfo::New();
  bluetooth_port_info->token = bluetooth_token;
  bluetooth_port_info->bluetooth_service_class_id = kServiceClassId;
  port_manager()->AddPort(std::move(bluetooth_port_info));

  EXPECT_CALL(delegate(), HasPortPermission(_, _))
      .Times(3)
      .WillRepeatedly(Return(true));
  TestFuture<std::vector<blink::mojom::SerialPortInfoPtr>> future;
  service->GetPorts(future.GetCallback());

  auto ports = future.Take();
  // Assert as we need to access all three ports.
  ASSERT_EQ(ports.size(), 3u);
  // Ports are not in any particular order so we will loop through and check
  // according to the token.
  bool has_platform = false;
  bool has_usb = false;
  bool has_bluetooth = false;
  for (const auto& port : ports) {
    if (port->token == platform_token) {
      has_platform = true;
      EXPECT_EQ(port->has_usb_vendor_id, false);
      EXPECT_EQ(port->has_usb_product_id, false);
      EXPECT_FALSE(port->bluetooth_service_class_id.has_value());
    } else if (port->token == usb_token) {
      has_usb = true;
      EXPECT_EQ(port->has_usb_vendor_id, true);
      EXPECT_EQ(port->usb_vendor_id, 0x1111);
      EXPECT_EQ(port->has_usb_product_id, true);
      EXPECT_EQ(port->usb_product_id, 0x2222);
      EXPECT_FALSE(port->bluetooth_service_class_id.has_value());
    } else if (port->token == bluetooth_token) {
      has_bluetooth = true;
      EXPECT_EQ(port->has_usb_vendor_id, false);
      EXPECT_EQ(port->has_usb_product_id, false);
      EXPECT_EQ(port->bluetooth_service_class_id, kServiceClassId);
    } else {
      ADD_FAILURE() << "Unexpected port token: " << port->token;
    }
  }
  EXPECT_TRUE(has_platform);
  EXPECT_TRUE(has_usb);
  EXPECT_TRUE(has_bluetooth);
}

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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  port.reset();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(
      contents()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
}

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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
}

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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
}

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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  NavigateAndCommit(GURL(kCrossOriginTestUrl));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(
      contents()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
  port.FlushForTesting();
  EXPECT_FALSE(port.is_connected());
}

TEST_F(SerialTest, SameBluetoothSerialPortSameToken) {
  NavigateAndCommit(GURL(kTestUrl));
  mojo::Remote<blink::mojom::SerialService> service;
  contents()->GetPrimaryMainFrame()->BindSerialService(
      service.BindNewPipeAndPassReceiver());
  MockSerialServiceClient client;
  service->SetClient(client.BindNewPipeAndPassRemote());
  service.FlushForTesting();
  ASSERT_TRUE(observer());

  const device::BluetoothUUID kServiceClassId(
      "ac822b69-d7e9-4bab-8fa6-ce40c87e1ac4");
  base::UnguessableToken bluetooth_token;
  std::vector<device::mojom::SerialPortInfoPtr> ports;
  for (size_t i = 0; i < 2; i++) {
    auto port = device::mojom::SerialPortInfo::New();
    port->token = base::UnguessableToken::Create();
    port->bluetooth_service_class_id = kServiceClassId;
    ports.push_back(std::move(port));
    if (i == 0) {
      // Both SerialPortInfos describe the same port (same device address and
      // service UUID). When the ports are delivered to the renderer, the
      // second port reuses the token from the first port even though they were
      // created with different tokens.
      bluetooth_token = ports[i]->token;
    }
  }

  for (size_t i = 0; i < 2; i++) {
    TestFuture<blink::mojom::SerialPortInfoPtr> future;
    EXPECT_CALL(delegate(), HasPortPermission(_, _)).WillOnce(Return(true));
    EXPECT_CALL(client, OnPortConnectedStateChanged)
        .WillOnce(InvokeFuture(future));
    observer()->OnPortAdded(*ports[i]);
    EXPECT_EQ(future.Get()->token, bluetooth_token);
  }
}

TEST_F(SerialTest, AddAndRemovePorts) {
  NavigateAndCommit(GURL(kTestUrl));

  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, OnPortConnectedStateChanged)
        .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, OnPortConnectedStateChanged)
        .Times(2)
        .WillRepeatedly(base::test::RunClosure(closure));

    for (const auto& port : ports)
      observer()->OnPortRemoved(*port);
    run_loop.Run();
  }
}

TEST_F(SerialTest, PortConnectedState) {
  NavigateAndCommit(GURL(kTestUrl));

  mojo::Remote<blink::mojom::SerialService> service;
  contents()->GetPrimaryMainFrame()->BindSerialService(
      service.BindNewPipeAndPassReceiver());

  MockSerialServiceClient client;
  service->SetClient(client.BindNewPipeAndPassRemote());
  service.FlushForTesting();

  ASSERT_TRUE(observer());

  // Create a disconnected port.
  auto port = device::mojom::SerialPortInfo::New();
  port->token = base::UnguessableToken::Create();
  port->connected = false;

  EXPECT_CALL(delegate(), HasPortPermission).WillRepeatedly(Return(true));

  // Add the disconnected port. The client is not notified.
  EXPECT_CALL(client, OnPortConnectedStateChanged).Times(0);
  observer()->OnPortAdded(*port);
  base::RunLoop().RunUntilIdle();

  // Connect the port.
  TestFuture<blink::mojom::SerialPortInfoPtr> connect_future;
  EXPECT_CALL(client, OnPortConnectedStateChanged)
      .WillOnce(InvokeFuture(connect_future));
  port->connected = true;
  observer()->OnPortConnectedStateChanged(*port);
  EXPECT_EQ(connect_future.Get()->token, port->token);
  EXPECT_TRUE(connect_future.Get()->connected);

  // Disconnect the port.
  TestFuture<blink::mojom::SerialPortInfoPtr> disconnect_future;
  EXPECT_CALL(client, OnPortConnectedStateChanged)
      .WillOnce(InvokeFuture(disconnect_future));
  port->connected = false;
  observer()->OnPortConnectedStateChanged(*port);
  EXPECT_EQ(disconnect_future.Get()->token, port->token);
  EXPECT_FALSE(disconnect_future.Get()->connected);
}

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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  ASSERT_TRUE(observer());
  observer()->OnPortManagerConnectionError();
  EXPECT_FALSE(
      contents()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  ASSERT_TRUE(observer());
  url::Origin different_origin =
      url::Origin::Create(GURL("http://different-origin.com"));
  observer()->OnPermissionRevoked(different_origin);
  EXPECT_TRUE(
      contents()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));

  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([&](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()->IsCapabilityActive(WebContentsCapabilityType::kSerial));
  port1.FlushForTesting();
  EXPECT_FALSE(port1.is_connected());
  port2.FlushForTesting();
  EXPECT_TRUE(port2.is_connected());
  service.FlushForTesting();
  EXPECT_TRUE(service.is_connected());
}

TEST_F(SerialTest, RejectOpaqueOrigin) {
  // Create a fake dispatch context to trigger a bad message in.
  mojo::FakeMessageDispatchContext fake_dispatch_context;
  mojo::test::BadMessageObserver bad_message_observer;

  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
  response_headers->SetHeader("Content-Security-Policy",
                              "sandbox allow-scripts");
  auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
      GURL("https://opaque.com"), main_test_rfh());
  navigation_simulator->SetResponseHeaders(response_headers);
  navigation_simulator->Start();
  navigation_simulator->Commit();

  mojo::Remote<blink::mojom::SerialService> service;
  main_test_rfh()->BindSerialService(service.BindNewPipeAndPassReceiver());
  EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
            "Web Serial is not allowed when the top-level document has an "
            "opaque origin.");
}

TEST_F(SerialTest, RejectOpaqueOriginEmbeddedFrame) {
  // Create a fake dispatch context to trigger a bad message in.
  mojo::FakeMessageDispatchContext fake_dispatch_context;
  mojo::test::BadMessageObserver bad_message_observer;

  auto response_headers =
      base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
  response_headers->SetHeader("Content-Security-Policy",
                              "sandbox allow-scripts");
  auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
      GURL("https://opaque.com"), main_test_rfh());
  navigation_simulator->SetResponseHeaders(response_headers);
  navigation_simulator->Start();
  navigation_simulator->Commit();

  const GURL kEmbeddedUrl("https://opaque.com");
  RenderFrameHost* embedded_rfh =
      RenderFrameHostTester::For(main_test_rfh())
          ->AppendChildWithPolicy(
              "embedded_frame",
              {{network::mojom::PermissionsPolicyFeature::kSerial,
                /*allowed_origins=*/{},
                /*self_if_matches=*/url::Origin::Create(kEmbeddedUrl),
                /*matches_all_origins=*/false, /*matches_opaque_src=*/true}});
  embedded_rfh = NavigationSimulator::NavigateAndCommitFromDocument(
      kEmbeddedUrl, embedded_rfh);

  mojo::Remote<blink::mojom::SerialService> service;
  static_cast<TestRenderFrameHost*>(embedded_rfh)
      ->BindSerialService(service.BindNewPipeAndPassReceiver());
  EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
            "Web Serial is not allowed when the top-level document has an "
            "opaque origin.");
}

}  // namespace content