Make WebBluetooth an optional feature. (#35479)

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-02-17 23:13:43 -05:00 committed by GitHub
parent 32f19c1eae
commit 1d606bb85c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 124 additions and 37 deletions

View file

@ -0,0 +1,788 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use bluetooth_traits::{BluetoothError, BluetoothRequest, GATTType};
use bluetooth_traits::{BluetoothResponse, BluetoothResponseResult};
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence};
use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence};
use crate::realms::{AlreadyInRealm, InRealm};
use crate::conversions::Convert;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothDataFilterInit;
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothLEScanFilterInit;
use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionDescriptor;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServer_Binding::
BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState};
use crate::dom::bindings::codegen::UnionTypes::{ArrayBufferViewOrArrayBuffer, StringOrUnsignedLong};
use crate::dom::bindings::error::Error::{self, Network, Security, Type};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bluetoothdevice::BluetoothDevice;
use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult;
use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::permissions::{descriptor_permission_state, PermissionAlgorithm};
use crate::dom::promise::Promise;
use crate::script_runtime::{CanGc, JSContext};
use crate::task::TaskOnce;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use js::conversions::ConversionResult;
use js::jsapi::JSObject;
use js::jsval::{ObjectValue, UndefinedValue};
use profile_traits::ipc as ProfiledIpc;
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
const KEY_CONVERSION_ERROR: &str =
"This `manufacturerData` key can not be parsed as unsigned short:";
const FILTER_EMPTY_ERROR: &str =
"'filters' member, if present, must be nonempty to find any devices.";
const FILTER_ERROR: &str = "A filter must restrict the devices in some way.";
const MANUFACTURER_DATA_ERROR: &str =
"'manufacturerData', if present, must be non-empty to filter devices.";
const MASK_LENGTH_ERROR: &str = "`mask`, if present, must have the same length as `dataPrefix`.";
// 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
const MAX_DEVICE_NAME_LENGTH: usize = 248;
const NAME_PREFIX_ERROR: &str = "'namePrefix', if present, must be nonempty.";
const NAME_TOO_LONG_ERROR: &str = "A device name can't be longer than 248 bytes.";
const SERVICE_DATA_ERROR: &str = "'serviceData', if present, must be non-empty to filter devices.";
const SERVICE_ERROR: &str = "'services', if present, must contain at least one service.";
const OPTIONS_ERROR: &str = "Fields of 'options' conflict with each other.
Either 'acceptAllDevices' member must be true, or 'filters' member must be set to a value.";
const BT_DESC_CONVERSION_ERROR: &str =
"Can't convert to an IDL value of type BluetoothPermissionDescriptor";
#[derive(JSTraceable, MallocSizeOf)]
#[allow(non_snake_case)]
pub(crate) struct AllowedBluetoothDevice {
pub(crate) deviceId: DOMString,
pub(crate) mayUseGATT: bool,
}
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct BluetoothExtraPermissionData {
allowed_devices: DomRefCell<Vec<AllowedBluetoothDevice>>,
}
impl BluetoothExtraPermissionData {
pub(crate) fn new() -> BluetoothExtraPermissionData {
BluetoothExtraPermissionData {
allowed_devices: DomRefCell::new(Vec::new()),
}
}
pub(crate) fn add_new_allowed_device(&self, allowed_device: AllowedBluetoothDevice) {
self.allowed_devices.borrow_mut().push(allowed_device);
}
fn get_allowed_devices(&self) -> Ref<Vec<AllowedBluetoothDevice>> {
self.allowed_devices.borrow()
}
pub(crate) fn allowed_devices_contains_id(&self, id: DOMString) -> bool {
self.allowed_devices
.borrow()
.iter()
.any(|d| d.deviceId == id)
}
}
impl Default for BluetoothExtraPermissionData {
fn default() -> Self {
Self::new()
}
}
struct BluetoothContext<T: AsyncBluetoothListener + DomObject> {
promise: Option<TrustedPromise>,
receiver: Trusted<T>,
}
pub(crate) trait AsyncBluetoothListener {
fn handle_response(&self, result: BluetoothResponse, promise: &Rc<Promise>, can_gc: CanGc);
}
impl<T> BluetoothContext<T>
where
T: AsyncBluetoothListener + DomObject,
{
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn response(&mut self, response: BluetoothResponseResult, can_gc: CanGc) {
let promise = self.promise.take().expect("bt promise is missing").root();
// JSAutoRealm needs to be manually made.
// Otherwise, Servo will crash.
match response {
Ok(response) => self
.receiver
.root()
.handle_response(response, &promise, can_gc),
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
// Step 3 - 4.
Err(error) => promise.reject_error(error.convert()),
}
}
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
#[dom_struct]
pub(crate) struct Bluetooth {
eventtarget: EventTarget,
device_instance_map: DomRefCell<HashMap<String, Dom<BluetoothDevice>>>,
}
impl Bluetooth {
pub(crate) fn new_inherited() -> Bluetooth {
Bluetooth {
eventtarget: EventTarget::new_inherited(),
device_instance_map: DomRefCell::new(HashMap::new()),
}
}
pub(crate) fn new(global: &GlobalScope) -> DomRoot<Bluetooth> {
reflect_dom_object(Box::new(Bluetooth::new_inherited()), global, CanGc::note())
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
pub(crate) fn get_device_map(&self) -> &DomRefCell<HashMap<String, Dom<BluetoothDevice>>> {
&self.device_instance_map
}
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
fn request_bluetooth_devices(
&self,
p: &Rc<Promise>,
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
optional_services: &[BluetoothServiceUUID],
sender: IpcSender<BluetoothResponseResult>,
) {
// TODO: Step 1: Triggered by user activation.
// Step 2.2: There are no requiredServiceUUIDS, we scan for all devices.
let mut uuid_filters = vec![];
if let Some(filters) = filters {
// Step 2.1.
if filters.is_empty() {
p.reject_error(Type(FILTER_EMPTY_ERROR.to_owned()));
return;
}
// Step 2.3: There are no requiredServiceUUIDS, we scan for all devices.
// Step 2.4.
for filter in filters {
// Step 2.4.1.
match canonicalize_filter(filter) {
// Step 2.4.2.
Ok(f) => uuid_filters.push(f),
Err(e) => {
p.reject_error(e);
return;
},
}
// Step 2.4.3: There are no requiredServiceUUIDS, we scan for all devices.
}
}
let mut optional_services_uuids = vec![];
for opt_service in optional_services {
// Step 2.5 - 2.6.
let uuid = match BluetoothUUID::service(opt_service.clone()) {
Ok(u) => u.to_string(),
Err(e) => {
p.reject_error(e);
return;
},
};
// Step 2.7.
// Note: What we are doing here, is adding the not blocklisted UUIDs to the result vector,
// instead of removing them from an already filled vector.
if !uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
optional_services_uuids.push(uuid);
}
}
let option = RequestDeviceoptions::new(
self.global().as_window().webview_id(),
BluetoothScanfilterSequence::new(uuid_filters),
ServiceUUIDSequence::new(optional_services_uuids),
);
// Step 4 - 5.
if let PermissionState::Denied =
descriptor_permission_state(PermissionName::Bluetooth, None)
{
return p.reject_error(Error::NotFound);
}
// Note: Step 3, 6 - 8 are implemented in
// components/net/bluetooth_thread.rs in request_device function.
self.get_bluetooth_thread()
.send(BluetoothRequest::RequestDevice(option, sender))
.unwrap();
}
}
pub(crate) fn response_async<T: AsyncBluetoothListener + DomObject + 'static>(
promise: &Rc<Promise>,
receiver: &T,
) -> IpcSender<BluetoothResponseResult> {
let (action_sender, action_receiver) = ipc::channel().unwrap();
let task_source = receiver
.global()
.task_manager()
.networking_task_source()
.to_sendable();
let context = Arc::new(Mutex::new(BluetoothContext {
promise: Some(TrustedPromise::new(promise.clone())),
receiver: Trusted::new(receiver),
}));
ROUTER.add_typed_route(
action_receiver,
Box::new(move |message| {
struct ListenerTask<T: AsyncBluetoothListener + DomObject> {
context: Arc<Mutex<BluetoothContext<T>>>,
action: BluetoothResponseResult,
}
impl<T> TaskOnce for ListenerTask<T>
where
T: AsyncBluetoothListener + DomObject,
{
fn run_once(self) {
let mut context = self.context.lock().unwrap();
context.response(self.action, CanGc::note());
}
}
let task = ListenerTask {
context: context.clone(),
action: message.unwrap(),
};
task_source.queue_unconditionally(task);
}),
);
action_sender
}
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
#[allow(clippy::too_many_arguments)]
pub(crate) fn get_gatt_children<T, F>(
attribute: &T,
single: bool,
uuid_canonicalizer: F,
uuid: Option<StringOrUnsignedLong>,
instance_id: String,
connected: bool,
child_type: GATTType,
can_gc: CanGc,
) -> Rc<Promise>
where
T: AsyncBluetoothListener + DomObject + 'static,
F: FnOnce(StringOrUnsignedLong) -> Fallible<UUID>,
{
let in_realm_proof = AlreadyInRealm::assert();
let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
let result_uuid = if let Some(u) = uuid {
// Step 1.
let canonicalized = match uuid_canonicalizer(u) {
Ok(canonicalized_uuid) => canonicalized_uuid.to_string(),
Err(e) => {
p.reject_error(e);
return p;
},
};
// Step 2.
if uuid_is_blocklisted(canonicalized.as_ref(), Blocklist::All) {
p.reject_error(Security);
return p;
}
Some(canonicalized)
} else {
None
};
// Step 3 - 4.
if !connected {
p.reject_error(Network);
return p;
}
// TODO: Step 5: Implement representedDevice internal slot for BluetoothDevice.
// Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_descriptor function
// and in handle_response function.
let sender = response_async(&p, attribute);
attribute
.global()
.as_window()
.bluetooth_thread()
.send(BluetoothRequest::GetGATTChildren(
instance_id,
result_uuid,
single,
child_type,
sender,
))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothlescanfilterinit-canonicalizing
fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<BluetoothScanfilter> {
// Step 1.
if filter.services.is_none() &&
filter.name.is_none() &&
filter.namePrefix.is_none() &&
filter.manufacturerData.is_none() &&
filter.serviceData.is_none()
{
return Err(Type(FILTER_ERROR.to_owned()));
}
// Step 2: There is no empty canonicalizedFilter member,
// we create a BluetoothScanfilter instance at the end of the function.
// Step 3.
let services_vec = match filter.services {
Some(ref services) => {
// Step 3.1.
if services.is_empty() {
return Err(Type(SERVICE_ERROR.to_owned()));
}
let mut services_vec = vec![];
for service in services {
// Step 3.2 - 3.3.
let uuid = BluetoothUUID::service(service.clone())?.to_string();
// Step 3.4.
if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) {
return Err(Security);
}
services_vec.push(uuid);
}
// Step 3.5.
services_vec
},
None => vec![],
};
// Step 4.
let name = match filter.name {
Some(ref name) => {
// Step 4.1.
// Note: DOMString::len() gives back the size in bytes.
if name.len() > MAX_DEVICE_NAME_LENGTH {
return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
}
// Step 4.2.
Some(name.to_string())
},
None => None,
};
// Step 5.
let name_prefix = match filter.namePrefix {
Some(ref name_prefix) => {
// Step 5.1.
if name_prefix.is_empty() {
return Err(Type(NAME_PREFIX_ERROR.to_owned()));
}
if name_prefix.len() > MAX_DEVICE_NAME_LENGTH {
return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
}
// Step 5.2.
name_prefix.to_string()
},
None => String::new(),
};
// Step 6 - 7.
let manufacturer_data = match filter.manufacturerData {
Some(ref manufacturer_data_map) => {
// Note: If manufacturer_data_map is empty, that means there are no key values in it.
if manufacturer_data_map.is_empty() {
return Err(Type(MANUFACTURER_DATA_ERROR.to_owned()));
}
let mut map = HashMap::new();
for (key, bdfi) in manufacturer_data_map.iter() {
// Step 7.1 - 7.2.
let manufacturer_id = match u16::from_str(key.as_ref()) {
Ok(id) => id,
Err(err) => {
return Err(Type(format!("{} {} {}", KEY_CONVERSION_ERROR, key, err)));
},
};
// Step 7.3: No need to convert to IDL values since this is only used by native code.
// Step 7.4 - 7.5.
map.insert(
manufacturer_id,
canonicalize_bluetooth_data_filter_init(bdfi)?,
);
}
Some(map)
},
None => None,
};
// Step 8 - 9.
let service_data = match filter.serviceData {
Some(ref service_data_map) => {
// Note: If service_data_map is empty, that means there are no key values in it.
if service_data_map.is_empty() {
return Err(Type(SERVICE_DATA_ERROR.to_owned()));
}
let mut map = HashMap::new();
for (key, bdfi) in service_data_map.iter() {
let service_name = match u32::from_str(key.as_ref()) {
// Step 9.1.
Ok(number) => StringOrUnsignedLong::UnsignedLong(number),
// Step 9.2.
_ => StringOrUnsignedLong::String(key.clone()),
};
// Step 9.3 - 9.4.
let service = BluetoothUUID::service(service_name)?.to_string();
// Step 9.5.
if uuid_is_blocklisted(service.as_ref(), Blocklist::All) {
return Err(Security);
}
// Step 9.6: No need to convert to IDL values since this is only used by native code.
// Step 9.7 - 9.8.
map.insert(service, canonicalize_bluetooth_data_filter_init(bdfi)?);
}
Some(map)
},
None => None,
};
// Step 10.
Ok(BluetoothScanfilter::new(
name,
name_prefix,
services_vec,
manufacturer_data,
service_data,
))
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdatafilterinit-canonicalizing
fn canonicalize_bluetooth_data_filter_init(
bdfi: &BluetoothDataFilterInit,
) -> Fallible<(Vec<u8>, Vec<u8>)> {
// Step 1.
let data_prefix = match bdfi.dataPrefix {
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(),
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(),
None => vec![],
};
// Step 2.
// If no mask present, mask will be a sequence of 0xFF bytes the same length as dataPrefix.
// Masking dataPrefix with this, leaves dataPrefix untouched.
let mask = match bdfi.mask {
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(),
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(),
None => vec![0xFF; data_prefix.len()],
};
// Step 3.
if mask.len() != data_prefix.len() {
return Err(Type(MASK_LENGTH_ERROR.to_owned()));
}
// Step 4.
Ok((data_prefix, mask))
}
impl Convert<Error> for BluetoothError {
fn convert(self) -> Error {
match self {
BluetoothError::Type(message) => Error::Type(message),
BluetoothError::Network => Error::Network,
BluetoothError::NotFound => Error::NotFound,
BluetoothError::NotSupported => Error::NotSupported,
BluetoothError::Security => Error::Security,
BluetoothError::InvalidState => Error::InvalidState,
}
}
}
impl BluetoothMethods<crate::DomTypeHolder> for Bluetooth {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
fn RequestDevice(
&self,
option: &RequestDeviceOptions,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if (option.filters.is_some() && option.acceptAllDevices) ||
(option.filters.is_none() && !option.acceptAllDevices)
{
p.reject_error(Error::Type(OPTIONS_ERROR.to_owned()));
return p;
}
// Step 2.
let sender = response_async(&p, self);
self.request_bluetooth_devices(&p, &option.filters, &option.optionalServices, sender);
//Note: Step 3 - 4. in response function, Step 5. in handle_response function.
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability
fn GetAvailability(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1. We did not override the method
// Step 2 - 3. in handle_response
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::GetAvailability(sender))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-onavailabilitychanged
event_handler!(
availabilitychanged,
GetOnavailabilitychanged,
SetOnavailabilitychanged
);
}
impl AsyncBluetoothListener for Bluetooth {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
// Step 11, 13 - 14.
BluetoothResponse::RequestDevice(device) => {
let mut device_instance_map = self.device_instance_map.borrow_mut();
if let Some(existing_device) = device_instance_map.get(&device.id.clone()) {
return promise.resolve_native(&**existing_device);
}
let bt_device = BluetoothDevice::new(
&self.global(),
DOMString::from(device.id.clone()),
device.name.map(DOMString::from),
self,
);
device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device));
self.global()
.as_window()
.bluetooth_extra_permission_data()
.add_new_allowed_device(AllowedBluetoothDevice {
deviceId: DOMString::from(device.id),
mayUseGATT: true,
});
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
// Step 5.
promise.resolve_native(&bt_device);
},
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability
// Step 2 - 3.
BluetoothResponse::GetAvailability(is_available) => {
promise.resolve_native(&is_available);
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}
impl PermissionAlgorithm for Bluetooth {
type Descriptor = BluetoothPermissionDescriptor;
type Status = BluetoothPermissionResult;
fn create_descriptor(
cx: JSContext,
permission_descriptor_obj: *mut JSObject,
) -> Result<BluetoothPermissionDescriptor, Error> {
rooted!(in(*cx) let mut property = UndefinedValue());
property
.handle_mut()
.set(ObjectValue(permission_descriptor_obj));
match BluetoothPermissionDescriptor::new(cx, property.handle()) {
Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
Err(_) => Err(Error::Type(String::from(BT_DESC_CONVERSION_ERROR))),
}
}
// https://webbluetoothcg.github.io/web-bluetooth/#query-the-bluetooth-permission
fn permission_query(
_cx: JSContext,
promise: &Rc<Promise>,
descriptor: &BluetoothPermissionDescriptor,
status: &BluetoothPermissionResult,
) {
// Step 1: We are not using the `global` variable.
// Step 2.
status.set_state(descriptor_permission_state(status.get_query(), None));
// Step 3.
if let PermissionState::Denied = status.get_state() {
status.set_devices(Vec::new());
return promise.resolve_native(status);
}
// Step 4.
rooted_vec!(let mut matching_devices);
// Step 5.
let global = status.global();
let allowed_devices = global
.as_window()
.bluetooth_extra_permission_data()
.get_allowed_devices();
let bluetooth = status.get_bluetooth();
let device_map = bluetooth.get_device_map().borrow();
// Step 6.
for allowed_device in allowed_devices.iter() {
// Step 6.1.
if let Some(ref id) = descriptor.deviceId {
if &allowed_device.deviceId != id {
continue;
}
}
let device_id = String::from(allowed_device.deviceId.as_ref());
// Step 6.2.
if let Some(ref filters) = descriptor.filters {
let mut scan_filters: Vec<BluetoothScanfilter> = Vec::new();
// Step 6.2.1.
for filter in filters {
match canonicalize_filter(filter) {
Ok(f) => scan_filters.push(f),
Err(error) => return promise.reject_error(error),
}
}
// Step 6.2.2.
// Instead of creating an internal slot we send an ipc message to the Bluetooth thread
// to check if one of the filters matches.
let (sender, receiver) =
ProfiledIpc::channel(global.time_profiler_chan().clone()).unwrap();
status
.get_bluetooth_thread()
.send(BluetoothRequest::MatchesFilter(
device_id.clone(),
BluetoothScanfilterSequence::new(scan_filters),
sender,
))
.unwrap();
match receiver.recv().unwrap() {
Ok(true) => (),
Ok(false) => continue,
Err(error) => return promise.reject_error(error.convert()),
};
}
// Step 6.3.
// TODO: Implement this correctly, not just using device ids here.
// https://webbluetoothcg.github.io/web-bluetooth/#get-the-bluetoothdevice-representing
if let Some(device) = device_map.get(&device_id) {
matching_devices.push(Dom::from_ref(&**device));
}
}
// Step 7.
status.set_devices(matching_devices.drain(..).collect());
// https://w3c.github.io/permissions/#dom-permissions-query
// Step 7.
promise.resolve_native(status);
}
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
fn permission_request(
_cx: JSContext,
promise: &Rc<Promise>,
descriptor: &BluetoothPermissionDescriptor,
status: &BluetoothPermissionResult,
) {
// Step 1.
if descriptor.filters.is_some() == descriptor.acceptAllDevices {
return promise.reject_error(Error::Type(OPTIONS_ERROR.to_owned()));
}
// Step 2.
let sender = response_async(promise, status);
let bluetooth = status.get_bluetooth();
bluetooth.request_bluetooth_devices(
promise,
&descriptor.filters,
&descriptor.optionalServices,
sender,
);
// NOTE: Step 3. is in BluetoothPermissionResult's `handle_response` function.
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
// https://webbluetoothcg.github.io/web-bluetooth/#revoke-bluetooth-access
fn permission_revoke(
_descriptor: &BluetoothPermissionDescriptor,
status: &BluetoothPermissionResult,
can_gc: CanGc,
) {
// Step 1.
let global = status.global();
let allowed_devices = global
.as_window()
.bluetooth_extra_permission_data()
.get_allowed_devices();
// Step 2.
let bluetooth = status.get_bluetooth();
let device_map = bluetooth.get_device_map().borrow();
for (id, device) in device_map.iter() {
let id = DOMString::from(id.clone());
// Step 2.1.
if allowed_devices.iter().any(|d| d.deviceId == id) &&
!device.is_represented_device_null()
{
// Note: We don't need to update the allowed_services,
// because we store it in the lower level
// where it is already up-to-date
continue;
}
// Step 2.2 - 2.4
let _ = device.get_gatt().Disconnect(can_gc);
}
}
}

View file

@ -0,0 +1,144 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::{
BluetoothAdvertisingEventInit, BluetoothAdvertisingEventMethods,
};
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bluetoothdevice::BluetoothDevice;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothadvertisingevent
#[dom_struct]
pub(crate) struct BluetoothAdvertisingEvent {
event: Event,
device: Dom<BluetoothDevice>,
name: Option<DOMString>,
appearance: Option<u16>,
tx_power: Option<i8>,
rssi: Option<i8>,
}
#[allow(non_snake_case)]
impl BluetoothAdvertisingEvent {
pub(crate) fn new_inherited(
device: &BluetoothDevice,
name: Option<DOMString>,
appearance: Option<u16>,
tx_power: Option<i8>,
rssi: Option<i8>,
) -> BluetoothAdvertisingEvent {
BluetoothAdvertisingEvent {
event: Event::new_inherited(),
device: Dom::from_ref(device),
name,
appearance,
tx_power,
rssi,
}
}
#[allow(clippy::too_many_arguments)]
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
device: &BluetoothDevice,
name: Option<DOMString>,
appearance: Option<u16>,
txPower: Option<i8>,
rssi: Option<i8>,
can_gc: CanGc,
) -> DomRoot<BluetoothAdvertisingEvent> {
let ev = reflect_dom_object_with_proto(
Box::new(BluetoothAdvertisingEvent::new_inherited(
device, name, appearance, txPower, rssi,
)),
global,
proto,
can_gc,
);
{
let event = ev.upcast::<Event>();
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
}
ev
}
}
impl BluetoothAdvertisingEventMethods<crate::DomTypeHolder> for BluetoothAdvertisingEvent {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-bluetoothadvertisingevent
#[allow(non_snake_case)]
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &BluetoothAdvertisingEventInit,
) -> Fallible<DomRoot<BluetoothAdvertisingEvent>> {
let name = init.name.clone();
let appearance = init.appearance;
let txPower = init.txPower;
let rssi = init.rssi;
let bubbles = EventBubbles::from(init.parent.bubbles);
let cancelable = EventCancelable::from(init.parent.cancelable);
Ok(BluetoothAdvertisingEvent::new(
window.as_global_scope(),
proto,
Atom::from(type_),
bubbles,
cancelable,
&init.device,
name,
appearance,
txPower,
rssi,
can_gc,
))
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-device
fn Device(&self) -> DomRoot<BluetoothDevice> {
DomRoot::from_ref(&*self.device)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-name
fn GetName(&self) -> Option<DOMString> {
self.name.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-appearance
fn GetAppearance(&self) -> Option<u16> {
self.appearance
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-txpower
fn GetTxPower(&self) -> Option<i8> {
self.tx_power
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-rssi
fn GetRssi(&self) -> Option<i8> {
self.rssi
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,134 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/#characteristicproperties
#[dom_struct]
pub(crate) struct BluetoothCharacteristicProperties {
reflector_: Reflector,
broadcast: bool,
read: bool,
write_without_response: bool,
write: bool,
notify: bool,
indicate: bool,
authenticated_signed_writes: bool,
reliable_write: bool,
writable_auxiliaries: bool,
}
#[allow(non_snake_case)]
impl BluetoothCharacteristicProperties {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_inherited(
broadcast: bool,
read: bool,
write_without_response: bool,
write: bool,
notify: bool,
indicate: bool,
authenticated_signed_writes: bool,
reliable_write: bool,
writable_auxiliaries: bool,
) -> BluetoothCharacteristicProperties {
BluetoothCharacteristicProperties {
reflector_: Reflector::new(),
broadcast,
read,
write_without_response,
write,
notify,
indicate,
authenticated_signed_writes,
reliable_write,
writable_auxiliaries,
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
global: &GlobalScope,
broadcast: bool,
read: bool,
writeWithoutResponse: bool,
write: bool,
notify: bool,
indicate: bool,
authenticatedSignedWrites: bool,
reliableWrite: bool,
writableAuxiliaries: bool,
) -> DomRoot<BluetoothCharacteristicProperties> {
reflect_dom_object(
Box::new(BluetoothCharacteristicProperties::new_inherited(
broadcast,
read,
writeWithoutResponse,
write,
notify,
indicate,
authenticatedSignedWrites,
reliableWrite,
writableAuxiliaries,
)),
global,
CanGc::note(),
)
}
}
impl BluetoothCharacteristicPropertiesMethods<crate::DomTypeHolder>
for BluetoothCharacteristicProperties
{
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-broadcast
fn Broadcast(&self) -> bool {
self.broadcast
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-read
fn Read(&self) -> bool {
self.read
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-writewithoutresponse
fn WriteWithoutResponse(&self) -> bool {
self.write_without_response
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-write
fn Write(&self) -> bool {
self.write
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-notify
fn Notify(&self) -> bool {
self.notify
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-indicate
fn Indicate(&self) -> bool {
self.indicate
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-authenticatedsignedwrites
fn AuthenticatedSignedWrites(&self) -> bool {
self.authenticated_signed_writes
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-reliablewrite
fn ReliableWrite(&self) -> bool {
self.reliable_write
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-writableauxiliaries
fn WritableAuxiliaries(&self) -> bool {
self.writable_auxiliaries
}
}

View file

@ -0,0 +1,334 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::collections::HashMap;
use std::rc::Rc;
use bluetooth_traits::{
BluetoothCharacteristicMsg, BluetoothDescriptorMsg, BluetoothRequest, BluetoothResponse,
BluetoothServiceMsg,
};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use profile_traits::ipc;
use crate::conversions::Convert;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::bluetooth::{response_async, AsyncBluetoothListener, Bluetooth};
use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
use crate::dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic;
use crate::dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor;
use crate::dom::bluetoothremotegattserver::BluetoothRemoteGATTServer;
use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use crate::script_runtime::CanGc;
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(JSTraceable, MallocSizeOf)]
struct AttributeInstanceMap {
service_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTService>>>,
characteristic_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTCharacteristic>>>,
descriptor_map: DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTDescriptor>>>,
}
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice
#[dom_struct]
pub(crate) struct BluetoothDevice {
eventtarget: EventTarget,
id: DOMString,
name: Option<DOMString>,
gatt: MutNullableDom<BluetoothRemoteGATTServer>,
context: Dom<Bluetooth>,
attribute_instance_map: AttributeInstanceMap,
watching_advertisements: Cell<bool>,
}
impl BluetoothDevice {
pub(crate) fn new_inherited(
id: DOMString,
name: Option<DOMString>,
context: &Bluetooth,
) -> BluetoothDevice {
BluetoothDevice {
eventtarget: EventTarget::new_inherited(),
id,
name,
gatt: Default::default(),
context: Dom::from_ref(context),
attribute_instance_map: AttributeInstanceMap {
service_map: DomRefCell::new(HashMap::new()),
characteristic_map: DomRefCell::new(HashMap::new()),
descriptor_map: DomRefCell::new(HashMap::new()),
},
watching_advertisements: Cell::new(false),
}
}
pub(crate) fn new(
global: &GlobalScope,
id: DOMString,
name: Option<DOMString>,
context: &Bluetooth,
) -> DomRoot<BluetoothDevice> {
reflect_dom_object(
Box::new(BluetoothDevice::new_inherited(id, name, context)),
global,
CanGc::note(),
)
}
pub(crate) fn get_gatt(&self) -> DomRoot<BluetoothRemoteGATTServer> {
self.gatt
.or_init(|| BluetoothRemoteGATTServer::new(&self.global(), self))
}
fn get_context(&self) -> DomRoot<Bluetooth> {
DomRoot::from_ref(&self.context)
}
pub(crate) fn get_or_create_service(
&self,
service: &BluetoothServiceMsg,
server: &BluetoothRemoteGATTServer,
) -> DomRoot<BluetoothRemoteGATTService> {
let service_map_ref = &self.attribute_instance_map.service_map;
let mut service_map = service_map_ref.borrow_mut();
if let Some(existing_service) = service_map.get(&service.instance_id) {
return DomRoot::from_ref(existing_service);
}
let bt_service = BluetoothRemoteGATTService::new(
&server.global(),
&server.Device(),
DOMString::from(service.uuid.clone()),
service.is_primary,
service.instance_id.clone(),
);
service_map.insert(service.instance_id.clone(), Dom::from_ref(&bt_service));
bt_service
}
pub(crate) fn get_or_create_characteristic(
&self,
characteristic: &BluetoothCharacteristicMsg,
service: &BluetoothRemoteGATTService,
) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
let characteristic_map_ref = &self.attribute_instance_map.characteristic_map;
let mut characteristic_map = characteristic_map_ref.borrow_mut();
if let Some(existing_characteristic) = characteristic_map.get(&characteristic.instance_id) {
return DomRoot::from_ref(existing_characteristic);
}
let properties = BluetoothCharacteristicProperties::new(
&service.global(),
characteristic.broadcast,
characteristic.read,
characteristic.write_without_response,
characteristic.write,
characteristic.notify,
characteristic.indicate,
characteristic.authenticated_signed_writes,
characteristic.reliable_write,
characteristic.writable_auxiliaries,
);
let bt_characteristic = BluetoothRemoteGATTCharacteristic::new(
&service.global(),
service,
DOMString::from(characteristic.uuid.clone()),
&properties,
characteristic.instance_id.clone(),
);
characteristic_map.insert(
characteristic.instance_id.clone(),
Dom::from_ref(&bt_characteristic),
);
bt_characteristic
}
pub(crate) fn is_represented_device_null(&self) -> bool {
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
self.get_bluetooth_thread()
.send(BluetoothRequest::IsRepresentedDeviceNull(
self.Id().to_string(),
sender,
))
.unwrap();
receiver.recv().unwrap()
}
pub(crate) fn get_or_create_descriptor(
&self,
descriptor: &BluetoothDescriptorMsg,
characteristic: &BluetoothRemoteGATTCharacteristic,
) -> DomRoot<BluetoothRemoteGATTDescriptor> {
let descriptor_map_ref = &self.attribute_instance_map.descriptor_map;
let mut descriptor_map = descriptor_map_ref.borrow_mut();
if let Some(existing_descriptor) = descriptor_map.get(&descriptor.instance_id) {
return DomRoot::from_ref(existing_descriptor);
}
let bt_descriptor = BluetoothRemoteGATTDescriptor::new(
&characteristic.global(),
characteristic,
DOMString::from(descriptor.uuid.clone()),
descriptor.instance_id.clone(),
);
descriptor_map.insert(
descriptor.instance_id.clone(),
Dom::from_ref(&bt_descriptor),
);
bt_descriptor
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
// https://webbluetoothcg.github.io/web-bluetooth/#clean-up-the-disconnected-device
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn clean_up_disconnected_device(&self, can_gc: CanGc) {
// Step 1.
self.get_gatt().set_connected(false);
// TODO: Step 2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 3: We don't need `context`, we get the attributeInstanceMap from the device.
// https://github.com/WebBluetoothCG/web-bluetooth/issues/330
// Step 4.
let mut service_map = self.attribute_instance_map.service_map.borrow_mut();
let service_ids = service_map.drain().map(|(id, _)| id).collect();
let mut characteristic_map = self.attribute_instance_map.characteristic_map.borrow_mut();
let characteristic_ids = characteristic_map.drain().map(|(id, _)| id).collect();
let mut descriptor_map = self.attribute_instance_map.descriptor_map.borrow_mut();
let descriptor_ids = descriptor_map.drain().map(|(id, _)| id).collect();
// Step 5, 6.4, 7.
// TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
let _ = self
.get_bluetooth_thread()
.send(BluetoothRequest::SetRepresentedToNull(
service_ids,
characteristic_ids,
descriptor_ids,
));
// Step 8.
self.upcast::<EventTarget>()
.fire_bubbling_event(atom!("gattserverdisconnected"), can_gc);
}
// https://webbluetoothcg.github.io/web-bluetooth/#garbage-collect-the-connection
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn garbage_collect_the_connection(&self) -> ErrorResult {
// Step 1: TODO: Check if other systems using this device.
// Step 2.
let context = self.get_context();
for (id, device) in context.get_device_map().borrow().iter() {
// Step 2.1 - 2.2.
if id == &self.Id().to_string() && device.get_gatt().Connected() {
return Ok(());
}
}
// Step 3.
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
self.get_bluetooth_thread()
.send(BluetoothRequest::GATTServerDisconnect(
String::from(self.Id()),
sender,
))
.unwrap();
receiver.recv().unwrap().map_err(Convert::convert)
}
}
impl BluetoothDeviceMethods<crate::DomTypeHolder> for BluetoothDevice {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-id
fn Id(&self) -> DOMString {
self.id.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-name
fn GetName(&self) -> Option<DOMString> {
self.name.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt
fn GetGatt(&self) -> Option<DomRoot<BluetoothRemoteGATTServer>> {
// Step 1.
if self
.global()
.as_window()
.bluetooth_extra_permission_data()
.allowed_devices_contains_id(self.id.clone()) &&
!self.is_represented_device_null()
{
return Some(self.get_gatt());
}
// Step 2.
None
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchadvertisements
fn WatchAdvertisements(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
let sender = response_async(&p, self);
// TODO: Step 1.
// Note: Steps 2 - 3 are implemented in components/bluetooth/lib.rs in watch_advertisements function
// and in handle_response function.
self.get_bluetooth_thread()
.send(BluetoothRequest::WatchAdvertisements(
String::from(self.Id()),
sender,
))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-unwatchadvertisements
fn UnwatchAdvertisements(&self) {
// Step 1.
self.watching_advertisements.set(false)
// TODO: Step 2.
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchingadvertisements
fn WatchingAdvertisements(&self) -> bool {
self.watching_advertisements.get()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdeviceeventhandlers-ongattserverdisconnected
event_handler!(
gattserverdisconnected,
GetOngattserverdisconnected,
SetOngattserverdisconnected
);
}
impl AsyncBluetoothListener for BluetoothDevice {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-unwatchadvertisements
BluetoothResponse::WatchAdvertisements(_result) => {
// Step 3.1.
self.watching_advertisements.set(true);
// Step 3.2.
promise.resolve_native(&());
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,141 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionResultMethods;
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatus_Binding::PermissionStatusMethods;
use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
PermissionName, PermissionState,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bluetooth::{AllowedBluetoothDevice, AsyncBluetoothListener, Bluetooth};
use crate::dom::bluetoothdevice::BluetoothDevice;
use crate::dom::globalscope::GlobalScope;
use crate::dom::permissionstatus::PermissionStatus;
use crate::dom::promise::Promise;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
#[dom_struct]
pub(crate) struct BluetoothPermissionResult {
status: PermissionStatus,
devices: DomRefCell<Vec<Dom<BluetoothDevice>>>,
}
impl BluetoothPermissionResult {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn new_inherited(status: &PermissionStatus) -> BluetoothPermissionResult {
let result = BluetoothPermissionResult {
status: PermissionStatus::new_inherited(status.get_query()),
devices: DomRefCell::new(Vec::new()),
};
result.status.set_state(status.State());
result
}
pub(crate) fn new(
global: &GlobalScope,
status: &PermissionStatus,
) -> DomRoot<BluetoothPermissionResult> {
reflect_dom_object(
Box::new(BluetoothPermissionResult::new_inherited(status)),
global,
CanGc::note(),
)
}
pub(crate) fn get_bluetooth(&self) -> DomRoot<Bluetooth> {
self.global().as_window().Navigator().Bluetooth()
}
pub(crate) fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
pub(crate) fn get_query(&self) -> PermissionName {
self.status.get_query()
}
pub(crate) fn set_state(&self, state: PermissionState) {
self.status.set_state(state)
}
pub(crate) fn get_state(&self) -> PermissionState {
self.status.State()
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn set_devices(&self, devices: Vec<Dom<BluetoothDevice>>) {
*self.devices.borrow_mut() = devices;
}
}
impl BluetoothPermissionResultMethods<crate::DomTypeHolder> for BluetoothPermissionResult {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothpermissionresult-devices
fn Devices(&self) -> Vec<DomRoot<BluetoothDevice>> {
let device_vec: Vec<DomRoot<BluetoothDevice>> = self
.devices
.borrow()
.iter()
.map(|d| DomRoot::from_ref(&**d))
.collect();
device_vec
}
}
impl AsyncBluetoothListener for BluetoothPermissionResult {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
// Step 3, 11, 13 - 14.
BluetoothResponse::RequestDevice(device) => {
self.set_state(PermissionState::Granted);
let bluetooth = self.get_bluetooth();
let mut device_instance_map = bluetooth.get_device_map().borrow_mut();
if let Some(existing_device) = device_instance_map.get(&device.id) {
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
// Step 3.
self.set_devices(vec![Dom::from_ref(existing_device)]);
// https://w3c.github.io/permissions/#dom-permissions-request
// Step 8.
return promise.resolve_native(self);
}
let bt_device = BluetoothDevice::new(
&self.global(),
DOMString::from(device.id.clone()),
device.name.map(DOMString::from),
&bluetooth,
);
device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device));
self.global()
.as_window()
.bluetooth_extra_permission_data()
.add_new_allowed_device(AllowedBluetoothDevice {
deviceId: DOMString::from(device.id),
mayUseGATT: true,
});
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
// Step 3.
self.set_devices(vec![Dom::from_ref(&bt_device)]);
// https://w3c.github.io/permissions/#dom-permissions-request
// Step 8.
promise.resolve_native(self);
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,363 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::error::Error::{
self, InvalidModification, Network, NotSupported, Security,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{ByteString, DOMString};
use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener};
use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
use crate::dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use crate::script_runtime::CanGc;
// Maximum length of an attribute value.
// https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 (Vol. 3, page 2169)
pub(crate) const MAXIMUM_ATTRIBUTE_LENGTH: usize = 512;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattcharacteristic
#[dom_struct]
pub(crate) struct BluetoothRemoteGATTCharacteristic {
eventtarget: EventTarget,
service: Dom<BluetoothRemoteGATTService>,
uuid: DOMString,
properties: Dom<BluetoothCharacteristicProperties>,
value: DomRefCell<Option<ByteString>>,
instance_id: String,
}
impl BluetoothRemoteGATTCharacteristic {
pub(crate) fn new_inherited(
service: &BluetoothRemoteGATTService,
uuid: DOMString,
properties: &BluetoothCharacteristicProperties,
instance_id: String,
) -> BluetoothRemoteGATTCharacteristic {
BluetoothRemoteGATTCharacteristic {
eventtarget: EventTarget::new_inherited(),
service: Dom::from_ref(service),
uuid,
properties: Dom::from_ref(properties),
value: DomRefCell::new(None),
instance_id,
}
}
pub(crate) fn new(
global: &GlobalScope,
service: &BluetoothRemoteGATTService,
uuid: DOMString,
properties: &BluetoothCharacteristicProperties,
instance_id: String,
) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
reflect_dom_object(
Box::new(BluetoothRemoteGATTCharacteristic::new_inherited(
service,
uuid,
properties,
instance_id,
)),
global,
CanGc::note(),
)
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
fn get_instance_id(&self) -> String {
self.instance_id.clone()
}
}
impl BluetoothRemoteGATTCharacteristicMethods<crate::DomTypeHolder>
for BluetoothRemoteGATTCharacteristic
{
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-properties
fn Properties(&self) -> DomRoot<BluetoothCharacteristicProperties> {
DomRoot::from_ref(&self.properties)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-service
fn Service(&self) -> DomRoot<BluetoothRemoteGATTService> {
DomRoot::from_ref(&self.service)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-uuid
fn Uuid(&self) -> DOMString {
self.uuid.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID, can_gc: CanGc) -> Rc<Promise> {
get_gatt_children(
self,
true,
BluetoothUUID::descriptor,
Some(descriptor),
self.get_instance_id(),
self.Service().Device().get_gatt().Connected(),
GATTType::Descriptor,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors
fn GetDescriptors(
&self,
descriptor: Option<BluetoothDescriptorUUID>,
can_gc: CanGc,
) -> Rc<Promise> {
get_gatt_children(
self,
false,
BluetoothUUID::descriptor,
descriptor,
self.get_instance_id(),
self.Service().Device().get_gatt().Connected(),
GATTType::Descriptor,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value
fn GetValue(&self) -> Option<ByteString> {
self.value.borrow().clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
fn ReadValue(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
p.reject_error(Security);
return p;
}
// Step 2.
if !self.Service().Device().get_gatt().Connected() {
p.reject_error(Network);
return p;
}
// TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
// Step 5.1.
if !self.Properties().Read() {
p.reject_error(NotSupported);
return p;
}
// Note: Steps 3 - 4 and the remaining substeps of Step 5 are implemented in components/bluetooth/lib.rs
// in readValue function and in handle_response function.
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
fn WriteValue(
&self,
value: ArrayBufferViewOrArrayBuffer,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) {
p.reject_error(Security);
return p;
}
// Step 2 - 3.
let vec = match value {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
};
if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
p.reject_error(InvalidModification);
return p;
}
// Step 4.
if !self.Service().Device().get_gatt().Connected() {
p.reject_error(Network);
return p;
}
// TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
// Step 7.1.
if !(self.Properties().Write() ||
self.Properties().WriteWithoutResponse() ||
self.Properties().AuthenticatedSignedWrites())
{
p.reject_error(NotSupported);
return p;
}
// Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs
// in writeValue function and in handle_response function.
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::WriteValue(
self.get_instance_id(),
vec,
sender,
))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
fn StartNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
p.reject_error(Security);
return p;
}
// Step 2.
if !self.Service().Device().get_gatt().Connected() {
p.reject_error(Network);
return p;
}
// Step 5.
if !(self.Properties().Notify() || self.Properties().Indicate()) {
p.reject_error(NotSupported);
return p;
}
// TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic.
// Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function
// and in handle_response function.
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::EnableNotification(
self.get_instance_id(),
true,
sender,
))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
fn StopNotifications(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
let sender = response_async(&p, self);
// TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic,
// Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs
// in enable_notification function and in handle_response function.
self.get_bluetooth_thread()
.send(BluetoothRequest::EnableNotification(
self.get_instance_id(),
false,
sender,
))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged
event_handler!(
characteristicvaluechanged,
GetOncharacteristicvaluechanged,
SetOncharacteristicvaluechanged
);
}
impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, can_gc: CanGc) {
let device = self.Service().Device();
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
// Step 7.
BluetoothResponse::GetDescriptors(descriptors_vec, single) => {
if single {
promise.resolve_native(
&device.get_or_create_descriptor(&descriptors_vec[0], self),
);
return;
}
let mut descriptors = vec![];
for descriptor in descriptors_vec {
let bt_descriptor = device.get_or_create_descriptor(&descriptor, self);
descriptors.push(bt_descriptor);
}
promise.resolve_native(&descriptors);
},
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
BluetoothResponse::ReadValue(result) => {
// TODO: Step 5.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 5.5.2.
// TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
let value = ByteString::new(result);
*self.value.borrow_mut() = Some(value.clone());
// Step 5.5.3.
self.upcast::<EventTarget>()
.fire_bubbling_event(atom!("characteristicvaluechanged"), can_gc);
// Step 5.5.4.
promise.resolve_native(&value);
},
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue
BluetoothResponse::WriteValue(result) => {
// TODO: Step 7.5.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 7.5.2.
// TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
*self.value.borrow_mut() = Some(ByteString::new(result));
// Step 7.5.3.
promise.resolve_native(&());
},
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications
BluetoothResponse::EnableNotification(_result) => {
// (StartNotification) TODO: Step 10: Implement `active notification context set`
// for BluetoothRemoteGATTCharacteristic.
// (StartNotification) Step 11.
// (StopNotification) Step 5.
promise.resolve_native(self);
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,212 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding::BluetoothRemoteGATTDescriptorMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::error::Error::{self, InvalidModification, Network, Security};
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{ByteString, DOMString};
use crate::dom::bluetooth::{response_async, AsyncBluetoothListener};
use crate::dom::bluetoothremotegattcharacteristic::{
BluetoothRemoteGATTCharacteristic, MAXIMUM_ATTRIBUTE_LENGTH,
};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use crate::script_runtime::CanGc;
// http://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattdescriptor
#[dom_struct]
pub(crate) struct BluetoothRemoteGATTDescriptor {
reflector_: Reflector,
characteristic: Dom<BluetoothRemoteGATTCharacteristic>,
uuid: DOMString,
value: DomRefCell<Option<ByteString>>,
instance_id: String,
}
impl BluetoothRemoteGATTDescriptor {
pub(crate) fn new_inherited(
characteristic: &BluetoothRemoteGATTCharacteristic,
uuid: DOMString,
instance_id: String,
) -> BluetoothRemoteGATTDescriptor {
BluetoothRemoteGATTDescriptor {
reflector_: Reflector::new(),
characteristic: Dom::from_ref(characteristic),
uuid,
value: DomRefCell::new(None),
instance_id,
}
}
pub(crate) fn new(
global: &GlobalScope,
characteristic: &BluetoothRemoteGATTCharacteristic,
uuid: DOMString,
instance_id: String,
) -> DomRoot<BluetoothRemoteGATTDescriptor> {
reflect_dom_object(
Box::new(BluetoothRemoteGATTDescriptor::new_inherited(
characteristic,
uuid,
instance_id,
)),
global,
CanGc::note(),
)
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
fn get_instance_id(&self) -> String {
self.instance_id.clone()
}
}
impl BluetoothRemoteGATTDescriptorMethods<crate::DomTypeHolder> for BluetoothRemoteGATTDescriptor {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-characteristic
fn Characteristic(&self) -> DomRoot<BluetoothRemoteGATTCharacteristic> {
DomRoot::from_ref(&self.characteristic)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-uuid
fn Uuid(&self) -> DOMString {
self.uuid.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-value
fn GetValue(&self) -> Option<ByteString> {
self.value.borrow().clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue
fn ReadValue(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) {
p.reject_error(Security);
return p;
}
// Step 2.
if !self
.Characteristic()
.Service()
.Device()
.get_gatt()
.Connected()
{
p.reject_error(Network);
return p;
}
// TODO: Step 5: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
// Note: Steps 3 - 4 and substeps of Step 5 are implemented in components/bluetooth/lib.rs
// in readValue function and in handle_response function.
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::ReadValue(self.get_instance_id(), sender))
.unwrap();
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue
fn WriteValue(
&self,
value: ArrayBufferViewOrArrayBuffer,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let p = Promise::new_in_current_realm(comp, can_gc);
// Step 1.
if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) {
p.reject_error(Security);
return p;
}
// Step 2 - 3.
let vec = match value {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(),
};
if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH {
p.reject_error(InvalidModification);
return p;
}
// Step 4.
if !self
.Characteristic()
.Service()
.Device()
.get_gatt()
.Connected()
{
p.reject_error(Network);
return p;
}
// TODO: Step 7: Implement the `connection-checking-wrapper` algorithm for BluetoothRemoteGATTServer.
// Note: Steps 5 - 6 and substeps of Step 7 are implemented in components/bluetooth/lib.rs
// in writeValue function and in handle_response function.
let sender = response_async(&p, self);
self.get_bluetooth_thread()
.send(BluetoothRequest::WriteValue(
self.get_instance_id(),
vec,
sender,
))
.unwrap();
p
}
}
impl AsyncBluetoothListener for BluetoothRemoteGATTDescriptor {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue
BluetoothResponse::ReadValue(result) => {
// TODO: Step 5.4.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 5.4.2.
// TODO(#5014): Replace ByteString with ArrayBuffer when it is implemented.
let value = ByteString::new(result);
*self.value.borrow_mut() = Some(value.clone());
// Step 5.4.3.
promise.resolve_native(&value);
},
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue
BluetoothResponse::WriteValue(result) => {
// TODO: Step 7.4.1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 7.4.2.
// TODO(#5014): Replace ByteString with an ArrayBuffer wrapped in a DataView.
*self.value.borrow_mut() = Some(ByteString::new(result));
// Step 7.4.3.
// TODO: Resolve promise with undefined instead of a value.
promise.resolve_native(&());
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,186 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::rc::Rc;
use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType};
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener};
use crate::dom::bluetoothdevice::BluetoothDevice;
use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver
#[dom_struct]
pub(crate) struct BluetoothRemoteGATTServer {
reflector_: Reflector,
device: Dom<BluetoothDevice>,
connected: Cell<bool>,
}
impl BluetoothRemoteGATTServer {
pub(crate) fn new_inherited(device: &BluetoothDevice) -> BluetoothRemoteGATTServer {
BluetoothRemoteGATTServer {
reflector_: Reflector::new(),
device: Dom::from_ref(device),
connected: Cell::new(false),
}
}
pub(crate) fn new(
global: &GlobalScope,
device: &BluetoothDevice,
) -> DomRoot<BluetoothRemoteGATTServer> {
reflect_dom_object(
Box::new(BluetoothRemoteGATTServer::new_inherited(device)),
global,
CanGc::note(),
)
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
pub(crate) fn set_connected(&self, connected: bool) {
self.connected.set(connected);
}
}
impl BluetoothRemoteGATTServerMethods<crate::DomTypeHolder> for BluetoothRemoteGATTServer {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-device
fn Device(&self) -> DomRoot<BluetoothDevice> {
DomRoot::from_ref(&self.device)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connected
fn Connected(&self) -> bool {
self.connected.get()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
#[allow(unsafe_code)]
fn Connect(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
// Step 1.
let p = Promise::new_in_current_realm(comp, can_gc);
let sender = response_async(&p, self);
// TODO: Step 3: Check if the UA is currently using the Bluetooth system.
// TODO: Step 4: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// TODO: Step 5.1 - 5.2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Note: Steps 2, 5.1.1 and 5.1.3 are in components/bluetooth/lib.rs in the gatt_server_connect function.
// Steps 5.2.3 - 5.2.5 are in response function.
self.get_bluetooth_thread()
.send(BluetoothRequest::GATTServerConnect(
String::from(self.Device().Id()),
sender,
))
.unwrap();
// Step 5: return promise.
p
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-disconnect
fn Disconnect(&self, can_gc: CanGc) -> ErrorResult {
// TODO: Step 1: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
// Step 2.
if !self.Connected() {
return Ok(());
}
// Step 3.
self.Device().clean_up_disconnected_device(can_gc);
// Step 4 - 5:
self.Device().garbage_collect_the_connection()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
fn GetPrimaryService(&self, service: BluetoothServiceUUID, can_gc: CanGc) -> Rc<Promise> {
// Step 1 - 2.
get_gatt_children(
self,
true,
BluetoothUUID::service,
Some(service),
String::from(self.Device().Id()),
self.Device().get_gatt().Connected(),
GATTType::PrimaryService,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
fn GetPrimaryServices(
&self,
service: Option<BluetoothServiceUUID>,
can_gc: CanGc,
) -> Rc<Promise> {
// Step 1 - 2.
get_gatt_children(
self,
false,
BluetoothUUID::service,
service,
String::from(self.Device().Id()),
self.Connected(),
GATTType::PrimaryService,
can_gc,
)
}
}
impl AsyncBluetoothListener for BluetoothRemoteGATTServer {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
BluetoothResponse::GATTServerConnect(connected) => {
// Step 5.2.3
if self.Device().is_represented_device_null() {
if let Err(e) = self.Device().garbage_collect_the_connection() {
return promise.reject_error(e);
}
return promise.reject_error(Error::Network);
}
// Step 5.2.4.
self.connected.set(connected);
// Step 5.2.5.
promise.resolve_native(self);
},
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
// Step 7.
BluetoothResponse::GetPrimaryServices(services_vec, single) => {
let device = self.Device();
if single {
promise.resolve_native(&device.get_or_create_service(&services_vec[0], self));
return;
}
let mut services = vec![];
for service in services_vec {
let bt_service = device.get_or_create_service(&service, self);
services.push(bt_service);
}
promise.resolve_native(&services);
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,205 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use bluetooth_traits::{BluetoothResponse, GATTType};
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bluetooth::{get_gatt_children, AsyncBluetoothListener};
use crate::dom::bluetoothdevice::BluetoothDevice;
use crate::dom::bluetoothuuid::{BluetoothCharacteristicUUID, BluetoothServiceUUID, BluetoothUUID};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattservice
#[dom_struct]
pub(crate) struct BluetoothRemoteGATTService {
eventtarget: EventTarget,
device: Dom<BluetoothDevice>,
uuid: DOMString,
is_primary: bool,
instance_id: String,
}
impl BluetoothRemoteGATTService {
pub(crate) fn new_inherited(
device: &BluetoothDevice,
uuid: DOMString,
is_primary: bool,
instance_id: String,
) -> BluetoothRemoteGATTService {
BluetoothRemoteGATTService {
eventtarget: EventTarget::new_inherited(),
device: Dom::from_ref(device),
uuid,
is_primary,
instance_id,
}
}
#[allow(non_snake_case)]
pub(crate) fn new(
global: &GlobalScope,
device: &BluetoothDevice,
uuid: DOMString,
isPrimary: bool,
instanceID: String,
) -> DomRoot<BluetoothRemoteGATTService> {
reflect_dom_object(
Box::new(BluetoothRemoteGATTService::new_inherited(
device, uuid, isPrimary, instanceID,
)),
global,
CanGc::note(),
)
}
fn get_instance_id(&self) -> String {
self.instance_id.clone()
}
}
impl BluetoothRemoteGATTServiceMethods<crate::DomTypeHolder> for BluetoothRemoteGATTService {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-device
fn Device(&self) -> DomRoot<BluetoothDevice> {
DomRoot::from_ref(&self.device)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-isprimary
fn IsPrimary(&self) -> bool {
self.is_primary
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-uuid
fn Uuid(&self) -> DOMString {
self.uuid.clone()
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristic
fn GetCharacteristic(
&self,
characteristic: BluetoothCharacteristicUUID,
can_gc: CanGc,
) -> Rc<Promise> {
get_gatt_children(
self,
true,
BluetoothUUID::characteristic,
Some(characteristic),
self.get_instance_id(),
self.Device().get_gatt().Connected(),
GATTType::Characteristic,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristics
fn GetCharacteristics(
&self,
characteristic: Option<BluetoothCharacteristicUUID>,
can_gc: CanGc,
) -> Rc<Promise> {
get_gatt_children(
self,
false,
BluetoothUUID::characteristic,
characteristic,
self.get_instance_id(),
self.Device().get_gatt().Connected(),
GATTType::Characteristic,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservice
fn GetIncludedService(&self, service: BluetoothServiceUUID, can_gc: CanGc) -> Rc<Promise> {
get_gatt_children(
self,
false,
BluetoothUUID::service,
Some(service),
self.get_instance_id(),
self.Device().get_gatt().Connected(),
GATTType::IncludedService,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservices
fn GetIncludedServices(
&self,
service: Option<BluetoothServiceUUID>,
can_gc: CanGc,
) -> Rc<Promise> {
get_gatt_children(
self,
false,
BluetoothUUID::service,
service,
self.get_instance_id(),
self.Device().get_gatt().Connected(),
GATTType::IncludedService,
can_gc,
)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceadded
event_handler!(serviceadded, GetOnserviceadded, SetOnserviceadded);
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onservicechanged
event_handler!(servicechanged, GetOnservicechanged, SetOnservicechanged);
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceremoved
event_handler!(serviceremoved, GetOnserviceremoved, SetOnserviceremoved);
}
impl AsyncBluetoothListener for BluetoothRemoteGATTService {
fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>, _can_gc: CanGc) {
let device = self.Device();
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
// Step 7.
BluetoothResponse::GetCharacteristics(characteristics_vec, single) => {
if single {
promise.resolve_native(
&device.get_or_create_characteristic(&characteristics_vec[0], self),
);
return;
}
let mut characteristics = vec![];
for characteristic in characteristics_vec {
let bt_characteristic =
device.get_or_create_characteristic(&characteristic, self);
characteristics.push(bt_characteristic);
}
promise.resolve_native(&characteristics);
},
// https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren
// Step 7.
BluetoothResponse::GetIncludedServices(services_vec, single) => {
if single {
return promise.resolve_native(
&device.get_or_create_service(&services_vec[0], &device.get_gatt()),
);
}
let mut services = vec![];
for service in services_vec {
let bt_service = device.get_or_create_service(&service, &device.get_gatt());
services.push(bt_service);
}
promise.resolve_native(&services);
},
_ => promise.reject_error(Error::Type("Something went wrong...".to_owned())),
}
}
}

View file

@ -0,0 +1,670 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use regex::Regex;
use crate::dom::bindings::codegen::Bindings::BluetoothUUIDBinding::BluetoothUUIDMethods;
use crate::dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
use crate::dom::bindings::error::Error::Type;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::Reflector;
use crate::dom::bindings::str::DOMString;
use crate::dom::window::Window;
#[allow(clippy::upper_case_acronyms)]
pub(crate) type UUID = DOMString;
pub(crate) type BluetoothServiceUUID = StringOrUnsignedLong;
pub(crate) type BluetoothCharacteristicUUID = StringOrUnsignedLong;
pub(crate) type BluetoothDescriptorUUID = StringOrUnsignedLong;
// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid
#[dom_struct]
pub(crate) struct BluetoothUUID {
reflector_: Reflector,
}
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
const BLUETOOTH_ASSIGNED_SERVICES: &[(&str, u32)] = &[
("org.bluetooth.service.alert_notification", 0x1811_u32),
("org.bluetooth.service.automation_io", 0x1815_u32),
("org.bluetooth.service.battery_service", 0x180f_u32),
("org.bluetooth.service.blood_pressure", 0x1810_u32),
("org.bluetooth.service.body_composition", 0x181b_u32),
("org.bluetooth.service.bond_management", 0x181e_u32),
(
"org.bluetooth.service.continuous_glucose_monitoring",
0x181f_u32,
),
("org.bluetooth.service.current_time", 0x1805_u32),
("org.bluetooth.service.cycling_power", 0x1818_u32),
(
"org.bluetooth.service.cycling_speed_and_cadence",
0x1816_u32,
),
("org.bluetooth.service.device_information", 0x180a_u32),
("org.bluetooth.service.environmental_sensing", 0x181a_u32),
("org.bluetooth.service.generic_access", 0x1800_u32),
("org.bluetooth.service.generic_attribute", 0x1801_u32),
("org.bluetooth.service.glucose", 0x1808_u32),
("org.bluetooth.service.health_thermometer", 0x1809_u32),
("org.bluetooth.service.heart_rate", 0x180d_u32),
("org.bluetooth.service.http_proxy", 0x1823_u32),
("org.bluetooth.service.human_interface_device", 0x1812_u32),
("org.bluetooth.service.immediate_alert", 0x1802_u32),
("org.bluetooth.service.indoor_positioning", 0x1821_u32),
(
"org.bluetooth.service.internet_protocol_support",
0x1820_u32,
),
("org.bluetooth.service.link_loss", 0x1803_u32),
("org.bluetooth.service.location_and_navigation", 0x1819_u32),
("org.bluetooth.service.next_dst_change", 0x1807_u32),
("org.bluetooth.service.object_transfer", 0x1825_u32),
("org.bluetooth.service.phone_alert_status", 0x180e_u32),
("org.bluetooth.service.pulse_oximeter", 0x1822_u32),
("org.bluetooth.service.reference_time_update", 0x1806_u32),
(
"org.bluetooth.service.running_speed_and_cadence",
0x1814_u32,
),
("org.bluetooth.service.scan_parameters", 0x1813_u32),
("org.bluetooth.service.transport_discovery", 0x1824),
("org.bluetooth.service.tx_power", 0x1804_u32),
("org.bluetooth.service.user_data", 0x181c_u32),
("org.bluetooth.service.weight_scale", 0x181d_u32),
];
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
const BLUETOOTH_ASSIGNED_CHARCTERISTICS: &[(&str, u32)] = &[
(
"org.bluetooth.characteristic.aerobic_heart_rate_lower_limit",
0x2a7e_u32,
),
(
"org.bluetooth.characteristic.aerobic_heart_rate_upper_limit",
0x2a84_u32,
),
("org.bluetooth.characteristic.aerobic_threshold", 0x2a7f_u32),
("org.bluetooth.characteristic.age", 0x2a80_u32),
("org.bluetooth.characteristic.aggregate", 0x2a5a_u32),
("org.bluetooth.characteristic.alert_category_id", 0x2a43_u32),
(
"org.bluetooth.characteristic.alert_category_id_bit_mask",
0x2a42_u32,
),
("org.bluetooth.characteristic.alert_level", 0x2a06_u32),
(
"org.bluetooth.characteristic.alert_notification_control_point",
0x2a44_u32,
),
("org.bluetooth.characteristic.alert_status", 0x2a3f_u32),
("org.bluetooth.characteristic.altitude", 0x2ab3_u32),
(
"org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit",
0x2a81_u32,
),
(
"org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit",
0x2a82_u32,
),
(
"org.bluetooth.characteristic.anaerobic_threshold",
0x2a83_u32,
),
("org.bluetooth.characteristic.analog", 0x2a58_u32),
(
"org.bluetooth.characteristic.apparent_wind_direction",
0x2a73_u32,
),
(
"org.bluetooth.characteristic.apparent_wind_speed",
0x2a72_u32,
),
("org.bluetooth.characteristic.gap.appearance", 0x2a01_u32),
(
"org.bluetooth.characteristic.barometric_pressure_trend",
0x2aa3_u32,
),
("org.bluetooth.characteristic.battery_level", 0x2a19_u32),
(
"org.bluetooth.characteristic.blood_pressure_feature",
0x2a49_u32,
),
(
"org.bluetooth.characteristic.blood_pressure_measurement",
0x2a35_u32,
),
(
"org.bluetooth.characteristic.body_composition_feature",
0x2a9b_u32,
),
(
"org.bluetooth.characteristic.body_composition_measurement",
0x2a9c_u32,
),
(
"org.bluetooth.characteristic.body_sensor_location",
0x2a38_u32,
),
(
"org.bluetooth.characteristic.bond_management_control_point",
0x2aa4_u32,
),
(
"org.bluetooth.characteristic.bond_management_feature",
0x2aa5_u32,
),
(
"org.bluetooth.characteristic.boot_keyboard_input_report",
0x2a22_u32,
),
(
"org.bluetooth.characteristic.boot_keyboard_output_report",
0x2a32_u32,
),
(
"org.bluetooth.characteristic.boot_mouse_input_report",
0x2a33_u32,
),
(
"org.bluetooth.characteristic.gap.central_address_resolution_support",
0x2aa6_u32,
),
("org.bluetooth.characteristic.cgm_feature", 0x2aa8_u32),
("org.bluetooth.characteristic.cgm_measurement", 0x2aa7_u32),
(
"org.bluetooth.characteristic.cgm_session_run_time",
0x2aab_u32,
),
(
"org.bluetooth.characteristic.cgm_session_start_time",
0x2aaa_u32,
),
(
"org.bluetooth.characteristic.cgm_specific_ops_control_point",
0x2aac_u32,
),
("org.bluetooth.characteristic.cgm_status", 0x2aa9_u32),
("org.bluetooth.characteristic.csc_feature", 0x2a5c_u32),
("org.bluetooth.characteristic.csc_measurement", 0x2a5b_u32),
("org.bluetooth.characteristic.current_time", 0x2a2b_u32),
(
"org.bluetooth.characteristic.cycling_power_control_point",
0x2a66_u32,
),
(
"org.bluetooth.characteristic.cycling_power_feature",
0x2a65_u32,
),
(
"org.bluetooth.characteristic.cycling_power_measurement",
0x2a63_u32,
),
(
"org.bluetooth.characteristic.cycling_power_vector",
0x2a64_u32,
),
(
"org.bluetooth.characteristic.database_change_increment",
0x2a99_u32,
),
("org.bluetooth.characteristic.date_of_birth", 0x2a85_u32),
(
"org.bluetooth.characteristic.date_of_threshold_assessment",
0x2a86_u32,
),
("org.bluetooth.characteristic.date_time", 0x2a08_u32),
("org.bluetooth.characteristic.day_date_time", 0x2a0a_u32),
("org.bluetooth.characteristic.day_of_week", 0x2a09_u32),
(
"org.bluetooth.characteristic.descriptor_value_changed",
0x2a7d_u32,
),
("org.bluetooth.characteristic.gap.device_name", 0x2a00_u32),
("org.bluetooth.characteristic.dew_point", 0x2a7b_u32),
("org.bluetooth.characteristic.digital", 0x2a56_u32),
("org.bluetooth.characteristic.dst_offset", 0x2a0d_u32),
("org.bluetooth.characteristic.elevation", 0x2a6c_u32),
("org.bluetooth.characteristic.email_address", 0x2a87_u32),
("org.bluetooth.characteristic.exact_time_256", 0x2a0c_u32),
(
"org.bluetooth.characteristic.fat_burn_heart_rate_lower_limit",
0x2a88_u32,
),
(
"org.bluetooth.characteristic.fat_burn_heart_rate_upper_limit",
0x2a89_u32,
),
(
"org.bluetooth.characteristic.firmware_revision_string",
0x2a26_u32,
),
("org.bluetooth.characteristic.first_name", 0x2a8a_u32),
(
"org.bluetooth.characteristic.five_zone_heart_rate_limits",
0x2a8b_u32,
),
("org.bluetooth.characteristic.floor_number", 0x2ab2_u32),
("org.bluetooth.characteristic.gender", 0x2a8c_u32),
("org.bluetooth.characteristic.glucose_feature", 0x2a51_u32),
(
"org.bluetooth.characteristic.glucose_measurement",
0x2a18_u32,
),
(
"org.bluetooth.characteristic.glucose_measurement_context",
0x2a34_u32,
),
("org.bluetooth.characteristic.gust_factor", 0x2a74_u32),
(
"org.bluetooth.characteristic.hardware_revision_string",
0x2a27_u32,
),
(
"org.bluetooth.characteristic.heart_rate_control_point",
0x2a39_u32,
),
("org.bluetooth.characteristic.heart_rate_max", 0x2a8d_u32),
(
"org.bluetooth.characteristic.heart_rate_measurement",
0x2a37_u32,
),
("org.bluetooth.characteristic.heat_index", 0x2a7a_u32),
("org.bluetooth.characteristic.height", 0x2a8e_u32),
("org.bluetooth.characteristic.hid_control_point", 0x2a4c_u32),
("org.bluetooth.characteristic.hid_information", 0x2a4a_u32),
("org.bluetooth.characteristic.hip_circumference", 0x2a8f_u32),
(
"org.bluetooth.characteristic.http_control_point",
0x2aba_u32,
),
("org.bluetooth.characteristic.http_entity_body", 0x2ab9_u32),
("org.bluetooth.characteristic.http_headers", 0x2ab7_u32),
("org.bluetooth.characteristic.http_status_code", 0x2ab8_u32),
("org.bluetooth.characteristic.https_security", 0x2abb_u32),
("org.bluetooth.characteristic.humidity", 0x2a6f_u32),
(
"org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list",
0x2a2a_u32,
),
(
"org.bluetooth.characteristic.indoor_positioning_configuration",
0x2aad_u32,
),
(
"org.bluetooth.characteristic.intermediate_cuff_pressure",
0x2a36_u32,
),
(
"org.bluetooth.characteristic.intermediate_temperature",
0x2a1e_u32,
),
("org.bluetooth.characteristic.irradiance", 0x2a77_u32),
("org.bluetooth.characteristic.language", 0x2aa2_u32),
("org.bluetooth.characteristic.last_name", 0x2a90_u32),
("org.bluetooth.characteristic.latitude", 0x2aae_u32),
("org.bluetooth.characteristic.ln_control_point", 0x2a6b_u32),
("org.bluetooth.characteristic.ln_feature", 0x2a6a_u32),
(
"org.bluetooth.characteristic.local_east_coordinate.xml",
0x2ab1_u32,
),
(
"org.bluetooth.characteristic.local_north_coordinate",
0x2ab0_u32,
),
(
"org.bluetooth.characteristic.local_time_information",
0x2a0f_u32,
),
(
"org.bluetooth.characteristic.location_and_speed",
0x2a67_u32,
),
("org.bluetooth.characteristic.location_name", 0x2ab5_u32),
("org.bluetooth.characteristic.longitude", 0x2aaf_u32),
(
"org.bluetooth.characteristic.magnetic_declination",
0x2a2c_u32,
),
(
"org.bluetooth.characteristic.magnetic_flux_density_2d",
0x2aa0_u32,
),
(
"org.bluetooth.characteristic.magnetic_flux_density_3d",
0x2aa1_u32,
),
(
"org.bluetooth.characteristic.manufacturer_name_string",
0x2a29_u32,
),
(
"org.bluetooth.characteristic.maximum_recommended_heart_rate",
0x2a91_u32,
),
(
"org.bluetooth.characteristic.measurement_interval",
0x2a21_u32,
),
(
"org.bluetooth.characteristic.model_number_string",
0x2a24_u32,
),
("org.bluetooth.characteristic.navigation", 0x2a68_u32),
("org.bluetooth.characteristic.new_alert", 0x2a46_u32),
(
"org.bluetooth.characteristic.object_action_control_point",
0x2ac5_u32,
),
("org.bluetooth.characteristic.object_changed", 0x2ac8_u32),
(
"org.bluetooth.characteristic.object_first_created",
0x2ac1_u32,
),
("org.bluetooth.characteristic.object_id", 0x2ac3_u32),
(
"org.bluetooth.characteristic.object_last_modified",
0x2ac2_u32,
),
(
"org.bluetooth.characteristic.object_list_control_point",
0x2ac6_u32,
),
(
"org.bluetooth.characteristic.object_list_filter",
0x2ac7_u32,
),
("org.bluetooth.characteristic.object_name", 0x2abe_u32),
("org.bluetooth.characteristic.object_properties", 0x2ac4_u32),
("org.bluetooth.characteristic.object_size", 0x2ac0_u32),
("org.bluetooth.characteristic.object_type", 0x2abf_u32),
("org.bluetooth.characteristic.ots_feature", 0x2abd_u32),
(
"org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters",
0x2a04_u32,
),
(
"org.bluetooth.characteristic.gap.peripheral_privacy_flag",
0x2a02_u32,
),
(
"org.bluetooth.characteristic.plx_continuous_measurement",
0x2a5f_u32,
),
("org.bluetooth.characteristic.plx_features", 0x2a60_u32),
(
"org.bluetooth.characteristic.plx_spot_check_measurement",
0x2a5e_u32,
),
("org.bluetooth.characteristic.pnp_id", 0x2a50_u32),
(
"org.bluetooth.characteristic.pollen_concentration",
0x2a75_u32,
),
("org.bluetooth.characteristic.position_quality", 0x2a69_u32),
("org.bluetooth.characteristic.pressure", 0x2a6d_u32),
("org.bluetooth.characteristic.protocol_mode", 0x2a4e_u32),
("org.bluetooth.characteristic.rainfall", 0x2a78_u32),
(
"org.bluetooth.characteristic.gap.reconnection_address",
0x2a03_u32,
),
(
"org.bluetooth.characteristic.record_access_control_point",
0x2a52_u32,
),
(
"org.bluetooth.characteristic.reference_time_information",
0x2a14_u32,
),
("org.bluetooth.characteristic.report", 0x2a4d_u32),
("org.bluetooth.characteristic.report_map", 0x2a4b_u32),
(
"org.bluetooth.characteristic.resting_heart_rate",
0x2a92_u32,
),
(
"org.bluetooth.characteristic.ringer_control_point",
0x2a40_u32,
),
("org.bluetooth.characteristic.ringer_setting", 0x2a41_u32),
("org.bluetooth.characteristic.rsc_feature", 0x2a54_u32),
("org.bluetooth.characteristic.rsc_measurement", 0x2a53_u32),
("org.bluetooth.characteristic.sc_control_point", 0x2a55_u32),
(
"org.bluetooth.characteristic.scan_interval_window",
0x2a4f_u32,
),
("org.bluetooth.characteristic.scan_refresh", 0x2a31_u32),
("org.bluetooth.characteristic.sensor_location", 0x2a5d_u32),
(
"org.bluetooth.characteristic.serial_number_string",
0x2a25_u32,
),
(
"org.bluetooth.characteristic.gatt.service_changed",
0x2a05_u32,
),
(
"org.bluetooth.characteristic.software_revision_string",
0x2a28_u32,
),
(
"org.bluetooth.characteristic.sport_type_for_aerobic_and_anaerobic_thresholds",
0x2a93_u32,
),
(
"org.bluetooth.characteristic.supported_new_alert_category",
0x2a47_u32,
),
(
"org.bluetooth.characteristic.supported_unread_alert_category",
0x2a48_u32,
),
("org.bluetooth.characteristic.system_id", 0x2a23_u32),
("org.bluetooth.characteristic.tds_control_point", 0x2abc_u32),
("org.bluetooth.characteristic.temperature", 0x2a6e_u32),
(
"org.bluetooth.characteristic.temperature_measurement",
0x2a1c_u32,
),
("org.bluetooth.characteristic.temperature_type", 0x2a1d_u32),
(
"org.bluetooth.characteristic.three_zone_heart_rate_limits",
0x2a94_u32,
),
("org.bluetooth.characteristic.time_accuracy", 0x2a12_u32),
("org.bluetooth.characteristic.time_source", 0x2a13_u32),
(
"org.bluetooth.characteristic.time_update_control_point",
0x2a16_u32,
),
("org.bluetooth.characteristic.time_update_state", 0x2a17_u32),
("org.bluetooth.characteristic.time_with_dst", 0x2a11_u32),
("org.bluetooth.characteristic.time_zone", 0x2a0e_u32),
(
"org.bluetooth.characteristic.true_wind_direction",
0x2a71_u32,
),
("org.bluetooth.characteristic.true_wind_speed", 0x2a70_u32),
(
"org.bluetooth.characteristic.two_zone_heart_rate_limit",
0x2a95_u32,
),
("org.bluetooth.characteristic.tx_power_level", 0x2a07_u32),
("org.bluetooth.characteristic.uncertainty", 0x2ab4_u32),
(
"org.bluetooth.characteristic.unread_alert_status",
0x2a45_u32,
),
("org.bluetooth.characteristic.uri", 0x2ab6_u32),
(
"org.bluetooth.characteristic.user_control_point",
0x2a9f_u32,
),
("org.bluetooth.characteristic.user_index", 0x2a9a_u32),
("org.bluetooth.characteristic.uv_index", 0x2a76_u32),
("org.bluetooth.characteristic.vo2_max", 0x2a96_u32),
(
"org.bluetooth.characteristic.waist_circumference",
0x2a97_u32,
),
("org.bluetooth.characteristic.weight", 0x2a98_u32),
(
"org.bluetooth.characteristic.weight_measurement",
0x2a9d_u32,
),
(
"org.bluetooth.characteristic.weight_scale_feature",
0x2a9e_u32,
),
("org.bluetooth.characteristic.wind_chill", 0x2a79_u32),
];
//https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
const BLUETOOTH_ASSIGNED_DESCRIPTORS: &[(&str, u32)] = &[
(
"org.bluetooth.descriptor.gatt.characteristic_extended_properties",
0x2900_u32,
),
(
"org.bluetooth.descriptor.gatt.characteristic_user_description",
0x2901_u32,
),
(
"org.bluetooth.descriptor.gatt.client_characteristic_configuration",
0x2902_u32,
),
(
"org.bluetooth.descriptor.gatt.server_characteristic_configuration",
0x2903_u32,
),
(
"org.bluetooth.descriptor.gatt.characteristic_presentation_format",
0x2904_u32,
),
(
"org.bluetooth.descriptor.gatt.characteristic_aggregate_format",
0x2905_u32,
),
("org.bluetooth.descriptor.valid_range", 0x2906_u32),
(
"org.bluetooth.descriptor.external_report_reference",
0x2907_u32,
),
("org.bluetooth.descriptor.report_reference", 0x2908_u32),
("org.bluetooth.descriptor.number_of_digitals", 0x2909_u32),
("org.bluetooth.descriptor.value_trigger_setting", 0x290a_u32),
("org.bluetooth.descriptor.es_configuration", 0x290b_u32),
("org.bluetooth.descriptor.es_measurement", 0x290c_u32),
("org.bluetooth.descriptor.es_trigger_setting", 0x290d_u32),
("org.bluetooth.descriptor.time_trigger_setting", 0x290e_u32),
];
const BASE_UUID: &str = "-0000-1000-8000-00805f9b34fb";
const SERVICE_PREFIX: &str = "org.bluetooth.service";
const CHARACTERISTIC_PREFIX: &str = "org.bluetooth.characteristic";
const DESCRIPTOR_PREFIX: &str = "org.bluetooth.descriptor";
const VALID_UUID_REGEX: &str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=314
const UUID_ERROR_MESSAGE: &str = "It must be a valid UUID alias (e.g. 0x1234), \
UUID (lowercase hex characters e.g. '00001234-0000-1000-8000-00805f9b34fb'),\nor recognized standard name from";
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=321
const SERVICES_ERROR_MESSAGE: &str =
"https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx\
\ne.g. 'alert_notification'.";
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=327
const CHARACTERISTIC_ERROR_MESSAGE: &str =
"https://developer.bluetooth.org/gatt/characteristics/Pages/\
CharacteristicsHome.aspx\ne.g. 'aerobic_heart_rate_lower_limit'.";
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=333
const DESCRIPTOR_ERROR_MESSAGE: &str = "https://developer.bluetooth.org/gatt/descriptors/Pages/\
DescriptorsHomePage.aspx\ne.g. 'gatt.characteristic_presentation_format'.";
impl BluetoothUUIDMethods<crate::DomTypeHolder> for BluetoothUUID {
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-canonicaluuid
fn CanonicalUUID(_: &Window, alias: u32) -> UUID {
canonical_uuid(alias)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getservice
fn GetService(_: &Window, name: BluetoothServiceUUID) -> Fallible<UUID> {
Self::service(name)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getcharacteristic
fn GetCharacteristic(_: &Window, name: BluetoothCharacteristicUUID) -> Fallible<UUID> {
Self::characteristic(name)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getdescriptor
fn GetDescriptor(_: &Window, name: BluetoothDescriptorUUID) -> Fallible<UUID> {
Self::descriptor(name)
}
}
impl BluetoothUUID {
pub(crate) fn service(name: BluetoothServiceUUID) -> Fallible<UUID> {
resolve_uuid_name(name, BLUETOOTH_ASSIGNED_SERVICES, SERVICE_PREFIX)
}
pub(crate) fn characteristic(name: BluetoothCharacteristicUUID) -> Fallible<UUID> {
resolve_uuid_name(
name,
BLUETOOTH_ASSIGNED_CHARCTERISTICS,
CHARACTERISTIC_PREFIX,
)
}
pub(crate) fn descriptor(name: BluetoothDescriptorUUID) -> Fallible<UUID> {
resolve_uuid_name(name, BLUETOOTH_ASSIGNED_DESCRIPTORS, DESCRIPTOR_PREFIX)
}
}
fn canonical_uuid(alias: u32) -> UUID {
UUID::from(format!("{:08x}", &alias) + BASE_UUID)
}
// https://webbluetoothcg.github.io/web-bluetooth/#resolveuuidname
fn resolve_uuid_name(
name: StringOrUnsignedLong,
assigned_numbers_table: &'static [(&'static str, u32)],
prefix: &str,
) -> Fallible<DOMString> {
match name {
// Step 1.
StringOrUnsignedLong::UnsignedLong(unsigned32) => Ok(canonical_uuid(unsigned32)),
StringOrUnsignedLong::String(dstring) => {
// Step 2.
let regex = Regex::new(VALID_UUID_REGEX).unwrap();
if regex.is_match(&dstring) {
Ok(dstring)
} else {
// Step 3.
let concatenated = format!("{}.{}", prefix, dstring);
let is_in_table = assigned_numbers_table.iter().find(|p| p.0 == concatenated);
match is_in_table {
Some(&(_, alias)) => Ok(canonical_uuid(alias)),
None => {
let (attribute_type, error_url_message) = match prefix {
SERVICE_PREFIX => ("Service", SERVICES_ERROR_MESSAGE),
CHARACTERISTIC_PREFIX => {
("Characteristic", CHARACTERISTIC_ERROR_MESSAGE)
},
DESCRIPTOR_PREFIX => ("Descriptor", DESCRIPTOR_ERROR_MESSAGE),
_ => unreachable!(),
};
// Step 4.
Err(Type(format!(
"Invalid {} name : '{}'.\n{} {}",
attribute_type, dstring, UUID_ERROR_MESSAGE, error_url_message
)))
},
}
}
},
}
}

View file

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub(crate) use self::bluetooth::*;
pub(crate) mod bluetooth;
pub(crate) mod bluetoothadvertisingevent;
pub(crate) mod bluetoothcharacteristicproperties;
pub(crate) mod bluetoothdevice;
pub(crate) mod bluetoothpermissionresult;
pub(crate) mod bluetoothremotegattcharacteristic;
pub(crate) mod bluetoothremotegattdescriptor;
pub(crate) mod bluetoothremotegattserver;
pub(crate) mod bluetoothremotegattservice;
pub(crate) mod bluetoothuuid;
pub(crate) mod testrunner;

View file

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use bluetooth_traits::BluetoothRequest;
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use profile_traits::ipc;
use crate::conversions::Convert;
use crate::dom::bindings::codegen::Bindings::TestRunnerBinding::TestRunnerMethods;
use crate::dom::bindings::error::ErrorResult;
use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
// https://webbluetoothcg.github.io/web-bluetooth/tests#test-runner
#[dom_struct]
pub(crate) struct TestRunner {
reflector_: Reflector,
}
impl TestRunner {
pub(crate) fn new_inherited() -> TestRunner {
TestRunner {
reflector_: Reflector::new(),
}
}
pub(crate) fn new(global: &GlobalScope) -> DomRoot<TestRunner> {
reflect_dom_object(Box::new(TestRunner::new_inherited()), global, CanGc::note())
}
fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
self.global().as_window().bluetooth_thread()
}
}
impl TestRunnerMethods<crate::DomTypeHolder> for TestRunner {
// https://webbluetoothcg.github.io/web-bluetooth/tests#setBluetoothMockDataSet
#[allow(non_snake_case)]
fn SetBluetoothMockDataSet(&self, dataSetName: DOMString) -> ErrorResult {
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
self.get_bluetooth_thread()
.send(BluetoothRequest::Test(String::from(dataSetName), sender))
.unwrap();
match receiver.recv().unwrap() {
Ok(()) => Ok(()),
Err(error) => Err(error.convert()),
}
}
}