blob: 948e877c3828c3ff05d30609d128a6b73dd83872 [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 <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.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/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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.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;
const char kBatteryServiceUUIDString[] = "0000180f-0000-1000-8000-00805f9b34fb";
using PromptEventCallback =
base::OnceCallback<void(BluetoothScanningPrompt::Event)>;
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(
const std::vector<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 {
std::move(callback).Run(
/*is_error=*/false,
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
}
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;
}
private:
~FakeBluetoothAdapter() override = default;
};
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(
absl::optional<BluetoothGattService::GattErrorCode> error_code) {
defer_next_start_notification_ = true;
notification_start_error_code_ = error_code;
}
private:
bool defer_next_start_notification_ = false;
absl::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 absl::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 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:
FakeBluetoothScanningPrompt* 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 FakeWebBluetoothAdvertisementClientImpl
: blink::mojom::WebBluetoothAdvertisementClient {
public:
FakeWebBluetoothAdvertisementClientImpl() = default;
~FakeWebBluetoothAdvertisementClientImpl() override = default;
// Move-only class.
FakeWebBluetoothAdvertisementClientImpl(
const FakeWebBluetoothAdvertisementClientImpl&) = delete;
FakeWebBluetoothAdvertisementClientImpl& operator=(
const FakeWebBluetoothAdvertisementClientImpl&) = 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(
&FakeWebBluetoothAdvertisementClientImpl::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 instances for battery device level testing.
class FakeBatteryObjectBundle {
public:
explicit FakeBatteryObjectBundle(scoped_refptr<FakeBluetoothAdapter> adapter)
: adapter_(std::move(adapter)) {
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");
auto device = std::make_unique<FakeBluetoothDevice>(adapter_.get());
device_ = device.get();
auto service = std::make_unique<FakeBluetoothGattService>(
device.get(), kBatteryServiceId, kBatteryServiceUUID);
service_ = service.get();
constexpr BluetoothRemoteGattCharacteristic::Properties
kTestCharacteristicProperties =
BluetoothRemoteGattCharacteristic::PROPERTY_BROADCAST |
BluetoothRemoteGattCharacteristic::PROPERTY_READ |
BluetoothRemoteGattCharacteristic::PROPERTY_INDICATE;
auto characteristic = std::make_unique<FakeBluetoothCharacteristic>(
service_, kBatteryLevelCharacteristicId,
kBatteryLevelCharacteristicUUID, kTestCharacteristicProperties,
BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
characteristic_ = characteristic.get();
service->AddMockCharacteristic(std::move(characteristic));
device->AddMockService(std::move(service));
adapter_->AddMockDevice(std::move(device));
}
FakeBatteryObjectBundle& operator=(const FakeBatteryObjectBundle&) = delete;
FakeBluetoothDevice& device() { return *device_; }
FakeBluetoothGattService& service() { return *service_; }
FakeBluetoothCharacteristic& characteristic() { return *characteristic_; }
private:
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<FakeBluetoothDevice> device_ = nullptr;
raw_ptr<FakeBluetoothGattService> service_ = nullptr;
raw_ptr<FakeBluetoothCharacteristic> characteristic_ = nullptr;
}; // namespace
} // 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_ = new FakeBluetoothAdapter();
EXPECT_CALL(*adapter_, IsPresent()).WillRepeatedly(Return(true));
BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterForTesting(
adapter_);
battery_object_bundle_ =
std::make_unique<FakeBatteryObjectBundle>(adapter_);
// 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.
service_ = contents()
->GetPrimaryMainFrame()
->CreateWebBluetoothServiceForTesting();
// 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_->GetAvailability(future_1.GetCallback());
service_->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());
}
void TearDown() override {
adapter_.reset();
battery_object_bundle_.reset();
service_ = 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) {
absl::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=*/absl::nullopt);
}
blink::mojom::WebBluetoothResult RequestScanningStartAndSimulatePromptEvent(
const blink::mojom::WebBluetoothLeScanFilter& filter,
FakeWebBluetoothAdvertisementClientImpl* client_impl,
BluetoothScanningPrompt::Event event) {
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client;
client_impl->BindReceiver(client.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=*/absl::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_->RequestScanningStart(
std::move(client), 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::ThreadTaskRunnerHandle::Get()->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 device_options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
device_options->accept_all_devices = true;
device_options->optional_services.push_back(
test_bundle().service().GetUUID());
const blink::WebBluetoothDeviceId& test_device_id =
service_->allowed_devices().AddDevice(
test_bundle().device().GetAddress(), device_options);
auto& device = battery_object_bundle_->device();
device.SetGattServicesDiscoveryComplete(true);
FakeBluetoothCharacteristic& test_characteristic =
test_bundle().characteristic();
{
base::RunLoop run_loop;
service_->RemoteServerGetPrimaryServices(
test_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
test_bundle().service().GetUUID(),
base::BindLambdaForTesting(
[&run_loop](
WebBluetoothResult result,
absl::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>
services) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
run_loop.Quit();
}));
run_loop.Run();
}
{
base::RunLoop run_loop;
service_->RemoteServiceGetCharacteristics(
test_bundle().service().GetIdentifier(),
WebBluetoothGATTQueryQuantity::SINGLE, test_characteristic.GetUUID(),
base::BindLambdaForTesting(
[&run_loop](
WebBluetoothResult result,
absl::optional<
std::vector<WebBluetoothRemoteGATTCharacteristicPtr>>
characteristic) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
run_loop.Quit();
}));
run_loop.Run();
}
}
FakeBatteryObjectBundle& test_bundle() const {
return *battery_object_bundle_;
}
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<WebBluetoothServiceImpl> service_;
TestContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> old_browser_client_ = nullptr;
std::unique_ptr<FakeBatteryObjectBundle> battery_object_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_->RequestDevice(std::move(options), callback.Get());
base::RunLoop loop;
std::exchange(service_, nullptr)->ResetAndDeleteThis();
loop.RunUntilIdle();
}
TEST_F(WebBluetoothServiceImplTest, PermissionAllowed) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
FakeWebBluetoothAdvertisementClientImpl client_impl;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client_impl, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result, blink::mojom::WebBluetoothResult::SUCCESS);
// |filters| should be allowed.
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest, DestroyedDuringRequestScanningStart) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
FakeWebBluetoothAdvertisementClientImpl client_impl;
mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient>
client;
client_impl.BindReceiver(client.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_->RequestScanningStart(std::move(client), std::move(options),
callback.Get());
// Post a task to delete the WebBluetoothService state during a call to
// RequestScanningStart().
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindLambdaForTesting([this]() {
std::exchange(service_, nullptr)->ResetAndDeleteThis();
}));
loop.RunUntilIdle();
}
TEST_F(WebBluetoothServiceImplTest, PermissionPromptCanceled) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
FakeWebBluetoothAdvertisementClientImpl client_impl;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client_impl, BluetoothScanningPrompt::Event::kCanceled);
EXPECT_EQ(blink::mojom::WebBluetoothResult::PROMPT_CANCELED, result);
// |filters| should still not be allowed.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenTabHidden) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl;
blink::mojom::WebBluetoothResult result =
RequestScanningStartAndSimulatePromptEvent(
*filter, &client_impl, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
contents()->SetVisibilityAndNotifyObservers(Visibility::HIDDEN);
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenTabOccluded) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl;
RequestScanningStartAndSimulatePromptEvent(
*filter, &client_impl, BluetoothScanningPrompt::Event::kAllow);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
contents()->SetVisibilityAndNotifyObservers(Visibility::OCCLUDED);
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenFocusIsLost) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl;
RequestScanningStartAndSimulatePromptEvent(
*filter, &client_impl, BluetoothScanningPrompt::Event::kAllow);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
main_test_rfh()->GetRenderWidgetHost()->LostFocus();
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenBlocked) {
blink::mojom::WebBluetoothLeScanFilterPtr filter_1 =
CreateScanFilter("a", "b");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters_1;
filters_1.emplace();
filters_1->push_back(filter_1.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl_1;
blink::mojom::WebBluetoothResult result_1 =
RequestScanningStartAndSimulatePromptEvent(
*filter_1, &client_impl_1, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result_1, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_1));
EXPECT_FALSE(client_impl_1.on_connection_error_called());
blink::mojom::WebBluetoothLeScanFilterPtr filter_2 =
CreateScanFilter("c", "d");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters_2;
filters_2.emplace();
filters_2->push_back(filter_2.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl_2;
blink::mojom::WebBluetoothResult result_2 =
RequestScanningStartAndSimulatePromptEvent(
*filter_2, &client_impl_2, BluetoothScanningPrompt::Event::kAllow);
EXPECT_EQ(result_2, blink::mojom::WebBluetoothResult::SUCCESS);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_2));
EXPECT_FALSE(client_impl_2.on_connection_error_called());
blink::mojom::WebBluetoothLeScanFilterPtr filter_3 =
CreateScanFilter("e", "f");
absl::optional<WebBluetoothServiceImpl::ScanFilters> filters_3;
filters_3.emplace();
filters_3->push_back(filter_3.Clone());
FakeWebBluetoothAdvertisementClientImpl client_impl_3;
blink::mojom::WebBluetoothResult result_3 =
RequestScanningStartAndSimulatePromptEvent(
*filter_3, &client_impl_3, BluetoothScanningPrompt::Event::kBlock);
EXPECT_EQ(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED, result_3);
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters_3));
// The previously granted Bluetooth scanning permission should be revoked.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters_1));
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters_2));
base::RunLoop().RunUntilIdle();
// All existing scanning clients are disconnected.
EXPECT_TRUE(client_impl_1.on_connection_error_called());
EXPECT_TRUE(client_impl_2.on_connection_error_called());
EXPECT_TRUE(client_impl_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 it's 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_->OnCharacteristicReadValue(
characteristic_instance_id,
base::BindLambdaForTesting(
[&callback_called](
blink::mojom::WebBluetoothResult result,
const absl::optional<std::vector<uint8_t>>& value) {
callback_called = true;
EXPECT_EQ(
blink::mojom::WebBluetoothResult::GATT_OPERATION_IN_PROGRESS,
result);
EXPECT_FALSE(value.has_value());
}),
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 =
test_bundle().characteristic();
MockWebBluetoothPairingManager* pairing_manager =
new MockWebBluetoothPairingManager();
service_->SetPairingManagerForTesting(
std::unique_ptr<WebBluetoothPairingManager>(pairing_manager));
EXPECT_CALL(*pairing_manager, PairForCharacteristicReadValue(_, _)).Times(1);
service_->OnCharacteristicReadValue(
test_characteristic.GetIdentifier(),
base::BindLambdaForTesting(
[&read_value_callback_called](
blink::mojom::WebBluetoothResult result,
const absl::optional<std::vector<uint8_t>>& value) {
read_value_callback_called = true;
EXPECT_EQ(blink::mojom::WebBluetoothResult::GATT_NOT_AUTHORIZED,
result);
EXPECT_FALSE(value.has_value());
}),
device::BluetoothGattService::GattErrorCode::kNotAuthorized,
read_error_value);
EXPECT_FALSE(read_value_callback_called);
}
TEST_F(WebBluetoothServiceImplTest, IncompletePairingOnShutdown) {
RegisterTestCharacteristic();
EXPECT_CALL(test_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_->RemoteCharacteristicReadValue(
test_bundle().characteristic().GetIdentifier(), callback.Get());
// Simulate the WebBluetoothServiceImpl being destroyed due to a navigation or
// tab closure while the pairing request is in progress.
std::exchange(service_, nullptr)->ResetAndDeleteThis();
}
#endif // PAIR_BLUETOOTH_ON_DEMAND()
TEST_F(WebBluetoothServiceImplTest, DeferredStartNotifySession) {
RegisterTestCharacteristic();
FakeBluetoothCharacteristic& test_characteristic =
test_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_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
service_->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=*/absl::nullopt);
auto callback = base::BindLambdaForTesting(
[&run_loop, &outstanding_callbacks](WebBluetoothResult result) {
EXPECT_EQ(result, WebBluetoothResult::SUCCESS);
if (--outstanding_callbacks == 0)
run_loop.Quit();
});
service_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
service_->RemoteCharacteristicStartNotifications(
test_characteristic.GetIdentifier(),
BindCharacteristicClientAndPassRemote(), callback);
test_characteristic.ResumeDeferredStartNotification();
run_loop.Run();
}
}
TEST_F(WebBluetoothServiceImplTest, DeviceGattServicesDiscoveryTimeout) {
auto device_options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
device_options->accept_all_devices = true;
device_options->optional_services.push_back(
test_bundle().service().GetUUID());
const blink::WebBluetoothDeviceId& test_device_id =
service_->allowed_devices().AddDevice(test_bundle().device().GetAddress(),
device_options);
auto& device = battery_object_bundle_->device();
device.SetGattServicesDiscoveryComplete(false);
TestFuture<WebBluetoothResult,
absl::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>>
get_primary_services_future;
service_->RemoteServerGetPrimaryServices(
test_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
test_bundle().service().GetUUID(),
get_primary_services_future.GetCallback());
device.SetConnected(false);
service_->DeviceChanged(device.GetAdapter(), &device);
EXPECT_EQ(get_primary_services_future.Get<0>(),
blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND);
}
TEST_F(WebBluetoothServiceImplTest, DeviceDisconnected) {
auto device_options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
device_options->accept_all_devices = true;
device_options->optional_services.push_back(
test_bundle().service().GetUUID());
const blink::WebBluetoothDeviceId& test_device_id =
service_->allowed_devices().AddDevice(test_bundle().device().GetAddress(),
device_options);
auto& device = battery_object_bundle_->device();
device.SetConnected(false);
TestFuture<WebBluetoothResult,
absl::optional<std::vector<WebBluetoothRemoteGATTServicePtr>>>
get_primary_services_future;
service_->RemoteServerGetPrimaryServices(
test_device_id, WebBluetoothGATTQueryQuantity::SINGLE,
test_bundle().service().GetUUID(),
get_primary_services_future.GetCallback());
EXPECT_EQ(get_primary_services_future.Get<0>(),
blink::mojom::WebBluetoothResult::NO_SERVICES_FOUND);
}
} // namespace content