diff options
Diffstat (limited to 'components/script/dom/bluetooth.rs')
-rw-r--r-- | components/script/dom/bluetooth.rs | 173 |
1 files changed, 106 insertions, 67 deletions
diff --git a/components/script/dom/bluetooth.rs b/components/script/dom/bluetooth.rs index 7080a38668b..1f5fd15c49f 100644 --- a/components/script/dom/bluetooth.rs +++ b/components/script/dom/bluetooth.rs @@ -2,7 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use bluetooth_traits::{BluetoothError, BluetoothMethodMsg}; +use bluetooth_traits::{BluetoothError, BluetoothRequest}; +use bluetooth_traits::{BluetoothResponse, BluetoothResponseListener, BluetoothResponseResult}; use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted}; use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence}; use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence}; @@ -13,6 +14,7 @@ use dom::bindings::codegen::Bindings::BluetoothBinding::RequestDeviceOptions; use dom::bindings::error::Error::{self, NotFound, Security, Type}; use dom::bindings::error::Fallible; use dom::bindings::js::{JS, MutHeap, Root}; +use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::str::DOMString; use dom::bluetoothadvertisingdata::BluetoothAdvertisingData; @@ -24,9 +26,12 @@ use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID}; use dom::globalscope::GlobalScope; use dom::promise::Promise; use ipc_channel::ipc::{self, IpcSender}; -use js::conversions::ToJSValConvertible; +use ipc_channel::router::ROUTER; +use js::jsapi::{JSAutoCompartment, JSContext}; +use network_listener::{NetworkListener, PreInvoke}; use std::collections::HashMap; use std::rc::Rc; +use std::sync::{Arc, Mutex}; const FILTER_EMPTY_ERROR: &'static str = "'filters' member, if present, must be nonempty to find any devices."; const FILTER_ERROR: &'static str = "A filter must restrict the devices in some way."; @@ -43,6 +48,33 @@ const SERVICE_ERROR: &'static str = "'services', if present, must contain at lea const OPTIONS_ERROR: &'static str = "Fields of 'options' conflict with each other. Either 'acceptAllDevices' member must be true, or 'filters' member must be set to a value."; +struct BluetoothContext<T: AsyncBluetoothListener + Reflectable> { + promise: Option<TrustedPromise>, + receiver: Trusted<T>, +} + +pub trait AsyncBluetoothListener { + fn handle_response(&self, result: BluetoothResponse, cx: *mut JSContext, promise: &Rc<Promise>); +} + +impl<Listener: AsyncBluetoothListener + Reflectable> PreInvoke for BluetoothContext<Listener> {} + +impl<Listener: AsyncBluetoothListener + Reflectable> BluetoothResponseListener for BluetoothContext<Listener> { + #[allow(unrooted_must_root)] + fn response(&mut self, response: BluetoothResponseResult) { + let promise = self.promise.take().expect("bt promise is missing").root(); + let promise_cx = promise.global().get_cx(); + + // JSAutoCompartment needs to be manually made. + // Otherwise, Servo will crash. + let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get()); + match response { + Ok(response) => self.receiver.root().handle_response(response, promise_cx, &promise), + Err(error) => promise.reject_error(promise_cx, Error::from(error)), + } + } +} + // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth #[dom_struct] pub struct Bluetooth { @@ -83,75 +115,55 @@ impl Bluetooth { &self.descriptor_instance_map } - fn get_bluetooth_thread(&self) -> IpcSender<BluetoothMethodMsg> { + fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { self.global().as_window().bluetooth_thread() } - fn request_device(&self, option: &RequestDeviceOptions) -> Fallible<Root<BluetoothDevice>> { - // Step 1. - // TODO(#4282): Reject promise. - if (option.filters.is_some() && option.acceptAllDevices) || - (option.filters.is_none() && !option.acceptAllDevices) { - return Err(Type(OPTIONS_ERROR.to_owned())); - } - // Step 2. - if !option.acceptAllDevices { - return self.request_bluetooth_devices(&option.filters, &option.optionalServices); - } - - self.request_bluetooth_devices(&None, &option.optionalServices) - // TODO(#4282): Step 3-5: Reject and resolve promise. - } - // https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices fn request_bluetooth_devices(&self, + p: &Rc<Promise>, filters: &Option<Vec<BluetoothRequestDeviceFilter>>, - optional_services: &Option<Vec<BluetoothServiceUUID>>) - -> Fallible<Root<BluetoothDevice>> { + optional_services: &Option<Vec<BluetoothServiceUUID>>) { // TODO: Step 1: Triggered by user activation. // Step 2. - let option = try!(convert_request_device_options(filters, optional_services)); + let option = match convert_request_device_options(filters, optional_services) { + Ok(o) => o, + Err(e) => { + p.reject_error(p.global().get_cx(), e); + return; + } + }; // TODO: Step 3-5: Implement the permission API. // Note: Steps 6-8 are implemented in // components/net/bluetooth_thread.rs in request_device function. - let (sender, receiver) = ipc::channel().unwrap(); - self.get_bluetooth_thread().send(BluetoothMethodMsg::RequestDevice(option, sender)).unwrap(); - let device = receiver.recv().unwrap(); - - // TODO: Step 9-10: Implement the permission API. - - // Step 11: This step is optional. - - // Step 12-13. - match device { - Ok(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 Ok(existing_device.get()); - } - let ad_data = BluetoothAdvertisingData::new(&self.global(), - device.appearance, - device.tx_power, - device.rssi); - let bt_device = BluetoothDevice::new(&self.global(), - DOMString::from(device.id.clone()), - device.name.map(DOMString::from), - &ad_data, - &self); - device_instance_map.insert(device.id, MutHeap::new(&bt_device)); - Ok(bt_device) - }, - Err(error) => { - Err(Error::from(error)) - }, - } - + let sender = response_async(p, self); + self.get_bluetooth_thread().send(BluetoothRequest::RequestDevice(option, sender)).unwrap(); } } +pub fn response_async<T: AsyncBluetoothListener + Reflectable + 'static>( + promise: &Rc<Promise>, + receiver: &T) -> IpcSender<BluetoothResponseResult> { + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let chan = receiver.global().networking_task_source(); + let context = Arc::new(Mutex::new(BluetoothContext { + promise: Some(TrustedPromise::new(promise.clone())), + receiver: Trusted::new(receiver), + })); + let listener = NetworkListener { + context: context, + script_chan: chan, + wrapper: None, + }; + ROUTER.add_route(action_receiver.to_opaque(), box move |message| { + listener.notify_response(message.to().unwrap()); + }); + action_sender +} + // https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices fn convert_request_device_options(filters: &Option<Vec<BluetoothRequestDeviceFilter>>, optional_services: &Option<Vec<BluetoothServiceUUID>>) @@ -300,18 +312,6 @@ fn canonicalize_filter(filter: &BluetoothRequestDeviceFilter) -> Fallible<Blueto service_data_uuid)) } -#[allow(unrooted_must_root)] -pub fn result_to_promise<T: ToJSValConvertible>(global: &GlobalScope, - bluetooth_result: Fallible<T>) - -> Rc<Promise> { - let p = Promise::new(global); - match bluetooth_result { - Ok(v) => p.resolve_native(p.global().get_cx(), &v), - Err(e) => p.reject_error(p.global().get_cx(), e), - } - p -} - impl From<BluetoothError> for Error { fn from(error: BluetoothError) -> Self { match error { @@ -329,6 +329,45 @@ impl BluetoothMethods for Bluetooth { #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice fn RequestDevice(&self, option: &RequestDeviceOptions) -> Rc<Promise> { - result_to_promise(&self.global(), self.request_device(option)) + let p = Promise::new(&self.global()); + // Step 1. + if (option.filters.is_some() && option.acceptAllDevices) || + (option.filters.is_none() && !option.acceptAllDevices) { + p.reject_error(p.global().get_cx(), Error::Type(OPTIONS_ERROR.to_owned())); + return p; + } + // Step 2. + if !option.acceptAllDevices { + self.request_bluetooth_devices(&p, &option.filters, &option.optionalServices); + } else { + self.request_bluetooth_devices(&p, &None, &option.optionalServices); + } + // TODO(#4282): Step 3-5: Reject and resolve promise. + return p; + } +} + +impl AsyncBluetoothListener for Bluetooth { + fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + match response { + 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(promise_cx, &existing_device.get()); + } + let ad_data = BluetoothAdvertisingData::new(&self.global(), + device.appearance, + device.tx_power, + device.rssi); + let bt_device = BluetoothDevice::new(&self.global(), + DOMString::from(device.id.clone()), + device.name.map(DOMString::from), + &ad_data, + &self); + device_instance_map.insert(device.id, MutHeap::new(&bt_device)); + promise.resolve_native(promise_cx, &bt_device); + }, + _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + } } } |