aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/atoms/static_atoms.txt1
-rw-r--r--components/bluetooth/lib.rs23
-rw-r--r--components/bluetooth_traits/lib.rs3
-rw-r--r--components/script/dom/bluetooth.rs229
-rw-r--r--components/script/dom/bluetoothdevice.rs24
-rw-r--r--components/script/dom/bluetoothpermissionresult.rs128
-rw-r--r--components/script/dom/bluetoothremotegattcharacteristic.rs11
-rw-r--r--components/script/dom/bluetoothremotegattdescriptor.rs5
-rw-r--r--components/script/dom/bluetoothremotegattserver.rs6
-rw-r--r--components/script/dom/bluetoothremotegattservice.rs15
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/permissions.rs43
-rw-r--r--components/script/dom/webidls/BluetoothDevice.webidl2
-rw-r--r--components/script/dom/webidls/BluetoothPermissionResult.webidl31
-rw-r--r--tests/html/bluetooth-permission.html68
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json2
-rw-r--r--tests/wpt/mozilla/tests/mozilla/bluetooth/connect/device-goes-out-of-range.html3
17 files changed, 550 insertions, 45 deletions
diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt
index 8e28f90e04e..df9cd426984 100644
--- a/components/atoms/static_atoms.txt
+++ b/components/atoms/static_atoms.txt
@@ -65,3 +65,4 @@ characteristicvaluechanged
fullscreenchange
fullscreenerror
gattserverdisconnected
+onchange
diff --git a/components/bluetooth/lib.rs b/components/bluetooth/lib.rs
index a22e2ba6c91..a5f5f5837d7 100644
--- a/components/bluetooth/lib.rs
+++ b/components/bluetooth/lib.rs
@@ -256,6 +256,9 @@ impl BluetoothManager {
BluetoothRequest::GetAvailability(sender) => {
let _ = sender.send(self.get_availability());
},
+ BluetoothRequest::MatchesFilter(id, filters, sender) => {
+ let _ = sender.send(self.device_matches_filter(&id, &filters));
+ },
BluetoothRequest::Exit => {
break
},
@@ -425,6 +428,17 @@ impl BluetoothManager {
self.cached_devices.contains_key(device_id) && self.address_to_id.values().any(|v| v == device_id)
}
+ fn device_matches_filter(&mut self,
+ device_id: &str,
+ filters: &BluetoothScanfilterSequence)
+ -> BluetoothResult<bool> {
+ let mut adapter = try!(self.get_adapter());
+ match self.get_device(&mut adapter, device_id) {
+ Some(ref device) => Ok(matches_filters(device, filters)),
+ None => Ok(false),
+ }
+ }
+
// Service
fn get_and_cache_gatt_services(&mut self,
@@ -561,6 +575,9 @@ impl BluetoothManager {
-> BluetoothResponseResult {
// Step 6.
let mut adapter = try!(self.get_adapter());
+
+ // Step 7.
+ // Note: There are no requiredServiceUUIDS, we scan for all devices.
if let Ok(ref session) = adapter.create_discovery_session() {
if session.start_discovery().is_ok() {
if !is_mock_adapter(&adapter) {
@@ -570,8 +587,6 @@ impl BluetoothManager {
let _ = session.stop_discovery();
}
- // Step 7.
- // Note: There are no requiredServiceUUIDS, we scan for all devices.
let mut matched_devices = self.get_and_cache_devices(&mut adapter);
// Step 8.
@@ -582,8 +597,6 @@ impl BluetoothManager {
}
// Step 9.
- // TODO: After the permission API implementation
- // https://w3c.github.io/permissions/#prompt-the-user-to-choose
if let Some(address) = self.select_device(matched_devices, &adapter) {
let device_id = match self.address_to_id.get(&address) {
Some(id) => id.clone(),
@@ -602,7 +615,7 @@ impl BluetoothManager {
return Ok(BluetoothResponse::RequestDevice(message));
}
}
- // TODO: Step 10 - 11: Implement the permission API.
+ // Step 10.
return Err(BluetoothError::NotFound);
// Step 12: Missing, because it is optional.
}
diff --git a/components/bluetooth_traits/lib.rs b/components/bluetooth_traits/lib.rs
index 85421990f71..e54d256fe0c 100644
--- a/components/bluetooth_traits/lib.rs
+++ b/components/bluetooth_traits/lib.rs
@@ -12,7 +12,7 @@ pub mod blocklist;
pub mod scanfilter;
use ipc_channel::ipc::IpcSender;
-use scanfilter::RequestDeviceoptions;
+use scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions};
#[derive(Deserialize, Serialize)]
pub enum BluetoothError {
@@ -92,6 +92,7 @@ pub enum BluetoothRequest {
SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>),
IsRepresentedDeviceNull(String, IpcSender<bool>),
GetAvailability(IpcSender<BluetoothResponseResult>),
+ MatchesFilter(String, BluetoothScanfilterSequence, IpcSender<BluetoothResult<bool>>),
Test(String, IpcSender<BluetoothResult<()>>),
Exit,
}
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();
+ }
+ }
+}
diff --git a/components/script/dom/bluetoothdevice.rs b/components/script/dom/bluetoothdevice.rs
index cd3d3dc07fa..16892f36013 100644
--- a/components/script/dom/bluetoothdevice.rs
+++ b/components/script/dom/bluetoothdevice.rs
@@ -15,7 +15,7 @@ use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableJS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
-use dom::bluetooth::{AsyncBluetoothListener, Bluetooth, response_async};
+use dom::bluetooth::{allowed_devices_contains_id, AsyncBluetoothListener, Bluetooth, response_async};
use dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
use dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic;
use dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor;
@@ -74,6 +74,12 @@ impl BluetoothDevice {
BluetoothDeviceBinding::Wrap)
}
+ pub fn get_gatt(&self) -> Root<BluetoothRemoteGATTServer> {
+ self.gatt.or_init(|| {
+ BluetoothRemoteGATTServer::new(&self.global(), self)
+ })
+ }
+
fn get_context(&self) -> Root<Bluetooth> {
Root::from_ref(&self.context)
}
@@ -157,7 +163,7 @@ impl BluetoothDevice {
#[allow(unrooted_must_root)]
pub fn clean_up_disconnected_device(&self) {
// Step 1.
- self.Gatt().set_connected(false);
+ self.get_gatt().set_connected(false);
// TODO: Step 2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
@@ -193,7 +199,7 @@ impl BluetoothDevice {
for (id, device) in context.get_device_map().borrow().iter() {
// Step 2.1 - 2.2.
if id == &self.Id().to_string() {
- if device.Gatt().Connected() {
+ if device.get_gatt().Connected() {
return Ok(());
}
// TODO: Step 2.3: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
@@ -220,11 +226,13 @@ impl BluetoothDeviceMethods for BluetoothDevice {
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt
- fn Gatt(&self) -> Root<BluetoothRemoteGATTServer> {
- // TODO: Step 1 - 2: Implement the Permission API.
- self.gatt.or_init(|| {
- BluetoothRemoteGATTServer::new(&self.global(), self)
- })
+ fn GetGatt(&self) -> Option<Root<BluetoothRemoteGATTServer>> {
+ // Step 1.
+ if allowed_devices_contains_id(self.id.clone()) && !self.is_represented_device_null() {
+ return Some(self.get_gatt())
+ }
+ // Step 2.
+ None
}
#[allow(unrooted_must_root)]
diff --git a/components/script/dom/bluetoothpermissionresult.rs b/components/script/dom/bluetoothpermissionresult.rs
new file mode 100644
index 00000000000..75e7b43e17e
--- /dev/null
+++ b/components/script/dom/bluetoothpermissionresult.rs
@@ -0,0 +1,128 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
+use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::{self, BluetoothPermissionResultMethods};
+use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::AllowedBluetoothDevice;
+use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods;
+use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState};
+use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusBinding::PermissionStatusMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
+use dom::bindings::codegen::UnionTypes::StringOrStringSequence;
+use dom::bindings::error::Error;
+use dom::bindings::js::{JS, Root};
+use dom::bindings::reflector::{DomObject, reflect_dom_object};
+use dom::bindings::str::DOMString;
+use dom::bluetooth::{add_new_allowed_device, AsyncBluetoothListener, Bluetooth};
+use dom::bluetoothdevice::BluetoothDevice;
+use dom::globalscope::GlobalScope;
+use dom::permissionstatus::PermissionStatus;
+use dom::promise::Promise;
+use ipc_channel::ipc::IpcSender;
+use js::jsapi::JSContext;
+use std::rc::Rc;
+
+// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
+#[dom_struct]
+pub struct BluetoothPermissionResult {
+ status: PermissionStatus,
+ devices: DOMRefCell<Vec<JS<BluetoothDevice>>>,
+}
+
+impl BluetoothPermissionResult {
+ #[allow(unrooted_must_root)]
+ pub 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 fn new(global: &GlobalScope, status: &PermissionStatus) -> Root<BluetoothPermissionResult> {
+ reflect_dom_object(box BluetoothPermissionResult::new_inherited(status),
+ global,
+ BluetoothPermissionResultBinding::Wrap)
+ }
+
+ pub fn get_bluetooth(&self) -> Root<Bluetooth> {
+ self.global().as_window().Navigator().Bluetooth()
+ }
+
+ pub fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
+ self.global().as_window().bluetooth_thread()
+ }
+
+ pub fn get_query(&self) -> PermissionName {
+ self.status.get_query()
+ }
+
+ pub fn set_state(&self, state: PermissionState) {
+ self.status.set_state(state)
+ }
+
+ pub fn get_state(&self) -> PermissionState {
+ self.status.State()
+ }
+
+ #[allow(unrooted_must_root)]
+ pub fn set_devices(&self, devices: Vec<JS<BluetoothDevice>>) {
+ *self.devices.borrow_mut() = devices;
+ }
+}
+
+impl BluetoothPermissionResultMethods for BluetoothPermissionResult {
+ // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothpermissionresult-devices
+ fn Devices(&self) -> Vec<Root<BluetoothDevice>> {
+ let device_vec: Vec<Root<BluetoothDevice>> =
+ self.devices.borrow().iter().map(|d| Root::from_ref(&**d)).collect();
+ device_vec
+ }
+}
+
+impl AsyncBluetoothListener for BluetoothPermissionResult {
+ 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 11, 13 - 14.
+ BluetoothResponse::RequestDevice(device) => {
+ let bluetooth = &self.get_bluetooth();
+ let mut device_instance_map = bluetooth.get_device_map().borrow_mut();
+ if let Some(ref existing_device) = device_instance_map.get(&device.id) {
+ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
+ // Step 3.
+ self.set_devices(vec!(JS::from_ref(&*existing_device)));
+
+ // https://w3c.github.io/permissions/#dom-permissions-request
+ // Step 8.
+ return promise.resolve_native(promise_cx, 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(), JS::from_ref(&bt_device));
+ add_new_allowed_device(
+ AllowedBluetoothDevice {
+ // TODO fix this
+ // allowedServices only relevant if the device store it as an internal slot as well
+ allowedServices: StringOrStringSequence::String(DOMString::from("all".to_owned())),
+ deviceId: DOMString::from(device.id),
+ mayUseGATT: true,
+ }
+ );
+ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
+ // Step 3.
+ self.set_devices(vec!(JS::from_ref(&bt_device)));
+
+ // https://w3c.github.io/permissions/#dom-permissions-request
+ // Step 8.
+ promise.resolve_native(promise_cx, self);
+ },
+ _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())),
+ }
+ }
+}
diff --git a/components/script/dom/bluetoothremotegattcharacteristic.rs b/components/script/dom/bluetoothremotegattcharacteristic.rs
index 636b9dc96f4..81210e4b170 100644
--- a/components/script/dom/bluetoothremotegattcharacteristic.rs
+++ b/components/script/dom/bluetoothremotegattcharacteristic.rs
@@ -7,7 +7,6 @@ use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::
BluetoothCharacteristicPropertiesMethods;
-use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
BluetoothRemoteGATTCharacteristicMethods;
@@ -104,7 +103,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID) -> Rc<Promise> {
get_gatt_children(self, true, BluetoothUUID::descriptor, Some(descriptor), self.get_instance_id(),
- self.Service().Device().Gatt().Connected(), GATTType::Descriptor)
+ self.Service().Device().get_gatt().Connected(), GATTType::Descriptor)
}
#[allow(unrooted_must_root)]
@@ -113,7 +112,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
descriptor: Option<BluetoothDescriptorUUID>)
-> Rc<Promise> {
get_gatt_children(self, false, BluetoothUUID::descriptor, descriptor, self.get_instance_id(),
- self.Service().Device().Gatt().Connected(), GATTType::Descriptor)
+ self.Service().Device().get_gatt().Connected(), GATTType::Descriptor)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value
@@ -134,7 +133,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
}
// Step 2.
- if !self.Service().Device().Gatt().Connected() {
+ if !self.Service().Device().get_gatt().Connected() {
p.reject_error(p_cx, Network);
return p;
}
@@ -174,7 +173,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
}
// Step 4.
- if !self.Service().Device().Gatt().Connected() {
+ if !self.Service().Device().get_gatt().Connected() {
p.reject_error(p_cx, Network);
return p;
}
@@ -210,7 +209,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
}
// Step 2.
- if !self.Service().Device().Gatt().Connected() {
+ if !self.Service().Device().get_gatt().Connected() {
p.reject_error(p_cx, Network);
return p;
}
diff --git a/components/script/dom/bluetoothremotegattdescriptor.rs b/components/script/dom/bluetoothremotegattdescriptor.rs
index 5290c096d3a..c0ddd6dabe5 100644
--- a/components/script/dom/bluetoothremotegattdescriptor.rs
+++ b/components/script/dom/bluetoothremotegattdescriptor.rs
@@ -5,7 +5,6 @@
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
BluetoothRemoteGATTCharacteristicMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding;
@@ -98,7 +97,7 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor {
}
// Step 2.
- if !self.Characteristic().Service().Device().Gatt().Connected() {
+ if !self.Characteristic().Service().Device().get_gatt().Connected() {
p.reject_error(p_cx, Network);
return p;
}
@@ -131,7 +130,7 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor {
}
// Step 4.
- if !self.Characteristic().Service().Device().Gatt().Connected() {
+ if !self.Characteristic().Service().Device().get_gatt().Connected() {
p.reject_error(p_cx, Network);
return p;
}
diff --git a/components/script/dom/bluetoothremotegattserver.rs b/components/script/dom/bluetoothremotegattserver.rs
index 11e0528f5be..fdc18fc7d0d 100644
--- a/components/script/dom/bluetoothremotegattserver.rs
+++ b/components/script/dom/bluetoothremotegattserver.rs
@@ -103,16 +103,16 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer {
#[allow(unrooted_must_root)]
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> {
- // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
+ // Step 1. is in get_gatt_children
// Step 2.
get_gatt_children(self, true, BluetoothUUID::service, Some(service), String::from(self.Device().Id()),
- self.Device().Gatt().Connected(), GATTType::PrimaryService)
+ self.Device().get_gatt().Connected(), GATTType::PrimaryService)
}
#[allow(unrooted_must_root)]
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> {
- // TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
+ // Step 1. is in get_gatt_children
// Step 2.
get_gatt_children(self, false, BluetoothUUID::service, service, String::from(self.Device().Id()),
self.Connected(), GATTType::PrimaryService)
diff --git a/components/script/dom/bluetoothremotegattservice.rs b/components/script/dom/bluetoothremotegattservice.rs
index da06678da0b..9b0ce5a3a3d 100644
--- a/components/script/dom/bluetoothremotegattservice.rs
+++ b/components/script/dom/bluetoothremotegattservice.rs
@@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use bluetooth_traits::{BluetoothResponse, GATTType};
-use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding;
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
@@ -87,7 +86,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
characteristic: BluetoothCharacteristicUUID)
-> Rc<Promise> {
get_gatt_children(self, true, BluetoothUUID::characteristic, Some(characteristic), self.get_instance_id(),
- self.Device().Gatt().Connected(), GATTType::Characteristic)
+ self.Device().get_gatt().Connected(), GATTType::Characteristic)
}
#[allow(unrooted_must_root)]
@@ -96,7 +95,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
characteristic: Option<BluetoothCharacteristicUUID>)
-> Rc<Promise> {
get_gatt_children(self, false, BluetoothUUID::characteristic, characteristic, self.get_instance_id(),
- self.Device().Gatt().Connected(), GATTType::Characteristic)
+ self.Device().get_gatt().Connected(), GATTType::Characteristic)
}
#[allow(unrooted_must_root)]
@@ -105,7 +104,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
service: BluetoothServiceUUID)
-> Rc<Promise> {
get_gatt_children(self, false, BluetoothUUID::service, Some(service), self.get_instance_id(),
- self.Device().Gatt().Connected(), GATTType::IncludedService)
+ self.Device().get_gatt().Connected(), GATTType::IncludedService)
}
@@ -115,7 +114,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
service: Option<BluetoothServiceUUID>)
-> Rc<Promise> {
get_gatt_children(self, false, BluetoothUUID::service, service, self.get_instance_id(),
- self.Device().Gatt().Connected(), GATTType::IncludedService)
+ self.Device().get_gatt().Connected(), GATTType::IncludedService)
}
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceadded
@@ -151,12 +150,12 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTService {
// Step 7.
BluetoothResponse::GetIncludedServices(services_vec, single) => {
if single {
- promise.resolve_native(promise_cx, &device.get_or_create_service(&services_vec[0], &device.Gatt()));
- return;
+ return promise.resolve_native(promise_cx,
+ &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.Gatt());
+ let bt_service = device.get_or_create_service(&service, &device.get_gatt());
services.push(bt_service);
}
promise.resolve_native(promise_cx, &services);
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index b1541a225b7..4b3c0c02cbb 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -223,6 +223,7 @@ pub mod bluetooth;
pub mod bluetoothadvertisingevent;
pub mod bluetoothcharacteristicproperties;
pub mod bluetoothdevice;
+pub mod bluetoothpermissionresult;
pub mod bluetoothremotegattcharacteristic;
pub mod bluetoothremotegattdescriptor;
pub mod bluetoothremotegattserver;
diff --git a/components/script/dom/permissions.rs b/components/script/dom/permissions.rs
index ebece7374a6..104376e2e0c 100644
--- a/components/script/dom/permissions.rs
+++ b/components/script/dom/permissions.rs
@@ -7,6 +7,8 @@ use dom::bindings::codegen::Bindings::PermissionsBinding::{self, PermissionsMeth
use dom::bindings::error::Error;
use dom::bindings::js::Root;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
+use dom::bluetooth::Bluetooth;
+use dom::bluetoothpermissionresult::BluetoothPermissionResult;
use dom::globalscope::GlobalScope;
use dom::permissionstatus::PermissionStatus;
use dom::promise::Promise;
@@ -79,6 +81,20 @@ impl PermissionsMethods for Permissions {
// Step 2.
match root_desc.name {
+ PermissionName::Bluetooth => {
+ let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc) {
+ Ok(descriptor) => descriptor,
+ Err(error) => {
+ p.reject_error(cx, error);
+ return p;
+ },
+ };
+ // Step 5.
+ let result = BluetoothPermissionResult::new(&self.global(), &status);
+ // Step 6.
+ Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result);
+ // Step 7. in permission_query
+ },
_ => {
// Step 6.
Permissions::permission_query(cx, &p, &root_desc, &status);
@@ -113,6 +129,20 @@ impl PermissionsMethods for Permissions {
// Step 2.
match root_desc.name {
+ PermissionName::Bluetooth => {
+ let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc) {
+ Ok(descriptor) => descriptor,
+ Err(error) => {
+ p.reject_error(cx, error);
+ return p;
+ },
+ };
+ // Step 5.
+ let result = BluetoothPermissionResult::new(&self.global(), &status);
+ // Step 6.
+ Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result);
+ // Step 7 - 8. in permission_request
+ },
_ => {
// Step 6.
Permissions::permission_request(cx, &p, &root_desc, &status);
@@ -145,6 +175,19 @@ impl PermissionsMethods for Permissions {
// Step 2.
match root_desc.name {
+ PermissionName::Bluetooth => {
+ let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc) {
+ Ok(descriptor) => descriptor,
+ Err(error) => {
+ let p = Promise::new(&self.global());
+ p.reject_error(cx, error);
+ return p;
+ },
+ };
+ let result = BluetoothPermissionResult::new(&self.global(), &status);
+ // Step 3 - 4. in permission_revoke
+ Bluetooth::permission_revoke(&bluetooth_desc, &result);
+ },
_ => {
Permissions::permission_revoke(&root_desc, &status);
},
diff --git a/components/script/dom/webidls/BluetoothDevice.webidl b/components/script/dom/webidls/BluetoothDevice.webidl
index eac8b533392..1eb9f495ec0 100644
--- a/components/script/dom/webidls/BluetoothDevice.webidl
+++ b/components/script/dom/webidls/BluetoothDevice.webidl
@@ -8,7 +8,7 @@
interface BluetoothDevice : EventTarget {
readonly attribute DOMString id;
readonly attribute DOMString? name;
- readonly attribute BluetoothRemoteGATTServer gatt;
+ readonly attribute BluetoothRemoteGATTServer? gatt;
Promise<void> watchAdvertisements();
void unwatchAdvertisements();
diff --git a/components/script/dom/webidls/BluetoothPermissionResult.webidl b/components/script/dom/webidls/BluetoothPermissionResult.webidl
new file mode 100644
index 00000000000..47d19999001
--- /dev/null
+++ b/components/script/dom/webidls/BluetoothPermissionResult.webidl
@@ -0,0 +1,31 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult
+
+dictionary BluetoothPermissionDescriptor : PermissionDescriptor {
+ DOMString deviceId;
+ // These match RequestDeviceOptions.
+ sequence<BluetoothLEScanFilterInit> filters;
+ sequence<BluetoothServiceUUID> optionalServices/* = []*/;
+ boolean acceptAllDevices = false;
+};
+
+dictionary AllowedBluetoothDevice {
+ required DOMString deviceId;
+ required boolean mayUseGATT;
+ // An allowedServices of "all" means all services are allowed.
+ required (DOMString or sequence<UUID>) allowedServices;
+};
+
+dictionary BluetoothPermissionData {
+ required sequence<AllowedBluetoothDevice> allowedDevices/* = []*/;
+};
+
+// [Pref="dom.bluetooth.enabled"]
+interface BluetoothPermissionResult : PermissionStatus {
+ // attribute FrozenArray<BluetoothDevice> devices;
+ // Workaround until FrozenArray get implemented.
+ sequence<BluetoothDevice> devices();
+};
diff --git a/tests/html/bluetooth-permission.html b/tests/html/bluetooth-permission.html
new file mode 100644
index 00000000000..a53448c98b9
--- /dev/null
+++ b/tests/html/bluetooth-permission.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<title>Bluetooth Permission Test</title>
+<body>
+ <button type="button" onclick="onRequestButtonClick()">request</button>
+ <button type="button" onclick="onQueryButtonClick()">query</button>
+ <pre id="log"></pre>
+ <script>
+ window.testRunner.setBluetoothMockDataSet('HeartRateAdapter');
+
+ function onRequestButtonClick() {
+ clear();
+ window.navigator.permissions.request({
+ name: 'bluetooth',
+ filters: [{
+ services: ['heart_rate'],
+ }],
+ })
+ .then(r => {
+ log("Result is instance of PermissionStatus: " + (r instanceof PermissionStatus));
+ log("Result is instance of BluetoothPermissionResult subclass: " + (r instanceof BluetoothPermissionResult));
+ log("State of result: " + r.state);
+ let device = r.devices()[0];
+ if (device) {
+ log("Result contains device: " + device.name);
+ sessionStorage.lastDevice = device.id;
+ log("Device id stored: " + device.id);
+ } else {
+ log("No device found!");
+ }
+ })
+ .catch(err => log(err));
+ }
+
+ function onQueryButtonClick() {
+ clear();
+ if (!sessionStorage.lastDevice)
+ return log("No stored device id!");
+ window.navigator.permissions.query({
+ name: "bluetooth",
+ deviceId: sessionStorage.lastDevice,
+ })
+ .then(r => {
+ log("Result is instance of PermissionStatus: " + (r instanceof PermissionStatus));
+ log("Result is instance of BluetoothPermissionResult subclass: " + (r instanceof BluetoothPermissionResult));
+ log("State of result: " + r.state);
+ log("Stored Device id: " + sessionStorage.lastDevice);
+ let device = r.devices()[0];
+ if (device) {
+ log("Result contains device: " + device.name);
+ log("Device id: " + device.id);
+ } else {
+ log("No device found!");
+ }
+ })
+ .catch(err => log(err));
+ }
+
+ function log(line) {
+ document.getElementById("log").textContent += line + '\n';
+ }
+
+ function clear() {
+ document.getElementById("log").textContent = "";
+ }
+ </script>
+</body>
+</html>
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 86ef8cfc0ff..c73fdd4a390 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -24367,7 +24367,7 @@
"testharness"
],
"mozilla/bluetooth/connect/device-goes-out-of-range.html": [
- "9e5dc423f92c3f73273d221e7fb7b8d905716db3",
+ "3ade30929f621b7c8c93b87e5392309729d68e42",
"testharness"
],
"mozilla/bluetooth/connect/get-same-gatt-server.html": [
diff --git a/tests/wpt/mozilla/tests/mozilla/bluetooth/connect/device-goes-out-of-range.html b/tests/wpt/mozilla/tests/mozilla/bluetooth/connect/device-goes-out-of-range.html
index f189a9971f2..cb20839271d 100644
--- a/tests/wpt/mozilla/tests/mozilla/bluetooth/connect/device-goes-out-of-range.html
+++ b/tests/wpt/mozilla/tests/mozilla/bluetooth/connect/device-goes-out-of-range.html
@@ -10,8 +10,9 @@ promise_test(t => {
filters: [{services: [heart_rate.name]}]
})
.then(device => {
+ var gatt_server = device.gatt;
window.testRunner.setBluetoothMockDataSet(adapter_type.empty);
- return promise_rejects(t, 'NetworkError', device.gatt.connect());
+ return promise_rejects(t, 'NetworkError', gatt_server.connect());
});
}, 'Device goes out of range. Reject with NetworkError.');
</script>