mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
Auto merge of #15314 - szeged:permissions-api, r=jdm
Permissions API and WebBluetooth integration <!-- Please describe your changes on the following line: --> This implements the [Permissions API](https://w3c.github.io/permissions/) spec. Also includes the WebBluetooth related implementation for this. There are some know issues: - [ ] If the descriptor name is invalid [this](https://gist.github.com/dati91/7a6a0a563d90f49ba5a351e48c5b626b#file-permissionstatusbindings-rs-L323) will throw an error, rather that return it and we could handle it. - [x] The [environment settings object](https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object) is not implemented in servo and the spec rely on it. - [x] There is a popup in the implementation which prevent us to add wpt test, we should figure out a way to make it work - [ ] The allowedDevice's allowed_services attribute is not used in our implementation, because we store these in the lower level, not in the dom side. - [ ] We think the bluetooth revoke function will need some more work, but the problem is the spec needs clarifications on that part. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15314) <!-- Reviewable:end -->
This commit is contained in:
commit
a537cf48b1
30 changed files with 1041 additions and 61 deletions
|
@ -65,3 +65,4 @@ characteristicvaluechanged
|
||||||
fullscreenchange
|
fullscreenchange
|
||||||
fullscreenerror
|
fullscreenerror
|
||||||
gattserverdisconnected
|
gattserverdisconnected
|
||||||
|
onchange
|
||||||
|
|
|
@ -256,6 +256,9 @@ impl BluetoothManager {
|
||||||
BluetoothRequest::GetAvailability(sender) => {
|
BluetoothRequest::GetAvailability(sender) => {
|
||||||
let _ = sender.send(self.get_availability());
|
let _ = sender.send(self.get_availability());
|
||||||
},
|
},
|
||||||
|
BluetoothRequest::MatchesFilter(id, filters, sender) => {
|
||||||
|
let _ = sender.send(self.device_matches_filter(&id, &filters));
|
||||||
|
},
|
||||||
BluetoothRequest::Exit => {
|
BluetoothRequest::Exit => {
|
||||||
break
|
break
|
||||||
},
|
},
|
||||||
|
@ -425,6 +428,17 @@ impl BluetoothManager {
|
||||||
self.cached_devices.contains_key(device_id) && self.address_to_id.values().any(|v| v == device_id)
|
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
|
// Service
|
||||||
|
|
||||||
fn get_and_cache_gatt_services(&mut self,
|
fn get_and_cache_gatt_services(&mut self,
|
||||||
|
@ -561,6 +575,9 @@ impl BluetoothManager {
|
||||||
-> BluetoothResponseResult {
|
-> BluetoothResponseResult {
|
||||||
// Step 6.
|
// Step 6.
|
||||||
let mut adapter = try!(self.get_adapter());
|
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 let Ok(ref session) = adapter.create_discovery_session() {
|
||||||
if session.start_discovery().is_ok() {
|
if session.start_discovery().is_ok() {
|
||||||
if !is_mock_adapter(&adapter) {
|
if !is_mock_adapter(&adapter) {
|
||||||
|
@ -570,8 +587,6 @@ impl BluetoothManager {
|
||||||
let _ = session.stop_discovery();
|
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);
|
let mut matched_devices = self.get_and_cache_devices(&mut adapter);
|
||||||
|
|
||||||
// Step 8.
|
// Step 8.
|
||||||
|
@ -582,8 +597,6 @@ impl BluetoothManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 9.
|
// 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) {
|
if let Some(address) = self.select_device(matched_devices, &adapter) {
|
||||||
let device_id = match self.address_to_id.get(&address) {
|
let device_id = match self.address_to_id.get(&address) {
|
||||||
Some(id) => id.clone(),
|
Some(id) => id.clone(),
|
||||||
|
@ -602,7 +615,7 @@ impl BluetoothManager {
|
||||||
return Ok(BluetoothResponse::RequestDevice(message));
|
return Ok(BluetoothResponse::RequestDevice(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Step 10 - 11: Implement the permission API.
|
// Step 10.
|
||||||
return Err(BluetoothError::NotFound);
|
return Err(BluetoothError::NotFound);
|
||||||
// Step 12: Missing, because it is optional.
|
// Step 12: Missing, because it is optional.
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub mod blocklist;
|
||||||
pub mod scanfilter;
|
pub mod scanfilter;
|
||||||
|
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use scanfilter::RequestDeviceoptions;
|
use scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum BluetoothError {
|
pub enum BluetoothError {
|
||||||
|
@ -92,6 +92,7 @@ pub enum BluetoothRequest {
|
||||||
SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>),
|
SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>),
|
||||||
IsRepresentedDeviceNull(String, IpcSender<bool>),
|
IsRepresentedDeviceNull(String, IpcSender<bool>),
|
||||||
GetAvailability(IpcSender<BluetoothResponseResult>),
|
GetAvailability(IpcSender<BluetoothResponseResult>),
|
||||||
|
MatchesFilter(String, BluetoothScanfilterSequence, IpcSender<BluetoothResult<bool>>),
|
||||||
Test(String, IpcSender<BluetoothResult<()>>),
|
Test(String, IpcSender<BluetoothResult<()>>),
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1845,6 +1845,8 @@ class CGImports(CGWrapper):
|
||||||
return type.flatMemberTypes
|
return type.flatMemberTypes
|
||||||
if type.isDictionary():
|
if type.isDictionary():
|
||||||
return [type] + getTypesFromDictionary(type)
|
return [type] + getTypesFromDictionary(type)
|
||||||
|
if type.isSequence():
|
||||||
|
return componentTypes(type.inner)
|
||||||
return [type]
|
return [type]
|
||||||
|
|
||||||
def isImportable(type):
|
def isImportable(type):
|
||||||
|
|
|
@ -11,7 +11,11 @@ use core::clone::Clone;
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothDataFilterInit, BluetoothLEScanFilterInit};
|
use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothDataFilterInit, BluetoothLEScanFilterInit};
|
||||||
use dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
|
use dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions};
|
||||||
|
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::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState};
|
||||||
use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
|
use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong;
|
||||||
use dom::bindings::error::Error::{self, Network, Security, Type};
|
use dom::bindings::error::Error::{self, Network, Security, Type};
|
||||||
use dom::bindings::error::Fallible;
|
use dom::bindings::error::Fallible;
|
||||||
|
@ -20,14 +24,19 @@ use dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||||
use dom::bindings::reflector::{DomObject, reflect_dom_object};
|
use dom::bindings::reflector::{DomObject, reflect_dom_object};
|
||||||
use dom::bindings::str::DOMString;
|
use dom::bindings::str::DOMString;
|
||||||
use dom::bluetoothdevice::BluetoothDevice;
|
use dom::bluetoothdevice::BluetoothDevice;
|
||||||
|
use dom::bluetoothpermissionresult::BluetoothPermissionResult;
|
||||||
use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID};
|
use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
use dom::globalscope::GlobalScope;
|
use dom::globalscope::GlobalScope;
|
||||||
|
use dom::permissions::{get_descriptor_permission_state, PermissionAlgorithm};
|
||||||
use dom::promise::Promise;
|
use dom::promise::Promise;
|
||||||
use ipc_channel::ipc::{self, IpcSender};
|
use ipc_channel::ipc::{self, IpcSender};
|
||||||
use ipc_channel::router::ROUTER;
|
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 script_thread::Runnable;
|
||||||
|
use std::cell::Ref;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -46,6 +55,38 @@ 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 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.
|
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.";
|
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";
|
||||||
|
|
||||||
|
#[derive(HeapSizeOf, JSTraceable)]
|
||||||
|
pub struct AllowedBluetoothDevice {
|
||||||
|
pub deviceId: DOMString,
|
||||||
|
pub mayUseGATT: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(HeapSizeOf, JSTraceable)]
|
||||||
|
pub struct BluetoothExtraPermissionData {
|
||||||
|
allowed_devices: DOMRefCell<Vec<AllowedBluetoothDevice>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BluetoothExtraPermissionData {
|
||||||
|
pub fn new() -> BluetoothExtraPermissionData {
|
||||||
|
BluetoothExtraPermissionData {
|
||||||
|
allowed_devices: DOMRefCell::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_new_allowed_device(&self, allowed_device: AllowedBluetoothDevice) {
|
||||||
|
self.allowed_devices.borrow_mut().push(allowed_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_allowed_devices(&self) -> Ref<Vec<AllowedBluetoothDevice>> {
|
||||||
|
self.allowed_devices.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allowed_devices_contains_id(&self, id: DOMString) -> bool {
|
||||||
|
self.allowed_devices.borrow().iter().any(|d| d.deviceId == id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct BluetoothContext<T: AsyncBluetoothListener + DomObject> {
|
struct BluetoothContext<T: AsyncBluetoothListener + DomObject> {
|
||||||
promise: Option<TrustedPromise>,
|
promise: Option<TrustedPromise>,
|
||||||
|
@ -107,7 +148,8 @@ impl Bluetooth {
|
||||||
fn request_bluetooth_devices(&self,
|
fn request_bluetooth_devices(&self,
|
||||||
p: &Rc<Promise>,
|
p: &Rc<Promise>,
|
||||||
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
|
filters: &Option<Vec<BluetoothLEScanFilterInit>>,
|
||||||
optional_services: &Option<Vec<BluetoothServiceUUID>>) {
|
optional_services: &Option<Vec<BluetoothServiceUUID>>,
|
||||||
|
sender: IpcSender<BluetoothResponseResult>) {
|
||||||
// TODO: Step 1: Triggered by user activation.
|
// TODO: Step 1: Triggered by user activation.
|
||||||
|
|
||||||
// Step 2.2: There are no requiredServiceUUIDS, we scan for all devices.
|
// Step 2.2: There are no requiredServiceUUIDS, we scan for all devices.
|
||||||
|
@ -161,11 +203,13 @@ impl Bluetooth {
|
||||||
let option = RequestDeviceoptions::new(BluetoothScanfilterSequence::new(uuid_filters),
|
let option = RequestDeviceoptions::new(BluetoothScanfilterSequence::new(uuid_filters),
|
||||||
ServiceUUIDSequence::new(optional_services_uuids));
|
ServiceUUIDSequence::new(optional_services_uuids));
|
||||||
|
|
||||||
// TODO: Step 3 - 5: Implement the permission API.
|
// Step 4 - 5.
|
||||||
|
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
|
// Note: Step 3, 6 - 8 are implemented in
|
||||||
// components/net/bluetooth_thread.rs in request_device function.
|
// 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();
|
self.get_bluetooth_thread().send(BluetoothRequest::RequestDevice(option, sender)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,7 +482,8 @@ impl BluetoothMethods for Bluetooth {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2.
|
// 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.
|
//Note: Step 3 - 4. in response function, Step 5. in handle_response function.
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -463,7 +508,7 @@ impl AsyncBluetoothListener for Bluetooth {
|
||||||
fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) {
|
fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) {
|
||||||
match response {
|
match response {
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
|
// https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices
|
||||||
// Step 13 - 14.
|
// Step 11, 13 - 14.
|
||||||
BluetoothResponse::RequestDevice(device) => {
|
BluetoothResponse::RequestDevice(device) => {
|
||||||
let mut device_instance_map = self.device_instance_map.borrow_mut();
|
let mut device_instance_map = self.device_instance_map.borrow_mut();
|
||||||
if let Some(existing_device) = device_instance_map.get(&device.id.clone()) {
|
if let Some(existing_device) = device_instance_map.get(&device.id.clone()) {
|
||||||
|
@ -473,7 +518,14 @@ impl AsyncBluetoothListener for Bluetooth {
|
||||||
DOMString::from(device.id.clone()),
|
DOMString::from(device.id.clone()),
|
||||||
device.name.map(DOMString::from),
|
device.name.map(DOMString::from),
|
||||||
&self);
|
&self);
|
||||||
device_instance_map.insert(device.id, JS::from_ref(&bt_device));
|
device_instance_map.insert(device.id.clone(), JS::from_ref(&bt_device));
|
||||||
|
|
||||||
|
self.global().as_window().bluetooth_extra_permission_data().add_new_allowed_device(
|
||||||
|
AllowedBluetoothDevice {
|
||||||
|
deviceId: DOMString::from(device.id),
|
||||||
|
mayUseGATT: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
|
||||||
// Step 5.
|
// Step 5.
|
||||||
promise.resolve_native(promise_cx, &bt_device);
|
promise.resolve_native(promise_cx, &bt_device);
|
||||||
|
@ -487,3 +539,143 @@ 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))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: We are not using the `global` variable.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
rooted_vec!(let mut matching_devices);
|
||||||
|
|
||||||
|
// Step 5.
|
||||||
|
let global = status.global();
|
||||||
|
let allowed_devices = global.as_window().bluetooth_extra_permission_data().get_allowed_devices();
|
||||||
|
|
||||||
|
let bluetooth = status.get_bluetooth();
|
||||||
|
let device_map = bluetooth.get_device_map().borrow();
|
||||||
|
|
||||||
|
// Step 6.
|
||||||
|
for allowed_device in allowed_devices.iter() {
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// Step 6.2.
|
||||||
|
if let Some(ref filters) = descriptor.filters {
|
||||||
|
let mut scan_filters: Vec<BluetoothScanfilter> = Vec::new();
|
||||||
|
|
||||||
|
// Step 6.2.1.
|
||||||
|
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.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.3.
|
||||||
|
// TODO: Implement this correctly, not just using device ids here.
|
||||||
|
// https://webbluetoothcg.github.io/web-bluetooth/#get-the-bluetoothdevice-representing
|
||||||
|
if let Some(device) = device_map.get(&device_id) {
|
||||||
|
matching_devices.push(JS::from_ref(&**device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7.
|
||||||
|
status.set_devices(matching_devices.drain(..).collect());
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-query
|
||||||
|
// Step 7.
|
||||||
|
promise.resolve_native(cx, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
|
||||||
|
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 global = status.global();
|
||||||
|
let allowed_devices = global.as_window().bluetooth_extra_permission_data().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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,6 +74,12 @@ impl BluetoothDevice {
|
||||||
BluetoothDeviceBinding::Wrap)
|
BluetoothDeviceBinding::Wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_gatt(&self) -> Root<BluetoothRemoteGATTServer> {
|
||||||
|
self.gatt.or_init(|| {
|
||||||
|
BluetoothRemoteGATTServer::new(&self.global(), self)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn get_context(&self) -> Root<Bluetooth> {
|
fn get_context(&self) -> Root<Bluetooth> {
|
||||||
Root::from_ref(&self.context)
|
Root::from_ref(&self.context)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +163,7 @@ impl BluetoothDevice {
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn clean_up_disconnected_device(&self) {
|
pub fn clean_up_disconnected_device(&self) {
|
||||||
// Step 1.
|
// Step 1.
|
||||||
self.Gatt().set_connected(false);
|
self.get_gatt().set_connected(false);
|
||||||
|
|
||||||
// TODO: Step 2: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
// 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() {
|
for (id, device) in context.get_device_map().borrow().iter() {
|
||||||
// Step 2.1 - 2.2.
|
// Step 2.1 - 2.2.
|
||||||
if id == &self.Id().to_string() {
|
if id == &self.Id().to_string() {
|
||||||
if device.Gatt().Connected() {
|
if device.get_gatt().Connected() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// TODO: Step 2.3: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
// TODO: Step 2.3: Implement activeAlgorithms internal slot for BluetoothRemoteGATTServer.
|
||||||
|
@ -220,11 +226,14 @@ impl BluetoothDeviceMethods for BluetoothDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt
|
||||||
fn Gatt(&self) -> Root<BluetoothRemoteGATTServer> {
|
fn GetGatt(&self) -> Option<Root<BluetoothRemoteGATTServer>> {
|
||||||
// TODO: Step 1 - 2: Implement the Permission API.
|
// Step 1.
|
||||||
self.gatt.or_init(|| {
|
if self.global().as_window().bluetooth_extra_permission_data()
|
||||||
BluetoothRemoteGATTServer::new(&self.global(), self)
|
.allowed_devices_contains_id(self.id.clone()) && !self.is_represented_device_null() {
|
||||||
})
|
return Some(self.get_gatt())
|
||||||
|
}
|
||||||
|
// Step 2.
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
|
|
124
components/script/dom/bluetoothpermissionresult.rs
Normal file
124
components/script/dom/bluetoothpermissionresult.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/* 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::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::error::Error;
|
||||||
|
use dom::bindings::js::{JS, Root};
|
||||||
|
use dom::bindings::reflector::{DomObject, reflect_dom_object};
|
||||||
|
use dom::bindings::str::DOMString;
|
||||||
|
use dom::bluetooth::{AsyncBluetoothListener, Bluetooth, AllowedBluetoothDevice};
|
||||||
|
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)]
|
||||||
|
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 3, 11, 13 - 14.
|
||||||
|
BluetoothResponse::RequestDevice(device) => {
|
||||||
|
self.set_state(PermissionState::Granted);
|
||||||
|
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/#request-the-bluetooth-permission
|
||||||
|
// 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));
|
||||||
|
self.global().as_window().bluetooth_extra_permission_data().add_new_allowed_device(
|
||||||
|
AllowedBluetoothDevice {
|
||||||
|
deviceId: DOMString::from(device.id),
|
||||||
|
mayUseGATT: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission
|
||||||
|
// 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())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::
|
use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::
|
||||||
BluetoothCharacteristicPropertiesMethods;
|
BluetoothCharacteristicPropertiesMethods;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
|
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding;
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
|
||||||
BluetoothRemoteGATTCharacteristicMethods;
|
BluetoothRemoteGATTCharacteristicMethods;
|
||||||
|
@ -104,7 +103,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
|
||||||
fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID) -> Rc<Promise> {
|
fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID) -> Rc<Promise> {
|
||||||
get_gatt_children(self, true, BluetoothUUID::descriptor, Some(descriptor), self.get_instance_id(),
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
|
@ -113,7 +112,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
|
||||||
descriptor: Option<BluetoothDescriptorUUID>)
|
descriptor: Option<BluetoothDescriptorUUID>)
|
||||||
-> Rc<Promise> {
|
-> Rc<Promise> {
|
||||||
get_gatt_children(self, false, BluetoothUUID::descriptor, descriptor, self.get_instance_id(),
|
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
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value
|
||||||
|
@ -134,7 +133,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
if !self.Service().Device().Gatt().Connected() {
|
if !self.Service().Device().get_gatt().Connected() {
|
||||||
p.reject_error(p_cx, Network);
|
p.reject_error(p_cx, Network);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +173,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4.
|
// Step 4.
|
||||||
if !self.Service().Device().Gatt().Connected() {
|
if !self.Service().Device().get_gatt().Connected() {
|
||||||
p.reject_error(p_cx, Network);
|
p.reject_error(p_cx, Network);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -210,7 +209,7 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
if !self.Service().Device().Gatt().Connected() {
|
if !self.Service().Device().get_gatt().Connected() {
|
||||||
p.reject_error(p_cx, Network);
|
p.reject_error(p_cx, Network);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
|
use bluetooth_traits::{BluetoothRequest, BluetoothResponse};
|
||||||
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
|
use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted};
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
|
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::
|
||||||
BluetoothRemoteGATTCharacteristicMethods;
|
BluetoothRemoteGATTCharacteristicMethods;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding;
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding;
|
||||||
|
@ -98,7 +97,7 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
if !self.Characteristic().Service().Device().Gatt().Connected() {
|
if !self.Characteristic().Service().Device().get_gatt().Connected() {
|
||||||
p.reject_error(p_cx, Network);
|
p.reject_error(p_cx, Network);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +130,7 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4.
|
// Step 4.
|
||||||
if !self.Characteristic().Service().Device().Gatt().Connected() {
|
if !self.Characteristic().Service().Device().get_gatt().Connected() {
|
||||||
p.reject_error(p_cx, Network);
|
p.reject_error(p_cx, Network);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,17 +103,15 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer {
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
|
||||||
fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> {
|
fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> {
|
||||||
// TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
|
// Step 1 - 2.
|
||||||
// Step 2.
|
|
||||||
get_gatt_children(self, true, BluetoothUUID::service, Some(service), String::from(self.Device().Id()),
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
|
||||||
fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> {
|
fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> {
|
||||||
// TODO: Step 1: Implement the Permission API and the allowedServices BluetoothDevice internal slot.
|
// Step 1 - 2.
|
||||||
// Step 2.
|
|
||||||
get_gatt_children(self, false, BluetoothUUID::service, service, String::from(self.Device().Id()),
|
get_gatt_children(self, false, BluetoothUUID::service, service, String::from(self.Device().Id()),
|
||||||
self.Connected(), GATTType::PrimaryService)
|
self.Connected(), GATTType::PrimaryService)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use bluetooth_traits::{BluetoothResponse, GATTType};
|
use bluetooth_traits::{BluetoothResponse, GATTType};
|
||||||
use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
|
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding;
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding;
|
||||||
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
|
use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
|
||||||
|
@ -87,7 +86,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
|
||||||
characteristic: BluetoothCharacteristicUUID)
|
characteristic: BluetoothCharacteristicUUID)
|
||||||
-> Rc<Promise> {
|
-> Rc<Promise> {
|
||||||
get_gatt_children(self, true, BluetoothUUID::characteristic, Some(characteristic), self.get_instance_id(),
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
|
@ -96,7 +95,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
|
||||||
characteristic: Option<BluetoothCharacteristicUUID>)
|
characteristic: Option<BluetoothCharacteristicUUID>)
|
||||||
-> Rc<Promise> {
|
-> Rc<Promise> {
|
||||||
get_gatt_children(self, false, BluetoothUUID::characteristic, characteristic, self.get_instance_id(),
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
|
@ -105,7 +104,7 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService {
|
||||||
service: BluetoothServiceUUID)
|
service: BluetoothServiceUUID)
|
||||||
-> Rc<Promise> {
|
-> Rc<Promise> {
|
||||||
get_gatt_children(self, false, BluetoothUUID::service, Some(service), self.get_instance_id(),
|
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>)
|
service: Option<BluetoothServiceUUID>)
|
||||||
-> Rc<Promise> {
|
-> Rc<Promise> {
|
||||||
get_gatt_children(self, false, BluetoothUUID::service, service, self.get_instance_id(),
|
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
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceadded
|
||||||
|
@ -151,12 +150,12 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTService {
|
||||||
// Step 7.
|
// Step 7.
|
||||||
BluetoothResponse::GetIncludedServices(services_vec, single) => {
|
BluetoothResponse::GetIncludedServices(services_vec, single) => {
|
||||||
if single {
|
if single {
|
||||||
promise.resolve_native(promise_cx, &device.get_or_create_service(&services_vec[0], &device.Gatt()));
|
return promise.resolve_native(promise_cx,
|
||||||
return;
|
&device.get_or_create_service(&services_vec[0], &device.get_gatt()));
|
||||||
}
|
}
|
||||||
let mut services = vec!();
|
let mut services = vec!();
|
||||||
for service in 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);
|
services.push(bt_service);
|
||||||
}
|
}
|
||||||
promise.resolve_native(promise_cx, &services);
|
promise.resolve_native(promise_cx, &services);
|
||||||
|
|
|
@ -223,6 +223,7 @@ pub mod bluetooth;
|
||||||
pub mod bluetoothadvertisingevent;
|
pub mod bluetoothadvertisingevent;
|
||||||
pub mod bluetoothcharacteristicproperties;
|
pub mod bluetoothcharacteristicproperties;
|
||||||
pub mod bluetoothdevice;
|
pub mod bluetoothdevice;
|
||||||
|
pub mod bluetoothpermissionresult;
|
||||||
pub mod bluetoothremotegattcharacteristic;
|
pub mod bluetoothremotegattcharacteristic;
|
||||||
pub mod bluetoothremotegattdescriptor;
|
pub mod bluetoothremotegattdescriptor;
|
||||||
pub mod bluetoothremotegattserver;
|
pub mod bluetoothremotegattserver;
|
||||||
|
@ -384,6 +385,8 @@ pub mod nodelist;
|
||||||
pub mod pagetransitionevent;
|
pub mod pagetransitionevent;
|
||||||
pub mod performance;
|
pub mod performance;
|
||||||
pub mod performancetiming;
|
pub mod performancetiming;
|
||||||
|
pub mod permissions;
|
||||||
|
pub mod permissionstatus;
|
||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
pub mod pluginarray;
|
pub mod pluginarray;
|
||||||
pub mod popstateevent;
|
pub mod popstateevent;
|
||||||
|
|
|
@ -10,6 +10,7 @@ use dom::bindings::str::DOMString;
|
||||||
use dom::bluetooth::Bluetooth;
|
use dom::bluetooth::Bluetooth;
|
||||||
use dom::mimetypearray::MimeTypeArray;
|
use dom::mimetypearray::MimeTypeArray;
|
||||||
use dom::navigatorinfo;
|
use dom::navigatorinfo;
|
||||||
|
use dom::permissions::Permissions;
|
||||||
use dom::pluginarray::PluginArray;
|
use dom::pluginarray::PluginArray;
|
||||||
use dom::serviceworkercontainer::ServiceWorkerContainer;
|
use dom::serviceworkercontainer::ServiceWorkerContainer;
|
||||||
use dom::vr::VR;
|
use dom::vr::VR;
|
||||||
|
@ -23,7 +24,8 @@ pub struct Navigator {
|
||||||
plugins: MutNullableJS<PluginArray>,
|
plugins: MutNullableJS<PluginArray>,
|
||||||
mime_types: MutNullableJS<MimeTypeArray>,
|
mime_types: MutNullableJS<MimeTypeArray>,
|
||||||
service_worker: MutNullableJS<ServiceWorkerContainer>,
|
service_worker: MutNullableJS<ServiceWorkerContainer>,
|
||||||
vr: MutNullableJS<VR>
|
vr: MutNullableJS<VR>,
|
||||||
|
permissions: MutNullableJS<Permissions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Navigator {
|
impl Navigator {
|
||||||
|
@ -35,6 +37,7 @@ impl Navigator {
|
||||||
mime_types: Default::default(),
|
mime_types: Default::default(),
|
||||||
service_worker: Default::default(),
|
service_worker: Default::default(),
|
||||||
vr: Default::default(),
|
vr: Default::default(),
|
||||||
|
permissions: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +126,11 @@ impl NavigatorMethods for Navigator {
|
||||||
fn Vr(&self) -> Root<VR> {
|
fn Vr(&self) -> Root<VR> {
|
||||||
self.vr.or_init(|| VR::new(&self.global()))
|
self.vr.or_init(|| VR::new(&self.global()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
|
||||||
|
fn Permissions(&self) -> Root<Permissions> {
|
||||||
|
self.permissions.or_init(|| Permissions::new(&self.global()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Navigator {
|
impl Navigator {
|
||||||
|
|
351
components/script/dom/permissions.rs
Normal file
351
components/script/dom/permissions.rs
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
/* 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 dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionDescriptor, PermissionName, PermissionState};
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods;
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionsBinding::{self, PermissionsMethods};
|
||||||
|
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;
|
||||||
|
use js::conversions::ConversionResult;
|
||||||
|
use js::jsapi::{JSContext, JSObject};
|
||||||
|
use js::jsval::{ObjectValue, UndefinedValue};
|
||||||
|
use servo_config::prefs::PREFS;
|
||||||
|
use std::rc::Rc;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use tinyfiledialogs::{self, MessageBoxIcon, YesNo};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const DIALOG_TITLE: &'static str = "Permission request dialog";
|
||||||
|
const NONSECURE_DIALOG_MESSAGE: &'static str = "feature is only safe to use in secure context,\
|
||||||
|
but servo can't guarantee\n that the current context is secure. Do you want to proceed and grant permission?";
|
||||||
|
const REQUEST_DIALOG_MESSAGE: &'static str = "Do you want to grant permission for";
|
||||||
|
const ROOT_DESC_CONVERSION_ERROR: &'static str = "Can't convert to an IDL value of type PermissionDescriptor";
|
||||||
|
|
||||||
|
pub trait PermissionAlgorithm {
|
||||||
|
type Descriptor;
|
||||||
|
type Status;
|
||||||
|
fn create_descriptor(cx: *mut JSContext,
|
||||||
|
permission_descriptor_obj: *mut JSObject)
|
||||||
|
-> Result<Self::Descriptor, Error>;
|
||||||
|
fn permission_query(cx: *mut JSContext, promise: &Rc<Promise>,
|
||||||
|
descriptor: &Self::Descriptor, status: &Self::Status);
|
||||||
|
fn permission_request(cx: *mut JSContext, promise: &Rc<Promise>,
|
||||||
|
descriptor: &Self::Descriptor, status: &Self::Status);
|
||||||
|
fn permission_revoke(descriptor: &Self::Descriptor, status: &Self::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Operation {
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Revoke,
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#permissions
|
||||||
|
#[dom_struct]
|
||||||
|
pub struct Permissions {
|
||||||
|
reflector_: Reflector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Permissions {
|
||||||
|
pub fn new_inherited() -> Permissions {
|
||||||
|
Permissions {
|
||||||
|
reflector_: Reflector::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(global: &GlobalScope) -> Root<Permissions> {
|
||||||
|
reflect_dom_object(box Permissions::new_inherited(),
|
||||||
|
global,
|
||||||
|
PermissionsBinding::Wrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-query
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-request
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-revoke
|
||||||
|
fn manipulate(&self,
|
||||||
|
op: Operation,
|
||||||
|
cx: *mut JSContext,
|
||||||
|
permissionDesc: *mut JSObject,
|
||||||
|
promise: Option<Rc<Promise>>)
|
||||||
|
-> Rc<Promise> {
|
||||||
|
// (Query, Request) Step 3.
|
||||||
|
let p = match promise {
|
||||||
|
Some(promise) => promise,
|
||||||
|
None => Promise::new(&self.global()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// (Query, Request, Revoke) Step 1.
|
||||||
|
let root_desc = match Permissions::create_descriptor(cx, permissionDesc) {
|
||||||
|
Ok(descriptor) => descriptor,
|
||||||
|
Err(error) => {
|
||||||
|
p.reject_error(cx, error);
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// (Query, Request) Step 5.
|
||||||
|
let status = PermissionStatus::new(&self.global(), &root_desc);
|
||||||
|
|
||||||
|
// (Query, Request, Revoke) 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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// (Query, Request) Step 5.
|
||||||
|
let result = BluetoothPermissionResult::new(&self.global(), &status);
|
||||||
|
|
||||||
|
match &op {
|
||||||
|
// (Request) Step 6 - 8.
|
||||||
|
&Operation::Request => Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result),
|
||||||
|
|
||||||
|
// (Query) Step 6 - 7.
|
||||||
|
&Operation::Query => Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result),
|
||||||
|
|
||||||
|
&Operation::Revoke => {
|
||||||
|
// (Revoke) Step 3.
|
||||||
|
let globalscope = self.global();
|
||||||
|
globalscope.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&root_desc.name.to_string());
|
||||||
|
|
||||||
|
// (Revoke) Step 4.
|
||||||
|
Bluetooth::permission_revoke(&bluetooth_desc, &result)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
match &op {
|
||||||
|
&Operation::Request => {
|
||||||
|
// (Request) Step 6.
|
||||||
|
Permissions::permission_request(cx, &p, &root_desc, &status);
|
||||||
|
|
||||||
|
// (Request) Step 7. The default algorithm always resolve
|
||||||
|
|
||||||
|
// (Request) Step 8.
|
||||||
|
p.resolve_native(cx, &status);
|
||||||
|
},
|
||||||
|
&Operation::Query => {
|
||||||
|
// (Query) Step 6.
|
||||||
|
Permissions::permission_query(cx, &p, &root_desc, &status);
|
||||||
|
|
||||||
|
// (Query) Step 7.
|
||||||
|
p.resolve_native(cx, &status);
|
||||||
|
},
|
||||||
|
|
||||||
|
&Operation::Revoke => {
|
||||||
|
// (Revoke) Step 3.
|
||||||
|
let globalscope = self.global();
|
||||||
|
globalscope.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&root_desc.name.to_string());
|
||||||
|
|
||||||
|
// (Revoke) Step 4.
|
||||||
|
Permissions::permission_revoke(&root_desc, &status);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
match op {
|
||||||
|
// (Revoke) Step 5.
|
||||||
|
Operation::Revoke => self.manipulate(Operation::Query, cx, permissionDesc, Some(p)),
|
||||||
|
|
||||||
|
// (Query, Request) Step 4.
|
||||||
|
_ => p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionsMethods for Permissions {
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-query
|
||||||
|
unsafe fn Query(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> {
|
||||||
|
self.manipulate(Operation::Query, cx, permissionDesc, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-request
|
||||||
|
unsafe fn Request(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> {
|
||||||
|
self.manipulate(Operation::Request, cx, permissionDesc, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissions-revoke
|
||||||
|
unsafe fn Revoke(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> {
|
||||||
|
self.manipulate(Operation::Revoke, cx, permissionDesc, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionAlgorithm for Permissions {
|
||||||
|
type Descriptor = PermissionDescriptor;
|
||||||
|
type Status = PermissionStatus;
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn create_descriptor(cx: *mut JSContext,
|
||||||
|
permission_descriptor_obj: *mut JSObject)
|
||||||
|
-> Result<PermissionDescriptor, Error> {
|
||||||
|
rooted!(in(cx) let mut property = UndefinedValue());
|
||||||
|
property.handle_mut().set(ObjectValue(permission_descriptor_obj));
|
||||||
|
unsafe {
|
||||||
|
match PermissionDescriptor::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(ROOT_DESC_CONVERSION_ERROR))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#boolean-permission-query-algorithm
|
||||||
|
fn permission_query(_cx: *mut JSContext,
|
||||||
|
_promise: &Rc<Promise>,
|
||||||
|
_descriptor: &PermissionDescriptor,
|
||||||
|
status: &PermissionStatus) {
|
||||||
|
// Step 1.
|
||||||
|
status.set_state(get_descriptor_permission_state(status.get_query(), None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#boolean-permission-request-algorithm
|
||||||
|
fn permission_request(cx: *mut JSContext,
|
||||||
|
promise: &Rc<Promise>,
|
||||||
|
descriptor: &PermissionDescriptor,
|
||||||
|
status: &PermissionStatus) {
|
||||||
|
// Step 1.
|
||||||
|
Permissions::permission_query(cx, promise, descriptor, status);
|
||||||
|
|
||||||
|
match status.State() {
|
||||||
|
// Step 3.
|
||||||
|
PermissionState::Prompt => {
|
||||||
|
let perm_name = status.get_query();
|
||||||
|
// https://w3c.github.io/permissions/#request-permission-to-use (Step 3 - 4)
|
||||||
|
let state =
|
||||||
|
prompt_user(&format!("{} {} ?", REQUEST_DIALOG_MESSAGE, perm_name.clone()));
|
||||||
|
|
||||||
|
let globalscope = GlobalScope::current();
|
||||||
|
globalscope.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(perm_name.to_string(), state);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Step 2.
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4.
|
||||||
|
Permissions::permission_query(cx, promise, descriptor, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permission_revoke(_descriptor: &PermissionDescriptor, _status: &PermissionStatus) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#permission-state
|
||||||
|
pub fn get_descriptor_permission_state(permission_name: PermissionName,
|
||||||
|
env_settings_obj: Option<&GlobalScope>)
|
||||||
|
-> PermissionState {
|
||||||
|
// Step 1.
|
||||||
|
let settings = match env_settings_obj {
|
||||||
|
Some(env_settings_obj) => Root::from_ref(env_settings_obj),
|
||||||
|
None => GlobalScope::current(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 2.
|
||||||
|
// TODO: The `is the environment settings object a non-secure context` check is missing.
|
||||||
|
// The current solution is a workaround with a message box to warn about this,
|
||||||
|
// if the feature is not allowed in non-secure contexcts,
|
||||||
|
// and let the user decide to grant the permission or not.
|
||||||
|
let state = match allowed_in_nonsecure_contexts(&permission_name) {
|
||||||
|
true => PermissionState::Prompt,
|
||||||
|
false => {
|
||||||
|
match PREFS.get("dom.permissions.testing.allowed_in_nonsecure_contexts").as_boolean().unwrap_or(false) {
|
||||||
|
true => PermissionState::Granted,
|
||||||
|
false => {
|
||||||
|
settings.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&permission_name.to_string());
|
||||||
|
prompt_user(&format!("The {} {}", permission_name, NONSECURE_DIALOG_MESSAGE))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 3.
|
||||||
|
if let Some(prev_result) = settings.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow()
|
||||||
|
.get(&permission_name.to_string()) {
|
||||||
|
return prev_result.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the invocation result
|
||||||
|
settings.as_window()
|
||||||
|
.permission_state_invocation_results()
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(permission_name.to_string(), state);
|
||||||
|
|
||||||
|
// Step 4.
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn prompt_user(message: &str) -> PermissionState {
|
||||||
|
match tinyfiledialogs::message_box_yes_no(DIALOG_TITLE,
|
||||||
|
message,
|
||||||
|
MessageBoxIcon::Question,
|
||||||
|
YesNo::No) {
|
||||||
|
YesNo::Yes => PermissionState::Granted,
|
||||||
|
YesNo::No => PermissionState::Denied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
fn prompt_user(_message: &str) -> PermissionState {
|
||||||
|
// TODO popup only supported on linux
|
||||||
|
PermissionState::Denied
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#allowed-in-non-secure-contexts
|
||||||
|
fn allowed_in_nonsecure_contexts(permission_name: &PermissionName) -> bool {
|
||||||
|
match *permission_name {
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-geolocation
|
||||||
|
PermissionName::Geolocation => true,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-notifications
|
||||||
|
PermissionName::Notifications => true,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-push
|
||||||
|
PermissionName::Push => false,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-midi
|
||||||
|
PermissionName::Midi => true,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-camera
|
||||||
|
PermissionName::Camera => false,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-microphone
|
||||||
|
PermissionName::Microphone => false,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-speaker
|
||||||
|
PermissionName::Speaker => false,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-device-info
|
||||||
|
PermissionName::Device_info => false,
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionname-background-sync
|
||||||
|
PermissionName::Background_sync => false,
|
||||||
|
// https://webbluetoothcg.github.io/web-bluetooth/#dom-permissionname-bluetooth
|
||||||
|
PermissionName::Bluetooth => false,
|
||||||
|
// https://storage.spec.whatwg.org/#dom-permissionname-persistent-storage
|
||||||
|
PermissionName::Persistent_storage => false,
|
||||||
|
}
|
||||||
|
}
|
62
components/script/dom/permissionstatus.rs
Normal file
62
components/script/dom/permissionstatus.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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 dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::{self, PermissionDescriptor, PermissionName};
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionNameValues, PermissionState};
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods;
|
||||||
|
use dom::bindings::js::Root;
|
||||||
|
use dom::bindings::reflector::reflect_dom_object;
|
||||||
|
use dom::eventtarget::EventTarget;
|
||||||
|
use dom::globalscope::GlobalScope;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#permissionstatus
|
||||||
|
#[dom_struct]
|
||||||
|
pub struct PermissionStatus {
|
||||||
|
eventtarget: EventTarget,
|
||||||
|
state: Cell<PermissionState>,
|
||||||
|
query: Cell<PermissionName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionStatus {
|
||||||
|
pub fn new_inherited(query: PermissionName) -> PermissionStatus {
|
||||||
|
PermissionStatus {
|
||||||
|
eventtarget: EventTarget::new_inherited(),
|
||||||
|
state: Cell::new(PermissionState::Denied),
|
||||||
|
query: Cell::new(query),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(global: &GlobalScope, query: &PermissionDescriptor) -> Root<PermissionStatus> {
|
||||||
|
reflect_dom_object(box PermissionStatus::new_inherited(query.name),
|
||||||
|
global,
|
||||||
|
PermissionStatusBinding::Wrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_state(&self, state: PermissionState) {
|
||||||
|
self.state.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_query(&self) -> PermissionName {
|
||||||
|
self.query.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionStatusMethods for PermissionStatus {
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionstatus-state
|
||||||
|
fn State(&self) -> PermissionState {
|
||||||
|
self.state.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#dom-permissionstatus-onchange
|
||||||
|
event_handler!(onchange, GetOnchange, SetOnchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PermissionName {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", PermissionNameValues::strings[*self as usize].to_string())
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
interface BluetoothDevice : EventTarget {
|
interface BluetoothDevice : EventTarget {
|
||||||
readonly attribute DOMString id;
|
readonly attribute DOMString id;
|
||||||
readonly attribute DOMString? name;
|
readonly attribute DOMString? name;
|
||||||
readonly attribute BluetoothRemoteGATTServer gatt;
|
readonly attribute BluetoothRemoteGATTServer? gatt;
|
||||||
|
|
||||||
Promise<void> watchAdvertisements();
|
Promise<void> watchAdvertisements();
|
||||||
void unwatchAdvertisements();
|
void unwatchAdvertisements();
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
[Pref="dom.bluetooth.enabled"]
|
||||||
|
interface BluetoothPermissionResult : PermissionStatus {
|
||||||
|
// attribute FrozenArray<BluetoothDevice> devices;
|
||||||
|
// Workaround until FrozenArray get implemented.
|
||||||
|
sequence<BluetoothDevice> devices();
|
||||||
|
};
|
|
@ -62,3 +62,9 @@ interface NavigatorCookies {
|
||||||
partial interface Navigator {
|
partial interface Navigator {
|
||||||
[SameObject, Pref="dom.webvr.enabled"] readonly attribute VR vr;
|
[SameObject, Pref="dom.webvr.enabled"] readonly attribute VR vr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
|
||||||
|
[Exposed=(Window)]
|
||||||
|
partial interface Navigator {
|
||||||
|
[Pref="dom.permissions.enabled"] readonly attribute Permissions permissions;
|
||||||
|
};
|
||||||
|
|
47
components/script/dom/webidls/PermissionStatus.webidl
Normal file
47
components/script/dom/webidls/PermissionStatus.webidl
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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://w3c.github.io/permissions/#permissionstatus
|
||||||
|
|
||||||
|
dictionary PermissionDescriptor {
|
||||||
|
required PermissionName name;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PermissionState {
|
||||||
|
"granted",
|
||||||
|
"denied",
|
||||||
|
"prompt",
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PermissionName {
|
||||||
|
"geolocation",
|
||||||
|
"notifications",
|
||||||
|
"push",
|
||||||
|
"midi",
|
||||||
|
"camera",
|
||||||
|
"microphone",
|
||||||
|
"speaker",
|
||||||
|
"device-info",
|
||||||
|
"background-sync",
|
||||||
|
"bluetooth",
|
||||||
|
"persistent-storage",
|
||||||
|
};
|
||||||
|
|
||||||
|
[Pref="dom.permissions.enabled", Exposed=(Window,Worker)]
|
||||||
|
interface PermissionStatus : EventTarget {
|
||||||
|
readonly attribute PermissionState state;
|
||||||
|
attribute EventHandler onchange;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary PushPermissionDescriptor : PermissionDescriptor {
|
||||||
|
boolean userVisibleOnly = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary MidiPermissionDescriptor : PermissionDescriptor {
|
||||||
|
boolean sysex = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary DevicePermissionDescriptor : PermissionDescriptor {
|
||||||
|
DOMString deviceId;
|
||||||
|
};
|
14
components/script/dom/webidls/Permissions.webidl
Normal file
14
components/script/dom/webidls/Permissions.webidl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/* 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://w3c.github.io/permissions/#permissions-interface
|
||||||
|
|
||||||
|
[Pref="dom.permissions.enabled", Exposed=(Window,Worker)]
|
||||||
|
interface Permissions {
|
||||||
|
Promise<PermissionStatus> query(object permissionDesc);
|
||||||
|
|
||||||
|
Promise<PermissionStatus> request(object permissionDesc);
|
||||||
|
|
||||||
|
Promise<PermissionStatus> revoke(object permissionDesc);
|
||||||
|
};
|
|
@ -8,3 +8,10 @@ interface WorkerNavigator {};
|
||||||
WorkerNavigator implements NavigatorID;
|
WorkerNavigator implements NavigatorID;
|
||||||
WorkerNavigator implements NavigatorLanguage;
|
WorkerNavigator implements NavigatorLanguage;
|
||||||
//WorkerNavigator implements NavigatorOnLine;
|
//WorkerNavigator implements NavigatorOnLine;
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
|
||||||
|
|
||||||
|
[Exposed=(Worker)]
|
||||||
|
partial interface WorkerNavigator {
|
||||||
|
[Pref="dom.permissions.enabled"] readonly attribute Permissions permissions;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHa
|
||||||
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
|
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
|
||||||
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
||||||
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
||||||
|
use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState;
|
||||||
use dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
|
use dom::bindings::codegen::Bindings::RequestBinding::RequestInit;
|
||||||
use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
|
use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
|
||||||
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
|
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
|
||||||
|
@ -26,6 +27,7 @@ use dom::bindings::reflector::DomObject;
|
||||||
use dom::bindings::str::DOMString;
|
use dom::bindings::str::DOMString;
|
||||||
use dom::bindings::structuredclone::StructuredCloneData;
|
use dom::bindings::structuredclone::StructuredCloneData;
|
||||||
use dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
|
use dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
|
||||||
|
use dom::bluetooth::BluetoothExtraPermissionData;
|
||||||
use dom::browsingcontext::BrowsingContext;
|
use dom::browsingcontext::BrowsingContext;
|
||||||
use dom::crypto::Crypto;
|
use dom::crypto::Crypto;
|
||||||
use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
|
use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
|
||||||
|
@ -209,6 +211,8 @@ pub struct Window {
|
||||||
#[ignore_heap_size_of = "channels are hard"]
|
#[ignore_heap_size_of = "channels are hard"]
|
||||||
bluetooth_thread: IpcSender<BluetoothRequest>,
|
bluetooth_thread: IpcSender<BluetoothRequest>,
|
||||||
|
|
||||||
|
bluetooth_extra_permission_data: BluetoothExtraPermissionData,
|
||||||
|
|
||||||
/// An enlarged rectangle around the page contents visible in the viewport, used
|
/// An enlarged rectangle around the page contents visible in the viewport, used
|
||||||
/// to prevent creating display list items for content that is far away from the viewport.
|
/// to prevent creating display list items for content that is far away from the viewport.
|
||||||
page_clip_rect: Cell<Rect<Au>>,
|
page_clip_rect: Cell<Rect<Au>>,
|
||||||
|
@ -246,7 +250,10 @@ pub struct Window {
|
||||||
|
|
||||||
/// A handle for communicating messages to the webvr thread, if available.
|
/// A handle for communicating messages to the webvr thread, if available.
|
||||||
#[ignore_heap_size_of = "channels are hard"]
|
#[ignore_heap_size_of = "channels are hard"]
|
||||||
webvr_thread: Option<IpcSender<WebVRMsg>>
|
webvr_thread: Option<IpcSender<WebVRMsg>>,
|
||||||
|
|
||||||
|
/// A map for storing the previous permission state read results.
|
||||||
|
permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -313,6 +320,10 @@ impl Window {
|
||||||
self.bluetooth_thread.clone()
|
self.bluetooth_thread.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bluetooth_extra_permission_data(&self) -> &BluetoothExtraPermissionData {
|
||||||
|
&self.bluetooth_extra_permission_data
|
||||||
|
}
|
||||||
|
|
||||||
pub fn css_error_reporter(&self) -> Box<ParseErrorReporter + Send> {
|
pub fn css_error_reporter(&self) -> Box<ParseErrorReporter + Send> {
|
||||||
self.error_reporter.clone()
|
self.error_reporter.clone()
|
||||||
}
|
}
|
||||||
|
@ -331,6 +342,10 @@ impl Window {
|
||||||
pub fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> {
|
pub fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> {
|
||||||
self.webvr_thread.clone()
|
self.webvr_thread.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn permission_state_invocation_results(&self) -> &DOMRefCell<HashMap<String, PermissionState>> {
|
||||||
|
&self.permission_state_invocation_results
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||||
|
@ -1678,6 +1693,7 @@ impl Window {
|
||||||
dom_static: GlobalStaticData::new(),
|
dom_static: GlobalStaticData::new(),
|
||||||
js_runtime: DOMRefCell::new(Some(runtime.clone())),
|
js_runtime: DOMRefCell::new(Some(runtime.clone())),
|
||||||
bluetooth_thread: bluetooth_thread,
|
bluetooth_thread: bluetooth_thread,
|
||||||
|
bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(),
|
||||||
page_clip_rect: Cell::new(max_rect()),
|
page_clip_rect: Cell::new(max_rect()),
|
||||||
resize_event: Cell::new(None),
|
resize_event: Cell::new(None),
|
||||||
layout_chan: layout_chan,
|
layout_chan: layout_chan,
|
||||||
|
@ -1696,7 +1712,8 @@ impl Window {
|
||||||
scroll_offsets: DOMRefCell::new(HashMap::new()),
|
scroll_offsets: DOMRefCell::new(HashMap::new()),
|
||||||
media_query_lists: WeakMediaQueryListVec::new(),
|
media_query_lists: WeakMediaQueryListVec::new(),
|
||||||
test_runner: Default::default(),
|
test_runner: Default::default(),
|
||||||
webvr_thread: webvr_thread
|
webvr_thread: webvr_thread,
|
||||||
|
permission_state_invocation_results: DOMRefCell::new(HashMap::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -4,22 +4,25 @@
|
||||||
|
|
||||||
use dom::bindings::codegen::Bindings::WorkerNavigatorBinding;
|
use dom::bindings::codegen::Bindings::WorkerNavigatorBinding;
|
||||||
use dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods;
|
use dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods;
|
||||||
use dom::bindings::js::Root;
|
use dom::bindings::js::{MutNullableJS, Root};
|
||||||
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
|
||||||
use dom::bindings::str::DOMString;
|
use dom::bindings::str::DOMString;
|
||||||
use dom::navigatorinfo;
|
use dom::navigatorinfo;
|
||||||
|
use dom::permissions::Permissions;
|
||||||
use dom::workerglobalscope::WorkerGlobalScope;
|
use dom::workerglobalscope::WorkerGlobalScope;
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#workernavigator
|
// https://html.spec.whatwg.org/multipage/#workernavigator
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct WorkerNavigator {
|
pub struct WorkerNavigator {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
|
permissions: MutNullableJS<Permissions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerNavigator {
|
impl WorkerNavigator {
|
||||||
fn new_inherited() -> WorkerNavigator {
|
fn new_inherited() -> WorkerNavigator {
|
||||||
WorkerNavigator {
|
WorkerNavigator {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
|
permissions: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,4 +73,9 @@ impl WorkerNavigatorMethods for WorkerNavigator {
|
||||||
fn Language(&self) -> DOMString {
|
fn Language(&self) -> DOMString {
|
||||||
navigatorinfo::Language()
|
navigatorinfo::Language()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
|
||||||
|
fn Permissions(&self) -> Root<Permissions> {
|
||||||
|
self.permissions.or_init(|| Permissions::new(&self.global()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
"dom.forcetouch.enabled": false,
|
"dom.forcetouch.enabled": false,
|
||||||
"dom.mouseevent.which.enabled": false,
|
"dom.mouseevent.which.enabled": false,
|
||||||
"dom.mozbrowser.enabled": false,
|
"dom.mozbrowser.enabled": false,
|
||||||
|
"dom.permissions.enabled": false,
|
||||||
|
"dom.permissions.testing.allowed_in_nonsecure_contexts": false,
|
||||||
"dom.serviceworker.timeout_seconds": 60,
|
"dom.serviceworker.timeout_seconds": 60,
|
||||||
"dom.testable_crash.enabled": false,
|
"dom.testable_crash.enabled": false,
|
||||||
"dom.testbinding.enabled": false,
|
"dom.testbinding.enabled": false,
|
||||||
|
|
69
tests/html/bluetooth-permission.html
Normal file
69
tests/html/bluetooth-permission.html
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<!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>
|
||||||
|
if (window.testRunner)
|
||||||
|
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>
|
37
tests/html/permission-test.html
Normal file
37
tests/html/permission-test.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<title>Permission Test</title>
|
||||||
|
<body>
|
||||||
|
<button type="button" onclick="onQueryButtonClick()">query</button>
|
||||||
|
<button type="button" onclick="onRequestButtonClick()">request</button>
|
||||||
|
<button type="button" onclick="onRevokeButtonClick()">revoke</button>
|
||||||
|
<input type="text" id="permissionName" value="geolocation"></input>
|
||||||
|
<pre id="log"></pre>
|
||||||
|
<script>
|
||||||
|
function onQueryButtonClick() {
|
||||||
|
let permissionName = document.getElementById('permissionName').value;
|
||||||
|
let permissionDescriptor = {name: permissionName};
|
||||||
|
window.navigator.permissions.query(permissionDescriptor)
|
||||||
|
.then(status => log("permission status of " + permissionName + " is: " + status.state))
|
||||||
|
.catch(err => log(err));
|
||||||
|
}
|
||||||
|
function onRequestButtonClick() {
|
||||||
|
let permissionName = document.getElementById('permissionName').value;
|
||||||
|
let permissionDescriptor = {name: permissionName};
|
||||||
|
window.navigator.permissions.request(permissionDescriptor)
|
||||||
|
.then(status => log("permission status of " + permissionName + " is: " + status.state))
|
||||||
|
.catch(err => log(err));
|
||||||
|
}
|
||||||
|
function onRevokeButtonClick() {
|
||||||
|
let permissionName = document.getElementById('permissionName').value;
|
||||||
|
let permissionDescriptor = {name: permissionName};
|
||||||
|
window.navigator.permissions.revoke(permissionDescriptor)
|
||||||
|
.then(status => log("permission status of " + permissionName + " is: " + status.state))
|
||||||
|
.catch(err => log(err));
|
||||||
|
}
|
||||||
|
function log(line) {
|
||||||
|
document.getElementById("log").textContent += line + '\n';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -24367,7 +24367,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"mozilla/bluetooth/connect/device-goes-out-of-range.html": [
|
"mozilla/bluetooth/connect/device-goes-out-of-range.html": [
|
||||||
"9e5dc423f92c3f73273d221e7fb7b8d905716db3",
|
"3ade30929f621b7c8c93b87e5392309729d68e42",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"mozilla/bluetooth/connect/get-same-gatt-server.html": [
|
"mozilla/bluetooth/connect/get-same-gatt-server.html": [
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
prefs: [dom.bluetooth.enabled:true, dom.bluetooth.testing.enabled:true]
|
prefs: [dom.bluetooth.enabled:true, dom.bluetooth.testing.enabled:true, dom.permissions.testing.allowed_in_nonsecure_contexts:true]
|
||||||
|
|
|
@ -51,18 +51,9 @@
|
||||||
[BluetoothPermissionResult interface: existence and properties of interface object]
|
[BluetoothPermissionResult interface: existence and properties of interface object]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[BluetoothPermissionResult interface object length]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[BluetoothPermissionResult interface object name]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[BluetoothPermissionResult interface: existence and properties of interface prototype object]
|
[BluetoothPermissionResult interface: existence and properties of interface prototype object]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[BluetoothPermissionResult interface: existence and properties of interface prototype object's "constructor" property]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[BluetoothPermissionResult interface: attribute devices]
|
[BluetoothPermissionResult interface: attribute devices]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,9 @@ promise_test(t => {
|
||||||
filters: [{services: [heart_rate.name]}]
|
filters: [{services: [heart_rate.name]}]
|
||||||
})
|
})
|
||||||
.then(device => {
|
.then(device => {
|
||||||
|
var gatt_server = device.gatt;
|
||||||
window.testRunner.setBluetoothMockDataSet(adapter_type.empty);
|
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.');
|
}, 'Device goes out of range. Reject with NetworkError.');
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue