910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/bluetooth/web_bluetooth_service_impl.h"

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "content/browser/bluetooth/bluetooth_adapter_factory_wrapper.h"
#include "content/browser/bluetooth/web_bluetooth_service_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/bluetooth_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/test_web_contents.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
#include "url/gurl.h"

using testing::_;
using testing::Mock;
using testing::Return;

namespace content {

namespace {
constexpr char kDeviceAddress[] = "00:00:00:00:00:00";
constexpr char kHeartRateUUIDString[] = "0000180d-0000-1000-8000-00805f9b34fb";

using PromptEventCallback =
    base::OnceCallback<void(BluetoothScanningPrompt::Event)>;

class FakeBluetoothScanningPrompt : public BluetoothScanningPrompt {
 public:
  explicit FakeBluetoothScanningPrompt(
      PromptEventCallback prompt_event_callback)
      : prompt_event_callback_(std::move(prompt_event_callback)) {}
  ~FakeBluetoothScanningPrompt() override = default;

  FakeBluetoothScanningPrompt(const FakeBluetoothScanningPrompt&) = delete;
  FakeBluetoothScanningPrompt& operator=(const FakeBluetoothScanningPrompt&) =
      delete;

  void RunPromptEventCallback(Event event) {
    ASSERT_TRUE(prompt_event_callback_);
    std::move(prompt_event_callback_).Run(event);
  }

 private:
  PromptEventCallback prompt_event_callback_;
};

class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
 public:
  FakeBluetoothAdapter() = default;

  FakeBluetoothAdapter(const FakeBluetoothAdapter&) = delete;
  FakeBluetoothAdapter& operator=(const FakeBluetoothAdapter&) = delete;

  // device::BluetoothAdapter:
  device::BluetoothAdapter::ConstDeviceList GetDevices() const override {
    device::BluetoothAdapter::ConstDeviceList devices;
    for (const auto& it : mock_devices_)
      devices.push_back(it.get());
    return devices;
  }

  device::BluetoothDevice* GetDevice(const std::string& address) override {
    device::MockBluetoothAdapter::GetDevice(address);
    for (const auto& it : mock_devices_) {
      if (it->GetAddress() == address)
        return it.get();
    }
    return nullptr;
  }

  void StartScanWithFilter(
      std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
      DiscoverySessionResultCallback callback) override {
    std::move(callback).Run(
        /*is_error=*/false,
        device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
  }
  void StopScan(DiscoverySessionResultCallback callback) override {
    std::move(callback).Run(
        /*is_error=*/false,
        device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
  }

 private:
  ~FakeBluetoothAdapter() override = default;
};

class FakeBluetoothChooser : public content::BluetoothChooser {
 public:
  FakeBluetoothChooser(content::BluetoothChooser::EventHandler event_handler,
                       const std::string& device_to_select)
      : event_handler_(event_handler), device_to_select_(device_to_select) {}
  FakeBluetoothChooser(const FakeBluetoothChooser&) = delete;
  FakeBluetoothChooser& operator=(const FakeBluetoothChooser&) = delete;
  ~FakeBluetoothChooser() override = default;

  // content::BluetoothChooser implementation:
  void AddOrUpdateDevice(const std::string& device_id,
                         bool should_update_name,
                         const std::u16string& device_name,
                         bool is_gatt_connected,
                         bool is_paired,
                         int signal_strength_level) override {
    // Select the added device if its device ID matches |device_to_select_|.
    if (device_to_select_ == device_id)
      event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id);
  }

 private:
  content::BluetoothChooser::EventHandler event_handler_;
  std::string device_to_select_;
};

class TestBluetoothDelegate : public BluetoothDelegate {
 public:
  TestBluetoothDelegate() = default;
  ~TestBluetoothDelegate() override = default;
  TestBluetoothDelegate(const TestBluetoothDelegate&) = delete;
  TestBluetoothDelegate& operator=(const TestBluetoothDelegate&) = delete;

  void SetDeviceToSelect(const std::string& device_address) {
    device_to_select_ = device_address;
  }

  // BluetoothDelegate:
  std::unique_ptr<BluetoothChooser> RunBluetoothChooser(
      RenderFrameHost* frame,
      const BluetoothChooser::EventHandler& event_handler) override {
    return std::make_unique<FakeBluetoothChooser>(event_handler,
                                                  device_to_select_);
  }
  std::unique_ptr<BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
      RenderFrameHost* frame,
      const BluetoothScanningPrompt::EventHandler& event_handler) override {
    showed_bluetooth_scanning_prompt_ = true;
    DCHECK_EQ(frame->GetLifecycleState(),
              RenderFrameHost::LifecycleState::kActive);
    if (quit_on_scanning_prompt_)
      std::move(quit_on_scanning_prompt_).Run();
    auto prompt =
        std::make_unique<FakeBluetoothScanningPrompt>(std::move(event_handler));
    prompt_ = prompt.get();
    return std::move(prompt);
  }

  void ShowDevicePairPrompt(content::RenderFrameHost* frame,
                            const std::u16string& device_identifier,
                            PairPromptCallback callback,
                            PairingKind pairing_kind,
                            const std::optional<std::u16string>& pin) override {
    NOTREACHED();
  }

  blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
      RenderFrameHost* frame,
      const std::string& device_address) override {
    return blink::WebBluetoothDeviceId();
  }
  std::string GetDeviceAddress(RenderFrameHost* frame,
                               const blink::WebBluetoothDeviceId&) override {
    return std::string();
  }
  blink::WebBluetoothDeviceId AddScannedDevice(
      RenderFrameHost* frame,
      const std::string& device_address) override {
    return blink::WebBluetoothDeviceId();
  }
  blink::WebBluetoothDeviceId GrantServiceAccessPermission(
      RenderFrameHost* frame,
      const device::BluetoothDevice* device,
      const blink::mojom::WebBluetoothRequestDeviceOptions* options) override {
    return blink::WebBluetoothDeviceId();
  }
  bool HasDevicePermission(
      RenderFrameHost* frame,
      const blink::WebBluetoothDeviceId& device_id) override {
    return false;
  }
  void RevokeDevicePermissionWebInitiated(
      RenderFrameHost* frame,
      const blink::WebBluetoothDeviceId& device_id) override {}
  bool IsAllowedToAccessService(RenderFrameHost* frame,
                                const blink::WebBluetoothDeviceId& device_id,
                                const device::BluetoothUUID& service) override {
    return false;
  }
  bool MayUseBluetooth(RenderFrameHost* rfh) override { return true; }
  bool IsAllowedToAccessAtLeastOneService(
      RenderFrameHost* frame,
      const blink::WebBluetoothDeviceId& device_id) override {
    return false;
  }
  bool IsAllowedToAccessManufacturerData(
      RenderFrameHost* frame,
      const blink::WebBluetoothDeviceId& device_id,
      const uint16_t manufacturer_code) override {
    return false;
  }
  std::vector<blink::mojom::WebBluetoothDevicePtr> GetPermittedDevices(
      RenderFrameHost* frame) override {
    return {};
  }

  void AddFramePermissionObserver(FramePermissionObserver* observer) override {}
  void RemoveFramePermissionObserver(
      FramePermissionObserver* observer) override {}

  void WaitForShowBluetoothScanningPrompt() {
    if (showed_bluetooth_scanning_prompt_)
      return;
    base::RunLoop run_loop;
    quit_on_scanning_prompt_ = run_loop.QuitClosure();
    run_loop.Run();
  }
  void RunBluetoothScanningPromptEventCallback(
      BluetoothScanningPrompt::Event event) {
    ASSERT_TRUE(prompt_);
    prompt_->RunPromptEventCallback(event);
  }

  bool showed_bluetooth_scanning_prompt() {
    return showed_bluetooth_scanning_prompt_;
  }

  void reset_showed_bluetooth_scanning_prompt() {
    showed_bluetooth_scanning_prompt_ = false;
  }

 private:
  std::string device_to_select_;
  raw_ptr<FakeBluetoothScanningPrompt, DanglingUntriaged> prompt_ = nullptr;
  base::OnceClosure quit_on_scanning_prompt_;
  bool showed_bluetooth_scanning_prompt_ = false;
};

class TestContentBrowserClient : public ContentBrowserTestContentBrowserClient {
 public:
  TestContentBrowserClient() = default;
  ~TestContentBrowserClient() override = default;
  TestContentBrowserClient(const TestContentBrowserClient&) = delete;
  TestContentBrowserClient& operator=(const TestContentBrowserClient&) = delete;

  TestBluetoothDelegate* bluetooth_delegate() { return &bluetooth_delegate_; }

  AllowWebBluetoothResult AllowWebBluetooth(
      content::BrowserContext* browser_context,
      const url::Origin& requesting_origin,
      const url::Origin& embedding_origin) override {
    checked_allow_web_bluetooth_ = true;

    if (block_globally_disabled_)
      return AllowWebBluetoothResult::BLOCK_GLOBALLY_DISABLED;

    return ContentBrowserClient::AllowWebBluetooth(
        browser_context, requesting_origin, embedding_origin);
  }

  void block_globally_disabled() { block_globally_disabled_ = true; }

  bool checked_allow_web_bluetooth() { return checked_allow_web_bluetooth_; }

 protected:
  // ChromeContentBrowserClient:
  BluetoothDelegate* GetBluetoothDelegate() override {
    return &bluetooth_delegate_;
  }

 private:
  TestBluetoothDelegate bluetooth_delegate_;
  bool checked_allow_web_bluetooth_ = false;
  bool block_globally_disabled_ = false;
};

}  // namespace

class WebBluetoothServiceImplBrowserTest : public ContentBrowserTest {
 public:
  WebBluetoothServiceImplBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &WebBluetoothServiceImplBrowserTest::GetWebContents,
            base::Unretained(this))) {}
  ~WebBluetoothServiceImplBrowserTest() override = default;

  void SetUp() override {
    prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
    ContentBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    ASSERT_TRUE(test_server_handle_ =
                    embedded_test_server()->StartAndReturnHandle());

    // Hook up the test bluetooth delegate.
    browser_client_ = std::make_unique<TestContentBrowserClient>();
    SetFakeBlueboothAdapter();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Sets up the blink runtime feature for accessing to navigator.bluetooth.
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
  }

  void SetFakeBlueboothAdapter() {
    adapter_ = new FakeBluetoothAdapter();
    EXPECT_CALL(*adapter_, IsPresent()).WillRepeatedly(Return(true));
    BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter_);
  }

  void AddFakeDevice(const std::string& device_address) {
    const device::BluetoothUUID kHeartRateUUID(kHeartRateUUIDString);
    auto fake_device =
        std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
            adapter_.get(), /*bluetooth_class=*/0u,
            /*name=*/"Test Device", device_address,
            /*paired=*/true,
            /*connected=*/true);
    fake_device->AddUUID(kHeartRateUUID);
    fake_device->AddMockService(
        std::make_unique<testing::NiceMock<device::MockBluetoothGattService>>(
            fake_device.get(), kHeartRateUUIDString, kHeartRateUUID,
            /*is_primary=*/true));
    adapter_->AddMockDevice(std::move(fake_device));
  }

  void SetDeviceToSelect(const std::string& device_address) {
    browser_client_->bluetooth_delegate()->SetDeviceToSelect(device_address);
  }

  bool CheckedAllowWebBluetooth() {
    return browser_client_->checked_allow_web_bluetooth();
  }

  void BlockGloballyDisabled() { browser_client_->block_globally_disabled(); }

  WebBluetoothServiceImpl* GetWebBluetoothServiceOverride(
      RenderFrameHost* render_frame_host) {
    return WebBluetoothServiceImpl::GetForCurrentDocument(render_frame_host);
  }

  WebContents* GetWebContents() { return shell()->web_contents(); }
  TestBluetoothDelegate* GetBluetoothDelegate() {
    return browser_client_->bluetooth_delegate();
  }

  test::PrerenderTestHelper* prerender_helper() { return &prerender_helper_; }
  FakeBluetoothAdapter* adapter() { return adapter_.get(); }

 private:
  test::PrerenderTestHelper prerender_helper_;
  net::test_server::EmbeddedTestServerHandle test_server_handle_;
  scoped_refptr<FakeBluetoothAdapter> adapter_;
  std::unique_ptr<TestContentBrowserClient> browser_client_;
};

// Tests that the scanning prompt is not shown in the prerendering. It also
// ensures that ScanningClient is not created in the prerendering.
IN_PROC_BROWSER_TEST_F(WebBluetoothServiceImplBrowserTest,
                       NoShowBluetoothScanningPromptInPrerendering) {
  GURL url = embedded_test_server()->GetURL("/hello.html");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  EXPECT_CALL(*adapter(), AddObserver(_));
  ASSERT_TRUE(content::ExecJs(GetWebContents()->GetPrimaryMainFrame(), R"(
      var requestLEScanPromise = navigator.bluetooth.requestLEScan({
        acceptAllAdvertisements: true});
  )"));
  // Waits for ShowBluetoothScanningPrompt().
  GetBluetoothDelegate()->WaitForShowBluetoothScanningPrompt();
  // It should show the scanning prompt.
  EXPECT_TRUE(GetBluetoothDelegate()->showed_bluetooth_scanning_prompt());

  WebBluetoothServiceImpl* service_for_main_frame =
      GetWebBluetoothServiceOverride(GetWebContents()->GetPrimaryMainFrame());
  // ScanningClient with the main frame is created.
  EXPECT_EQ(service_for_main_frame->scanning_clients_.size(), 1u);

  GetBluetoothDelegate()->reset_showed_bluetooth_scanning_prompt();

  // Posts a task to simulate a prompt event during a call to
  // RequestScanningStart().
  GetBluetoothDelegate()->RunBluetoothScanningPromptEventCallback(
      BluetoothScanningPrompt::Event::kAllow);

  // Loads a page in the prerender.
  auto prerender_url = embedded_test_server()->GetURL("/empty.html");
  // The prerendering doesn't affect the current scanning.
  FrameTreeNodeId host_id = prerender_helper()->AddPrerender(prerender_url);
  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
                                                     host_id);
  RenderFrameHost* prerendered_frame_host =
      prerender_helper()->GetPrerenderedMainFrameHost(host_id);

  // A SecurityError is thrown when there is no user gesture.
  constexpr char kUserGestureError[] =
      "Must be handling a user gesture to show a permission request.";
  auto result = EvalJs(prerendered_frame_host, R"(
      navigator.bluetooth.requestLEScan({acceptAllAdvertisements: true});)",
                       content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE);
  EXPECT_THAT(result,
              EvalJsResult::ErrorIs(::testing::HasSubstr(kUserGestureError)));

  // The prerendering doesn't show the bluetoothscanning prompt.
  EXPECT_FALSE(GetBluetoothDelegate()->showed_bluetooth_scanning_prompt());
  // ScanningClient is not created in the prerendering.
  EXPECT_EQ(service_for_main_frame->scanning_clients_.size(), 1u);

  // Loading a new primary page removes observer and stops scanning.
  EXPECT_CALL(*adapter(), RemoveObserver(_));

  RenderFrameDeletedObserver rfh_observer(
      GetWebContents()->GetPrimaryMainFrame());

  // Navigates the primary page to the URL.
  prerender_helper()->NavigatePrimaryPage(prerender_url);
  // The page should be activated from the prerendering.
  EXPECT_TRUE(host_observer.was_activated());

  // Wait until the previous RFH to be disposed of, so a new bluetooth adapter
  // can be set after that.
  rfh_observer.WaitUntilDeleted();

  // Sets BluetoothAdapter for the new primary page since the previous
  // adapter is released by BluetoothAdapterFactoryWrapper::ReleaseAdapter().
  BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter());

  EXPECT_CALL(*adapter(), AddObserver(_));

  // Scanning after the prerendering activation to ensure it shows the prompt on
  // the activated page.
  EXPECT_TRUE(ExecJs(GetWebContents()->GetPrimaryMainFrame(), R"(
      var requestLEScanPromise = navigator.bluetooth.requestLEScan({
        acceptAllAdvertisements: true});)"));
  // Waits for ShowBluetoothScanningPrompt() since the page is activated.
  GetBluetoothDelegate()->WaitForShowBluetoothScanningPrompt();
  // It should show the scanning prompt.
  EXPECT_TRUE(GetBluetoothDelegate()->showed_bluetooth_scanning_prompt());

  WebBluetoothServiceImpl* service_for_activated_frame =
      GetWebBluetoothServiceOverride(GetWebContents()->GetPrimaryMainFrame());
  // ScanningClient is created after the prerendering activation.
  EXPECT_EQ(service_for_activated_frame->scanning_clients_.size(), 1u);

  // Post a task to simulate a prompt event during a call to
  // RequestScanningStart().
  GetBluetoothDelegate()->RunBluetoothScanningPromptEventCallback(
      BluetoothScanningPrompt::Event::kAllow);
  EXPECT_CALL(*adapter(), RemoveObserver(_));
}

// Tests that navigator.bluetooth.requestDevice() has an error without a user
// gesture in the prerendering and works in the prerendering activation.
IN_PROC_BROWSER_TEST_F(WebBluetoothServiceImplBrowserTest,
                       RequestDeviceInPrerendering) {
  GURL url = embedded_test_server()->GetURL("/hello.html");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // Setup the fake device.
  AddFakeDevice(kDeviceAddress);
  SetDeviceToSelect(kDeviceAddress);

  EXPECT_CALL(*adapter(), AddObserver(_));
  EXPECT_CALL(*adapter(), GetDevice(kDeviceAddress));

  EXPECT_EQ("", content::EvalJs(GetWebContents(), R"(
    (async() => {
      try {
        let device = await navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]});
        return "";
      } catch(e) {
        return `${e.name}: ${e.message}`;
      }
    })()
  )"));

  // WebBluetoothService is created for the main frame.
  EXPECT_NE(
      GetWebBluetoothServiceOverride(GetWebContents()->GetPrimaryMainFrame()),
      nullptr);

  // Loads a page in the prerender.
  auto prerender_url = embedded_test_server()->GetURL("/empty.html");
  FrameTreeNodeId host_id = prerender_helper()->AddPrerender(prerender_url);
  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
                                                     host_id);
  content::RenderFrameHost* prerendered_frame_host =
      prerender_helper()->GetPrerenderedMainFrameHost(host_id);

  // A SecurityError is thrown when there is no user gesture.
  constexpr char kUserGestureError[] =
      "Must be handling a user gesture to show a permission request.";
  auto result =
      content::EvalJs(prerendered_frame_host, R"(
      navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]}))",
                      content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE);
  EXPECT_THAT(result,
              EvalJsResult::ErrorIs(::testing::HasSubstr(kUserGestureError)));

  // WebBluetoothService is not created for `prerendered_frame_host`.
  EXPECT_EQ(GetWebBluetoothServiceOverride(prerendered_frame_host), nullptr);

  // Loading a new primary page removes observer.
  EXPECT_CALL(*adapter(), RemoveObserver(_));

  RenderFrameDeletedObserver rfh_observer(
      GetWebContents()->GetPrimaryMainFrame());

  // Navigate to the prerendered page.
  prerender_helper()->NavigatePrimaryPage(prerender_url);
  // The page should be activated from the prerendering.
  EXPECT_TRUE(host_observer.was_activated());

  // Wait until the previous RFH to be disposed of, so a new bluetooth adapter
  // can be set after that.
  rfh_observer.WaitUntilDeleted();

  // Sets BluetoothAdapter for the new primary page since the previous
  // adapter is released by BluetoothAdapterFactoryWrapper::ReleaseAdapter().
  BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter());
  EXPECT_CALL(*adapter(), AddObserver(_));
  EXPECT_CALL(*adapter(), GetDevice(kDeviceAddress));

  EXPECT_TRUE(content::ExecJs(GetWebContents()->GetPrimaryMainFrame(), R"(
      navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]}))"));

  // WebBluetoothService is created for the activated page.
  EXPECT_NE(
      GetWebBluetoothServiceOverride(GetWebContents()->GetPrimaryMainFrame()),
      nullptr);

  EXPECT_CALL(*adapter(), RemoveObserver(_));
}

// Tests that GetBluetoothAllowed() only works with the main page in order to
// ensure that it's no problem to get the main frame from the WebContents.
IN_PROC_BROWSER_TEST_F(WebBluetoothServiceImplBrowserTest,
                       GetBluetoothAllowedNotCalledInPrerendering) {
  GURL url = embedded_test_server()->GetURL("/hello.html");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // Loads a page in the prerender.
  auto prerender_url = embedded_test_server()->GetURL("/empty.html");
  // The prerendering doesn't affect the current scanning.
  FrameTreeNodeId host_id = prerender_helper()->AddPrerender(prerender_url);
  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
                                                     host_id);
  RenderFrameHost* prerendered_frame_host =
      prerender_helper()->GetPrerenderedMainFrameHost(host_id);

  // Runs JS asynchronously since Mojo calls are deferred during prerendering.
  content::DOMMessageQueue message_queue(prerendered_frame_host);
  content::ExecuteScriptAsync(prerendered_frame_host, R"(
    navigator.bluetooth.getAvailability()
    .then(isBluetoothAvailable => {
      window.domAutomationController.send('Done');
    });
  )");

  // WebBluetoothService is not created for `prerendered_frame_host`.
  EXPECT_EQ(GetWebBluetoothServiceOverride(prerendered_frame_host), nullptr);
  // It should not be called in the prerendering.
  EXPECT_FALSE(CheckedAllowWebBluetooth());

  // Navigates the primary page to the URL.
  prerender_helper()->NavigatePrimaryPage(prerender_url);
  // The page should be activated from the prerendering.
  EXPECT_TRUE(host_observer.was_activated());

  // Sets BlueboothAdapter for the new primary page since the previous
  // adapter is released by BluetoothAdapterFactoryWrapper::ReleaseAdapter().
  BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter());
  EXPECT_CALL(*adapter(), AddObserver(_));

  std::string message;
  do {
    ASSERT_TRUE(message_queue.WaitForMessage(&message));
  } while (message != "\"Done\"");

  // It should be called when activated.
  EXPECT_TRUE(CheckedAllowWebBluetooth());
  EXPECT_NE(GetWebBluetoothServiceOverride(prerendered_frame_host), nullptr);
  EXPECT_CALL(*adapter(), RemoveObserver(_));
}

// Tests that console messages have correct source frames.
IN_PROC_BROWSER_TEST_F(WebBluetoothServiceImplBrowserTest,
                       ConsoleLogFromSourceFrame) {
  WebContentsConsoleObserver console_observer(GetWebContents());
  constexpr char kConsoleLog[] = "Bluetooth permission has been blocked.";
  console_observer.SetPattern(kConsoleLog);

  // Block Web Bluetooth to get the console message.
  BlockGloballyDisabled();

  GURL url = embedded_test_server()->GetURL("/page_with_blank_iframe.html");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  EXPECT_CALL(*adapter(), AddObserver(_));

  RenderFrameHost* sub_frame = ChildFrameAt(GetWebContents(), 0);
  ASSERT_TRUE(sub_frame);

  constexpr char kErrorMessage[] =
      "NotFoundError: Web Bluetooth API globally disabled.";

  EXPECT_EQ(kErrorMessage, content::EvalJs(sub_frame, R"(
    (async() => {
      try {
        let device = await navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]});
        return "";
      } catch(e) {
        return `${e.name}: ${e.message}`;
      }
    })()
  )"));

  ASSERT_TRUE(console_observer.Wait());
  std::vector<WebContentsConsoleObserver::Message> messages =
      console_observer.messages();
  EXPECT_EQ(messages.size(), 1u);
  EXPECT_EQ(messages.back().source_frame, sub_frame);
  EXPECT_CALL(*adapter(), RemoveObserver(_));
}

class WebBluetoothServiceImplFencedFramesBrowserTest
    : public WebBluetoothServiceImplBrowserTest {
 public:
  WebBluetoothServiceImplFencedFramesBrowserTest() = default;
  ~WebBluetoothServiceImplFencedFramesBrowserTest() override = default;
  WebBluetoothServiceImplFencedFramesBrowserTest(
      const WebBluetoothServiceImplFencedFramesBrowserTest&) = delete;

  WebBluetoothServiceImplFencedFramesBrowserTest& operator=(
      const WebBluetoothServiceImplFencedFramesBrowserTest&) = delete;

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(WebBluetoothServiceImplFencedFramesBrowserTest,
                       BlockFromFencedFrame) {
  const GURL kInitialUrl = embedded_test_server()->GetURL("/hello.html");
  EXPECT_TRUE(NavigateToURL(shell(), kInitialUrl));

  // Setup the fake device.
  AddFakeDevice(kDeviceAddress);
  SetDeviceToSelect(kDeviceAddress);

  EXPECT_CALL(*adapter(), AddObserver(_));
  EXPECT_CALL(*adapter(), GetDevice(kDeviceAddress));

  EXPECT_EQ("", content::EvalJs(GetWebContents(), R"(
    (async() => {
      try {
        let device = await navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]});
        return "";
      } catch(e) {
        return `${e.name}: ${e.message}`;
      }
    })()
  )"));

  // WebBluetoothService is created for the main frame.
  EXPECT_NE(
      GetWebBluetoothServiceOverride(GetWebContents()->GetPrimaryMainFrame()),
      nullptr);

  // Loads a fenced frame
  const GURL kFencedFrameUrl =
      embedded_test_server()->GetURL("/fenced_frames/empty.html");
  content::RenderFrameHost* render_frame_host =
      fenced_frame_test_helper().CreateFencedFrame(
          GetWebContents()->GetPrimaryMainFrame(), kFencedFrameUrl);
  EXPECT_NE(nullptr, render_frame_host);

  // Tries to request a device from the fenced, which must cause an error.
  constexpr char kFencedFrameError[] =
      "Web Bluetooth is not allowed in a fenced frame tree.";
  auto result = content::EvalJs(render_frame_host, R"(
      navigator.bluetooth.requestDevice({
          filters: [{name: 'Test Device', services: ['heart_rate']}]}))");
  EXPECT_THAT(result,
              EvalJsResult::ErrorIs(::testing::HasSubstr(kFencedFrameError)));

  // No service should be created, as this is a fenced-frame
  EXPECT_EQ(nullptr, GetWebBluetoothServiceOverride(render_frame_host));

  EXPECT_CALL(*adapter(), RemoveObserver(GetWebBluetoothServiceOverride(
                              GetWebContents()->GetPrimaryMainFrame())));
}

}  // namespace content