Bug 782588 - 'mozBluetooth is not a singleton'. r=qDot.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 06 Sep 2012 07:15:36 -0700
changeset 111336 0ee117877c93638147dd8e516e13e3603bf469fa
parent 111335 42777635165aa643095462b12975fe18896d4bb4
child 111337 fe317c21b7497e27311f5f63e0da672773618c3a
push id239
push userakeybl@mozilla.com
push dateThu, 03 Jan 2013 21:54:43 +0000
treeherdermozilla-release@3a7b66445659 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersqDot
bugs782588
milestone18.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 782588 - 'mozBluetooth is not a singleton'. r=qDot.
dom/bluetooth/BluetoothManager.cpp
dom/bluetooth/BluetoothManager.h
dom/bluetooth/BluetoothService.cpp
dom/bluetooth/BluetoothService.h
layout/build/nsLayoutModule.cpp
--- a/dom/bluetooth/BluetoothManager.cpp
+++ b/dom/bluetooth/BluetoothManager.cpp
@@ -10,27 +10,21 @@
 #include "BluetoothAdapter.h"
 #include "BluetoothService.h"
 #include "BluetoothTypes.h"
 #include "BluetoothReplyRunnable.h"
 
 #include "nsContentUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsDOMEvent.h"
-#include "nsDOMEventTargetHelper.h"
 #include "nsIDOMDOMRequest.h"
-#include "nsIJSContextStack.h"
-#include "nsIObserverService.h"
 #include "nsIPermissionManager.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCIDInternal.h"
-#include "mozilla/LazyIdleThread.h"
-#include "mozilla/Services.h"
 #include "mozilla/Util.h"
-#include "nsIDOMDOMRequest.h"
 
 using namespace mozilla;
 
 USING_BLUETOOTH_NAMESPACE
 
 DOMCI_DATA(BluetoothManager, BluetoothManager)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothManager)
@@ -111,37 +105,36 @@ public:
       mEnabled(aEnabled)
   {
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    mManagerPtr->SetEnabledInternal(mEnabled);
-    mManagerPtr->FireEnabledDisabledEvent();
+    mManagerPtr->FireEnabledDisabledEvent(mEnabled);
 
     // mManagerPtr must be null before returning to prevent the background
     // thread from racing to release it during the destruction of this runnable.
     mManagerPtr = nullptr;
 
     return NS_OK;
   }
 
 private:
   nsRefPtr<BluetoothManager> mManagerPtr;
   bool mEnabled;
 };
 
 nsresult
-BluetoothManager::FireEnabledDisabledEvent()
+BluetoothManager::FireEnabledDisabledEvent(bool aEnabled)
 {
   nsString eventName;
 
-  if (mEnabled) {
+  if (aEnabled) {
     eventName.AssignLiteral("enabled");
   } else {
     eventName.AssignLiteral("disabled");
   }
 
   nsRefPtr<nsDOMEvent> event = new nsDOMEvent(nullptr, nullptr);
   nsresult rv = event->InitEvent(eventName, false, false);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -151,152 +144,31 @@ BluetoothManager::FireEnabledDisabledEve
 
   bool dummy;
   rv = DispatchEvent(event, &dummy);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-BluetoothManager::BluetoothManager(nsPIDOMWindow *aWindow) :
-  BluetoothPropertyContainer(BluetoothObjectType::TYPE_MANAGER),
-  mEnabled(false)
+BluetoothManager::BluetoothManager(nsPIDOMWindow *aWindow)
+: BluetoothPropertyContainer(BluetoothObjectType::TYPE_MANAGER)
 {
+  MOZ_ASSERT(aWindow);
+
   BindToOwner(aWindow);
   mPath.AssignLiteral("/");
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->AddObserver(this, "mozsettings-changed", false);
-  }
 }
 
 BluetoothManager::~BluetoothManager()
 {
   BluetoothService* bs = BluetoothService::Get();
-  // We can be null on shutdown, where this might happen
   if (bs) {
-    if (NS_FAILED(bs->UnregisterBluetoothSignalHandler(mPath, this))) {
-      NS_WARNING("Failed to unregister object with observer!");
-    }
-  }
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, "mozsettings-changed");
-  }
-}
-
-nsresult
-BluetoothManager::HandleMozsettingChanged(const PRUnichar* aData)
-{
-  // The string that we're interested in will be a JSON string that looks like:
-  //  {"key":"bluetooth.enabled","value":true}
-  nsresult rv;
-
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  JSContext *cx = sc->GetNativeContext();
-  if (!cx) {
-    return NS_OK;
-  }
-
-  // In the following [if] blocks, NS_OK will be returned even if JS_* functions
-  // return false. That's because this function gets called whenever mozSettings
-  // changes, so that we'll receive signals we're not interested in and it would
-  // be one of the reasons for making JS_* functions return false.
-  nsDependentString dataStr(aData);
-  JS::Value val;
-  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val)) {
-    return NS_OK;
-  }
-
-  if (!val.isObject()) {
-    return NS_OK;
-  }
-
-  JSObject &obj(val.toObject());
-  JS::Value key;
-  if (!JS_GetProperty(cx, &obj, "key", &key)) {
-    return NS_OK;
-  }
-
-  if (!key.isString()) {
-    return NS_OK;
-  }
-
-  JSBool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), "bluetooth.enabled", &match)) {
-    return NS_OK;
+    bs->UnregisterManager(this);
   }
-
-  if (!match) {
-    return NS_OK;
-  }
-
-  JS::Value value;
-  if (!JS_GetProperty(cx, &obj, "value", &value)) {
-    return NS_OK;
-  }
-
-  if (!value.isBoolean()) {
-    return NS_OK;
-  }
-
-  BluetoothService* bs = BluetoothService::Get();
-  if (!bs) {
-    NS_WARNING("BluetoothService not available!");
-    return NS_ERROR_FAILURE;
-  }
-
-  bool enabled = value.toBoolean();
-  bool isEnabled = (bs->IsEnabledInternal() > 0);
-  if (!isEnabled && enabled) {
-    if (NS_FAILED(bs->RegisterBluetoothSignalHandler(NS_LITERAL_STRING("/"), this))) {
-      NS_ERROR("Failed to register object with observer!");
-      return NS_ERROR_FAILURE;
-    }
-  } else if (isEnabled && !enabled){
-    if (NS_FAILED(bs->UnregisterBluetoothSignalHandler(NS_LITERAL_STRING("/"), this))) {
-      NS_WARNING("Failed to unregister object with observer!");
-    }
-  } else {
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIRunnable> resultTask = new ToggleBtResultTask(this, enabled);
-
-  if (enabled) {
-    if (NS_FAILED(bs->Start(resultTask))) {
-      return NS_ERROR_FAILURE;
-    }
-  } else {
-    if (NS_FAILED(bs->Stop(resultTask))) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-BluetoothManager::Observe(nsISupports* aSubject,
-                          const char* aTopic,
-                          const PRUnichar* aData)
-{
-  nsresult rv = NS_OK;
-
-  if (!strcmp("mozsettings-changed", aTopic)) {
-    rv = HandleMozsettingChanged(aData);
-  }
-
-  return rv;
 }
 
 void
 BluetoothManager::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
 #ifdef DEBUG
     const nsString& name = aValue.name();
     nsCString warningMsg;
@@ -304,29 +176,35 @@ BluetoothManager::SetPropertyByValue(con
     warningMsg.Append(NS_ConvertUTF16toUTF8(name));
     NS_WARNING(warningMsg.get());
 #endif
 }
 
 NS_IMETHODIMP
 BluetoothManager::GetEnabled(bool* aEnabled)
 {
-  *aEnabled = mEnabled;
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs) {
+    NS_WARNING("BluetoothService not available!");
+    return NS_ERROR_FAILURE;
+  }
+
+  *aEnabled = bs->IsEnabled();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BluetoothManager::GetDefaultAdapter(nsIDOMDOMRequest** aAdapter)
 {
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     NS_WARNING("BluetoothService not available!");
     return NS_ERROR_FAILURE;
   }
-  
+
   nsCOMPtr<nsIDOMRequestService> rs = do_GetService("@mozilla.org/dom/dom-request-service;1");
 
   if (!rs) {
     NS_WARNING("No DOMRequest Service!");
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIDOMDOMRequest> request;
@@ -342,32 +220,31 @@ BluetoothManager::GetDefaultAdapter(nsID
     return NS_ERROR_FAILURE;
   }
   request.forget(aAdapter);
   return NS_OK;
 }
 
 // static
 already_AddRefed<BluetoothManager>
-BluetoothManager::Create(nsPIDOMWindow* aWindow) {
-  nsRefPtr<BluetoothManager> manager = new BluetoothManager(aWindow);
+BluetoothManager::Create(nsPIDOMWindow* aWindow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     NS_WARNING("BluetoothService not available!");
     return nullptr;
   }
 
-  bool isEnabled = (bs->IsEnabledInternal() > 0);
-  if (isEnabled) {
-    if (NS_FAILED(bs->RegisterBluetoothSignalHandler(NS_LITERAL_STRING("/"), manager))) {
-      NS_ERROR("Failed to register object with observer!");
-      return nullptr;
-    }
-  }
-  
+  nsRefPtr<BluetoothManager> manager = new BluetoothManager(aWindow);
+
+  bs->RegisterManager(manager);
+
   return manager.forget();
 }
 
 nsresult
 NS_NewBluetoothManager(nsPIDOMWindow* aWindow,
                        nsIDOMBluetoothManager** aBluetoothManager)
 {
   NS_ASSERTION(aWindow, "Null pointer!");
@@ -384,28 +261,24 @@ NS_NewBluetoothManager(nsPIDOMWindow* aW
   NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
 
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   NS_ENSURE_TRUE(permMgr, NS_ERROR_UNEXPECTED);
 
   uint32_t permission;
   nsresult rv =
-    permMgr->TestPermissionFromPrincipal(principal, "mozBluetooth", &permission);
+    permMgr->TestPermissionFromPrincipal(principal, "mozBluetooth",
+                                         &permission);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (permission != nsIPermissionManager::ALLOW_ACTION) {
-    *aBluetoothManager = nullptr;
-    return NS_OK;
-  }
+  nsRefPtr<BluetoothManager> bluetoothManager;
 
-  nsRefPtr<BluetoothManager> bluetoothManager = BluetoothManager::Create(aWindow);
-  if (!bluetoothManager) {
-    NS_ERROR("Cannot create bluetooth manager!");
-    return NS_ERROR_FAILURE;
+  if (permission == nsIPermissionManager::ALLOW_ACTION) {
+    bluetoothManager = BluetoothManager::Create(aWindow);
   }
 
   bluetoothManager.forget(aBluetoothManager);
   return NS_OK;
 }
 
 void
 BluetoothManager::Notify(const BluetoothSignal& aData)
--- a/dom/bluetooth/BluetoothManager.h
+++ b/dom/bluetooth/BluetoothManager.h
@@ -6,55 +6,44 @@
 
 #ifndef mozilla_dom_bluetooth_bluetoothmanager_h__
 #define mozilla_dom_bluetooth_bluetoothmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothPropertyContainer.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDOMBluetoothManager.h"
-#include "nsIEventTarget.h"
-#include "nsIObserver.h"
 #include "mozilla/Observer.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothNamedValue;
 
 class BluetoothManager : public nsDOMEventTargetHelper
                        , public nsIDOMBluetoothManager
                        , public BluetoothSignalObserver
                        , public BluetoothPropertyContainer
-                       , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMBLUETOOTHMANAGER
-  NS_DECL_NSIOBSERVER
 
   NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::)
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothManager,
                                            nsDOMEventTargetHelper)
 
-
-  inline void SetEnabledInternal(bool aEnabled) {mEnabled = aEnabled;}
-
   static already_AddRefed<BluetoothManager>
   Create(nsPIDOMWindow* aWindow);
   void Notify(const BluetoothSignal& aData);
   virtual void SetPropertyByValue(const BluetoothNamedValue& aValue);
-  nsresult FireEnabledDisabledEvent();
+  nsresult FireEnabledDisabledEvent(bool aEnabled);
 private:
   BluetoothManager(nsPIDOMWindow* aWindow);
   ~BluetoothManager();
-
-  nsresult HandleMozsettingChanged(const PRUnichar* aData);
-
-  bool mEnabled;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 nsresult NS_NewBluetoothManager(nsPIDOMWindow* aWindow,
                                 nsIDOMBluetoothManager** aBluetoothManager);
 
 #endif
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -2,116 +2,150 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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 "base/basictypes.h"
 
 #include "BluetoothService.h"
+#include "BluetoothManager.h"
 #include "BluetoothTypes.h"
 #include "BluetoothReplyRunnable.h"
 
+#include "jsapi.h"
+#include "mozilla/Services.h"
+#include "mozilla/Util.h"
+#include "nsContentUtils.h"
 #include "nsIDOMDOMRequest.h"
+#include "nsIObserverService.h"
+#include "nsISettingsService.h"
 #include "nsThreadUtils.h"
+#include "nsXPCOM.h"
 #include "nsXPCOMCIDInternal.h"
-#include "nsObserverService.h"
-#include "mozilla/Services.h"
-#include "mozilla/LazyIdleThread.h"
-#include "mozilla/Util.h"
+
+#define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
+#define BLUETOOTH_ENABLED_SETTING "bluetooth.enabled"
 
 using namespace mozilla;
 
 USING_BLUETOOTH_NAMESPACE
 
 static nsRefPtr<BluetoothService> gBluetoothService;
 static bool gInShutdown = false;
 
 NS_IMPL_ISUPPORTS1(BluetoothService, nsIObserver)
 
-class ToggleBtAck : public nsRunnable
+class BluetoothService::ToggleBtAck : public nsRunnable
 {
 public:
-  ToggleBtAck(bool aEnabled) : mEnabled(aEnabled)
+  ToggleBtAck(bool aEnabled)
+    : mEnabled(aEnabled)
   {
+    MOZ_ASSERT(!NS_IsMainThread());
   }
-  
+
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    if (!gBluetoothService) {
+      return NS_OK;
+    }
+
+    if (!gInShutdown) {
+      // Notify all the managers about the state change.
+      gBluetoothService->SetEnabled(mEnabled);
+    }
+
     if (!mEnabled || gInShutdown) {
+      // Shut down the command thread if it still exists.
       if (gBluetoothService->mBluetoothCommandThread) {
-        nsCOMPtr<nsIThread> t;
-        gBluetoothService->mBluetoothCommandThread.swap(t);
-        t->Shutdown();
+        nsCOMPtr<nsIThread> thread;
+        gBluetoothService->mBluetoothCommandThread.swap(thread);
+        if (NS_FAILED(thread->Shutdown())) {
+          NS_WARNING("Failed to shut down the bluetooth command thread!");
+        }
       }
-    }
-    
-    if (gInShutdown) {
-      gBluetoothService = nullptr;
+
+      if (gInShutdown) {
+        gBluetoothService = nullptr;
+      }
     }
 
     return NS_OK;
   }
 
+private:
   bool mEnabled;
 };
 
-class ToggleBtTask : public nsRunnable
+class BluetoothService::ToggleBtTask : public nsRunnable
 {
 public:
-  ToggleBtTask(bool aEnabled,
-               nsIRunnable* aRunnable)
-    : mEnabled(aEnabled),
-      mRunnable(aRunnable)
+  ToggleBtTask(bool aEnabled)
+    : mEnabled(aEnabled)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   NS_IMETHOD Run() 
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
-    nsString replyError;
     if (mEnabled) {
       if (NS_FAILED(gBluetoothService->StartInternal())) {
-        replyError.AssignLiteral("Bluetooth service not available - We should never reach this point!");
+        NS_WARNING("Bluetooth service failed to start!");
+        mEnabled = !mEnabled;
       }
     }
-    else {
-      if (NS_FAILED(gBluetoothService->StopInternal())) {        
-        replyError.AssignLiteral("Bluetooth service not available - We should never reach this point!");
-      }
+    else if (NS_FAILED(gBluetoothService->StopInternal())) {
+      NS_WARNING("Bluetooth service failed to stop!");
+      mEnabled = !mEnabled;
     }
 
-    // Always has to be called since this is where we take care of our reference
-    // count for runnables. If there's an error, replyError won't be empty, so
-    // consider our status flipped.
-    nsCOMPtr<nsIRunnable> ackTask = new ToggleBtAck(mEnabled);
+    nsCOMPtr<nsIRunnable> ackTask = new BluetoothService::ToggleBtAck(mEnabled);
     if (NS_FAILED(NS_DispatchToMainThread(ackTask))) {
       NS_WARNING("Failed to dispatch to main thread!");
     }
-    
-    if (!mRunnable) {
-      return NS_OK;
-    }
 
-    if (NS_FAILED(NS_DispatchToMainThread(mRunnable))) {
-      NS_WARNING("Failed to dispatch to main thread!");
-    }
-    
     return NS_OK;
   }
 
 private:
   bool mEnabled;
-  nsCOMPtr<nsIRunnable> mRunnable;
 };
 
+class BluetoothService::StartupTask : public nsISettingsServiceCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Handle(const nsAString& aName, const jsval& aResult, JSContext* aCx)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(gBluetoothService);
+
+    if (!aResult.isBoolean()) {
+      NS_WARNING("Setting for '" BLUETOOTH_ENABLED_SETTING "' is not a boolean!");
+      return NS_OK;
+    }
+
+    return aResult.toBoolean() ? gBluetoothService->Start() : NS_OK;
+  }
+
+  NS_IMETHOD HandleError(const nsAString& aName, JSContext* aCx)
+  {
+    NS_WARNING("Unable to get value for '" BLUETOOTH_ENABLED_SETTING "'");
+    return NS_OK;
+  }
+};
+
+NS_IMPL_ISUPPORTS1(BluetoothService::StartupTask, nsISettingsServiceCallback);
+
 nsresult
 BluetoothService::RegisterBluetoothSignalHandler(const nsAString& aNodeName,
                                                  BluetoothSignalObserver* aHandler)
 {
   MOZ_ASSERT(NS_IsMainThread());
   BluetoothSignalObserverList* ol;
   if (!mBluetoothSignalObserverTable.Get(aNodeName, &ol)) {
     ol = new BluetoothSignalObserverList();
@@ -158,88 +192,278 @@ BluetoothService::DistributeSignal(const
     NS_WARNING("Distributing to observer list of 0");
   }
 #endif
   ol->Broadcast(signal);
   return NS_OK;
 }
 
 nsresult
-BluetoothService::StartStopBluetooth(nsIRunnable* aResultRunnable, bool aStart)
+BluetoothService::StartStopBluetooth(bool aStart)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // If we're shutting down, bail early.
-  if (gInShutdown && aStart) {
-    NS_ERROR("Start called while in shutdown!");
-    return NS_ERROR_FAILURE;
+#ifdef DEBUG
+  if (aStart && mLastRequestedEnable) {
+    MOZ_ASSERT(false, "Calling Start twice in a row!");
+  }
+  else if (!aStart && !mLastRequestedEnable) {
+    MOZ_ASSERT(false, "Calling Stop twice in a row!");
   }
+  mLastRequestedEnable = aStart;
+#endif
+
+  if (gInShutdown) {
+    if (aStart) {
+      // Don't try to start if we're already shutting down.
+      MOZ_ASSERT(false, "Start called while in shutdown!");
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!mBluetoothCommandThread) {
+      // Don't create a new thread after we've begun shutdown since bluetooth
+      // can't be running.
+      return NS_OK;
+    }
+  }
+
+  nsresult rv;
+
   if (!mBluetoothCommandThread) {
-    nsresult rv = NS_NewNamedThread("BluetoothCmd",
-                                    getter_AddRefs(mBluetoothCommandThread));
+    MOZ_ASSERT(!gInShutdown);
+
+    rv = NS_NewNamedThread("BluetoothCmd",
+                           getter_AddRefs(mBluetoothCommandThread));
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  nsCOMPtr<nsIRunnable> r = new ToggleBtTask(aStart, aResultRunnable);
-  if (NS_FAILED(mBluetoothCommandThread->Dispatch(r, NS_DISPATCH_NORMAL))) {
-    NS_WARNING("Cannot dispatch firmware loading task!");
-    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIRunnable> runnable = new ToggleBtTask(aStart);
+  rv = mBluetoothCommandThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+BluetoothService::SetEnabled(bool aEnabled)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aEnabled == mEnabled) {
+    // Nothing to do, maybe something failed.
+    return;
+  }
+
+  mEnabled = aEnabled;
+
+  BluetoothManagerList::ForwardIterator iter(mLiveManagers);
+  while (iter.HasMore()) {
+    if (NS_FAILED(iter.GetNext()->FireEnabledDisabledEvent(mEnabled))) {
+      NS_WARNING("FireEnabledDisabledEvent failed!");
+    }
   }
+}
+
+nsresult
+BluetoothService::Start()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return StartStopBluetooth(true);
+}
+
+nsresult
+BluetoothService::Stop()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return StartStopBluetooth(false);
+}
+
+nsresult
+BluetoothService::HandleStartup()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsISettingsService> settings =
+    do_GetService("@mozilla.org/settingsService;1");
+  NS_ENSURE_TRUE(settings, NS_ERROR_UNEXPECTED);
+
+  nsCOMPtr<nsISettingsServiceLock> settingsLock;
+  nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<StartupTask> callback = new StartupTask();
+  rv = settingsLock->Get(BLUETOOTH_ENABLED_SETTING, callback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
-BluetoothService::Start(nsIRunnable* aResultRunnable)
+BluetoothService::HandleSettingsChanged(const nsAString& aData)
 {
-  return StartStopBluetooth(aResultRunnable, true);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // The string that we're interested in will be a JSON string that looks like:
+  //  {"key":"bluetooth.enabled","value":true}
+
+  JSContext* cx = nsContentUtils::GetSafeJSContext();
+  if (!cx) {
+    return NS_OK;
+  }
+
+  JS::Value val;
+  if (!JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)) {
+    return JS_ReportPendingException(cx) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!val.isObject()) {
+    return NS_OK;
+  }
+
+  JSObject& obj(val.toObject());
+
+  JS::Value key;
+  if (!JS_GetProperty(cx, &obj, "key", &key)) {
+    MOZ_ASSERT(!JS_IsExceptionPending(cx));
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!key.isString()) {
+    return NS_OK;
+  }
+
+  JSBool match;
+  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_ENABLED_SETTING,
+                            &match)) {
+    MOZ_ASSERT(!JS_IsExceptionPending(cx));
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!match) {
+    return NS_OK;
+  }
+
+  JS::Value value;
+  if (!JS_GetProperty(cx, &obj, "value", &value)) {
+    MOZ_ASSERT(!JS_IsExceptionPending(cx));
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!value.isBoolean()) {
+    MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (value.toBoolean() == IsEnabled()) {
+    // Nothing to do here.
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  if (IsEnabled()) {
+    rv = Stop();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  rv = Start();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 nsresult
-BluetoothService::Stop(nsIRunnable* aResultRunnable)
+BluetoothService::HandleShutdown()
 {
-  return StartStopBluetooth(aResultRunnable, false);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  gInShutdown = true;
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs &&
+      (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) ||
+       NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID)))) {
+    NS_WARNING("Can't unregister observers!");
+  }
+
+  return Stop();
+}
+
+void
+BluetoothService::RegisterManager(BluetoothManager* aManager)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aManager);
+  MOZ_ASSERT(!mLiveManagers.Contains(aManager));
+
+  mLiveManagers.AppendElement(aManager);
+  RegisterBluetoothSignalHandler(aManager->GetPath(), aManager);
+}
+
+void
+BluetoothService::UnregisterManager(BluetoothManager* aManager)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aManager);
+  MOZ_ASSERT(mLiveManagers.Contains(aManager));
+
+  UnregisterBluetoothSignalHandler(aManager->GetPath(), aManager);
+  mLiveManagers.RemoveElement(aManager);
 }
 
 // static
 BluetoothService*
 BluetoothService::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // If we already exist, exit early
   if (gBluetoothService) {
     return gBluetoothService;
   }
-  
+
   // If we're in shutdown, don't create a new instance
   if (gInShutdown) {
-    NS_WARNING("BluetoothService returns null during shutdown");
+    NS_WARNING("BluetoothService can't be created during shutdown");
     return nullptr;
   }
 
   // Create new instance, register, return
-  gBluetoothService = BluetoothService::Create();
-  if (!gBluetoothService) {
-    NS_WARNING("Cannot create bluetooth service!");
+  nsRefPtr<BluetoothService> service = BluetoothService::Create();
+  NS_ENSURE_TRUE(service, nullptr);
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE(obs, nullptr);
+
+  if (NS_FAILED(obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+                                 false)) ||
+      NS_FAILED(obs->AddObserver(service, MOZSETTINGS_CHANGED_ID, false))) {
+    NS_WARNING("AddObserver failed!");
     return nullptr;
   }
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  MOZ_ASSERT(obs);
 
-  if (NS_FAILED(obs->AddObserver(gBluetoothService, "xpcom-shutdown", false))) {
-    NS_ERROR("AddObserver failed!");
-  }
+  gBluetoothService.swap(service);
   return gBluetoothService;
 }
 
 nsresult
 BluetoothService::Observe(nsISupports* aSubject, const char* aTopic,
                           const PRUnichar* aData)
 {
-  NS_ASSERTION(!strcmp(aTopic, "xpcom-shutdown"),
-               "BluetoothService got unexpected topic!");
-  gInShutdown = true;
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (obs && NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown"))) {
-    NS_WARNING("Can't unregister bluetooth service with xpcom shutdown!");
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!strcmp(aTopic, "profile-after-change")) {
+    return HandleStartup();
   }
 
-  return Stop(nullptr);
+  if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
+    return HandleSettingsChanged(nsDependentString(aData));
+  }
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    return HandleShutdown();
+  }
+
+  MOZ_ASSERT(false, "BluetoothService got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
 }
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -2,30 +2,41 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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_bluetootheventservice_h__
 #define mozilla_dom_bluetooth_bluetootheventservice_h__
 
-#include "nsThreadUtils.h"
+#include "BluetoothCommon.h"
+#include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 #include "nsIObserver.h"
-#include "nsIRunnable.h"
-#include "BluetoothCommon.h"
+#include "nsIThread.h"
+#include "nsTObserverArray.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothSignal;
+class BluetoothManager;
+class BluetoothNamedValue;
 class BluetoothReplyRunnable;
-class BluetoothNamedValue;
+class BluetoothSignal;
 
 class BluetoothService : public nsIObserver
 {
+  class ToggleBtAck;
+  friend class ToggleBtAck;
+
+  class ToggleBtTask;
+  friend class ToggleBtTask;
+
+  class StartupTask;
+  friend class StartupTask;
+
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   /** 
    * Add a message handler object from message distribution observer.
    * Must be called from the main thread.
    *
@@ -60,51 +71,73 @@ public:
    */
   nsresult DistributeSignal(const BluetoothSignal& aEvent);
 
   /** 
    * Start bluetooth services. Starts up any threads and connections that
    * bluetooth needs to operate on the current platform. Assumed to be run on
    * the main thread with delayed return for blocking startup functions via
    * runnable.
-   *
-   * @param aResultRunnable Runnable object to execute after bluetooth has
-   * either come up or failed. Runnable should check existence of
-   * BluetoothService via Get() function to see if service started correctly.
-   *
    * @return NS_OK on initialization starting correctly, NS_ERROR_FAILURE
    * otherwise
    */
-  nsresult Start(nsIRunnable* aResultRunnable);
+  nsresult Start();
 
   /** 
    * Stop bluetooth services. Starts up any threads and connections that
    * bluetooth needs to operate on the current platform. Assumed to be run on
    * the main thread with delayed return for blocking startup functions via
    * runnable.
    *
-   * @param aResultRunnable Runnable object to execute after bluetooth has
-   * either shut down or failed. Runnable should check lack of existence of
-   * BluetoothService via Get() function to see if service stopped correctly.
-   *
    * @return NS_OK on initialization starting correctly, NS_ERROR_FAILURE
    * otherwise
    */
-  nsresult Stop(nsIRunnable* aResultRunnable);
+  nsresult Stop();
+
+  /**
+   * Called when XPCOM first creates this service.
+   */
+  nsresult HandleStartup();
+
+  /**
+   * Called when "mozsettings-changed" observer topic fires.
+   */
+  nsresult HandleSettingsChanged(const nsAString& aData);
+
+  /**
+   * Called when XPCOM is shutting down.
+   */
+  nsresult HandleShutdown();
+
+  /**
+   * Called when a BluetoothManager is created.
+   */
+  void RegisterManager(BluetoothManager* aManager);
+
+  /**
+   * Called when a BluetoothManager is destroyed.
+   */
+  void UnregisterManager(BluetoothManager* aManager);
 
   /** 
    * Returns the BluetoothService singleton. Only to be called from main thread.
    *
    * @param aService Pointer to return singleton into. 
    *
    * @return NS_OK on proper assignment, NS_ERROR_FAILURE otherwise (if service
    * has not yet been started, for instance)
    */
   static BluetoothService* Get();
-  
+
+  static already_AddRefed<BluetoothService> FactoryCreate()
+  {
+    nsRefPtr<BluetoothService> service = Get();
+    return service.forget();
+  }
+
   /**
    * Returns the path of the default adapter, implemented via a platform
    * specific method.
    *
    * @return Default adapter path/name on success, NULL otherwise
    */
   virtual nsresult GetDefaultAdapterPathInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
@@ -226,49 +259,68 @@ public:
 
   virtual bool
   CloseSocket(int aFd, BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual bool SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode) = 0;
   virtual bool SetPasskeyInternal(const nsAString& aDeviceAddress, uint32_t aPasskey) = 0;
   virtual bool SetPairingConfirmationInternal(const nsAString& aDeviceAddress, bool aConfirm) = 0;
   virtual bool SetAuthorizationInternal(const nsAString& aDeviceAddress, bool aAllow) = 0;
-  virtual int IsEnabledInternal() = 0;
+
+  virtual bool IsEnabled()
+  {
+    return mEnabled;
+  }
+
+protected:
+  BluetoothService()
+  : mEnabled(false)
+#ifdef DEBUG
+    , mLastRequestedEnable(false)
+#endif
+  {
+    mBluetoothSignalObserverTable.Init();
+  }
+
+  virtual ~BluetoothService()
+  { }
+
+  nsresult StartStopBluetooth(bool aStart);
+
+  // Called by ToggleBtAck.
+  void SetEnabled(bool aEnabled);
+
+  // This function is implemented in platform-specific BluetoothServiceFactory
+  // files
+  static BluetoothService* Create();
 
   /**
    * Due to the fact that some operations require multiple calls, a
    * CommandThread is created that can run blocking, platform-specific calls
    * where either no asynchronous equivilent exists, or else where multiple
    * asynchronous calls would require excessive runnable bouncing between main
    * thread and IO thread.
    *
    * For instance, when we retrieve an Adapter object, we would like it to come
    * with all of its properties filled in and registered as an agent, which
    * requires a minimum of 3 calls to platform specific code on some platforms.
    *
    */
   nsCOMPtr<nsIThread> mBluetoothCommandThread;
 
-protected:
-  BluetoothService()
-  {
-    mBluetoothSignalObserverTable.Init();
-  }
-
-  virtual ~BluetoothService()
-  {
-  }
-
-  nsresult StartStopBluetooth(nsIRunnable* aResultRunnable, bool aStart);
-  // This function is implemented in platform-specific BluetoothServiceFactory
-  // files
-  static BluetoothService* Create();
-
   typedef mozilla::ObserverList<BluetoothSignal> BluetoothSignalObserverList;
   typedef nsClassHashtable<nsStringHashKey, BluetoothSignalObserverList >
   BluetoothSignalObserverTable;
 
   BluetoothSignalObserverTable mBluetoothSignalObserverTable;
+
+  typedef nsTObserverArray<BluetoothManager*> BluetoothManagerList;
+  BluetoothManagerList mLiveManagers;
+
+  bool mEnabled;
+#ifdef DEBUG
+  bool mLastRequestedEnable;
+#endif
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -105,16 +105,25 @@ using mozilla::dom::Activity;
 #include "SystemWorkerManager.h"
 using mozilla::dom::gonk::SystemWorkerManager;
 #define SYSTEMWORKERMANAGER_CID \
   {0xd53b6524, 0x6ac3, 0x42b0, {0xae, 0xca, 0x62, 0xb3, 0xc4, 0xe5, 0x2b, 0x04}}
 #define SYSTEMWORKERMANAGER_CONTRACTID \
   "@mozilla.org/telephony/system-worker-manager;1"
 #endif
 
+#ifdef MOZ_B2G_BT
+#include "BluetoothService.h"
+using mozilla::dom::bluetooth::BluetoothService;
+#define BLUETOOTHSERVICE_CID \
+  {0xa753b487, 0x3344, 0x4de4, {0xad, 0x5f, 0x06, 0x36, 0x76, 0xa7, 0xc1, 0x04}}
+#define BLUETOOTHSERVICE_CONTRACTID \
+  "@mozilla.org/bluetooth/service;1"
+#endif
+
 #ifdef MOZ_WIDGET_GONK
 #include "AudioManager.h"
 using mozilla::dom::gonk::AudioManager;
 #include "nsVolumeService.h"
 using mozilla::system::nsVolumeService;
 #endif
 #include "nsDOMMutationObserver.h"
 
@@ -268,16 +277,20 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsChannel
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(IndexedDatabaseManager,
                                          IndexedDatabaseManager::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DOMRequestService,
                                          DOMRequestService::FactoryCreate)
 #ifdef MOZ_B2G_RIL
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(SystemWorkerManager,
                                          SystemWorkerManager::FactoryCreate)
 #endif
+#ifdef MOZ_B2G_BT
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(BluetoothService,
+                                         BluetoothService::FactoryCreate)
+#endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMMutationObserver)
 
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_CONSTRUCTOR(AudioManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsVolumeService)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceSensors)
 
@@ -747,16 +760,19 @@ NS_DEFINE_NAMED_CID(NS_DOMSTORAGE2_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMJSON_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(INDEXEDDB_MANAGER_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 #ifdef MOZ_B2G_RIL
 NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_CID);
 #endif
+#ifdef MOZ_B2G_BT
+NS_DEFINE_NAMED_CID(BLUETOOTHSERVICE_CID);
+#endif
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(NS_AUDIOMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_VOLUMESERVICE_CID);
 #endif
 #ifdef ENABLE_EDITOR_API_LOG
 NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 #else
 NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
@@ -1020,16 +1036,19 @@ static const mozilla::Module::CIDEntry k
   { &kNS_DOMSTORAGEMANAGER_CID, false, NULL, nsDOMStorageManagerConstructor },
   { &kNS_DOMJSON_CID, false, NULL, NS_NewJSON },
   { &kNS_TEXTEDITOR_CID, false, NULL, nsPlaintextEditorConstructor },
   { &kINDEXEDDB_MANAGER_CID, false, NULL, IndexedDatabaseManagerConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, NULL, DOMRequestServiceConstructor },
 #ifdef MOZ_B2G_RIL
   { &kSYSTEMWORKERMANAGER_CID, true, NULL, SystemWorkerManagerConstructor },
 #endif
+#ifdef MOZ_B2G_BT
+  { &kBLUETOOTHSERVICE_CID, true, NULL, BluetoothServiceConstructor },
+#endif
 #ifdef MOZ_WIDGET_GONK
   { &kNS_AUDIOMANAGER_CID, true, NULL, AudioManagerConstructor },
   { &kNS_VOLUMESERVICE_CID, true, NULL, nsVolumeServiceConstructor },
 #endif
 #ifdef ENABLE_EDITOR_API_LOG
   { &kNS_HTMLEDITOR_CID, false, NULL, nsHTMLEditorLogConstructor },
 #else
   { &kNS_HTMLEDITOR_CID, false, NULL, nsHTMLEditorConstructor },
@@ -1155,19 +1174,22 @@ static const mozilla::Module::ContractID
   { NS_DOMACTIVITY_CONTRACTID, &kNS_DOMACTIVITY_CID },
   { NS_DOMPARSER_CONTRACTID, &kNS_DOMPARSER_CID },
   { "@mozilla.org/dom/storage;2", &kNS_DOMSTORAGE2_CID },
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { INDEXEDDB_MANAGER_CONTRACTID, &kINDEXEDDB_MANAGER_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
-#ifdef MOZ_B2G_RIL  
+#ifdef MOZ_B2G_RIL
   { SYSTEMWORKERMANAGER_CONTRACTID, &kSYSTEMWORKERMANAGER_CID },
 #endif
+#ifdef MOZ_B2G_BT
+  { BLUETOOTHSERVICE_CONTRACTID, &kBLUETOOTHSERVICE_CID },
+#endif
 #ifdef MOZ_WIDGET_GONK
   { NS_AUDIOMANAGER_CONTRACTID, &kNS_AUDIOMANAGER_CID },
   { NS_VOLUMESERVICE_CONTRACTID, &kNS_VOLUMESERVICE_CID },
 #endif
 #ifdef ENABLE_EDITOR_API_LOG
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
 #else
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
@@ -1226,16 +1248,19 @@ static const mozilla::Module::CategoryEn
   { "app-startup", "Script Security Manager", "service," NS_SCRIPTSECURITYMANAGER_CONTRACTID },
 #ifdef MOZ_WIDGET_GONK
   { "app-startup", "Volume Service", "service," NS_VOLUMESERVICE_CONTRACTID },
 #endif
   CONTENTDLF_CATEGORIES
 #ifdef MOZ_B2G_RIL
   { "profile-after-change", "Telephony System Worker Manager", SYSTEMWORKERMANAGER_CONTRACTID },
 #endif
+#ifdef MOZ_B2G_BT
+  { "profile-after-change", "Bluetooth Service", BLUETOOTHSERVICE_CONTRACTID },
+#endif
   { NULL }
 };
 
 static void
 LayoutModuleDtor()
 {
   nsScriptSecurityManager::Shutdown();
   xpcModuleDtor();