blob: 336fb576582dc1853d1f318c22eb192aad98fb80 [file] [log] [blame]
// Copyright 2016 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.
// This file contains browsertests for Web Bluetooth that depend on behavior
// defined in chrome/, not just in content/.
#include "base/callback.h"
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/permissions/permission_context_base.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.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_connection.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
namespace {
constexpr char kDeviceAddress[] = "00:00:00:00:00:00";
constexpr char kHeartRateUUIDString[] = "0000180d-0000-1000-8000-00805f9b34fb";
const device::BluetoothUUID kHeartRateUUID(kHeartRateUUIDString);
class FakeBluetoothAdapter
: public testing::NiceMock<device::MockBluetoothAdapter> {
public:
FakeBluetoothAdapter() = default;
// Move-only class
FakeBluetoothAdapter(const FakeBluetoothAdapter&) = delete;
FakeBluetoothAdapter& operator=(const FakeBluetoothAdapter&) = delete;
void SetIsPresent(bool is_present) { is_present_ = is_present; }
void SimulateDeviceAdvertisementReceived(
const std::string& device_address) const {
for (auto& observer : observers_) {
observer.DeviceAdvertisementReceived(
device_address, /*device_name=*/base::nullopt,
/*advertisement_name=*/base::nullopt,
/*rssi=*/base::nullopt, /*tx_power=*/base::nullopt,
/*appearance=*/base::nullopt,
/*advertised_uuids=*/{}, /*service_data_map=*/{},
/*manufacturer_data_map=*/{});
}
}
// device::BluetoothAdapter implementation:
void AddObserver(device::BluetoothAdapter::Observer* observer) override {
device::BluetoothAdapter::AddObserver(observer);
}
bool IsPresent() const override { return is_present_; }
bool IsPowered() const override { return true; }
device::BluetoothAdapter::ConstDeviceList GetDevices() const override {
device::BluetoothAdapter::ConstDeviceList devices;
for (const auto& it : mock_devices_)
devices.push_back(it.get());
return devices;
}
device::BluetoothDevice* GetDevice(const std::string& address) override {
for (const auto& it : mock_devices_) {
if (it->GetAddress() == address)
return it.get();
}
return nullptr;
}
void StartScanWithFilter(
std::unique_ptr<device::BluetoothDiscoveryFilter> filter,
base::OnceCallback<void(/*is_error*/ bool,
device::UMABluetoothDiscoverySessionOutcome)>
callback) override {
std::move(callback).Run(
/*is_error=*/false,
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
}
protected:
~FakeBluetoothAdapter() override = default;
bool is_present_ = true;
};
class FakeBluetoothGattService
: public testing::NiceMock<device::MockBluetoothGattService> {
public:
FakeBluetoothGattService(device::MockBluetoothDevice* device,
const std::string& identifier,
const device::BluetoothUUID& uuid)
: testing::NiceMock<device::MockBluetoothGattService>(device,
identifier,
uuid,
/*is_primary=*/true,
/*is_local=*/true) {
}
// Move-only class
FakeBluetoothGattService(const FakeBluetoothGattService&) = delete;
FakeBluetoothGattService operator=(const FakeBluetoothGattService&) = delete;
};
class FakeBluetoothGattConnection
: public testing::NiceMock<device::MockBluetoothGattConnection> {
public:
FakeBluetoothGattConnection(scoped_refptr<device::BluetoothAdapter> adapter,
const std::string& device_address)
: testing::NiceMock<device::MockBluetoothGattConnection>(adapter,
device_address) {
}
// Move-only class
FakeBluetoothGattConnection(const FakeBluetoothGattConnection&) = delete;
FakeBluetoothGattConnection operator=(const FakeBluetoothGattConnection&) =
delete;
};
class FakeBluetoothDevice
: public testing::NiceMock<device::MockBluetoothDevice> {
public:
FakeBluetoothDevice(device::MockBluetoothAdapter* adapter,
const std::string& address)
: testing::NiceMock<device::MockBluetoothDevice>(adapter,
/*bluetooth_class=*/0u,
/*name=*/"Test Device",
address,
/*paired=*/true,
/*connected=*/true) {}
void CreateGattConnection(
base::OnceCallback<void(std::unique_ptr<device::BluetoothGattConnection>)>
callback,
base::OnceCallback<void(enum ConnectErrorCode)> error_callback,
base::Optional<device::BluetoothUUID> service_uuid =
base::nullopt) override {
SetConnected(true);
gatt_services_discovery_complete_ = true;
std::move(callback).Run(
std::make_unique<FakeBluetoothGattConnection>(adapter_, GetAddress()));
}
bool IsGattServicesDiscoveryComplete() const override {
return gatt_services_discovery_complete_;
}
std::vector<device::BluetoothRemoteGattService*> GetGattServices()
const override {
return GetMockServices();
}
// Move-only class
FakeBluetoothDevice(const FakeBluetoothDevice&) = delete;
FakeBluetoothDevice& operator=(const FakeBluetoothDevice&) = delete;
protected:
bool gatt_services_discovery_complete_ = false;
};
class FakeBluetoothChooser : public content::BluetoothChooser {
public:
FakeBluetoothChooser(content::BluetoothChooser::EventHandler event_handler,
const base::Optional<std::string>& device_to_select)
: event_handler_(event_handler), device_to_select_(device_to_select) {}
~FakeBluetoothChooser() override = default;
// content::BluetoothChooser implementation:
void AddOrUpdateDevice(const std::string& device_id,
bool should_update_name,
const base::string16& device_name,
bool is_gatt_connected,
bool is_paired,
int signal_strength_level) override {
// Select the first device that is added if |device_to_select_| is not
// populated.
if (!device_to_select_) {
event_handler_.Run(content::BluetoothChooser::Event::SELECTED, device_id);
return;
}
// Otherwise, select the added device if its device ID matches
// |device_to_select_|.
if (device_to_select_.value() == device_id) {
event_handler_.Run(content::BluetoothChooser::Event::SELECTED, device_id);
}
}
// Move-only class
FakeBluetoothChooser(const FakeBluetoothChooser&) = delete;
FakeBluetoothChooser& operator=(const FakeBluetoothChooser&) = delete;
private:
content::BluetoothChooser::EventHandler event_handler_;
base::Optional<std::string> device_to_select_;
};
class FakeBluetoothScanningPrompt : public content::BluetoothScanningPrompt {
public:
explicit FakeBluetoothScanningPrompt(
const content::BluetoothScanningPrompt::EventHandler& event_handler)
: event_handler_(event_handler) {}
~FakeBluetoothScanningPrompt() override = default;
// Move-only class
FakeBluetoothScanningPrompt(const FakeBluetoothScanningPrompt&) = delete;
FakeBluetoothScanningPrompt& operator=(const FakeBluetoothScanningPrompt&) =
delete;
void RunPromptEventHandler(content::BluetoothScanningPrompt::Event event) {
if (event_handler_.is_null()) {
FAIL() << "event_handler_ is not set";
return;
}
event_handler_.Run(event);
}
protected:
content::BluetoothScanningPrompt::EventHandler event_handler_;
};
class TestWebContentsDelegate : public content::WebContentsDelegate {
public:
void SetDeviceToSelect(const std::string& device_address) {
device_to_select_ = device_address;
}
// This method waits until ShowBluetoothScanningPrompt() has been called and
// |scanning_prompt_| contains a pointer to the created prompt, so the test
// will timeout if |navigator.bluetooth.requestLEScan()| has not been called
// in JavaScript.
void RunPromptEventHandler(content::BluetoothScanningPrompt::Event event) {
if (!scanning_prompt_)
scanning_prompt_creation_loop_.Run();
scanning_prompt_->RunPromptEventHandler(event);
}
protected:
// content::WebContentsDelegate implementation:
std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler) override {
return std::make_unique<FakeBluetoothChooser>(event_handler,
device_to_select_);
}
std::unique_ptr<content::BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
content::RenderFrameHost* frame,
const content::BluetoothScanningPrompt::EventHandler& event_handler)
override {
auto scanning_prompt =
std::make_unique<FakeBluetoothScanningPrompt>(event_handler);
scanning_prompt_ = scanning_prompt.get();
scanning_prompt_creation_loop_.Quit();
return scanning_prompt;
}
base::Optional<std::string> device_to_select_;
FakeBluetoothScanningPrompt* scanning_prompt_;
// This RunLoop is used to ensure that |scanning_prompt_| is not nullptr when
// RunPromptEventHandler() is called.
base::RunLoop scanning_prompt_creation_loop_;
};
class WebBluetoothTest : public InProcessBrowserTest {
public:
WebBluetoothTest() = default;
~WebBluetoothTest() override = default;
// Move-only class
WebBluetoothTest(const WebBluetoothTest&) = delete;
WebBluetoothTest& operator=(const WebBluetoothTest&) = delete;
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
// TODO(juncai): Remove this switch once Web Bluetooth is supported on Linux
// and Windows.
// https://crbug.com/570344
// https://crbug.com/507419
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
void SetUpOnMainThread() override {
// Web Bluetooth permissions are granted for an origin. The tests for Web
// Bluetooth permissions run code across a browser restart by splitting the
// tests into separate test cases where the test prefixed with PRE_ runs
// first. EmbeddedTestServer is not capable of maintaining a consistent
// origin across the separate tests, so URLLoaderInterceptor is used instead
// to intercept navigation requests and serve the test page. This enables
// the separate test cases to grant and check permissions for the same
// origin.
url_loader_interceptor_ =
std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
[](content::URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url.host() == "example.com") {
content::URLLoaderInterceptor::WriteResponse(
"content/test/data/simple_page.html", params->client.get());
return true;
}
return false;
}));
ui_test_utils::NavigateToURL(browser(), GURL("https://example.com"));
web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_THAT(
web_contents_->GetMainFrame()->GetLastCommittedOrigin().Serialize(),
testing::StartsWith("https://example.com"));
adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
global_values_ =
device::BluetoothAdapterFactory::Get().InitGlobalValuesForTesting();
global_values_->SetLESupported(true);
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
}
void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
void AddFakeDevice(const std::string& device_address) {
auto fake_device =
std::make_unique<FakeBluetoothDevice>(adapter_.get(), device_address);
fake_device->AddUUID(kHeartRateUUID);
fake_device->AddMockService(std::make_unique<FakeBluetoothGattService>(
fake_device.get(), kHeartRateUUIDString, kHeartRateUUID));
adapter_->AddMockDevice(std::move(fake_device));
}
void RemoveFakeDevice(const std::string& device_address) {
adapter_->RemoveMockDevice(device_address);
}
void SimulateDeviceAdvertisement(const std::string& device_address) {
adapter_->SimulateDeviceAdvertisementReceived(device_address);
}
TestWebContentsDelegate* UseAndGetTestWebContentsDelegate() {
if (!test_delegate_)
test_delegate_ = std::make_unique<TestWebContentsDelegate>();
web_contents_->SetDelegate(test_delegate_.get());
return test_delegate_.get();
}
void SetDeviceToSelect(const std::string& device_address) {
test_delegate_->SetDeviceToSelect(device_address);
}
std::unique_ptr<device::BluetoothAdapterFactory::GlobalValuesForTesting>
global_values_;
scoped_refptr<FakeBluetoothAdapter> adapter_;
content::WebContents* web_contents_ = nullptr;
std::unique_ptr<TestWebContentsDelegate> test_delegate_;
std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
};
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, DISABLED_WebBluetoothAfterCrash) {
// Make sure we can use Web Bluetooth after the tab crashes.
// Set up adapter with one device.
adapter_->SetIsPresent(false);
std::string result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents_,
"navigator.bluetooth.requestDevice({filters: [{services: [0x180d]}]})"
" .catch(e => domAutomationController.send(e.toString()));",
&result));
EXPECT_EQ("NotFoundError: Bluetooth adapter not available.", result);
// Crash the renderer process.
content::RenderProcessHost* process =
web_contents_->GetMainFrame()->GetProcess();
content::RenderProcessHostWatcher crash_observer(
process, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
process->Shutdown(0);
crash_observer.Wait();
// Reload tab.
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
content::WaitForLoadStop(
browser()->tab_strip_model()->GetActiveWebContents());
// Use Web Bluetooth again.
std::string result_after_crash;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents_,
"navigator.bluetooth.requestDevice({filters: [{services: [0x180d]}]})"
" .catch(e => domAutomationController.send(e.toString()));",
&result_after_crash));
EXPECT_EQ("NotFoundError: Bluetooth adapter not available.",
result_after_crash);
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, KillSwitchShouldBlock) {
// Turn on the global kill switch.
std::map<std::string, std::string> params;
params["Bluetooth"] =
PermissionContextBase::kPermissionsKillSwitchBlockedValue;
variations::AssociateVariationParams(
PermissionContextBase::kPermissionsKillSwitchFieldStudy, "TestGroup",
params);
base::FieldTrialList::CreateFieldTrial(
PermissionContextBase::kPermissionsKillSwitchFieldStudy, "TestGroup");
std::string rejection;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents_,
"navigator.bluetooth.requestDevice({filters: [{name: 'Hello'}]})"
" .then(() => { domAutomationController.send('Success'); },"
" reason => {"
" domAutomationController.send(reason.name + ': ' + reason.message);"
" });",
&rejection));
EXPECT_THAT(rejection,
testing::MatchesRegex("NotFoundError: .*globally disabled.*"));
}
// Tests that using Finch field trial parameters for blocklist additions has
// the effect of rejecting requestDevice calls.
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, BlocklistShouldBlock) {
if (base::FieldTrialList::TrialExists("WebBluetoothBlocklist")) {
LOG(INFO) << "WebBluetoothBlocklist field trial already configured.";
ASSERT_NE(variations::GetVariationParamValue("WebBluetoothBlocklist",
"blocklist_additions")
.find("ed5f25a4"),
std::string::npos)
<< "ERROR: WebBluetoothBlocklist field trial being tested in\n"
"testing/variations/fieldtrial_testing_config_*.json must\n"
"include this test's random UUID 'ed5f25a4' in\n"
"blocklist_additions.\n";
} else {
LOG(INFO) << "Creating WebBluetoothBlocklist field trial for test.";
// Create a field trial with test parameter.
std::map<std::string, std::string> params;
params["blocklist_additions"] = "ed5f25a4:e";
variations::AssociateVariationParams("WebBluetoothBlocklist", "TestGroup",
params);
base::FieldTrialList::CreateFieldTrial("WebBluetoothBlocklist",
"TestGroup");
}
std::string rejection;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents_,
"navigator.bluetooth.requestDevice({filters: [{services: [0xed5f25a4]}]})"
" .then(() => { domAutomationController.send('Success'); },"
" reason => {"
" domAutomationController.send(reason.name + ': ' + reason.message);"
" });",
&rejection));
EXPECT_THAT(rejection,
testing::MatchesRegex("SecurityError: .*blocklisted UUID.*"));
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, NavigateWithChooserCrossOrigin) {
content::TestNavigationObserver observer(
web_contents_, 1 /* number_of_navigations */,
content::MessageLoopRunner::QuitMode::DEFERRED);
EXPECT_TRUE(content::ExecuteScript(
web_contents_,
"navigator.bluetooth.requestDevice({filters: [{name: 'Hello'}]});"
"document.location.href = \"https://google.com\";"));
observer.Wait();
EXPECT_EQ(0u, browser()->GetBubbleManager()->GetBubbleCountForTesting());
EXPECT_EQ(GURL("https://google.com"), web_contents_->GetLastCommittedURL());
}
// The new Web Bluetooth permissions backend is currently implemented behind a
// feature flag.
// TODO(https://crbug.com/589228): Delete this class and convert all the tests
// using it to use WebBluetoothTest instead.
class WebBluetoothTestWithNewPermissionsBackendEnabled
: public WebBluetoothTest {
public:
WebBluetoothTestWithNewPermissionsBackendEnabled() {
feature_list_.InitAndEnableFeature(
features::kWebBluetoothNewPermissionsBackend);
}
// Move-only class
WebBluetoothTestWithNewPermissionsBackendEnabled(
const WebBluetoothTestWithNewPermissionsBackendEnabled&) = delete;
WebBluetoothTestWithNewPermissionsBackendEnabled& operator=(
const WebBluetoothTestWithNewPermissionsBackendEnabled&) = delete;
protected:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
PRE_WebBluetoothPersistentIds) {
auto* delegate = UseAndGetTestWebContentsDelegate();
AddFakeDevice(kDeviceAddress);
delegate->SetDeviceToSelect(kDeviceAddress);
// Grant permission for the device with address |kDeviceAddress| and store its
// WebBluetoothDeviceId in localStorage to retrieve it after the browser
// restarts.
auto request_device_result = content::EvalJs(web_contents_, R"((async() => {
try {
let device = await navigator.bluetooth.requestDevice({
filters: [{name: 'Test Device'}]});
localStorage.setItem('requestDeviceId', device.id);
return device.id;
} catch(e) {
return `${e.name}: ${e.message}`;
}
})())");
const std::string& granted_id = request_device_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(granted_id));
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
WebBluetoothPersistentIds) {
auto* delegate = UseAndGetTestWebContentsDelegate();
AddFakeDevice(kDeviceAddress);
delegate->SetDeviceToSelect(kDeviceAddress);
// At the moment, there is not a way for Web Bluetooth to return a list of the
// previously granted Bluetooth devices, so use requestDevice here.
// TODO(https://crbug.com/577953): Once there is an API that can return the
// permitted Web Bluetooth devices, use that API instead.
auto request_device_result = content::EvalJs(web_contents_, R"((async() => {
try {
let device = await navigator.bluetooth.requestDevice({
filters: [{name: 'Test Device'}]});
return device.id;
} catch(e) {
return `${e.name}: ${e.message}`;
}
})())");
const std::string& granted_id = request_device_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(granted_id));
auto local_storage_get_item_result = content::EvalJs(web_contents_, R"(
(async() => {
return localStorage.getItem('requestDeviceId');
})())");
const std::string& prev_granted_id =
local_storage_get_item_result.ExtractString();
EXPECT_EQ(granted_id, prev_granted_id);
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
PRE_WebBluetoothScanningIdsNotPersistent) {
auto* delegate = UseAndGetTestWebContentsDelegate();
// Grant permission to scan for Bluetooth devices and store the detected
// device's WebBluetoothDeviceId in localStorage to retrieve it after the
// browser restarts.
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var requestLEScanPromise = navigator.bluetooth.requestLEScan({
acceptAllAdvertisements: true});
)"));
delegate->RunPromptEventHandler(
content::BluetoothScanningPrompt::Event::kAllow);
ASSERT_TRUE(content::ExecJs(web_contents_, "requestLEScanPromise"));
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var advertisementreceivedPromise = new Promise(resolve => {
navigator.bluetooth.addEventListener('advertisementreceived',
event => {
localStorage.setItem('requestLEScanId', event.device.id);
resolve(event.device.id);
});
});
)"));
SimulateDeviceAdvertisement(kDeviceAddress);
auto advertisementreceived_promise_result =
content::EvalJs(web_contents_, "advertisementreceivedPromise ");
const std::string& scan_id =
advertisementreceived_promise_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(scan_id));
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
WebBluetoothScanningIdsNotPersistent) {
auto* delegate = UseAndGetTestWebContentsDelegate();
// Grant permission to scan for Bluetooth devices again, and compare the ID
// assigned to the scanned device against the one that was stored previously.
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var requestLEScanPromise = navigator.bluetooth.requestLEScan({
acceptAllAdvertisements: true});
)"));
delegate->RunPromptEventHandler(
content::BluetoothScanningPrompt::Event::kAllow);
ASSERT_TRUE(content::ExecJs(web_contents_, "requestLEScanPromise"));
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var advertisementreceivedPromise = new Promise(resolve => {
navigator.bluetooth.addEventListener('advertisementreceived',
event => {
resolve(event.device.id);
});
});
)"));
SimulateDeviceAdvertisement(kDeviceAddress);
auto advertisementreceived_promise_result =
content::EvalJs(web_contents_, "advertisementreceivedPromise ");
const std::string& scan_id =
advertisementreceived_promise_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(scan_id));
auto local_storage_get_item_result =
content::EvalJs(web_contents_, "localStorage.getItem('requestLEScanId')");
const std::string& prev_scan_id =
local_storage_get_item_result.ExtractString();
EXPECT_NE(scan_id, prev_scan_id);
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
PRE_WebBluetoothIdsUsedInWebBluetoothScanning) {
auto* delegate = UseAndGetTestWebContentsDelegate();
AddFakeDevice(kDeviceAddress);
delegate->SetDeviceToSelect(kDeviceAddress);
// Grant permission for the device with address |kDeviceAddress| and store its
// WebBluetoothDeviceId in localStorage to retrieve it after the browser
// restarts.
auto request_device_result = content::EvalJs(web_contents_, R"((async() => {
try {
let device = await navigator.bluetooth.requestDevice({
filters: [{name: 'Test Device'}]});
localStorage.setItem('requestDeviceId', device.id);
return device.id;
} catch(e) {
return `${e.name}: ${e.message}`;
}
})())");
const std::string& granted_id = request_device_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(granted_id));
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
WebBluetoothIdsUsedInWebBluetoothScanning) {
auto* delegate = UseAndGetTestWebContentsDelegate();
// Grant permission to scan for Bluetooth devices again, and compare the ID
// assigned to the scanned device against the one that was stored previously.
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var requestLEScanPromise = navigator.bluetooth.requestLEScan({
acceptAllAdvertisements: true});
)"));
delegate->RunPromptEventHandler(
content::BluetoothScanningPrompt::Event::kAllow);
ASSERT_TRUE(content::ExecJs(web_contents_, "requestLEScanPromise"));
ASSERT_TRUE(content::ExecJs(web_contents_, R"(
var advertisementreceivedPromise = new Promise(resolve => {
navigator.bluetooth.addEventListener('advertisementreceived',
event => {
resolve(event.device.id);
});
});
)"));
SimulateDeviceAdvertisement(kDeviceAddress);
auto advertisementreceived_promise_result =
content::EvalJs(web_contents_, "advertisementreceivedPromise ");
const std::string& scan_id =
advertisementreceived_promise_result.ExtractString();
EXPECT_TRUE(blink::WebBluetoothDeviceId::IsValid(scan_id));
auto local_storage_get_item_result =
content::EvalJs(web_contents_, "localStorage.getItem('requestDeviceId')");
const std::string& granted_id = local_storage_get_item_result.ExtractString();
EXPECT_EQ(scan_id, granted_id);
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
PRE_WebBluetoothPersistentServices) {
auto* delegate = UseAndGetTestWebContentsDelegate();
AddFakeDevice(kDeviceAddress);
delegate->SetDeviceToSelect(kDeviceAddress);
// Grant permission for the device with address |kDeviceAddress| and store its
// WebBluetoothDeviceId in localStorage to retrieve it after the browser
// restarts.
EXPECT_EQ(kHeartRateUUIDString,
content::EvalJs(web_contents_, R"((async() => {
try {
let device = await navigator.bluetooth.requestDevice({
filters: [{name: 'Test Device', services: ['heart_rate']}]});
let gatt = await device.gatt.connect();
let service = await gatt.getPrimaryService('heart_rate');
return service.uuid;
} catch(e) {
return `${e.name}: ${e.message}`;
}
})())"));
}
IN_PROC_BROWSER_TEST_F(WebBluetoothTestWithNewPermissionsBackendEnabled,
WebBluetoothPersistentServices) {
auto* delegate = UseAndGetTestWebContentsDelegate();
AddFakeDevice(kDeviceAddress);
delegate->SetDeviceToSelect(kDeviceAddress);
// At the moment, there is not a way for Web Bluetooth to return a list of the
// previously granted Bluetooth devices, so use requestDevice here without
// specifying a filter for services. The site should still be able to GATT
// connect and get the primary 'heart_rate' GATT service.
// TODO(https://crbug.com/577953): Once there is an API that can return the
// permitted Web Bluetooth devices, use that API instead.
EXPECT_EQ(kHeartRateUUIDString,
content::EvalJs(web_contents_, R"((async() => {
try {
let device = await navigator.bluetooth.requestDevice({
filters: [{name: 'Test Device'}]});
let gatt = await device.gatt.connect();
let service = await gatt.getPrimaryService('heart_rate');
return service.uuid;
} catch(e) {
return `${e.name}: ${e.message}`;
}
})())"));
}
} // namespace