blob: 98be178c963bb3ce38fe1b87b5dc3f0e469b5362 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/bluetooth/web_bluetooth_service_impl.h"
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/test_future.h"
#include "content/browser/bluetooth/bluetooth_adapter_factory_wrapper.h"
#include "content/browser/bluetooth/bluetooth_allowed_devices.h"
#include "content/browser/bluetooth/web_bluetooth_pairing_manager.h"
#include "content/public/browser/bluetooth_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_web_contents_factory.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
namespace content {
namespace {
using ::base::test::TestFuture;
using ::blink::mojom::WebBluetoothCharacteristicClient;
using ::blink::mojom::WebBluetoothGATTQueryQuantity;
using ::blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr;
using ::blink::mojom::WebBluetoothRemoteGATTServicePtr;
using ::blink::mojom::WebBluetoothResult;
using ::device::BluetoothDevice;
using ::device::BluetoothGattService;
using ::device::BluetoothRemoteGattCharacteristic;
using ::device::BluetoothRemoteGattService;
using ::device::BluetoothUUID;
using ::device::MockBluetoothAdapter;
using ::device::MockBluetoothDevice;
using ::device::MockBluetoothGattCharacteristic;
using ::device::MockBluetoothGattNotifySession;
using ::device::MockBluetoothGattService;
using ::testing::_;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::WithParamInterface;
using PromptEventCallback =
base::OnceCallback<void(BluetoothScanningPrompt::Event)>;
using WatchAdvertisementsForDeviceCallback =
base::OnceCallback<void(WebBluetoothResult)>;
// Plain data struct for fake bluetooth device.
struct BluetoothDeviceBundleData {
std::string_view service_id;
std::string_view chracteristic_id;
device::BluetoothUUID service_uuid;
device::BluetoothUUID chracteristic_uuid;
BluetoothRemoteGattCharacteristic::Properties characteristic_properties;
};
constexpr BluetoothRemoteGattCharacteristic::Properties
kTestCharacteristicProperties =
BluetoothRemoteGattCharacteristic::PROPERTY_BROADCAST |
BluetoothRemoteGattCharacteristic::PROPERTY_READ |
BluetoothRemoteGattCharacteristic::PROPERTY_INDICATE;
// Constants for battery service fake bluetooth device.
const char kBatteryServiceUUIDString[] = "0000180f-0000-1000-8000-00805f9b34fb";
constexpr char kBatteryServiceId[] = "battery_service_id";
constexpr char kBatteryLevelCharacteristicId[] = "battery_level_id";
const device::BluetoothUUID kBatteryServiceUUID(kBatteryServiceUUIDString);
const device::BluetoothUUID kBatteryLevelCharacteristicUUID(
"00002a19-0000-1000-8000-00805f9b34fb");
const BluetoothDeviceBundleData battery_device_bundle_data = {
kBatteryServiceId, kBatteryLevelCharacteristicId, kBatteryServiceUUID,
kBatteryLevelCharacteristicUUID, kTestCharacteristicProperties};
// Constants for heart rate service fake bluetooth device.
const char kHeartRateServiceUUIDString[] =
"0000180d-0000-1000-8000-00805f9b34fb";
constexpr char kHeartRateServiceId[] = "heart_rate_service_id";
constexpr char kHeartRateMeasurementCharacteristicId[] =
"heart_rate_measurement_id";
const device::BluetoothUUID kHeartRateServiceUUID(kHeartRateServiceUUIDString);
const device::BluetoothUUID kHeartRateMeasurementCharacteristicUUID(
"00002a37-0000-1000-8000-00805f9b34fb");
const BluetoothDeviceBundleData heart_rate_device_bundle_data = {
kHeartRateServiceId, kHeartRateMeasurementCharacteristicId,
kHeartRateServiceUUID, kHeartRateMeasurementCharacteristicUUID,
kTestCharacteristicProperties};
class MockWebBluetoothPairingManager : public WebBluetoothPairingManager {
public:
MockWebBluetoothPairingManager() = default;
MockWebBluetoothPairingManager(const MockWebBluetoothPairingManager&) =
delete;
MockWebBluetoothPairingManager& operator=(
const MockWebBluetoothPairingManager&) = delete;
~MockWebBluetoothPairingManager() override = default;
MOCK_METHOD2(PairForCharacteristicReadValue,
void(const std::string& characteristic_instance_id,
blink::mojom::WebBluetoothService::
RemoteCharacteristicReadValueCallback read_callback));
MOCK_METHOD4(PairForCharacteristicWriteValue,
void(const std::string& characteristic_instance_id,
const std::vector<uint8_t>& value,
blink::mojom::WebBluetoothWriteType write_type,
blink::mojom::WebBluetoothService::
RemoteCharacteristicWriteValueCallback callback));
MOCK_METHOD2(
PairForDescriptorReadValue,
void(const std::string& descriptor_instance_id,
blink::mojom::WebBluetoothService::RemoteDescriptorReadValueCallback
read_callback));
MOCK_METHOD3(
PairForDescriptorWriteValue,
void(const std::string& descriptor_instance_id,
const std::vector<uint8_t>& value,
blink::mojom::WebBluetoothService::RemoteDescriptorWriteValueCallback
callback));
MOCK_METHOD3(
PairForCharacteristicStartNotifications,
void(const std::string& characteristic_instance_id,
mojo::AssociatedRemote<
blink::mojom::WebBluetoothCharacteristicClient> client,
blink::mojom::WebBluetoothService::
RemoteCharacteristicStartNotificationsCallback callback));
};
class FakeBluetoothScanningPrompt : public BluetoothScanningPrompt {
public:
explicit FakeBluetoothScanningPrompt(
PromptEventCallback prompt_event_callback)
: prompt_event_callback_(std::move(prompt_event_callback)) {}
~FakeBluetoothScanningPrompt() override = default;
// Move-only class.
FakeBluetoothScanningPrompt(const FakeBluetoothScanningPrompt&) = delete;
FakeBluetoothScanningPrompt& operator=(const FakeBluetoothScanningPrompt&) =
delete;
void RunPromptEventCallback(Event event) {
if (prompt_event_callback_.is_null()) {
FAIL() << "prompt_event_callback_ is not set";
}
std::move(prompt_event_callback_).Run(event);
}
private:
PromptEventCallback prompt_event_callback_;
};
class FakeWebBluetoothCharacteristicClient : WebBluetoothCharacteristicClient {
public:
mojo::PendingAssociatedRemote<WebBluetoothCharacteristicClient>
BindNewEndpointClientAndPassRemote() {
receiver_.reset();
return receiver_.BindNewEndpointAndPassDedicatedRemote();
}
protected:
// WebBluetoothCharacteristicClient implementation:
void RemoteCharacteristicValueChanged(
base::span<const uint8_t> value) override {
NOTREACHED();
}
private:
mojo::AssociatedReceiver<WebBluetoothCharacteristicClient> receiver_{this};
};
class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
public:
FakeBluetoothAdapter() = default;
// Move-only class.
FakeBluetoothAdapter(const FakeBluetoothAdapter&) = delete;
FakeBluetoothAdapter& operator=(const FakeBluetoothAdapter&) = delete;
// device::BluetoothAdapter:
void StartScanWithFilter(
std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
DiscoverySessionResultCallback callback) override {
// PostTask here to simulate fake adapter return start discovery result
// asynchronously.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), is_start_scan_result_error_,
start_scan_result_));
}
void AddObserver(BluetoothAdapter::Observer* observer) override {}
void RemoveObserver(BluetoothAdapter::Observer* observer) override {}
BluetoothDevice* GetDevice(const std::string& address) override {
for (auto& device : mock_devices_) {
if (device->GetAddress() == address) {
return device.get();
}
}
return nullptr;
}
void SetStartScanWithFilterResult(
device::UMABluetoothDiscoverySessionOutcome result) {
start_scan_result_ = result;
is_start_scan_result_error_ =
result == device::UMABluetoothDiscoverySessionOutcome::SUCCESS ? false
: true;
}
private:
~FakeBluetoothAdapter() override = default;
// Inputs for `DiscoverySessionResultCallback`.
bool is_start_scan_result_error_;
device::UMABluetoothDiscoverySessionOutcome start_scan_result_;
};
class FakeBluetoothGattService : public NiceMock<MockBluetoothGattService> {
public:
FakeBluetoothGattService(MockBluetoothDevice* device,
const std::string& identifier,
const device::BluetoothUUID& uuid)
: NiceMock<MockBluetoothGattService>(device,
identifier,
uuid,
/*is_primary=*/true) {}
};
class FakeBluetoothDevice : public NiceMock<MockBluetoothDevice> {
public:
explicit FakeBluetoothDevice(MockBluetoothAdapter* adapter)
: NiceMock<MockBluetoothDevice>(adapter,
/*bluetooth_class=*/0,
/*name=*/"device with battery",
/*address=*/"00:00:01",
/*paired=*/false,
/*connected=*/true) {}
bool IsGattServicesDiscoveryComplete() const override {
return gatt_services_discovery_complete_;
}
std::vector<BluetoothRemoteGattService*> GetGattServices() const override {
return GetMockServices();
}
BluetoothRemoteGattService* GetGattService(
const std::string& identifier) const override {
return GetMockService(identifier);
}
};
class FakeBluetoothCharacteristic
: public NiceMock<MockBluetoothGattCharacteristic> {
public:
FakeBluetoothCharacteristic(MockBluetoothGattService* service,
const std::string& identifier,
const device::BluetoothUUID& uuid,
Properties properties,
Permissions permissions)
: NiceMock<MockBluetoothGattCharacteristic>(service,
identifier,
uuid,
properties,
permissions) {}
void StartNotifySession(NotifySessionCallback callback,
ErrorCallback error_callback) override {
if (defer_next_start_notification_) {
defer_next_start_notification_ = false;
DCHECK(deferred_start_notification_callback_.is_null());
DCHECK(deferred_start_notification_error_callback_.is_null());
deferred_start_notification_callback_ = std::move(callback);
deferred_start_notification_error_callback_ = std::move(error_callback);
return;
}
std::move(callback).Run(
std::make_unique<MockBluetoothGattNotifySession>(GetWeakPtr()));
}
void ResumeDeferredStartNotification() {
if (notification_start_error_code_.has_value()) {
deferred_start_notification_callback_.Reset();
std::move(deferred_start_notification_error_callback_)
.Run(notification_start_error_code_.value());
} else {
deferred_start_notification_error_callback_.Reset();
std::move(deferred_start_notification_callback_)
.Run(std::make_unique<MockBluetoothGattNotifySession>(GetWeakPtr()));
}
}
void DeferNextStartNotification(
std::optional<BluetoothGattService::GattErrorCode> error_code) {
defer_next_start_notification_ = true;
notification_start_error_code_ = error_code;
}
private:
bool defer_next_start_notification_ = false;
std::optional<BluetoothGattService::GattErrorCode>
notification_start_error_code_;
NotifySessionCallback deferred_start_notification_callback_;
ErrorCallback deferred_start_notification_error_callback_;
};
class TestBluetoothDelegate : public BluetoothDelegate {
public:
TestBluetoothDelegate() = default;
~TestBluetoothDelegate() override = default;
TestBluetoothDelegate(const TestBluetoothDelegate&) = delete;
TestBluetoothDelegate& operator=(const TestBluetoothDelegate&) = delete;
// BluetoothDelegate:
std::unique_ptr<BluetoothChooser> RunBluetoothChooser(
RenderFrameHost* frame,
const BluetoothChooser::EventHandler& event_handler) override {
return nullptr;
}
std::unique_ptr<BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
RenderFrameHost* frame,
const BluetoothScanningPrompt::EventHandler& event_handler) override {
auto prompt =
std::make_unique<FakeBluetoothScanningPrompt>(std::move(event_handler));
prompt_ = prompt.get();
return std::move(prompt);
}
void ShowDevicePairPrompt(content::RenderFrameHost* frame,
const std::u16string& device_identifier,
PairPromptCallback callback,
PairingKind pairing_kind,
const std::optional<std::u16string>& pin) override {
std::move(callback).Run(PairPromptResult(PairPromptStatus::kCancelled));
}
blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
RenderFrameHost* frame,
const std::string& device_address) override {
return blink::WebBluetoothDeviceId();
}
std::string GetDeviceAddress(RenderFrameHost* frame,
const blink::WebBluetoothDeviceId&) override {
return std::string();
}
blink::WebBluetoothDeviceId AddScannedDevice(
RenderFrameHost* frame,
const std::string& device_address) override {
return blink::WebBluetoothDeviceId();
}
blink::WebBluetoothDeviceId GrantServiceAccessPermission(
RenderFrameHost* frame,
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptions* options) override {
return blink::WebBluetoothDeviceId();
}
bool HasDevicePermission(
RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override {
return false;
}
void RevokeDevicePermissionWebInitiated(
RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override {}
bool MayUseBluetooth(RenderFrameHost* rfh) override { return true; }
bool IsAllowedToAccessService(RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id,
const device::BluetoothUUID& service) override {
return false;
}
bool IsAllowedToAccessAtLeastOneService(
RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override {
return false;
}
bool IsAllowedToAccessManufacturerData(
RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id,
const uint16_t manufacturer_code) override {
return false;
}
std::vector<blink::mojom::WebBluetoothDevicePtr> GetPermittedDevices(
RenderFrameHost* frame) override {
return {};
}
void RunBluetoothScanningPromptEventCallback(
BluetoothScanningPrompt::Event event) {
if (!prompt_) {
FAIL() << "ShowBluetoothScanningPrompt must be called before "
<< __func__;
}
prompt_->RunPromptEventCallback(event);
}
void AddFramePermissionObserver(FramePermissionObserver* observer) override {}
void RemoveFramePermissionObserver(
FramePermissionObserver* observer) override {}
private:
raw_ptr<FakeBluetoothScanningPrompt, AcrossTasksDanglingUntriaged> prompt_ =
nullptr;
};
class TestContentBrowserClient : public ContentBrowserClient {
public:
TestContentBrowserClient() = default;
~TestContentBrowserClient() override = default;
TestContentBrowserClient(const TestContentBrowserClient&) = delete;
TestContentBrowserClient& operator=(const TestContentBrowserClient&) = delete;
TestBluetoothDelegate* bluetooth_delegate() { return &bluetooth_delegate_; }
protected:
// ChromeContentBrowserClient:
BluetoothDelegate* GetBluetoothDelegate() override {
return &bluetooth_delegate_;
}
private:
TestBluetoothDelegate bluetooth_delegate_;
};
class FakeWebBluetoothAdvertisementClient
: blink::mojom::WebBluetoothAdvertisementClient {
public:
FakeWebBluetoothAdvertisementClient() = default;
~FakeWebBluetoothAdvertisementClient() override = default;
// Move-only class.
FakeWebBluetoothAdvertisementClient(
const FakeWebBluetoothAdvertisementClient&) = delete;
FakeWebBluetoothAdvertisementClient& operator=(
const FakeWebBluetoothAdvertisementClient&) = delete;
// blink::mojom::WebBluetoothAdvertisementClient:
void AdvertisingEvent(
blink::mojom::WebBluetoothAdvertisingEventPtr event) override {}
void BindReceiver(mojo::PendingAssociatedReceiver<
blink::mojom::WebBluetoothAdvertisementClient> receiver) {
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(
base::BindOnce(&FakeWebBluetoothAdvertisementClient::OnConnectionError,
base::Unretained(this)));
}
void OnConnectionError() { on_connection_error_called_ = true; }
bool on_connection_error_called() { return on_connection_error_called_; }
private:
mojo::AssociatedReceiver<blink::mojom::WebBluetoothAdvertisementClient>
receiver_{this};
bool on_connection_error_called_ = false;
};
// A collection of Bluetooth objects which present related
// device/service/characteristic/AdvertisementClient instances for
// device level testing.
class FakeBluetoothDeviceBundle {
public:
explicit FakeBluetoothDeviceBundle(
scoped_refptr<FakeBluetoothAdapter> adapter,
BluetoothDeviceBundleData bundle_data)
: adapter_(std::move(adapter)) {
auto device = std::make_unique<FakeBluetoothDevice>(adapter_.get());
device_ = device.get();
auto service = std::make_unique<FakeBluetoothGattService>(
device.get(), bundle_data.service_id.data(), bundle_data.service_uuid);
service_ = service.get();
auto characteristic = std::make_unique<FakeBluetoothCharacteristic>(
service_, bundle_data.chracteristic_id.data(),
bundle_data.chracteristic_uuid, bundle_data.characteristic_properties,
BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
characteristic_ = characteristic.get();
service->AddMockCharacteristic(std::move(characteristic));
device->AddMockService(std::move(service));
adapter_->AddMockDevice(std::move(device));
advertisement_client_ =
std::make_unique<FakeWebBluetoothAdvertisementClient>();
}
FakeBluetoothDeviceBundle& operator=(const FakeBluetoothDeviceBundle&) =
delete;
FakeBluetoothDevice& device() { return *device_; }
FakeBluetoothGattService& service() { return *service_; }
FakeBluetoothCharacteristic& characteristic() { return *characteristic_; }
FakeWebBluetoothAdvertisementClient& advertisement_client() {
return *advertisement_client_;
}
private:
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<FakeBluetoothDevice> device_ = nullptr;
raw_ptr<FakeBluetoothGattService> service_ = nullptr;
raw_ptr<FakeBluetoothCharacteristic> characteristic_ = nullptr;
std::unique_ptr<FakeWebBluetoothAdvertisementClient> advertisement_client_;
};
} // namespace
class WebBluetoothServiceImplTest : public RenderViewHostImplTestHarness,
public WithParamInterface<bool> {
public:
WebBluetoothServiceImplTest() = default;
~WebBluetoothServiceImplTest() override = default;
// Move-only class.
WebBluetoothServiceImplTest(const WebBluetoothServiceImplTest&) = delete;
WebBluetoothServiceImplTest& operator=(const WebBluetoothServiceImplTest&) =
delete;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Set up an adapter.
adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
EXPECT_CALL(*adapter_, IsPresent()).WillRepeatedly(Return(true));
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter_);
battery_device_bundle_ = std::make_unique<FakeBluetoothDeviceBundle>(
adapter_, battery_device_bundle_data);
heart_rate_device_bundle_ = std::make_unique<FakeBluetoothDeviceBundle>(
adapter_, heart_rate_device_bundle_data);
// Hook up the test bluetooth delegate.
old_browser_client_ = SetBrowserClientForTesting(&browser_client_);
contents()->GetPrimaryMainFrame()->InitializeRenderFrameIfNeeded();
// Navigate to a URL so that WebBluetoothServiceImpl::GetOrigin() returns a
// valid origin. This is required when checking for Bluetooth permissions.
constexpr char kTestURL[] = "https://my-battery-level.com";
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GURL(kTestURL));
// Simulate a frame connected to a bluetooth service.
mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver =
service_.BindNewPipeAndPassReceiver();
service_ptr_ = WebBluetoothServiceImpl::CreateForTesting(
contents()->GetPrimaryMainFrame(), std::move(receiver));
// GetAvailability connects the Web Bluetooth service to the adapter. Call
// it twice in parallel to exercise what happens when multiple requests to
// acquire the BluetoothAdapter are in flight.
TestFuture<bool> future_1;
TestFuture<bool> future_2;
service_ptr_->GetAvailability(future_1.GetCallback());
service_ptr_->GetAvailability(future_2.GetCallback());
// Use Wait() instead of Get() because we don't care about the result.
EXPECT_TRUE(future_1.Wait());
EXPECT_TRUE(future_2.Wait());
// WebBluetoothServiceImpl assumes one of GetDevices, RequestDevice, or
// RequestScanningStart will be called before device objects and their IDs
// are available. This assumption may be violated in tests when a simulated
// device is added and its IDs are used to invoke service methods without
// first invoking one of the entry point methods.
//
// To satisfy this requirement, call GetDevices before the test case runs.
TestFuture<std::vector<blink::mojom::WebBluetoothDevicePtr>>
get_devices_future;
service_ptr_->GetDevices(get_devices_future.GetCallback());
ASSERT_TRUE(get_devices_future.IsReady());
}
void TearDown() override {
adapter_.reset();
battery_device_bundle_.reset();
heart_rate_device_bundle_.reset();
service_ptr_ = nullptr;
SetBrowserClientForTesting(old_browser_client_);
RenderViewHostImplTestHarness::TearDown();
}
mojo::PendingAssociatedRemote<WebBluetoothCharacteristicClient>
BindCharacteristicClientAndPassRemote() {
return characteristic_client_.BindNewEndpointClientAndPassRemote();
}
protected:
blink::mojom::WebBluetoothLeScanFilterPtr CreateScanFilter(
const std::string& name,
const std::string& name_prefix) {
std::optional<std::vector<device::BluetoothUUID>> services;
services.emplace();
services->push_back(device::BluetoothUUID(kBatteryServiceUUIDString));
return blink::mojom::WebBluetoothLeScanFilter::New(
services, name, name_prefix, /*manufacturer_data=*/std::nullopt);
}
blink::mojom::WebBluetoothResult RequestScanningStartAndSimulatePromptEvent(
const blink::mojom::WebBluetoothLeScanFilter& filter,
FakeWebBluetoothAdvertisementClient* client,
BluetoothScanningPrompt::Event event) {
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote;
client->BindReceiver(client_remote.InitWithNewEndpointAndPassReceiver());
auto options = blink::mojom::WebBluetoothRequestLEScanOptions::New();
options->filters.emplace();
auto filter_ptr = blink::mojom::WebBluetoothLeScanFilter::New(
filter.services, filter.name, filter.name_prefix,
/*manufacturer_data=*/std::nullopt);
options->filters->push_back(std::move(filter_ptr));
// Use two RunLoops to guarantee the order of operations for this test.
// |callback_loop| guarantees that RequestScanningStartCallback has finished
// executing and |result| has been populated. |request_loop| ensures that
// the entire RequestScanningStart flow has finished before the method
// returns.
base::RunLoop callback_loop, request_loop;
blink::mojom::WebBluetoothResult result;
service_ptr_->RequestScanningStart(
std::move(client_remote), std::move(options),
base::BindLambdaForTesting(
[&callback_loop, &result](blink::mojom::WebBluetoothResult r) {
result = std::move(r);
callback_loop.Quit();
}));
// Post a task to simulate a prompt event during a call to
// RequestScanningStart().
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting(
[&callback_loop, &event, &request_loop, this]() {
browser_client_.bluetooth_delegate()
->RunBluetoothScanningPromptEventCallback(event);
callback_loop.Run();
request_loop.Quit();
}));
request_loop.Run();
return result;
}
void RegisterTestCharacteristic() {
auto& battery_device_id = AddTestDevice(battery_device_bundle());
auto& device = battery_device_bundle_->device();
device.SetGattServicesDiscoveryComplete(true);
FakeBluetoothCharacteristic& test_characteristic =
battery_device_bundle().characteristic();
{
base::RunLoop run_loop;
service_ptr_->RemoteServerGetPrimaryServices(
battery_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
battery_device_bundle().service().GetUUID(),
base::BindLambdaForTesting(
[&run_loop](
WebBluetoothResult result,
std::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>
services) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
run_loop.Quit();
}));
run_loop.Run();
}
{
base::RunLoop run_loop;
service_ptr_->RemoteServiceGetCharacteristics(
battery_device_bundle().service().GetIdentifier(),
WebBluetoothGATTQueryQuantity::SINGLE, test_characteristic.GetUUID(),
base::BindLambdaForTesting(
[&run_loop](
WebBluetoothResult result,
std::optional<
std::vector<WebBluetoothRemoteGATTCharacteristicPtr>>
characteristic) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
run_loop.Quit();
}));
run_loop.Run();
}
}
FakeBluetoothDeviceBundle& battery_device_bundle() const {
return *battery_device_bundle_;
}
FakeBluetoothDeviceBundle& heart_rate_device_bundle() const {
return *heart_rate_device_bundle_;
}
const blink::WebBluetoothDeviceId& AddTestDevice(
FakeBluetoothDeviceBundle& device_bundle) {
auto device_options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
device_options->accept_all_devices = true;
device_options->optional_services.push_back(
device_bundle.service().GetUUID());
return service_ptr_->allowed_devices().AddDevice(
device_bundle.device().GetAddress(), device_options);
}
void DeleteService() {
// This is a hack; destruction is normally implicitly triggered by
// navigation or destruction of the frame itself, and not explicitly like
// this test does.
WebBluetoothServiceImpl::DeleteForCurrentDocument(
&service_ptr_.ExtractAsDangling()->render_frame_host());
}
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<WebBluetoothServiceImpl> service_ptr_ = nullptr;
mojo::Remote<blink::mojom::WebBluetoothService> service_;
TestContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> old_browser_client_ = nullptr;
std::unique_ptr<FakeBluetoothDeviceBundle> battery_device_bundle_;
std::unique_ptr<FakeBluetoothDeviceBundle> heart_rate_device_bundle_;
FakeWebBluetoothCharacteristicClient characteristic_client_;
};
TEST_F(WebBluetoothServiceImplTest, DestroyedDuringRequestDevice) {
auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
options->accept_all_devices = true;
base::MockCallback<WebBluetoothServiceImpl::RequestDeviceCallback> callback;
EXPECT_CALL(callback, Run).Times(0);
service_ptr_->RequestDevice(std::move(options), callback.Get());
base::RunLoop loop;
DeleteService();
loop.RunUntilIdle();
}
TEST_F(WebBluetoothServiceImplTest, PermissionAllowed) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
FakeWebBluetoothAdvertisementClient client;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result, blink::mojom::WebBluetoothResult::SUCCESS);
// |filters| should be allowed.
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest, DestroyedDuringRequestScanningStart) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
FakeWebBluetoothAdvertisementClient client;
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote;
client.BindReceiver(client_remote.InitWithNewEndpointAndPassReceiver());
auto options = blink::mojom::WebBluetoothRequestLEScanOptions::New();
options->filters.emplace();
options->filters->push_back(std::move(filter));
// The callback is currently called before delete is completed, during
// the scanning request. Though, this is a behavior that is not mandatory
// so not calling the callback would also be valid.
base::RunLoop loop;
base::MockCallback<WebBluetoothServiceImpl::RequestScanningStartCallback>
callback;
EXPECT_CALL(callback, Run).Times(1);
service_ptr_->RequestScanningStart(std::move(client_remote),
std::move(options), callback.Get());
// Post a task to delete the WebBluetoothService state during a call to
// RequestScanningStart().
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([this]() { DeleteService(); }));
loop.RunUntilIdle();
}
TEST_F(WebBluetoothServiceImplTest, PermissionPromptCanceled) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
FakeWebBluetoothAdvertisementClient client;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client, BluetoothScanningPrompt::Event::kCanceled);
EXPECT_EQ(blink::mojom::WebBluetoothResult::PROMPT_CANCELED, result);
// |filters| should still not be allowed.
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenTabHidden) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClient client;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters));
contents()->SetVisibilityAndNotifyObservers(Visibility::HIDDEN);
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenTabOccluded) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClient client;
RequestScanningStartAndSimulatePromptEvent(
*filter, &client, BluetoothScanningPrompt::Event::kAllow);
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters));
contents()->SetVisibilityAndNotifyObservers(Visibility::OCCLUDED);
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenFocusIsLost) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClient client;
RequestScanningStartAndSimulatePromptEvent(
*filter, &client, BluetoothScanningPrompt::Event::kAllow);
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters));
main_test_rfh()->GetRenderWidgetHost()->LostFocus();
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenBlocked) {
blink::mojom::WebBluetoothLeScanFilterPtr filter_1 =
CreateScanFilter("a", "b");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters_1;
filters_1.emplace();
filters_1->push_back(filter_1.Clone());
FakeWebBluetoothAdvertisementClient client_1;
blink::mojom::WebBluetoothResult result_1 =
RequestScanningStartAndSimulatePromptEvent(
*filter_1, &client_1, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result_1, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters_1));
EXPECT_FALSE(client_1.on_connection_error_called());
blink::mojom::WebBluetoothLeScanFilterPtr filter_2 =
CreateScanFilter("c", "d");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters_2;
filters_2.emplace();
filters_2->push_back(filter_2.Clone());
FakeWebBluetoothAdvertisementClient client_2;
blink::mojom::WebBluetoothResult result_2 =
RequestScanningStartAndSimulatePromptEvent(
*filter_2, &client_2, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result_2, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_ptr_->AreScanFiltersAllowed(filters_2));
EXPECT_FALSE(client_2.on_connection_error_called());
blink::mojom::WebBluetoothLeScanFilterPtr filter_3 =
CreateScanFilter("e", "f");
std::optional<WebBluetoothServiceImpl::ScanFilters> filters_3;
filters_3.emplace();
filters_3->push_back(filter_3.Clone());
FakeWebBluetoothAdvertisementClient client_3;
blink::mojom::WebBluetoothResult result_3 =
RequestScanningStartAndSimulatePromptEvent(
*filter_3, &client_3, BluetoothScanningPrompt::Event::kBlock);
EXPECT_EQ(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED, result_3);
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters_3));
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters_1));
EXPECT_FALSE(service_ptr_->AreScanFiltersAllowed(filters_2));
base::RunLoop().RunUntilIdle();
// All existing scanning clients are disconnected.
EXPECT_TRUE(client_1.on_connection_error_called());
EXPECT_TRUE(client_2.on_connection_error_called());
EXPECT_TRUE(client_3.on_connection_error_called());
}
TEST_F(WebBluetoothServiceImplTest,
ReadCharacteristicValueErrorWithValueIgnored) {
// The contract for calls accepting a
// BluetoothRemoteGattCharacteristic::ValueCallback callback argument is that
// when an error occurs, value must be ignored. This test verifies that
// WebBluetoothServiceImpl::OnCharacteristicReadValue honors that contract
// and will not pass a value to its callback
// (a RemoteCharacteristicReadValueCallback instance) when an error occurs
// with a non-empty value array.
const std::vector<uint8_t> read_error_value = {1, 2, 3};
bool callback_called = false;
const std::string characteristic_instance_id = "fake-id";
service_ptr_->OnCharacteristicReadValue(
characteristic_instance_id,
base::BindLambdaForTesting(
[&callback_called](blink::mojom::WebBluetoothResult result,
base::span<const uint8_t> value) {
callback_called = true;
EXPECT_EQ(
blink::mojom::WebBluetoothResult::GATT_OPERATION_IN_PROGRESS,
result);
EXPECT_TRUE(value.empty());
}),
device::BluetoothGattService::GattErrorCode::kInProgress,
read_error_value);
EXPECT_TRUE(callback_called);
// This test doesn't invoke any methods of the mock adapter. Allow it to be
// leaked without producing errors.
Mock::AllowLeak(adapter_.get());
}
#if PAIR_BLUETOOTH_ON_DEMAND()
TEST_F(WebBluetoothServiceImplTest, ReadCharacteristicValueNotAuthorized) {
const std::vector<uint8_t> read_error_value = {1, 2, 3};
bool read_value_callback_called = false;
RegisterTestCharacteristic();
const FakeBluetoothCharacteristic& test_characteristic =
battery_device_bundle().characteristic();
MockWebBluetoothPairingManager* pairing_manager =
new MockWebBluetoothPairingManager();
service_ptr_->SetPairingManagerForTesting(
std::unique_ptr<WebBluetoothPairingManager>(pairing_manager));
EXPECT_CALL(*pairing_manager, PairForCharacteristicReadValue(_, _)).Times(1);
service_ptr_->OnCharacteristicReadValue(
test_characteristic.GetIdentifier(),
base::BindLambdaForTesting(
[&read_value_callback_called](blink::mojom::WebBluetoothResult result,
base::span<const uint8_t> value) {
read_value_callback_called = true;
EXPECT_EQ(blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED,
result);
EXPECT_TRUE(value.empty());
}),
device::BluetoothGattService::GattErrorCode::kNotAuthorized,
read_error_value);
EXPECT_FALSE(read_value_callback_called);
}
TEST_F(WebBluetoothServiceImplTest, IncompletePairingOnShutdown) {
RegisterTestCharacteristic();
EXPECT_CALL(battery_device_bundle().characteristic(),
ReadRemoteCharacteristic_(_))
.WillOnce(base::test::RunOnceCallback<0>(
device::BluetoothGattService::GattErrorCode::kNotAuthorized,
std::vector<uint8_t>()));
base::MockCallback<
WebBluetoothServiceImpl::RemoteCharacteristicReadValueCallback>
callback;
// The pairing is never completed so the callback won't be run before the
// test ends.
EXPECT_CALL(callback, Run(_, _)).Times(0);
service_ptr_->RemoteCharacteristicReadValue(
battery_device_bundle().characteristic().GetIdentifier(), callback.Get());
// Simulate the WebBluetoothServiceImpl being destroyed due to a navigation or
// tab closure while the pairing request is in progress.
DeleteService();
}
#endif // PAIR_BLUETOOTH_ON_DEMAND()
TEST_F(WebBluetoothServiceImplTest, DeferredStartNotifySession) {
RegisterTestCharacteristic();
FakeBluetoothCharacteristic& test_characteristic =
battery_device_bundle().characteristic();
// Test both failing.
{
base::RunLoop run_loop;
int outstanding_callbacks = 2;
test_characteristic.DeferNextStartNotification(
BluetoothGattService::GattErrorCode::kFailed);
auto callback = base::BindLambdaForTesting(
[&run_loop, &outstanding_callbacks](WebBluetoothResult result) {
EXPECT_EQ(result, WebBluetoothResult::GATT_UNKNOWN_FAILURE);
if (--outstanding_callbacks == 0) {
run_loop.Quit();
}
});
service_ptr_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
service_ptr_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
test_characteristic.ResumeDeferredStartNotification();
run_loop.Run();
}
// Test both succeeding.
{
base::RunLoop run_loop;
int outstanding_callbacks = 2;
test_characteristic.DeferNextStartNotification(
/*error_code=*/std::nullopt);
auto callback = base::BindLambdaForTesting(
[&run_loop, &outstanding_callbacks](WebBluetoothResult result) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
if (--outstanding_callbacks == 0) {
run_loop.Quit();
}
});
service_ptr_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
service_ptr_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
test_characteristic.ResumeDeferredStartNotification();
run_loop.Run();
}
}
TEST_F(WebBluetoothServiceImplTest, DeviceGattServicesDiscoveryTimeout) {
auto& battery_device_id = AddTestDevice(battery_device_bundle());
auto& device = battery_device_bundle_->device();
device.SetGattServicesDiscoveryComplete(false);
TestFuture<WebBluetoothResult,
std::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>>
get_primary_services_future;
service_ptr_->RemoteServerGetPrimaryServices(
battery_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
battery_device_bundle().service().GetUUID(),
get_primary_services_future.GetCallback());
device.SetConnected(false);
service_ptr_->DeviceChanged(device.GetAdapter(), &device);
EXPECT_EQ(get_primary_services_future.Get<0>(),
blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND);
}
TEST_F(WebBluetoothServiceImplTest, DeviceDisconnected) {
auto& battery_device_id = AddTestDevice(battery_device_bundle());
auto& device = battery_device_bundle_->device();
device.SetConnected(false);
TestFuture<WebBluetoothResult,
std::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>>
get_primary_services_future;
service_ptr_->RemoteServerGetPrimaryServices(
battery_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
battery_device_bundle().service().GetUUID(),
get_primary_services_future.GetCallback());
EXPECT_EQ(get_primary_services_future.Get<0>(),
blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND);
}
TEST_F(WebBluetoothServiceImplTest, RejectOpaqueOrigin) {
// Create a fake dispatch context to trigger a bad message in.
mojo::FakeMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
auto response_headers =
base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
response_headers->SetHeader("Content-Security-Policy",
"sandbox allow-scripts");
// The WebBluetoothService lifetime is tied to the document it was created in.
// A navigation is going to remove it. So it must be cleared to avoid the
// pointer to dangle.
service_ptr_ = nullptr;
auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
GURL("http://whatever.com"), main_test_rfh());
navigation_simulator->SetResponseHeaders(response_headers);
navigation_simulator->Start();
navigation_simulator->Commit();
mojo::Remote<blink::mojom::WebBluetoothService> service;
WebBluetoothServiceImpl::BindIfAllowed(contents()->GetPrimaryMainFrame(),
service.BindNewPipeAndPassReceiver());
EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
"Web Bluetooth is not allowed from an opaque origin.");
}
TEST_F(WebBluetoothServiceImplTest, TwoWatchAdvertisementsReqSuccess) {
TestFuture<WebBluetoothResult> future1;
TestFuture<WebBluetoothResult> future2;
auto& battry_device_id = AddTestDevice(battery_device_bundle());
auto& heart_rate_device_id = AddTestDevice(heart_rate_device_bundle());
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote1;
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote2;
battery_device_bundle().advertisement_client().BindReceiver(
client_remote1.InitWithNewEndpointAndPassReceiver());
heart_rate_device_bundle().advertisement_client().BindReceiver(
client_remote2.InitWithNewEndpointAndPassReceiver());
// Install SUCCESS result for StartScanWithFilter
adapter_->SetStartScanWithFilterResult(
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
service_ptr_->WatchAdvertisementsForDevice(
battry_device_id, std::move(client_remote1), future1.GetCallback());
service_ptr_->WatchAdvertisementsForDevice(
heart_rate_device_id, std::move(client_remote2), future2.GetCallback());
EXPECT_EQ(future1.Get(), WebBluetoothResult::SUCCESS);
EXPECT_EQ(future2.Get(), WebBluetoothResult::SUCCESS);
// StopScan call from device::BluetoothDiscoverySession dtor is expected while
// tearing down the test.
EXPECT_CALL(*adapter_, StopScan).Times(1);
}
TEST_F(WebBluetoothServiceImplTest, TwoWatchAdvertisementsReqFail) {
TestFuture<WebBluetoothResult> future1;
TestFuture<WebBluetoothResult> future2;
auto& battry_device_id = AddTestDevice(battery_device_bundle());
auto& heart_rate_device_id = AddTestDevice(heart_rate_device_bundle());
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote1;
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote2;
battery_device_bundle().advertisement_client().BindReceiver(
client_remote1.InitWithNewEndpointAndPassReceiver());
heart_rate_device_bundle().advertisement_client().BindReceiver(
client_remote2.InitWithNewEndpointAndPassReceiver());
// Install FAILED result for StartScanWithFilter
adapter_->SetStartScanWithFilterResult(
device::UMABluetoothDiscoverySessionOutcome::FAILED);
service_ptr_->WatchAdvertisementsForDevice(
battry_device_id, std::move(client_remote1), future1.GetCallback());
service_ptr_->WatchAdvertisementsForDevice(
heart_rate_device_id, std::move(client_remote2), future2.GetCallback());
EXPECT_EQ(future1.Get(), WebBluetoothResult::NO_BLUETOOTH_ADAPTER);
EXPECT_EQ(future2.Get(), WebBluetoothResult::NO_BLUETOOTH_ADAPTER);
}
TEST_F(WebBluetoothServiceImplTest,
SecWatchAdvertisementsReqAfterFirstSuccess) {
// Install SUCCESS result for StartScanWithFilter
adapter_->SetStartScanWithFilterResult(
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
TestFuture<WebBluetoothResult> future1;
auto& battry_device_id = AddTestDevice(battery_device_bundle());
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote1;
battery_device_bundle().advertisement_client().BindReceiver(
client_remote1.InitWithNewEndpointAndPassReceiver());
service_ptr_->WatchAdvertisementsForDevice(
battry_device_id, std::move(client_remote1), future1.GetCallback());
EXPECT_EQ(future1.Get(), WebBluetoothResult::SUCCESS);
// When second watchAdvertisements request comes in and there is an active
// discovery session, it should get SUCCESS result directly.
TestFuture<WebBluetoothResult> future2;
auto& heart_rate_device_id = AddTestDevice(heart_rate_device_bundle());
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client_remote2;
heart_rate_device_bundle().advertisement_client().BindReceiver(
client_remote2.InitWithNewEndpointAndPassReceiver());
// Install FAILED result for StartScanWithFilter.
// This is to ensure that the second request succeed for an active discovery
// session instead of going through entire StartScanWithFilter call.
adapter_->SetStartScanWithFilterResult(
device::UMABluetoothDiscoverySessionOutcome::FAILED);
service_ptr_->WatchAdvertisementsForDevice(
heart_rate_device_id, std::move(client_remote2), future2.GetCallback());
EXPECT_EQ(future2.Get(), WebBluetoothResult::SUCCESS);
// StopScan call from device::BluetoothDiscoverySession dtor is expected while
// tearing down the test.
EXPECT_CALL(*adapter_, StopScan).Times(1);
}
TEST_F(WebBluetoothServiceImplTest, ServiceDestroyedDuringAdapterAcquisition) {
// Remove the adapter configured by the base test to ensure an async
// AcquireAdapter flow.
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(nullptr);
// Due to the service being destroyed before acquisition, this adapter will
// never receive these observer calls.
EXPECT_CALL(*adapter_, AddObserver).Times(0);
EXPECT_CALL(*adapter_, RemoveObserver).Times(0);
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(adapter_);
// Post a task that destroys the service during adapter acquisition.
// This is a hack; destruction is normally implicitly triggered by
// navigation or destruction of the frame itself, and not explicitly
// like this test does.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
WebBluetoothServiceImpl::DeleteForCurrentDocument(
&service_ptr_.ExtractAsDangling()->render_frame_host());
}));
// GetAvailability connects the Web Bluetooth service to the adapter,
// running through the AcquireAdapter flow.
TestFuture<bool> future_1;
service_ptr_->GetAvailability(future_1.GetCallback());
EXPECT_TRUE(future_1.Wait());
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(nullptr);
}
class WebBluetoothServiceImplTestWithBaseAdapter
: public RenderViewHostImplTestHarness,
public WithParamInterface<bool> {
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Set up a fake system adapter.
base_adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
EXPECT_CALL(*base_adapter_, IsPresent()).WillRepeatedly(Return(true));
BluetoothAdapterFactoryWrapper::Get().SetAdapterInternal(
base_adapter_, /*is_override_adapter=*/false);
// Hook up the test bluetooth delegate.
old_browser_client_ = SetBrowserClientForTesting(&browser_client_);
contents()->GetPrimaryMainFrame()->InitializeRenderFrameIfNeeded();
// Navigate to a URL so that WebBluetoothServiceImpl::GetOrigin() returns a
// valid origin. This is required when checking for Bluetooth permissions.
constexpr char kTestURL[] = "https://my-battery-level.com";
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GURL(kTestURL));
// Set up an adapter.
mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver =
service_.BindNewPipeAndPassReceiver();
service_ptr_ = WebBluetoothServiceImpl::CreateForTesting(
contents()->GetPrimaryMainFrame(), std::move(receiver));
// GetAvailability connects the Web Bluetooth service to the adapter. Call
// it twice in parallel to exercise what happens when multiple requests to
// acquire the BluetoothAdapter are in flight.
TestFuture<bool> future_1;
TestFuture<bool> future_2;
service_ptr_->GetAvailability(future_1.GetCallback());
service_ptr_->GetAvailability(future_2.GetCallback());
EXPECT_TRUE(future_1.Wait());
EXPECT_TRUE(future_2.Wait());
}
void TearDown() override {
base_adapter_.reset();
service_ptr_ = nullptr;
SetBrowserClientForTesting(old_browser_client_);
RenderViewHostImplTestHarness::TearDown();
}
protected:
scoped_refptr<FakeBluetoothAdapter> base_adapter_;
raw_ptr<WebBluetoothServiceImpl> service_ptr_ = nullptr;
mojo::Remote<blink::mojom::WebBluetoothService> service_;
TestContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> old_browser_client_ = nullptr;
};
TEST_F(WebBluetoothServiceImplTestWithBaseAdapter,
EmulatedAdapterRemovalRestoresOriginalAdapter) {
// Confirm that the system adapter is being used.
EXPECT_EQ(BluetoothAdapterFactoryWrapper::Get().GetAdapter(service_ptr_),
base_adapter_);
// Create an override adapter and configure to use it.
scoped_refptr<FakeBluetoothAdapter> override_adapter_ =
base::MakeRefCounted<FakeBluetoothAdapter>();
EXPECT_CALL(*override_adapter_, IsPresent()).WillRepeatedly(Return(true));
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(
override_adapter_);
// Confirm that the override adapter is being used.
EXPECT_EQ(BluetoothAdapterFactoryWrapper::Get().GetAdapter(service_ptr_),
override_adapter_);
// Remove the override and confirm that it returns to the system adapter.
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(nullptr);
EXPECT_EQ(BluetoothAdapterFactoryWrapper::Get().GetAdapter(service_ptr_),
base_adapter_);
}
} // namespace content