servo: Merge #13918 - WebBluetooth fixes for the wpt tests (from szeged:wpt-error-fixes); r=jdm
authorzakorgyula <gyula.zakor@gmail.com>
Mon, 07 Nov 2016 09:50:13 -0600
changeset 340086 bd248d0160f1e5f77abe116d004f0aa7efea20a6
parent 340085 212737747389e0c7359182724edd02b3c5809987
child 340087 ce312a7532165b984d88c12e29b466a99123a10e
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #13918 - WebBluetooth fixes for the wpt tests (from szeged:wpt-error-fixes); r=jdm <!-- Please describe your changes on the following line: --> Note: depends on #13612 WebBluetooth fixes for the failing wpt tests, excluding the `disconnect-during` tests cases, due to the lack of the event handling in the current implementation. --- <!-- 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 - [x] There are tests for these changes OR <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: eb2484f86dd57c95957b417ac0dd249ea86df06b
servo/components/bluetooth/lib.rs
servo/components/bluetooth_traits/Cargo.toml
servo/components/bluetooth_traits/blacklist.rs
servo/components/bluetooth_traits/lib.rs
servo/components/bluetooth_traits/scanfilter.rs
servo/components/script/bluetooth_blacklist.rs
servo/components/script/dom/bluetooth.rs
servo/components/script/dom/bluetoothdevice.rs
servo/components/script/dom/bluetoothremotegattcharacteristic.rs
servo/components/script/dom/bluetoothremotegattdescriptor.rs
servo/components/script/dom/bluetoothremotegattserver.rs
servo/components/script/dom/bluetoothremotegattservice.rs
servo/components/script/dom/webidls/BluetoothDevice.webidl
servo/components/script/lib.rs
servo/components/servo/Cargo.lock
servo/ports/cef/Cargo.lock
--- a/servo/components/bluetooth/lib.rs
+++ b/servo/components/bluetooth/lib.rs
@@ -14,32 +14,29 @@ extern crate util;
 extern crate uuid;
 
 pub mod test;
 
 use bluetooth_traits::{BluetoothCharacteristicMsg, BluetoothCharacteristicsMsg};
 use bluetooth_traits::{BluetoothDescriptorMsg, BluetoothDescriptorsMsg};
 use bluetooth_traits::{BluetoothDeviceMsg, BluetoothError, BluetoothMethodMsg};
 use bluetooth_traits::{BluetoothResult, BluetoothServiceMsg, BluetoothServicesMsg};
+use bluetooth_traits::blacklist::{uuid_is_blacklisted, Blacklist};
 use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence, RequestDeviceoptions};
 use device::bluetooth::{BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic};
 use device::bluetooth::{BluetoothGATTDescriptor, BluetoothGATTService};
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
 use rand::Rng;
 use std::borrow::ToOwned;
 use std::collections::{HashMap, HashSet};
 use std::string::String;
 use std::thread;
 use std::time::Duration;
 use util::thread::spawn_named;
 
-const ADAPTER_ERROR: &'static str = "No adapter found";
-
-const ADAPTER_NOT_POWERED_ERROR: &'static str = "Bluetooth adapter not powered";
-
 // A transaction not completed within 30 seconds shall time out. Such a transaction shall be considered to have failed.
 // https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439 (Vol. 3, page 480)
 const MAXIMUM_TRANSACTION_TIME: u8 = 30;
 const CONNECTION_TIMEOUT_MS: u64 = 1000;
 // The discovery session needs some time to find any nearby devices
 const DISCOVERY_TIMEOUT_MS: u64 = 1500;
 #[cfg(target_os = "linux")]
 const DIALOG_TITLE: &'static str = "Choose a device";
@@ -70,21 +67,21 @@ macro_rules! return_if_cached(
     );
 );
 
 macro_rules! get_adapter_or_return_error(
     ($bl_manager:expr, $sender:expr) => (
         match $bl_manager.get_or_create_adapter() {
             Some(adapter) => {
                 if !adapter.is_powered().unwrap_or(false) {
-                    return drop($sender.send(Err(BluetoothError::Type(ADAPTER_NOT_POWERED_ERROR.to_string()))))
+                    return drop($sender.send(Err(BluetoothError::NotFound)))
                 }
                 adapter
             },
-            None => return drop($sender.send(Err(BluetoothError::Type(ADAPTER_ERROR.to_string())))),
+            None => return drop($sender.send(Err(BluetoothError::NotFound))),
         }
     );
 );
 
 pub trait BluetoothThreadFactory {
     fn new() -> Self;
 }
 
@@ -101,18 +98,18 @@ impl BluetoothThreadFactory for IpcSende
 
 // https://webbluetoothcg.github.io/web-bluetooth/#matches-a-filter
 fn matches_filter(device: &BluetoothDevice, filter: &BluetoothScanfilter) -> bool {
     if filter.is_empty_or_invalid() {
         return false;
     }
 
     // Step 1.
-    if !filter.get_name().is_empty() {
-        if device.get_name().ok() != Some(filter.get_name().to_string()) {
+    if let Some(name) = filter.get_name() {
+        if device.get_name().ok() != Some(name.to_string()) {
             return false;
         }
     }
 
     // Step 2.
     if !filter.get_name_prefix().is_empty() {
         if let Ok(device_name) = device.get_name() {
             if !device_name.starts_with(filter.get_name_prefix()) {
@@ -623,16 +620,19 @@ impl BluetoothManager {
             None => return drop(sender.send(Err(BluetoothError::NotFound))),
         }
     }
 
     fn get_primary_service(&mut self,
                            device_id: String,
                            uuid: String,
                            sender: IpcSender<BluetoothResult<BluetoothServiceMsg>>) {
+        if !self.cached_devices.contains_key(&device_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         if !self.allowed_services.get(&device_id).map_or(false, |s| s.contains(&uuid)) {
             return drop(sender.send(Err(BluetoothError::Security)));
         }
         let services = self.get_gatt_services_by_uuid(&mut adapter, &device_id, &uuid);
         if services.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
@@ -649,16 +649,19 @@ impl BluetoothManager {
         }
         return drop(sender.send(Err(BluetoothError::NotFound)));
     }
 
     fn get_primary_services(&mut self,
                             device_id: String,
                             uuid: Option<String>,
                             sender: IpcSender<BluetoothResult<BluetoothServicesMsg>>) {
+        if !self.cached_devices.contains_key(&device_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let services = match uuid {
             Some(ref id) => {
                 if !self.allowed_services.get(&device_id).map_or(false, |s| s.contains(id)) {
                     return drop(sender.send(Err(BluetoothError::Security)))
                 }
                 self.get_gatt_services_by_uuid(&mut adapter, &device_id, id)
             },
@@ -674,30 +677,37 @@ impl BluetoothManager {
                     services_vec.push(BluetoothServiceMsg {
                                           uuid: uuid,
                                           is_primary: true,
                                           instance_id: service.get_id(),
                                       });
                 }
             }
         }
+        services_vec.retain(|s| !uuid_is_blacklisted(&s.uuid, Blacklist::All) &&
+                                self.allowed_services
+                                    .get(&device_id)
+                                    .map_or(false, |uuids| uuids.contains(&s.uuid)));
         if services_vec.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
 
         let _ = sender.send(Ok(services_vec));
     }
 
     fn get_included_service(&mut self,
                             service_id: String,
                             uuid: String,
                             sender: IpcSender<BluetoothResult<BluetoothServiceMsg>>) {
+        if !self.cached_services.contains_key(&service_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = match self.get_or_create_adapter() {
             Some(a) => a,
-            None => return drop(sender.send(Err(BluetoothError::Type(ADAPTER_ERROR.to_string())))),
+            None => return drop(sender.send(Err(BluetoothError::NotFound))),
         };
         let device = match self.device_from_service_id(&service_id) {
             Some(device) => device,
             None => return drop(sender.send(Err(BluetoothError::NotFound))),
         };
         let primary_service = match self.get_gatt_service(&mut adapter, &service_id) {
             Some(s) => s,
             None => return drop(sender.send(Err(BluetoothError::NotFound))),
@@ -716,19 +726,22 @@ impl BluetoothManager {
         }
         return drop(sender.send(Err(BluetoothError::NotFound)));
     }
 
     fn get_included_services(&mut self,
                              service_id: String,
                              uuid: Option<String>,
                              sender: IpcSender<BluetoothResult<BluetoothServicesMsg>>) {
+        if !self.cached_services.contains_key(&service_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = match self.get_or_create_adapter() {
             Some(a) => a,
-            None => return drop(sender.send(Err(BluetoothError::Type(ADAPTER_ERROR.to_string())))),
+            None => return drop(sender.send(Err(BluetoothError::NotFound))),
         };
         let device = match self.device_from_service_id(&service_id) {
             Some(device) => device,
             None => return drop(sender.send(Err(BluetoothError::NotFound))),
         };
         let primary_service = match self.get_gatt_service(&mut adapter, &service_id) {
             Some(s) => s,
             None => return drop(sender.send(Err(BluetoothError::NotFound))),
@@ -742,27 +755,31 @@ impl BluetoothManager {
                                       is_primary: service.is_primary().unwrap_or(false),
                                       instance_id: service.get_id(),
                                   });
             }
         }
         if let Some(uuid) = uuid {
             services_vec.retain(|ref s| s.uuid == uuid);
         }
+        services_vec.retain(|s| !uuid_is_blacklisted(&s.uuid, Blacklist::All));
         if services_vec.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
 
         let _ = sender.send(Ok(services_vec));
     }
 
     fn get_characteristic(&mut self,
                           service_id: String,
                           uuid: String,
                           sender: IpcSender<BluetoothResult<BluetoothCharacteristicMsg>>) {
+        if !self.cached_services.contains_key(&service_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let characteristics = self.get_gatt_characteristics_by_uuid(&mut adapter, &service_id, &uuid);
         if characteristics.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
         for characteristic in characteristics {
             if let Ok(uuid) = characteristic.get_uuid() {
                 let properties = self.get_characteristic_properties(&characteristic);
@@ -784,16 +801,19 @@ impl BluetoothManager {
         }
         return drop(sender.send(Err(BluetoothError::NotFound)));
     }
 
     fn get_characteristics(&mut self,
                            service_id: String,
                            uuid: Option<String>,
                            sender: IpcSender<BluetoothResult<BluetoothCharacteristicsMsg>>) {
+        if !self.cached_services.contains_key(&service_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let characteristics = match uuid {
             Some(id) => self.get_gatt_characteristics_by_uuid(&mut adapter, &service_id, &id),
             None => self.get_and_cache_gatt_characteristics(&mut adapter, &service_id),
         };
         if characteristics.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
@@ -812,27 +832,31 @@ impl BluetoothManager {
                                     notify: properties.contains(NOTIFY),
                                     indicate: properties.contains(INDICATE),
                                     authenticated_signed_writes: properties.contains(AUTHENTICATED_SIGNED_WRITES),
                                     reliable_write: properties.contains(RELIABLE_WRITE),
                                     writable_auxiliaries: properties.contains(WRITABLE_AUXILIARIES),
                                 });
             }
         }
+        characteristics_vec.retain(|c| !uuid_is_blacklisted(&c.uuid, Blacklist::All));
         if characteristics_vec.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
 
         let _ = sender.send(Ok(characteristics_vec));
     }
 
     fn get_descriptor(&mut self,
                       characteristic_id: String,
                       uuid: String,
                       sender: IpcSender<BluetoothResult<BluetoothDescriptorMsg>>) {
+        if !self.cached_characteristics.contains_key(&characteristic_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let descriptors = self.get_gatt_descriptors_by_uuid(&mut adapter, &characteristic_id, &uuid);
         if descriptors.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
         for descriptor in descriptors {
             if let Ok(uuid) = descriptor.get_uuid() {
                 return drop(sender.send(Ok(BluetoothDescriptorMsg {
@@ -843,16 +867,19 @@ impl BluetoothManager {
         }
         return drop(sender.send(Err(BluetoothError::NotFound)));
     }
 
     fn get_descriptors(&mut self,
                        characteristic_id: String,
                        uuid: Option<String>,
                        sender: IpcSender<BluetoothResult<BluetoothDescriptorsMsg>>) {
+        if !self.cached_characteristics.contains_key(&characteristic_id) {
+            return drop(sender.send(Err(BluetoothError::InvalidState)));
+        }
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let descriptors = match uuid {
             Some(id) => self.get_gatt_descriptors_by_uuid(&mut adapter, &characteristic_id, &id),
             None => self.get_and_cache_gatt_descriptors(&mut adapter, &characteristic_id),
         };
         if descriptors.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
@@ -860,43 +887,44 @@ impl BluetoothManager {
         for descriptor in descriptors {
             if let Ok(uuid) = descriptor.get_uuid() {
                 descriptors_vec.push(BluetoothDescriptorMsg {
                                          uuid: uuid,
                                          instance_id: descriptor.get_id(),
                                      });
             }
         }
+        descriptors_vec.retain(|d| !uuid_is_blacklisted(&d.uuid, Blacklist::All));
         if descriptors_vec.is_empty() {
             return drop(sender.send(Err(BluetoothError::NotFound)));
         }
         let _ = sender.send(Ok(descriptors_vec));
     }
 
     fn read_value(&mut self, id: String, sender: IpcSender<BluetoothResult<Vec<u8>>>) {
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let mut value = self.get_gatt_characteristic(&mut adapter, &id)
                             .map(|c| c.read_value().unwrap_or(vec![]));
         if value.is_none() {
             value = self.get_gatt_descriptor(&mut adapter, &id)
                         .map(|d| d.read_value().unwrap_or(vec![]));
         }
-        let _ = sender.send(value.ok_or(BluetoothError::NotSupported));
+        let _ = sender.send(value.ok_or(BluetoothError::InvalidState));
     }
 
     fn write_value(&mut self, id: String, value: Vec<u8>, sender: IpcSender<BluetoothResult<bool>>) {
         let mut adapter = get_adapter_or_return_error!(self, sender);
         let mut result = self.get_gatt_characteristic(&mut adapter, &id)
                              .map(|c| c.write_value(value.clone()));
         if result.is_none() {
             result = self.get_gatt_descriptor(&mut adapter, &id)
                          .map(|d| d.write_value(value.clone()));
         }
         let message = match result {
             Some(v) => match v {
                 Ok(_) => Ok(true),
                 Err(_) => return drop(sender.send(Err(BluetoothError::NotSupported))),
             },
-            None => return drop(sender.send(Err(BluetoothError::NotSupported))),
+            None => return drop(sender.send(Err(BluetoothError::InvalidState))),
         };
         let _ = sender.send(message);
     }
 }
--- a/servo/components/bluetooth_traits/Cargo.toml
+++ b/servo/components/bluetooth_traits/Cargo.toml
@@ -6,10 +6,12 @@ license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "bluetooth_traits"
 path = "lib.rs"
 
 [dependencies]
 ipc-channel = "0.5"
+regex = "0.1.43"
 serde = "0.8"
 serde_derive = "0.8"
+util = {path = "../util"}
rename from servo/components/script/bluetooth_blacklist.rs
rename to servo/components/bluetooth_traits/blacklist.rs
--- a/servo/components/bluetooth_traits/lib.rs
+++ b/servo/components/bluetooth_traits/lib.rs
@@ -1,30 +1,34 @@
 /* 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/. */
 
 #![feature(proc_macro)]
 
 extern crate ipc_channel;
+extern crate regex;
 #[macro_use]
 extern crate serde_derive;
+extern crate util;
 
+pub mod blacklist;
 pub mod scanfilter;
 
 use ipc_channel::ipc::IpcSender;
 use scanfilter::RequestDeviceoptions;
 
 #[derive(Deserialize, Serialize)]
 pub enum BluetoothError {
     Type(String),
     Network,
     NotFound,
     NotSupported,
     Security,
+    InvalidState,
 }
 
 #[derive(Deserialize, Serialize)]
 pub struct BluetoothDeviceMsg {
     // Bluetooth Device properties
     pub id: String,
     pub name: Option<String>,
     // Advertisiong Data properties
--- a/servo/components/bluetooth_traits/scanfilter.rs
+++ b/servo/components/bluetooth_traits/scanfilter.rs
@@ -20,41 +20,41 @@ impl ServiceUUIDSequence {
 
     fn get_services_set(&self) -> HashSet<String> {
         self.0.iter().map(String::clone).collect()
     }
 }
 
 #[derive(Deserialize, Serialize)]
 pub struct BluetoothScanfilter {
-    name: String,
+    name: Option<String>,
     name_prefix: String,
     services: ServiceUUIDSequence,
     manufacturer_id: Option<u16>,
     service_data_uuid: String,
 }
 
 impl BluetoothScanfilter {
-    pub fn new(name: String,
+    pub fn new(name: Option<String>,
                name_prefix: String,
                services: Vec<String>,
                manufacturer_id: Option<u16>,
                service_data_uuid: String)
                -> BluetoothScanfilter {
         BluetoothScanfilter {
             name: name,
             name_prefix: name_prefix,
             services: ServiceUUIDSequence::new(services),
             manufacturer_id: manufacturer_id,
             service_data_uuid: service_data_uuid,
         }
     }
 
-    pub fn get_name(&self) -> &str {
-        &self.name
+    pub fn get_name(&self) -> Option<&str> {
+        self.name.as_ref().map(|s| s.as_str())
     }
 
     pub fn get_name_prefix(&self) -> &str {
         &self.name_prefix
     }
 
     pub fn get_services(&self) -> &[String] {
         &self.services.0
@@ -64,22 +64,22 @@ impl BluetoothScanfilter {
         self.manufacturer_id
     }
 
     pub fn get_service_data_uuid(&self) -> &str {
         &self.service_data_uuid
     }
 
     pub fn is_empty_or_invalid(&self) -> bool {
-        (self.name.is_empty() &&
+        (self.name.is_none() &&
          self.name_prefix.is_empty() &&
          self.get_services().is_empty() &&
          self.manufacturer_id.is_none() &&
          self.service_data_uuid.is_empty()) ||
-        self.name.len() > MAX_NAME_LENGTH ||
+        self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH ||
         self.name_prefix.len() > MAX_NAME_LENGTH
     }
 }
 
 #[derive(Deserialize, Serialize)]
 pub struct BluetoothScanfilterSequence(Vec<BluetoothScanfilter>);
 
 impl BluetoothScanfilterSequence {
--- a/servo/components/script/dom/bluetooth.rs
+++ b/servo/components/script/dom/bluetooth.rs
@@ -1,36 +1,40 @@
 /* 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_blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::{BluetoothError, BluetoothMethodMsg};
+use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence};
 use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence};
 use core::clone::Clone;
+use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothMethods, BluetoothRequestDeviceFilter};
 use dom::bindings::codegen::Bindings::BluetoothBinding::RequestDeviceOptions;
-use dom::bindings::error::Error::{self, Security, Type};
+use dom::bindings::error::Error::{self, NotFound, Security, Type};
 use dom::bindings::error::Fallible;
-use dom::bindings::js::Root;
+use dom::bindings::js::{JS, MutHeap, Root};
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::bluetoothadvertisingdata::BluetoothAdvertisingData;
 use dom::bluetoothdevice::BluetoothDevice;
+use dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic;
+use dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor;
+use dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
 use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
 use dom::globalscope::GlobalScope;
 use dom::promise::Promise;
 use ipc_channel::ipc::{self, IpcSender};
 use js::conversions::ToJSValConvertible;
+use std::collections::HashMap;
 use std::rc::Rc;
 
 const FILTER_EMPTY_ERROR: &'static str = "'filters' member, if present, must be nonempty to find any devices.";
 const FILTER_ERROR: &'static str = "A filter must restrict the devices in some way.";
-const FILTER_NAME_TOO_LONG_ERROR: &'static str = "A 'name' or 'namePrefix' can't be longer then 29 bytes.";
 // 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name.
 const MAX_DEVICE_NAME_LENGTH: usize = 248;
 // A device name can never be longer than 29 bytes.
 // An advertising packet is at most 31 bytes long.
 // The length and identifier of the length field take 2 bytes.
 // That leaves 29 bytes for the name.
 const MAX_FILTER_NAME_LENGTH: usize = 29;
 const NAME_PREFIX_ERROR: &'static str = "'namePrefix', if present, must be nonempty.";
@@ -38,31 +42,52 @@ const NAME_TOO_LONG_ERROR: &'static str 
 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.";
 
 // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
 #[dom_struct]
 pub struct Bluetooth {
     reflector_: Reflector,
+    device_instance_map: DOMRefCell<HashMap<String, MutHeap<JS<BluetoothDevice>>>>,
+    service_instance_map: DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTService>>>>,
+    characteristic_instance_map: DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTCharacteristic>>>>,
+    descriptor_instance_map: DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTDescriptor>>>>,
 }
 
 impl Bluetooth {
     pub fn new_inherited() -> Bluetooth {
         Bluetooth {
             reflector_: Reflector::new(),
+            device_instance_map: DOMRefCell::new(HashMap::new()),
+            service_instance_map: DOMRefCell::new(HashMap::new()),
+            characteristic_instance_map: DOMRefCell::new(HashMap::new()),
+            descriptor_instance_map: DOMRefCell::new(HashMap::new()),
         }
     }
 
     pub fn new(global: &GlobalScope) -> Root<Bluetooth> {
         reflect_dom_object(box Bluetooth::new_inherited(),
                            global,
                            BluetoothBinding::Wrap)
     }
 
+    pub fn get_service_map(&self) -> &DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTService>>>> {
+        &self.service_instance_map
+    }
+
+    pub fn get_characteristic_map(&self)
+            -> &DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTCharacteristic>>>> {
+        &self.characteristic_instance_map
+    }
+
+    pub fn get_descriptor_map(&self) -> &DOMRefCell<HashMap<String, MutHeap<JS<BluetoothRemoteGATTDescriptor>>>> {
+        &self.descriptor_instance_map
+    }
+
     fn get_bluetooth_thread(&self) -> IpcSender<BluetoothMethodMsg> {
         self.global().as_window().bluetooth_thread()
     }
 
     fn request_device(&self, option: &RequestDeviceOptions) -> Fallible<Root<BluetoothDevice>> {
         // Step 1.
         // TODO(#4282): Reject promise.
         if (option.filters.is_some() && option.acceptAllDevices) ||
@@ -98,25 +123,31 @@ impl Bluetooth {
 
         // TODO: Step 9-10: Implement the permission API.
 
         // Step 11: This step is optional.
 
         // Step 12-13.
         match device {
             Ok(device) => {
-                let global = self.global();
-                let ad_data = BluetoothAdvertisingData::new(&global,
+                let mut device_instance_map = self.device_instance_map.borrow_mut();
+                if let Some(existing_device) = device_instance_map.get(&device.id.clone()) {
+                    return Ok(existing_device.get());
+                }
+                let ad_data = BluetoothAdvertisingData::new(&self.global(),
                                                             device.appearance,
                                                             device.tx_power,
                                                             device.rssi);
-                Ok(BluetoothDevice::new(&global,
-                                        DOMString::from(device.id),
-                                        device.name.map(DOMString::from),
-                                        &ad_data))
+                let bt_device = BluetoothDevice::new(&self.global(),
+                                                     DOMString::from(device.id.clone()),
+                                                     device.name.map(DOMString::from),
+                                                     &ad_data,
+                                                     &self);
+                device_instance_map.insert(device.id, MutHeap::new(&bt_device));
+                Ok(bt_device)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
 
     }
 }
@@ -208,37 +239,37 @@ fn canonicalize_filter(filter: &Bluetoot
     let name = match filter.name {
         Some(ref name) => {
             // Step 2.4.4.1.
             // Note: DOMString::len() gives back the size in bytes.
             if name.len() > MAX_DEVICE_NAME_LENGTH {
                 return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
             }
             if name.len() > MAX_FILTER_NAME_LENGTH {
-                return Err(Type(FILTER_NAME_TOO_LONG_ERROR.to_owned()));
+                return Err(NotFound);
             }
 
             // Step 2.4.4.2.
-            name.to_string()
+            Some(name.to_string())
         },
-        None => String::new(),
+        None => None,
     };
 
     // Step 2.4.5.
     let name_prefix = match filter.namePrefix {
         Some(ref name_prefix) => {
             // Step 2.4.5.1.
             if name_prefix.is_empty() {
                 return Err(Type(NAME_PREFIX_ERROR.to_owned()));
             }
             if name_prefix.len() > MAX_DEVICE_NAME_LENGTH {
                 return Err(Type(NAME_TOO_LONG_ERROR.to_owned()));
             }
             if name_prefix.len() > MAX_FILTER_NAME_LENGTH {
-                return Err(Type(FILTER_NAME_TOO_LONG_ERROR.to_owned()));
+                return Err(NotFound);
             }
 
             // Step 2.4.5.2.
             name_prefix.to_string()
         },
         None => String::new(),
     };
 
@@ -284,16 +315,17 @@ pub fn result_to_promise<T: ToJSValConve
 impl From<BluetoothError> for Error {
     fn from(error: BluetoothError) -> Self {
         match error {
             BluetoothError::Type(message) => Error::Type(message),
             BluetoothError::Network => Error::Network,
             BluetoothError::NotFound => Error::NotFound,
             BluetoothError::NotSupported => Error::NotSupported,
             BluetoothError::Security => Error::Security,
+            BluetoothError::InvalidState => Error::InvalidState,
         }
     }
 }
 
 impl BluetoothMethods for Bluetooth {
     #[allow(unrooted_must_root)]
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice
     fn RequestDevice(&self, option: &RequestDeviceOptions) -> Rc<Promise> {
--- a/servo/components/script/dom/bluetoothdevice.rs
+++ b/servo/components/script/dom/bluetoothdevice.rs
@@ -2,55 +2,65 @@
  * 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::BluetoothDeviceBinding;
 use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
 use dom::bindings::js::{JS, Root, MutHeap, MutNullableHeap};
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
+use dom::bluetooth::Bluetooth;
 use dom::bluetoothadvertisingdata::BluetoothAdvertisingData;
 use dom::bluetoothremotegattserver::BluetoothRemoteGATTServer;
 use dom::globalscope::GlobalScope;
 
 // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice
 #[dom_struct]
 pub struct BluetoothDevice {
     reflector_: Reflector,
     id: DOMString,
     name: Option<DOMString>,
     ad_data: MutHeap<JS<BluetoothAdvertisingData>>,
     gatt: MutNullableHeap<JS<BluetoothRemoteGATTServer>>,
+    context: MutHeap<JS<Bluetooth>>,
 }
 
 impl BluetoothDevice {
     pub fn new_inherited(id: DOMString,
                          name: Option<DOMString>,
-                         ad_data: &BluetoothAdvertisingData)
+                         ad_data: &BluetoothAdvertisingData,
+                         context: &Bluetooth)
                          -> BluetoothDevice {
         BluetoothDevice {
             reflector_: Reflector::new(),
             id: id,
             name: name,
             ad_data: MutHeap::new(ad_data),
             gatt: Default::default(),
+            context: MutHeap::new(context),
         }
     }
 
     pub fn new(global: &GlobalScope,
                id: DOMString,
                name: Option<DOMString>,
-               adData: &BluetoothAdvertisingData)
+               adData: &BluetoothAdvertisingData,
+               context: &Bluetooth)
                -> Root<BluetoothDevice> {
         reflect_dom_object(box BluetoothDevice::new_inherited(id,
                                                               name,
-                                                              adData),
+                                                              adData,
+                                                              context),
                            global,
                            BluetoothDeviceBinding::Wrap)
     }
+
+    pub fn get_context(&self) -> Root<Bluetooth> {
+        self.context.get()
+    }
 }
 
 impl BluetoothDeviceMethods for BluetoothDevice {
      // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-id
     fn Id(&self) -> DOMString {
         self.id.clone()
     }
 
--- a/servo/components/script/dom/bluetoothremotegattcharacteristic.rs
+++ b/servo/components/script/dom/bluetoothremotegattcharacteristic.rs
@@ -1,14 +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/. */
 
-use bluetooth_blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::BluetoothMethodMsg;
+use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted};
 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;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
@@ -82,26 +82,36 @@ impl BluetoothRemoteGATTCharacteristic {
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor
     fn get_descriptor(&self, descriptor: BluetoothDescriptorUUID) -> Fallible<Root<BluetoothRemoteGATTDescriptor>> {
         let uuid = try!(BluetoothUUID::descriptor(descriptor)).to_string();
         if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
             return Err(Security)
         }
+        if !self.Service().Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetDescriptor(self.get_instance_id(), uuid, sender)).unwrap();
         let descriptor = receiver.recv().unwrap();
         match descriptor {
             Ok(descriptor) => {
-                Ok(BluetoothRemoteGATTDescriptor::new(&self.global(),
-                                                      self,
-                                                      DOMString::from(descriptor.uuid),
-                                                      descriptor.instance_id))
+                let context = self.service.get().get_device().get_context();
+                let mut descriptor_map = context.get_descriptor_map().borrow_mut();
+                if let Some(existing_descriptor) = descriptor_map.get(&descriptor.instance_id) {
+                    return Ok(existing_descriptor.get());
+                }
+                let bt_descriptor = BluetoothRemoteGATTDescriptor::new(&self.global(),
+                                                                       self,
+                                                                       DOMString::from(descriptor.uuid),
+                                                                       descriptor.instance_id.clone());
+                descriptor_map.insert(descriptor.instance_id, MutHeap::new(&bt_descriptor));
+                Ok(bt_descriptor)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors
@@ -112,28 +122,44 @@ impl BluetoothRemoteGATTCharacteristic {
         if let Some(d) = descriptor {
             uuid = Some(try!(BluetoothUUID::descriptor(d)).to_string());
             if let Some(ref uuid) = uuid {
                 if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
                     return Err(Security)
                 }
             }
         };
+        if !self.Service().Device().Gatt().Connected() {
+            return Err(Network)
+        }
+        let mut descriptors = vec!();
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetDescriptors(self.get_instance_id(), uuid, sender)).unwrap();
         let descriptors_vec = receiver.recv().unwrap();
         match descriptors_vec {
             Ok(descriptor_vec) => {
-                Ok(descriptor_vec.into_iter()
-                                 .map(|desc| BluetoothRemoteGATTDescriptor::new(&self.global(),
-                                                                                self,
-                                                                                DOMString::from(desc.uuid),
-                                                                                desc.instance_id))
-                                 .collect())
+                let context = self.service.get().get_device().get_context();
+                let mut descriptor_map = context.get_descriptor_map().borrow_mut();
+                for descriptor in descriptor_vec {
+                    let bt_descriptor = match descriptor_map.get(&descriptor.instance_id) {
+                        Some(existing_descriptor) => existing_descriptor.get(),
+                        None => {
+                            BluetoothRemoteGATTDescriptor::new(&self.global(),
+                                                               self,
+                                                               DOMString::from(descriptor.uuid),
+                                                               descriptor.instance_id.clone())
+                        },
+                    };
+                    if !descriptor_map.contains_key(&descriptor.instance_id) {
+                        descriptor_map.insert(descriptor.instance_id, MutHeap::new(&bt_descriptor));
+                    }
+                    descriptors.push(bt_descriptor);
+                }
+                Ok(descriptors)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue
@@ -177,20 +203,20 @@ impl BluetoothRemoteGATTCharacteristic {
 
         if !(self.Properties().Write() ||
              self.Properties().WriteWithoutResponse() ||
              self.Properties().AuthenticatedSignedWrites()) {
             return Err(NotSupported)
         }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
-            BluetoothMethodMsg::WriteValue(self.get_instance_id(), value, sender)).unwrap();
+            BluetoothMethodMsg::WriteValue(self.get_instance_id(), value.clone(), sender)).unwrap();
         let result = receiver.recv().unwrap();
         match result {
-            Ok(_) => Ok(()),
+            Ok(_) => Ok(*self.value.borrow_mut() = Some(ByteString::new(value))),
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 }
 
 impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteristic {
--- a/servo/components/script/dom/bluetoothremotegattdescriptor.rs
+++ b/servo/components/script/dom/bluetoothremotegattdescriptor.rs
@@ -1,14 +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/. */
 
-use bluetooth_blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::BluetoothMethodMsg;
+use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted};
 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;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding::BluetoothRemoteGATTDescriptorMethods;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods;
@@ -100,20 +100,20 @@ impl BluetoothRemoteGATTDescriptor {
         if value.len() > MAXIMUM_ATTRIBUTE_LENGTH {
             return Err(InvalidModification)
         }
         if !self.Characteristic().Service().Device().Gatt().Connected() {
             return Err(Network)
         }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
-            BluetoothMethodMsg::WriteValue(self.get_instance_id(), value, sender)).unwrap();
+            BluetoothMethodMsg::WriteValue(self.get_instance_id(), value.clone(), sender)).unwrap();
         let result = receiver.recv().unwrap();
         match result {
-            Ok(_) => Ok(()),
+            Ok(_) => Ok(*self.value.borrow_mut() = Some(ByteString::new(value))),
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 }
 
 impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor {
--- a/servo/components/script/dom/bluetoothremotegattserver.rs
+++ b/servo/components/script/dom/bluetoothremotegattserver.rs
@@ -1,19 +1,19 @@
 /* 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_blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::BluetoothMethodMsg;
+use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted};
 use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding;
 use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods;
 use dom::bindings::error::{ErrorResult, Fallible};
-use dom::bindings::error::Error::{self, Security};
+use dom::bindings::error::Error::{self, Network, Security};
 use dom::bindings::js::{JS, MutHeap, Root};
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::bluetooth::result_to_promise;
 use dom::bluetoothdevice::BluetoothDevice;
 use dom::bluetoothremotegattservice::BluetoothRemoteGATTService;
 use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID};
 use dom::globalscope::GlobalScope;
@@ -67,27 +67,37 @@ impl BluetoothRemoteGATTServer {
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice
     fn get_primary_service(&self, service: BluetoothServiceUUID) -> Fallible<Root<BluetoothRemoteGATTService>> {
         let uuid = try!(BluetoothUUID::service(service)).to_string();
         if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
             return Err(Security)
         }
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetPrimaryService(String::from(self.Device().Id()), uuid, sender)).unwrap();
         let service = receiver.recv().unwrap();
         match service {
             Ok(service) => {
-                Ok(BluetoothRemoteGATTService::new(&self.global(),
-                                                   &self.device.get(),
-                                                   DOMString::from(service.uuid),
-                                                   service.is_primary,
-                                                   service.instance_id))
+                let context = self.device.get().get_context();
+                let mut service_map = context.get_service_map().borrow_mut();
+                if let Some(existing_service) = service_map.get(&service.instance_id) {
+                    return Ok(existing_service.get());
+                }
+                let bt_service = BluetoothRemoteGATTService::new(&self.global(),
+                                                                 &self.device.get(),
+                                                                 DOMString::from(service.uuid),
+                                                                 service.is_primary,
+                                                                 service.instance_id.clone());
+                service_map.insert(service.instance_id, MutHeap::new(&bt_service));
+                Ok(bt_service)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices
@@ -98,29 +108,45 @@ impl BluetoothRemoteGATTServer {
         if let Some(s) = service {
             uuid = Some(try!(BluetoothUUID::service(s)).to_string());
             if let Some(ref uuid) = uuid {
                 if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
                     return Err(Security)
                 }
             }
         };
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
+        let mut services = vec!();
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetPrimaryServices(String::from(self.Device().Id()), uuid, sender)).unwrap();
         let services_vec = receiver.recv().unwrap();
         match services_vec {
             Ok(service_vec) => {
-                Ok(service_vec.into_iter()
-                              .map(|service| BluetoothRemoteGATTService::new(&self.global(),
-                                                                             &self.device.get(),
-                                                                             DOMString::from(service.uuid),
-                                                                             service.is_primary,
-                                                                             service.instance_id))
-                              .collect())
+                let context = self.device.get().get_context();
+                let mut service_map = context.get_service_map().borrow_mut();
+                for service in service_vec {
+                    let bt_service = match service_map.get(&service.instance_id) {
+                        Some(existing_service) => existing_service.get(),
+                        None => {
+                            BluetoothRemoteGATTService::new(&self.global(),
+                                                            &self.device.get(),
+                                                            DOMString::from(service.uuid),
+                                                            service.is_primary,
+                                                            service.instance_id.clone())
+                        },
+                    };
+                    if !service_map.contains_key(&service.instance_id) {
+                        service_map.insert(service.instance_id, MutHeap::new(&bt_service));
+                    }
+                    services.push(bt_service);
+                }
+                Ok(services)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 }
 
--- a/servo/components/script/dom/bluetoothremotegattservice.rs
+++ b/servo/components/script/dom/bluetoothremotegattservice.rs
@@ -1,17 +1,19 @@
 /* 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_blacklist::{Blacklist, uuid_is_blacklisted};
 use bluetooth_traits::BluetoothMethodMsg;
+use bluetooth_traits::blacklist::{Blacklist, uuid_is_blacklisted};
+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;
-use dom::bindings::error::Error::{self, Security};
+use dom::bindings::error::Error::{self, Network, Security};
 use dom::bindings::error::Fallible;
 use dom::bindings::js::{JS, MutHeap, Root};
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::bluetooth::result_to_promise;
 use dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties;
 use dom::bluetoothdevice::BluetoothDevice;
 use dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic;
@@ -55,54 +57,68 @@ impl BluetoothRemoteGATTService {
         reflect_dom_object(box BluetoothRemoteGATTService::new_inherited(device,
                                                                          uuid,
                                                                          isPrimary,
                                                                          instanceID),
                            global,
                            BluetoothRemoteGATTServiceBinding::Wrap)
     }
 
+    pub fn get_device(&self) -> Root<BluetoothDevice> {
+        self.device.get()
+    }
+
     fn get_bluetooth_thread(&self) -> IpcSender<BluetoothMethodMsg> {
         self.global().as_window().bluetooth_thread()
     }
 
     fn get_instance_id(&self) -> String {
         self.instance_id.clone()
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristic
     fn get_characteristic(&self,
                           characteristic: BluetoothCharacteristicUUID)
                           -> Fallible<Root<BluetoothRemoteGATTCharacteristic>> {
         let uuid = try!(BluetoothUUID::characteristic(characteristic)).to_string();
         if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
             return Err(Security)
         }
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetCharacteristic(self.get_instance_id(), uuid, sender)).unwrap();
         let characteristic = receiver.recv().unwrap();
         match characteristic {
             Ok(characteristic) => {
+                let context = self.device.get().get_context();
+                let mut characteristic_map = context.get_characteristic_map().borrow_mut();
+                if let Some(existing_characteristic) = characteristic_map.get(&characteristic.instance_id) {
+                    return Ok(existing_characteristic.get());
+                }
                 let global = self.global();
                 let properties = BluetoothCharacteristicProperties::new(&global,
                                                                         characteristic.broadcast,
                                                                         characteristic.read,
                                                                         characteristic.write_without_response,
                                                                         characteristic.write,
                                                                         characteristic.notify,
                                                                         characteristic.indicate,
                                                                         characteristic.authenticated_signed_writes,
                                                                         characteristic.reliable_write,
                                                                         characteristic.writable_auxiliaries);
-                Ok(BluetoothRemoteGATTCharacteristic::new(&global,
-                                                          self,
-                                                          DOMString::from(characteristic.uuid),
-                                                          &properties,
-                                                          characteristic.instance_id))
+                let bt_characteristic = BluetoothRemoteGATTCharacteristic::new(&global,
+                                                                               self,
+                                                                               DOMString::from(characteristic.uuid),
+                                                                               &properties,
+                                                                               characteristic.instance_id.clone());
+                characteristic_map.insert(characteristic.instance_id, MutHeap::new(&bt_characteristic));
+                Ok(bt_characteristic)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristics
@@ -113,40 +129,55 @@ impl BluetoothRemoteGATTService {
         if let Some(c) = characteristic {
             uuid = Some(try!(BluetoothUUID::characteristic(c)).to_string());
             if let Some(ref uuid) = uuid {
                 if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
                     return Err(Security)
                 }
             }
         };
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let mut characteristics = vec!();
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetCharacteristics(self.get_instance_id(), uuid, sender)).unwrap();
         let characteristics_vec = receiver.recv().unwrap();
         match characteristics_vec {
             Ok(characteristic_vec) => {
+                let context = self.device.get().get_context();
+                let mut characteristic_map = context.get_characteristic_map().borrow_mut();
                 for characteristic in characteristic_vec {
-                    let global = self.global();
-                    let properties = BluetoothCharacteristicProperties::new(&global,
-                                                                            characteristic.broadcast,
-                                                                            characteristic.read,
-                                                                            characteristic.write_without_response,
-                                                                            characteristic.write,
-                                                                            characteristic.notify,
-                                                                            characteristic.indicate,
-                                                                            characteristic.authenticated_signed_writes,
-                                                                            characteristic.reliable_write,
-                                                                            characteristic.writable_auxiliaries);
-                    characteristics.push(BluetoothRemoteGATTCharacteristic::new(&global,
-                                                                                self,
-                                                                                DOMString::from(characteristic.uuid),
-                                                                                &properties,
-                                                                                characteristic.instance_id));
+                    let bt_characteristic = match characteristic_map.get(&characteristic.instance_id) {
+                        Some(existing_characteristic) => existing_characteristic.get(),
+                        None => {
+                            let properties =
+                                BluetoothCharacteristicProperties::new(&self.global(),
+                                                                       characteristic.broadcast,
+                                                                       characteristic.read,
+                                                                       characteristic.write_without_response,
+                                                                       characteristic.write,
+                                                                       characteristic.notify,
+                                                                       characteristic.indicate,
+                                                                       characteristic.authenticated_signed_writes,
+                                                                       characteristic.reliable_write,
+                                                                       characteristic.writable_auxiliaries);
+
+                            BluetoothRemoteGATTCharacteristic::new(&self.global(),
+                                                                   self,
+                                                                   DOMString::from(characteristic.uuid),
+                                                                   &properties,
+                                                                   characteristic.instance_id.clone())
+                        },
+                    };
+                    if !characteristic_map.contains_key(&characteristic.instance_id) {
+                        characteristic_map.insert(characteristic.instance_id, MutHeap::new(&bt_characteristic));
+                    }
+                    characteristics.push(bt_characteristic);
                 }
                 Ok(characteristics)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
@@ -154,29 +185,39 @@ impl BluetoothRemoteGATTService {
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservice
     fn get_included_service(&self,
                            service: BluetoothServiceUUID)
                            -> Fallible<Root<BluetoothRemoteGATTService>> {
         let uuid = try!(BluetoothUUID::service(service)).to_string();
         if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
             return Err(Security)
         }
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetIncludedService(self.get_instance_id(),
                                                    uuid,
                                                    sender)).unwrap();
         let service = receiver.recv().unwrap();
         match service {
             Ok(service) => {
-                Ok(BluetoothRemoteGATTService::new(&self.global(),
-                                                   &self.device.get(),
-                                                   DOMString::from(service.uuid),
-                                                   service.is_primary,
-                                                   service.instance_id))
+                let context = self.device.get().get_context();
+                let mut service_map = context.get_service_map().borrow_mut();
+                if let Some(existing_service) = service_map.get(&service.instance_id) {
+                    return Ok(existing_service.get());
+                }
+                let bt_service = BluetoothRemoteGATTService::new(&self.global(),
+                                                                 &self.device.get(),
+                                                                 DOMString::from(service.uuid),
+                                                                 service.is_primary,
+                                                                 service.instance_id.clone());
+                service_map.insert(service.instance_id, MutHeap::new(&bt_service));
+                Ok(bt_service)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 
     // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservices
@@ -187,31 +228,47 @@ impl BluetoothRemoteGATTService {
         if let Some(s) = service {
             uuid = Some(try!(BluetoothUUID::service(s)).to_string());
             if let Some(ref uuid) = uuid {
                 if uuid_is_blacklisted(uuid.as_ref(), Blacklist::All) {
                     return Err(Security)
                 }
             }
         };
+        if !self.Device().Gatt().Connected() {
+            return Err(Network)
+        }
         let (sender, receiver) = ipc::channel().unwrap();
         self.get_bluetooth_thread().send(
             BluetoothMethodMsg::GetIncludedServices(self.get_instance_id(),
                                                     uuid,
                                                     sender)).unwrap();
         let services_vec = receiver.recv().unwrap();
+        let mut services = vec!();
         match services_vec {
             Ok(service_vec) => {
-                Ok(service_vec.into_iter()
-                              .map(|service| BluetoothRemoteGATTService::new(&self.global(),
-                                                                             &self.device.get(),
-                                                                             DOMString::from(service.uuid),
-                                                                             service.is_primary,
-                                                                             service.instance_id))
-                              .collect())
+                let context = self.device.get().get_context();
+                let mut service_map = context.get_service_map().borrow_mut();
+                for service in service_vec {
+                    let bt_service = match service_map.get(&service.instance_id) {
+                        Some(existing_service) => existing_service.get(),
+                        None => {
+                            BluetoothRemoteGATTService::new(&self.global(),
+                                                            &self.device.get(),
+                                                            DOMString::from(service.uuid),
+                                                            service.is_primary,
+                                                            service.instance_id.clone())
+                        },
+                    };
+                    if !service_map.contains_key(&service.instance_id) {
+                        service_map.insert(service.instance_id, MutHeap::new(&bt_service));
+                    }
+                    services.push(bt_service);
+                }
+                Ok(services)
             },
             Err(error) => {
                 Err(Error::from(error))
             },
         }
     }
 }
 
--- a/servo/components/script/dom/webidls/BluetoothDevice.webidl
+++ b/servo/components/script/dom/webidls/BluetoothDevice.webidl
@@ -3,17 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice
 
 [Pref="dom.bluetooth.enabled"]
 interface BluetoothDevice {
     readonly attribute DOMString id;
     readonly attribute DOMString? name;
+    // TODO: remove this after BluetoothAdvertisingEvent implemented.
     readonly attribute BluetoothAdvertisingData adData;
     readonly attribute BluetoothRemoteGATTServer gatt;
-    // readonly attribute FrozenArray[] uuids;
+
+    // Promise<void> watchAdvertisements();
+    // void unwatchAdvertisements();
+    // readonly attribute boolean watchingAdvertisements;
 };
 
 // BluetoothDevice implements EventTarget;
 // BluetoothDevice implements BluetoothDeviceEventHandlers;
 // BluetoothDevice implements CharacteristicEventHandlers;
 // BluetoothDevice implements ServiceEventHandlers;
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -91,17 +91,16 @@ extern crate tinyfiledialogs;
 extern crate url;
 #[macro_use]
 extern crate util;
 extern crate uuid;
 extern crate webrender_traits;
 extern crate websocket;
 extern crate xml5ever;
 
-pub mod bluetooth_blacklist;
 mod body;
 pub mod clipboard_provider;
 mod devtools;
 pub mod document_loader;
 #[macro_use]
 pub mod dom;
 pub mod fetch;
 pub mod layout_wrapper;
--- a/servo/components/servo/Cargo.lock
+++ b/servo/components/servo/Cargo.lock
@@ -202,18 +202,20 @@ dependencies = [
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bluetooth_traits"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.1.76 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "util 0.0.1",
 ]
 
 [[package]]
 name = "blurdroid"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
--- a/servo/ports/cef/Cargo.lock
+++ b/servo/ports/cef/Cargo.lock
@@ -173,18 +173,20 @@ dependencies = [
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bluetooth_traits"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.1.76 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "util 0.0.1",
 ]
 
 [[package]]
 name = "blurdroid"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]