blob: b39ea00e060964da1e8826998462aa4cb1fbc638 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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 "base/macros.h"
#include "base/test/bind_test_util.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "device/bluetooth/bluetooth_adapter_factory_wrapper.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::Return;
namespace content {
namespace {
const char kBatteryServiceUUIDString[] = "0000180f-0000-1000-8000-00805f9b34fb";
class FakeBluetoothScanningPrompt : public BluetoothScanningPrompt {
public:
FakeBluetoothScanningPrompt(const EventHandler& event_handler,
content::BluetoothScanningPrompt::Event event) {
event_handler.Run(event);
}
~FakeBluetoothScanningPrompt() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(FakeBluetoothScanningPrompt);
};
class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
public:
FakeBluetoothAdapter() = default;
// device::BluetoothAdapter:
void StartScanWithFilter(
std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
DiscoverySessionResultCallback callback) override {
std::move(callback).Run(
/*is_error=*/false,
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
}
protected:
~FakeBluetoothAdapter() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(FakeBluetoothAdapter);
};
class FakeWebContentsDelegate : public content::WebContentsDelegate {
public:
FakeWebContentsDelegate() = default;
~FakeWebContentsDelegate() override = default;
std::unique_ptr<BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
RenderFrameHost* frame,
const BluetoothScanningPrompt::EventHandler& event_handler) override {
return std::make_unique<FakeBluetoothScanningPrompt>(event_handler, event_);
}
void set_event(content::BluetoothScanningPrompt::Event event) {
event_ = event;
}
private:
content::BluetoothScanningPrompt::Event event_ =
content::BluetoothScanningPrompt::Event::kAllow;
DISALLOW_COPY_AND_ASSIGN(FakeWebContentsDelegate);
};
class FakeWebBluetoothScanClientImpl : blink::mojom::WebBluetoothScanClient {
public:
FakeWebBluetoothScanClientImpl() = default;
~FakeWebBluetoothScanClientImpl() override = default;
// blink::mojom::WebBluetoothScanClient:
void ScanEvent(blink::mojom::WebBluetoothScanResultPtr result) override {}
void BindRequest(
blink::mojom::WebBluetoothScanClientAssociatedRequest request) {
binding_.Bind(std::move(request));
binding_.set_connection_error_handler(
base::BindOnce(&FakeWebBluetoothScanClientImpl::OnConnectionError,
base::Unretained(this)));
}
void OnConnectionError() { on_connection_error_called_ = true; }
bool on_connection_error_called() { return on_connection_error_called_; }
private:
mojo::AssociatedBinding<blink::mojom::WebBluetoothScanClient> binding_{this};
bool on_connection_error_called_ = false;
DISALLOW_COPY_AND_ASSIGN(FakeWebBluetoothScanClientImpl);
};
} // namespace
class WebBluetoothServiceImplTest : public RenderViewHostImplTestHarness {
public:
WebBluetoothServiceImplTest() = default;
~WebBluetoothServiceImplTest() override = default;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Set up an adapter.
scoped_refptr<FakeBluetoothAdapter> adapter(new FakeBluetoothAdapter());
EXPECT_CALL(*adapter, IsPresent()).WillRepeatedly(Return(true));
device::BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterForTesting(
adapter);
contents()->GetMainFrame()->InitializeRenderFrameIfNeeded();
contents()->SetDelegate(&delegate_);
// Simulate a frame connected to a bluetooth service.
service_ =
contents()->GetMainFrame()->CreateWebBluetoothServiceForTesting();
}
void TearDown() override {
service_ = nullptr;
RenderViewHostImplTestHarness::TearDown();
}
protected:
blink::mojom::WebBluetoothLeScanFilterPtr CreateScanFilter(
const std::string& name,
const std::string& name_prefix) {
base::Optional<std::vector<device::BluetoothUUID>> services;
services.emplace();
services->push_back(device::BluetoothUUID(kBatteryServiceUUIDString));
return blink::mojom::WebBluetoothLeScanFilter::New(services, name,
name_prefix);
}
blink::mojom::RequestScanningStartResultPtr RequestScanningStart(
const blink::mojom::WebBluetoothLeScanFilter& filter,
FakeWebBluetoothScanClientImpl* client_impl) {
blink::mojom::WebBluetoothScanClientAssociatedPtrInfo client_info;
client_impl->BindRequest(mojo::MakeRequest(&client_info));
auto options = blink::mojom::WebBluetoothRequestLEScanOptions::New();
options->filters.emplace();
auto filter_ptr = blink::mojom::WebBluetoothLeScanFilter::New(filter);
options->filters->push_back(std::move(filter_ptr));
base::RunLoop loop;
blink::mojom::RequestScanningStartResultPtr result;
service_->RequestScanningStart(
std::move(client_info), std::move(options),
base::BindLambdaForTesting(
[&](blink::mojom::RequestScanningStartResultPtr p) {
result = std::move(p);
loop.Quit();
}));
loop.Run();
return result;
}
WebBluetoothServiceImpl* service_;
FakeWebContentsDelegate delegate_;
private:
DISALLOW_COPY_AND_ASSIGN(WebBluetoothServiceImplTest);
};
TEST_F(WebBluetoothServiceImplTest, PermissionAllowed) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
FakeWebBluetoothScanClientImpl client_impl;
blink::mojom::RequestScanningStartResultPtr result =
RequestScanningStart(*filter, &client_impl);
EXPECT_FALSE(result->is_error_result());
// |filters| should be allowed.
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest, PermissionPromptCanceled) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
// Set |event_| to kCanceled in the FakeWebContentsDelegate so that the call
// to WebBluetoothServiceImpl::RequestScanningStart() will not grant the
// permission.
delegate_.set_event(content::BluetoothScanningPrompt::Event::kCanceled);
FakeWebBluetoothScanClientImpl client_impl;
blink::mojom::RequestScanningStartResultPtr result =
RequestScanningStart(*filter, &client_impl);
EXPECT_TRUE(result->is_error_result());
EXPECT_EQ(blink::mojom::WebBluetoothResult::PROMPT_CANCELED,
result->get_error_result());
// |filters| should still not be allowed.
EXPECT_FALSE(service_->AreScanFiltersAllowed(filters));
}
TEST_F(WebBluetoothServiceImplTest,
BluetoothScanningPermissionRevokedWhenTabHidden) {
blink::mojom::WebBluetoothLeScanFilterPtr filter = CreateScanFilter("a", "b");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothScanClientImpl client_impl;
blink::mojom::RequestScanningStartResultPtr result =
RequestScanningStart(*filter, &client_impl);
EXPECT_FALSE(result->is_error_result());
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
contents()->SetVisibility(content::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");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters;
filters.emplace();
filters->push_back(filter.Clone());
FakeWebBluetoothScanClientImpl client_impl;
RequestScanningStart(*filter, &client_impl);
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters));
contents()->SetVisibility(content::Visibility::OCCLUDED);
// 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");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters_1;
filters_1.emplace();
filters_1->push_back(filter_1.Clone());
FakeWebBluetoothScanClientImpl client_impl_1;
blink::mojom::RequestScanningStartResultPtr result_1 =
RequestScanningStart(*filter_1, &client_impl_1);
EXPECT_FALSE(result_1->is_error_result());
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_1));
EXPECT_FALSE(client_impl_1.on_connection_error_called());
blink::mojom::WebBluetoothLeScanFilterPtr filter_2 =
CreateScanFilter("c", "d");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters_2;
filters_2.emplace();
filters_2->push_back(filter_2.Clone());
FakeWebBluetoothScanClientImpl client_impl_2;
blink::mojom::RequestScanningStartResultPtr result_2 =
RequestScanningStart(*filter_2, &client_impl_2);
EXPECT_FALSE(result_2->is_error_result());
EXPECT_TRUE(service_->AreScanFiltersAllowed(filters_2));
EXPECT_FALSE(client_impl_2.on_connection_error_called());
// Set |event_| to kBlock in the FakeWebContentsDelegate so that the next call
// to WebBluetoothServiceImpl::RequestScanningStart() will block the
// permission.
delegate_.set_event(content::BluetoothScanningPrompt::Event::kBlock);
blink::mojom::WebBluetoothLeScanFilterPtr filter_3 =
CreateScanFilter("e", "f");
base::Optional<WebBluetoothServiceImpl::ScanFilters> filters_3;
filters_3.emplace();
filters_3->push_back(filter_3.Clone());
FakeWebBluetoothScanClientImpl client_impl_3;
blink::mojom::RequestScanningStartResultPtr result_3 =
RequestScanningStart(*filter_3, &client_impl_3);
EXPECT_TRUE(result_3->is_error_result());
EXPECT_EQ(blink::mojom::WebBluetoothResult::SCANNING_BLOCKED,
result_3->get_error_result());
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());
}
} // namespace content