0

Bluetooth: Migrate some WPT tests to use Bidi SimulateAdapter

Migrate some Web Bluetooth WPT tests to use WebDriver Bidi
SimulateAdapter.

HeadlessBluetoothDelegate is created in order to use navigator.bluetooth.getAvailability.

Change-Id: I9ee03ec00a67e95c8c0a485fa9f6a247e1f0ff59
Bug: 41484719
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6165347
Commit-Queue: Jack Hsieh <chengweih@chromium.org>
Reviewed-by: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1416319}
This commit is contained in:
Jack Hsieh
2025-02-05 10:58:28 -08:00
committed by Chromium LUCI CQ
parent 586087b975
commit 8cdaf24335
12 changed files with 263 additions and 12 deletions

@ -239,6 +239,8 @@ component("headless_non_renderer") {
"lib/browser/command_line_handler.h",
"lib/browser/directory_enumerator.cc",
"lib/browser/directory_enumerator.h",
"lib/browser/headless_bluetooth_delegate.cc",
"lib/browser/headless_bluetooth_delegate.h",
"lib/browser/headless_browser_context_impl.cc",
"lib/browser/headless_browser_context_impl.h",
"lib/browser/headless_browser_context_options.cc",

@ -12,3 +12,4 @@ dvallet@chromium.org
per-file *.cmx=set noparent
per-file *.cmx=file://build/fuchsia/SECURITY_OWNERS
per-file headless_bluetooth_*=file://content/browser/bluetooth/OWNERS

@ -15,6 +15,7 @@ include_rules = [
"+third_party/skia/include",
"+third_party/blink/public/mojom/quota",
"+third_party/blink/public/mojom/badging",
"+third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h",
"+third_party/blink/public/common/client_hints/enabled_client_hints.h",
"+third_party/blink/public/common/renderer_preferences/renderer_preferences.h",
"+third_party/blink/public/common/user_agent/user_agent_metadata.h",

@ -0,0 +1,120 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "headless/lib/browser/headless_bluetooth_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
namespace headless {
using ::blink::WebBluetoothDeviceId;
using ::content::BluetoothChooser;
using ::content::BluetoothScanningPrompt;
using ::content::RenderFrameHost;
using ::device::BluetoothDevice;
using ::device::BluetoothUUID;
HeadlessBluetoothDelegate::HeadlessBluetoothDelegate() = default;
HeadlessBluetoothDelegate::~HeadlessBluetoothDelegate() = default;
std::unique_ptr<BluetoothChooser>
HeadlessBluetoothDelegate::RunBluetoothChooser(
RenderFrameHost* frame,
const BluetoothChooser::EventHandler& event_handler) {
return std::make_unique<BluetoothChooser>();
}
std::unique_ptr<BluetoothScanningPrompt>
HeadlessBluetoothDelegate::ShowBluetoothScanningPrompt(
RenderFrameHost* frame,
const BluetoothScanningPrompt::EventHandler& event_handler) {
return nullptr;
}
void HeadlessBluetoothDelegate::ShowDevicePairPrompt(
RenderFrameHost* frame,
const std::u16string& device_identifier,
PairPromptCallback callback,
PairingKind pairing_kind,
const std::optional<std::u16string>& pin) {}
WebBluetoothDeviceId HeadlessBluetoothDelegate::GetWebBluetoothDeviceId(
RenderFrameHost* frame,
const std::string& device_address) {
return {};
}
std::string HeadlessBluetoothDelegate::GetDeviceAddress(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id) {
return std::string();
}
WebBluetoothDeviceId HeadlessBluetoothDelegate::AddScannedDevice(
RenderFrameHost* frame,
const std::string& device_address) {
return WebBluetoothDeviceId();
}
WebBluetoothDeviceId HeadlessBluetoothDelegate::GrantServiceAccessPermission(
RenderFrameHost* frame,
const BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
return WebBluetoothDeviceId();
}
bool HeadlessBluetoothDelegate::HasDevicePermission(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id) {
return false;
}
void HeadlessBluetoothDelegate::RevokeDevicePermissionWebInitiated(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id) {}
bool HeadlessBluetoothDelegate::MayUseBluetooth(RenderFrameHost* rfh) {
// Disable any other non-default StoragePartition contexts, unless it has a
// non-http/https scheme.
if (rfh->GetStoragePartition() !=
rfh->GetBrowserContext()->GetDefaultStoragePartition()) {
return !rfh->GetLastCommittedURL().SchemeIsHTTPOrHTTPS();
}
return true;
}
bool HeadlessBluetoothDelegate::IsAllowedToAccessService(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id,
const BluetoothUUID& service) {
return false;
}
bool HeadlessBluetoothDelegate::IsAllowedToAccessAtLeastOneService(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id) {
return false;
}
bool HeadlessBluetoothDelegate::IsAllowedToAccessManufacturerData(
RenderFrameHost* frame,
const WebBluetoothDeviceId& device_id,
const uint16_t manufacturer_code) {
return false;
}
void HeadlessBluetoothDelegate::AddFramePermissionObserver(
FramePermissionObserver* observer) {}
void HeadlessBluetoothDelegate::RemoveFramePermissionObserver(
FramePermissionObserver* observer) {}
std::vector<blink::mojom::WebBluetoothDevicePtr>
HeadlessBluetoothDelegate::GetPermittedDevices(RenderFrameHost* frame) {
return {};
}
} // namespace headless

@ -0,0 +1,92 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef HEADLESS_LIB_BROWSER_HEADLESS_BLUETOOTH_DELEGATE_H_
#define HEADLESS_LIB_BROWSER_HEADLESS_BLUETOOTH_DELEGATE_H_
#include <string>
#include <vector>
#include "content/public/browser/bluetooth_delegate.h"
namespace blink {
class WebBluetoothDeviceId;
} // namespace blink
namespace content {
class BluetoothChooser;
class BluetoothScanningPrompt;
} // namespace content
namespace device {
class BluetoothDevice;
class BluetoothUUID;
class RenderFrameHost;
} // namespace device
namespace headless {
// A thin layer of BluetoothDelegate for Headless shell that provides a basic
// chooser and rejects any permission of accessing a bluetooth device.
class HeadlessBluetoothDelegate : public content::BluetoothDelegate {
public:
HeadlessBluetoothDelegate();
// Not copyable or movable.
HeadlessBluetoothDelegate(const HeadlessBluetoothDelegate&) = delete;
HeadlessBluetoothDelegate& operator=(const HeadlessBluetoothDelegate&) =
delete;
~HeadlessBluetoothDelegate() override;
// BluetoothDelegate implementation:
std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
content::RenderFrameHost* frame,
const content::BluetoothChooser::EventHandler& event_handler) override;
std::unique_ptr<content::BluetoothScanningPrompt> ShowBluetoothScanningPrompt(
content::RenderFrameHost* frame,
const content::BluetoothScanningPrompt::EventHandler& event_handler)
override;
void ShowDevicePairPrompt(content::RenderFrameHost* frame,
const std::u16string& device_identifier,
PairPromptCallback callback,
PairingKind pairing_kind,
const std::optional<std::u16string>& pin) override;
blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
content::RenderFrameHost* frame,
const std::string& device_address) override;
std::string GetDeviceAddress(content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId&) override;
blink::WebBluetoothDeviceId AddScannedDevice(
content::RenderFrameHost* frame,
const std::string& device_address) override;
blink::WebBluetoothDeviceId GrantServiceAccessPermission(
content::RenderFrameHost* frame,
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptions* options) override;
bool HasDevicePermission(
content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override;
void RevokeDevicePermissionWebInitiated(
content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override;
bool MayUseBluetooth(content::RenderFrameHost* rfh) override;
bool IsAllowedToAccessService(content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id,
const device::BluetoothUUID& service) override;
bool IsAllowedToAccessAtLeastOneService(
content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id) override;
bool IsAllowedToAccessManufacturerData(
content::RenderFrameHost* frame,
const blink::WebBluetoothDeviceId& device_id,
const uint16_t manufacturer_code) override;
std::vector<blink::mojom::WebBluetoothDevicePtr> GetPermittedDevices(
content::RenderFrameHost* frame) override;
void AddFramePermissionObserver(FramePermissionObserver* observer) override;
void RemoveFramePermissionObserver(
FramePermissionObserver* observer) override;
};
} // namespace headless
#endif // HEADLESS_LIB_BROWSER_HEADLESS_BLUETOOTH_DELEGATE_H_

@ -4,6 +4,7 @@
#include "headless/lib/browser/headless_content_browser_client.h"
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
@ -31,6 +32,7 @@
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "headless/lib/browser/headless_bluetooth_delegate.h"
#include "headless/lib/browser/headless_browser_context_impl.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_browser_main_parts.h"
@ -531,4 +533,12 @@ void HeadlessContentBrowserClient::SetEncryptionKey(
#endif
}
content::BluetoothDelegate*
HeadlessContentBrowserClient::GetBluetoothDelegate() {
if (!bluetooth_delegate_) {
bluetooth_delegate_ = std::make_unique<HeadlessBluetoothDelegate>();
}
return bluetooth_delegate_.get();
}
} // namespace headless

@ -18,6 +18,7 @@
namespace headless {
class HeadlessBluetoothDelegate;
class HeadlessBrowserImpl;
class HeadlessContentBrowserClient : public content::ContentBrowserClient {
@ -148,6 +149,8 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient {
bool ShouldSandboxNetworkService() override;
content::BluetoothDelegate* GetBluetoothDelegate() override;
private:
class StubBadgeService;
@ -162,6 +165,8 @@ class HeadlessContentBrowserClient : public content::ContentBrowserClient {
raw_ptr<HeadlessBrowserImpl> browser_; // Not owned.
std::unique_ptr<StubBadgeService> stub_badge_service_;
std::unique_ptr<HeadlessBluetoothDelegate> bluetooth_delegate_;
};
} // namespace headless

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -6,8 +6,8 @@
const test_desc = 'getAvailability() resolves with false if the system does ' +
'not have an adapter.';
bluetooth_test(async () => {
await navigator.bluetooth.test.simulateCentral({state: 'absent'});
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "absent"});
let availability = await navigator.bluetooth.getAvailability();
assert_false(
availability,

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -6,8 +6,8 @@
const test_desc = 'getAvailability() resolves with true if the Bluetooth ' +
'radio is powered off, but the platform that supports Bluetooth LE.';
bluetooth_test(async () => {
await navigator.bluetooth.test.simulateCentral({state: 'powered-off'});
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-off"});
let availability = await navigator.bluetooth.getAvailability();
assert_true(
availability,

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -6,8 +6,8 @@
const test_desc = 'getAvailability() resolves with true if the Bluetooth ' +
'radio is powered on and the platform supports Bluetooth LE.';
bluetooth_test(async () => {
await navigator.bluetooth.test.simulateCentral({state: 'powered-on'});
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-on"});
let availability = await navigator.bluetooth.getAvailability();
assert_true(
availability,

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -9,8 +9,8 @@ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
'/bluetooth/resources/health-thermometer-iframe.html'
let iframe = document.createElement('iframe');
bluetooth_test(async () => {
await navigator.bluetooth.test.simulateCentral({state: 'powered-on'});
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-on"});
await new Promise(resolve => {
iframe.src = cross_origin_src;
document.body.appendChild(iframe);

@ -89,6 +89,26 @@ function bluetooth_test(
}, name, properties);
}
/**
* These tests rely on the User Agent providing an implementation of the
* WebDriver-Bidi for testing Web Bluetooth
* https://webbluetoothcg.github.io/web-bluetooth/#automated-testing
* @param {function{*}: Promise<*>} test_function The Web Bluetooth test to run.
* @param {string} name The name or description of the test.
* @param {object} properties An object containing extra options for the test.
* @param {Boolean} validate_response_consumed Whether to validate all response
* consumed or not.
* @returns {Promise<void>} Resolves if Web Bluetooth test ran successfully, or
* rejects if the test failed.
*/
function bluetooth_bidi_test(
test_function, name, properties, validate_response_consumed = true) {
return promise_test(async (t) => {
assert_implements(navigator.bluetooth, 'missing navigator.bluetooth');
await test_function(t);
}, name, properties);
}
/**
* Test Helpers
*/