/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/bluetooth/bluetoothdevice.rs
373 строки
13 KB
Simon Wülker
script: Migrate some calls from `DomRefCell::borrow_mut` to `safe_borrow_mut(cx.no_gc())` (#46435)
10 июл 2026, 12:08
Не верифицирован
10 июл 2026, 12:08
5b9e3a8
Код
Авторство
О чём код?
/* 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 dom_struct::dom_struct; use js::context::JSContext; use js::realm::CurrentRealm; use profile_traits::generic_channel; use script_bindings::cell::DomRefCell; use script_bindings::reflector::reflect_dom_object_with_cx; use servo_base::generic_channel::GenericSender; use servo_bluetooth_traits::{ BluetoothCharacteristicMsg, BluetoothDescriptorMsg, BluetoothRequest, BluetoothResponse, BluetoothServiceMsg, }; use crate::conversions::Convert; 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::DomGlobal; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::bluetooth::{AsyncBluetoothListener, Bluetooth, response_async}; 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; #[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( cx: &mut JSContext, global: &GlobalScope, id: DOMString, name: Option<DOMString>, context: &Bluetooth, ) -> DomRoot<BluetoothDevice> { reflect_dom_object_with_cx( Box::new(BluetoothDevice::new_inherited(id, name, context)), global, cx, ) } pub(crate) fn get_gatt(&self, cx: &mut JSContext) -> DomRoot<BluetoothRemoteGATTServer> { self.gatt .or_init(|| BluetoothRemoteGATTServer::new(cx, &self.global(), self)) } fn get_context(&self) -> DomRoot<Bluetooth> { DomRoot::from_ref(&self.context) } pub(crate) fn get_or_create_service( &self, cx: &mut JSContext, service: &BluetoothServiceMsg, server: &BluetoothRemoteGATTServer, ) -> DomRoot<BluetoothRemoteGATTService> { let service_map_ref = &self.attribute_instance_map.service_map; { let service_map = service_map_ref.borrow(); if let Some(existing_service) = service_map.get(&service.instance_id) { return DomRoot::from_ref(existing_service); } } let bt_service = BluetoothRemoteGATTService::new( cx, &server.global(), &server.Device(), DOMString::from(service.uuid.clone()), service.is_primary, service.instance_id.clone(), ); service_map_ref .safe_borrow_mut(cx.no_gc()) .insert(service.instance_id.clone(), Dom::from_ref(&bt_service)); bt_service } pub(crate) fn get_or_create_characteristic( &self, cx: &mut JSContext, characteristic: &BluetoothCharacteristicMsg, service: &BluetoothRemoteGATTService, ) -> DomRoot<BluetoothRemoteGATTCharacteristic> { let characteristic_map_ref = &self.attribute_instance_map.characteristic_map; { let characteristic_map = characteristic_map_ref.borrow(); if let Some(existing_characteristic) = characteristic_map.get(&characteristic.instance_id) { return DomRoot::from_ref(existing_characteristic); } } let properties = BluetoothCharacteristicProperties::new( cx, &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( cx, &service.global(), service, DOMString::from(characteristic.uuid.clone()), &properties, characteristic.instance_id.clone(), ); characteristic_map_ref.safe_borrow_mut(cx.no_gc()).insert( characteristic.instance_id.clone(), Dom::from_ref(&bt_characteristic), ); bt_characteristic } pub(crate) fn is_represented_device_null(&self) -> bool { let (sender, receiver) = generic_channel::channel(self.global().time_profiler_chan().clone()).unwrap(); self.get_bluetooth_thread() .send(BluetoothRequest::IsRepresentedDeviceNull( String::from(self.Id()), sender, )) .unwrap(); receiver.recv().unwrap() } pub(crate) fn get_or_create_descriptor( &self, cx: &mut JSContext, descriptor: &BluetoothDescriptorMsg, characteristic: &BluetoothRemoteGATTCharacteristic, ) -> DomRoot<BluetoothRemoteGATTDescriptor> { let descriptor_map_ref = &self.attribute_instance_map.descriptor_map; { let descriptor_map = descriptor_map_ref.borrow(); if let Some(existing_descriptor) = descriptor_map.get(&descriptor.instance_id) { return DomRoot::from_ref(existing_descriptor); } } let bt_descriptor = BluetoothRemoteGATTDescriptor::new( cx, &characteristic.global(), characteristic, DOMString::from(descriptor.uuid.clone()), descriptor.instance_id.clone(), ); descriptor_map_ref.safe_borrow_mut(cx.no_gc()).insert( descriptor.instance_id.clone(), Dom::from_ref(&bt_descriptor), ); bt_descriptor } fn get_bluetooth_thread(&self) -> GenericSender<BluetoothRequest> { self.global().as_window().bluetooth_thread() } // https://webbluetoothcg.github.io/web-bluetooth/#clean-up-the-disconnected-device pub(crate) fn clean_up_disconnected_device(&self, cx: &mut JSContext) { // Step 1. self.get_gatt(cx).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 service_ids = { let mut service_map = self .attribute_instance_map .service_map .safe_borrow_mut(cx.no_gc()); service_map.drain().map(|(id, _)| id).collect() }; let characteristic_ids = { let mut characteristic_map = self .attribute_instance_map .characteristic_map .safe_borrow_mut(cx.no_gc()); characteristic_map.drain().map(|(id, _)| id).collect() }; let descriptor_ids = { let mut descriptor_map = self .attribute_instance_map .descriptor_map .safe_borrow_mut(cx.no_gc()); 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(cx, atom!("gattserverdisconnected")); } // https://webbluetoothcg.github.io/web-bluetooth/#garbage-collect-the-connection pub(crate) fn garbage_collect_the_connection(&self, cx: &mut JSContext) -> 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().str() as &str && device.get_gatt(cx).Connected() { return Ok(()); } } // Step 3. let (sender, receiver) = generic_channel::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, cx: &mut JSContext) -> 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(cx)); } // Step 2. None } /// <https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchadvertisements> fn WatchAdvertisements(&self, cx: &mut CurrentRealm) -> Rc<Promise> { let p = Promise::new_in_realm(cx); 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, cx: &mut JSContext, response: BluetoothResponse, promise: &Rc<Promise>, ) { 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(cx, &()); }, _ => promise.reject_error(cx, Error::Type(c"Something went wrong...".to_owned())), } } }