aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/bluetooth.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/bluetooth.rs')
-rw-r--r--components/script/dom/bluetooth.rs173
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())),
+ }
}
}