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

// ID Not In Map Note: A service, characteristic, or descriptor ID not in the
// corresponding WebBluetoothServiceImpl map [service_id_to_device_address_,
// characteristic_id_to_service_id_, descriptor_id_to_characteristic_id_]
// implies a hostile renderer because a renderer obtains the corresponding ID
// from this class and it will be added to the map at that time.

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

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/queue.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "content/browser/bluetooth/advertisement_client.h"
#include "content/browser/bluetooth/bluetooth_adapter_factory_wrapper.h"
#include "content/browser/bluetooth/bluetooth_allowed_devices.h"
#include "content/browser/bluetooth/bluetooth_allowed_devices_map.h"
#include "content/browser/bluetooth/bluetooth_blocklist.h"
#include "content/browser/bluetooth/bluetooth_device_chooser_controller.h"
#include "content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h"
#include "content/browser/bluetooth/bluetooth_metrics.h"
#include "content/browser/bluetooth/bluetooth_util.h"
#include "content/browser/bluetooth/frame_connected_bluetooth_devices.h"
#include "content/browser/bluetooth/web_bluetooth_pairing_manager_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/bluetooth_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "device/bluetooth/bluetooth_discovery_session.h"
#include "device/bluetooth/bluetooth_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/bluetooth_gatt_notify_session.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_remote_gatt_descriptor.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.h"
#include "device/bluetooth/public/cpp/bluetooth_features.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"

namespace content {

namespace {

using ::device::BluetoothAdapter;
using ::device::BluetoothDevice;
using ::device::BluetoothDiscoverySession;
using ::device::BluetoothGattCharacteristic;
using ::device::BluetoothGattConnection;
using ::device::BluetoothGattNotifySession;
using ::device::BluetoothGattService;
using ::device::BluetoothRemoteGattCharacteristic;
using ::device::BluetoothRemoteGattDescriptor;
using ::device::BluetoothRemoteGattService;
using ::device::BluetoothUUID;
using GattErrorCode = ::device::BluetoothGattService::GattErrorCode;

// Client names for logging in BLE scanning.
constexpr char kScanClientNameWatchAdvertisements[] =
    "Web Bluetooth watchAdvertisements()";
constexpr char kScanClientNameRequestLeScan[] = "Web Bluetooth requestLeScan()";

// The renderer performs its own checks so a request that gets to the browser
// process indicates some failure to check for fenced frames.
const char kFencedFrameError[] =
    "Use of Web Bluetooth API is blocked in a <fencedframe> tree.";

blink::mojom::WebBluetoothResult TranslateGATTErrorAndRecord(
    GattErrorCode error_code,
    UMAGATTOperation operation) {
  switch (error_code) {
    case device::BluetoothRemoteGattService::GattErrorCode::kUnknown:
      RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::kUnknown);
      return blink::mojom::WebBluetoothResult::GATT_UNKNOWN_ERROR;
    case device::BluetoothRemoteGattService::GattErrorCode::kFailed:
      RecordGATTOperationOutcome(operation, UMAGATTOperationOutcome::kFailed);
      return blink::mojom::WebBluetoothResult::GATT_UNKNOWN_FAILURE;
    case device::BluetoothRemoteGattService::GattErrorCode::kInProgress:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kInProgress);
      return blink::mojom::WebBluetoothResult::GATT_OPERATION_IN_PROGRESS;
    case device::BluetoothRemoteGattService::GattErrorCode::kInvalidLength:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kInvalidLength);
      return blink::mojom::WebBluetoothResult::GATT_INVALID_ATTRIBUTE_LENGTH;
    case device::BluetoothRemoteGattService::GattErrorCode::kNotPermitted:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kNotPermitted);
      return blink::mojom::WebBluetoothResult::GATT_NOT_PERMITTED;
    case device::BluetoothRemoteGattService::GattErrorCode::kNotAuthorized:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kNotAuthorized);
      return blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED;
    case device::BluetoothRemoteGattService::GattErrorCode::kNotPaired:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kNotPaired);
      return blink::mojom::WebBluetoothResult::GATT_NOT_PAIRED;
    case device::BluetoothRemoteGattService::GattErrorCode::kNotSupported:
      RecordGATTOperationOutcome(operation,
                                 UMAGATTOperationOutcome::kNotSupported);
      return blink::mojom::WebBluetoothResult::GATT_NOT_SUPPORTED;
  }
  NOTREACHED();
}

// Max length of device name in filter. Bluetooth 5.0 3.C.3.2.2.3 states that
// the maximum device name length is 248 bytes (UTF-8 encoded).
constexpr size_t kMaxLengthForDeviceName = 248;

bool IsValidFilter(const blink::mojom::WebBluetoothLeScanFilterPtr& filter) {
  // At least one member needs to be present.
  if (!filter->name && !filter->name_prefix && !filter->services &&
      !filter->manufacturer_data) {
    return false;
  }

  // The |services| should not be empty.
  if (filter->services && filter->services->empty())
    return false;

  // The renderer will never send a |name| or a |name_prefix| longer than
  // kMaxLengthForDeviceName.
  if (filter->name && filter->name->size() > kMaxLengthForDeviceName)
    return false;

  if (filter->name_prefix &&
      filter->name_prefix->size() > kMaxLengthForDeviceName)
    return false;

  // The |name_prefix| should not be empty.
  if (filter->name_prefix && filter->name_prefix->empty())
    return false;

  // The |manufacturer_data| should not be empty.
  if (filter->manufacturer_data && filter->manufacturer_data->empty())
    return false;

  return true;
}

bool IsValidRequestDeviceOptions(
    const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
  if (options->accept_all_devices)
    return !options->filters.has_value();

  if (!HasValidFilter(options->filters)) {
    return false;
  }

  if (options->exclusion_filters.has_value()) {
    return HasValidFilter(options->exclusion_filters);
  }

  return true;
}

bool IsValidRequestScanOptions(
    const blink::mojom::WebBluetoothRequestLEScanOptionsPtr& options) {
  if (options->accept_all_advertisements)
    return !options->filters.has_value();

  return HasValidFilter(options->filters);
}

bool& ShouldIgnoreVisibilityRequirementsForTesting() {
  static bool should_ignore_visibility_requirements = false;
  return should_ignore_visibility_requirements;
}

}  // namespace

// Parameters for a call to RemoteCharacteristicStartNotifications. Used to
// defer notification starts when one is currently running for the same
// characteristic instance.
struct WebBluetoothServiceImpl::DeferredStartNotificationData {
  DeferredStartNotificationData(
      mojo::PendingAssociatedRemote<
          blink::mojom::WebBluetoothCharacteristicClient> client,
      RemoteCharacteristicStartNotificationsCallback callback)
      : client(std::move(client)), callback(std::move(callback)) {}

  ~DeferredStartNotificationData() = default;

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

  mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
      client;
  RemoteCharacteristicStartNotificationsCallback callback;
};

// static
blink::mojom::WebBluetoothResult
WebBluetoothServiceImpl::TranslateConnectErrorAndRecord(
    BluetoothDevice::ConnectErrorCode error_code) {
  switch (error_code) {
    case BluetoothDevice::ERROR_UNKNOWN:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kUnknown);
      return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_ERROR;
    case BluetoothDevice::ERROR_INPROGRESS:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kInProgress);
      return blink::mojom::WebBluetoothResult::CONNECT_ALREADY_IN_PROGRESS;
    case BluetoothDevice::ERROR_FAILED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kFailed);
      return blink::mojom::WebBluetoothResult::CONNECT_CONN_FAILED;
    case BluetoothDevice::ERROR_AUTH_FAILED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAuthFailed);
      return blink::mojom::WebBluetoothResult::CONNECT_AUTH_FAILED;
    case BluetoothDevice::ERROR_AUTH_CANCELED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAuthCanceled);
      return blink::mojom::WebBluetoothResult::CONNECT_AUTH_CANCELED;
    case BluetoothDevice::ERROR_AUTH_REJECTED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAuthRejected);
      return blink::mojom::WebBluetoothResult::CONNECT_AUTH_REJECTED;
    case BluetoothDevice::ERROR_AUTH_TIMEOUT:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAuthTimeout);
      return blink::mojom::WebBluetoothResult::CONNECT_AUTH_TIMEOUT;
    case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kUnsupportedDevice);
      return blink::mojom::WebBluetoothResult::CONNECT_UNSUPPORTED_DEVICE;
    case BluetoothDevice::ERROR_DEVICE_NOT_READY:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kNotReady);
      return blink::mojom::WebBluetoothResult::CONNECT_NOT_READY;
    case BluetoothDevice::ERROR_ALREADY_CONNECTED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAlreadyConnected);
      return blink::mojom::WebBluetoothResult::CONNECT_ALREADY_CONNECTED;
    case BluetoothDevice::ERROR_DEVICE_ALREADY_EXISTS:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kAlreadyExists);
      return blink::mojom::WebBluetoothResult::CONNECT_ALREADY_EXISTS;
    case BluetoothDevice::ERROR_DEVICE_UNCONNECTED:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kNotConnected);
      return blink::mojom::WebBluetoothResult::CONNECT_NOT_CONNECTED;
    case BluetoothDevice::ERROR_DOES_NOT_EXIST:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kDoesNotExist);
      return blink::mojom::WebBluetoothResult::CONNECT_DOES_NOT_EXIST;
    case BluetoothDevice::ERROR_INVALID_ARGS:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kInvalidArgs);
      return blink::mojom::WebBluetoothResult::CONNECT_INVALID_ARGS;
    case BluetoothDevice::ERROR_NON_AUTH_TIMEOUT:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kNonAuthTimeout);
      return blink::mojom::WebBluetoothResult::CONNECT_NON_AUTH_TIMEOUT;
    case device::BluetoothDevice::ERROR_NO_MEMORY:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kNoMemory);
      return blink::mojom::WebBluetoothResult::CONNECT_NO_MEMORY;
    case device::BluetoothDevice::ERROR_JNI_ENVIRONMENT:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kJniEnvironment);
      return blink::mojom::WebBluetoothResult::CONNECT_JNI_ENVIRONMENT;
    case device::BluetoothDevice::ERROR_JNI_THREAD_ATTACH:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kJniThreadAttach);
      return blink::mojom::WebBluetoothResult::CONNECT_JNI_THREAD_ATTACH;
    case device::BluetoothDevice::ERROR_WAKELOCK:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kWakelock);
      return blink::mojom::WebBluetoothResult::CONNECT_WAKELOCK;
    case device::BluetoothDevice::ERROR_UNEXPECTED_STATE:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kUnexpectedState);
      return blink::mojom::WebBluetoothResult::CONNECT_UNEXPECTED_STATE;
    case device::BluetoothDevice::ERROR_SOCKET:
      RecordConnectGATTOutcome(UMAConnectGATTOutcome::kSocketError);
      return blink::mojom::WebBluetoothResult::CONNECT_SOCKET_ERROR;
    case BluetoothDevice::NUM_CONNECT_ERROR_CODES:
      NOTREACHED();
  }
  NOTREACHED();
}

// static
void WebBluetoothServiceImpl::IgnoreVisibilityRequirementsForTesting() {
  ShouldIgnoreVisibilityRequirementsForTesting() = true;
}

bool HasValidFilter(
    const std::optional<std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>>&
        filters) {
  if (!filters) {
    return false;
  }

  return !filters->empty() && std::ranges::all_of(*filters, IsValidFilter);
}

// Struct that holds the result of a cache query.
struct CacheQueryResult {
  CacheQueryResult() : outcome(CacheQueryOutcome::kSuccess) {}

  explicit CacheQueryResult(CacheQueryOutcome outcome) : outcome(outcome) {}

  ~CacheQueryResult() {}

  blink::mojom::WebBluetoothResult GetWebResult() const {
    switch (outcome) {
      case CacheQueryOutcome::kSuccess:
      case CacheQueryOutcome::kBadRenderer:
        NOTREACHED();
      case CacheQueryOutcome::kNoDevice:
        return blink::mojom::WebBluetoothResult::DEVICE_NO_LONGER_IN_RANGE;
      case CacheQueryOutcome::kNoService:
        return blink::mojom::WebBluetoothResult::SERVICE_NO_LONGER_EXISTS;
      case CacheQueryOutcome::kNoCharacteristic:
        return blink::mojom::WebBluetoothResult::
            CHARACTERISTIC_NO_LONGER_EXISTS;
      case CacheQueryOutcome::kNoDescriptor:
        return blink::mojom::WebBluetoothResult::DESCRIPTOR_NO_LONGER_EXISTS;
    }
    NOTREACHED();
  }

  raw_ptr<BluetoothDevice> device = nullptr;
  raw_ptr<BluetoothRemoteGattService> service = nullptr;
  raw_ptr<BluetoothRemoteGattCharacteristic> characteristic = nullptr;
  raw_ptr<BluetoothRemoteGattDescriptor> descriptor = nullptr;
  CacheQueryOutcome outcome;
};

struct GATTNotifySessionAndCharacteristicClient {
  GATTNotifySessionAndCharacteristicClient(
      mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
          client)
      : characteristic_client(std::move(client)) {}

  std::unique_ptr<BluetoothGattNotifySession> gatt_notify_session;
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
      characteristic_client;
};

// static
void WebBluetoothServiceImpl::BindIfAllowed(
    RenderFrameHost* render_frame_host,
    mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK(render_frame_host);

  if (render_frame_host->IsNestedWithinFencedFrame()) {
    // The renderer is supposed to disallow the use of web bluetooth when inside
    // a fenced frame. Anything getting past the renderer checks must be marked
    // as a bad request.
    mojo::ReportBadMessage(kFencedFrameError);
    return;
  }

  if (render_frame_host->GetOutermostMainFrame()
          ->GetLastCommittedOrigin()
          .opaque()) {
    mojo::ReportBadMessage(
        "Web Bluetooth is not allowed from an opaque origin.");
    return;
  }

  auto* impl = GetOrCreateForCurrentDocument(render_frame_host);
  if (!impl->Bind(std::move(receiver))) {
    // The renderer should only ever try to bind one instance of this service
    // per document.
    mojo::ReportBadMessage("Web Bluetooth already bound for current document.");
  }
}

WebBluetoothServiceImpl* WebBluetoothServiceImpl::CreateForTesting(
    RenderFrameHost* render_frame_host,
    mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) {
  WebBluetoothServiceImpl::BindIfAllowed(render_frame_host,
                                         std::move(receiver));
  return WebBluetoothServiceImpl::GetForCurrentDocument(render_frame_host);
}

DOCUMENT_USER_DATA_KEY_IMPL(WebBluetoothServiceImpl);

WebBluetoothServiceImpl::WebBluetoothServiceImpl(
    RenderFrameHost* render_frame_host)
    : DocumentUserData(render_frame_host),
      WebContentsObserver(WebContents::FromRenderFrameHost(render_frame_host)),
      receiver_(this),
      connected_devices_(new FrameConnectedBluetoothDevices(*render_frame_host))
#if PAIR_BLUETOOTH_ON_DEMAND()
      ,
      pairing_manager_(std::make_unique<WebBluetoothPairingManagerImpl>(this))
#endif
{
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(web_contents());

  if (!base::FeatureList::IsEnabled(
          features::kWebBluetoothAllowGetAvailabilityWithBfcache)) {
    PreventBackForwardCache();
  }

  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (delegate) {
      observer_.Observe(delegate);
    }
  }
}

bool WebBluetoothServiceImpl::Bind(
    mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) {
  if (receiver_.is_bound()) {
    return false;
  }
  receiver_.Bind(std::move(receiver));
  return true;
}

WebBluetoothServiceImpl::~WebBluetoothServiceImpl() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

#if PAIR_BLUETOOTH_ON_DEMAND()
  // Destroy the pairing manager before releasing the adapter to give it an
  // opportunity to cancel pairing operations that are in progress.
  pairing_manager_.reset();
#endif

  BluetoothAdapterFactoryWrapper::Get().ReleaseAdapter(this);

  // Force destructor of device_scanning_prompt_controller_ happening before
  // members destruction stage to prevent use-after-free accessing
  // scanning_clients_.empty().
  device_scanning_prompt_controller_.reset();
}

blink::mojom::WebBluetoothResult
WebBluetoothServiceImpl::GetBluetoothAllowed() {
  // The use of render_frame_host().GetMainFrame() below is safe as fenced
  // frames are disallowed.
  DCHECK(!render_frame_host().IsNestedWithinFencedFrame());

  BluetoothDelegate* delegate =
      GetContentClient()->browser()->GetBluetoothDelegate();
  if (!delegate || !delegate->MayUseBluetooth(&render_frame_host())) {
    return blink::mojom::WebBluetoothResult::WEB_BLUETOOTH_NOT_SUPPORTED;
  }

  // Check if Web Bluetooth is allowed by Permissions Policy.
  if (!render_frame_host().IsFeatureEnabled(
          network::mojom::PermissionsPolicyFeature::kBluetooth)) {
    return blink::mojom::WebBluetoothResult::PERMISSIONS_POLICY_VIOLATION;
  }

  const url::Origin& requesting_origin = origin();
  const url::Origin& embedding_origin =
      render_frame_host().GetMainFrame()->GetLastCommittedOrigin();

  // Some embedders that don't support Web Bluetooth indicate this by not
  // returning a chooser.
  // TODO(crbug.com/41476036): Perform this check once there is a way to
  // check if a platform is capable of producing a chooser and return a
  // |blink::mojom::WebBluetoothResult::WEB_BLUETOOTH_NOT_SUPPORTED| error.
  switch (GetContentClient()->browser()->AllowWebBluetooth(
      web_contents()->GetBrowserContext(), requesting_origin,
      embedding_origin)) {
    case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_POLICY:
      return blink::mojom::WebBluetoothResult::
          CHOOSER_NOT_SHOWN_API_LOCALLY_DISABLED;
    case ContentBrowserClient::AllowWebBluetoothResult::BLOCK_GLOBALLY_DISABLED:
      return blink::mojom::WebBluetoothResult::
          CHOOSER_NOT_SHOWN_API_GLOBALLY_DISABLED;
    case ContentBrowserClient::AllowWebBluetoothResult::ALLOW:
      return blink::mojom::WebBluetoothResult::SUCCESS;
  }
}

bool WebBluetoothServiceImpl::IsDevicePaired(
    const std::string& device_address) {
  return GetWebBluetoothDeviceId(device_address).IsValid();
}

void WebBluetoothServiceImpl::OnBluetoothScanningPromptEvent(
    BluetoothScanningPrompt::Event event,
    BluetoothDeviceScanningPromptController* prompt_controller) {
  // The use of render_frame_host().GetMainFrame() below is safe as fenced
  // frames are disallowed.
  DCHECK(!render_frame_host().IsNestedWithinFencedFrame());

  // It is possible for |scanning_clients_| to be empty if a Mojo connection
  // error has occurred before this method was called.
  if (scanning_clients_.empty())
    return;

  auto& client = scanning_clients_.back();

  DCHECK(client->prompt_controller() == prompt_controller);

  auto result = blink::mojom::WebBluetoothResult::SUCCESS;
  if (event == BluetoothScanningPrompt::Event::kAllow) {
    result = blink::mojom::WebBluetoothResult::SUCCESS;
    StoreAllowedScanOptions(client->scan_options());
  } else if (event == BluetoothScanningPrompt::Event::kBlock) {
    result = blink::mojom::WebBluetoothResult::SCANNING_BLOCKED;
    const url::Origin requesting_origin = origin();
    const url::Origin embedding_origin =
        render_frame_host().GetMainFrame()->GetLastCommittedOrigin();
    GetContentClient()->browser()->BlockBluetoothScanning(
        web_contents()->GetBrowserContext(), requesting_origin,
        embedding_origin);
  } else if (event == BluetoothScanningPrompt::Event::kCanceled) {
    result = blink::mojom::WebBluetoothResult::PROMPT_CANCELED;
  } else {
    NOTREACHED();
  }

  client->RunCallback(std::move(result));
  client->set_prompt_controller(nullptr);
  if (event == BluetoothScanningPrompt::Event::kAllow) {
    client->set_allow_send_event(true);
  } else if (event == BluetoothScanningPrompt::Event::kBlock) {
    // Here because user explicitly blocks the permission to do Bluetooth
    // scanning in one request, it can be interpreted as user wants the current
    // and all previous scanning to be blocked, so remove all existing scanning
    // clients.
    scanning_clients_.clear();
    allowed_scan_filters_.clear();
    accept_all_advertisements_ = false;
  } else if (event == BluetoothScanningPrompt::Event::kCanceled) {
    scanning_clients_.pop_back();
  } else {
    NOTREACHED();
  }
}

void WebBluetoothServiceImpl::OnPermissionRevoked(const url::Origin& origin) {
  // The use of render_frame_host().GetMainFrame() below is safe as fenced
  // frames are disallowed.
  DCHECK(!render_frame_host().IsNestedWithinFencedFrame());

  if (render_frame_host().GetMainFrame()->GetLastCommittedOrigin() != origin) {
    return;
  }

  BluetoothDelegate* delegate =
      GetContentClient()->browser()->GetBluetoothDelegate();
  if (!delegate)
    return;

  std::set<blink::WebBluetoothDeviceId> permitted_ids;
  for (const auto& device : delegate->GetPermittedDevices(&render_frame_host()))
    permitted_ids.insert(device->id);

  connected_devices_->CloseConnectionsToDevicesNotInList(permitted_ids);

  std::erase_if(watch_advertisements_clients_,
                [&](const std::unique_ptr<WatchAdvertisementsClient>& client) {
                  return !base::Contains(permitted_ids, client->device_id());
                });

  MaybeStopDiscovery();
}

content::RenderFrameHost* WebBluetoothServiceImpl::GetRenderFrameHost() {
  return &render_frame_host();
}

void WebBluetoothServiceImpl::OnVisibilityChanged(Visibility visibility) {
  if (ShouldIgnoreVisibilityRequirementsForTesting()) {
    return;
  }

  if (visibility == content::Visibility::HIDDEN ||
      visibility == content::Visibility::OCCLUDED) {
    ClearAdvertisementClients();
  }
}

void WebBluetoothServiceImpl::OnWebContentsLostFocus(
    RenderWidgetHost* render_widget_host) {
  if (ShouldIgnoreVisibilityRequirementsForTesting()) {
    return;
  }

  ClearAdvertisementClients();
}

void WebBluetoothServiceImpl::AdapterPoweredChanged(BluetoothAdapter* adapter,
                                                    bool powered) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (device_chooser_controller_.get()) {
    device_chooser_controller_->AdapterPoweredChanged(powered);
  }
}

void WebBluetoothServiceImpl::DeviceAdded(BluetoothAdapter* adapter,
                                          BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (device_chooser_controller_.get()) {
    device_chooser_controller_->AddFilteredDevice(*device);
  }
}

void WebBluetoothServiceImpl::DeviceChanged(BluetoothAdapter* adapter,
                                            BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (device_chooser_controller_.get()) {
    device_chooser_controller_->AddFilteredDevice(*device);
  }

  if (!device->IsGattConnected()) {
    std::optional<blink::WebBluetoothDeviceId> device_id =
        connected_devices_->CloseConnectionToDeviceWithAddress(
            device->GetAddress());

    // Since the device disconnected we need to send an error for pending
    // primary services requests.
    RunPendingPrimaryServicesRequests(device);
  }
}

void WebBluetoothServiceImpl::DeviceAdvertisementReceived(
    const std::string& device_address,
    const std::optional<std::string>& device_name,
    const std::optional<std::string>& advertisement_name,
    std::optional<int8_t> rssi,
    std::optional<int8_t> tx_power,
    std::optional<uint16_t> appearance,
    const BluetoothDevice::UUIDList& advertised_uuids,
    const BluetoothDevice::ServiceDataMap& service_data_map,
    const BluetoothDevice::ManufacturerDataMap& manufacturer_data_map) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!HasActiveDiscoverySession())
    return;

  // Construct the WebBluetoothAdvertisingEvent.
  auto device = blink::mojom::WebBluetoothDevice::New();
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate)
      return;
    device->id =
        delegate->AddScannedDevice(&render_frame_host(), device_address);
  } else {
    device->id = allowed_devices().AddDevice(device_address);
  }
  device->name = device_name;

  auto result = blink::mojom::WebBluetoothAdvertisingEvent::New();
  result->device = std::move(device);

  result->name = advertisement_name;

  // Note about the default value for these optional types. On the other side of
  // this IPC, the receiver will be checking to see if |*_is_set| is true before
  // using the value. Here we chose reasonable defaults in case the other side
  // does something incorrect. We have to do this manual serialization because
  // mojo does not support optional primitive types.
  result->appearance_is_set = appearance.has_value();
  result->appearance = appearance.value_or(/*not present=*/0xffc0);

  result->rssi_is_set = rssi.has_value();
  result->rssi = rssi.value_or(/*invalid value=*/128);

  result->tx_power_is_set = tx_power.has_value();
  result->tx_power = tx_power.value_or(/*invalid value=*/128);

  std::vector<BluetoothUUID> uuids;
  for (auto& uuid : advertised_uuids)
    uuids.push_back(BluetoothUUID(uuid.canonical_value()));
  result->uuids = std::move(uuids);

  auto& manufacturer_data = result->manufacturer_data;
  for (const auto& entry : manufacturer_data_map) {
    manufacturer_data.emplace(
        blink::mojom::WebBluetoothCompany::New(entry.first), entry.second);
  }

  auto& service_data = result->service_data;
  service_data.insert(service_data_map.begin(), service_data_map.end());

  // TODO(crbug.com/40132791): These two classes can potentially be
  // combined into the same container.
  for (const auto& scanning_client : scanning_clients_)
    scanning_client->SendEvent(*result);

  for (const auto& watch_advertisements_client : watch_advertisements_clients_)
    watch_advertisements_client->SendEvent(*result);

  MaybeStopDiscovery();
}

void WebBluetoothServiceImpl::GattServicesDiscovered(BluetoothAdapter* adapter,
                                                     BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  DVLOG(1) << "Services discovered for device: " << device->GetAddress();

  if (device_chooser_controller_.get()) {
    device_chooser_controller_->AddFilteredDevice(*device);
  }

  RunPendingPrimaryServicesRequests(device);
}

void WebBluetoothServiceImpl::GattCharacteristicValueChanged(
    BluetoothAdapter* adapter,
    BluetoothRemoteGattCharacteristic* characteristic,
    const std::vector<uint8_t>& value) {
  // Don't notify of characteristics that we haven't returned.
  if (!base::Contains(characteristic_id_to_service_id_,
                      characteristic->GetIdentifier())) {
    return;
  }

  // On Chrome OS and Linux, GattCharacteristicValueChanged is called before the
  // success callback for ReadRemoteCharacteristic is called, which could result
  // in an event being fired before the readValue promise is resolved.
  if (!base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(
              &WebBluetoothServiceImpl::NotifyCharacteristicValueChanged,
              weak_ptr_factory_.GetWeakPtr(), characteristic->GetIdentifier(),
              value))) {
    LOG(WARNING) << "No TaskRunner.";
  }
}

void WebBluetoothServiceImpl::NotifyCharacteristicValueChanged(
    const std::string& characteristic_instance_id,
    const std::vector<uint8_t>& value) {
  auto iter =
      characteristic_id_to_notify_session_.find(characteristic_instance_id);
  if (iter != characteristic_id_to_notify_session_.end()) {
    iter->second->characteristic_client->RemoteCharacteristicValueChanged(
        value);
  }
}

void WebBluetoothServiceImpl::GetAvailability(
    GetAvailabilityCallback callback) {
  if (GetBluetoothAllowed() != blink::mojom::WebBluetoothResult::SUCCESS) {
    std::move(callback).Run(/*result=*/false);
    return;
  }

  if (!BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) {
    std::move(callback).Run(/*result=*/false);
    return;
  }

  auto get_availability_impl = base::BindOnce(
      [](GetAvailabilityCallback callback,
         scoped_refptr<BluetoothAdapter> adapter) {
        std::move(callback).Run(adapter->IsPresent());
      },
      std::move(callback));

  auto* adapter = GetAdapter();
  if (adapter) {
    std::move(get_availability_impl).Run(adapter);
    return;
  }

  BluetoothAdapterFactoryWrapper::Get().AcquireAdapter(
      this, std::move(get_availability_impl));
}

void WebBluetoothServiceImpl::RequestDevice(
    blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
    RequestDeviceCallback callback) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothAllowGetAvailabilityWithBfcache)) {
    PreventBackForwardCache();
  }

  RecordRequestDeviceOptions(options);

  if (!GetAdapter()) {
    if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) {
      BluetoothAdapterFactoryWrapper::Get().AcquireAdapter(
          this, base::BindOnce(&WebBluetoothServiceImpl::RequestDeviceImpl,
                               weak_ptr_factory_.GetWeakPtr(),
                               std::move(options), std::move(callback)));
      return;
    }
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE,
        nullptr /* device */);
    return;
  }
  RequestDeviceImpl(std::move(options), std::move(callback), GetAdapter());
}

void WebBluetoothServiceImpl::GetDevices(GetDevicesCallback callback) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothAllowGetAvailabilityWithBfcache)) {
    PreventBackForwardCache();
  }

  if (GetBluetoothAllowed() != blink::mojom::WebBluetoothResult::SUCCESS ||
      !BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) {
    std::move(callback).Run({});
    return;
  }

  auto* adapter = GetAdapter();
  if (adapter) {
    GetDevicesImpl(std::move(callback), adapter);
    return;
  }

  BluetoothAdapterFactoryWrapper::Get().AcquireAdapter(
      this,
      base::BindOnce(&WebBluetoothServiceImpl::GetDevicesImpl,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void WebBluetoothServiceImpl::ForgetDevice(
    const blink::WebBluetoothDeviceId& device_id,
    ForgetDeviceCallback callback) {
  CHECK(back_forward_cache_feature_handle_.IsValid());
  if (!base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    auto device_address = allowed_devices().GetDeviceAddress(device_id);
    // allowed_devices().RemoveDevice() expects a valid |device_address|.
    if (!device_address.empty()) {
      allowed_devices().RemoveDevice(device_address);
    }
    std::move(callback).Run();
    return;
  }

  BluetoothDelegate* delegate =
      GetContentClient()->browser()->GetBluetoothDelegate();
  if (delegate &&
      delegate->HasDevicePermission(&render_frame_host(), device_id)) {
    delegate->RevokeDevicePermissionWebInitiated(&render_frame_host(),
                                                 device_id);
  }
  std::move(callback).Run();
}

void WebBluetoothServiceImpl::RemoteServerConnect(
    const blink::WebBluetoothDeviceId& device_id,
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothServerClient>
        client,
    RemoteServerConnectCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  bool is_connect_allowed = false;
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (delegate) {
      is_connect_allowed =
          delegate->HasDevicePermission(&render_frame_host(), device_id);
    }
  } else {
    is_connect_allowed = allowed_devices().IsAllowedToGATTConnect(device_id);
  }
  if (!is_connect_allowed) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED);
    return;
  }

  const CacheQueryResult query_result = QueryCacheForDevice(device_id);

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    RecordConnectGATTOutcome(query_result.outcome);
    std::move(callback).Run(query_result.GetWebResult());
    return;
  }

  if (connected_devices_->IsConnectedToDeviceWithId(device_id)) {
    DVLOG(1) << "Already connected.";
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
    return;
  }

  // It's possible for WebBluetoothServiceImpl to issue two successive
  // connection requests for which it would get two successive responses
  // and consequently try to insert two BluetoothGattConnections for the
  // same device. WebBluetoothServiceImpl should reject or queue connection
  // requests if there is a pending connection already, but the platform
  // abstraction doesn't currently support checking for pending connections.
  // TODO(ortuno): CHECK that this never happens once the platform
  // abstraction allows to check for pending connections.
  // http://crbug.com/583544
  mojo::AssociatedRemote<blink::mojom::WebBluetoothServerClient>
      web_bluetooth_server_client(std::move(client));

  if (base::FeatureList::IsEnabled(
          blink::features::kWebBluetoothCancelConnect)) {
    pending_connection_device_ids_.insert(device_id);
  }
  query_result.device->CreateGattConnection(base::BindOnce(
      &WebBluetoothServiceImpl::OnCreateGATTConnection,
      weak_ptr_factory_.GetWeakPtr(), device_id,
      std::move(web_bluetooth_server_client), std::move(callback)));
}

void WebBluetoothServiceImpl::RemoteServerDisconnect(
    const blink::WebBluetoothDeviceId& device_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  if (base::FeatureList::IsEnabled(
          blink::features::kWebBluetoothCancelConnect)) {
    auto connecting_iter = pending_connection_device_ids_.find(device_id);
    if (connecting_iter != pending_connection_device_ids_.end()) {
      pending_connection_device_ids_.erase(connecting_iter);
      const CacheQueryResult query_result = QueryCacheForDevice(device_id);
      if (query_result.outcome != CacheQueryOutcome::kSuccess) {
        return;
      }
      query_result.device->DisconnectGatt();
      return;
    }
  }

  if (connected_devices_->IsConnectedToDeviceWithId(device_id)) {
    DVLOG(1) << "Disconnecting device: " << device_id.str();
    connected_devices_->CloseConnectionToDeviceWithId(device_id);
  }
}

void WebBluetoothServiceImpl::RemoteServerGetPrimaryServices(
    const blink::WebBluetoothDeviceId& device_id,
    blink::mojom::WebBluetoothGATTQueryQuantity quantity,
    const std::optional<BluetoothUUID>& services_uuid,
    RemoteServerGetPrimaryServicesCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  RecordGetPrimaryServicesServices(quantity, services_uuid);

  if (!IsAllowedToAccessAtLeastOneService(device_id)) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_ANY_SERVICE,
        /*service=*/std::nullopt);
    return;
  }

  if (services_uuid &&
      !IsAllowedToAccessService(device_id, services_uuid.value())) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_SERVICE,
        /*service=*/std::nullopt);
    return;
  }

  const CacheQueryResult query_result = QueryCacheForDevice(device_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    std::move(callback).Run(query_result.GetWebResult(),
                            std::nullopt /* service */);
    return;
  }

  RemoteServerGetPrimaryServicesImpl(device_id, quantity, services_uuid,
                                     std::move(callback), query_result.device);
}

void WebBluetoothServiceImpl::RemoteServiceGetCharacteristics(
    const std::string& service_instance_id,
    blink::mojom::WebBluetoothGATTQueryQuantity quantity,
    const std::optional<BluetoothUUID>& characteristics_uuid,
    RemoteServiceGetCharacteristicsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  RecordGetCharacteristicsCharacteristic(quantity, characteristics_uuid);

  if (characteristics_uuid &&
      BluetoothBlocklist::Get().IsExcluded(characteristics_uuid.value())) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLOCKLISTED_CHARACTERISTIC_UUID,
        std::nullopt /* characteristics */);
    return;
  }

  const CacheQueryResult query_result =
      QueryCacheForService(service_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    std::move(callback).Run(query_result.GetWebResult(),
                            std::nullopt /* characteristics */);
    return;
  }

  std::vector<BluetoothRemoteGattCharacteristic*> characteristics =
      characteristics_uuid ? query_result.service->GetCharacteristicsByUUID(
                                 characteristics_uuid.value())
                           : query_result.service->GetCharacteristics();

  std::vector<blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr>
      response_characteristics;
  for (BluetoothRemoteGattCharacteristic* characteristic : characteristics) {
    if (BluetoothBlocklist::Get().IsExcluded(characteristic->GetUUID())) {
      continue;
    }
    std::string characteristic_instance_id = characteristic->GetIdentifier();
    auto insert_result = characteristic_id_to_service_id_.insert(
        std::make_pair(characteristic_instance_id, service_instance_id));
    // If value is already in map, DCHECK it's valid.
    if (!insert_result.second)
      DCHECK(insert_result.first->second == service_instance_id);

    blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr characteristic_ptr =
        blink::mojom::WebBluetoothRemoteGATTCharacteristic::New();
    characteristic_ptr->instance_id = characteristic_instance_id;
    characteristic_ptr->uuid = characteristic->GetUUID();
    characteristic_ptr->properties =
        static_cast<uint32_t>(characteristic->GetProperties());
    response_characteristics.push_back(std::move(characteristic_ptr));

    if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) {
      break;
    }
  }

  if (!response_characteristics.empty()) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS,
                            std::move(response_characteristics));
    return;
  }

  std::move(callback).Run(
      characteristics_uuid
          ? blink::mojom::WebBluetoothResult::CHARACTERISTIC_NOT_FOUND
          : blink::mojom::WebBluetoothResult::NO_CHARACTERISTICS_FOUND,
      std::nullopt /* characteristics */);
}

void WebBluetoothServiceImpl::RemoteCharacteristicGetDescriptors(
    const std::string& characteristic_instance_id,
    blink::mojom::WebBluetoothGATTQueryQuantity quantity,
    const std::optional<BluetoothUUID>& descriptors_uuid,
    RemoteCharacteristicGetDescriptorsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  if (descriptors_uuid &&
      BluetoothBlocklist::Get().IsExcluded(descriptors_uuid.value())) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLOCKLISTED_DESCRIPTOR_UUID,
        std::nullopt /* descriptor */);
    return;
  }

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    std::move(callback).Run(query_result.GetWebResult(),
                            std::nullopt /* descriptor */);
    return;
  }

  auto descriptors = descriptors_uuid
                         ? query_result.characteristic->GetDescriptorsByUUID(
                               descriptors_uuid.value())
                         : query_result.characteristic->GetDescriptors();

  std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr>
      response_descriptors;
  for (BluetoothRemoteGattDescriptor* descriptor : descriptors) {
    if (BluetoothBlocklist::Get().IsExcluded(descriptor->GetUUID())) {
      continue;
    }
    std::string descriptor_instance_id = descriptor->GetIdentifier();
    auto insert_result = descriptor_id_to_characteristic_id_.insert(
        {descriptor_instance_id, characteristic_instance_id});
    // If value is already in map, DCHECK it's valid.
    if (!insert_result.second)
      DCHECK(insert_result.first->second == characteristic_instance_id);

    auto descriptor_ptr(blink::mojom::WebBluetoothRemoteGATTDescriptor::New());
    descriptor_ptr->instance_id = descriptor_instance_id;
    descriptor_ptr->uuid = descriptor->GetUUID();
    response_descriptors.push_back(std::move(descriptor_ptr));

    if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) {
      break;
    }
  }

  if (!response_descriptors.empty()) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS,
                            std::move(response_descriptors));
    return;
  }
  std::move(callback).Run(
      descriptors_uuid ? blink::mojom::WebBluetoothResult::DESCRIPTOR_NOT_FOUND
                       : blink::mojom::WebBluetoothResult::NO_DESCRIPTORS_FOUND,
      std::nullopt /* descriptors */);
}

void WebBluetoothServiceImpl::RemoteCharacteristicReadValue(
    const std::string& characteristic_instance_id,
    RemoteCharacteristicReadValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    RecordCharacteristicReadValueOutcome(query_result.outcome);
    std::move(callback).Run(query_result.GetWebResult(),
                            /*value=*/{});
    return;
  }

  if (BluetoothBlocklist::Get().IsExcludedFromReads(
          query_result.characteristic->GetUUID())) {
    RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::kBlocklisted);
    std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
                            /*value=*/{});
    return;
  }

  query_result.characteristic->ReadRemoteCharacteristic(
      base::BindOnce(&WebBluetoothServiceImpl::OnCharacteristicReadValue,
                     weak_ptr_factory_.GetWeakPtr(), characteristic_instance_id,
                     std::move(callback)));
}

void WebBluetoothServiceImpl::RemoteCharacteristicWriteValue(
    const std::string& characteristic_instance_id,
    base::span<const uint8_t> value,
    blink::mojom::WebBluetoothWriteType write_type,
    RemoteCharacteristicWriteValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  // We perform the length check on the renderer side. So if we
  // get a value with length > 512, we can assume it's a hostile
  // renderer and kill it.
  if (value.size() > 512) {
    ReceivedBadMessage(bad_message::BDH_INVALID_WRITE_VALUE_LENGTH);
    return;
  }

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    RecordCharacteristicWriteValueOutcome(query_result.outcome);
    std::move(callback).Run(query_result.GetWebResult());
    return;
  }

  if (BluetoothBlocklist::Get().IsExcludedFromWrites(
          query_result.characteristic->GetUUID())) {
    RecordCharacteristicWriteValueOutcome(
        UMAGATTOperationOutcome::kBlocklisted);
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
    return;
  }

  // TODO(crbug.com/40524549): Remove SplitOnceCallback() by updating
  // the callee interface.
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  base::OnceClosure write_callback = base::BindOnce(
      &WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess,
      weak_ptr_factory_.GetWeakPtr(), std::move(split_callback.first));
  BluetoothGattCharacteristic::ErrorCallback write_error_callback =
      base::BindOnce(&WebBluetoothServiceImpl::OnCharacteristicWriteValueFailed,
                     weak_ptr_factory_.GetWeakPtr(), characteristic_instance_id,
                     base::ToVector(value), write_type,
                     std::move(split_callback.second));
  using WebBluetoothWriteType = blink::mojom::WebBluetoothWriteType;
  using WriteType = BluetoothRemoteGattCharacteristic::WriteType;
  switch (write_type) {
    case WebBluetoothWriteType::kWriteDefaultDeprecated:
      query_result.characteristic->DeprecatedWriteRemoteCharacteristic(
          value, std::move(write_callback), std::move(write_error_callback));
      break;
    case WebBluetoothWriteType::kWriteWithResponse:
      query_result.characteristic->WriteRemoteCharacteristic(
          value, WriteType::kWithResponse, std::move(write_callback),
          std::move(write_error_callback));
      break;
    case WebBluetoothWriteType::kWriteWithoutResponse:
      query_result.characteristic->WriteRemoteCharacteristic(
          value, WriteType::kWithoutResponse, std::move(write_callback),
          std::move(write_error_callback));
      break;
  }
}

void WebBluetoothServiceImpl::RemoteCharacteristicStartNotificationsInternal(
    const std::string& characteristic_instance_id,
    mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
        client,
    RemoteCharacteristicStartNotificationsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);
  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    RecordStartNotificationsOutcome(UMAGATTOperationOutcome::kNotSupported);
    std::move(callback).Run(query_result.GetWebResult());
    return;
  }

  characteristic_id_to_notify_session_[characteristic_instance_id] =
      std::make_unique<GATTNotifySessionAndCharacteristicClient>(
          std::move(client));

  // TODO(crbug.com/40524549): Remove SplitOnceCallback() by updating
  // the callee interface.
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  query_result.characteristic->StartNotifySession(
      base::BindOnce(&WebBluetoothServiceImpl::OnStartNotifySessionSuccess,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(split_callback.first)),
      base::BindOnce(&WebBluetoothServiceImpl::OnStartNotifySessionFailed,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(split_callback.second),
                     characteristic_instance_id));
}

void WebBluetoothServiceImpl::RemoteCharacteristicStartNotifications(
    const std::string& characteristic_instance_id,
    mojo::PendingAssociatedRemote<
        blink::mojom::WebBluetoothCharacteristicClient> client,
    RemoteCharacteristicStartNotificationsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  auto iter =
      characteristic_id_to_notify_session_.find(characteristic_instance_id);
  if (iter != characteristic_id_to_notify_session_.end()) {
    const auto& notification_client = iter->second;
    if (!notification_client->gatt_notify_session) {
      // There is an in-flight startNotification being processed which is
      // awaiting a notify session. Defer this start, and continue once the
      // in-flight start has completed.
      characteristic_id_to_deferred_start_[characteristic_instance_id].emplace(
          std::make_unique<DeferredStartNotificationData>(std::move(client),
                                                          std::move(callback)));
      return;
    }
    if (notification_client->gatt_notify_session->IsActive()) {
      // If the frame has already started notifications and the notifications
      // are active we return SUCCESS.
      std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
      return;
    }
  }

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    RecordStartNotificationsOutcome(UMAGATTOperationOutcome::kNotSupported);
    std::move(callback).Run(query_result.GetWebResult());
    return;
  }

  BluetoothRemoteGattCharacteristic::Properties notify_or_indicate =
      query_result.characteristic->GetProperties() &
      (BluetoothRemoteGattCharacteristic::PROPERTY_NOTIFY |
       BluetoothRemoteGattCharacteristic::PROPERTY_INDICATE);
  if (!notify_or_indicate) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::GATT_NOT_SUPPORTED);
    return;
  }

  // Create entry in the notify session map - even before the notification
  // is successfully registered. This allows clients to send value change
  // notifications during the notification registration process.
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
      characteristic_client(std::move(client));

  RemoteCharacteristicStartNotificationsInternal(
      characteristic_instance_id, std::move(characteristic_client),
      std::move(callback));
}

void WebBluetoothServiceImpl::RemoteCharacteristicStopNotifications(
    const std::string& characteristic_instance_id,
    RemoteCharacteristicStopNotificationsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  const CacheQueryResult query_result =
      QueryCacheForCharacteristic(characteristic_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  auto notify_session_iter =
      characteristic_id_to_notify_session_.find(characteristic_instance_id);
  if (notify_session_iter == characteristic_id_to_notify_session_.end()) {
    // If the frame hasn't subscribed to notifications before we just
    // run the callback.
    std::move(callback).Run();
    return;
  }
  notify_session_iter->second->gatt_notify_session->Stop(
      base::BindOnce(&WebBluetoothServiceImpl::OnStopNotifySessionComplete,
                     weak_ptr_factory_.GetWeakPtr(), characteristic_instance_id,
                     std::move(callback)));
}

void WebBluetoothServiceImpl::RemoteDescriptorReadValue(
    const std::string& descriptor_instance_id,
    RemoteDescriptorReadValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  const CacheQueryResult query_result =
      QueryCacheForDescriptor(descriptor_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    std::move(callback).Run(query_result.GetWebResult(),
                            /*value=*/{});
    return;
  }

  if (BluetoothBlocklist::Get().IsExcludedFromReads(
          query_result.descriptor->GetUUID())) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
                            /*value=*/{});
    return;
  }

  query_result.descriptor->ReadRemoteDescriptor(
      base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorReadValue,
                     weak_ptr_factory_.GetWeakPtr(), descriptor_instance_id,
                     std::move(callback)));
}

void WebBluetoothServiceImpl::RemoteDescriptorWriteValue(
    const std::string& descriptor_instance_id,
    base::span<const uint8_t> value,
    RemoteDescriptorWriteValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  // We perform the length check on the renderer side. So if we
  // get a value with length > 512, we can assume it's a hostile
  // renderer and kill it.
  if (value.size() > 512) {
    ReceivedBadMessage(bad_message::BDH_INVALID_WRITE_VALUE_LENGTH);
    return;
  }

  const CacheQueryResult query_result =
      QueryCacheForDescriptor(descriptor_instance_id);

  if (query_result.outcome == CacheQueryOutcome::kBadRenderer) {
    return;
  }

  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    std::move(callback).Run(query_result.GetWebResult());
    return;
  }

  if (BluetoothBlocklist::Get().IsExcludedFromWrites(
          query_result.descriptor->GetUUID())) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
    return;
  }

  // TODO(crbug.com/40524549): Remove SplitOnceCallback() by updating
  // the callee interface.
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  query_result.descriptor->WriteRemoteDescriptor(
      value,
      base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(split_callback.first)),
      base::BindOnce(&WebBluetoothServiceImpl::OnDescriptorWriteValueFailed,
                     weak_ptr_factory_.GetWeakPtr(), descriptor_instance_id,
                     base::ToVector(value), std::move(split_callback.second)));
}

void WebBluetoothServiceImpl::RequestScanningStart(
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
        client_remote,
    blink::mojom::WebBluetoothRequestLEScanOptionsPtr options,
    RequestScanningStartCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // The use of render_frame_host().GetMainFrame() below is safe as fenced
  // frames are disallowed.
  DCHECK(!render_frame_host().IsNestedWithinFencedFrame());

  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothAllowGetAvailabilityWithBfcache)) {
    PreventBackForwardCache();
  }

  const url::Origin requesting_origin = origin();
  const url::Origin embedding_origin =
      render_frame_host().GetMainFrame()->GetLastCommittedOrigin();

  bool blocked = GetContentClient()->browser()->IsBluetoothScanningBlocked(
      web_contents()->GetBrowserContext(), requesting_origin, embedding_origin);
  if (blocked) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED);
    return;
  }

  // The renderer should never send invalid options.
  if (!IsValidRequestScanOptions(options)) {
    ReceivedBadMessage(bad_message::BDH_INVALID_OPTIONS);
    return;
  }

  if (!GetAdapter()) {
    if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) {
      BluetoothAdapterFactoryWrapper::Get().AcquireAdapter(
          this, base::BindOnce(
                    &WebBluetoothServiceImpl::RequestScanningStartImpl,
                    weak_ptr_factory_.GetWeakPtr(), std::move(client_remote),
                    std::move(options), std::move(callback)));
      return;
    }
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE);
    return;
  }

  RequestScanningStartImpl(std::move(client_remote), std::move(options),
                           std::move(callback), GetAdapter());
}

void WebBluetoothServiceImpl::WatchAdvertisementsForDevice(
    const blink::WebBluetoothDeviceId& device_id,
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
        client_remote,
    WatchAdvertisementsForDeviceCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(back_forward_cache_feature_handle_.IsValid());

  blink::mojom::WebBluetoothResult allowed_result = GetBluetoothAllowed();
  if (allowed_result != blink::mojom::WebBluetoothResult::SUCCESS) {
    std::move(callback).Run(allowed_result);
    return;
  }

  // The renderer should never send an invalid |device_id|.
  if (!device_id.IsValid()) {
    ReceivedBadMessage(bad_message::BDH_INVALID_OPTIONS);
    return;
  }

  if (!GetAdapter()) {
    if (BluetoothAdapterFactoryWrapper::Get().IsLowEnergySupported()) {
      BluetoothAdapterFactoryWrapper::Get().AcquireAdapter(
          this, base::BindOnce(
                    &WebBluetoothServiceImpl::WatchAdvertisementsForDeviceImpl,
                    weak_ptr_factory_.GetWeakPtr(), device_id,
                    std::move(client_remote), std::move(callback)));
      return;
    }
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE);
    return;
  }

  WatchAdvertisementsForDeviceImpl(std::move(device_id),
                                   std::move(client_remote),
                                   std::move(callback), GetAdapter());
}

void WebBluetoothServiceImpl::RemoveDisconnectedClients() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // TODO(crbug.com/40132791): These two classes can potentially be
  // combined into the same container.
  std::erase_if(scanning_clients_,
                [](const std::unique_ptr<ScanningClient>& client) {
                  return !client->is_connected();
                });
  std::erase_if(watch_advertisements_clients_,
                [](const std::unique_ptr<WatchAdvertisementsClient>& client) {
                  return !client->is_connected();
                });
  MaybeStopDiscovery();
}

void WebBluetoothServiceImpl::MaybeStopDiscovery() {
  if (scanning_clients_.empty())
    ble_scan_discovery_session_.reset();

  if (watch_advertisements_clients_.empty())
    watch_advertisements_discovery_session_.reset();
}

void WebBluetoothServiceImpl::RequestScanningStartImpl(
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
        client_remote,
    blink::mojom::WebBluetoothRequestLEScanOptionsPtr options,
    RequestScanningStartCallback callback,
    scoped_refptr<BluetoothAdapter> adapter) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!adapter) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE);
    return;
  }

  if (request_scanning_start_callback_) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::PROMPT_CANCELED);
    return;
  }

  if (ble_scan_discovery_session_) {
    auto scanning_client = std::make_unique<ScanningClient>(
        /*service=*/this, std::move(client_remote), std::move(options),
        std::move(callback));

    if (AreScanFiltersAllowed(scanning_client->scan_options().filters)) {
      scanning_client->RunCallback(blink::mojom::WebBluetoothResult::SUCCESS);
      scanning_client->set_allow_send_event(true);
      scanning_clients_.push_back(std::move(scanning_client));
      return;
    }

    // By resetting |device_scanning_prompt_controller_|, it returns an error if
    // there are duplicate calls to RequestScanningStart().
    device_scanning_prompt_controller_ =
        std::make_unique<BluetoothDeviceScanningPromptController>(
            this, render_frame_host());
    scanning_client->SetPromptController(
        device_scanning_prompt_controller_.get());
    scanning_clients_.push_back(std::move(scanning_client));
    device_scanning_prompt_controller_->ShowPermissionPrompt();
    return;
  }

  request_scanning_start_callback_ = std::move(callback);

  // TODO(crbug.com/40630111): Since scanning without a filter wastes
  // resources, we need use StartDiscoverySessionWithFilter() instead of
  // StartDiscoverySession() here.
  adapter->StartDiscoverySession(
      kScanClientNameRequestLeScan,
      base::BindOnce(
          &WebBluetoothServiceImpl::OnStartDiscoverySessionForScanning,
          weak_ptr_factory_.GetWeakPtr(), std::move(client_remote),
          std::move(options)),
      base::BindOnce(
          &WebBluetoothServiceImpl::OnDiscoverySessionErrorForScanning,
          weak_ptr_factory_.GetWeakPtr()));
}

void WebBluetoothServiceImpl::OnStartDiscoverySessionForScanning(
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
        client_remote,
    blink::mojom::WebBluetoothRequestLEScanOptionsPtr options,
    std::unique_ptr<BluetoothDiscoverySession> session) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!ble_scan_discovery_session_);

  ble_scan_discovery_session_ = std::move(session);

  auto scanning_client = std::make_unique<ScanningClient>(
      /*service=*/this, std::move(client_remote), std::move(options),
      std::move(request_scanning_start_callback_));

  if (AreScanFiltersAllowed(scanning_client->scan_options().filters)) {
    scanning_client->RunCallback(blink::mojom::WebBluetoothResult::SUCCESS);
    scanning_client->set_allow_send_event(true);
    scanning_clients_.push_back(std::move(scanning_client));
    return;
  }

  device_scanning_prompt_controller_ =
      std::make_unique<BluetoothDeviceScanningPromptController>(
          this, render_frame_host());
  scanning_client->SetPromptController(
      device_scanning_prompt_controller_.get());
  scanning_clients_.push_back(std::move(scanning_client));
  device_scanning_prompt_controller_->ShowPermissionPrompt();
}

void WebBluetoothServiceImpl::OnDiscoverySessionErrorForScanning() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  device_scanning_prompt_controller_.reset();

  std::move(request_scanning_start_callback_)
      .Run(blink::mojom::WebBluetoothResult::NO_BLUETOOTH_ADAPTER);
  ClearAdvertisementClients();
}

void WebBluetoothServiceImpl::RequestDeviceImpl(
    blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
    RequestDeviceCallback callback,
    scoped_refptr<BluetoothAdapter> adapter) {
  // The renderer should never send invalid options.
  if (!IsValidRequestDeviceOptions(options)) {
    ReceivedBadMessage(bad_message::BDH_INVALID_OPTIONS);
    return;
  }

  // Calls to requestDevice() require user activation (user gestures).  We
  // should close any opened chooser when a duplicate requestDevice call is
  // made with the same user activation or when any gesture occurs outside
  // of the opened chooser. This does not happen on all platforms so we
  // don't DCHECK that the old one is closed.  We destroy the old chooser
  // before constructing the new one to make sure they can't conflict.
  device_chooser_controller_.reset();

  device_chooser_controller_ =
      std::make_unique<BluetoothDeviceChooserController>(
          this, render_frame_host(), std::move(adapter));
  device_chooser_controller_->GetDevice(
      std::move(options),
      base::BindOnce(&WebBluetoothServiceImpl::OnGetDevice,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void WebBluetoothServiceImpl::GetDevicesImpl(
    GetDevicesCallback callback,
    scoped_refptr<BluetoothAdapter> adapter) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate) {
      std::move(callback).Run({});
      return;
    }

    std::move(callback).Run(
        delegate->GetPermittedDevices(&render_frame_host()));
    return;
  }

  // BluetoothAllowedDevices does not provide a way to get all of the
  // permitted devices, so instead return all of the allowed devices that
  // are currently known to the system.
  std::vector<blink::mojom::WebBluetoothDevicePtr> web_bluetooth_devices;
  for (const auto* device : adapter->GetDevices()) {
    const blink::WebBluetoothDeviceId* device_id =
        allowed_devices().GetDeviceId(device->GetAddress());
    if (!device_id || !allowed_devices().IsAllowedToGATTConnect(*device_id))
      continue;

    web_bluetooth_devices.push_back(
        blink::mojom::WebBluetoothDevice::New(*device_id, device->GetName()));
  }
  std::move(callback).Run(std::move(web_bluetooth_devices));
}

void WebBluetoothServiceImpl::WatchAdvertisementsForDeviceImpl(
    const blink::WebBluetoothDeviceId& device_id,
    mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
        client_remote,
    WatchAdvertisementsForDeviceCallback callback,
    scoped_refptr<BluetoothAdapter> adapter) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!adapter) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLUETOOTH_LOW_ENERGY_NOT_AVAILABLE);
    return;
  }

  auto pending_client = std::make_unique<WatchAdvertisementsClient>(
      /*service=*/this, std::move(client_remote), std::move(device_id),
      std::move(callback));
  if (watch_advertisements_discovery_session_) {
    pending_client->RunCallback(blink::mojom::WebBluetoothResult::SUCCESS);
    watch_advertisements_clients_.push_back(std::move(pending_client));
    return;
  }

  // If |watch_advertisements_pending_clients_| has more than one client,
  // then it means that a previous watch advertisements operation has already
  // started a discovery session, so the |pending_client| for this
  // operation needs to be stored until the start discovery operation is
  // complete.
  watch_advertisements_pending_clients_.push_back(std::move(pending_client));
  if (watch_advertisements_pending_clients_.size() > 1) {
    return;
  }

  // Not all platforms support filtering by address.
  // TODO(crbug.com/40630111): Use StartDiscoverySessionWithFilter() to
  // filter out by MAC address when platforms provide this capability.
  adapter->StartDiscoverySession(
      kScanClientNameWatchAdvertisements,
      base::BindOnce(&WebBluetoothServiceImpl::
                         OnStartDiscoverySessionForWatchAdvertisements,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&WebBluetoothServiceImpl::
                         OnDiscoverySessionErrorForWatchAdvertisements,
                     weak_ptr_factory_.GetWeakPtr()));
}

void WebBluetoothServiceImpl::OnStartDiscoverySessionForWatchAdvertisements(
    std::unique_ptr<BluetoothDiscoverySession> session) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!watch_advertisements_discovery_session_);
  watch_advertisements_discovery_session_ = std::move(session);

  BluetoothDelegate* delegate =
      GetContentClient()->browser()->GetBluetoothDelegate();

  for (auto& pending_client : watch_advertisements_pending_clients_) {
    // Check if |pending_client| is still alive.
    if (!pending_client->is_connected()) {
      pending_client->RunCallback(
          blink::mojom::WebBluetoothResult::WATCH_ADVERTISEMENTS_ABORTED);
      continue;
    }

    // If the new permissions backend is enabled, verify the permission using
    // the delegate.
    if (base::FeatureList::IsEnabled(
            features::kWebBluetoothNewPermissionsBackend) &&
        (!delegate || !delegate->HasDevicePermission(
                          &render_frame_host(), pending_client->device_id()))) {
      pending_client->RunCallback(
          blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_ANY_SERVICE);
      continue;
    }

    // Otherwise verify it via |allowed_devices|.
    if (!base::FeatureList::IsEnabled(
            features::kWebBluetoothNewPermissionsBackend) &&
        !allowed_devices().IsAllowedToGATTConnect(
            pending_client->device_id())) {
      pending_client->RunCallback(
          blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_ANY_SERVICE);
      continue;
    }

    // Here we already make sure that pending_client is still alive and have
    // permissions. Add it to |watch_advertisements_clients_|.
    pending_client->RunCallback(blink::mojom::WebBluetoothResult::SUCCESS);
    watch_advertisements_clients_.push_back(std::move(pending_client));
  }

  watch_advertisements_pending_clients_.clear();

  // If a client was disconnected while a discovery session was being started,
  // then there may not be any valid clients, so discovery should be stopped.
  MaybeStopDiscovery();
}

void WebBluetoothServiceImpl::OnDiscoverySessionErrorForWatchAdvertisements() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  for (auto& pending_client : watch_advertisements_pending_clients_) {
    pending_client->RunCallback(
        blink::mojom::WebBluetoothResult::NO_BLUETOOTH_ADAPTER);
  }
  watch_advertisements_pending_clients_.clear();
  ClearAdvertisementClients();
}

void WebBluetoothServiceImpl::RemoteServerGetPrimaryServicesImpl(
    const blink::WebBluetoothDeviceId& device_id,
    blink::mojom::WebBluetoothGATTQueryQuantity quantity,
    const std::optional<BluetoothUUID>& services_uuid,
    RemoteServerGetPrimaryServicesCallback callback,
    BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!device->IsGattConnected()) {
    // The device disconnected while discovery was pending. The returned error
    // does not matter because the renderer ignores the error if the device
    // disconnected.
    std::move(callback).Run(blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND,
                            std::nullopt /* services */);
    return;
  }

  // We can't know if a service is present or not until GATT service discovery
  // is complete for the device.
  if (!device->IsGattServicesDiscoveryComplete()) {
    DVLOG(1) << "Services not yet discovered.";
    pending_primary_services_requests_[device->GetAddress()].push_back(
        base::BindOnce(
            &WebBluetoothServiceImpl::RemoteServerGetPrimaryServicesImpl,
            base::Unretained(this), device_id, quantity, services_uuid,
            std::move(callback)));
    return;
  }

  DCHECK(device->IsGattServicesDiscoveryComplete());

  std::vector<BluetoothRemoteGattService*> services =
      services_uuid ? device->GetPrimaryServicesByUUID(services_uuid.value())
                    : device->GetPrimaryServices();

  std::vector<blink::mojom::WebBluetoothRemoteGATTServicePtr> response_services;
  for (BluetoothRemoteGattService* service : services) {
    if (!IsAllowedToAccessService(device_id, service->GetUUID()))
      continue;

    std::string service_instance_id = service->GetIdentifier();
    const std::string& device_address = device->GetAddress();
    auto insert_result = service_id_to_device_address_.insert(
        make_pair(service_instance_id, device_address));
    // If value is already in map, DCHECK it's valid.
    if (!insert_result.second)
      DCHECK_EQ(insert_result.first->second, device_address);

    blink::mojom::WebBluetoothRemoteGATTServicePtr service_ptr =
        blink::mojom::WebBluetoothRemoteGATTService::New();
    service_ptr->instance_id = service_instance_id;
    service_ptr->uuid = service->GetUUID();
    response_services.push_back(std::move(service_ptr));

    if (quantity == blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE) {
      break;
    }
  }

  if (!response_services.empty()) {
    DVLOG(1) << "Services found in device.";
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS,
                            std::move(response_services));
    return;
  }

  DVLOG(1) << "Services not found in device.";
  std::move(callback).Run(
      services_uuid ? blink::mojom::WebBluetoothResult::SERVICE_NOT_FOUND
                    : blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND,
      std::nullopt /* services */);
}

void WebBluetoothServiceImpl::OnGetDevice(
    RequestDeviceCallback callback,
    blink::mojom::WebBluetoothResult result,
    blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
    const std::string& device_address) {
  device_chooser_controller_.reset();

  if (result != blink::mojom::WebBluetoothResult::SUCCESS) {
    // Errors are recorded by |device_chooser_controller_|.
    std::move(callback).Run(result, /*device=*/nullptr);
    return;
  }

  const BluetoothDevice* const device = GetAdapter()->GetDevice(device_address);
  if (device == nullptr) {
    DVLOG(1) << "Device " << device_address << " no longer in adapter";
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::CHOSEN_DEVICE_VANISHED,
        nullptr /* device */);
    return;
  }

  DVLOG(1) << "Device: " << device->GetNameForDisplay();

  auto web_bluetooth_device = blink::mojom::WebBluetoothDevice::New();
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate) {
      std::move(callback).Run(
          blink::mojom::WebBluetoothResult::WEB_BLUETOOTH_NOT_SUPPORTED,
          /*device=*/nullptr);
      return;
    }
    web_bluetooth_device->id = delegate->GrantServiceAccessPermission(
        &render_frame_host(), device, options.get());
  } else {
    web_bluetooth_device->id =
        allowed_devices().AddDevice(device_address, options);
  }
  web_bluetooth_device->name = device->GetName();

  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS,
                          std::move(web_bluetooth_device));
}

void WebBluetoothServiceImpl::OnCreateGATTConnection(
    const blink::WebBluetoothDeviceId& device_id,
    mojo::AssociatedRemote<blink::mojom::WebBluetoothServerClient> client,
    RemoteServerConnectCallback callback,
    std::unique_ptr<BluetoothGattConnection> connection,
    std::optional<BluetoothDevice::ConnectErrorCode> error_code) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (base::FeatureList::IsEnabled(
          blink::features::kWebBluetoothCancelConnect)) {
    pending_connection_device_ids_.erase(device_id);
  }
  if (error_code.has_value()) {
    std::move(callback).Run(TranslateConnectErrorAndRecord(error_code.value()));
    return;
  }
  RecordConnectGATTOutcome(UMAConnectGATTOutcome::kSuccess);

  if (connected_devices_->IsConnectedToDeviceWithId(device_id)) {
    DVLOG(1) << "Already connected.";
    std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
    return;
  }

  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
  connected_devices_->Insert(device_id, std::move(connection),
                             std::move(client));
}

void WebBluetoothServiceImpl::OnCharacteristicReadValue(
    const std::string& characteristic_instance_id,
    RemoteCharacteristicReadValueCallback callback,
    std::optional<GattErrorCode> error_code,
    const std::vector<uint8_t>& value) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (error_code.has_value()) {
#if PAIR_BLUETOOTH_ON_DEMAND()
    if (error_code.value() == GattErrorCode::kNotAuthorized ||
        error_code.value() == GattErrorCode::kNotPaired) {
      BluetoothDevice* device = GetCachedDevice(
          GetCharacteristicDeviceID(characteristic_instance_id));
      if (device && !device->IsPaired()) {
        // Initiate pairing. See (Secure Characteristics) in README.md for more
        // information.
        pairing_manager_->PairForCharacteristicReadValue(
            characteristic_instance_id, std::move(callback));
        return;
      }
    }
#endif  // PAIR_BLUETOOTH_ON_DEMAND()
    std::move(callback).Run(
        TranslateGATTErrorAndRecord(error_code.value(),
                                    UMAGATTOperation::kCharacteristicRead),
        /*value=*/{});
    return;
  }
  RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome::kSuccess);
  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, value);
}

void WebBluetoothServiceImpl::OnCharacteristicWriteValueSuccess(
    RemoteCharacteristicWriteValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome::kSuccess);
  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
}

void WebBluetoothServiceImpl::OnCharacteristicWriteValueFailed(
    const std::string& characteristic_instance_id,
    const std::vector<uint8_t>& value,
    blink::mojom::WebBluetoothWriteType write_type,
    RemoteCharacteristicWriteValueCallback callback,
    GattErrorCode error_code) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

#if PAIR_BLUETOOTH_ON_DEMAND()
  if (error_code == GattErrorCode::kNotAuthorized) {
    BluetoothDevice* device =
        GetCachedDevice(GetCharacteristicDeviceID(characteristic_instance_id));
    if (device && !device->IsPaired()) {
      // Initiate pairing. See (Secure Characteristics) in README.md for more
      // information.
      pairing_manager_->PairForCharacteristicWriteValue(
          characteristic_instance_id, value, write_type, std::move(callback));
      return;
    }
  }
#endif  // PAIR_BLUETOOTH_ON_DEMAND()

  std::move(callback).Run(TranslateGATTErrorAndRecord(
      error_code, UMAGATTOperation::kCharacteristicWrite));
}

void WebBluetoothServiceImpl::OnStartNotifySessionSuccess(
    RemoteCharacteristicStartNotificationsCallback callback,
    std::unique_ptr<BluetoothGattNotifySession> notify_session) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
  std::string characteristic_id = notify_session->GetCharacteristicIdentifier();
  auto iter = characteristic_id_to_notify_session_.find(characteristic_id);

  if (iter == characteristic_id_to_notify_session_.end())
    return;
  // Saving the BluetoothGattNotifySession keeps notifications active.
  iter->second->gatt_notify_session = std::move(notify_session);

  // Continue any deferred notification starts.
  auto deferred_iter =
      characteristic_id_to_deferred_start_.find(characteristic_id);
  if (deferred_iter != characteristic_id_to_deferred_start_.end()) {
    base::queue<std::unique_ptr<DeferredStartNotificationData>> deferral_queue =
        std::move(deferred_iter->second);
    characteristic_id_to_deferred_start_.erase(deferred_iter);
    while (!deferral_queue.empty()) {
      RemoteCharacteristicStartNotifications(
          characteristic_id, std::move(deferral_queue.front()->client),
          std::move(deferral_queue.front()->callback));
      deferral_queue.pop();
    }
  }
}

void WebBluetoothServiceImpl::OnStartNotifySessionFailed(
    RemoteCharacteristicStartNotificationsCallback callback,
    const std::string& characteristic_instance_id,
    GattErrorCode error_code) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto iter =
      characteristic_id_to_notify_session_.find(characteristic_instance_id);
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client;
  if (iter != characteristic_id_to_notify_session_.end()) {
    client = std::move(iter->second->characteristic_client);
    characteristic_id_to_notify_session_.erase(iter);
  }

#if PAIR_BLUETOOTH_ON_DEMAND()
  if (error_code == GattErrorCode::kNotAuthorized && client) {
    BluetoothDevice* device =
        GetCachedDevice(GetCharacteristicDeviceID(characteristic_instance_id));
    if (device && !device->IsPaired()) {
      // Initiate pairing. See (Secure Characteristics) in README.md for more
      // information.
      pairing_manager_->PairForCharacteristicStartNotifications(
          characteristic_instance_id, std::move(client), std::move(callback));
      return;
    }
  }
#endif  // PAIR_BLUETOOTH_ON_DEMAND()

  std::move(callback).Run(TranslateGATTErrorAndRecord(
      error_code, UMAGATTOperation::kStartNotifications));

  // Fail any deferred notification starts blocked on this one.
  auto deferred_iter =
      characteristic_id_to_deferred_start_.find(characteristic_instance_id);
  if (deferred_iter != characteristic_id_to_deferred_start_.end()) {
    base::queue<std::unique_ptr<DeferredStartNotificationData>> deferral_queue =
        std::move(deferred_iter->second);
    characteristic_id_to_deferred_start_.erase(deferred_iter);
    while (!deferral_queue.empty()) {
      // Run paused start callbacks with the same error code that caused the
      // first one to fail.
      std::move(deferral_queue.front()->callback)
          .Run(TranslateGATTErrorAndRecord(
              error_code, UMAGATTOperation::kStartNotifications));
      deferral_queue.pop();
    }
  }
}

void WebBluetoothServiceImpl::OnStopNotifySessionComplete(
    const std::string& characteristic_instance_id,
    RemoteCharacteristicStopNotificationsCallback callback) {
  characteristic_id_to_notify_session_.erase(characteristic_instance_id);
  std::move(callback).Run();
}

void WebBluetoothServiceImpl::OnDescriptorReadValue(
    const std::string& descriptor_instance_id,
    RemoteDescriptorReadValueCallback callback,
    std::optional<GattErrorCode> error_code,
    const std::vector<uint8_t>& value) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (error_code.has_value()) {
#if PAIR_BLUETOOTH_ON_DEMAND()
    if (error_code.value() == GattErrorCode::kNotAuthorized) {
      BluetoothDevice* device =
          GetCachedDevice(GetDescriptorDeviceId(descriptor_instance_id));
      if (device && !device->IsPaired()) {
        // Initiate pairing. See (Secure Characteristics) in README.md for more
        // information.
        pairing_manager_->PairForDescriptorReadValue(descriptor_instance_id,
                                                     std::move(callback));
        return;
      }
    }
#endif  // PAIR_BLUETOOTH_ON_DEMAND()
    std::move(callback).Run(
        TranslateGATTErrorAndRecord(error_code.value(),
                                    UMAGATTOperation::kDescriptorReadObsolete),
        /*value=*/{});
    return;
  }
  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS, value);
}

void WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess(
    RemoteDescriptorWriteValueCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  std::move(callback).Run(blink::mojom::WebBluetoothResult::SUCCESS);
}

void WebBluetoothServiceImpl::OnDescriptorWriteValueFailed(
    const std::string& descriptor_instance_id,
    const std::vector<uint8_t>& value,
    RemoteDescriptorWriteValueCallback callback,
    GattErrorCode error_code) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

#if PAIR_BLUETOOTH_ON_DEMAND()
  if (error_code == GattErrorCode::kNotAuthorized) {
    BluetoothDevice* device =
        GetCachedDevice(GetDescriptorDeviceId(descriptor_instance_id));
    if (device && !device->IsPaired()) {
      // Initiate pairing. See (Secure Characteristics) in README.md for more
      // information.
      pairing_manager_->PairForDescriptorWriteValue(descriptor_instance_id,
                                                    value, std::move(callback));
      return;
    }
  }
#endif  // PAIR_BLUETOOTH_ON_DEMAND()

  std::move(callback).Run(TranslateGATTErrorAndRecord(
      error_code, UMAGATTOperation::kDescriptorWriteObsolete));
}

CacheQueryResult WebBluetoothServiceImpl::QueryCacheForDevice(
    const blink::WebBluetoothDeviceId& device_id) {
  std::string device_address;
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (delegate) {
      device_address =
          delegate->GetDeviceAddress(&render_frame_host(), device_id);
    }
  } else {
    device_address = allowed_devices().GetDeviceAddress(device_id);
  }

  if (device_address.empty()) {
    ReceivedBadMessage(bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }

  CacheQueryResult result;
  result.device = GetAdapter()->GetDevice(device_address);

  // When a device can't be found in the BluetoothAdapter, that generally
  // indicates that it's gone out of range. We reject with a NetworkError in
  // that case.
  if (result.device == nullptr) {
    result.outcome = CacheQueryOutcome::kNoDevice;
  }
  return result;
}

CacheQueryResult WebBluetoothServiceImpl::QueryCacheForService(
    const std::string& service_instance_id) {
  auto device_iter = service_id_to_device_address_.find(service_instance_id);

  // Kill the render, see "ID Not in Map Note" above.
  if (device_iter == service_id_to_device_address_.end()) {
    ReceivedBadMessage(bad_message::BDH_INVALID_SERVICE_ID);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }

  const blink::WebBluetoothDeviceId device_id =
      GetWebBluetoothDeviceId(device_iter->second);

  // Kill the renderer if origin is not allowed to access the device.
  if (!device_id.IsValid()) {
    ReceivedBadMessage(bad_message::BDH_DEVICE_NOT_ALLOWED_FOR_ORIGIN);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }

  CacheQueryResult result = QueryCacheForDevice(device_id);
  if (result.outcome != CacheQueryOutcome::kSuccess) {
    return result;
  }

  result.service = result.device->GetGattService(service_instance_id);
  if (result.service == nullptr) {
    result.outcome = CacheQueryOutcome::kNoService;
    return result;
  }

  if (!IsAllowedToAccessService(device_id, result.service->GetUUID())) {
    ReceivedBadMessage(bad_message::BDH_SERVICE_NOT_ALLOWED_FOR_ORIGIN);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }
  return result;
}

CacheQueryResult WebBluetoothServiceImpl::QueryCacheForCharacteristic(
    const std::string& characteristic_instance_id) {
  auto characteristic_iter =
      characteristic_id_to_service_id_.find(characteristic_instance_id);

  // Kill the render, see "ID Not in Map Note" above.
  if (characteristic_iter == characteristic_id_to_service_id_.end()) {
    ReceivedBadMessage(bad_message::BDH_INVALID_CHARACTERISTIC_ID);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }

  CacheQueryResult result = QueryCacheForService(characteristic_iter->second);

  if (result.outcome != CacheQueryOutcome::kSuccess) {
    return result;
  }

  result.characteristic =
      result.service->GetCharacteristic(characteristic_instance_id);

  if (result.characteristic == nullptr) {
    result.outcome = CacheQueryOutcome::kNoCharacteristic;
  }

  return result;
}

CacheQueryResult WebBluetoothServiceImpl::QueryCacheForDescriptor(
    const std::string& descriptor_instance_id) {
  auto descriptor_iter =
      descriptor_id_to_characteristic_id_.find(descriptor_instance_id);

  // Kill the render, see "ID Not in Map Note" above.
  if (descriptor_iter == descriptor_id_to_characteristic_id_.end()) {
    ReceivedBadMessage(bad_message::BDH_INVALID_DESCRIPTOR_ID);
    return CacheQueryResult(CacheQueryOutcome::kBadRenderer);
  }

  CacheQueryResult result =
      QueryCacheForCharacteristic(descriptor_iter->second);

  if (result.outcome != CacheQueryOutcome::kSuccess) {
    return result;
  }

  result.descriptor =
      result.characteristic->GetDescriptor(descriptor_instance_id);

  if (result.descriptor == nullptr) {
    result.outcome = CacheQueryOutcome::kNoDescriptor;
  }

  return result;
}

void WebBluetoothServiceImpl::RunPendingPrimaryServicesRequests(
    BluetoothDevice* device) {
  const std::string& device_address = device->GetAddress();

  auto iter = pending_primary_services_requests_.find(device_address);
  if (iter == pending_primary_services_requests_.end()) {
    return;
  }
  std::vector<PrimaryServicesRequestCallback> requests =
      std::move(iter->second);
  pending_primary_services_requests_.erase(iter);

  for (PrimaryServicesRequestCallback& request : requests) {
    std::move(request).Run(device);
  }

  // Sending get-service responses unexpectedly queued another request.
  DCHECK(!base::Contains(pending_primary_services_requests_, device_address));
}

RenderProcessHost* WebBluetoothServiceImpl::GetRenderProcessHost() {
  return render_frame_host().GetProcess();
}

BluetoothAdapter* WebBluetoothServiceImpl::GetAdapter() {
  return BluetoothAdapterFactoryWrapper::Get().GetAdapter(this);
}

void WebBluetoothServiceImpl::ReceivedBadMessage(
    bad_message::BadMessageReason reason) {
  bad_message::ReceivedBadMessage(GetRenderProcessHost(), reason);
  // Ideally, this would use receiver_.ReportBadMessage(), but for legacy
  // reasons, the Bluetooth service code uses the BadMessageReason enum, which
  // is incompatible.
  receiver_.reset();
}

BluetoothAllowedDevices& WebBluetoothServiceImpl::allowed_devices() {
  // We should use the embedding origin so that permission delegation using
  // Permissions Policy works correctly.
  const url::Origin& embedding_origin =
      render_frame_host().GetMainFrame()->GetLastCommittedOrigin();
  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
      web_contents()->GetBrowserContext()->GetDefaultStoragePartition());
  return partition->GetBluetoothAllowedDevicesMap()->GetOrCreateAllowedDevices(
      embedding_origin);
}

void WebBluetoothServiceImpl::StoreAllowedScanOptions(
    const blink::mojom::WebBluetoothRequestLEScanOptions& options) {
  if (options.filters.has_value()) {
    for (const auto& filter : options.filters.value())
      allowed_scan_filters_.push_back(filter.Clone());
  } else {
    accept_all_advertisements_ = true;
  }
}

bool WebBluetoothServiceImpl::AreScanFiltersAllowed(
    const std::optional<ScanFilters>& filters) const {
  if (accept_all_advertisements_) {
    // Previously allowed accepting all advertisements and no filters. In this
    // case since filtered advertisements are a subset of all advertisements,
    // any filters should be allowed.
    return true;
  }

  if (!filters.has_value()) {
    // |acceptAllAdvertisements| is set in the Bluetooth scanning options, but
    // accepting all advertisements has not been allowed yet, in this case the
    // permission prompt needs to be shown to the user.
    return false;
  }

  // If each |filter| in |filters| can be found in |allowed_scan_filters_|, then
  // |filters| are allowed, otherwise |filters| are not allowed.
  for (const auto& filter : filters.value()) {
    bool allowed = false;
    for (const auto& allowed_filter : allowed_scan_filters_) {
      if (AreScanFiltersSame(*filter, *allowed_filter)) {
        allowed = true;
        break;
      }
    }

    if (!allowed)
      return false;
  }

  return true;
}

void WebBluetoothServiceImpl::ClearAdvertisementClients() {
  scanning_clients_.clear();
  watch_advertisements_clients_.clear();
  allowed_scan_filters_.clear();
  accept_all_advertisements_ = false;
}

bool WebBluetoothServiceImpl::IsAllowedToAccessAtLeastOneService(
    const blink::WebBluetoothDeviceId& device_id) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate)
      return false;
    return delegate->IsAllowedToAccessAtLeastOneService(&render_frame_host(),
                                                        device_id);
  }
  return allowed_devices().IsAllowedToAccessAtLeastOneService(device_id);
}

bool WebBluetoothServiceImpl::IsAllowedToAccessService(
    const blink::WebBluetoothDeviceId& device_id,
    const BluetoothUUID& service) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate)
      return false;
    return delegate->IsAllowedToAccessService(&render_frame_host(), device_id,
                                              service);
  }
  return allowed_devices().IsAllowedToAccessService(device_id, service);
}

bool WebBluetoothServiceImpl::IsAllowedToAccessManufacturerData(
    const blink::WebBluetoothDeviceId& device_id,
    uint16_t manufacturer_code) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate)
      return false;
    return delegate->IsAllowedToAccessManufacturerData(
        &render_frame_host(), device_id, manufacturer_code);
  }
  return allowed_devices().IsAllowedToAccessManufacturerData(device_id,
                                                             manufacturer_code);
}

bool WebBluetoothServiceImpl::HasActiveDiscoverySession() {
  return (ble_scan_discovery_session_ &&
          ble_scan_discovery_session_->IsActive()) ||
         (watch_advertisements_discovery_session_ &&
          watch_advertisements_discovery_session_->IsActive());
}

void WebBluetoothServiceImpl::PreventBackForwardCache() {
  if (back_forward_cache_feature_handle_.IsValid()) {
    return;
  }
  back_forward_cache_feature_handle_ =
      RenderFrameHostImpl::From(&render_frame_host())
          ->RegisterBackForwardCacheDisablingNonStickyFeature(
              blink::scheduler::WebSchedulerTrackedFeature::kWebBluetooth);
}

blink::WebBluetoothDeviceId WebBluetoothServiceImpl::GetCharacteristicDeviceID(
    const std::string& characteristic_instance_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto characteristic_iter =
      characteristic_id_to_service_id_.find(characteristic_instance_id);
  if (characteristic_iter == characteristic_id_to_service_id_.end())
    return blink::WebBluetoothDeviceId();
  auto device_iter =
      service_id_to_device_address_.find(characteristic_iter->second);
  if (device_iter == service_id_to_device_address_.end())
    return blink::WebBluetoothDeviceId();

  return GetWebBluetoothDeviceId(device_iter->second);
}

BluetoothDevice* WebBluetoothServiceImpl::GetCachedDevice(
    const blink::WebBluetoothDeviceId& device_id) {
  DCHECK(device_id.IsValid());
  CacheQueryResult query_result = QueryCacheForDevice(device_id);
  if (query_result.outcome != CacheQueryOutcome::kSuccess) {
    return nullptr;
  }

  return query_result.device;
}

blink::WebBluetoothDeviceId WebBluetoothServiceImpl::GetDescriptorDeviceId(
    const std::string& descriptor_instance_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto iter = descriptor_id_to_characteristic_id_.find(descriptor_instance_id);
  if (iter == descriptor_id_to_characteristic_id_.end())
    return blink::WebBluetoothDeviceId();

  return GetCharacteristicDeviceID(iter->second);
}

blink::WebBluetoothDeviceId WebBluetoothServiceImpl::GetWebBluetoothDeviceId(
    const std::string& device_address) {
  if (base::FeatureList::IsEnabled(
          features::kWebBluetoothNewPermissionsBackend)) {
    BluetoothDelegate* delegate =
        GetContentClient()->browser()->GetBluetoothDelegate();
    if (!delegate)
      return blink::WebBluetoothDeviceId();
    return delegate->GetWebBluetoothDeviceId(&render_frame_host(),
                                             device_address);
  }

  const blink::WebBluetoothDeviceId* device_id_ptr =
      allowed_devices().GetDeviceId(device_address);
  return device_id_ptr ? *device_id_ptr : blink::WebBluetoothDeviceId();
}

void WebBluetoothServiceImpl::PairDevice(
    const blink::WebBluetoothDeviceId& device_id,
    BluetoothDevice::PairingDelegate* pairing_delegate,
    BluetoothDevice::ConnectCallback callback) {
  if (!device_id.IsValid()) {
    std::move(callback).Run(BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN);
    return;
  }

  BluetoothDevice* device = GetCachedDevice(device_id);
  if (!device) {
    std::move(callback).Run(BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN);
    return;
  }

  DCHECK(!device->IsPaired());

  device->Pair(pairing_delegate, std::move(callback));
}

void WebBluetoothServiceImpl::CancelPairing(
    const blink::WebBluetoothDeviceId& device_id) {
  DCHECK(device_id.IsValid());

  BluetoothDevice* device = GetCachedDevice(device_id);
  if (!device)
    return;

  device->CancelPairing();
}

void WebBluetoothServiceImpl::SetPinCode(
    const blink::WebBluetoothDeviceId& device_id,
    const std::string& pincode) {
  DCHECK(device_id.IsValid());

  BluetoothDevice* device = GetCachedDevice(device_id);
  if (!device)
    return;

  device->SetPinCode(pincode);
}

void WebBluetoothServiceImpl::PairConfirmed(
    const blink::WebBluetoothDeviceId& device_id) {
  DCHECK(device_id.IsValid());

  BluetoothDevice* device = GetCachedDevice(device_id);
  if (!device)
    return;

  device->ConfirmPairing();
}

void WebBluetoothServiceImpl::PromptForBluetoothPairing(
    const std::u16string& device_identifier,
    BluetoothDelegate::PairPromptCallback callback,
    BluetoothDelegate::PairingKind pairing_kind,
    const std::optional<std::u16string>& pin) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  BluetoothDelegate* delegate =
      GetContentClient()->browser()->GetBluetoothDelegate();

  if (!delegate) {
    std::move(callback).Run(BluetoothDelegate::PairPromptResult(
        BluetoothDelegate::PairPromptStatus::kCancelled));
    return;
  }

  switch (pairing_kind) {
    case BluetoothDelegate::PairingKind::kConfirmOnly:
    case BluetoothDelegate::PairingKind::kProvidePin:
    case BluetoothDelegate::PairingKind::kConfirmPinMatch:
      delegate->ShowDevicePairPrompt(&render_frame_host(), device_identifier,
                                     std::move(callback), pairing_kind, pin);
      break;
    default:
      NOTREACHED();
  }
}

#if PAIR_BLUETOOTH_ON_DEMAND()
void WebBluetoothServiceImpl::SetPairingManagerForTesting(
    std::unique_ptr<WebBluetoothPairingManager> pairing_manager) {
  pairing_manager_ = std::move(pairing_manager);
}
#endif  // PAIR_BLUETOOTH_ON_DEMAND()

}  // namespace content