Bug 1063444 - Implement LE scan related methods and LE device event. r=joliu, r=mrbkap
authorJamin Liu <jaliu@mozilla.com>
Mon, 04 May 2015 18:59:02 +0800
changeset 273561 887ac5b1c5f9696882fa874830e74737872cbf72
parent 273560 fa72e341ac591a5d4bf920f12e5464056027f33f
child 273562 04ad216908dc77ee40d4e61808479c18b78ccbf0
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoliu, mrbkap
bugs1063444
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1063444 - Implement LE scan related methods and LE device event. r=joliu, r=mrbkap
dom/bindings/Bindings.conf
dom/bluetooth/BluetoothCommon.h
dom/bluetooth/BluetoothUtils.cpp
dom/bluetooth/BluetoothUtils.h
dom/bluetooth/bluedroid/BluetoothGattManager.cpp
dom/bluetooth/bluedroid/BluetoothGattManager.h
dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
dom/bluetooth/bluetooth1/BluetoothAdapter.cpp
dom/bluetooth/bluetooth1/BluetoothService.h
dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp
dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp
dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h
dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
dom/bluetooth/bluetooth2/BluetoothAdapter.h
dom/bluetooth/bluetooth2/BluetoothDevice.cpp
dom/bluetooth/bluetooth2/BluetoothDevice.h
dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp
dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
dom/bluetooth/bluetooth2/BluetoothGatt.cpp
dom/bluetooth/bluetooth2/BluetoothGatt.h
dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp
dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h
dom/bluetooth/bluetooth2/BluetoothService.h
dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
dom/bluetooth/bluez/BluetoothDBusService.cpp
dom/bluetooth/bluez/BluetoothDBusService.h
dom/bluetooth/moz.build
dom/events/test/test_all_synthetic_events.html
dom/webidl/BluetoothAdapter2.webidl
dom/webidl/BluetoothLeDeviceEvent.webidl
dom/webidl/moz.build
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -174,16 +174,20 @@ DOMInterfaces = {
 'BluetoothGattDescriptor': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattDescriptor',
 },
 
 'BluetoothGattService': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattService',
 },
 
+'BluetoothLeDeviceEvent': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothLeDeviceEvent',
+},
+
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
 },
 
 'BluetoothPairingHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle',
 },
 
--- a/dom/bluetooth/BluetoothCommon.h
+++ b/dom/bluetooth/BluetoothCommon.h
@@ -746,11 +746,27 @@ struct BluetoothGattNotifyParam {
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
   nsString mBdAddr;
   BluetoothGattServiceId mServiceId;
   BluetoothGattId mCharId;
   uint16_t mLength;
   bool mIsNotify;
 };
 
+/**
+ * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions
+ * Please refer to https://www.bluetooth.org/en-us/specification/\
+ * assigned-numbers/generic-access-profile
+ */
+enum BluetoothGapDataType {
+  GAP_INCOMPLETE_UUID16  = 0X02, // Incomplete List of 16-bit Service Class UUIDs
+  GAP_COMPLETE_UUID16    = 0X03, // Complete List of 16-bit Service Class UUIDs
+  GAP_INCOMPLETE_UUID32  = 0X04, // Incomplete List of 32-bit Service Class UUIDs
+  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs┬╗
+  GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs
+  GAP_COMPLETE_UUID128   = 0X07, // Complete List of 128-bit Service Class UUIDs
+  GAP_SHORTENED_NAME     = 0X08, // Shortened Local Name
+  GAP_COMPLETE_NAME      = 0X09, // Complete Local Name
+};
+
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothcommon_h__
--- a/dom/bluetooth/BluetoothUtils.cpp
+++ b/dom/bluetooth/BluetoothUtils.cpp
@@ -7,16 +7,17 @@
 #include "BluetoothUtils.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "jsapi.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsContentUtils.h"
 #include "nsISystemMessagesInternal.h"
+#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXULAppAPI.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 void
 UuidToString(const BluetoothUuid& aUuid, nsAString& aString)
 {
@@ -72,16 +73,37 @@ StringToUuid(const char* aString, Blueto
   memcpy(&aUuid.mUuid[4], &uuid1, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[6], &uuid2, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[8], &uuid3, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[10], &uuid4, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[14], &uuid5, sizeof(uint16_t));
 }
 
 void
+GenerateUuid(nsAString &aUuidString)
+{
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsID uuid;
+  rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+  char uuidBuffer[NSID_LENGTH];
+  uuid.ToProvidedString(uuidBuffer);
+  NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
+
+  // Remove {} and the null terminator
+  aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
+}
+
+void
 GeneratePathFromGattId(const BluetoothGattId& aId,
                        nsAString& aPath,
                        nsAString& aUuidStr)
 {
   ReversedUuidToString(aId.mUuid, aUuidStr);
 
   aPath.Assign(aUuidStr);
   aPath.AppendLiteral("_");
--- a/dom/bluetooth/BluetoothUtils.h
+++ b/dom/bluetooth/BluetoothUtils.h
@@ -46,16 +46,24 @@ ReversedUuidToString(const BluetoothUuid
  * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
  *
  * Note: This utility function is used by gecko internal only to convert uuid
  * string created by gecko back to BluetoothUuid representation.
  */
 void
 StringToUuid(const char* aString, BluetoothUuid& aUuid);
 
+/**
+ * Generate a random uuid.
+ *
+ * @param aUuidString [out] String to store the generated uuid.
+ */
+void
+GenerateUuid(nsAString &aUuidString);
+
 //
 // Generate bluetooth signal path from GattId
 //
 
 /**
  * Generate bluetooth signal path and UUID string from a GattId.
  *
  * @param aId      [in] GattId value to convert.
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -168,16 +168,17 @@ public:
     mDescriptors.Clear();
     mDiscoverRunnable = nullptr;
   }
 
   nsString mAppUuid;
   nsString mDeviceAddr;
   int mClientIf;
   int mConnId;
+  nsRefPtr<BluetoothReplyRunnable> mStartLeScanRunnable;
   nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDiscoverRunnable;
   nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
   nsRefPtr<BluetoothReplyRunnable> mRegisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDeregisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
 
@@ -483,16 +484,150 @@ BluetoothGattManager::UnregisterClient(i
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mUnregisterClientRunnable = aRunnable;
 
   sBluetoothGattClientInterface->UnregisterClient(
     aClientIf,
     new UnregisterClientResultHandler(client));
 }
 
+class BluetoothGattManager::StartLeScanResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  StartLeScanResultHandler(BluetoothGattClient* aClient)
+    : mClient(aClient)
+  { }
+
+  void Scan() override
+  {
+    MOZ_ASSERT(mClient > 0);
+
+    DispatchReplySuccess(mClient->mStartLeScanRunnable,
+                         BluetoothValue(mClient->mAppUuid));
+    mClient->mStartLeScanRunnable = nullptr;
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::StartLeScan failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mStartLeScanRunnable);
+
+    // Unregister client if startLeScan failed
+    if (mClient->mClientIf > 0) {
+      BluetoothGattManager* gattManager = BluetoothGattManager::Get();
+      NS_ENSURE_TRUE_VOID(gattManager);
+
+      nsRefPtr<BluetoothVoidReplyRunnable> result =
+        new BluetoothVoidReplyRunnable(nullptr);
+      gattManager->UnregisterClient(mClient->mClientIf, result);
+    }
+
+    DispatchReplyError(mClient->mStartLeScanRunnable,
+                       BluetoothValue(mClient->mAppUuid));
+    mClient->mStartLeScanRunnable = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+class BluetoothGattManager::StopLeScanResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+   StopLeScanResultHandler(BluetoothReplyRunnable* aRunnable, int aClientIf)
+     : mRunnable(aRunnable), mClientIf(aClientIf)
+  { }
+
+  void Scan() override
+  {
+    DispatchReplySuccess(mRunnable);
+
+    // Unregister client when stopLeScan succeeded
+    if (mClientIf > 0) {
+      BluetoothGattManager* gattManager = BluetoothGattManager::Get();
+      NS_ENSURE_TRUE_VOID(gattManager);
+
+      nsRefPtr<BluetoothVoidReplyRunnable> result =
+        new BluetoothVoidReplyRunnable(nullptr);
+      gattManager->UnregisterClient(mClientIf, result);
+    }
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::StopLeScan failed: %d",
+                (int)aStatus);
+    DispatchReplyError(mRunnable, aStatus);
+  }
+
+private:
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+  int mClientIf;
+};
+
+void
+BluetoothGattManager::StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                                  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  nsString appUuidStr;
+  GenerateUuid(appUuidStr);
+
+  size_t index = sClients->IndexOf(appUuidStr, 0 /* Start */, UuidComparator());
+
+  // Reject the startLeScan request if the clientIf is being used.
+  if (index != sClients->NoIndex) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("start LE scan failed"));
+    return;
+  }
+
+  index = sClients->Length();
+  sClients->AppendElement(new BluetoothGattClient(appUuidStr, EmptyString()));
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  client->mStartLeScanRunnable = aRunnable;
+
+  BluetoothUuid appUuid;
+  StringToUuid(NS_ConvertUTF16toUTF8(appUuidStr).get(), appUuid);
+
+  // 'startLeScan' will be proceeded after client registered
+  sBluetoothGattClientInterface->RegisterClient(
+    appUuid, new RegisterClientResultHandler(client));
+}
+
+void
+BluetoothGattManager::StopLeScan(const nsAString& aScanUuid,
+                                 BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aScanUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the stop LE scan request
+    DispatchReplyError(aRunnable, NS_LITERAL_STRING("StopLeScan failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  sBluetoothGattClientInterface->Scan(
+    client->mClientIf,
+    false /* Stop */,
+    new StopLeScanResultHandler(aRunnable, client->mClientIf));
+}
+
 class BluetoothGattManager::ConnectResultHandler final
   : public BluetoothGattClientResultHandler
 {
 public:
   ConnectResultHandler(BluetoothGattClient* aClient)
   : mClient(aClient)
   {
     MOZ_ASSERT(mClient);
@@ -1229,18 +1364,24 @@ BluetoothGattManager::RegisterClientNoti
       "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
       aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
 
     // Notify BluetoothGatt for client disconnected
     bs->DistributeSignal(
       NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
       uuid, BluetoothValue(false)); // Disconnected
 
-    // Reject the connect request
-    if (client->mConnectRunnable) {
+    if (client->mStartLeScanRunnable) {
+      // Reject the LE scan request
+      DispatchReplyError(client->mStartLeScanRunnable,
+                         NS_LITERAL_STRING(
+                           "StartLeScan failed due to registration failed"));
+      client->mStartLeScanRunnable = nullptr;
+    } else if (client->mConnectRunnable) {
+      // Reject the connect request
       DispatchReplyError(client->mConnectRunnable,
                          NS_LITERAL_STRING(
                            "Connect failed due to registration failed"));
       client->mConnectRunnable = nullptr;
     }
 
     sClients->RemoveElement(client);
     return;
@@ -1248,30 +1389,53 @@ BluetoothGattManager::RegisterClientNoti
 
   client->mClientIf = aClientIf;
 
   // Notify BluetoothGatt to update the clientIf
   bs->DistributeSignal(
     NS_LITERAL_STRING("ClientRegistered"),
     uuid, BluetoothValue(uint32_t(aClientIf)));
 
-  // Client just registered, proceed remaining connect request.
-  if (client->mConnectRunnable) {
+  if (client->mStartLeScanRunnable) {
+    // Client just registered, proceed remaining startLeScan request.
+    sBluetoothGattClientInterface->Scan(
+      aClientIf, true /* start */,
+      new StartLeScanResultHandler(client));
+  } else if (client->mConnectRunnable) {
+    // Client just registered, proceed remaining connect request.
     sBluetoothGattClientInterface->Connect(
       aClientIf, client->mDeviceAddr, true /* direct connect */,
       TRANSPORT_AUTO,
       new ConnectResultHandler(client));
   }
 }
 
 void
 BluetoothGattManager::ScanResultNotification(
   const nsAString& aBdAddr, int aRssi,
   const BluetoothGattAdvData& aAdvData)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  nsTArray<uint8_t> advData;
+  advData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData));
+
+  BT_APPEND_NAMED_VALUE(properties, "Address", nsString(aBdAddr));
+  BT_APPEND_NAMED_VALUE(properties, "Rssi", static_cast<int32_t>(aRssi));
+  BT_APPEND_NAMED_VALUE(properties, "GattAdv", advData);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  bs->DistributeSignal(NS_LITERAL_STRING("LeDeviceFound"),
+                       NS_LITERAL_STRING(KEY_ADAPTER),
+                       BluetoothValue(properties));
+}
 
 void
 BluetoothGattManager::ConnectNotification(int aConnId,
                                           BluetoothGattStatus aStatus,
                                           int aClientIf,
                                           const nsAString& aDeviceAddr)
 {
   BT_API2_LOGR();
@@ -1868,17 +2032,17 @@ BluetoothGattManager::ReadRemoteRssiNoti
     }
 
     return;
   }
 
   // Resolve the read remote rssi request
   if (client->mReadRemoteRssiRunnable) {
     DispatchReplySuccess(client->mReadRemoteRssiRunnable,
-                         BluetoothValue(static_cast<uint32_t>(aRssi)));
+                         BluetoothValue(static_cast<int32_t>(aRssi)));
     client->mReadRemoteRssiRunnable = nullptr;
   }
 }
 
 void
 BluetoothGattManager::ListenNotification(BluetoothGattStatus aStatus,
                                          int aServerIf)
 { }
@@ -1912,16 +2076,17 @@ BluetoothGattManager::Observe(nsISupport
 }
 
 void
 BluetoothGattManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInShutdown = true;
   sBluetoothGattManager = nullptr;
+  sClients = nullptr;
 }
 
 void
 BluetoothGattManager::ProceedDiscoverProcess(
   BluetoothGattClient* aClient,
   const BluetoothGattServiceId& aServiceId)
 {
   /**
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h
@@ -22,16 +22,22 @@ class BluetoothGattManager final : publi
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static BluetoothGattManager* Get();
   static void InitGattInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
 
+  void StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                   BluetoothReplyRunnable* aRunnable);
+
+  void StopLeScan(const nsAString& aScanUuid,
+                  BluetoothReplyRunnable* aRunnable);
+
   void Connect(const nsAString& aAppUuid,
                const nsAString& aDeviceAddr,
                BluetoothReplyRunnable* aRunnable);
 
   void Disconnect(const nsAString& aAppUuid,
                   const nsAString& aDeviceAddr,
                   BluetoothReplyRunnable* aRunnable);
 
@@ -87,16 +93,18 @@ public:
 private:
   ~BluetoothGattManager();
 
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
+  class StartLeScanResultHandler;
+  class StopLeScanResultHandler;
   class ConnectResultHandler;
   class DisconnectResultHandler;
   class DiscoverResultHandler;
   class ReadRemoteRssiResultHandler;
   class RegisterNotificationsResultHandler;
   class DeregisterNotificationsResultHandler;
   class ReadCharacteristicValueResultHandler;
   class WriteCharacteristicValueResultHandler;
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -385,16 +385,44 @@ BluetoothServiceBluedroid::StopInternal(
   return ret;
 }
 
 //
 // GATT Client
 //
 
 void
+BluetoothServiceBluedroid::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->StartLeScan(aServiceUuids, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::StopLeScanInternal(
+  const nsAString& aScanUuid,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->StopLeScan(aScanUuid, aRunnable);
+}
+
+void
 BluetoothServiceBluedroid::ConnectGattClientInternal(
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
@@ -574,16 +602,25 @@ BluetoothServiceBluedroid::GattClientWri
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
       DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
       return result;                                                   \
     }                                                                  \
   } while(0)
 
+#define ENSURE_BLUETOOTH_IS_READY_VOID(runnable)                       \
+  do {                                                                 \
+    if (!sBtInterface || !IsEnabled()) {                               \
+      NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
+      DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
+      return;                                                          \
+    }                                                                  \
+  } while(0)
+
 // Audio: Major service class = 0x100 (Bit 21 is set)
 #define SET_AUDIO_BIT(cod)               (cod |= 0x200000)
 // Rendering: Major service class = 0x20 (Bit 18 is set)
 #define SET_RENDERING_BIT(cod)           (cod |= 0x40000)
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
@@ -1208,33 +1245,30 @@ public:
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("StartDiscovery"));
   }
 #endif
 
 private:
   BluetoothReplyRunnable* mRunnable;
 };
 
-nsresult
+void
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
 #ifdef MOZ_B2G_BT_API_V2
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->StartDiscovery(new StartDiscoveryResultHandler(aRunnable));
-
-  return NS_OK;
 }
 
 class BluetoothServiceBluedroid::CancelDiscoveryResultHandler final
   : public BluetoothResultHandler
 {
 public:
   CancelDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
@@ -1307,33 +1341,30 @@ BluetoothServiceBluedroid::FetchUuidsInt
     new GetRemoteServicesResultHandler(aRunnable));
 
   return NS_OK;
 }
 #else
 // Missing in bluetooth1
 #endif
 
-nsresult
+void
 BluetoothServiceBluedroid::StopDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
 #ifdef MOZ_B2G_BT_API_V2
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
-
-  return NS_OK;
 }
 
 class BluetoothServiceBluedroid::SetAdapterPropertyResultHandler final
   : public BluetoothResultHandler
 {
 public:
   SetAdapterPropertyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -51,18 +51,18 @@ public:
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddress,
                                     BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
@@ -186,16 +186,22 @@ public:
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) override;
 
   //
   // GATT Client
   //
 
+  virtual void StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                                   BluetoothReplyRunnable* aRunnable);
+
+  virtual void StopLeScanInternal(const nsAString& aScanUuid,
+                                  BluetoothReplyRunnable* aRunnable);
+
   virtual void
   ConnectGattClientInternal(const nsAString& aAppUuid,
                             const nsAString& aDeviceAddress,
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
@@ -357,18 +363,18 @@ public:
 
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
                                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddress,
                                      BluetoothReplyRunnable* aRunnable);
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
--- a/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp
+++ b/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp
@@ -392,26 +392,21 @@ BluetoothAdapter::StartStopDiscovery(boo
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(request);
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-  nsresult rv;
+
   if (aStart) {
-    rv = bs->StartDiscoveryInternal(results);
+    bs->StartDiscoveryInternal(results);
   } else {
-    rv = bs->StopDiscoveryInternal(results);
-  }
-  if (NS_FAILED(rv)) {
-    BT_WARNING("Start/Stop Discovery failed!");
-    aRv.Throw(rv);
-    return nullptr;
+    bs->StopDiscoveryInternal(results);
   }
 
   // mDiscovering is not set here, we'll get a Property update from our external
   // protocol to tell us that it's been set.
 
   return request.forget();
 }
 
--- a/dom/bluetooth/bluetooth1/BluetoothService.h
+++ b/dom/bluetooth/bluetooth1/BluetoothService.h
@@ -152,25 +152,25 @@ public:
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Stop device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Start device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Set a property for the specified object
    *
    * @param aPropName Name of the property
    * @param aValue Boolean value
    * @param aRunnable Runnable to run on async reply
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp
@@ -338,32 +338,28 @@ BluetoothRequestParent::DoRequest(const 
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest);
 
-  nsresult rv =
-    mService->StartDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StartDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest);
 
-  nsresult rv =
-    mService->StopDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StopDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const PairRequest& aRequest)
 {
   MOZ_ASSERT(mService);
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp
@@ -119,30 +119,28 @@ BluetoothServiceChildProcess::GetPairedD
 {
   PairedDevicePropertiesRequest request;
   request.addresses().AppendElements(aDeviceAddresses);
 
   SendRequest(aRunnable, request);
   return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StopDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StopDiscoveryRequest());
-  return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StartDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StartDiscoveryRequest());
-  return NS_OK;
 }
 
 nsresult
 BluetoothServiceChildProcess::SetProperty(BluetoothObjectType aType,
                                           const BluetoothNamedValue& aValue,
                                           BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, SetPropertyRequest(aType, aValue));
@@ -290,17 +288,17 @@ BluetoothServiceChildProcess::ConfirmRec
   bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   if(aConfirm) {
     SendRequest(aRunnable,
                 ConfirmReceivingFileRequest(nsString(aDeviceAddress)));
     return;
   }
-  
+
   SendRequest(aRunnable,
               DenyReceivingFileRequest(nsString(aDeviceAddress)));
 }
 
 void
 BluetoothServiceChildProcess::ConnectSco(BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectScoRequest());
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h
@@ -38,20 +38,20 @@ public:
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable)
                                     override;
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        override;
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
--- a/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
@@ -32,32 +32,34 @@ USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothAdapter)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothAdapter,
                                                 DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevices)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDiscoveryHandleInUse)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPairingReqs)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLeScanHandleArray)
 
   /**
    * Unregister the bluetooth signal handler after unlinked.
    *
    * This is needed to avoid ending up with exposing a deleted object to JS or
    * accessing deleted objects while receiving signals from parent process
    * after unlinked. Please see Bug 1138267 for detail informations.
    */
   UnregisterBluetoothSignalHandler(NS_LITERAL_STRING(KEY_ADAPTER), tmp);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothAdapter,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevices)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDiscoveryHandleInUse)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPairingReqs)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeScanHandleArray)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 // QueryInterface implementation for BluetoothAdapter
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothAdapter)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, DOMEventTargetHelper)
@@ -105,16 +107,111 @@ public:
     BluetoothReplyRunnable::ReleaseMembers();
     mAdapter = nullptr;
   }
 
 private:
   nsRefPtr<BluetoothAdapter> mAdapter;
 };
 
+class StartLeScanTask final : public BluetoothReplyRunnable
+{
+public:
+  StartLeScanTask(BluetoothAdapter* aAdapter, Promise* aPromise,
+                  const nsTArray<nsString>& aServiceUuids)
+    : BluetoothReplyRunnable(nullptr, aPromise,
+                             NS_LITERAL_STRING("StartLeScan"))
+    , mAdapter(aAdapter)
+    , mServiceUuids(aServiceUuids)
+  {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aAdapter);
+  }
+
+  bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    aValue.setUndefined();
+
+    AutoJSAPI jsapi;
+    NS_ENSURE_TRUE(jsapi.Init(mAdapter->GetParentObject()), false);
+    JSContext* cx = jsapi.cx();
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::TnsString, false);
+
+    /**
+     * Create a new discovery handle and wrap it to return. Each
+     * discovery handle is one-time-use only.
+     */
+    nsRefPtr<BluetoothDiscoveryHandle> discoveryHandle =
+      BluetoothDiscoveryHandle::Create(mAdapter->GetParentObject(),
+                                       mServiceUuids, v.get_nsString(),
+                                       mAdapter);
+
+    if (!ToJSValue(cx, discoveryHandle, aValue)) {
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    // Append a BluetoothDiscoveryHandle to LeScan handle array.
+    mAdapter->AppendLeScanHandle(discoveryHandle);
+
+    return true;
+  }
+
+  virtual void
+  ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mAdapter = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothAdapter> mAdapter;
+  nsTArray<nsString> mServiceUuids;
+};
+
+class StopLeScanTask final : public BluetoothReplyRunnable
+{
+public:
+  StopLeScanTask(BluetoothAdapter* aAdapter,
+                 Promise* aPromise,
+                 const nsAString& aScanUuid)
+      : BluetoothReplyRunnable(nullptr, aPromise,
+                               NS_LITERAL_STRING("StopLeScan"))
+      , mAdapter(aAdapter)
+      , mScanUuid(aScanUuid)
+  {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aAdapter);
+    MOZ_ASSERT(!aScanUuid.IsEmpty());
+  }
+
+protected:
+  virtual bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue) override
+  {
+    mAdapter->RemoveLeScanHandle(mScanUuid);
+    aValue.setUndefined();
+    return true;
+  }
+
+  virtual void
+  ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mAdapter = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothAdapter> mAdapter;
+  nsString mScanUuid;
+};
+
 class GetDevicesTask : public BluetoothReplyRunnable
 {
 public:
   GetDevicesTask(BluetoothAdapter* aAdapterPtr, nsIDOMDOMRequest* aReq)
     : BluetoothReplyRunnable(aReq)
     , mAdapterPtr(aAdapterPtr)
   {
     MOZ_ASSERT(aReq && aAdapterPtr);
@@ -269,19 +366,20 @@ void
 BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
   const nsString& name = aValue.name();
   const BluetoothValue& value = aValue.value();
   if (name.EqualsLiteral("State")) {
     mState = value.get_bool() ? BluetoothAdapterState::Enabled
                               : BluetoothAdapterState::Disabled;
 
-    // Clear saved devices when state changes to disabled
+    // Clear saved devices and LE scan handles when state changes to disabled
     if (mState == BluetoothAdapterState::Disabled) {
       mDevices.Clear();
+      mLeScanHandleArray.Clear();
     }
   } else if (name.EqualsLiteral("Name")) {
     mName = value.get_nsString();
   } else if (name.EqualsLiteral("Address")) {
     mAddress = value.get_nsString();
   } else if (name.EqualsLiteral("Discoverable")) {
     mDiscoverable = value.get_bool();
   } else if (name.EqualsLiteral("Discovering")) {
@@ -348,16 +446,20 @@ BluetoothAdapter::Notify(const Bluetooth
      * doing discovery operations.
      * The signal needs to be handled only if this adapter is holding a valid
      * discovery handle, which means that the discovery operation is triggered
      * by this adapter.
      */
     if (mDiscoveryHandleInUse) {
       HandleDeviceFound(v);
     }
+  } else if (aData.name().EqualsLiteral("LeDeviceFound")) {
+    if (!mLeScanHandleArray.IsEmpty()) {
+      HandleLeDeviceFound(v);
+    }
   } else if (aData.name().EqualsLiteral(DEVICE_PAIRED_ID)) {
     HandleDevicePaired(aData.value());
   } else if (aData.name().EqualsLiteral(DEVICE_UNPAIRED_ID)) {
     HandleDeviceUnpaired(aData.value());
   } else if (aData.name().EqualsLiteral(HFP_STATUS_CHANGED_ID) ||
              aData.name().EqualsLiteral(SCO_STATUS_CHANGED_ID) ||
              aData.name().EqualsLiteral(A2DP_STATUS_CHANGED_ID)) {
     MOZ_ASSERT(v.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
@@ -394,16 +496,36 @@ BluetoothAdapter::SetDiscoveryHandleInUs
   // Stop discovery handle in use from listening to "DeviceFound" signal
   if (mDiscoveryHandleInUse) {
     mDiscoveryHandleInUse->DisconnectFromOwner();
   }
 
   mDiscoveryHandleInUse = aDiscoveryHandle;
 }
 
+void
+BluetoothAdapter::AppendLeScanHandle(
+  BluetoothDiscoveryHandle* aDiscoveryHandle)
+{
+  mLeScanHandleArray.AppendElement(aDiscoveryHandle);
+}
+
+void
+BluetoothAdapter::RemoveLeScanHandle(const nsAString& aScanUuid)
+{
+  nsString uuid;
+  for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) {
+    mLeScanHandleArray[i]->GetLeScanUuid(uuid);
+    if (aScanUuid.Equals(uuid)) {
+      mLeScanHandleArray.RemoveElementAt(i);
+      break;
+    }
+  }
+}
+
 already_AddRefed<Promise>
 BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
@@ -432,19 +554,17 @@ BluetoothAdapter::StartDiscovery(ErrorRe
     if (!mDevices[i]->Paired()) {
       mDevices.RemoveElementAt(i);
     }
   }
 
   // Return BluetoothDiscoveryHandle in StartDiscoveryTask
   nsRefPtr<BluetoothReplyRunnable> result =
     new StartDiscoveryTask(this, promise);
-  BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StartDiscoveryInternal(result)),
-                        promise,
-                        NS_ERROR_DOM_OPERATION_ERR);
+  bs->StartDiscoveryInternal(result);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 BluetoothAdapter::StopDiscovery(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
@@ -470,19 +590,78 @@ BluetoothAdapter::StopDiscovery(ErrorRes
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   BT_API2_LOGR();
 
   nsRefPtr<BluetoothReplyRunnable> result =
     new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
                                    promise,
                                    NS_LITERAL_STRING("StopDiscovery"));
-  BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StopDiscoveryInternal(result)),
+  bs->StopDiscoveryInternal(result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothAdapter::StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                              ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled,
                         promise,
-                        NS_ERROR_DOM_OPERATION_ERR);
+                        NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new StartLeScanTask(this, promise, aServiceUuids);
+  bs->StartLeScanInternal(aServiceUuids, result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothAdapter::StopLeScan(BluetoothDiscoveryHandle& aDiscoveryHandle,
+                             ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled,
+                        promise,
+                        NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  // Reject the request if there's no ongoing LE Scan using this handle.
+  BT_ENSURE_TRUE_REJECT(!mLeScanHandleArray.Contains(&aDiscoveryHandle),
+                        promise,
+                        NS_ERROR_DOM_BLUETOOTH_DONE);
+
+  nsString scanUuid;
+  aDiscoveryHandle.GetLeScanUuid(scanUuid);
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new StopLeScanTask(this, promise, scanUuid);
+  bs->StopLeScanInternal(scanUuid, result);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 BluetoothAdapter::SetName(const nsAString& aName, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
@@ -875,16 +1054,52 @@ BluetoothAdapter::HandleDeviceFound(cons
     discoveredDevice = mDevices[index];
   }
 
   // Notify application of discovered device via discovery handle
   mDiscoveryHandleInUse->DispatchDeviceEvent(discoveredDevice);
 }
 
 void
+BluetoothAdapter::HandleLeDeviceFound(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const InfallibleTArray<BluetoothNamedValue>& values =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  int rssi = 0;
+  nsTArray<uint8_t> advData;
+  for (uint32_t i = 0; i < values.Length(); ++i) {
+    nsString name = values[i].name();
+    BluetoothValue value = values[i].value();
+    if (name.EqualsLiteral("Rssi")) {
+      MOZ_ASSERT(value.type() == BluetoothValue::Tint32_t);
+      rssi = value.get_int32_t();
+    } else if (name.EqualsLiteral("GattAdv")) {
+      MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t);
+      advData = value.get_ArrayOfuint8_t();
+    } else {
+      BT_WARNING("Receive an unexpected value name '%s'",
+                 NS_ConvertUTF16toUTF8(name).get());
+    }
+  }
+
+  // Create an individual scanned BluetoothDevice for each LeDeviceEvent even
+  // the device exists in adapter's devices array
+  nsRefPtr<BluetoothDevice> scannedDevice =
+    BluetoothDevice::Create(GetOwner(), aValue);
+
+  // Notify application of scanned devices via discovery handle
+  for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) {
+    mLeScanHandleArray[i]->DispatchLeDeviceEvent(scannedDevice, rssi, advData);
+  }
+}
+
+void
 BluetoothAdapter::HandleDevicePaired(const BluetoothValue& aValue)
 {
   if (NS_WARN_IF(mState != BluetoothAdapterState::Enabled)) {
     return;
   }
 
   MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
 
--- a/dom/bluetooth/bluetooth2/BluetoothAdapter.h
+++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.h
@@ -95,16 +95,22 @@ public:
   already_AddRefed<Promise> Enable(ErrorResult& aRv);
   already_AddRefed<Promise> Disable(ErrorResult& aRv);
 
   already_AddRefed<Promise> SetName(const nsAString& aName, ErrorResult& aRv);
   already_AddRefed<Promise> SetDiscoverable(bool aDiscoverable,
                                             ErrorResult& aRv);
   already_AddRefed<Promise> StartDiscovery(ErrorResult& aRv);
   already_AddRefed<Promise> StopDiscovery(ErrorResult& aRv);
+
+  already_AddRefed<Promise> StartLeScan(
+    const nsTArray<nsString>& aServiceUuids, ErrorResult& aRv);
+  already_AddRefed<Promise> StopLeScan(
+    BluetoothDiscoveryHandle& aDiscoveryHandle, ErrorResult& aRv);
+
   already_AddRefed<Promise> Pair(const nsAString& aDeviceAddress,
                                  ErrorResult& aRv);
   already_AddRefed<Promise> Unpair(const nsAString& aDeviceAddress,
                                    ErrorResult& aRv);
 
   /**
    * Get a list of paired bluetooth devices.
    *
@@ -174,16 +180,31 @@ public:
    * |mDiscoveryHandleInUse| is set to the latest discovery handle when adapter
    * just starts discovery, and is reset to nullptr when discovery is stopped
    * by some adapter.
    *
    * @param aDiscoveryHandle [in] Discovery handle to set.
    */
   void SetDiscoveryHandleInUse(BluetoothDiscoveryHandle* aDiscoveryHandle);
 
+  /**
+   * Append a BluetoothDiscoveryHandle to LeScan handle array.
+   *
+   * @param aDiscoveryHandle [in] Discovery handle to be appended.
+   */
+  void AppendLeScanHandle(BluetoothDiscoveryHandle* aDiscoveryHandle);
+
+  /**
+   * Remove the BluetoothDiscoverHandle with the given UUID from LeScan handle
+   * array.
+   *
+   * @param aScanUuid [in] The UUID of the LE scan task.
+   */
+  void RemoveLeScanHandle(const nsAString& aScanUuid);
+
 private:
   BluetoothAdapter(nsPIDOMWindow* aOwner, const BluetoothValue& aValue);
   ~BluetoothAdapter();
 
   /**
    * Set adapter properties according to properties array.
    *
    * @param aValue [in] Properties array to set with
@@ -246,16 +267,23 @@ private:
    * @param aValue [in] Properties array of the unpaired device.
    *                    The array should contain two properties:
    *                    - nsString  'Address'
    *                    - bool      'Paired'
    */
   void HandleDeviceUnpaired(const BluetoothValue& aValue);
 
   /**
+   * Handle "LeDeviceFound" bluetooth signal.
+   *
+   * @param aValue [in] Properties array of the scanned device.
+   */
+  void HandleLeDeviceFound(const BluetoothValue& aValue);
+
+  /**
    * Fire BluetoothAttributeEvent to trigger onattributechanged event handler.
    */
   void DispatchAttributeEvent(const Sequence<nsString>& aTypes);
 
   /**
    * Fire BluetoothDeviceEvent to trigger
    * ondeviceparied/ondeviceunpaired event handler.
    *
@@ -334,16 +362,24 @@ private:
    *
    * This variable is set to the latest discovery handle when adapter just
    * starts discovery, and is reset to nullptr when discovery is stopped by
    * some adapter.
    */
   nsRefPtr<BluetoothDiscoveryHandle> mDiscoveryHandleInUse;
 
   /**
+   * Handles to fire 'ondevicefound' event handler for scanned device
+   *
+   * Each non-stopped LeScan process has a LeScan handle which is
+   * responsible to dispatch LeDeviceEvent.
+   */
+  nsTArray<nsRefPtr<BluetoothDiscoveryHandle> > mLeScanHandleArray;
+
+  /**
    * nsRefPtr array of BluetoothDevices created by this adapter. The array is
    * empty when adapter state is Disabled.
    *
    * Devices will be appended when
    *   1) adapter is enabling: Paired devices reported by stack.
    *   2) adapter is discovering: Discovered devices during discovery operation.
    *   3) adapter paired with a device: The paired device reported by stack.
    * Note devices with identical address won't be appended.
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
@@ -157,16 +157,21 @@ BluetoothDevice::SetPropertyByValue(cons
   } else if (name.EqualsLiteral("UUIDs")) {
     // We assume the received uuids array is sorted without duplicate items.
     // If it's not, we require additional processing before assigning it
     // directly.
     mUuids = value.get_ArrayOfnsString();
     BluetoothDeviceBinding::ClearCachedUuidsValue(this);
   } else if (name.EqualsLiteral("Type")) {
     mType = ConvertUint32ToDeviceType(value.get_uint32_t());
+  } else if (name.EqualsLiteral("GattAdv")) {
+    MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t);
+    nsTArray<uint8_t> advData;
+    advData = value.get_ArrayOfuint8_t();
+    UpdatePropertiesFromAdvData(advData);
   } else {
     BT_WARNING("Not handling device property: %s",
                NS_ConvertUTF16toUTF8(name).get());
   }
 }
 
 already_AddRefed<Promise>
 BluetoothDevice::FetchUuids(ErrorResult& aRv)
@@ -316,14 +321,112 @@ BluetoothDevice::GetGatt()
                  nullptr);
   if (!mGatt) {
     mGatt = new BluetoothGatt(GetOwner(), mAddress);
   }
 
   return mGatt;
 }
 
+void
+BluetoothDevice::UpdatePropertiesFromAdvData(const nsTArray<uint8_t>& aAdvData)
+{
+  // According to BT Core Spec. Vol 3 - Ch 11, advertisement data consists of a
+  // significant part and a non-significant part.
+  // The significant part contains a sequence of AD structures. Each AD
+  // structure shall have a Length field of one octet, which contains the
+  // Length value, and a Data field of Length octets.
+  unsigned int offset = 0;
+  while (offset < aAdvData.Length()) {
+    int dataFieldLength = aAdvData[offset++];
+
+    // According to BT Core Spec, it only occurs to allow an early termination
+    // of the Advertising data.
+    if (dataFieldLength <= 0) {
+      break;
+    }
+
+    // Length of the data field which is composed by AD type (1 byte) and
+    // AD data (dataFieldLength -1 bytes)
+    int dataLength = dataFieldLength - 1;
+    if (offset + dataLength >= aAdvData.Length()) {
+      break;
+    }
+
+    // Update UUIDs and name of BluetoothDevice.
+    int type = aAdvData[offset++];
+    switch (type) {
+      case GAP_INCOMPLETE_UUID16:
+      case GAP_COMPLETE_UUID16:
+      case GAP_INCOMPLETE_UUID32:
+      case GAP_COMPLETE_UUID32:
+      case GAP_INCOMPLETE_UUID128:
+      case GAP_COMPLETE_UUID128: {
+        mUuids.Clear();
+
+        // The length of uint16_t UUID array
+        uint8_t len = 0;
+        if (GAP_INCOMPLETE_UUID16 && GAP_COMPLETE_UUID16) {
+          len = 1;
+        } else if (GAP_INCOMPLETE_UUID32 && GAP_COMPLETE_UUID32) {
+          len = 2;
+        } else {
+          len = 8;
+        }
+        uint16_t uuid[len];
+
+        while (dataLength > 0) {
+          // Read (len * 2) bytes from the data buffer and compose a 16-bits
+          // UUID array.
+          for (uint8_t i = 0; i < len; ++i) {
+            uuid[i] = aAdvData[offset++];
+            uuid[i] += (aAdvData[offset++] << 8);
+            dataLength -= 2;
+          }
+
+          char uuidStr[36];
+          if (type == GAP_INCOMPLETE_UUID16 || type == GAP_COMPLETE_UUID16) {
+            // Convert 16-bits UUID into string.
+            sprintf(uuidStr, "0000%04x-0000-1000-8000-00805f9b34fb", uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID32 || type == GAP_COMPLETE_UUID32) {
+            // Convert 32-bits UUID into string.
+            sprintf(uuidStr, "%04x%04x-0000-1000-8000-00805f9b34fb",
+              uuid[1], uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID128 || type == GAP_COMPLETE_UUID128) {
+            // Convert 128-bits UUID into string.
+            sprintf(uuidStr, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
+              uuid[7], uuid[6], uuid[5], uuid[4],
+              uuid[3], uuid[2], uuid[1], uuid[0]);
+          }
+          nsString uuidNsString;
+          uuidNsString.AssignLiteral(uuidStr);
+
+          mUuids.AppendElement(uuidNsString);
+        }
+
+        BluetoothDeviceBinding::ClearCachedUuidsValue(this);
+        break;
+      }
+      case GAP_SHORTENED_NAME:
+        if (!mName.IsEmpty()) break;
+      case GAP_COMPLETE_NAME: {
+        // Read device name from data buffer.
+        char deviceName[dataLength];
+        for (int i = 0; i < dataLength; ++i) {
+          deviceName[i] = aAdvData[offset++];
+        }
+
+        mName.AssignASCII(deviceName, dataLength);
+        break;
+      }
+      default:
+        offset += dataLength;
+        break;
+    }
+  }
+}
+
 JSObject*
 BluetoothDevice::WrapObject(JSContext* aContext,
                             JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothDeviceBinding::Wrap(aContext, this, aGivenProto);
 }
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.h
@@ -139,16 +139,26 @@ private:
    * Check whether value of given device property has changed.
    *
    * @param aType  [in] Device property to check
    * @param aValue [in] New value of the device property
    */
   bool IsDeviceAttributeChanged(BluetoothDeviceAttribute aType,
                                 const BluetoothValue& aValue);
 
+  /**
+   * Parse advertising data to update device properties.
+   *
+   * Parse 'Advertising Data Type' from an inquiry response and set name, UUIDs
+   * and COD if they exist in ADV data.
+   *
+   * @param aAdvData [in] advertising data which provided by the LeScan result.
+   */
+   void UpdatePropertiesFromAdvData(const nsTArray<uint8_t>& aAdvData);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   /**
    * BD address of this device.
    */
   nsString mAddress;
 
--- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp
@@ -1,65 +1,158 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#include "BluetoothDiscoveryHandle.h"
 #include "BluetoothService.h"
-
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothDeviceEvent.h"
 #include "mozilla/dom/BluetoothDiscoveryHandleBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothDiscoveryHandle.h"
+#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsThreadUtils.h"
 
 USING_BLUETOOTH_NAMESPACE
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle,
+                                   DOMEventTargetHelper,
+                                   mAdapter)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothDiscoveryHandle, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothDiscoveryHandle, DOMEventTargetHelper)
 
 BluetoothDiscoveryHandle::BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
+  , mLeScanUuid(EmptyString())
+{
+  MOZ_ASSERT(aWindow);
+}
+
+BluetoothDiscoveryHandle::BluetoothDiscoveryHandle(
+  nsPIDOMWindow* aWindow,
+  const nsTArray<nsString>& aServiceUuids,
+  const nsAString& aLeScanUuid,
+  BluetoothAdapter* aAdapter)
+  : DOMEventTargetHelper(aWindow)
+  , mLeScanUuid(aLeScanUuid)
+  , mServiceUuids(aServiceUuids)
+  , mAdapter(aAdapter)
 {
   MOZ_ASSERT(aWindow);
 }
 
 BluetoothDiscoveryHandle::~BluetoothDiscoveryHandle()
 {
+  // Remove itself from the adapter's mLeScanHandleArray
+  if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) {
+    mAdapter->RemoveLeScanHandle(mLeScanUuid);
+  }
+}
+
+void
+BluetoothDiscoveryHandle::DisconnectFromOwner()
+{
+  DOMEventTargetHelper::DisconnectFromOwner();
+
+  // Remove itself from the adapter's mLeScanHandleArray
+  if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) {
+    mAdapter->RemoveLeScanHandle(mLeScanUuid);
+  }
 }
 
 // static
 already_AddRefed<BluetoothDiscoveryHandle>
 BluetoothDiscoveryHandle::Create(nsPIDOMWindow* aWindow)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
 
   nsRefPtr<BluetoothDiscoveryHandle> handle =
     new BluetoothDiscoveryHandle(aWindow);
   return handle.forget();
 }
 
+already_AddRefed<BluetoothDiscoveryHandle>
+BluetoothDiscoveryHandle::Create(
+  nsPIDOMWindow* aWindow,
+  const nsTArray<nsString>& aServiceUuids,
+  const nsAString& aLeScanUuid,
+  BluetoothAdapter* aAdapter)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aAdapter);
+
+  nsRefPtr<BluetoothDiscoveryHandle> handle =
+    new BluetoothDiscoveryHandle(aWindow, aServiceUuids, aLeScanUuid, aAdapter);
+  return handle.forget();
+}
+
 void
 BluetoothDiscoveryHandle::DispatchDeviceEvent(BluetoothDevice* aDevice)
 {
   MOZ_ASSERT(aDevice);
 
   BluetoothDeviceEventInit init;
   init.mDevice = aDevice;
 
   nsRefPtr<BluetoothDeviceEvent> event =
     BluetoothDeviceEvent::Constructor(this,
                                       NS_LITERAL_STRING("devicefound"),
                                       init);
   DispatchTrustedEvent(event);
 }
 
+void
+BluetoothDiscoveryHandle::DispatchLeDeviceEvent(BluetoothDevice* aLeDevice,
+  int32_t aRssi, nsTArray<uint8_t>& aScanRecord)
+{
+  MOZ_ASSERT(aLeDevice);
+
+  nsTArray<nsString> remoteUuids;
+  aLeDevice->GetUuids(remoteUuids);
+
+  bool hasUuidsFilter = !mServiceUuids.IsEmpty();
+  bool noAdvertisingUuid  = remoteUuids.IsEmpty();
+  // If a LE device doesn't advertise its service UUIDs, it can't possibly pass
+  // the UUIDs filter.
+  if (hasUuidsFilter && noAdvertisingUuid) {
+    return;
+  }
+
+  // The web API startLeScan() makes the device's adapter start seeking for
+  // remote LE devices advertising given service UUIDs.
+  // Since current Bluetooth stack can't filter the results of LeScan by UUIDs,
+  // gecko has to filter the results and dispach what API asked for.
+  bool matched = false;
+  for (size_t index = 0; index < remoteUuids.Length(); ++index) {
+    if (mServiceUuids.Contains(remoteUuids[index])) {
+      matched = true;
+      break;
+    }
+  }
+
+  // Dispach 'devicefound 'event only if
+  //  - the service UUIDs in the scan record matchs the given UUIDs.
+  //  - the given UUIDs is empty.
+  if (matched || mServiceUuids.IsEmpty()) {
+    nsRefPtr<BluetoothLeDeviceEvent> event =
+      BluetoothLeDeviceEvent::Constructor(this,
+                                          NS_LITERAL_STRING("devicefound"),
+                                          aLeDevice,
+                                          aRssi,
+                                          aScanRecord);
+    DispatchTrustedEvent(event);
+  }
+}
+
 JSObject*
 BluetoothDiscoveryHandle::WrapObject(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothDiscoveryHandleBinding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
@@ -2,41 +2,92 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
 #define mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
 
-#include "BluetoothCommon.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/bluetooth/BluetoothAdapter.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/DOMEventTargetHelper.h"
 #include "nsISupportsImpl.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothDevice;
 
 class BluetoothDiscoveryHandle final : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDiscoveryHandle,
+                                           DOMEventTargetHelper)
 
   static already_AddRefed<BluetoothDiscoveryHandle>
     Create(nsPIDOMWindow* aWindow);
 
+  static already_AddRefed<BluetoothDiscoveryHandle>
+    Create(nsPIDOMWindow* aWindow,
+           const nsTArray<nsString>& aServiceUuids,
+           const nsAString& aLeScanUuid,
+           BluetoothAdapter* aAdapter);
+
   void DispatchDeviceEvent(BluetoothDevice* aDevice);
 
+  void DispatchLeDeviceEvent(BluetoothDevice* aLeDevice,
+                             int32_t aRssi,
+                             nsTArray<uint8_t>& aScanRecord);
+
   IMPL_EVENT_HANDLER(devicefound);
 
+  void GetLeScanUuid(nsString& aLeScanUuid) const
+  {
+    aLeScanUuid = mLeScanUuid;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  virtual void DisconnectFromOwner() override;
+
 private:
   BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow);
+
+  BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow,
+                           const nsTArray<nsString>& aServiceUuids,
+                           const nsAString& aLeScanUuid,
+                           BluetoothAdapter* aAdapter);
+
   ~BluetoothDiscoveryHandle();
+
+  /**
+   * Random generated UUID of LE scan
+   *
+   * This UUID is used only when the handle is built for LE scan.
+   * If BluetoothDiscoveryHandle is built for classic discovery, the value would
+   * remain empty string during the entire life cycle.
+   */
+  nsString mLeScanUuid;
+
+  /**
+   * A DOMString array of service UUIDs to discover / scan for.
+   *
+   * This array is only used by LE scan. If BluetoothDiscoveryHandle is built
+   * for classic discovery, the array should be empty.
+   */
+  nsTArray<nsString> mServiceUuids;
+
+  /**
+   * The adapter which called startLeScan and created this discovery handle
+   *
+   * If BluetoothDiscoveryHandle is built for classic discovery, this value
+   * should be nullptr.
+   */
+  nsRefPtr<BluetoothAdapter> mAdapter;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
@@ -8,17 +8,16 @@
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGatt.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothGattBinding.h"
 #include "mozilla/dom/BluetoothGattCharacteristicEvent.h"
 #include "mozilla/dom/Promise.h"
-#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
@@ -72,37 +71,16 @@ BluetoothGatt::~BluetoothGatt()
       new BluetoothVoidReplyRunnable(nullptr);
     bs->UnregisterGattClientInternal(mClientIf, result);
   }
 
   UnregisterBluetoothSignalHandler(mAppUuid, this);
 }
 
 void
-BluetoothGatt::GenerateUuid(nsAString &aUuidString)
-{
-  nsresult rv;
-  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
-    do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  nsID uuid;
-  rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
-  char uuidBuffer[NSID_LENGTH];
-  uuid.ToProvidedString(uuidBuffer);
-  NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
-
-  // Remove {} and the null terminator
-  aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
-}
-
-void
 BluetoothGatt::DisconnectFromOwner()
 {
   DOMEventTargetHelper::DisconnectFromOwner();
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (mClientIf > 0) {
@@ -195,19 +173,19 @@ public:
   }
 
   bool
   ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
   {
     aValue.setUndefined();
 
     const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
-    NS_ENSURE_TRUE(v.type() == BluetoothValue::Tuint32_t, false);
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::Tint32_t, false);
 
-    aValue.setInt32(static_cast<int32_t>(v.get_uint32_t()));
+    aValue.setInt32(static_cast<int32_t>(v.get_int32_t()));
     return true;
   }
 };
 
 already_AddRefed<Promise>
 BluetoothGatt::ReadRemoteRssi(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.h
@@ -83,23 +83,16 @@ private:
    * Update mConnectionState to aState and fire
    * connectionstatechanged event to the application.
    *
    * @param aState [in] New connection state
    */
   void UpdateConnectionState(BluetoothConnectionState aState);
 
   /**
-   * Generate a random uuid.
-   *
-   * @param aUuidString [out] String to store the generated uuid.
-   */
-  void GenerateUuid(nsAString &aUuidString);
-
-  /**
    * Add newly discovered GATT services into mServices and update the cache
    * value of mServices.
    *
    * @param aValue [in] BluetoothValue which contains an array of
    *                    BluetoothGattServiceId of all discovered services.
    */
   void HandleServicesDiscovered(const BluetoothValue& aValue);
 
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h"
+
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/BluetoothLeDeviceEventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/bluetooth/BluetoothDevice.h"
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothLeDeviceEvent)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothLeDeviceEvent, Event)
+NS_IMPL_RELEASE_INHERITED(BluetoothLeDeviceEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevice)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScanRecord)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevice)
+  tmp->mScanRecord = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothLeDeviceEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+BluetoothLeDeviceEvent::BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+BluetoothLeDeviceEvent::~BluetoothLeDeviceEvent()
+{
+  mScanRecord = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+BluetoothLeDeviceEvent::WrapObjectInternal(JSContext* aCx,
+                                           JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothLeDeviceEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<BluetoothLeDeviceEvent>
+BluetoothLeDeviceEvent::Constructor(
+  mozilla::dom::EventTarget* aOwner,
+  const nsAString& aType,
+  BluetoothDevice* const aDevice,
+  const int16_t aRssi,
+  const nsTArray<uint8_t>& aScanRecord)
+{
+  nsRefPtr<BluetoothLeDeviceEvent> e = new BluetoothLeDeviceEvent(aOwner);
+  bool trusted = e->Init(aOwner);
+  e->InitEvent(aType, false, false);
+  e->mDevice = aDevice;
+  e->mRssi = aRssi;
+  e->mRawScanRecord = aScanRecord;
+
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+already_AddRefed<BluetoothLeDeviceEvent>
+BluetoothLeDeviceEvent::Constructor(
+  const GlobalObject& aGlobal,
+  const nsAString& aType,
+  const BluetoothLeDeviceEventInit& aEventInitDict,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<mozilla::dom::EventTarget> owner =
+    do_QueryInterface(aGlobal.GetAsSupports());
+
+  nsRefPtr<BluetoothLeDeviceEvent> e = new BluetoothLeDeviceEvent(owner);
+  bool trusted = e->Init(owner);
+  e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+  e->mDevice = aEventInitDict.mDevice;
+  e->mRssi = aEventInitDict.mRssi;
+
+  aEventInitDict.mScanRecord.ComputeLengthAndData();
+  const uint8_t* data = aEventInitDict.mScanRecord.Data();
+  size_t length = aEventInitDict.mScanRecord.Length();
+  e->mScanRecord = ArrayBuffer::Create(aGlobal.Context(), length, data);
+  if (!e->mScanRecord) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+BluetoothDevice*
+BluetoothLeDeviceEvent::Device() const
+{
+  return mDevice;
+}
+
+int16_t
+BluetoothLeDeviceEvent::Rssi() const
+{
+  return mRssi;
+}
+
+void
+BluetoothLeDeviceEvent::GetScanRecord(
+  JSContext* cx,
+  JS::MutableHandle<JSObject*> aScanRecord,
+  ErrorResult& aRv)
+{
+  if (!mScanRecord) {
+    mScanRecord = ArrayBuffer::Create(cx,
+                                      mRawScanRecord.Length(),
+                                      mRawScanRecord.Elements());
+    if (!mScanRecord) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+    mRawScanRecord.Clear();
+  }
+  JS::ExposeObjectToActiveJS(mScanRecord);
+  aScanRecord.set(mScanRecord);
+
+  return;
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothledeviceevent_h
+#define mozilla_dom_bluetooth_bluetoothledeviceevent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BluetoothLeDeviceEventBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothLeDeviceEvent : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(BluetoothLeDeviceEvent,
+                                                         Event)
+protected:
+  virtual ~BluetoothLeDeviceEvent();
+  explicit BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner);
+
+  nsRefPtr<BluetoothDevice> mDevice;
+  int16_t mRssi;
+  JS::Heap<JSObject*> mScanRecord;
+
+public:
+  virtual JSObject* WrapObjectInternal(
+    JSContext* aCx,
+    JS::Handle<JSObject*> aGivenProto) override;
+
+  static already_AddRefed<BluetoothLeDeviceEvent>
+    Constructor(EventTarget* aOwner,
+                const nsAString& aType,
+                BluetoothDevice* const aDevice,
+                const int16_t aRssi,
+                const nsTArray<uint8_t>& aScanRecord);
+
+  static already_AddRefed<BluetoothLeDeviceEvent>
+    Constructor(const GlobalObject& aGlobal,
+                const nsAString& aType,
+                const BluetoothLeDeviceEventInit& aEventInitDict,
+                ErrorResult& aRv);
+
+  BluetoothDevice* Device() const;
+
+  int16_t Rssi() const;
+
+  void GetScanRecord(JSContext* cx,
+                     JS::MutableHandle<JSObject*> aScanRecord,
+                     ErrorResult& aRv);
+
+  private:
+    nsTArray<uint8_t> mRawScanRecord;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif // mozilla_dom_bluetooth_bluetoothledeviceevent_h
--- a/dom/bluetooth/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth/bluetooth2/BluetoothService.h
@@ -171,29 +171,39 @@ public:
    * @return NS_OK on success, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Stop device discovery (platform specific implementation)
-   *
-   * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Start device discovery (platform specific implementation)
-   *
-   * @return NS_OK if discovery stopped correctly, false otherwise
+   */
+  virtual void
+  StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Stops an ongoing Bluetooth LE device scan.
    */
-  virtual nsresult
-  StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
+  virtual void
+  StopLeScanInternal(const nsAString& aScanUuid,
+                     BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Starts a Bluetooth LE device scan.
+   */
+  virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Set a property for the specified object
    *
    * @param aPropName Name of the property
    * @param aValue Boolean value
    * @param aRunnable Runnable to run on async reply
    *
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
@@ -197,16 +197,20 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TStopBluetoothRequest:
       return actor->DoRequest(aRequest.get_StopBluetoothRequest());
     case Request::TSetPropertyRequest:
       return actor->DoRequest(aRequest.get_SetPropertyRequest());
     case Request::TStartDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StartDiscoveryRequest());
     case Request::TStopDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StopDiscoveryRequest());
+    case Request::TStartLeScanRequest:
+      return actor->DoRequest(aRequest.get_StartLeScanRequest());
+    case Request::TStopLeScanRequest:
+      return actor->DoRequest(aRequest.get_StopLeScanRequest());
     case Request::TPairRequest:
       return actor->DoRequest(aRequest.get_PairRequest());
     case Request::TUnpairRequest:
       return actor->DoRequest(aRequest.get_UnpairRequest());
     case Request::TPairedDevicePropertiesRequest:
       return actor->DoRequest(aRequest.get_PairedDevicePropertiesRequest());
     case Request::TConnectedDevicePropertiesRequest:
       return actor->DoRequest(aRequest.get_ConnectedDevicePropertiesRequest());
@@ -398,32 +402,50 @@ BluetoothRequestParent::DoRequest(const 
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest);
 
-  nsresult rv =
-    mService->StartDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StartDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest);
 
-  nsresult rv =
-    mService->StopDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StopDiscoveryInternal(mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const StartLeScanRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStartLeScanRequest);
+
+  mService->StartLeScanInternal(aRequest.serviceUuids(), mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const StopLeScanRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStopLeScanRequest);
+
+  mService->StopLeScanInternal(aRequest.scanUuid(), mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const PairRequest& aRequest)
 {
   MOZ_ASSERT(mService);
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
@@ -142,16 +142,22 @@ protected:
 
   bool
   DoRequest(const StartDiscoveryRequest& aRequest);
 
   bool
   DoRequest(const StopDiscoveryRequest& aRequest);
 
   bool
+  DoRequest(const StartLeScanRequest& aRequest);
+
+  bool
+  DoRequest(const StopLeScanRequest& aRequest);
+
+  bool
   DoRequest(const PairRequest& aRequest);
 
   bool
   DoRequest(const UnpairRequest& aRequest);
 
   bool
   DoRequest(const PairedDevicePropertiesRequest& aRequest);
 
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -142,30 +142,44 @@ BluetoothServiceChildProcess::GetPairedD
 nsresult
 BluetoothServiceChildProcess::FetchUuidsInternal(
   const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
   return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StopDiscoveryInternal(
-                                              BluetoothReplyRunnable* aRunnable)
+   BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StopDiscoveryRequest());
-  return NS_OK;
+}
+
+void
+BluetoothServiceChildProcess::StartDiscoveryInternal(
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StartDiscoveryRequest());
 }
 
-nsresult
-BluetoothServiceChildProcess::StartDiscoveryInternal(
-                                              BluetoothReplyRunnable* aRunnable)
+void
+BluetoothServiceChildProcess::StopLeScanInternal(
+  const nsAString& aScanUuid,
+  BluetoothReplyRunnable* aRunnable)
 {
-  SendRequest(aRunnable, StartDiscoveryRequest());
-  return NS_OK;
+  SendRequest(aRunnable, StopLeScanRequest(nsString(aScanUuid)));
+}
+
+void
+BluetoothServiceChildProcess::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StartLeScanRequest(aServiceUuids));
 }
 
 nsresult
 BluetoothServiceChildProcess::SetProperty(BluetoothObjectType aType,
                                           const BluetoothNamedValue& aValue,
                                           BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, SetPropertyRequest(aType, aValue));
@@ -324,17 +338,17 @@ BluetoothServiceChildProcess::ConfirmRec
   bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   if(aConfirm) {
     SendRequest(aRunnable,
                 ConfirmReceivingFileRequest(nsString(aDeviceAddress)));
     return;
   }
-  
+
   SendRequest(aRunnable,
               DenyReceivingFileRequest(nsString(aDeviceAddress)));
 }
 
 void
 BluetoothServiceChildProcess::ConnectSco(BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectScoRequest());
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -48,22 +48,30 @@ public:
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        override;
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  StopLeScanInternal(const nsAString& aScanUuid,
+                     BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) override;
+
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   CreatePairedDeviceInternal(const nsAString& aAddress,
                              int aTimeout,
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
@@ -23,16 +23,17 @@ namespace bluetooth {
 
 /**
  * Value structure for returns from bluetooth. Currently modeled after dbus
  * returns, which can be a 32-bit int, an UTF16 string, a bool, or an array of
  * UTF16 strings. Can also hold key-value pairs for dictionary-ish access.
  */
 union BluetoothValue
 {
+  int32_t;
   uint32_t;
   nsString;
   bool;
   nsString[];
   uint8_t[];
   BluetoothNamedValue[];
   BluetoothGattId;
   BluetoothGattId[];
--- a/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
@@ -48,16 +48,26 @@ struct GetPropertyRequest
 struct StartDiscoveryRequest
 {
 };
 
 struct StopDiscoveryRequest
 {
 };
 
+struct StartLeScanRequest
+{
+  nsString[] serviceUuids;
+};
+
+struct StopLeScanRequest
+{
+  nsString scanUuid;
+};
+
 struct PairRequest
 {
   nsString address;
   uint32_t timeoutMS;
 };
 
 struct UnpairRequest
 {
@@ -269,16 +279,18 @@ union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
   StopDiscoveryRequest;
+  StartLeScanRequest;
+  StopLeScanRequest;
   PairRequest;
   UnpairRequest;
   PinReplyRequest;
   SspReplyRequest;
   SetPinCodeRequest;
   SetPasskeyRequest;
   ConfirmPairingConfirmationRequest;
   DenyPairingConfirmationRequest;
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -2839,26 +2839,26 @@ BluetoothDBusService::SendSinkMessage(co
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(!sAdapterPath.IsEmpty());
   nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress);
   return SendAsyncDBusMessage(objectPath, DBUS_SINK_IFACE, aMessage, callback);
 }
 
-nsresult
+void
 BluetoothDBusService::StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable)
 {
-  return SendDiscoveryMessage("StopDiscovery", aRunnable);
+  SendDiscoveryMessage("StopDiscovery", aRunnable);
 }
 
-nsresult
+void
 BluetoothDBusService::StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable)
 {
-  return SendDiscoveryMessage("StartDiscovery", aRunnable);
+  SendDiscoveryMessage("StartDiscovery", aRunnable);
 }
 
 class BluetoothArrayOfDevicePropertiesReplyHandler : public DBusReplyHandler
 {
 public:
   BluetoothArrayOfDevicePropertiesReplyHandler(
     const nsTArray<nsString>& aDeviceAddresses,
     const FilterFunc aFilterFunc, BluetoothReplyRunnable* aRunnable)
@@ -4691,16 +4691,30 @@ BluetoothDBusService::UpdateNotification
   a2dp->GetAddress(deviceAddress);
 
   Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
   DispatchToDBusThread(task);
 }
 
 #ifdef MOZ_B2G_BT_API_V2
 void
+BluetoothDBusService::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable);
+{
+}
+
+void
+BluetoothDBusService::StopLeScanInternal(
+  const nsAString& aAppUuid,
+  BluetoothReplyRunnable* aRunnable);
+{
+}
+
+void
 BluetoothDBusService::ConnectGattClientInternal(
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
 BluetoothDBusService::DisconnectGattClientInternal(
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -77,19 +77,19 @@ public:
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                              BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable) override;
 #endif
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
@@ -230,16 +230,24 @@ public:
                   const nsAString& aMessage) override;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) override;
 
 #ifdef MOZ_B2G_BT_API_V2
   virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  StopLeScanInternal(const nsAString& aAppUuid,
+                     BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
   ConnectGattClientInternal(const nsAString& aAppUuid,
                             const nsAString& aDeviceAddress,
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) override;
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -29,16 +29,17 @@ if CONFIG['MOZ_B2G_BT']:
             'bluetooth2/BluetoothAdapter.cpp',
             'bluetooth2/BluetoothClassOfDevice.cpp',
             'bluetooth2/BluetoothDevice.cpp',
             'bluetooth2/BluetoothDiscoveryHandle.cpp',
             'bluetooth2/BluetoothGatt.cpp',
             'bluetooth2/BluetoothGattCharacteristic.cpp',
             'bluetooth2/BluetoothGattDescriptor.cpp',
             'bluetooth2/BluetoothGattService.cpp',
+            'bluetooth2/BluetoothLeDeviceEvent.cpp',
             'bluetooth2/BluetoothManager.cpp',
             'bluetooth2/BluetoothPairingHandle.cpp',
             'bluetooth2/BluetoothPairingListener.cpp',
             'bluetooth2/BluetoothProfileController.cpp',
             'bluetooth2/BluetoothReplyRunnable.cpp',
             'bluetooth2/BluetoothService.cpp',
             'bluetooth2/ipc/BluetoothChild.cpp',
             'bluetooth2/ipc/BluetoothParent.cpp',
@@ -167,16 +168,17 @@ if CONFIG['MOZ_B2G_BT_API_V2']:
         'bluetooth2/BluetoothAdapter.h',
         'bluetooth2/BluetoothClassOfDevice.h',
         'bluetooth2/BluetoothDevice.h',
         'bluetooth2/BluetoothDiscoveryHandle.h',
         'bluetooth2/BluetoothGatt.h',
         'bluetooth2/BluetoothGattCharacteristic.h',
         'bluetooth2/BluetoothGattDescriptor.h',
         'bluetooth2/BluetoothGattService.h',
+        'bluetooth2/BluetoothLeDeviceEvent.h',
         'bluetooth2/BluetoothManager.h',
         'bluetooth2/BluetoothPairingHandle.h',
         'bluetooth2/BluetoothPairingListener.h',
     ]
     IPDL_SOURCES += [
         'bluetooth2/ipc/BluetoothTypes.ipdlh',
         'bluetooth2/ipc/PBluetooth.ipdl',
         'bluetooth2/ipc/PBluetoothRequest.ipdl',
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -63,16 +63,20 @@ const kEventConstructors = {
   BluetoothDiscoveryStateChangedEvent:       { create: function (aName, aProps) {
                                                           return new BluetoothDiscoveryStateChangedEvent(aName, aProps);
                                                        },
                                              },
   BluetoothGattCharacteristicEvent:          { create: function (aName, aProps) {
                                                           return new BluetoothGattCharacteristicEvent(aName, aProps);
                                                        },
                                              },
+  BluetoothLeDeviceEvent:                    { create: function (aName, aProps) {
+                                                          return new BluetoothLeDeviceEvent(aName, aProps);
+                                                       },
+                                             },
   BluetoothPairingEvent:                     { create: function (aName, aProps) {
                                                           return new BluetoothPairingEvent(aName, aProps);
                                                        },
                                              },
   BluetoothStatusChangedEvent:               { create: function (aName, aProps) {
                                                           return new BluetoothStatusChangedEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/webidl/BluetoothAdapter2.webidl
+++ b/dom/webidl/BluetoothAdapter2.webidl
@@ -92,16 +92,22 @@ interface BluetoothAdapter : EventTarget
 
   [NewObject]
   Promise<void> pair(DOMString deviceAddress);
   [NewObject]
   Promise<void> unpair(DOMString deviceAddress);
 
   sequence<BluetoothDevice> getPairedDevices();
 
+  [NewObject]
+  Promise<BluetoothDiscoveryHandle> startLeScan(sequence<DOMString> serviceUuids);
+
+  [NewObject]
+  Promise<void> stopLeScan(BluetoothDiscoveryHandle discoveryHandle);
+
   [NewObject, Throws, AvailableIn=CertifiedApps]
   DOMRequest getConnectedDevices(unsigned short serviceUuid);
 
   /**
    * Connect/Disconnect to a specific service of a target remote device.
    * To check the value of service UUIDs, please check "Bluetooth Assigned
    * Numbers" / "Service Discovery Protocol" for more information.
    *
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothLeDeviceEvent.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[CheckPermissions="bluetooth",
+ Constructor(DOMString type, optional BluetoothLeDeviceEventInit eventInitDict)]
+interface BluetoothLeDeviceEvent : Event
+{
+  readonly attribute BluetoothDevice device;
+  readonly attribute short rssi;
+  [Throws]
+  readonly attribute ArrayBuffer scanRecord;
+};
+
+dictionary BluetoothLeDeviceEventInit : EventInit
+{
+  required BluetoothDevice device;
+  short  rssi = 0;
+  required ArrayBuffer scanRecord;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -644,16 +644,17 @@ if CONFIG['MOZ_B2G_BT']:
             'BluetoothAdapter2.webidl',
             'BluetoothClassOfDevice.webidl',
             'BluetoothDevice2.webidl',
             'BluetoothDiscoveryHandle.webidl',
             'BluetoothGatt.webidl',
             'BluetoothGattCharacteristic.webidl',
             'BluetoothGattDescriptor.webidl',
             'BluetoothGattService.webidl',
+            'BluetoothLeDeviceEvent.webidl',
             'BluetoothManager2.webidl',
             'BluetoothPairingHandle.webidl',
             'BluetoothPairingListener.webidl',
         ]
     else:
         WEBIDL_FILES += [
             'BluetoothAdapter.webidl',
             'BluetoothDevice.webidl',