aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/bluetooth.rs
diff options
context:
space:
mode:
authorZakor Gyula <zakorgy@inf.u-szeged.hu>2017-01-30 11:30:06 +0100
committerAttila Dusnoki <dati91@gmail.com>2017-02-13 14:58:06 +0100
commit5287cd3beaebd8a7fe778e71e9693160cee38434 (patch)
tree3e1ee818d1f163f0d537f8d6711a63066b0274c9 /components/script/dom/bluetooth.rs
parentf3ddee5dbcecd51c35b7d40c7fc6d767cbfafb27 (diff)
downloadservo-5287cd3beaebd8a7fe778e71e9693160cee38434.tar.gz
servo-5287cd3beaebd8a7fe778e71e9693160cee38434.zip
Bluetooth Permission API integration
Diffstat (limited to 'components/script/dom/bluetooth.rs')
-rw-r--r--components/script/dom/bluetooth.rs229
1 files changed, 221 insertions, 8 deletions
diff --git a/components/script/dom/bluetooth.rs b/components/script/dom/bluetooth.rs
index 82c39782133..b932046acc3 100644
--- a/components/script/dom/bluetooth.rs
+++ b/components/script/dom/bluetooth.rs
@@ -11,8 +11,14 @@ use core::clone::Clone;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothDataFilterInit, BluetoothLEScanFilterInit};
use dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
+use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::AllowedBluetoothDevice;
+use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionData;
+use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionDescriptor;
+use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerBinding::
+ BluetoothRemoteGATTServerMethods;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
-use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
+use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState;
+use dom::bindings::codegen::UnionTypes::{StringOrStringSequence, StringOrUnsignedLong};
use dom::bindings::error::Error::{self, Network, Security, Type};
use dom::bindings::error::Fallible;
use dom::bindings::js::{JS, Root};
@@ -20,14 +26,19 @@ use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bluetoothdevice::BluetoothDevice;
+use dom::bluetoothpermissionresult::BluetoothPermissionResult;
use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID};
use dom::eventtarget::EventTarget;
use dom::globalscope::GlobalScope;
+use dom::permissions::{get_descriptor_permission_state, PermissionAlgorithm};
use dom::promise::Promise;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
-use js::jsapi::{JSAutoCompartment, JSContext};
+use js::conversions::ConversionResult;
+use js::jsapi::{JSAutoCompartment, JSContext, JSObject};
+use js::jsval::{ObjectValue, UndefinedValue};
use script_thread::Runnable;
+use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
@@ -46,6 +57,48 @@ const SERVICE_DATA_ERROR: &'static str = "'serviceData', if present, must be non
const SERVICE_ERROR: &'static str = "'services', if present, must contain at least one service.";
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.";
+const BT_DESC_CONVERSION_ERROR: &'static str = "Can't convert to an IDL value of type BluetoothPermissionDescriptor";
+
+
+thread_local!(pub static EXTRA_PERMISSION_DATA: RefCell<BluetoothPermissionData> =
+ RefCell::new(BluetoothPermissionData { allowedDevices: Vec::new() }));
+
+pub fn add_new_allowed_device(allowed_device: AllowedBluetoothDevice) {
+ EXTRA_PERMISSION_DATA.with(|epdata| {
+ epdata.borrow_mut().allowedDevices.push(allowed_device);
+ });
+}
+
+fn get_allowed_devices() -> Vec<AllowedBluetoothDevice> {
+ EXTRA_PERMISSION_DATA.with(|epdata| {
+ epdata.borrow().allowedDevices.clone()
+ })
+}
+
+pub fn allowed_devices_contains_id(id: DOMString) -> bool {
+ EXTRA_PERMISSION_DATA.with(|epdata| {
+ epdata.borrow_mut().allowedDevices.iter().any(|d| d.deviceId == id)
+ })
+}
+
+impl Clone for StringOrStringSequence {
+ fn clone(&self) -> StringOrStringSequence {
+ match self {
+ &StringOrStringSequence::String(ref s) => StringOrStringSequence::String(s.clone()),
+ &StringOrStringSequence::StringSequence(ref v) => StringOrStringSequence::StringSequence(v.clone()),
+ }
+ }
+}
+
+impl Clone for AllowedBluetoothDevice {
+ fn clone(&self) -> AllowedBluetoothDevice {
+ AllowedBluetoothDevice {
+ deviceId: self.deviceId.clone(),
+ mayUseGATT: self.mayUseGATT,
+ allowedServices: self.allowedServices.clone(),
+ }
+ }
+}
struct BluetoothContext<T: AsyncBluetoothListener + DomObject> {
promise: Option<TrustedPromise>,
@@ -107,7 +160,8 @@ impl Bluetooth {
fn request_bluetooth_devices(&self,
p: &Rc<Promise>,
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
- optional_services: &Option<Vec<BluetoothServiceUUID>>) {
+ optional_services: &Option<Vec<BluetoothServiceUUID>>,
+ sender: IpcSender<BluetoothResponseResult>) {
// TODO: Step 1: Triggered by user activation.
// Step 2.2: There are no requiredServiceUUIDS, we scan for all devices.
@@ -161,11 +215,15 @@ impl Bluetooth {
let option = RequestDeviceoptions::new(BluetoothScanfilterSequence::new(uuid_filters),
ServiceUUIDSequence::new(optional_services_uuids));
- // TODO: Step 3 - 5: Implement the permission API.
+ // Step 3 - 5
+ // FIXME The following call will create a popup, which will mess up the testing...
+ // Maybe create a call to the lower level to check if we are testing or not
+ // if let PermissionState::Denied = get_descriptor_permission_state(PermissionName::Bluetooth, None) {
+ // return p.reject_error(p.global().get_cx(), Error::NotFound);
+ // }
// Note: Steps 6 - 8 are implemented in
// components/net/bluetooth_thread.rs in request_device function.
- let sender = response_async(p, self);
self.get_bluetooth_thread().send(BluetoothRequest::RequestDevice(option, sender)).unwrap();
}
}
@@ -438,7 +496,8 @@ impl BluetoothMethods for Bluetooth {
}
// Step 2.
- self.request_bluetooth_devices(&p, &option.filters, &option.optionalServices);
+ 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.
return p;
}
@@ -463,7 +522,7 @@ impl AsyncBluetoothListener for Bluetooth {
fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) {
match response {
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
- // Step 13 - 14.
+ // 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()) {
@@ -473,7 +532,16 @@ impl AsyncBluetoothListener for Bluetooth {
DOMString::from(device.id.clone()),
device.name.map(DOMString::from),
&self);
- device_instance_map.insert(device.id, JS::from_ref(&bt_device));
+ device_instance_map.insert(device.id.clone(), JS::from_ref(&bt_device));
+ add_new_allowed_device(
+ AllowedBluetoothDevice {
+ // TODO fix this
+ // allowedServices only relevant if the device store it as an inter slot as well
+ allowedServices: StringOrStringSequence::String(DOMString::from("all".to_owned())),
+ deviceId: DOMString::from(device.id),
+ mayUseGATT: true,
+ }
+ );
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
// Step 5.
promise.resolve_native(promise_cx, &bt_device);
@@ -487,3 +555,148 @@ impl AsyncBluetoothListener for Bluetooth {
}
}
}
+
+impl PermissionAlgorithm for Bluetooth {
+ type Descriptor = BluetoothPermissionDescriptor;
+ type Status = BluetoothPermissionResult;
+
+ #[allow(unsafe_code)]
+ fn create_descriptor(cx: *mut JSContext,
+ permission_descriptor_obj: *mut JSObject)
+ -> Result<BluetoothPermissionDescriptor, Error> {
+ rooted!(in(cx) let mut property = UndefinedValue());
+ property.handle_mut().set(ObjectValue(permission_descriptor_obj));
+ unsafe {
+ 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))),
+ }
+ }
+ }
+
+ #[allow(unrooted_must_root)]
+ // https://webbluetoothcg.github.io/web-bluetooth/#query-the-bluetooth-permission
+ fn permission_query(cx: *mut JSContext, promise: &Rc<Promise>,
+ descriptor: &BluetoothPermissionDescriptor,
+ status: &BluetoothPermissionResult) {
+ // Step 1.
+ // TODO: `environment settings object` is not implemented in Servo yet.
+
+ // Step 2.
+ status.set_state(get_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(cx, status);
+ }
+
+ // Step 4.
+ let mut matching_devices: Vec<JS<BluetoothDevice>> = Vec::new();
+
+ // TODO: Step 5: Create a map between the current setting object and BluetoothPermissionData
+ // extra permission data, which replaces the exisitng EXTRA_PERMISSION_DATA global variable.
+ // For this also use the extra permission data constraints from the specification:
+ // https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothpermissiondata
+
+ // Step 5.
+ let allowed_devices = get_allowed_devices();
+
+ let bluetooth = status.get_bluetooth();
+ let device_map = bluetooth.get_device_map().borrow();
+
+ // Step 6.
+ for allowed_device in allowed_devices {
+ // 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());
+ if let Some(ref filters) = descriptor.filters {
+ let mut scan_filters: Vec<BluetoothScanfilter> = Vec::new();
+
+ // NOTE(zakorgy): This canonicalizing step is missing from the specification.
+ // But there is an issue for this: https://github.com/WebBluetoothCG/web-bluetooth/issues/347
+ for filter in filters {
+ match canonicalize_filter(&filter) {
+ Ok(f) => scan_filters.push(f),
+ Err(error) => return promise.reject_error(cx, error),
+ }
+ }
+
+ // Step 6.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) = ipc::channel().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(cx, Error::from(error)),
+ };
+ }
+
+ // Step 6.4.
+ // TODO: Implement this correctly, not just using device ids here.
+ // https://webbluetoothcg.github.io/web-bluetooth/#get-the-bluetoothdevice-representing
+ if let Some(ref device) = device_map.get(&device_id) {
+ matching_devices.push(JS::from_ref(&*device));
+ }
+ }
+
+ // Step 7.
+ status.set_devices(matching_devices);
+
+ // https://w3c.github.io/permissions/#dom-permissions-query
+ // Step 7.
+ promise.resolve_native(cx, status);
+ }
+
+ // NOTE(zakorgy): There is no link for this algorithm until this PR for the spec is pending:
+ // https://github.com/WebBluetoothCG/web-bluetooth/pull/349
+ fn permission_request(cx: *mut JSContext, promise: &Rc<Promise>,
+ descriptor: &BluetoothPermissionDescriptor,
+ status: &BluetoothPermissionResult) {
+ // Step 1.
+ if descriptor.filters.is_some() == descriptor.acceptAllDevices {
+ return promise.reject_error(cx, 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.
+ }
+
+ #[allow(unrooted_must_root)]
+ // https://webbluetoothcg.github.io/web-bluetooth/#revoke-bluetooth-access
+ fn permission_revoke(_descriptor: &BluetoothPermissionDescriptor, status: &BluetoothPermissionResult) {
+ // Step 1.
+ let allowed_devices = 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();
+ }
+ }
+}