Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 10 May 2013 14:57:50 -0400
changeset 142557 ada852fc265a9f475f741199d3838c4bec801586
parent 142556 17bc0e8cfa8b5f5cf115c10c269f372bf9393961 (current diff)
parent 142463 1d7c615b4b6220629b8a3ca923e3952787dcc2d3 (diff)
child 142558 e3b527872ef0ede55efe4b2638123212a4b9af42
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.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
Merge m-c to inbound.
b2g/installer/package-manifest.in
dom/bluetooth/BluetoothScoManager.cpp
dom/bluetooth/BluetoothScoManager.h
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -559,47 +559,67 @@ pref("ui.showHideScrollbars", 1);
 
 // Enable the ProcessPriorityManager, and give processes with no visible
 // documents a 1s grace period before they're eligible to be marked as
 // background.
 pref("dom.ipc.processPriorityManager.enabled", true);
 pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
 pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
 
-// Kernel parameters for how processes are killed on low-memory.
-pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);
-pref("hal.processPriorityManager.gonk.masterOomScoreAdjust", 0);
-pref("hal.processPriorityManager.gonk.masterKillUnderMB", 4);
-pref("hal.processPriorityManager.gonk.foregroundHighOomScoreAdjust", 67);
-pref("hal.processPriorityManager.gonk.foregroundHighKillUnderMB", 5);
-pref("hal.processPriorityManager.gonk.foregroundOomScoreAdjust", 134);
-pref("hal.processPriorityManager.gonk.foregroundKillUnderMB", 6);
-pref("hal.processPriorityManager.gonk.backgroundPerceivableOomScoreAdjust", 200);
-pref("hal.processPriorityManager.gonk.backgroundPerceivableKillUnderMB", 7);
-pref("hal.processPriorityManager.gonk.backgroundHomescreenOomScoreAdjust", 267);
-pref("hal.processPriorityManager.gonk.backgroundHomescreenKillUnderMB", 8);
-pref("hal.processPriorityManager.gonk.backgroundOomScoreAdjust", 400);
-pref("hal.processPriorityManager.gonk.backgroundKillUnderMB", 20);
-pref("hal.processPriorityManager.gonk.notifyLowMemUnderMB", 10);
-
-// Niceness values (i.e., CPU priorities) for B2G processes.
+// Kernel parameters for process priorities.  These affect how processes are
+// killed on low-memory and their relative CPU priorities.
 //
 // Note: The maximum nice value on Linux is 19, but the max value you should
 // use here is 18.  NSPR adds 1 to some threads' nice values, to mark
 // low-priority threads.  If the process priority manager were to renice a
 // process (and all its threads) to 19, all threads would have the same
 // niceness.  Then when we reniced the process to (say) 10, all threads would
 // /still/ have the same niceness; we'd effectively have erased NSPR's thread
 // priorities.
-pref("hal.processPriorityManager.gonk.masterNice", 0);
-pref("hal.processPriorityManager.gonk.foregroundHighNice", 0);
-pref("hal.processPriorityManager.gonk.foregroundNice", 1);
-pref("hal.processPriorityManager.gonk.backgroundPerceivableNice", 10);
-pref("hal.processPriorityManager.gonk.backgroundHomescreenNice", 18);
-pref("hal.processPriorityManager.gonk.backgroundNice", 18);
+
+pref("hal.processPriorityManager.gonk.MASTER.OomScoreAdjust", 0);
+pref("hal.processPriorityManager.gonk.MASTER.KillUnderMB", 4);
+pref("hal.processPriorityManager.gonk.MASTER.Nice", 0);
+
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.OomScoreAdjust", 67);
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.KillUnderMB", 5);
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.Nice", 0);
+
+pref("hal.processPriorityManager.gonk.FOREGROUND.OomScoreAdjust", 134);
+pref("hal.processPriorityManager.gonk.FOREGROUND.KillUnderMB", 6);
+pref("hal.processPriorityManager.gonk.FOREGROUND.Nice", 1);
+
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.OomScoreAdjust", 200);
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.KillUnderMB", 7);
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.Nice", 10);
+
+pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.OomScoreAdjust", 267);
+pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.KillUnderMB", 8);
+pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.Nice", 18);
+
+pref("hal.processPriorityManager.gonk.BACKGROUND.OomScoreAdjust", 400);
+pref("hal.processPriorityManager.gonk.BACKGROUND.KillUnderMB", 20);
+pref("hal.processPriorityManager.gonk.BACKGROUND.Nice", 18);
+
+// Processes get this niceness when they have low CPU priority.
+pref("hal.processPriorityManager.gonk.LowCPUNice", 18);
+
+// Fire a memory pressure event when the system has less than Xmb of memory
+// remaining.  You should probably set this just above Y.KillUnderMB for
+// the highest priority class Y that you want to make an effort to keep alive.
+// (For example, we want BACKGROUND_PERCEIVABLE to stay alive.)  If you set
+// this too high, then we'll send out a memory pressure event every Z seconds
+// (see below), even while we have processes that we would happily kill in
+// order to free up memory.
+pref("hal.processPriorityManager.gonk.notifyLowMemUnderMB", 10);
+
+// We wait this long before polling the memory-pressure fd after seeing one
+// memory pressure event.  (When we're not under memory pressure, we sit
+// blocked on a poll(), and this pref has no effect.)
+pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);
 
 #ifndef DEBUG
 // Enable pre-launching content processes for improved startup time
 // (hiding latency).
 pref("dom.ipc.processPrelaunch.enabled", true);
 // Wait this long before pre-launching a new subprocess.
 pref("dom.ipc.processPrelaunch.delayMs", 5000);
 #endif
@@ -692,8 +712,11 @@ pref("toolkit.storage.pageSize", 2048);
 #endif
 
 // Enable captive portal detection.
 pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt");
 pref("captivedetect.canonicalContent", "success\n");
 
 // The url of the manifest we use for ADU pings.
 pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
+
+// Enable the disk space watcher
+pref("disk_space_watcher.enabled", true);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -150,16 +150,17 @@
 @BINPATH@/components/content_base.xpt
 @BINPATH@/components/content_events.xpt
 @BINPATH@/components/content_canvas.xpt
 @BINPATH@/components/content_htmldoc.xpt
 @BINPATH@/components/content_html.xpt
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
+@BINPATH@/components/diskspacewatcher.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_activities.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_audiochannel.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_system.xpt
 #ifdef MOZ_B2G_RIL
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -2549,16 +2549,20 @@ nsFrameLoader::ResetPermissionManagerSta
     mAppIdSentToPermissionManager = appId;
     permMgr->AddrefAppId(mAppIdSentToPermissionManager);
   }
 }
 
 /* [infallible] */ NS_IMETHODIMP
 nsFrameLoader::SetVisible(bool aVisible)
 {
+  if (mVisible == aVisible) {
+    return NS_OK;
+  }
+
   mVisible = aVisible;
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
                         "frameloader-visible-changed", nullptr);
   }
   return NS_OK;
 }
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -116,17 +116,17 @@ public:
   virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable,
                                int32_t *aTabIndex);
   virtual int32_t TabIndexDefault();
 
   /**
    * Call this to reevaluate whether we should start/stop due to our owner
    * document being active, inactive, visible or hidden.
    */
-  void NotifyOwnerDocumentActivityChanged();
+  virtual void NotifyOwnerDocumentActivityChanged();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
   virtual void MetadataLoaded(int aChannels,
                               int aRate,
                               bool aHasAudio,
                               bool aHasVideo,
@@ -531,19 +531,25 @@ protected:
     bool operator !() const { return !mValue; }
 
   private:
     void UpdateWakeLock();
 
     bool mValue;
     bool mCanPlay;
     HTMLMediaElement* mOuter;
+  };
 
-    nsCOMPtr<nsIDOMMozWakeLock> mWakeLock;
-  };
+  /**
+   * These two methods are called by the WakeLockBoolWrapper when the wakelock
+   * has to be created or released.
+   */
+  virtual void WakeLockCreate();
+  virtual void WakeLockRelease();
+  nsCOMPtr<nsIDOMMozWakeLock> mWakeLock;
 
   /**
    * Logs a warning message to the web console to report various failures.
    * aMsg is the localized message identifier, aParams is the parameters to
    * be substituted into the localized message, and aParamCount is the number
    * of parameters in aParams.
    */
   void ReportLoadError(const char* aMsg,
--- a/content/html/content/public/HTMLVideoElement.h
+++ b/content/html/content/public/HTMLVideoElement.h
@@ -103,17 +103,25 @@ public:
   uint32_t MozPresentedFrames() const;
 
   uint32_t MozPaintedFrames();
 
   double MozFrameDelay();
 
   bool MozHasAudio() const;
 
+  void NotifyOwnerDocumentActivityChanged() MOZ_OVERRIDE;
+
 protected:
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  virtual void WakeLockCreate();
+  virtual void WakeLockRelease();
+  void WakeLockUpdate();
+
+  nsCOMPtr<nsIDOMMozWakeLock> mScreenWakeLock;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLVideoElement_h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -2112,31 +2112,45 @@ HTMLMediaElement::WakeLockBoolWrapper::S
   UpdateWakeLock();
 }
 
 void
 HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
 {
   if (!mOuter) {
     return;
-
   }
+
   bool playing = (!mValue && mCanPlay);
 
   if (playing) {
+    mOuter->WakeLockCreate();
+  } else {
+    mOuter->WakeLockRelease();
+  }
+}
+
+void
+HTMLMediaElement::WakeLockCreate()
+{
+  if (!mWakeLock) {
     nsCOMPtr<nsIPowerManagerService> pmService =
       do_GetService(POWERMANAGERSERVICE_CONTRACTID);
     NS_ENSURE_TRUE_VOID(pmService);
 
-    if (!mWakeLock) {
-      pmService->NewWakeLock(NS_LITERAL_STRING("cpu"),
-                             mOuter->OwnerDoc()->GetWindow(),
-                             getter_AddRefs(mWakeLock));
-    }
-  } else if (mWakeLock) {
+    pmService->NewWakeLock(NS_LITERAL_STRING("cpu"),
+                           OwnerDoc()->GetWindow(),
+                           getter_AddRefs(mWakeLock));
+  }
+}
+
+void
+HTMLMediaElement::WakeLockRelease()
+{
+  if (mWakeLock) {
     mWakeLock->Unlock();
     mWakeLock = nullptr;
   }
 }
 
 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
                                       nsIAtom* aAttribute,
                                       const nsAString& aValue,
--- a/content/html/content/src/HTMLVideoElement.cpp
+++ b/content/html/content/src/HTMLVideoElement.cpp
@@ -25,16 +25,17 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMProgressEvent.h"
+#include "nsIPowerManagerService.h"
 #include "MediaError.h"
 #include "MediaDecoder.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Video)
 
 namespace mozilla {
 namespace dom {
 
@@ -230,10 +231,56 @@ NS_IMETHODIMP HTMLVideoElement::GetMozHa
 }
 
 JSObject*
 HTMLVideoElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return HTMLVideoElementBinding::Wrap(aCx, aScope, this);
 }
 
+void
+HTMLVideoElement::NotifyOwnerDocumentActivityChanged()
+{
+  HTMLMediaElement::NotifyOwnerDocumentActivityChanged();
+  WakeLockUpdate();
+}
+
+void
+HTMLVideoElement::WakeLockCreate()
+{
+  WakeLockUpdate();
+}
+
+void
+HTMLVideoElement::WakeLockRelease()
+{
+  WakeLockUpdate();
+}
+
+void
+HTMLVideoElement::WakeLockUpdate()
+{
+  bool hidden = true;
+
+  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
+  if (domDoc) {
+    domDoc->GetHidden(&hidden);
+  }
+
+  if (mScreenWakeLock && (mPaused || hidden)) {
+    mScreenWakeLock->Unlock();
+    mScreenWakeLock = nullptr;
+    return;
+  }
+
+  if (!mScreenWakeLock && !mPaused && !hidden) {
+    nsCOMPtr<nsIPowerManagerService> pmService =
+      do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+    NS_ENSURE_TRUE_VOID(pmService);
+
+    pmService->NewWakeLock(NS_LITERAL_STRING("screen"),
+                           OwnerDoc()->GetWindow(),
+                           getter_AddRefs(mScreenWakeLock));
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -347,17 +347,19 @@ MOCHITEST_FILES = \
 		test_mozaudiochannel.html \
 		test_mozLoadFrom.html \
 		test_style_attributes_reflection.html \
 		test_bug629801.html \
 		test_bug839371.html \
 		test_element_prototype.html \
 		test_formData.html \
 		test_audio_wakelock.html \
+		test_video_wakelock.html \
 		wakelock.ogg \
+		wakelock.ogv \
 		test_bug869040.html \
 		$(NULL)
 
 MOCHITEST_BROWSER_FILES = \
 		browser_bug649778.js \
 		file_bug649778.html \
 		file_bug649778.html^headers^ \
 		$(NULL)
copy from content/html/content/test/test_audio_wakelock.html
copy to content/html/content/test/test_video_wakelock.html
--- a/content/html/content/test/test_audio_wakelock.html
+++ b/content/html/content/test/test_video_wakelock.html
@@ -17,88 +17,80 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 868943 **/
 
 SpecialPowers.addPermission("power", true, document);
 
-function testAudioPlayPause() {
+function testVideoPlayPause() {
   var lockState = true;
   var count = 0;
 
   var content = document.getElementById('content');
 
-  var audio = document.createElement('audio');
-  audio.src = "wakelock.ogg";
-  content.appendChild(audio);
+  var video = document.createElement('video');
+  video.src = "wakelock.ogv";
+  content.appendChild(video);
 
-  audio.addEventListener('progress', function() {
-    lockState = false;
-    audio.pause();
-  });
-
-  navigator.mozPower.addWakeLockListener(function testAudioPlayListener(topic, state) {
-    is(topic, "cpu", "Audio element locked the target == cpu");
+  navigator.mozPower.addWakeLockListener(function testVideoPlayPauseListener(topic, state) {
+    is(topic, "screen", "Video element locked the target == screen");
     var locked = state == "locked-foreground" ||
                  state == "locked-background";
 
-    is(locked, lockState, "Audio element locked the cpu - no paused");
+    is(locked, lockState, "Video element locked the screen - paused");
     count++;
 
-    // count == 1 is when the cpu wakelock is created
-    // count == 2 is when the cpu wakelock is released
-
-    if (count == 2) {
-      content.removeChild(audio);
-      navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
+    if (count == 1) {
+      // The next step is to unlock the resource.
+      lockState = false;
+      video.pause();
+    } else if (count == 2) {
+      content.removeChild(video);
+      navigator.mozPower.removeWakeLockListener(testVideoPlayPauseListener);
       runTests();
     }
   });
 
-  audio.play();
+  video.play();
 }
 
-function testAudioPlay() {
+function testVideoPlay() {
   var lockState = true;
   var count = 0;
 
   var content = document.getElementById('content');
 
-  var audio = document.createElement('audio');
-  audio.src = "wakelock.ogg";
-  content.appendChild(audio);
+  var video = document.createElement('video');
+  video.src = "wakelock.ogv";
+  content.appendChild(video);
 
-  navigator.mozPower.addWakeLockListener(function testAudioPlayListener(topic, state) {
-    is(topic, "cpu", "Audio element locked the target == cpu");
+  navigator.mozPower.addWakeLockListener(function testVideoPlayListener(topic, state) {
+    is(topic, "screen", "Video element locked the target == screen");
     var locked = state == "locked-foreground" ||
                  state == "locked-background";
 
-    is(locked, lockState, "Audio element locked the cpu - no paused");
+    is(locked, lockState, "Video element locked the screen - no paused");
     count++;
 
-    // count == 1 is when the cpu wakelock is created: the wakelock must be
-    // created when the media element starts playing.
-    // count == 2 is when the cpu wakelock is released.
-
     if (count == 1) {
       // The next step is to unlock the resource.
       lockState = false;
     } else if (count == 2) {
-      content.removeChild(audio);
-      navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
+      content.removeChild(video);
+      navigator.mozPower.removeWakeLockListener(testVideoPlayListener);
       runTests();
     }
   });
 
-  audio.play();
+  video.play();
 }
 
-var tests = [ testAudioPlayPause, testAudioPlay ];
+var tests = [ testVideoPlayPause, testVideoPlay ];
 function runTests() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
   var test =  tests.pop();
   test();
copy from content/media/test/bug520908.ogv
copy to content/html/content/test/wakelock.ogv
--- a/dom/bluetooth/BluetoothAdapter.cpp
+++ b/dom/bluetooth/BluetoothAdapter.cpp
@@ -119,16 +119,47 @@ public:
   {
     BluetoothReplyRunnable::ReleaseMembers();
     mAdapterPtr = nullptr;
   }
 private:
   nsRefPtr<BluetoothAdapter> mAdapterPtr;
 };
 
+class GetScoConnectionStatusTask : public BluetoothReplyRunnable
+{
+public:
+  GetScoConnectionStatusTask(nsIDOMDOMRequest* aReq) :
+    BluetoothReplyRunnable(aReq)
+  {
+    MOZ_ASSERT(aReq);
+  }
+
+  virtual bool ParseSuccessfulReply(JS::Value* aValue)
+  {
+    *aValue = JSVAL_VOID;
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    if (v.type() != BluetoothValue::Tbool) {
+      NS_WARNING("Not a boolean!");
+      SetError(NS_LITERAL_STRING("BluetoothReplyTypeError"));
+      return false;
+    }
+
+    aValue->setBoolean(v.get_bool());
+    return true;
+  }
+
+  void
+  ReleaseMembers()
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+  }
+};
+
 static int kCreatePairedDeviceTimeout = 50000; // unit: msec
 
 nsresult
 PrepareDOMRequest(nsIDOMWindow* aWindow, nsIDOMDOMRequest** aRequest)
 {
   MOZ_ASSERT(aWindow);
 
   nsCOMPtr<nsIDOMRequestService> rs =
@@ -738,9 +769,63 @@ BluetoothAdapter::ConfirmReceivingFile(c
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   bs->ConfirmReceivingFile(aDeviceAddress, aConfirmation, results);
 
   req.forget(aRequest);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+BluetoothAdapter::ConnectSco(nsIDOMDOMRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  nsRefPtr<BluetoothVoidReplyRunnable> results =
+    new BluetoothVoidReplyRunnable(req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  bs->ConnectSco(results);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BluetoothAdapter::DisconnectSco(nsIDOMDOMRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  nsRefPtr<BluetoothVoidReplyRunnable> results =
+    new BluetoothVoidReplyRunnable(req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  bs->DisconnectSco(results);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BluetoothAdapter::IsScoConnected(nsIDOMDOMRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  nsRefPtr<BluetoothReplyRunnable> results =
+    new GetScoConnectionStatusTask(req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  bs->IsScoConnected(results);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
 NS_IMPL_EVENT_HANDLER(BluetoothAdapter, devicefound)
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -4,17 +4,16 @@
  * 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 "BluetoothHfpManager.h"
 
 #include "BluetoothReplyRunnable.h"
-#include "BluetoothScoManager.h"
 #include "BluetoothService.h"
 #include "BluetoothSocket.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 
 #include "MobileConnection.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/Hal.h"
@@ -26,16 +25,17 @@
 #include "nsISettingsService.h"
 #include "nsITelephonyProvider.h"
 #include "nsRadioInterfaceLayer.h"
 
 #define AUDIO_VOLUME_BT_SCO "audio.volume.bt_sco"
 #define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
 #define MOBILE_CONNECTION_ICCINFO_CHANGED "mobile-connection-iccinfo-changed"
 #define MOBILE_CONNECTION_VOICE_CHANGED "mobile-connection-voice-changed"
+#define BLUETOOTH_SCO_STATUS_CHANGED "bluetooth-sco-status-changed"
 
 /**
  * These constants are used in result code such as +CLIP and +CCWA. The value
  * of these constants is the same as TOA_INTERNATIONAL/TOA_UNKNOWN defined in
  * ril_consts.js
  */
 #define TOA_UNKNOWN 0x81
 #define TOA_INTERNATIONAL 0x91
@@ -120,17 +120,17 @@ static CINDItem sCINDItems[] = {
   {"signal", "0-5", 0},
   {"roam", "0,1", 0}
 };
 
 class mozilla::dom::bluetooth::Call {
   public:
     Call(uint16_t aState = nsITelephonyProvider::CALL_STATE_DISCONNECTED,
          bool aDirection = false,
-         const nsAString& aNumber = NS_LITERAL_STRING(""),
+         const nsAString& aNumber = EmptyString(),
          int aType = TOA_UNKNOWN)
       : mState(aState), mDirection(aDirection), mNumber(aNumber), mType(aType)
     {
     }
 
     uint16_t mState;
     bool mDirection; // true: incoming call; false: outgoing call
     nsString mNumber;
@@ -328,45 +328,16 @@ public:
                       sRingInterval);
   }
 
 private:
   nsString mNumber;
   int mType;
 };
 
-void
-OpenScoSocket(const nsAString& aDeviceAddress)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  BluetoothScoManager* sco = BluetoothScoManager::Get();
-  if (!sco) {
-    NS_WARNING("BluetoothScoManager is not available!");
-    return;
-  }
-
-  if (!sco->Connect(aDeviceAddress)) {
-    NS_WARNING("Failed to create a sco socket!");
-  }
-}
-
-void
-CloseScoSocket()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  BluetoothScoManager* sco = BluetoothScoManager::Get();
-  if (!sco) {
-    NS_WARNING("BluetoothScoManager is not available!");
-    return;
-  }
-  sco->Disconnect();
-}
-
 static bool
 IsValidDtmf(const char aChar) {
   // Valid DTMF: [*#0-9ABCD]
   if (aChar == '*' || aChar == '#') {
     return true;
   } else if (aChar >= '0' && aChar <= '9') {
     return true;
   } else if (aChar >= 'A' && aChar <= 'D') {
@@ -434,16 +405,22 @@ BluetoothHfpManager::Init()
   NS_ENSURE_SUCCESS(rv, false);
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
   Listen();
 
+  mScoSocket = new BluetoothSocket(this,
+                                   BluetoothSocketType::SCO,
+                                   true,
+                                   false);
+  mScoSocketStatus = mScoSocket->GetConnectionStatus();
+  ListenSco();
   return true;
 }
 
 BluetoothHfpManager::~BluetoothHfpManager()
 {
   Cleanup();
 }
 
@@ -492,17 +469,17 @@ BluetoothHfpManager::NotifySettings()
   InfallibleTArray<BluetoothNamedValue> parameters;
   type.AssignLiteral("bluetooth-hfp-status-changed");
 
   name.AssignLiteral("connected");
   v = IsConnected();
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("address");
-  v = mDevicePath;
+  v = mDeviceAddress;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to broadcast system message to settings");
     return;
   }
 }
 
@@ -519,16 +496,32 @@ BluetoothHfpManager::NotifyDialer(const 
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to broadcast system message to dialer");
     return;
   }
 }
 
+void
+BluetoothHfpManager::NotifyAudioManager(const nsAString& aAddress)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs =
+    do_GetService("@mozilla.org/observer-service;1");
+  NS_ENSURE_TRUE_VOID(obs);
+
+  if (NS_FAILED(obs->NotifyObservers(nullptr,
+                                     BLUETOOTH_SCO_STATUS_CHANGED,
+                                     aAddress.BeginReading()))) {
+    NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
+  }
+}
+
 nsresult
 BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
@@ -677,16 +670,17 @@ BluetoothHfpManager::HandleIccInfoChange
 }
 
 nsresult
 BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   gInShutdown = true;
   Disconnect();
+  DisconnectSco();
   gBluetoothHfpManager = nullptr;
   return NS_OK;
 }
 
 // Virtual function of class SocketConsumer
 void
 BluetoothHfpManager::ReceiveSocketData(BluetoothSocket* aSocket,
                                        nsAutoPtr<UnixSocketRawData>& aMessage)
@@ -761,17 +755,16 @@ BluetoothHfpManager::ReceiveSocketData(B
     }
   } else if (msg.Find("AT+COPS?") != -1) {
     nsAutoCString message("+COPS: ");
     message.AppendInt(mNetworkSelectionMode);
     message.AppendLiteral(",0,\"");
     message.Append(NS_ConvertUTF16toUTF8(mOperatorName));
     message.AppendLiteral("\"");
     SendLine(message.get());
-    return;
   } else if (msg.Find("AT+VTS=") != -1) {
     ParseAtCommand(msg, 7, atCommandValues);
 
     if (atCommandValues.Length() != 1) {
       NS_WARNING("Couldn't get the value of command [AT+VTS=]");
       goto respond_with_ok;
     }
 
@@ -912,44 +905,36 @@ BluetoothHfpManager::ReceiveSocketData(B
 
     if (atCommandValues.IsEmpty()) {
       NS_WARNING("Could't get the value of command [AT+CCWA=]");
       goto respond_with_ok;
     }
 
     mCCWA = atCommandValues[0].EqualsLiteral("1");
   } else if (msg.Find("AT+CKPD") != -1) {
-    BluetoothScoManager* sco = BluetoothScoManager::Get();
-    if (!sco) {
-      NS_WARNING("Couldn't get BluetoothScoManager instance");
-      goto respond_with_ok;
-    }
-
     if (!sStopSendingRingFlag) {
       // Bluetooth HSP spec 4.2.2
       // There is an incoming call, notify Dialer to pick up the phone call
       // and SCO will be established after we get the CallStateChanged event
       // indicating the call is answered successfully.
       NotifyDialer(NS_LITERAL_STRING("ATA"));
     } else {
-      if (!sco->IsConnected()) {
+      if (!IsScoConnected()) {
         // Bluetooth HSP spec 4.3
         // If there's no SCO, set up a SCO link.
-        nsAutoString address;
-        mSocket->GetAddress(address);
-        sco->Connect(address);
+        ConnectSco();
       } else if (!mFirstCKPD) {
         // Bluetooth HSP spec 4.5
         // There are two ways to release SCO: sending CHUP to dialer or closing
         // SCO socket directly. We notify dialer only if there is at least one
         // active call.
         if (mCurrentCallArray.Length() > 1) {
           NotifyDialer(NS_LITERAL_STRING("CHUP"));
         } else {
-          sco->Disconnect();
+          DisconnectSco();
         }
       } else {
         // Three conditions have to be matched to come in here:
         // (1) Not sending RING indicator
         // (2) A SCO link exists
         // (3) This is the very first AT+CKPD=200 of this session
         // It is the case of Figure 4.3, Bluetooth HSP spec. Do nothing.
         NS_WARNING("AT+CKPD=200: Do nothing");
@@ -977,76 +962,66 @@ BluetoothHfpManager::ReceiveSocketData(B
     return;
   }
 
 respond_with_ok:
   // We always respond to remote device with "OK" in general cases.
   SendLine("OK");
 }
 
-bool
+void
 BluetoothHfpManager::Connect(const nsAString& aDevicePath,
                              const bool aIsHandsfree,
                              BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (gInShutdown) {
-    NS_WARNING("Connect called while in shutdown!");
-    return false;
+  NS_ENSURE_FALSE_VOID(gInShutdown);
+  NS_ENSURE_FALSE_VOID(mSocket);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  nsString uuid;
+  if (aIsHandsfree) {
+    BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
+  } else {
+    BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, uuid);
   }
 
-  if (mSocket) {
-    NS_WARNING("BluetoothHfpManager has been already connected");
-    return false;
+  if (NS_FAILED(bs->GetServiceChannel(aDevicePath, uuid, this))) {
+    BluetoothValue v;
+    DispatchBluetoothReply(aRunnable, v,
+                           NS_LITERAL_STRING("GetServiceChannelError"));
+    return;
   }
 
   // Stop listening because currently we only support one connection at a time.
   if (mHandsfreeSocket) {
     mHandsfreeSocket->Disconnect();
     mHandsfreeSocket = nullptr;
   }
 
   if (mHeadsetSocket) {
     mHeadsetSocket->Disconnect();
     mHeadsetSocket = nullptr;
   }
 
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE(bs, false);
-
-  nsString uuid;
-  if (aIsHandsfree) {
-    BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
-  } else {
-    BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, uuid);
-  }
-
   mRunnable = aRunnable;
   mSocket =
     new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
-
-  nsresult rv = bs->GetSocketViaService(aDevicePath,
-                                        uuid,
-                                        BluetoothSocketType::RFCOMM,
-                                        true,
-                                        true,
-                                        mSocket,
-                                        mRunnable);
-
-  return NS_FAILED(rv) ? false : true;
 }
 
 bool
 BluetoothHfpManager::Listen()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
-    MOZ_ASSERT(false, "Listen called while in shutdown!");
+    NS_WARNING("Listen called while in shutdown!");
     return false;
   }
 
   if (mSocket) {
     NS_WARNING("mSocket exists. Failed to listen.");
     return false;
   }
 
@@ -1214,30 +1189,32 @@ BluetoothHfpManager::UpdateCIND(uint8_t 
     }
   }
 }
 
 void
 BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
                                             uint16_t aCallState,
                                             const nsAString& aNumber,
+                                            const bool aIsOutgoing,
                                             bool aSend)
 {
   if (!IsConnected()) {
     // Normal case. No need to print out warnings.
     return;
   }
 
   while (aCallIndex >= mCurrentCallArray.Length()) {
     Call call;
     mCurrentCallArray.AppendElement(call);
   }
 
   uint16_t prevCallState = mCurrentCallArray[aCallIndex].mState;
   mCurrentCallArray[aCallIndex].mState = aCallState;
+  mCurrentCallArray[aCallIndex].mDirection = !aIsOutgoing;
 
   // Same logic as implementation in ril_worker.js
   if (aNumber.Length() && aNumber[0] == '+') {
     mCurrentCallArray[aCallIndex].mType = TOA_INTERNATIONAL;
   }
   mCurrentCallArray[aCallIndex].mNumber = aNumber;
 
   nsRefPtr<nsRunnable> sendRingTask;
@@ -1246,17 +1223,16 @@ BluetoothHfpManager::HandleCallStateChan
   uint32_t index = 1;
 
   switch (aCallState) {
     case nsITelephonyProvider::CALL_STATE_HELD:
       sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE;
       SendCommand("+CIEV: ", CINDType::CALLHELD);
       break;
     case nsITelephonyProvider::CALL_STATE_INCOMING:
-      mCurrentCallArray[aCallIndex].mDirection = true;
 
       if (mCurrentCallIndex) {
         if (mCCWA) {
           nsAutoCString ccwaMsg("+CCWA: \"");
           ccwaMsg.Append(NS_ConvertUTF16toUTF8(aNumber));
           ccwaMsg.AppendLiteral("\",");
           ccwaMsg.AppendInt(mCurrentCallArray[aCallIndex].mType);
           SendLine(ccwaMsg.get());
@@ -1280,41 +1256,34 @@ BluetoothHfpManager::HandleCallStateChan
       }
       break;
     case nsITelephonyProvider::CALL_STATE_DIALING:
       if (!mBLDNProcessed) {
         SendLine("OK");
         mBLDNProcessed = true;
       }
 
-      mCurrentCallArray[aCallIndex].mDirection = false;
       UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING, aSend);
-
-      mSocket->GetAddress(address);
-      OpenScoSocket(address);
+      ConnectSco();
       break;
     case nsITelephonyProvider::CALL_STATE_ALERTING:
-      mCurrentCallArray[aCallIndex].mDirection = false;
       UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING_ALERTING, aSend);
 
       // If there's an ongoing call when the headset is just connected, we have
       // to open a sco socket here.
-      mSocket->GetAddress(address);
-      OpenScoSocket(address);
+      ConnectSco();
       break;
     case nsITelephonyProvider::CALL_STATE_CONNECTED:
       mCurrentCallIndex = aCallIndex;
       switch (prevCallState) {
         case nsITelephonyProvider::CALL_STATE_INCOMING:
         case nsITelephonyProvider::CALL_STATE_DISCONNECTED:
           // Incoming call, no break
           sStopSendingRingFlag = true;
-
-          mSocket->GetAddress(address);
-          OpenScoSocket(address);
+          ConnectSco();
         case nsITelephonyProvider::CALL_STATE_ALERTING:
           // Outgoing call
           UpdateCIND(CINDType::CALL, CallState::IN_PROGRESS, aSend);
           UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend);
           break;
         case nsITelephonyProvider::CALL_STATE_HELD:
           // Check whether to update CINDType::CALLHELD or not
           while (index < callArrayLength) {
@@ -1374,32 +1343,38 @@ BluetoothHfpManager::HandleCallStateChan
             mCurrentCallIndex = index;
             break;
           }
           index++;
         }
 
         // There is no call, close Sco and clear mCurrentCallArray
         if (index == callArrayLength) {
-          CloseScoSocket();
+          DisconnectSco();
           ResetCallArray();
         }
       }
       break;
     default:
       NS_WARNING("Not handling state changed");
       break;
   }
 }
 
 void
 BluetoothHfpManager::OnConnectSuccess(BluetoothSocket* aSocket)
 {
   MOZ_ASSERT(aSocket);
 
+  // Success to create a SCO socket
+  if (aSocket == mScoSocket) {
+    OnScoConnectSuccess();
+    return;
+  }
+
   /**
    * If the created connection is an inbound connection, close another server
    * socket because currently only one SLC is allowed. After that, we need to
    * make sure that both server socket would be nulled out. As for outbound
    * connections, we do nothing since sockets have been already handled in
    * function Connect().
    */
   if (aSocket == mHandsfreeSocket) {
@@ -1422,75 +1397,234 @@ BluetoothHfpManager::OnConnectSuccess(Bl
   provider->EnumerateCalls(mListener->GetListener());
 
   // For active connection request, we need to reply the DOMRequest
   if (mRunnable) {
     BluetoothValue v = true;
     nsString errorStr;
     DispatchBluetoothReply(mRunnable, v, errorStr);
 
-    mRunnable.forget();
+    mRunnable = nullptr;
   }
 
   mFirstCKPD = true;
 
   // Cache device path for NotifySettings() since we can't get socket address
   // when a headset disconnect with us
-  mSocket->GetAddress(mDevicePath);
+  mSocket->GetAddress(mDeviceAddress);
+  NotifySettings();
 
-  NotifySettings();
+  ListenSco();
 }
 
 void
 BluetoothHfpManager::OnConnectError(BluetoothSocket* aSocket)
 {
+  // Failed to create a SCO socket
+  if (aSocket == mScoSocket) {
+    OnScoConnectError();
+    return;
+  }
+
   // For active connection request, we need to reply the DOMRequest
   if (mRunnable) {
-    BluetoothValue v;
-    nsString errorStr;
-    errorStr.AssignLiteral("Failed to connect with a bluetooth headset!");
-    DispatchBluetoothReply(mRunnable, v, errorStr);
+    NS_NAMED_LITERAL_STRING(replyError,
+                            "Failed to connect with a bluetooth headset!");
+    DispatchBluetoothReply(mRunnable, BluetoothValue(), replyError);
 
-    mRunnable.forget();
+    mRunnable = nullptr;
   }
 
   mSocket = nullptr;
   mHandsfreeSocket = nullptr;
   mHeadsetSocket = nullptr;
 
   // If connecting for some reason didn't work, restart listening
   Listen();
 }
 
 void
 BluetoothHfpManager::OnDisconnect(BluetoothSocket* aSocket)
 {
   MOZ_ASSERT(aSocket);
 
+  if (aSocket == mScoSocket) {
+    // SCO socket is closed
+    OnScoDisconnect();
+    return;
+  }
+
   if (aSocket != mSocket) {
     // Do nothing when a listening server socket is closed.
     return;
   }
 
   mSocket = nullptr;
-  CloseScoSocket();
+  DisconnectSco();
 
   Listen();
   NotifySettings();
   Reset();
 }
 
+void
+BluetoothHfpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                         const nsAString& aServiceUuid,
+                                         int aChannel)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mRunnable);
+
+  BluetoothValue v;
+
+  if (aChannel < 0) {
+    DispatchBluetoothReply(mRunnable, v,
+                           NS_LITERAL_STRING("DeviceChannelRetrievalError"));
+    mSocket = nullptr;
+    Listen();
+    return;
+  }
+
+  if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) {
+    DispatchBluetoothReply(mRunnable, v,
+                           NS_LITERAL_STRING("SocketConnectionError"));
+    mSocket = nullptr;
+    Listen();
+    return;
+  }
+}
+
+void
+BluetoothHfpManager::OnScoConnectSuccess()
+{
+  // For active connection request, we need to reply the DOMRequest
+  if (mScoRunnable) {
+    DispatchBluetoothReply(mScoRunnable,
+                           BluetoothValue(true), EmptyString());
+    mScoRunnable = nullptr;
+  }
+
+  NotifyAudioManager(mDeviceAddress);
+
+  mScoSocketStatus = mScoSocket->GetConnectionStatus();
+}
+
+void
+BluetoothHfpManager::OnScoConnectError()
+{
+  if (mScoRunnable) {
+    NS_NAMED_LITERAL_STRING(replyError, "Failed to create SCO socket!");
+    DispatchBluetoothReply(mScoRunnable, BluetoothValue(), replyError);
+
+    mScoRunnable = nullptr;
+  }
+
+  ListenSco();
+}
+
+void
+BluetoothHfpManager::OnScoDisconnect()
+{
+  if (mScoSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
+    ListenSco();
+    NotifyAudioManager(EmptyString());
+  }
+}
+
 bool
 BluetoothHfpManager::IsConnected()
 {
   if (mSocket) {
     return mSocket->GetConnectionStatus() ==
            SocketConnectionStatus::SOCKET_CONNECTED;
   }
 
   return false;
 }
 
 void
 BluetoothHfpManager::GetAddress(nsAString& aDeviceAddress)
 {
   return mSocket->GetAddress(aDeviceAddress);
 }
+
+bool
+BluetoothHfpManager::ConnectSco(BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gInShutdown) {
+    NS_WARNING("ConnecteSco called while in shutdown!");
+    return false;
+  }
+
+  if (!IsConnected()) {
+    NS_WARNING("BluetoothHfpManager is not connected");
+    return false;
+  }
+
+  SocketConnectionStatus status = mScoSocket->GetConnectionStatus();
+  if (status == SocketConnectionStatus::SOCKET_CONNECTED ||
+      status == SocketConnectionStatus::SOCKET_CONNECTING ||
+      (mScoRunnable && (mScoRunnable != aRunnable))) {
+    NS_WARNING("SCO connection exists or is being established");
+    return false;
+  }
+
+  mScoSocket->Disconnect();
+
+  mScoRunnable = aRunnable;
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, false);
+  nsresult rv = bs->GetScoSocket(mDeviceAddress, true, false, mScoSocket);
+
+  return NS_SUCCEEDED(rv);
+}
+
+bool
+BluetoothHfpManager::DisconnectSco()
+{
+  if (!mScoSocket) {
+    NS_WARNING("BluetoothHfpManager is not connected");
+    return false;
+  }
+
+  mScoSocket->Disconnect();
+  return true;
+}
+
+bool
+BluetoothHfpManager::ListenSco()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gInShutdown) {
+    NS_WARNING("ListenSco called while in shutdown!");
+    return false;
+  }
+
+  if (mScoSocket->GetConnectionStatus() ==
+      SocketConnectionStatus::SOCKET_LISTENING) {
+    NS_WARNING("SCO socket has been already listening");
+    return false;
+  }
+
+  mScoSocket->Disconnect();
+
+  if (!mScoSocket->Listen(-1)) {
+    NS_WARNING("Can't listen on SCO socket!");
+    return false;
+  }
+
+  mScoSocketStatus = mScoSocket->GetConnectionStatus();
+  return true;
+}
+
+bool
+BluetoothHfpManager::IsScoConnected()
+{
+  if (mScoSocket) {
+    return mScoSocket->GetConnectionStatus() ==
+           SocketConnectionStatus::SOCKET_CONNECTED;
+  }
+  return false;
+}
--- a/dom/bluetooth/BluetoothHfpManager.h
+++ b/dom/bluetooth/BluetoothHfpManager.h
@@ -3,16 +3,17 @@
 /* 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_bluetoothhfpmanager_h__
 #define mozilla_dom_bluetooth_bluetoothhfpmanager_h__
 
 #include "BluetoothCommon.h"
+#include "BluetoothProfileManagerBase.h"
 #include "BluetoothSocketObserver.h"
 #include "BluetoothTelephonyListener.h"
 #include "mozilla/ipc/UnixSocket.h"
 #include "nsIObserver.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothHfpManagerObserver;
@@ -46,40 +47,50 @@ enum BluetoothCmeError {
   DIAL_STRING_TOO_LONG = 26,
   INVALID_CHARACTERS_IN_DIAL_STRING = 27,
   NO_NETWORK_SERVICE = 30,
   NETWORK_TIMEOUT = 31,
   NETWORK_NOT_ALLOWED = 32
 };
 
 class BluetoothHfpManager : public BluetoothSocketObserver
+                          , public BluetoothProfileManagerBase
 {
 public:
   static BluetoothHfpManager* Get();
   ~BluetoothHfpManager();
 
   virtual void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
   virtual void OnConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
+  virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                   const nsAString& aServiceUuid,
+                                   int aChannel) MOZ_OVERRIDE;
 
-  bool Connect(const nsAString& aDeviceObjectPath,
+  void Connect(const nsAString& aDeviceObjectPath,
                const bool aIsHandsfree,
                BluetoothReplyRunnable* aRunnable);
   void Disconnect();
   bool Listen();
+  bool ConnectSco(BluetoothReplyRunnable* aRunnable = nullptr);
+  bool DisconnectSco();
+  bool ListenSco();
 
   /**
    * @param aSend A boolean indicates whether we need to notify headset or not
    */
   void HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
-                              const nsAString& aNumber, bool aSend);
+                              const nsAString& aNumber, const bool aIsOutgoing,
+                              bool aSend);
+
   bool IsConnected();
+  bool IsScoConnected();
   void GetAddress(nsAString& aDeviceAddress);
 
 private:
   class GetVolumeTask;
   class RespondToBLDNTask;
   class SendRingIndicatorTask;
 
   friend class GetVolumeTask;
@@ -95,48 +106,55 @@ private:
 
   bool Init();
   void Cleanup();
   void Reset();
   void ResetCallArray();
 
   void NotifyDialer(const nsAString& aCommand);
   void NotifySettings();
+  void NotifyAudioManager(const nsAString& aAddress);
 
   bool SendCommand(const char* aCommand, uint8_t aValue = 0);
   bool SendLine(const char* aMessage);
   void UpdateCIND(uint8_t aType, uint8_t aValue, bool aSend);
+  void OnScoConnectSuccess();
+  void OnScoConnectError();
+  void OnScoDisconnect();
 
   int mCurrentVgs;
   int mCurrentVgm;
   uint32_t mCurrentCallIndex;
   bool mCCWA;
   bool mCLIP;
   bool mCMEE;
   bool mCMER;
   bool mFirstCKPD;
   int mNetworkSelectionMode;
   bool mReceiveVgsFlag;
   bool mBLDNProcessed;
-  nsString mDevicePath;
+  nsString mDeviceAddress;
   nsString mMsisdn;
   nsString mOperatorName;
 
   nsTArray<Call> mCurrentCallArray;
   nsAutoPtr<BluetoothTelephonyListener> mListener;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mScoRunnable;
 
   // If a connection has been established, mSocket will be the socket
   // communicating with the remote socket. We maintain the invariant that if
   // mSocket is non-null, mHandsfreeSocket and mHeadsetSocket must be null (and
   // vice versa).
   nsRefPtr<BluetoothSocket> mSocket;
 
   // Server sockets. Once an inbound connection is established, it will hand
   // over the ownership to mSocket, and get a new server socket while Listen()
   // is called.
   nsRefPtr<BluetoothSocket> mHandsfreeSocket;
   nsRefPtr<BluetoothSocket> mHeadsetSocket;
+  nsRefPtr<BluetoothSocket> mScoSocket;
+  SocketConnectionStatus mScoSocketStatus;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -83,17 +83,16 @@ static const uint32_t kUpdateProgressBas
 
 /*
  * The format of the header of an PUT request is
  * [opcode:1][packet length:2][headerId:1][header length:2]
  */
 static const uint32_t kPutRequestHeaderSize = 6;
 
 StaticAutoPtr<BluetoothOppManager> sInstance;
-StaticRefPtr<BluetoothOppManagerObserver> sOppObserver;
 
 /*
  * FIXME / Bug 806749
  *
  * Currently Bluetooth*Manager inherits mozilla::ipc::UnixSocketConsumer,
  * which means that each Bluetooth*Manager can handle only one socket
  * connection at a time. We need to support concurrent multiple socket
  * connections, and then we will be able to have multiple file transferring
@@ -248,57 +247,51 @@ BluetoothOppManager::Get()
 
   if (!sInstance) {
     sInstance = new BluetoothOppManager();
   }
 
   return sInstance;
 }
 
-bool
+void
 BluetoothOppManager::Connect(const nsAString& aDeviceObjectPath,
                              BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mSocket) {
-    NS_WARNING("BluetoothOppManager has been already connected");
-    return false;
+  NS_ENSURE_FALSE_VOID(mSocket);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  nsString uuid;
+  BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
+
+  if (NS_FAILED(bs->GetServiceChannel(aDeviceObjectPath, uuid, this))) {
+    BluetoothValue v;
+    DispatchBluetoothReply(aRunnable, v,
+                           NS_LITERAL_STRING("GetServiceChannelError"));
+    return;
   }
 
   // Stop listening because currently we only support one connection at a time.
   if (mRfcommSocket) {
     mRfcommSocket->Disconnect();
     mRfcommSocket = nullptr;
   }
 
   if (mL2capSocket) {
     mL2capSocket->Disconnect();
     mL2capSocket = nullptr;
   }
 
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE(bs, false);
-
-  nsString uuid;
-  BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
-
   mRunnable = aRunnable;
   mSocket =
     new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
-
-  nsresult rv = bs->GetSocketViaService(aDeviceObjectPath,
-                                        uuid,
-                                        BluetoothSocketType::RFCOMM,
-                                        true,
-                                        true,
-                                        mSocket,
-                                        mRunnable);
-
-  return NS_FAILED(rv) ? false : true;
 }
 
 void
 BluetoothOppManager::Disconnect()
 {
   if (mSocket) {
     mSocket->Disconnect();
     mSocket = nullptr;
@@ -1431,23 +1424,19 @@ BluetoothOppManager::OnConnectSuccess(Bl
   // device disconnect with us.
   mSocket->GetAddress(mConnectedDeviceAddress);
 }
 
 void
 BluetoothOppManager::OnConnectError(BluetoothSocket* aSocket)
 {
   if (mRunnable) {
-    nsString errorStr;
-    errorStr.AssignLiteral("Failed to connect with a bluetooth opp manager!");
-    BluetoothReply* reply = new BluetoothReply(BluetoothReplyError(errorStr));
-    mRunnable->SetReply(reply);
-    if (NS_FAILED(NS_DispatchToMainThread(mRunnable))) {
-      NS_WARNING("Failed to dispatch to main thread!");
-    }
+    BluetoothValue v;
+    DispatchBluetoothReply(mRunnable, v,
+                           NS_LITERAL_STRING("OnConnectError:no runnable"));
     mRunnable.forget();
   }
 
   mSocket = nullptr;
   mRfcommSocket = nullptr;
   mL2capSocket = nullptr;
 
   Listen();
@@ -1479,8 +1468,36 @@ BluetoothOppManager::OnDisconnect(Blueto
 
   AfterOppDisconnected();
   mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
   mSuccessFlag = false;
 
   mSocket = nullptr;
   Listen();
 }
+
+void
+BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                         const nsAString& aServiceUuid,
+                                         int aChannel)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mRunnable);
+
+  BluetoothValue v;
+
+  if (aChannel < 0) {
+    DispatchBluetoothReply(mRunnable, v,
+                           NS_LITERAL_STRING("DeviceChannelRetrievalError"));
+    mSocket = nullptr;
+    Listen();
+    return;
+  }
+
+  if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) {
+    DispatchBluetoothReply(mRunnable, v,
+                           NS_LITERAL_STRING("SocketConnectionError"));
+    mSocket = nullptr;
+    Listen();
+    return;
+  }
+}
+
--- a/dom/bluetooth/BluetoothOppManager.h
+++ b/dom/bluetooth/BluetoothOppManager.h
@@ -3,32 +3,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothoppmanager_h__
 #define mozilla_dom_bluetooth_bluetoothoppmanager_h__
 
 #include "BluetoothCommon.h"
+#include "BluetoothProfileManagerBase.h"
 #include "BluetoothSocketObserver.h"
 #include "DeviceStorage.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/ipc/UnixSocket.h"
 #include "nsCOMArray.h"
 
 class nsIOutputStream;
 class nsIInputStream;
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothReplyRunnable;
 class BluetoothSocket;
 class ObexHeaderSet;
 
 class BluetoothOppManager : public BluetoothSocketObserver
+                          , public BluetoothProfileManagerBase
 {
 public:
   /*
    * Channel of reserved services are fixed values, please check
    * function add_reserved_service_records() in
    * external/bluetooth/bluez/src/adapter.c for more information.
    */
   static const int DEFAULT_OPP_CHANNEL = 10;
@@ -45,17 +47,17 @@ public:
    * that, call SendFile()/StopSendingFile() to control file-sharing
    * process. During the file transfering process, the application
    * will receive several system messages which contain the processed
    * percentage of file. At the end, the application will get another
    * system message indicating that te process is complete, then it can
    * either call Disconnect() to close RFCOMM connection or start another
    * file-sending thread via calling SendFile() again.
    */
-  bool Connect(const nsAString& aDeviceObjectPath,
+  void Connect(const nsAString& aDeviceObjectPath,
                BluetoothReplyRunnable* aRunnable);
   void Disconnect();
   bool Listen();
 
   bool SendFile(const nsAString& aDeviceAddress, BlobParent* aBlob);
   bool StopSendingFile();
   bool ConfirmReceivingFile(bool aConfirm);
 
@@ -78,19 +80,19 @@ public:
   // Implement interface BluetoothSocketObserver
   void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
 
   virtual void OnConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-  void OnConnectSuccess() MOZ_OVERRIDE;
-  void OnConnectError() MOZ_OVERRIDE;
-  void OnDisconnect() MOZ_OVERRIDE;
+  virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                   const nsAString& aServiceUuid,
+                                   int aChannel) MOZ_OVERRIDE;
 
 private:
   BluetoothOppManager();
   void StartFileTransfer();
   void StartSendingNextFile();
   void FileTransferComplete();
   void UpdateProgress();
   void ReceivingFileConfirmation();
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -0,0 +1,24 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* 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_bluetoothprofilemanagerbase_h__
+#define mozilla_dom_bluetooth_bluetoothprofilemanagerbase_h__
+
+#include "BluetoothCommon.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothProfileManagerBase
+{
+public:
+  virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                   const nsAString& aServiceUuid,
+                                   int aChannel) = 0;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif  //#ifndef mozilla_dom_bluetooth_bluetoothprofilemanagerbase_h__
deleted file mode 100644
--- a/dom/bluetooth/BluetoothScoManager.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* 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 "BluetoothScoManager.h"
-
-#include "BluetoothReplyRunnable.h"
-#include "BluetoothService.h"
-#include "BluetoothSocket.h"
-#include "BluetoothUtils.h"
-
-#include "mozilla/Services.h"
-#include "mozilla/StaticPtr.h"
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "nsContentUtils.h"
-#include "nsIAudioManager.h"
-#include "nsIObserverService.h"
-
-#define BLUETOOTH_SCO_STATUS_CHANGED "bluetooth-sco-status-changed"
-
-using namespace mozilla;
-using namespace mozilla::ipc;
-USING_BLUETOOTH_NAMESPACE
-
-class mozilla::dom::bluetooth::BluetoothScoManagerObserver : public nsIObserver
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  BluetoothScoManagerObserver()
-  {
-  }
-
-  bool Init()
-  {
-    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    MOZ_ASSERT(obs);
-
-    if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
-      NS_WARNING("Failed to add shutdown observer!");
-      return false;
-    }
-    return true;
-  }
-
-  bool Shutdown()
-  {
-    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    if (!obs ||
-        (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) {
-      NS_WARNING("Can't unregister observers!");
-      return false;
-    }
-    return true;
-  }
-
-  ~BluetoothScoManagerObserver()
-  {
-    Shutdown();
-  }
-};
-
-void
-BluetoothScoManager::NotifyAudioManager(const nsAString& aAddress)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIObserverService> obs =
-    do_GetService("@mozilla.org/observer-service;1");
-  NS_ENSURE_TRUE_VOID(obs);
-
-  const PRUnichar* addr =
-    aAddress.IsEmpty() ? nullptr : aAddress.BeginReading();
-
-  if (NS_FAILED(obs->NotifyObservers(nullptr,
-                                     BLUETOOTH_SCO_STATUS_CHANGED,
-                                     addr))) {
-    NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
-    return;
-  }
-}
-
-NS_IMPL_ISUPPORTS1(BluetoothScoManagerObserver, nsIObserver)
-
-namespace {
-StaticAutoPtr<BluetoothScoManager> gBluetoothScoManager;
-StaticRefPtr<BluetoothScoManagerObserver> sScoObserver;
-bool gInShutdown = false;
-} // anonymous namespace
-
-NS_IMETHODIMP
-BluetoothScoManagerObserver::Observe(nsISupports* aSubject,
-                                     const char* aTopic,
-                                     const PRUnichar* aData)
-{
-  MOZ_ASSERT(gBluetoothScoManager);
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
-    return gBluetoothScoManager->HandleShutdown();
-  }
-
-  MOZ_ASSERT(false, "BluetoothScoManager got unexpected topic!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-BluetoothScoManager::BluetoothScoManager()
-{
-}
-
-bool
-BluetoothScoManager::Init()
-{
-  mSocket = new BluetoothSocket(this,
-                                BluetoothSocketType::SCO,
-                                true,
-                                false);
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
-
-  sScoObserver = new BluetoothScoManagerObserver();
-  if (!sScoObserver->Init()) {
-    NS_WARNING("Cannot set up SCO observers!");
-  }
-  return true;
-}
-
-BluetoothScoManager::~BluetoothScoManager()
-{
-  Cleanup();
-}
-
-void
-BluetoothScoManager::Cleanup()
-{
-  sScoObserver->Shutdown();
-  sScoObserver = nullptr;
-}
-
-//static
-BluetoothScoManager*
-BluetoothScoManager::Get()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // If we already exist, exit early
-  if (gBluetoothScoManager) {
-    return gBluetoothScoManager;
-  }
-
-  // If we're in shutdown, don't create a new instance
-  if (gInShutdown) {
-    NS_WARNING("BluetoothScoManager can't be created during shutdown");
-    return nullptr;
-  }
-
-  // Create new instance, register, return
-  BluetoothScoManager* manager = new BluetoothScoManager();
-  NS_ENSURE_TRUE(manager->Init(), nullptr);
-
-  gBluetoothScoManager = manager;
-  return gBluetoothScoManager;
-}
-
-// Virtual function of class SocketConsumer
-void
-BluetoothScoManager::ReceiveSocketData(BluetoothSocket* aSocket,
-                                       nsAutoPtr<UnixSocketRawData>& aMessage)
-{
-  // SCO socket do nothing here
-  MOZ_NOT_REACHED("This should never be called!");
-}
-
-nsresult
-BluetoothScoManager::HandleShutdown()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  gInShutdown = true;
-  mSocket->Disconnect();
-  gBluetoothScoManager = nullptr;
-  return NS_OK;
-}
-
-bool
-BluetoothScoManager::Connect(const nsAString& aDeviceAddress)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (gInShutdown) {
-    MOZ_ASSERT(false, "Connect called while in shutdown!");
-    return false;
-  }
-
-  SocketConnectionStatus status = mSocket->GetConnectionStatus();
-  if (status == SocketConnectionStatus::SOCKET_CONNECTED ||
-      status == SocketConnectionStatus::SOCKET_CONNECTING) {
-    NS_WARNING("SCO connection exists or is being established");
-    return false;
-  }
-
-  mSocket->Disconnect();
-
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE(bs, false);
-
-  nsresult rv = bs->GetScoSocket(aDeviceAddress,
-                                 true,
-                                 false,
-                                 mSocket);
-
-  return NS_FAILED(rv) ? false : true;
-}
-
-bool
-BluetoothScoManager::Listen()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (gInShutdown) {
-    MOZ_ASSERT(false, "Listen called while in shutdown!");
-    return false;
-  }
-
-  if (mSocket->GetConnectionStatus() ==
-      SocketConnectionStatus::SOCKET_LISTENING) {
-    NS_WARNING("BluetoothScoManager has been already listening");
-    return true;
-  }
-
-  mSocket->Disconnect();
-
-  if (!mSocket->Listen(-1)) {
-    NS_WARNING("[SCO] Can't listen on socket!");
-    return false;
-  }
-
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
-  return true;
-}
-
-void
-BluetoothScoManager::Disconnect()
-{
-  mSocket->Disconnect();
-}
-
-void
-BluetoothScoManager::OnConnectSuccess(BluetoothSocket* aSocket)
-{
-  MOZ_ASSERT(aSocket == mSocket);
-
-  nsString address;
-  mSocket->GetAddress(address);
-  NotifyAudioManager(address);
-
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
-}
-
-void
-BluetoothScoManager::OnConnectError(BluetoothSocket* aSocket)
-{
-  MOZ_ASSERT(aSocket == mSocket);
-
-  mSocket->Disconnect();
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
-  Listen();
-}
-
-void
-BluetoothScoManager::OnDisconnect(BluetoothSocket* aSocket)
-{
-  MOZ_ASSERT(aSocket == mSocket);
-
-  if (mPrevSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
-    Listen();
-
-    nsString address = NS_LITERAL_STRING("");
-    NotifyAudioManager(address);
-  }
-}
-
-bool
-BluetoothScoManager::IsConnected()
-{
-  if (mSocket) {
-    return mSocket->GetConnectionStatus() ==
-           SocketConnectionStatus::SOCKET_CONNECTED;
-  }
-
-  return false;
-}
deleted file mode 100644
--- a/dom/bluetooth/BluetoothScoManager.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* 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_bluetoothscomanager_h__
-#define mozilla_dom_bluetooth_bluetoothscomanager_h__
-
-#include "BluetoothCommon.h"
-#include "BluetoothSocketObserver.h"
-#include "nsIObserver.h"
-
-BEGIN_BLUETOOTH_NAMESPACE
-
-class BluetoothReplyRunnable;
-class BluetoothScoManagerObserver;
-class BluetoothSocket;
-
-class BluetoothScoManager : public BluetoothSocketObserver
-{
-public:
-  static BluetoothScoManager* Get();
-  ~BluetoothScoManager();
-
-  virtual void ReceiveSocketData(
-    BluetoothSocket* aSocket,
-    nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
-  virtual void OnConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-  virtual void OnConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-  virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-
-  bool Connect(const nsAString& aDeviceObjectPath);
-  void Disconnect();
-  bool Listen();
-  bool IsConnected();
-
-private:
-  friend class BluetoothScoManagerObserver;
-  BluetoothScoManager();
-  bool Init();
-  void Cleanup();
-  nsresult HandleShutdown();
-  void NotifyAudioManager(const nsAString& aAddress);
-
-  SocketConnectionStatus mPrevSocketStatus;
-  nsRefPtr<BluetoothSocket> mSocket;
-};
-
-END_BLUETOOTH_NAMESPACE
-
-#endif
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -3,16 +3,17 @@
 /* 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 "BluetoothCommon.h"
+#include "BluetoothProfileManagerBase.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 #include "nsIObserver.h"
 #include "nsIThread.h"
 #include "nsTObserverArray.h"
 
 namespace mozilla {
@@ -176,17 +177,17 @@ public:
    *
    * @return NS_OK if property is set correctly, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) = 0;
 
-  /** 
+  /**
    * Get the path of a device
    *
    * @param aAdapterPath Path to the Adapter that's communicating with the device
    * @param aDeviceAddress Device address (XX:XX:XX:XX:XX:XX format)
    * @param aDevicePath Return value of path
    *
    * @return True if path set correctly, false otherwise
    */
@@ -205,24 +206,30 @@ public:
                        BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual nsresult
   GetScoSocket(const nsAString& aObjectPath,
                bool aAuth,
                bool aEncrypt,
                mozilla::ipc::UnixSocketConsumer* aConsumer) = 0;
 
+  /**
+   * Get corresponding service channel of specific service on remote device.
+   * It's usually the very first step of establishing an outbound connection.
+   *
+   * @param aObjectPath Object path of remote device
+   * @param aServiceUuid UUID of the target service
+   * @param aManager Instance which has callback function OnGetServiceChannel()
+   *
+   * @return NS_OK if the task begins, NS_ERROR_FAILURE otherwise
+   */
   virtual nsresult
-  GetSocketViaService(const nsAString& aObjectPath,
-                      const nsAString& aService,
-                      BluetoothSocketType aType,
-                      bool aAuth,
-                      bool aEncrypt,
-                      mozilla::ipc::UnixSocketConsumer* aSocketConsumer,
-                      BluetoothReplyRunnable* aRunnable) = 0;
+  GetServiceChannel(const nsAString& aObjectPath,
+                    const nsAString& aServiceUuid,
+                    BluetoothProfileManagerBase* aManager) = 0;
 
   virtual bool
   SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode,
                      BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual bool
   SetPasskeyInternal(const nsAString& aDeviceAddress, uint32_t aPasskey,
                      BluetoothReplyRunnable* aRunnable) = 0;
@@ -258,16 +265,25 @@ public:
   virtual void
   StopSendingFile(const nsAString& aDeviceAddress,
                   BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual void
   ConfirmReceivingFile(const nsAString& aDeviceAddress, bool aConfirm,
                        BluetoothReplyRunnable* aRunnable) = 0;
 
+  virtual void
+  ConnectSco(BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  DisconnectSco(BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  IsScoConnected(BluetoothReplyRunnable* aRunnable) = 0;
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
--- a/dom/bluetooth/BluetoothTelephonyListener.cpp
+++ b/dom/bluetooth/BluetoothTelephonyListener.cpp
@@ -25,33 +25,37 @@ public:
 };
 
 NS_IMPL_ISUPPORTS1(TelephonyListener, nsITelephonyListener)
 
 NS_IMETHODIMP
 TelephonyListener::CallStateChanged(uint32_t aCallIndex,
                                     uint16_t aCallState,
                                     const nsAString& aNumber,
-                                    bool aIsActive)
+                                    bool aIsActive,
+                                    bool aIsOutgoing)
 {
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-  hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber, true);
+  hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber,
+                              aIsOutgoing, true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelephonyListener::EnumerateCallState(uint32_t aCallIndex,
                                       uint16_t aCallState,
                                       const nsAString_internal& aNumber,
                                       bool aIsActive,
+                                      bool aIsOutgoing,
                                       bool* aResult)
 {
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-  hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber, false);
+  hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber,
+                              aIsOutgoing, false);
   *aResult = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelephonyListener::NotifyError(int32_t aCallIndex,
                                const nsAString& aError)
 {
--- a/dom/bluetooth/Makefile.in
+++ b/dom/bluetooth/Makefile.in
@@ -39,17 +39,16 @@ CPPSRCS += \
   BluetoothUtils.cpp \
   BluetoothChild.cpp \
   BluetoothParent.cpp \
   BluetoothServiceChildProcess.cpp \
   BluetoothUnixSocketConnector.cpp \
   BluetoothHfpManager.cpp \
   BluetoothOppManager.cpp \
   ObexBase.cpp \
-  BluetoothScoManager.cpp \
   BluetoothUuid.cpp \
   BluetoothSocket.cpp \
   $(NULL)
 
 ifdef MOZ_B2G_RIL
 CPPSRCS += BluetoothTelephonyListener.cpp
 endif
 
--- a/dom/bluetooth/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/ipc/BluetoothParent.cpp
@@ -220,16 +220,22 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TSendFileRequest:
       return actor->DoRequest(aRequest.get_SendFileRequest());
     case Request::TStopSendingFileRequest:
       return actor->DoRequest(aRequest.get_StopSendingFileRequest());
     case Request::TConfirmReceivingFileRequest:
       return actor->DoRequest(aRequest.get_ConfirmReceivingFileRequest());
     case Request::TDenyReceivingFileRequest:
       return actor->DoRequest(aRequest.get_DenyReceivingFileRequest());
+    case Request::TConnectScoRequest:
+      return actor->DoRequest(aRequest.get_ConnectScoRequest());
+    case Request::TDisconnectScoRequest:
+      return actor->DoRequest(aRequest.get_DisconnectScoRequest());
+    case Request::TIsScoConnectedRequest:
+      return actor->DoRequest(aRequest.get_IsScoConnectedRequest());
     default:
       MOZ_NOT_REACHED("Unknown type!");
       return false;
   }
 
   MOZ_NOT_REACHED("Should never get here!");
   return false;
 }
@@ -565,8 +571,38 @@ BluetoothRequestParent::DoRequest(const 
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TDenyReceivingFileRequest);
 
   mService->ConfirmReceivingFile(aRequest.devicePath(),
                                  false,
                                  mReplyRunnable.get());
   return true;
 }
+
+bool
+BluetoothRequestParent::DoRequest(const ConnectScoRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TConnectScoRequest);
+
+  mService->ConnectSco(mReplyRunnable.get());
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const DisconnectScoRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TDisconnectScoRequest);
+
+  mService->DisconnectSco(mReplyRunnable.get());
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const IsScoConnectedRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TIsScoConnectedRequest);
+
+  mService->IsScoConnected(mReplyRunnable.get());
+  return true;
+}
--- a/dom/bluetooth/ipc/BluetoothParent.h
+++ b/dom/bluetooth/ipc/BluetoothParent.h
@@ -180,13 +180,22 @@ protected:
   bool
   DoRequest(const StopSendingFileRequest& aRequest);
 
   bool
   DoRequest(const ConfirmReceivingFileRequest& aRequest);
 
   bool
   DoRequest(const DenyReceivingFileRequest& aRequest);
+
+  bool
+  DoRequest(const ConnectScoRequest& aRequest);
+
+  bool
+  DoRequest(const DisconnectScoRequest& aRequest);
+
+  bool
+  DoRequest(const IsScoConnectedRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothparent_h__
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -202,24 +202,19 @@ BluetoothServiceChildProcess::GetScoSock
                                     bool aEncrypt,
                                     mozilla::ipc::UnixSocketConsumer* aConsumer)
 {
   MOZ_NOT_REACHED("This should never be called!");
   return NS_ERROR_FAILURE;
 }
 
 nsresult
-BluetoothServiceChildProcess::GetSocketViaService(
-                                       const nsAString& aObjectPath,
-                                       const nsAString& aService,
-                                       BluetoothSocketType aType,
-                                       bool aAuth,
-                                       bool aEncrypt,
-                                       mozilla::ipc::UnixSocketConsumer* aConsumer,
-                                       BluetoothReplyRunnable* aRunnable)
+BluetoothServiceChildProcess::GetServiceChannel(const nsAString& aObjectPath,
+                                                const nsAString& aServiceUuid,
+                                                BluetoothProfileManagerBase* aManager)
 {
   MOZ_NOT_REACHED("This should never be called!");
   return NS_ERROR_FAILURE;
 }
 
 bool
 BluetoothServiceChildProcess::SetPinCodeInternal(
                                                 const nsAString& aDeviceAddress,
@@ -331,16 +326,34 @@ BluetoothServiceChildProcess::ConfirmRec
                 ConfirmReceivingFileRequest(nsString(aDeviceAddress)));
     return;
   }
   
   SendRequest(aRunnable,
               DenyReceivingFileRequest(nsString(aDeviceAddress)));
 }
 
+void
+BluetoothServiceChildProcess::ConnectSco(BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, ConnectScoRequest());
+}
+
+void
+BluetoothServiceChildProcess::DisconnectSco(BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, DisconnectScoRequest());
+}
+
+void
+BluetoothServiceChildProcess::IsScoConnected(BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, IsScoConnectedRequest());
+}
+
 nsresult
 BluetoothServiceChildProcess::HandleStartup()
 {
   // Don't need to do anything here for startup since our Create function takes
   // care of the actor machinery.
   return NS_OK;
 }
 
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -82,23 +82,19 @@ public:
 
   virtual nsresult
   GetScoSocket(const nsAString& aObjectPath,
                bool aAuth,
                bool aEncrypt,
                mozilla::ipc::UnixSocketConsumer* aConsumer) MOZ_OVERRIDE;
 
   virtual nsresult
-  GetSocketViaService(const nsAString& aObjectPath,
-                      const nsAString& aService,
-                      BluetoothSocketType aType,
-                      bool aAuth,
-                      bool aEncrypt,
-                      mozilla::ipc::UnixSocketConsumer* aConsumer,
-                      BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+  GetServiceChannel(const nsAString& aObjectPath,
+                    const nsAString& aServiceUuid,
+                    BluetoothProfileManagerBase* aManager) MOZ_OVERRIDE;
 
   virtual bool
   SetPinCodeInternal(const nsAString& aDeviceAddress,
                      const nsAString& aPinCode,
                      BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual bool
   SetPasskeyInternal(const nsAString& aDeviceAddress,
@@ -137,16 +133,26 @@ public:
   virtual void
   StopSendingFile(const nsAString& aDeviceAddress,
                   BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual void
   ConfirmReceivingFile(const nsAString& aDeviceAddress,
                        bool aConfirm,
                        BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  ConnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  DisconnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  IsScoConnected(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/ipc/PBluetooth.ipdl
@@ -124,16 +124,28 @@ struct ConfirmReceivingFileRequest
   nsString devicePath;
 };
 
 struct DenyReceivingFileRequest
 {
   nsString devicePath;
 };
 
+struct ConnectScoRequest
+{
+};
+
+struct DisconnectScoRequest
+{
+};
+
+struct IsScoConnectedRequest
+{
+};
+
 union Request
 {
   DefaultAdapterPathRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
   StopDiscoveryRequest;
   PairRequest;
@@ -147,16 +159,19 @@ union Request
   ConnectedDevicePropertiesRequest;
   PairedDevicePropertiesRequest;
   ConnectRequest;
   DisconnectRequest;
   SendFileRequest;
   StopSendingFileRequest;
   ConfirmReceivingFileRequest;
   DenyReceivingFileRequest;
+  ConnectScoRequest;
+  DisconnectScoRequest;
+  IsScoConnectedRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -16,17 +16,16 @@
 ** limitations under the License.
 */
 
 #include "base/basictypes.h"
 #include "BluetoothDBusService.h"
 #include "BluetoothHfpManager.h"
 #include "BluetoothOppManager.h"
 #include "BluetoothReplyRunnable.h"
-#include "BluetoothScoManager.h"
 #include "BluetoothUnixSocketConnector.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 
 #include <cstdio>
 #include <dbus/dbus.h>
 
 #include "pratom.h"
@@ -833,57 +832,26 @@ public:
   Run()
   {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     if (!hfp || !hfp->Listen()) {
       NS_WARNING("Failed to start listening for BluetoothHfpManager!");
       return NS_ERROR_FAILURE;
     }
 
-    BluetoothScoManager* sco = BluetoothScoManager::Get();
-    if (!sco || !sco->Listen()) {
-      NS_WARNING("Failed to start listening for BluetoothScoManager!");
-      return NS_ERROR_FAILURE;
-    }
-
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     if (!opp || !opp->Listen()) {
       NS_WARNING("Failed to start listening for BluetoothOppManager!");
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 };
 
-class ShutdownProfileManagersRunnable : public nsRunnable
-{
-public:
-  NS_IMETHOD
-  Run()
-  {
-    BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    if (hfp) {
-      hfp->Disconnect();
-    }
-
-    BluetoothOppManager* opp = BluetoothOppManager::Get();
-    if (opp) {
-      opp->Disconnect();
-    }
-
-    BluetoothScoManager* sco = BluetoothScoManager::Get();
-    if (sco) {
-      sco->Disconnect();
-    }
-
-    return NS_OK;
-  }
-};
-
 class PrepareAdapterRunnable : public nsRunnable
 {
 public:
   PrepareAdapterRunnable()
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
@@ -1972,16 +1940,37 @@ public:
         DispatchBluetoothReply(mRunnable, values, errorStr);
         return NS_OK;
       }
 
       // We have to manually attach the path to the rest of the elements
       v.get_ArrayOfBluetoothNamedValue().AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("Path"), objectPath)
       );
+      const InfallibleTArray<BluetoothNamedValue>& deviceProperties =
+        v.get_ArrayOfBluetoothNamedValue();
+      uint32_t length = deviceProperties.Length();
+      // It is possible that property Icon missed due to CoD of major
+      // class is TOY but service class is "Audio", we need to assign
+      // Icon as audio-card. This is for PTS test TC_AG_COD_BV_02_I.
+      // As HFP specification defined that
+      // service class is "Audio" can be considered as HFP AG.
+      if (!ContainsIcon(deviceProperties)) {
+        for (uint32_t p = 0; p < length; ++p) {
+          if (deviceProperties[p].name().EqualsLiteral("Class")) {
+            if (HasAudioService(deviceProperties[p].value().get_uint32_t())) {
+              v.get_ArrayOfBluetoothNamedValue()
+                 .AppendElement(
+                 BluetoothNamedValue(NS_LITERAL_STRING("Icon"),
+                 NS_LITERAL_STRING("audio-card")));
+            }
+            break;
+          }
+        }
+      }
 
       if (mFilterFunc(v)) {
         values.get_ArrayOfBluetoothNamedValue().AppendElement(
           BluetoothNamedValue(mDeviceAddresses[i],
                               v.get_ArrayOfBluetoothNamedValue())
         );
       }
     }
@@ -2523,47 +2512,33 @@ BluetoothDBusService::PrepareAdapterInte
   return NS_OK;
 }
 
 void
 BluetoothDBusService::Connect(const nsAString& aDeviceAddress,
                               const uint16_t aProfileId,
                               BluetoothReplyRunnable* aRunnable)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
-
-  BluetoothValue v;
-  nsAutoString errorStr;
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (aProfileId == BluetoothServiceClass::HANDSFREE) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    if (!hfp->Connect(GetObjectPathFromAddress(sAdapterPath, aDeviceAddress),
-                      true, aRunnable)) {
-      errorStr.AssignLiteral("BluetoothHfpManager has connected/is connecting \
-                              to a headset!");
-      DispatchBluetoothReply(aRunnable, v, errorStr);
-    }
+    hfp->Connect(
+      GetObjectPathFromAddress(sAdapterPath, aDeviceAddress), true, aRunnable);
   } else if (aProfileId == BluetoothServiceClass::HEADSET) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    if (!hfp->Connect(GetObjectPathFromAddress(sAdapterPath, aDeviceAddress),
-                      false, aRunnable)) {
-      errorStr.AssignLiteral("BluetoothHfpManager has connected/is connecting \
-                              to a headset!");
-      DispatchBluetoothReply(aRunnable, v, errorStr);
-    }
+    hfp->Connect(
+      GetObjectPathFromAddress(sAdapterPath, aDeviceAddress), false, aRunnable);
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
-    if (!opp->Connect(GetObjectPathFromAddress(sAdapterPath, aDeviceAddress),
-                      aRunnable)) {
-      errorStr.AssignLiteral("BluetoothOppManager has been connected/is \
-                              connecting!");
-      DispatchBluetoothReply(aRunnable, v, errorStr);
-    }
+    opp->Connect(
+      GetObjectPathFromAddress(sAdapterPath, aDeviceAddress), aRunnable);
   } else {
-    errorStr.AssignLiteral("Unknown profile");
-    DispatchBluetoothReply(aRunnable, v, errorStr);
+    BluetoothValue v;
+    DispatchBluetoothReply(aRunnable, v, NS_LITERAL_STRING("UnknownProfileError"));
   }
 }
 
 void
 BluetoothDBusService::Disconnect(const uint16_t aProfileId,
                                  BluetoothReplyRunnable* aRunnable)
 {
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
@@ -2649,107 +2624,105 @@ private:
   nsString mObjectPath;
   nsString mServiceUUID;
   BluetoothSocketType mType;
   bool mAuth;
   bool mEncrypt;
   int mChannel;
 };
 
-class GetDeviceChannelForConnectRunnable : public nsRunnable
+class OnGetServiceChannelRunnable : public nsRunnable
 {
 public:
-  GetDeviceChannelForConnectRunnable(BluetoothReplyRunnable* aRunnable,
-                                     UnixSocketConsumer* aConsumer,
-                                     const nsAString& aObjectPath,
-                                     const nsAString& aServiceUUID,
-                                     BluetoothSocketType aType,
-                                     bool aAuth,
-                                     bool aEncrypt)
-    : mRunnable(dont_AddRef(aRunnable)),
-      mConsumer(aConsumer),
-      mObjectPath(aObjectPath),
-      mServiceUUID(aServiceUUID),
-      mType(aType),
-      mAuth(aAuth),
-      mEncrypt(aEncrypt)
+  OnGetServiceChannelRunnable(const nsAString& aObjectPath,
+                              const nsAString& aServiceUuid,
+                              int aChannel,
+                              BluetoothProfileManagerBase* aManager)
+    : mServiceUuid(aServiceUuid),
+      mChannel(aChannel),
+      mManager(aManager)
+  {
+    MOZ_ASSERT(!aObjectPath.IsEmpty());
+    MOZ_ASSERT(!aServiceUuid.IsEmpty());
+    MOZ_ASSERT(aManager);
+
+    mDeviceAddress = GetAddressFromObjectPath(aObjectPath);
+  }
+
+  nsresult
+  Run()
   {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mManager->OnGetServiceChannel(mDeviceAddress, mServiceUuid, mChannel);
+
+    return NS_OK;
+  }
+
+private:
+  nsString mDeviceAddress;
+  nsString mServiceUuid;
+  int mChannel;
+  BluetoothProfileManagerBase* mManager;
+};
+
+class GetServiceChannelRunnable : public nsRunnable
+{
+public:
+  GetServiceChannelRunnable(const nsAString& aObjectPath,
+                            const nsAString& aServiceUuid,
+                            BluetoothProfileManagerBase* aManager)
+    : mObjectPath(aObjectPath),
+      mServiceUuid(aServiceUuid),
+      mManager(aManager)
+  {
+    MOZ_ASSERT(!aObjectPath.IsEmpty());
+    MOZ_ASSERT(!aServiceUuid.IsEmpty());
+    MOZ_ASSERT(aManager);
   }
 
   nsresult
   Run()
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
-    int channel = GetDeviceServiceChannel(mObjectPath, mServiceUUID, 0x0004);
-    BluetoothValue v;
-    nsString replyError;
-    if (channel < 0) {
-      replyError.AssignLiteral("DeviceChannelRetrievalError");
-      DispatchBluetoothReply(mRunnable, v, replyError);
-      return NS_OK;
-    }
-    nsRefPtr<nsRunnable> func(new ConnectBluetoothSocketRunnable(mRunnable,
-                                                                 mConsumer,
-                                                                 mObjectPath,
-                                                                 mServiceUUID,
-                                                                 mType, mAuth,
-                                                                 mEncrypt,
-                                                                 channel));
-    if (NS_FAILED(NS_DispatchToMainThread(func, NS_DISPATCH_NORMAL))) {
-      NS_WARNING("Cannot dispatch connection task!");
+    int channel = GetDeviceServiceChannel(mObjectPath, mServiceUuid, 0x0004);
+    nsRefPtr<nsRunnable> r(new OnGetServiceChannelRunnable(mObjectPath,
+                                                           mServiceUuid,
+                                                           channel,
+                                                           mManager));
+    if (NS_FAILED(NS_DispatchToMainThread(r))) {
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 
 private:
-  nsRefPtr<BluetoothReplyRunnable> mRunnable;
-  nsRefPtr<UnixSocketConsumer> mConsumer;
   nsString mObjectPath;
-  nsString mServiceUUID;
-  BluetoothSocketType mType;
-  bool mAuth;
-  bool mEncrypt;
+  nsString mServiceUuid;
+  BluetoothProfileManagerBase* mManager;
 };
 
 nsresult
-BluetoothDBusService::GetSocketViaService(
-                                    const nsAString& aObjectPath,
-                                    const nsAString& aService,
-                                    BluetoothSocketType aType,
-                                    bool aAuth,
-                                    bool aEncrypt,
-                                    mozilla::ipc::UnixSocketConsumer* aConsumer,
-                                    BluetoothReplyRunnable* aRunnable)
+BluetoothDBusService::GetServiceChannel(const nsAString& aObjectPath,
+                                        const nsAString& aServiceUuid,
+                                        BluetoothProfileManagerBase* aManager)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
-  if (!IsReady()) {
-    BluetoothValue v;
-    nsAutoString errorStr;
-    errorStr.AssignLiteral("Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, v, errorStr);
-    return NS_OK;
-  }
-
-  nsRefPtr<BluetoothReplyRunnable> runnable = aRunnable;
-
-  nsRefPtr<nsRunnable> func(new GetDeviceChannelForConnectRunnable(
-                              runnable,
-                              aConsumer,
-                              aObjectPath,
-                              aService, aType,
-                              aAuth, aEncrypt));
-  if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
-    NS_WARNING("Cannot dispatch firmware loading task!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBluetoothCommandThread);
+
+  nsRefPtr<nsRunnable> r(new GetServiceChannelRunnable(aObjectPath,
+                                                       aServiceUuid,
+                                                       aManager));
+
+  if (NS_FAILED(mBluetoothCommandThread->Dispatch(r, NS_DISPATCH_NORMAL))) {
     return NS_ERROR_FAILURE;
   }
 
-  runnable.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDBusService::GetScoSocket(const nsAString& aAddress,
                                    bool aAuth,
                                    bool aEncrypt,
                                    mozilla::ipc::UnixSocketConsumer* aConsumer)
@@ -2835,8 +2808,51 @@ BluetoothDBusService::ConfirmReceivingFi
 
   if (!opp->ConfirmReceivingFile(aConfirm)) {
     errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, v, errorStr);
 }
 
+void
+BluetoothDBusService::ConnectSco(BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
+  NS_ENSURE_TRUE_VOID(hfp);
+  if(!hfp->ConnectSco(aRunnable)) {
+    NS_NAMED_LITERAL_STRING(replyError,
+      "SCO socket exists or HFP is not connected");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError);
+  }
+}
+
+void
+BluetoothDBusService::DisconnectSco(BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
+  NS_ENSURE_TRUE_VOID(hfp);
+  if (hfp->DisconnectSco()) {
+    DispatchBluetoothReply(aRunnable,
+                           BluetoothValue(true), NS_LITERAL_STRING(""));
+    return;
+  }
+
+  NS_NAMED_LITERAL_STRING(replyError,
+    "SCO socket doesn't exist or HFP is not connected");
+  DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError);
+}
+
+void
+BluetoothDBusService::IsScoConnected(BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
+  NS_ENSURE_TRUE_VOID(hfp);
+  DispatchBluetoothReply(aRunnable,
+                         hfp->IsScoConnected(), EmptyString());
+}
+
--- a/dom/bluetooth/linux/BluetoothDBusService.h
+++ b/dom/bluetooth/linux/BluetoothDBusService.h
@@ -80,23 +80,19 @@ public:
 
   virtual nsresult
   GetScoSocket(const nsAString& aObjectPath,
                bool aAuth,
                bool aEncrypt,
                mozilla::ipc::UnixSocketConsumer* aConsumer);
 
   virtual nsresult
-  GetSocketViaService(const nsAString& aObjectPath,
-                      const nsAString& aService,
-                      BluetoothSocketType aType,
-                      bool aAuth,
-                      bool aEncrypt,
-                      mozilla::ipc::UnixSocketConsumer* aConsumer,
-                      BluetoothReplyRunnable* aRunnable);
+  GetServiceChannel(const nsAString& aObjectPath,
+                    const nsAString& aServiceUuid,
+                    BluetoothProfileManagerBase* aManager);
 
   virtual nsresult
   CreatePairedDeviceInternal(const nsAString& aDeviceAddress,
                              int aTimeout,
                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   RemoveDeviceInternal(const nsAString& aDeviceObjectPath,
@@ -141,16 +137,25 @@ public:
   virtual void
   StopSendingFile(const nsAString& aDeviceAddress,
                   BluetoothReplyRunnable* aRunnable);
 
   virtual void
   ConfirmReceivingFile(const nsAString& aDeviceAddress, bool aConfirm,
                        BluetoothReplyRunnable* aRunnable);
 
+  virtual void
+  ConnectSco(BluetoothReplyRunnable* aRunnable);
+
+  virtual void
+  DisconnectSco(BluetoothReplyRunnable* aRunnable);
+
+  virtual void
+  IsScoConnected(BluetoothReplyRunnable* aRunnable);
+
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
   nsresult SendDiscoveryMessage(const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
   nsresult SendSetPropertyMessage(const char* aInterface,
--- a/dom/bluetooth/nsIDOMBluetoothAdapter.idl
+++ b/dom/bluetooth/nsIDOMBluetoothAdapter.idl
@@ -5,17 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMEventTarget.idl"
 
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 interface nsIDOMBluetoothDevice;
 
-[scriptable, builtinclass, uuid(88a5638f-f55a-4d67-8437-392d0a9a87c7)]
+[scriptable, builtinclass, uuid(7058d214-3575-4913-99ad-0980296f617a)]
 interface nsIDOMBluetoothAdapter : nsIDOMEventTarget
 {
   readonly attribute DOMString address;
   [binaryname(AdapterClass)] readonly attribute unsigned long class;
   readonly attribute bool discovering;
 
   [implicit_jscontext]
   readonly attribute jsval devices;
@@ -53,11 +53,16 @@ interface nsIDOMBluetoothAdapter : nsIDO
   nsIDOMDOMRequest connect(in DOMString aDeviceAddress, in unsigned short aProfile);
   nsIDOMDOMRequest disconnect(in unsigned short aProfile);
 
   // One device can only send one file at a time
   nsIDOMDOMRequest sendFile(in DOMString aDeviceAddress, in nsIDOMBlob aBlob);
   nsIDOMDOMRequest stopSendingFile(in DOMString aDeviceAddress);
   nsIDOMDOMRequest confirmReceivingFile(in DOMString aDeviceAddress, in bool aConfirmation);
 
+  // Connect/Disconnect SCO (audio) connection
+  nsIDOMDOMRequest connectSco();
+  nsIDOMDOMRequest disconnectSco();
+  nsIDOMDOMRequest isScoConnected();
+
   // Fired when discoverying and any device is discovered.
   [implicit_jscontext] attribute jsval ondevicefound;
 };
--- a/dom/browser-element/mochitest/browserElementTestHelpers.js
+++ b/dom/browser-element/mochitest/browserElementTestHelpers.js
@@ -138,46 +138,59 @@ function expectOnlyOneProcessCreated() {
     expectProcessCreated().then(function(childID) {
       ok(false, 'Got unexpected process creation, childID=' + childID);
     });
   });
   return p;
 }
 
 // Returns a promise which is resolved or rejected the next time the process
-// childID changes its priority.  We resolve if the priority matches
-// expectedPriority, and we reject otherwise.
-function expectPriorityChange(childID, expectedPriority) {
+// childID changes its priority.  We resolve if the (priority, CPU priority)
+// tuple matches (expectedPriority, expectedCPUPriority) and we reject
+// otherwise.
+//
+// expectedCPUPriority is an optional argument; if it's not specified, we
+// resolve if priority matches expectedPriority.
+
+function expectPriorityChange(childID, expectedPriority,
+                              /* optional */ expectedCPUPriority) {
   var deferred = Promise.defer();
 
   var observed = false;
   browserElementTestHelpers.addProcessPriorityObserver(
     'process-priority-set',
     function(subject, topic, data) {
       if (observed) {
         return;
       }
 
-      [id, priority] = data.split(":");
+      [id, priority, cpuPriority] = data.split(":");
       if (id != childID) {
         return;
       }
 
       // Make sure we run the is() calls in this observer only once, otherwise
       // we'll expect /every/ priority change to match expectedPriority.
       observed = true;
 
       is(priority, expectedPriority,
          'Expected priority of childID ' + childID +
          ' to change to ' + expectedPriority);
 
-      if (priority == expectedPriority) {
-        deferred.resolve(priority);
+      if (expectedCPUPriority) {
+        is(cpuPriority, expectedCPUPriority,
+           'Expected CPU priority of childID ' + childID +
+           ' to change to ' + expectedCPUPriority);
+      }
+
+      if (priority == expectedPriority &&
+          (!expectedCPUPriority || expectedCPUPriority == cpuPriority)) {
+        deferred.resolve();
       } else {
-        deferred.reject(priority);
+        deferred.reject();
       }
     }
   );
 
   return deferred.promise;
 }
 
 // Returns a promise which is resolved the first time the given iframe fires
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/priority/CAUTION
@@ -0,0 +1,15 @@
+A word to the wise:
+
+You must ensure that if your test finishes successfully, no processes have
+priority FOREGROUND_HIGH.
+
+If you don't do this, expect to see tests randomly fail with mysterious
+FOREGROUND --> FOREGROUND priority transitions.
+
+What's happening in this case is that your FOREGROUND_HIGH process lives until
+the beginning of the next test.  This causes the process started by the next
+test to have low CPU priority.  Then the FOREGROUND_HIGH process dies, because
+its iframe gets GC'ed, and we transition the new test's process from low CPU
+priority to regular CPU priority.
+
+Ouch.
--- a/dom/browser-element/mochitest/priority/Makefile.in
+++ b/dom/browser-element/mochitest/priority/Makefile.in
@@ -23,16 +23,18 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 #       test_WebGLContextLost.html \
 #       file_WebGLContextLost.html \
 
 MOCHITEST_FILES = \
 	test_Simple.html \
 	test_Visibility.html \
 	test_HighPriority.html \
 	file_HighPriority.html \
+	test_HighPriorityDowngrade.html \
+	test_HighPriorityDowngrade2.html \
 	test_Background.html \
 	test_Audio.html \
 	file_Audio.html \
 	silence.ogg \
 	test_MultipleFrames.html \
 	file_MultipleFrames.html \
 	test_Preallocated.html \
 	test_ExpectingSystemMessage.html \
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/priority/test_HighPriorityDowngrade.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that high-priority processes downgrade the CPU priority of regular
+processes.
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="../browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+browserElementTestHelpers.enableProcessPriorityManager();
+SpecialPowers.addPermission("embed-apps", true, document);
+
+var iframe = null;
+var childID = null;
+
+function runTest() {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', true);
+
+  iframe.src = browserElementTestHelpers.emptyPage1;
+
+  var highPriorityIframe = null;
+  var childID = null;
+
+  expectProcessCreated().then(function(chid) {
+    childID = chid;
+    return expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
+  }).then(function() {
+    // Create a new, high-priority iframe.
+    highPriorityIframe = document.createElement('iframe');
+    highPriorityIframe.setAttribute('mozbrowser', true);
+    highPriorityIframe.setAttribute('expecting-system-message', true);
+    highPriorityIframe.setAttribute('mozapptype', 'critical');
+    highPriorityIframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
+    highPriorityIframe.src = browserElementTestHelpers.emptyPage2;
+
+    var p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_LOW');
+
+    document.body.appendChild(highPriorityIframe);
+
+    return p;
+  }).then(function() {
+    return expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
+  }).then(SimpleTest.finish);
+
+  document.body.appendChild(iframe);
+}
+
+addEventListener('testready', function() {
+  // Cause the CPU wake lock taken on behalf of the high-priority process to
+  // time out after 1s.
+  SpecialPowers.pushPrefEnv(
+    {set: [["dom.ipc.systemMessageCPULockTimeoutSec", 1]]},
+    runTest);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/priority/test_HighPriorityDowngrade2.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that high-priority processes downgrade the CPU priority of regular
+processes.
+
+This is just like test_HighPriorityDowngrade, except instead of waiting for the
+high-priority process's wake lock to expire, we kill the process by removing
+its iframe from the DOM.
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="../browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+browserElementTestHelpers.enableProcessPriorityManager();
+SpecialPowers.addPermission("embed-apps", true, document);
+
+function runTest() {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', true);
+
+  iframe.src = browserElementTestHelpers.emptyPage1;
+
+  var highPriorityIframe = null;
+  var childID = null;
+
+  expectProcessCreated().then(function(chid) {
+    childID = chid;
+    return expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
+  }).then(function() {
+    // Create a new, high-priority iframe.
+    highPriorityIframe = document.createElement('iframe');
+    highPriorityIframe.setAttribute('mozbrowser', true);
+    highPriorityIframe.setAttribute('expecting-system-message', true);
+    highPriorityIframe.setAttribute('mozapptype', 'critical');
+    highPriorityIframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
+    highPriorityIframe.src = browserElementTestHelpers.emptyPage2;
+
+    var p = Promise.all(
+      [expectPriorityChange(childID, 'FOREGROUND', 'CPU_LOW'),
+       expectMozbrowserEvent(highPriorityIframe, 'loadend')]
+    );
+
+    document.body.appendChild(highPriorityIframe);
+
+    return p;
+  }).then(function() {
+    // Killing the high-priority iframe should cause our CPU priority to go back
+    // up to regular.
+    var p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
+    document.body.removeChild(highPriorityIframe);
+    return p;
+  }).then(SimpleTest.finish);
+
+  document.body.appendChild(iframe);
+}
+
+addEventListener('testready', function() {
+  // Cause the CPU wake lock taken on behalf of the high-priority process never
+  // to time out during this test.
+  SpecialPowers.pushPrefEnv(
+    {set: [["dom.ipc.systemMessageCPULockTimeoutSec", 1000]]},
+    runTest);
+});
+
+</script>
+</body>
+</html>
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1041,16 +1041,17 @@ nsDOMDeviceStorage::SetRootDirectoryForT
     }
   }
 
   RegisterForSDCardChanges(this);
 #endif
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(this, "file-watcher-update", false);
+  obs->AddObserver(this, "disk-space-watcher", false);
   mRootDirectory = f;
   mStorageType = aType;
 }
 
 JS::Value
 InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID)
 {
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
@@ -2164,16 +2165,17 @@ nsDOMDeviceStorage::Shutdown()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
 #ifdef MOZ_WIDGET_GONK
   UnregisterForSDCardChanges(this);
 #endif
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->RemoveObserver(this, "file-watcher-update");
+  obs->RemoveObserver(this, "disk-space-watcher");
 }
 
 void
 nsDOMDeviceStorage::GetOrderedVolumeNames(const nsAString &aType,
                                           nsTArray<nsString> &aVolumeNames)
 {
 #ifdef MOZ_WIDGET_GONK
   if (DeviceStorageTypeChecker::IsVolumeBased(aType)) {
@@ -2617,16 +2619,28 @@ nsDOMDeviceStorage::Observe(nsISupports 
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!strcmp(aTopic, "file-watcher-update")) {
 
     DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
     Notify(NS_ConvertUTF16toUTF8(aData).get(), file);
     return NS_OK;
+  } else if (!strcmp(aTopic, "disk-space-watcher")) {
+    // 'disk-space-watcher' notifications are sent when there is a modification
+    // of a file in a specific location while a low device storage situation
+    // exists or after recovery of a low storage situation. For Firefox OS,
+    // these notifications are specific for apps storage.
+    nsRefPtr<DeviceStorageFile> file = new DeviceStorageFile(mStorageType);
+    if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "full")) {
+      Notify("low-disk-space", file);
+    } else if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "free")) {
+      Notify("available-disk-space", file);
+    }
+    return NS_OK;
   }
 
 #ifdef MOZ_WIDGET_GONK
   else if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
     nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
     if (!vol) {
       return NS_OK;
     }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1357,17 +1357,17 @@ ContentParent::RecvGetShowPasswordSettin
 
 bool
 ContentParent::RecvFirstIdle()
 {
     // When the ContentChild goes idle, it sends us a FirstIdle message which we
     // use as an indicator that it's a good time to prelaunch another process.
     // If we prelaunch any sooner than this, then we'll be competing with the
     // child process and slowing it down.
-    PreallocatedProcessManager::AllocateOnIdle();
+    PreallocatedProcessManager::AllocateAfterDelay();
     return true;
 }
 
 bool
 ContentParent::RecvAudioChannelGetMuted(const AudioChannelType& aType,
                                         const bool& aElementHidden,
                                         const bool& aElementWasHidden,
                                         bool* aValue)
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -138,16 +138,31 @@ public:
 
   /**
    * If a magic testing-only pref is set, notify the observer service on the
    * given topic with the given data.  This is used for testing
    */
   void FireTestOnlyObserverNotification(const char* aTopic,
                                         const nsACString& aData = EmptyCString());
 
+  /**
+   * Does some process, other than the one handled by aParticularManager, have
+   * priority FOREGROUND_HIGH?
+   */
+  bool OtherProcessHasHighPriority(
+    ParticularProcessPriorityManager* aParticularManager);
+
+  /**
+   * This must be called by a ParticularProcessPriorityManager when it changes
+   * its priority.
+   */
+  void NotifyProcessPriorityChanged(
+    ParticularProcessPriorityManager* aParticularManager,
+    hal::ProcessPriority aOldPriority);
+
 private:
   static bool sPrefListenersRegistered;
   static bool sInitialized;
   static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton;
 
   static int PrefChangedCallback(const char* aPref, void* aClosure);
 
   ProcessPriorityManagerImpl();
@@ -159,16 +174,18 @@ private:
   already_AddRefed<ParticularProcessPriorityManager>
   GetParticularProcessPriorityManager(ContentParent* aContentParent);
 
   void ObserveContentParentCreated(nsISupports* aContentParent);
   void ObserveContentParentDestroyed(nsISupports* aSubject);
 
   nsDataHashtable<nsUint64HashKey, nsRefPtr<ParticularProcessPriorityManager> >
     mParticularManagers;
+
+  nsTHashtable<nsUint64HashKey> mHighPriorityChildIDs;
 };
 
 /**
  * This singleton class implements the parts of the process priority manager
  * that are available from all processes.
  */
 class ProcessPriorityManagerChild MOZ_FINAL
   : public nsIObserver
@@ -197,16 +214,17 @@ private:
 /**
  * This class manages the priority of one particular process.  It is
  * main-process only.
  */
 class ParticularProcessPriorityManager MOZ_FINAL
   : public WakeLockObserver
   , public nsIObserver
   , public nsITimerCallback
+  , public nsSupportsWeakReference
 {
 public:
   ParticularProcessPriorityManager(ContentParent* aContentParent);
   ~ParticularProcessPriorityManager();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
@@ -232,37 +250,49 @@ public:
   bool IsExpectingSystemMessage();
 
   void OnAudioChannelProcessChanged(nsISupports* aSubject);
   void OnRemoteBrowserFrameShown(nsISupports* aSubject);
   void OnTabParentDestroyed(nsISupports* aSubject);
   void OnFrameloaderVisibleChanged(nsISupports* aSubject);
   void OnChannelConnected(nsISupports* aSubject);
 
+  ProcessPriority CurrentPriority();
   ProcessPriority ComputePriority();
+  ProcessCPUPriority ComputeCPUPriority();
+
   void ScheduleResetPriority(const char* aTimeoutPref);
   void ResetPriority();
   void ResetPriorityNow();
+  void ResetCPUPriorityNow();
 
+  /**
+   * This overload is equivalent to SetPriorityNow(aPriority,
+   * ComputeCPUPriority()).
+   */
   void SetPriorityNow(ProcessPriority aPriority);
 
+  void SetPriorityNow(ProcessPriority aPriority,
+                      ProcessCPUPriority aCPUPriority);
+
   void ShutDown();
 
 private:
   void FireTestOnlyObserverNotification(
     const char* aTopic,
     const nsACString& aData = EmptyCString());
 
   void FireTestOnlyObserverNotification(
     const char* aTopic,
     const char* aData = nullptr);
 
   ContentParent* mContentParent;
   uint64_t mChildID;
   ProcessPriority mPriority;
+  ProcessCPUPriority mCPUPriority;
   bool mHoldsCPUWakeLock;
   bool mHoldsHighPriorityWakeLock;
 
   /**
    * Used to implement NameWithComma().
    */
   nsAutoCString mNameWithComma;
 
@@ -337,27 +367,29 @@ ProcessPriorityManagerImpl::GetSingleton
 
   return sSingleton;
 }
 
 ProcessPriorityManagerImpl::ProcessPriorityManagerImpl()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   mParticularManagers.Init();
+  mHighPriorityChildIDs.Init();
 }
 
 void
 ProcessPriorityManagerImpl::Init()
 {
   LOG("Starting up.  This is the master process.");
 
   // The master process's priority never changes; set it here and then forget
   // about it.  We'll manage only subprocesses' priorities using the process
   // priority manager.
-  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
+  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER,
+                          PROCESS_CPU_PRIORITY_NORMAL);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->AddObserver(this, "ipc:content-created", /* ownsWeak */ false);
     os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ false);
   }
 }
 
@@ -413,16 +445,28 @@ ProcessPriorityManagerImpl::ObserveConte
 {
   // Do nothing; it's sufficient to get the PPPM.  But assign to nsRefPtr so we
   // don't leak the already_AddRefed object.
   nsCOMPtr<nsIObserver> cp = do_QueryInterface(aContentParent);
   nsRefPtr<ParticularProcessPriorityManager> pppm =
     GetParticularProcessPriorityManager(static_cast<ContentParent*>(cp.get()));
 }
 
+static PLDHashOperator
+EnumerateParticularProcessPriorityManagers(
+  const uint64_t& aKey,
+  nsRefPtr<ParticularProcessPriorityManager> aValue,
+  void* aUserData)
+{
+  nsTArray<nsRefPtr<ParticularProcessPriorityManager> >* aArray =
+    static_cast<nsTArray<nsRefPtr<ParticularProcessPriorityManager> >*>(aUserData);
+  aArray->AppendElement(aValue);
+  return PL_DHASH_NEXT;
+}
+
 void
 ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject)
 {
   nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
   NS_ENSURE_TRUE_VOID(props);
 
   uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
   props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
@@ -431,45 +475,105 @@ ProcessPriorityManagerImpl::ObserveConte
   nsRefPtr<ParticularProcessPriorityManager> pppm;
   mParticularManagers.Get(childID, &pppm);
   MOZ_ASSERT(pppm);
   if (pppm) {
     pppm->ShutDown();
   }
 
   mParticularManagers.Remove(childID);
+
+  if (mHighPriorityChildIDs.Contains(childID)) {
+    mHighPriorityChildIDs.RemoveEntry(childID);
+
+    // We just lost a high-priority process; reset everyone's CPU priorities.
+    nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
+    mParticularManagers.EnumerateRead(
+      &EnumerateParticularProcessPriorityManagers,
+      &pppms);
+
+    for (uint32_t i = 0; i < pppms.Length(); i++) {
+      pppms[i]->ResetCPUPriorityNow();
+    }
+  }
+}
+
+bool
+ProcessPriorityManagerImpl::OtherProcessHasHighPriority(
+  ParticularProcessPriorityManager* aParticularManager)
+{
+  if (mHighPriorityChildIDs.Contains(aParticularManager->ChildID())) {
+    return mHighPriorityChildIDs.Count() > 1;
+  }
+  return mHighPriorityChildIDs.Count() > 0;
 }
 
-NS_IMPL_ISUPPORTS2(ParticularProcessPriorityManager,
+void
+ProcessPriorityManagerImpl::NotifyProcessPriorityChanged(
+  ParticularProcessPriorityManager* aParticularManager,
+  ProcessPriority aOldPriority)
+{
+  // This priority change can only affect other processes' priorities if we're
+  // changing to/from FOREGROUND_HIGH.
+
+  if (aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH &&
+      aParticularManager->CurrentPriority() <
+        PROCESS_PRIORITY_FOREGROUND_HIGH) {
+
+    return;
+  }
+
+  if (aParticularManager->CurrentPriority() >=
+      PROCESS_PRIORITY_FOREGROUND_HIGH) {
+    mHighPriorityChildIDs.PutEntry(aParticularManager->ChildID());
+  } else {
+    mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID());
+  }
+
+  nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
+  mParticularManagers.EnumerateRead(
+    &EnumerateParticularProcessPriorityManagers,
+    &pppms);
+
+  for (uint32_t i = 0; i < pppms.Length(); i++) {
+    if (pppms[i] != aParticularManager) {
+      pppms[i]->ResetCPUPriorityNow();
+    }
+  }
+}
+
+NS_IMPL_ISUPPORTS3(ParticularProcessPriorityManager,
                    nsIObserver,
-                   nsITimerCallback);
+                   nsITimerCallback,
+                   nsISupportsWeakReference);
 
 ParticularProcessPriorityManager::ParticularProcessPriorityManager(
   ContentParent* aContentParent)
   : mContentParent(aContentParent)
   , mChildID(aContentParent->ChildID())
   , mPriority(PROCESS_PRIORITY_UNKNOWN)
+  , mCPUPriority(PROCESS_CPU_PRIORITY_NORMAL)
   , mHoldsCPUWakeLock(false)
   , mHoldsHighPriorityWakeLock(false)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   LOGP("Creating ParticularProcessPriorityManager.");
 }
 
 void
 ParticularProcessPriorityManager::Init()
 {
   RegisterWakeLockObserver(this);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
-    os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ false);
-    os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ false);
-    os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ false);
-    os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ false);
+    os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true);
+    os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ true);
+    os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
+    os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true);
   }
 
   // This process may already hold the CPU lock; for example, our parent may
   // have acquired it on our behalf.
   WakeLockInformation info1, info2;
   GetWakeLockInfo(NS_LITERAL_STRING("cpu"), &info1);
   mHoldsCPUWakeLock = info1.lockingProcesses().Contains(ChildID());
 
@@ -734,16 +838,22 @@ ParticularProcessPriorityManager::IsExpe
       return true;
     }
   }
 
   return false;
 }
 
 ProcessPriority
+ParticularProcessPriorityManager::CurrentPriority()
+{
+  return mPriority;
+}
+
+ProcessPriority
 ParticularProcessPriorityManager::ComputePriority()
 {
   if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) &&
       HasAppType("critical")) {
     return PROCESS_PRIORITY_FOREGROUND_HIGH;
   }
 
   bool isVisible = false;
@@ -770,52 +880,91 @@ ParticularProcessPriorityManager::Comput
     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
   }
 
   return HasAppType("homescreen") ?
          PROCESS_PRIORITY_BACKGROUND_HOMESCREEN :
          PROCESS_PRIORITY_BACKGROUND;
 }
 
+ProcessCPUPriority
+ParticularProcessPriorityManager::ComputeCPUPriority()
+{
+  if (mPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
+    return PROCESS_CPU_PRIORITY_NORMAL;
+  }
+
+  return ProcessPriorityManagerImpl::GetSingleton()->
+    OtherProcessHasHighPriority(this) ?
+    PROCESS_CPU_PRIORITY_LOW :
+    PROCESS_CPU_PRIORITY_NORMAL;
+}
+
+void
+ParticularProcessPriorityManager::ResetCPUPriorityNow()
+{
+  SetPriorityNow(mPriority);
+}
+
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority)
 {
+  SetPriorityNow(aPriority, ComputeCPUPriority());
+}
+
+void
+ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
+                                                 ProcessCPUPriority aCPUPriority)
+{
   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
     MOZ_ASSERT(false);
     return;
   }
 
-  if (mPriority == aPriority) {
+  if (!mContentParent ||
+      !ProcessPriorityManagerImpl::PrefsEnabled() ||
+      (mPriority == aPriority && mCPUPriority == aCPUPriority)) {
     return;
   }
 
   // If the prefs were disabled after this ParticularProcessPriorityManager was
   // created, we can at least avoid any further calls to
   // hal::SetProcessPriority.  Supporting dynamic enabling/disabling of the
   // ProcessPriorityManager is mostly for testing.
   if (!ProcessPriorityManagerImpl::PrefsEnabled()) {
     return;
   }
 
   LOGP("Changing priority from %s to %s.",
-       ProcessPriorityToString(mPriority),
-       ProcessPriorityToString(aPriority));
+       ProcessPriorityToString(mPriority, mCPUPriority),
+       ProcessPriorityToString(aPriority, aCPUPriority));
+
+  ProcessPriority oldPriority = mPriority;
+
   mPriority = aPriority;
-  hal::SetProcessPriority(Pid(), mPriority);
+  mCPUPriority = aCPUPriority;
+  hal::SetProcessPriority(Pid(), mPriority, mCPUPriority);
 
-  unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
+  if (oldPriority != mPriority) {
+    unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
+  }
 
   if (aPriority >= PROCESS_PRIORITY_FOREGROUND) {
     unused << mContentParent->SendCancelMinimizeMemoryUsage();
   } else {
     unused << mContentParent->SendMinimizeMemoryUsage();
   }
 
   FireTestOnlyObserverNotification("process-priority-set",
-                                   ProcessPriorityToString(mPriority));
+    ProcessPriorityToString(mPriority, mCPUPriority));
+
+  if (oldPriority != mPriority) {
+    ProcessPriorityManagerImpl::GetSingleton()->
+      NotifyProcessPriorityChanged(this, oldPriority);
+  }
 }
 
 void
 ParticularProcessPriorityManager::ShutDown()
 {
   MOZ_ASSERT(mContentParent);
 
   UnregisterWakeLockObserver(this);
--- a/dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
+++ b/dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
@@ -8,17 +8,17 @@ dictionary SmsThreadListItem
 {
   unsigned long long id;
   DOMString senderOrReceiver;
   unsigned long long timestamp;
   DOMString body;
   unsigned long long unreadCount;
 };
 
-[scriptable, builtinclass, uuid(5e993cfc-fb34-46a8-bb14-3df5c09ff748)]
+[scriptable, builtinclass, uuid(6e20c451-8bae-4b36-be3c-da166fdd10ba)]
 interface nsIMobileMessageCallback : nsISupports
 {
   /**
    * All SMS related errors.
    * Make sure to keep this list in sync with the list in:
    * mobile/android/GeckoSmsManager.java
    */
   const unsigned short SUCCESS_NO_ERROR = 0;
@@ -34,14 +34,15 @@ interface nsIMobileMessageCallback : nsI
   void notifySendMessageFailed(in long error);
 
   /**
    * |message| can be nsIDOMMoz{Mms,Sms}Message.
    */
   void notifyMessageGot(in nsISupports message);
   void notifyGetMessageFailed(in long error);
 
-  void notifyMessageDeleted(in boolean deleted);
+  void notifyMessageDeleted([array, size_is(count)] in boolean deleted,
+                            in uint32_t count);
   void notifyDeleteMessageFailed(in long error);
 
   void notifyMessageMarkedRead(in boolean read);
   void notifyMarkMessageReadFailed(in long error);
 };
--- a/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
@@ -11,24 +11,25 @@
 #define MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID "@mozilla.org/mobilemessage/mobilemessagedatabaseservice;1"
 %}
 
 interface nsICursorContinueCallback;
 interface nsIDOMMozSmsFilter;
 interface nsIMobileMessageCallback;
 interface nsIMobileMessageCursorCallback;
 
-[scriptable, uuid(ec1ca45f-e621-4c67-9c50-74c16842e780)]
+[scriptable, uuid(ea6f49ae-3a4c-47eb-a489-15578e634100)]
 interface nsIMobileMessageDatabaseService : nsISupports
 {
   [binaryname(GetMessageMoz)]
   void getMessage(in long messageId,
                   in nsIMobileMessageCallback request);
 
-  void deleteMessage(in long messageId,
+  void deleteMessage([array, size_is(count)] in long messageIds,
+                     in uint32_t count,
                      in nsIMobileMessageCallback request);
 
   nsICursorContinueCallback createMessageCursor(in nsIDOMMozSmsFilter filter,
                                                 in boolean reverse,
                                                 in nsIMobileMessageCursorCallback callback);
 
   void markMessageRead(in long messageId,
                        in boolean value,
--- a/dom/mobilemessage/src/MobileMessageCallback.cpp
+++ b/dom/mobilemessage/src/MobileMessageCallback.cpp
@@ -8,16 +8,17 @@
 #include "nsIDOMMozSmsMessage.h"
 #include "nsIDOMMozMmsMessage.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsPIDOMWindow.h"
 #include "MmsMessage.h"
 #include "jsapi.h"
 #include "xpcpublic.h"
 #include "nsServiceManagerUtils.h"
+#include "nsTArrayHelpers.h"
 
 namespace mozilla {
 namespace dom {
 namespace mobilemessage {
 
 NS_IMPL_ADDREF(MobileMessageCallback)
 NS_IMPL_RELEASE(MobileMessageCallback)
 
@@ -112,19 +113,39 @@ MobileMessageCallback::NotifyMessageGot(
 
 NS_IMETHODIMP
 MobileMessageCallback::NotifyGetMessageFailed(int32_t aError)
 {
   return NotifyError(aError);
 }
 
 NS_IMETHODIMP
-MobileMessageCallback::NotifyMessageDeleted(bool aDeleted)
+MobileMessageCallback::NotifyMessageDeleted(bool *aDeleted, uint32_t aSize)
 {
-  return NotifySuccess(aDeleted ? JSVAL_TRUE : JSVAL_FALSE);
+  if (aSize == 1) {
+    return NotifySuccess(aDeleted[0] ? JSVAL_TRUE : JSVAL_FALSE);
+  }
+
+  nsresult rv;
+  nsIScriptContext* sc = mDOMRequest->GetContextForEventHandlers(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(sc, NS_ERROR_FAILURE);
+
+  AutoPushJSContext cx(sc->GetNativeContext());
+  NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
+
+  JSObject *deleteArrayObj = JS_NewArrayObject(cx, aSize, NULL);
+  JS::Value jsValTrue = BOOLEAN_TO_JSVAL(1);
+  JS::Value jsValFalse = BOOLEAN_TO_JSVAL(0);
+  for (uint32_t i = 0; i < aSize; i++) {
+    JS_SetElement(cx, deleteArrayObj, i,
+                  aDeleted[i] ? &jsValTrue : &jsValFalse);
+  }
+
+  return NotifySuccess(OBJECT_TO_JSVAL(deleteArrayObj));
 }
 
 NS_IMETHODIMP
 MobileMessageCallback::NotifyDeleteMessageFailed(int32_t aError)
 {
   return NotifyError(aError);
 }
 
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -208,63 +208,102 @@ MobileMessageManager::GetMessageMoz(int3
   nsresult rv = mobileMessageDBService->GetMessageMoz(aId, msgCallback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   request.forget(aRequest);
   return NS_OK;
 }
 
 nsresult
-MobileMessageManager::Delete(int32_t aId, nsIDOMDOMRequest** aRequest)
+MobileMessageManager::GetMessageId(AutoPushJSContext &aCx,
+                                   const JS::Value &aMessage, int32_t &aId)
 {
-  nsCOMPtr<nsIMobileMessageDatabaseService> mobileMessageDBService =
-    do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(mobileMessageDBService, NS_ERROR_FAILURE);
+  nsCOMPtr<nsIDOMMozSmsMessage> smsMessage =
+    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &aMessage.toObject()));
+  if (smsMessage) {
+    return smsMessage->GetId(&aId);
+  }
 
-  nsRefPtr<DOMRequest> request = new DOMRequest(GetOwner());
-  nsCOMPtr<nsIMobileMessageCallback> msgCallback = new MobileMessageCallback(request);
-  nsresult rv = mobileMessageDBService->DeleteMessage(aId, msgCallback);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMMozMmsMessage> mmsMessage =
+    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &aMessage.toObject()));
+  if (mmsMessage) {
+    return mmsMessage->GetId(&aId);
+  }
 
-  request.forget(aRequest);
-  return NS_OK;
+  return NS_ERROR_INVALID_ARG;
 }
 
 NS_IMETHODIMP
 MobileMessageManager::Delete(const JS::Value& aParam, nsIDOMDOMRequest** aRequest)
 {
-  if (aParam.isInt32()) {
-    return Delete(aParam.toInt32(), aRequest);
-  }
-
-  if (!aParam.isObject()) {
+  // We expect Int32, SmsMessage, MmsMessage, Int32[], SmsMessage[], MmsMessage[]
+  if (!aParam.isObject() && !aParam.isInt32()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
   nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   AutoPushJSContext cx(sc->GetNativeContext());
   NS_ENSURE_STATE(sc);
 
-  int32_t id;
-  nsCOMPtr<nsIDOMMozSmsMessage> smsMessage =
-    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, &aParam.toObject()));
-  if (smsMessage) {
-    smsMessage->GetId(&id);
+  int32_t id, *idArray;
+  uint32_t size;
+
+  if (aParam.isInt32()) {
+    // Single Integer Message ID
+    id = aParam.toInt32();
+
+    size = 1;
+    idArray = &id;
+  } else if (!JS_IsArrayObject(cx, &aParam.toObject())) {
+    // Single SmsMessage/MmsMessage object
+    rv = GetMessageId(cx, aParam, id);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    size = 1;
+    idArray = &id;
   } else {
-    nsCOMPtr<nsIDOMMozMmsMessage> mmsMessage =
-      do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, &aParam.toObject()));
-    if (mmsMessage) {
-      mmsMessage->GetId(&id);
-    } else {
-      return NS_ERROR_INVALID_ARG;
+    // Int32[], SmsMessage[], or MmsMessage[]
+    JSObject& ids = aParam.toObject();
+
+    JS_ALWAYS_TRUE(JS_GetArrayLength(cx, &ids, &size));
+    nsAutoArrayPtr<int32_t> idAutoArray(new int32_t[size]);
+
+    JS::Value idJsValue;
+    for (uint32_t i = 0; i < size; i++) {
+      if (!JS_GetElement(cx, &ids, i, &idJsValue)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      if (idJsValue.isInt32()) {
+        idAutoArray[i] = idJsValue.toInt32();
+      } else if (idJsValue.isObject()) {
+        rv = GetMessageId(cx, idJsValue, id);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        idAutoArray[i] = id;
+      }
     }
+
+    idArray = idAutoArray.forget();
   }
 
-  return Delete(id, aRequest);
+  nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
+    do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(dbService, NS_ERROR_FAILURE);
+
+  nsRefPtr<DOMRequest> request = new DOMRequest(GetOwner());
+  nsCOMPtr<nsIMobileMessageCallback> msgCallback =
+    new MobileMessageCallback(request);
+
+  rv = dbService->DeleteMessage(idArray, size, msgCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  request.forget(aRequest);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
                                   bool aReverse,
                                   nsIDOMDOMCursor** aCursor)
 {
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
--- a/dom/mobilemessage/src/MobileMessageManager.h
+++ b/dom/mobilemessage/src/MobileMessageManager.h
@@ -32,22 +32,23 @@ public:
 
 private:
   /**
    * Internal Send() method used to send one message.
    */
   nsresult Send(JSContext* aCx, JSObject* aGlobal, JSString* aNumber,
                 const nsAString& aMessage, JS::Value* aRequest);
 
-  /**
-   * Internal Delete() method used to delete a message.
-   */
-  nsresult Delete(int32_t aId, nsIDOMDOMRequest** aRequest);
-
   nsresult DispatchTrustedSmsEventToSelf(const char* aTopic,
                                          const nsAString& aEventName,
                                          nsISupports* aMsg);
+
+  /**
+   * Helper to get message ID from SMS/MMS Message object
+   */
+  nsresult GetMessageId(AutoPushJSContext &aCx, const JS::Value &aMessage,
+                        int32_t &aId);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mobilemessage_MobileMessageManager_h
--- a/dom/mobilemessage/src/SmsManager.cpp
+++ b/dom/mobilemessage/src/SmsManager.cpp
@@ -249,59 +249,97 @@ SmsManager::GetMessageMoz(int32_t aId, n
   nsresult rv = dbService->GetMessageMoz(aId, msgCallback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   request.forget(aRequest);
   return NS_OK;
 }
 
 nsresult
-SmsManager::Delete(int32_t aId, nsIDOMDOMRequest** aRequest)
+SmsManager::GetSmsMessageId(AutoPushJSContext &aCx,
+                            const JS::Value &aSmsMessage, int32_t &aId)
+{
+  nsCOMPtr<nsIDOMMozSmsMessage> message =
+    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &aSmsMessage.toObject()));
+  NS_ENSURE_TRUE(message, NS_ERROR_INVALID_ARG);
+
+  return message->GetId(&aId);
+}
+
+NS_IMETHODIMP
+SmsManager::Delete(const JS::Value& aParam, nsIDOMDOMRequest** aRequest)
 {
+  // We expect Int32, SmsMessage, Int32[], SmsMessage[]
+  if (!aParam.isObject() && !aParam.isInt32()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv;
+  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  AutoPushJSContext cx(sc->GetNativeContext());
+  NS_ENSURE_STATE(sc);
+
+  int32_t id, *idArray;
+  uint32_t size;
+
+  if (aParam.isInt32()) {
+    // Single Integer Message ID
+    id = aParam.toInt32();
+
+    size = 1;
+    idArray = &id;
+  } else if (!JS_IsArrayObject(cx, &aParam.toObject())) {
+    // Single SmsMessage object
+    rv = GetSmsMessageId(cx, aParam, id);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    size = 1;
+    idArray = &id;
+  } else {
+    // Int32[] or SmsMessage[]
+    JSObject& ids = aParam.toObject();
+
+    JS_ALWAYS_TRUE(JS_GetArrayLength(cx, &ids, &size));
+    nsAutoArrayPtr<int32_t> idAutoArray(new int32_t[size]);
+
+    JS::Value idJsValue;
+    for (uint32_t i = 0; i < size; i++) {
+      if (!JS_GetElement(cx, &ids, i, &idJsValue)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      if (idJsValue.isInt32()) {
+        idAutoArray[i] = idJsValue.toInt32();
+      } else if (idJsValue.isObject()) {
+        rv = GetSmsMessageId(cx, idJsValue, id);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        idAutoArray[i] = id;
+      }
+    }
+
+    idArray = idAutoArray.forget();
+  }
+
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
     do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(dbService, NS_ERROR_FAILURE);
 
   nsRefPtr<DOMRequest> request = new DOMRequest(GetOwner());
   nsCOMPtr<nsIMobileMessageCallback> msgCallback =
     new MobileMessageCallback(request);
 
-  nsresult rv = dbService->DeleteMessage(aId, msgCallback);
+  rv = dbService->DeleteMessage(idArray, size, msgCallback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   request.forget(aRequest);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsManager::Delete(const JS::Value& aParam, nsIDOMDOMRequest** aRequest)
-{
-  if (aParam.isInt32()) {
-    return Delete(aParam.toInt32(), aRequest);
-  }
-
-  if (!aParam.isObject()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  nsresult rv;
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
-  AutoPushJSContext cx(sc->GetNativeContext());
-  NS_ENSURE_STATE(sc);
-  nsCOMPtr<nsIDOMMozSmsMessage> message =
-    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, &aParam.toObject()));
-  NS_ENSURE_TRUE(message, NS_ERROR_INVALID_ARG);
-
-  int32_t id;
-  message->GetId(&id);
-
-  return Delete(id, aRequest);
-}
-
-NS_IMETHODIMP
 SmsManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
                         bool aReverse,
                         nsIDOMDOMCursor** aCursor)
 {
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
     do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(dbService, NS_ERROR_FAILURE);
 
--- a/dom/mobilemessage/src/SmsManager.h
+++ b/dom/mobilemessage/src/SmsManager.h
@@ -34,21 +34,22 @@ public:
 
 private:
   /**
    * Internal Send() method used to send one message.
    */
   nsresult Send(JSContext* aCx, JSObject* aGlobal, JS::Handle<JSString*> aNumber,
                 const nsAString& aMessage, JS::Value* aRequest);
 
-  /**
-   * Internal Delete() method used to delete a message.
-   */
-  nsresult Delete(int32_t aId, nsIDOMDOMRequest** aRequest);
-
   nsresult DispatchTrustedSmsEventToSelf(const nsAString& aEventName,
                                          nsIDOMMozSmsMessage* aMessage);
+
+  /**
+   * Helper to get message ID from SMS Message object
+   */
+  nsresult GetSmsMessageId(AutoPushJSContext &aCx, const JS::Value &aSmsMessage,
+                           int32_t &aId);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mobilemessage_SmsManager_h
--- a/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
+++ b/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
@@ -21,24 +21,33 @@ MobileMessageDatabaseService::GetMessage
     return NS_OK;
   }
 
   AndroidBridge::Bridge()->GetMessage(aMessageId, aRequest);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MobileMessageDatabaseService::DeleteMessage(int32_t aMessageId,
+MobileMessageDatabaseService::DeleteMessage(int32_t *aMessageIds,
+                                            uint32_t aLength,
                                             nsIMobileMessageCallback* aRequest)
 {
   if (!AndroidBridge::Bridge()) {
     return NS_OK;
   }
 
-  AndroidBridge::Bridge()->DeleteMessage(aMessageId, aRequest);
+  if (!aMessageIds) {
+    return NS_OK;
+  }
+
+  if (aLength != 1) {
+    return NS_ERROR_FAILURE;
+  }
+
+  AndroidBridge::Bridge()->DeleteMessage(aMessageIds[0], aRequest);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageDatabaseService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
                                                   bool aReverse,
                                                   nsIMobileMessageCursorCallback* aCallback,
                                                   nsICursorContinueCallback** aResult)
--- a/dom/mobilemessage/src/fallback/MobileMessageDatabaseService.cpp
+++ b/dom/mobilemessage/src/fallback/MobileMessageDatabaseService.cpp
@@ -15,17 +15,18 @@ NS_IMETHODIMP
 MobileMessageDatabaseService::GetMessageMoz(int32_t aMessageId,
                                             nsIMobileMessageCallback* aRequest)
 {
   NS_ERROR("We should not be here!");
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MobileMessageDatabaseService::DeleteMessage(int32_t aMessageId,
+MobileMessageDatabaseService::DeleteMessage(int32_t *aMessageIds,
+                                            uint32_t aLength,
                                             nsIMobileMessageCallback* aRequest)
 {
   NS_ERROR("We should not be here!");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageDatabaseService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
--- a/dom/mobilemessage/src/ipc/PSms.ipdl
+++ b/dom/mobilemessage/src/ipc/PSms.ipdl
@@ -41,17 +41,17 @@ struct RetrieveMessageRequest
 
 struct GetMessageRequest
 {
   int32_t messageId;
 };
 
 struct DeleteMessageRequest
 {
-  int32_t messageId;
+  int32_t[] messageIds;
 };
 
 struct CreateMessageCursorRequest
 {
   SmsFilterData filter;
   bool reverse;
 };
 
--- a/dom/mobilemessage/src/ipc/PSmsRequest.ipdl
+++ b/dom/mobilemessage/src/ipc/PSmsRequest.ipdl
@@ -43,17 +43,17 @@ struct ReplyGetMessage
 
 struct ReplyGetMessageFail
 {
   int32_t error;
 };
 
 struct ReplyMessageDelete
 {
-  bool deleted;
+  bool[] deleted;
 };
 
 struct ReplyMessageDeleteFail
 {
   int32_t error;
 };
 
 struct ReplyMarkeMessageRead
--- a/dom/mobilemessage/src/ipc/SmsChild.cpp
+++ b/dom/mobilemessage/src/ipc/SmsChild.cpp
@@ -172,21 +172,24 @@ SmsRequestChild::Recv__delete__(const Me
           aReply.get_ReplyGetMessage().messageData();
         nsCOMPtr<nsISupports> msg = CreateMessageFromMessageData(data);
         mReplyRequest->NotifyMessageGot(msg);
       }
       break;
     case MessageReply::TReplyGetMessageFail:
       mReplyRequest->NotifyGetMessageFailed(aReply.get_ReplyGetMessageFail().error());
       break;
-    case MessageReply::TReplyMessageDelete:
-      mReplyRequest->NotifyMessageDeleted(aReply.get_ReplyMessageDelete().deleted());
+    case MessageReply::TReplyMessageDelete: {
+        const InfallibleTArray<bool>& deletedResult = aReply.get_ReplyMessageDelete().deleted();
+        mReplyRequest->NotifyMessageDeleted(const_cast<bool *>(deletedResult.Elements()),
+                                            deletedResult.Length());
+      }
       break;
     case MessageReply::TReplyMessageDeleteFail:
-      mReplyRequest->NotifyMessageDeleted(aReply.get_ReplyMessageDeleteFail().error());
+      mReplyRequest->NotifyDeleteMessageFailed(aReply.get_ReplyMessageDeleteFail().error());
       break;
     case MessageReply::TReplyMarkeMessageRead:
       mReplyRequest->NotifyMessageMarkedRead(aReply.get_ReplyMarkeMessageRead().read());
       break;
     case MessageReply::TReplyMarkeMessageReadFail:
       mReplyRequest->NotifyMarkMessageReadFailed(aReply.get_ReplyMarkeMessageReadFail().error());
       break;
     default:
--- a/dom/mobilemessage/src/ipc/SmsIPCService.cpp
+++ b/dom/mobilemessage/src/ipc/SmsIPCService.cpp
@@ -124,20 +124,22 @@ SmsIPCService::Send(const nsAString& aNu
 NS_IMETHODIMP
 SmsIPCService::GetMessageMoz(int32_t aMessageId,
                              nsIMobileMessageCallback* aRequest)
 {
   return SendRequest(GetMessageRequest(aMessageId), aRequest);
 }
 
 NS_IMETHODIMP
-SmsIPCService::DeleteMessage(int32_t aMessageId,
+SmsIPCService::DeleteMessage(int32_t *aMessageIds, uint32_t aSize,
                              nsIMobileMessageCallback* aRequest)
 {
-  return SendRequest(DeleteMessageRequest(aMessageId), aRequest);
+  DeleteMessageRequest data;
+  data.messageIds().AppendElements(aMessageIds, aSize);
+  return SendRequest(data, aRequest);
 }
 
 NS_IMETHODIMP
 SmsIPCService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
                                    bool aReverse,
                                    nsIMobileMessageCursorCallback* aCursorCallback,
                                    nsICursorContinueCallback** aResult)
 {
--- a/dom/mobilemessage/src/ipc/SmsParent.cpp
+++ b/dom/mobilemessage/src/ipc/SmsParent.cpp
@@ -477,17 +477,19 @@ SmsRequestParent::DoRequest(const GetMes
 bool
 SmsRequestParent::DoRequest(const DeleteMessageRequest& aRequest)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
     do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
   if (dbService) {
-    rv = dbService->DeleteMessage(aRequest.messageId(), this);
+    const InfallibleTArray<int32_t>& messageIds = aRequest.messageIds();
+    rv = dbService->DeleteMessage(const_cast<int32_t *>(messageIds.Elements()),
+                                  messageIds.Length(), this);
   }
 
   if (NS_FAILED(rv)) {
     return NS_SUCCEEDED(NotifyDeleteMessageFailed(nsIMobileMessageCallback::INTERNAL_ERROR));
   }
 
   return true;
 }
@@ -578,19 +580,21 @@ SmsRequestParent::NotifyMessageGot(nsISu
 
 NS_IMETHODIMP
 SmsRequestParent::NotifyGetMessageFailed(int32_t aError)
 {
   return SendReply(ReplyGetMessageFail(aError));
 }
 
 NS_IMETHODIMP
-SmsRequestParent::NotifyMessageDeleted(bool aDeleted)
+SmsRequestParent::NotifyMessageDeleted(bool *aDeleted, uint32_t aSize)
 {
-  return SendReply(ReplyMessageDelete(aDeleted));
+  ReplyMessageDelete data;
+  data.deleted().AppendElements(aDeleted, aSize);
+  return SendReply(data);
 }
 
 NS_IMETHODIMP
 SmsRequestParent::NotifyDeleteMessageFailed(int32_t aError)
 {
   return SendReply(ReplyMessageDeleteFail(aError));
 }
 
--- a/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
@@ -1412,63 +1412,66 @@ MobileMessageDatabaseService.prototype =
                 (threadRecord.unreadCount + 1) + " -> " +
                 threadRecord.unreadCount);
         }
         threadStore.put(threadRecord);
       }
     };
   },
 
-  deleteMessage: function deleteMessage(messageId, aRequest) {
-    if (DEBUG) debug("deleteMessage: message id " + messageId);
-    let deleted = false;
+  deleteMessage: function deleteMessage(messageIds, length, aRequest) {
+    if (DEBUG) debug("deleteMessage: message ids " + JSON.stringify(messageIds));
+    let deleted = [];
     let self = this;
     this.newTxn(READ_WRITE, function (error, txn, stores) {
       if (error) {
         if (DEBUG) debug("deleteMessage: failed to open transaction");
         aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       txn.onerror = function onerror(event) {
         if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
         //TODO look at event.target.errorCode, pick appropriate error constant
         aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
       };
 
       const messageStore = stores[0];
       const threadStore = stores[1];
 
-      let deleted = false;
-
       txn.oncomplete = function oncomplete(event) {
         if (DEBUG) debug("Transaction " + txn + " completed.");
-        aRequest.notifyMessageDeleted(deleted);
+        aRequest.notifyMessageDeleted(deleted, length);
       };
 
-      messageStore.get(messageId).onsuccess = function(event) {
-        let messageRecord = event.target.result;
-        if (messageRecord) {
-          if (DEBUG) debug("Deleting message id " + messageId);
-
-          // First actually delete the message.
-          messageStore.delete(messageId).onsuccess = function(event) {
-            if (DEBUG) debug("Message id " + messageId + " deleted");
-            deleted = true;
+      for (let i = 0; i < length; i++) {
+        let messageId = messageIds[i];
+        deleted[i] = false;
+        messageStore.get(messageId).onsuccess = function(messageIndex, event) {
+          let messageRecord = event.target.result;
+          let messageId = messageIds[messageIndex];
+          if (messageRecord) {
+            if (DEBUG) debug("Deleting message id " + messageId);
 
-            // Then update unread count and most recent message.
-            self.updateThreadByMessageChange(messageStore,
-                                             threadStore,
-                                             messageRecord.threadId,
-                                             messageId,
-                                             messageRecord.read);
-          };
-        } else if (DEBUG) {
-          debug("Message id " + messageId + " does not exist");
-        }
-      };
+            // First actually delete the message.
+            messageStore.delete(messageId).onsuccess = function(event) {
+              if (DEBUG) debug("Message id " + messageId + " deleted");
+              deleted[messageIndex] = true;
+
+              // Then update unread count and most recent message.
+              self.updateThreadByMessageChange(messageStore,
+                                               threadStore,
+                                               messageRecord.threadId,
+                                               messageId,
+                                               messageRecord.read);
+              };
+          } else if (DEBUG) {
+            debug("Message id " + messageId + " does not exist");
+          }
+        }.bind(null, i);
+      }
     }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]);
   },
 
   createMessageCursor: function createMessageCursor(filter, reverse, callback) {
     if (DEBUG) {
       debug("Creating a message cursor. Filters:" +
             " startDate: " + filter.startDate +
             " endDate: " + filter.endDate +
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -26,8 +26,9 @@ qemu = true
 [test_segment_info.js]
 [test_mark_msg_read.js]
 [test_mark_msg_read_error.js]
 [test_bug814761.js]
 [test_strict_7bit_encoding.js]
 [test_incoming_max_segments.js]
 [test_outgoing_max_segments.js]
 [test_update_thread_record_in_delete.js]
+[test_massive_incoming_delete.js]
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_massive_incoming_delete.js
@@ -0,0 +1,216 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+
+SpecialPowers.setBoolPref("dom.sms.enabled", true);
+SpecialPowers.addPermission("sms", true, document);
+
+const SENDER = "5555552368"; // the remote number
+const RECEIVER = "15555215554"; // the emulator's number
+
+let sms = window.navigator.mozMobileMessage;
+let MSG_TEXT = "Mozilla Firefox OS!";
+let SMS_NUMBER = 100;
+
+let SmsList = [];
+let checkDone = true;
+let emulatorReady = true;
+
+let pendingEmulatorCmdCount = 0;
+function sendSmsToEmulator(from, text) {
+  ++pendingEmulatorCmdCount;
+
+  let cmd = "sms send " + from + " " + text;
+  runEmulatorCmd(cmd, function (result) {
+    --pendingEmulatorCmdCount;
+
+    is(result[0], "OK", "Emulator response");
+  });
+}
+
+let tasks = {
+  // List of test fuctions. Each of them should call |tasks.next()| when
+  // completed or |tasks.finish()| to jump to the last one.
+  _tasks: [],
+  _nextTaskIndex: 0,
+
+  push: function push(func) {
+    this._tasks.push(func);
+  },
+
+  next: function next() {
+    let index = this._nextTaskIndex++;
+    let task = this._tasks[index];
+    try {
+      task();
+    } catch (ex) {
+      ok(false, "test task[" + index + "] throws: " + ex);
+      // Run last task as clean up if possible.
+      if (index != this._tasks.length - 1) {
+        this.finish();
+      }
+    }
+  },
+
+  finish: function finish() {
+    this._tasks[this._tasks.length - 1]();
+  },
+
+  run: function run() {
+    this.next();
+  }
+};
+
+function taskNextWrapper() {
+  tasks.next();
+}
+
+function verifySmsExists(incomingSms) {
+  log("Getting SMS (id: " + incomingSms.id + ").");
+  let requestRet = sms.getMessage(incomingSms.id);
+  ok(requestRet, "smsrequest obj returned");
+
+  requestRet.onsuccess = function(event) {
+    log("Received 'onsuccess' smsrequest event.");
+    ok(event.target.result, "smsrequest event.target.result");
+    let foundSms = event.target.result;
+    is(foundSms.id, incomingSms.id, "found SMS id matches");
+    is(foundSms.threadId, incomingSms.threadId, "found SMS thread id matches");
+    is(foundSms.body, MSG_TEXT, "found SMS msg text matches");
+    is(foundSms.delivery, "received", "delivery");
+    is(foundSms.deliveryStatus, "success", "deliveryStatus");
+    is(foundSms.read, false, "read");
+    is(foundSms.receiver, RECEIVER, "receiver");
+    is(foundSms.sender, SENDER, "sender");
+    is(foundSms.messageClass, "normal", "messageClass");
+    log("Got SMS (id: " + foundSms.id + ") as expected.");
+
+    SmsList.push(incomingSms);
+  };
+
+  requestRet.onerror = function(event) {
+    log("Received 'onerror' smsrequest event.");
+    ok(event.target.error, "domerror obj");
+    is(event.target.error.name, "NotFoundError", "error returned");
+    log("Could not get SMS (id: " + incomingSms.id + ") but should have.");
+    ok(false,"SMS was not found");
+    tasks.finish();
+  };
+}
+
+let verifDeletedCount = 0;
+function verifySmsDeleted(smsId) {
+  log("Getting SMS (id: " + smsId + ").");
+  let requestRet = sms.getMessage(smsId);
+  ok(requestRet, "smsrequest obj returned");
+
+  requestRet.onsuccess = function(event) {
+    log("Received 'onsuccess' smsrequest event.");
+    ok(event.target.result, "smsrequest event.target.result");
+    let foundSms = event.target.result;
+    is(foundSms.id, smsId, "found SMS id matches");
+    is(foundSms.body, MSG_TEXT, "found SMS msg text matches");
+    log("Got SMS (id: " + foundSms.id + ") but should not have.");
+    ok(false, "SMS was not deleted");
+    tasks.finish();
+  };
+
+  requestRet.onerror = function(event) {
+    log("Received 'onerror' smsrequest event.");
+    ok(event.target.error, "domerror obj");
+    is(event.target.error.name, "NotFoundError", "error returned");
+    log("Could not get SMS (id: " + smsId + ") as expected.");
+    verifDeletedCount++;
+  };
+}
+
+tasks.push(function init() {
+  log("Initialize test object.");
+  ok(sms, "mozSms");
+
+  // Callback for incoming sms
+  sms.onreceived = function onreceived(event) {
+    log("Received 'onreceived' smsmanager event.");
+    let incomingSms = event.message;
+    ok(incomingSms, "incoming sms");
+    ok(incomingSms.id, "sms id");
+    log("Received SMS (id: " + incomingSms.id + ").");
+    ok(incomingSms.threadId, "thread id");
+    is(incomingSms.body, MSG_TEXT, "msg body");
+    is(incomingSms.delivery, "received", "delivery");
+    is(incomingSms.deliveryStatus, "success", "deliveryStatus");
+    is(incomingSms.read, false, "read");
+    is(incomingSms.receiver, RECEIVER, "receiver");
+    is(incomingSms.sender, SENDER, "sender");
+    is(incomingSms.messageClass, "normal", "messageClass");
+    ok(incomingSms.timestamp instanceof Date, "timestamp is istanceof date");
+
+    verifySmsExists(incomingSms);
+  };
+
+  tasks.next();
+});
+
+tasks.push(function sendAllSms() {
+  log("Send " + SMS_NUMBER + " SMS");
+  for (let i = 0; i < SMS_NUMBER; i++) {
+    sendSmsToEmulator(SENDER, MSG_TEXT);
+  }
+
+  waitFor(taskNextWrapper, function() {
+    return (pendingEmulatorCmdCount === 0) && (SmsList.length === SMS_NUMBER);
+  });
+});
+
+tasks.push(function deleteAllSms() {
+  log("Deleting SMS using smsmsg obj array parameter.");
+  let deleteStart = Date.now();
+  log("deleteStart: " + deleteStart);
+  log("SmsList: " + JSON.stringify(SmsList));
+  let requestRet = sms.delete(SmsList);
+  ok(requestRet,"smsrequest obj returned");
+
+  requestRet.onsuccess = function(event) {
+    let deleteDone = Date.now();
+    log("Delete " + SMS_NUMBER + " SMS takes " + (deleteDone - deleteStart) + " ms.");
+    log("Received 'onsuccess' smsrequest event.");
+    if (event.target.result) {
+      for (let i = 0; i < SmsList.length; i++) {
+        verifySmsDeleted(SmsList[i].id);
+      }
+    } else {
+      log("smsrequest returned false for sms.delete");
+      ok(false, "SMS delete failed");
+    }
+  };
+
+  requestRet.onerror = function(event) {
+    log("Received 'onerror' smsrequest event.");
+    ok(event.target.error, "domerror obj");
+    ok(false, "sms.delete request returned unexpected error: "
+        + event.target.error.name);
+    tasks.finish();
+  };
+
+  waitFor(taskNextWrapper, function() {
+    return verifDeletedCount === SMS_NUMBER;
+  });
+});
+
+// WARNING: All tasks should be pushed before this!!!
+tasks.push(function cleanUp() {
+  if (pendingEmulatorCmdCount) {
+    window.setTimeout(cleanUp, 100);
+    return;
+  }
+
+  sms.onreceived = null;
+  SpecialPowers.removePermission("sms", document);
+  SpecialPowers.setBoolPref("dom.sms.enabled", false);
+  log("Finish!!!");
+  finish();
+});
+
+// Start the test
+tasks.run();
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -1084,17 +1084,18 @@ RILContentHelper.prototype = {
       case "RIL:SelectNetworkAuto":
         this.handleSelectNetwork(msg.json,
                                  RIL.GECKO_NETWORK_SELECTION_AUTOMATIC);
         break;
       case "RIL:CallStateChanged":
         this._deliverEvent("_telephonyListeners",
                            "callStateChanged",
                            [msg.json.callIndex, msg.json.state,
-                            msg.json.number, msg.json.isActive]);
+                            msg.json.number, msg.json.isActive,
+                            msg.json.isOutgoing]);
         break;
       case "RIL:CallError":
         this._deliverEvent("_telephonyListeners",
                            "notifyError",
                            [msg.json.callIndex, msg.json.errorMsg]);
         break;
       case "RIL:VoicemailNotification":
         this.handleVoicemailNotification(msg.json);
@@ -1193,17 +1194,17 @@ RILContentHelper.prototype = {
     debug("handleEnumerateCalls: " + JSON.stringify(calls));
     let callback = this._enumerateTelephonyCallbacks.shift();
     for (let i in calls) {
       let call = calls[i];
       let keepGoing;
       try {
         keepGoing =
           callback.enumerateCallState(call.callIndex, call.state, call.number,
-                                      call.isActive);
+                                      call.isActive, call.isOutgoing);
       } catch (e) {
         debug("callback handler for 'enumerateCallState' threw an " +
               " exception: " + e);
         keepGoing = true;
       }
       if (!keepGoing) {
         break;
       }
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1357,17 +1357,17 @@ RadioInterfaceLayer.prototype = {
   handleCallDisconnected: function handleCallDisconnected(call) {
     debug("handleCallDisconnected: " + JSON.stringify(call));
     call.state = nsITelephonyProvider.CALL_STATE_DISCONNECTED;
     let duration = ("started" in call && typeof call.started == "number") ?
       new Date().getTime() - call.started : 0;
     let data = {
       number: call.number,
       duration: duration,
-      direction: call.direction
+      direction: call.isOutgoing ? "outgoing" : "incoming"
     };
     gSystemMessenger.broadcastMessage("telephony-call-ended", data);
     this.updateCallAudioState(call);
     this._sendTelephonyMessage("RIL:CallStateChanged", call);
   },
 
   /**
    * Handle calls delivered in response to a 'enumerateCalls' request.
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -3254,19 +3254,19 @@ let RIL = {
       if (newCall.isVoice) {
         // Format international numbers appropriately.
         if (newCall.number &&
             newCall.toa == TOA_INTERNATIONAL &&
             newCall.number[0] != "+") {
           newCall.number = "+" + newCall.number;
         }
         if (newCall.state == CALL_STATE_INCOMING) {
-          newCall.direction = 'incoming';
+          newCall.isOutgoing = false;
         } else if (newCall.state == CALL_STATE_DIALING) {
-          newCall.direction = 'outgoing';
+          newCall.isOutgoing = true;
         }
         // Add to our map.
         this.currentCalls[newCall.callIndex] = newCall;
         this._handleChangedCallState(newCall);
       }
     }
 
     // Update our mute status. If there is anything in our currentCalls map then
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -361,17 +361,18 @@ Telephony::StopTone()
   return NS_OK;
 }
 
 NS_IMPL_EVENT_HANDLER(Telephony, incoming)
 NS_IMPL_EVENT_HANDLER(Telephony, callschanged)
 
 NS_IMETHODIMP
 Telephony::CallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
-                            const nsAString& aNumber, bool aIsActive)
+                            const nsAString& aNumber, bool aIsActive,
+                            bool aIsOutgoing)
 {
   NS_ASSERTION(aCallIndex != kOutgoingPlaceholderCallIndex,
                "This should never happen!");
 
   nsRefPtr<TelephonyCall> modifiedCall;
   nsRefPtr<TelephonyCall> outgoingCall;
 
   for (uint32_t index = 0; index < mCalls.Length(); index++) {
@@ -436,17 +437,17 @@ Telephony::CallStateChanged(uint32_t aCa
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::EnumerateCallState(uint32_t aCallIndex, uint16_t aCallState,
                               const nsAString& aNumber, bool aIsActive,
-                              bool* aContinue)
+                              bool aIsOutgoing, bool* aContinue)
 {
   // Make sure we don't somehow add duplicates.
   for (uint32_t index = 0; index < mCalls.Length(); index++) {
     nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
     if (tempCall->CallIndex() == aCallIndex) {
       // We have the call already. Skip it.
       *aContinue = true;
       return NS_OK;
--- a/dom/telephony/nsITelephonyProvider.idl
+++ b/dom/telephony/nsITelephonyProvider.idl
@@ -1,54 +1,60 @@
 /* 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 "nsISupports.idl"
 
-[scriptable, uuid(2ab9abfe-09fb-4fea-985f-acf29fc7376a)]
+[scriptable, uuid(45c5a67b-3780-4c41-a670-8abacffb4851)]
 interface nsITelephonyListener : nsISupports
 {
   /**
    * Notified when a telephony call changes state.
    *
    * @param callIndex
    *        Call identifier assigned by the RIL.
    * @param callState
    *        One of the nsITelephonyProvider::CALL_STATE_* values.
    * @param number
    *        Number of the other party.
    * @param isActive
    *        Indicates whether this call is the currently active one.
+   * @param isOutgoing
+   *        Indicates whether this call is outgoing or incoming.
    */
   void callStateChanged(in unsigned long callIndex,
                         in unsigned short callState,
                         in AString number,
-                        in boolean isActive);
+                        in boolean isActive,
+                        in boolean isOutgoing);
 
   /**
    * Called when nsITelephonyProvider is asked to enumerate the current
    * telephony call state (nsITelephonyProvider::enumerateCalls). This is
    * called once per call that is currently managed by the RIL.
    *
    * @param callIndex
    *        Call identifier assigned by the RIL.
    * @param callState
    *        One of the nsITelephonyProvider::CALL_STATE_* values.
    * @param number
    *        Number of the other party.
    * @param isActive
    *        Indicates whether this call is the active one.
+   * @param isOutgoing
+   *        Indicates whether this call is outgoing or incoming.
    *
    * @return true to continue enumeration or false to cancel.
    */
   boolean enumerateCallState(in unsigned long callIndex,
                              in unsigned short callState,
                              in AString number,
-                             in boolean isActive);
+                             in boolean isActive,
+                             in boolean isOutgoing);
 
   /**
    * Called when RIL error occurs.
    *
    * @param callIndex
    *        Call identifier assigned by the RIL. -1 if no connection
    * @param error
    *        Error from RIL.
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -29,18 +29,20 @@
 
 #include <windows.h>
 #include <objbase.h>
 
 #ifdef CAIRO_HAS_D2D_SURFACE
 #include <dxgi.h>
 #endif
 
+// This header is available in the June 2010 SDK and in the Win8 SDK
+#include <d3dcommon.h>
 // Win 8.0 SDK types we'll need when building using older sdks.
-#if MOZ_WINSDK_TARGETVER <= 0x06010000
+#if !defined(D3D_FEATURE_LEVEL_11_1) // defined in the 8.0 SDK only
 #define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100)
 #define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048
 #define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096
 #endif
 
 class ID3D11Device;
 class IDXGIAdapter1;
 
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -841,19 +841,23 @@ bool
 SetAlarm(int32_t aSeconds, int32_t aNanoseconds)
 {
   // It's pointless to program an alarm nothing is going to observe ...
   MOZ_ASSERT(sAlarmObserver);
   RETURN_PROXY_IF_SANDBOXED(SetAlarm(aSeconds, aNanoseconds), false);
 }
 
 void
-SetProcessPriority(int aPid, ProcessPriority aPriority)
+SetProcessPriority(int aPid,
+                   ProcessPriority aPriority,
+                   ProcessCPUPriority aCPUPriority)
 {
-  PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority));
+  // n.b. The sandboxed implementation crashes; SetProcessPriority works only
+  // from the main process.
+  PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority, aCPUPriority));
 }
 
 // From HalTypes.h.
 const char*
 ProcessPriorityToString(ProcessPriority aPriority)
 {
   switch (aPriority) {
   case PROCESS_PRIORITY_MASTER:
@@ -871,16 +875,85 @@ ProcessPriorityToString(ProcessPriority 
   case PROCESS_PRIORITY_UNKNOWN:
     return "UNKNOWN";
   default:
     MOZ_ASSERT(false);
     return "???";
   }
 }
 
+// From HalTypes.h.
+const char*
+ProcessPriorityToString(ProcessPriority aPriority,
+                        ProcessCPUPriority aCPUPriority)
+{
+  // Sorry this is ugly.  At least it's all in one place.
+  //
+  // We intentionally fall through if aCPUPriority is invalid; we won't hit any
+  // of the if statements further down, so it's OK.
+
+  switch (aPriority) {
+  case PROCESS_PRIORITY_MASTER:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "MASTER:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "MASTER:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_FOREGROUND_HIGH:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "FOREGROUND_HIGH:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "FOREGROUND_HIGH:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_FOREGROUND:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "FOREGROUND:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "FOREGROUND:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "BACKGROUND_PERCEIVABLE:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "BACKGROUND_PERCEIVABLE:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_BACKGROUND_HOMESCREEN:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "BACKGROUND_HOMESCREEN:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "BACKGROUND_HOMESCREEN:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_BACKGROUND:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "BACKGROUND:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "BACKGROUND:CPU_LOW";
+    }
+  case PROCESS_PRIORITY_UNKNOWN:
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+      return "UNKNOWN:CPU_NORMAL";
+    }
+    if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+      return "UNKNOWN:CPU_LOW";
+    }
+  default:
+    // Fall through.  (|default| is here to silence warnings.)
+    break;
+  }
+
+  MOZ_ASSERT(false);
+  return "???";
+}
+
 static StaticAutoPtr<ObserverList<FMRadioOperationInformation> > sFMRadioObservers;
 
 static void
 InitializeFMRadioObserver()
 {
   if (!sFMRadioObservers) {
     sFMRadioObservers = new ObserverList<FMRadioOperationInformation>;
     ClearOnShutdown(&sFMRadioObservers);
@@ -1080,10 +1153,26 @@ GetFMBandSettings(FMRadioCountry aCountr
 }
 
 void FactoryReset()
 {
   AssertMainThread();
   PROXY_IF_SANDBOXED(FactoryReset());
 }
 
+void
+StartDiskSpaceWatcher()
+{
+  AssertMainProcess();
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(StartDiskSpaceWatcher());
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  AssertMainProcess();
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(StopDiskSpaceWatcher());
+}
+
 } // namespace hal
 } // namespace mozilla
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -244,17 +244,17 @@ void NotifyNetworkChange(const hal::Netw
 /**
  * Adjusting system clock.
  * @param aDeltaMilliseconds The difference compared with current system clock.
  */
 void AdjustSystemClock(int64_t aDeltaMilliseconds);
 
 /**
  * Set timezone
- * @param aTimezoneSpec The definition can be found in 
+ * @param aTimezoneSpec The definition can be found in
  * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  */
 void SetTimezone(const nsCString& aTimezoneSpec);
 
 /**
  * Get timezone
  * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  */
@@ -298,24 +298,24 @@ void UnregisterSystemTimezoneChangeObser
  * Notify of a change in the system timezone.
  * @param aSystemTimezoneChangeInfo
  */
 void NotifySystemTimezoneChange(
   const hal::SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo);
 
 /**
  * Reboot the device.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void Reboot();
 
 /**
  * Power off the device.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void PowerOff();
 
 /**
  * Enable wake lock notifications from the backend.
  *
  * This method is only used by WakeLockObserversManager.
@@ -418,17 +418,17 @@ void UnlockScreenOrientation();
 void RegisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
 
 /**
  * Unregister an observer for the switch of given SwitchDevice.
  */
 void UnregisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
 
 /**
- * Notify the state of the switch. 
+ * Notify the state of the switch.
  *
  * This API is internal to hal; clients shouldn't call it directly.
  */
 void NotifySwitchChange(const hal::SwitchEvent& aEvent);
 
 /**
  * Get current switch information.
  */
@@ -465,23 +465,31 @@ void NotifyAlarmFired();
  * The alarm can be reprogrammed at any time.
  *
  * This API is currently only allowed to be used from non-sandboxed
  * contexts.
  */
 bool SetAlarm(int32_t aSeconds, int32_t aNanoseconds);
 
 /**
- * Set the priority of the given process.
+ * Set the priority of the given process.  A process's priority is a two-tuple
+ * consisting of a hal::ProcessPriority value and a hal::ProcessCPUPriority
+ * value.
+ *
+ * Two processes with the same ProcessCPUPriority value don't necessarily have
+ * the same CPU priority; the CPU priority we assign to a process is a function
+ * of its ProcessPriority and ProcessCPUPriority.
  *
  * Exactly what this does will vary between platforms.  On *nix we might give
  * background processes higher nice values.  On other platforms, we might
  * ignore this call entirely.
  */
-void SetProcessPriority(int aPid, hal::ProcessPriority aPriority);
+void SetProcessPriority(int aPid,
+                        hal::ProcessPriority aPriority,
+                        hal::ProcessCPUPriority aCPUPriority);
 
 /**
  * Register an observer for the FM radio.
  */
 void RegisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
  * Unregister the observer for the FM radio.
@@ -544,17 +552,17 @@ void CancelFMRadioSeek();
  * Get FM radio band settings by country.
  */
 hal::FMRadioSettings GetFMBandSettings(hal::FMRadioCountry aCountry);
 
 /**
  * Start a watchdog to compulsively shutdown the system if it hangs.
  * @param aMode Specify how to shutdown the system.
  * @param aTimeoutSecs Specify the delayed seconds to shutdown the system.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
 
 /**
  * Perform Factory Reset to wipe out all user data.
  */
 void FactoryReset();
@@ -564,16 +572,30 @@ void FactoryReset();
  */
 void StartMonitoringGamepadStatus();
 
 /**
  * Stop monitoring the status of gamepads attached to the system.
  */
 void StopMonitoringGamepadStatus();
 
+/**
+ * Start monitoring disk space for low space situations.
+ *
+ * This API is currently only allowed to be used from the main process.
+ */
+void StartDiskSpaceWatcher();
+
+/**
+ * Stop monitoring disk space for low space situations.
+ *
+ * This API is currently only allowed to be used from the main process.
+ */
+void StopDiskSpaceWatcher();
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -87,24 +87,35 @@ enum ProcessPriority {
   // "foreground" for the purposes of priority testing, for example
   // CurrentProcessIsForeground().
   PROCESS_PRIORITY_FOREGROUND,
   PROCESS_PRIORITY_FOREGROUND_HIGH,
   PROCESS_PRIORITY_MASTER,
   NUM_PROCESS_PRIORITY
 };
 
-// Convert a ProcessPriority enum value to a string.  The strings returned by
-// this function are statically allocated; do not attempt to free one!
+enum ProcessCPUPriority {
+  PROCESS_CPU_PRIORITY_LOW,
+  PROCESS_CPU_PRIORITY_NORMAL,
+  NUM_PROCESS_CPU_PRIORITY
+};
+
+// Convert a ProcessPriority enum value (with an optional ProcessCPUPriority)
+// to a string.  The strings returned by this function are statically
+// allocated; do not attempt to free one!
 //
 // If you pass an unknown process priority (or NUM_PROCESS_PRIORITY), we
 // fatally assert in debug builds and otherwise return "???".
 const char*
 ProcessPriorityToString(ProcessPriority aPriority);
 
+const char*
+ProcessPriorityToString(ProcessPriority aPriority,
+                        ProcessCPUPriority aCPUPriority);
+
 /**
  * Used by ModifyWakeLock
  */
 enum WakeLockControl {
   WAKE_LOCK_REMOVE_ONE = -1,
   WAKE_LOCK_NO_CHANGE  = 0,
   WAKE_LOCK_ADD_ONE    = 1,
   NUM_WAKE_LOCK
--- a/hal/HalWakeLock.cpp
+++ b/hal/HalWakeLock.cpp
@@ -9,58 +9,57 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/ContentParent.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIPropertyBag2.h"
 #include "nsObserverService.h"
 
+using namespace mozilla;
 using namespace mozilla::hal;
 
-namespace mozilla {
-namespace hal {
+namespace {
 
-WakeLockState
-ComputeWakeLockState(int aNumLocks, int aNumHidden)
-{
-  if (aNumLocks == 0) {
-    return WAKE_LOCK_STATE_UNLOCKED;
-  } else if (aNumLocks == aNumHidden) {
-    return WAKE_LOCK_STATE_HIDDEN;
-  } else {
-    return WAKE_LOCK_STATE_VISIBLE;
-  }
-}
-
-} // hal
-} // mozilla
-
-namespace mozilla {
-namespace hal_impl {
-
-namespace {
 struct LockCount {
   LockCount()
     : numLocks(0)
     , numHidden(0)
   {}
   uint32_t numLocks;
   uint32_t numHidden;
   nsTArray<uint64_t> processes;
 };
+
 typedef nsDataHashtable<nsUint64HashKey, LockCount> ProcessLockTable;
 typedef nsClassHashtable<nsStringHashKey, ProcessLockTable> LockTable;
 
-static int sActiveListeners = 0;
-static StaticAutoPtr<LockTable> sLockTable;
-static bool sInitialized = false;
-static bool sIsShuttingDown = false;
+int sActiveListeners = 0;
+StaticAutoPtr<LockTable> sLockTable;
+bool sInitialized = false;
+bool sIsShuttingDown = false;
 
-static PLDHashOperator
+WakeLockInformation
+WakeLockInfoFromLockCount(const nsAString& aTopic, const LockCount& aLockCount)
+{
+  // TODO: Once we abandon b2g18, we can switch this to use the
+  // WakeLockInformation constructor, which is better because it doesn't let us
+  // forget to assign a param.  For now we have to do it this way, because
+  // b2g18 doesn't have the nsTArray <--> InfallibleTArray conversion (bug
+  // 819791).
+
+  WakeLockInformation info;
+  info.topic() = aTopic;
+  info.numLocks() = aLockCount.numLocks;
+  info.numHidden() = aLockCount.numHidden;
+  info.lockingProcesses().AppendElements(aLockCount.processes);
+  return info;
+}
+
+PLDHashOperator
 CountWakeLocks(const uint64_t& aKey, LockCount aCount, void* aUserArg)
 {
   MOZ_ASSERT(aUserArg);
 
   LockCount* totalCount = static_cast<LockCount*>(aUserArg);
   totalCount->numLocks += aCount.numLocks;
   totalCount->numHidden += aCount.numHidden;
 
@@ -75,29 +74,27 @@ CountWakeLocks(const uint64_t& aKey, Loc
 static PLDHashOperator
 RemoveChildFromList(const nsAString& aKey, nsAutoPtr<ProcessLockTable>& aTable,
                     void* aUserArg)
 {
   MOZ_ASSERT(aUserArg);
 
   PLDHashOperator op = PL_DHASH_NEXT;
   uint64_t childID = *static_cast<uint64_t*>(aUserArg);
-  if (aTable->Get(childID, NULL)) {
+  if (aTable->Get(childID, nullptr)) {
     aTable->Remove(childID);
+
+    LockCount totalCount;
+    aTable->EnumerateRead(CountWakeLocks, &totalCount);
+    if (!totalCount.numLocks) {
+      op = PL_DHASH_REMOVE;
+    }
+
     if (sActiveListeners) {
-      LockCount totalCount;
-      WakeLockInformation info;
-      aTable->EnumerateRead(CountWakeLocks, &totalCount);
-      if (!totalCount.numLocks) {
-        op = PL_DHASH_REMOVE;
-      }
-      info.numLocks() = totalCount.numLocks;
-      info.numHidden() = totalCount.numHidden;
-      info.topic() = aKey;
-      NotifyWakeLockChange(info);
+      NotifyWakeLockChange(WakeLockInfoFromLockCount(aKey, totalCount));
     }
   }
 
   return op;
 }
 
 class ClearHashtableOnShutdown MOZ_FINAL : public nsIObserver {
 public:
@@ -147,31 +144,52 @@ CleanupOnContentShutdown::Observe(nsISup
   if (NS_SUCCEEDED(rv)) {
     sLockTable->Enumerate(RemoveChildFromList, &childID);
   } else {
     NS_WARNING("ipc:content-shutdown message without childID property");
   }
   return NS_OK;
 }
 
-static void
+void
 Init()
 {
   sLockTable = new LockTable();
   sLockTable->Init();
   sInitialized = true;
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false);
     obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown", false);
   }
 }
+
 } // anonymous namespace
 
+namespace mozilla {
+
+namespace hal {
+
+WakeLockState
+ComputeWakeLockState(int aNumLocks, int aNumHidden)
+{
+  if (aNumLocks == 0) {
+    return WAKE_LOCK_STATE_UNLOCKED;
+  } else if (aNumLocks == aNumHidden) {
+    return WAKE_LOCK_STATE_HIDDEN;
+  } else {
+    return WAKE_LOCK_STATE_VISIBLE;
+  }
+}
+
+} // namespace hal
+
+namespace hal_impl {
+
 void
 EnableWakeLockNotifications()
 {
   sActiveListeners++;
 }
 
 void
 DisableWakeLockNotifications()
@@ -210,61 +228,60 @@ ModifyWakeLock(const nsAString& aTopic,
   MOZ_ASSERT(processCount.numLocks >= processCount.numHidden);
   MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0);
   MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0);
   MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden);
   MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0);
   MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0);
 
   WakeLockState oldState = ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden);
+  bool processWasLocked = processCount.numLocks > 0;
 
   processCount.numLocks += aLockAdjust;
   processCount.numHidden += aHiddenAdjust;
 
   totalCount.numLocks += aLockAdjust;
   totalCount.numHidden += aHiddenAdjust;
 
   if (processCount.numLocks) {
     table->Put(aProcessID, processCount);
   } else {
     table->Remove(aProcessID);
   }
   if (!totalCount.numLocks) {
     sLockTable->Remove(aTopic);
   }
 
-  WakeLockState newState = ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden);
+  if (sActiveListeners &&
+      (oldState != ComputeWakeLockState(totalCount.numLocks,
+                                        totalCount.numHidden) ||
+       processWasLocked != (processCount.numLocks > 0))) {
 
-  if (sActiveListeners && oldState != newState) {
     WakeLockInformation info;
     GetWakeLockInfo(aTopic, &info);
     NotifyWakeLockChange(info);
   }
 }
 
 void
 GetWakeLockInfo(const nsAString& aTopic, WakeLockInformation* aWakeLockInfo)
 {
   if (sIsShuttingDown) {
     NS_WARNING("You don't want to get wake lock information during xpcom-shutdown!");
+    *aWakeLockInfo = WakeLockInformation();
     return;
   }
   if (!sInitialized) {
     Init();
   }
 
   ProcessLockTable* table = sLockTable->Get(aTopic);
   if (!table) {
-    aWakeLockInfo->numLocks() = 0;
-    aWakeLockInfo->numHidden() = 0;
-    aWakeLockInfo->topic() = aTopic;
+    *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount());
     return;
   }
   LockCount totalCount;
   table->EnumerateRead(CountWakeLocks, &totalCount);
-  aWakeLockInfo->numLocks() = totalCount.numLocks;
-  aWakeLockInfo->numHidden() = totalCount.numHidden;
-  aWakeLockInfo->lockingProcesses() = totalCount.processes;
-  aWakeLockInfo->topic() = aTopic;
+  *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount);
 }
 
 } // hal_impl
 } // mozilla
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -55,16 +55,17 @@ CPPSRCS += \
 else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   GonkHal.cpp \
   LinuxPower.cpp \
   GonkSensor.cpp \
   UeventPoller.cpp \
   GonkSwitch.cpp \
   GonkFMRadio.cpp \
+  GonkDiskSpaceWatcher.cpp \
   $(NULL)
 else ifeq (Linux,$(OS_TARGET))
 CPPSRCS += \
   LinuxPower.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackSensor.cpp \
   FallbackVibration.cpp \
   FallbackAlarm.cpp \
@@ -129,16 +130,17 @@ CPPSRCS += \
   FallbackLights.cpp  \
   FallbackTime.cpp \
   FallbackWakeLocks.cpp \
   FallbackSwitch.cpp \
   FallbackScreenPower.cpp \
   FallbackProcessPriority.cpp \
   FallbackFMRadio.cpp \
   FallbackFactoryReset.cpp \
+  FallbackDiskSpaceWatcher.cpp \
   $(NULL)
 endif #}
 
 # Fallbacks for backends implemented on Android only.
 ifneq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += FallbackNetwork.cpp
 endif
 
new file mode 100644
--- /dev/null
+++ b/hal/fallback/FallbackDiskSpaceWatcher.cpp
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+namespace hal_impl {
+
+void
+StartDiskSpaceWatcher()
+{
+}
+
+void
+StopDiskSpaceWatcher()
+{
+}
+
+} // namespace hal_impl
+} // namespace mozilla
--- a/hal/fallback/FallbackProcessPriority.cpp
+++ b/hal/fallback/FallbackProcessPriority.cpp
@@ -5,15 +5,18 @@
 #include "Hal.h"
 
 using namespace mozilla::hal;
 
 namespace mozilla {
 namespace hal_impl {
 
 void
-SetProcessPriority(int aPid, ProcessPriority aPriority)
+SetProcessPriority(int aPid,
+                   ProcessPriority aPriority,
+                   ProcessCPUPriority aCPUPriority)
 {
-  HAL_LOG(("FallbackProcessPriority - SetProcessPriority(%d, %d)\n", aPid, aPriority));
+  HAL_LOG(("FallbackProcessPriority - SetProcessPriority(%d, %s)\n",
+           aPid, ProcessPriorityToString(aPriority, aCPUPriority)));
 }
 
 } // hal_impl
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/hal/gonk/GonkDiskSpaceWatcher.cpp
@@ -0,0 +1,308 @@
+/* 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 "Hal.h"
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "nsIObserverService.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "base/message_loop.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsXULAppAPI.h"
+#include "fanotify.h"
+#include "DiskSpaceWatcher.h"
+
+using namespace mozilla;
+
+namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } }
+
+using namespace mozilla::hal_impl;
+
+template<>
+struct RunnableMethodTraits<GonkDiskSpaceWatcher>
+{
+  static void RetainCallee(GonkDiskSpaceWatcher* obj) { }
+  static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { }
+};
+
+namespace mozilla {
+namespace hal_impl {
+
+// fanotify_init and fanotify_mark functions are syscalls.
+// The user space bits are not part of bionic so we add them here
+// as well as fanotify.h
+int fanotify_init (unsigned int flags, unsigned int event_f_flags)
+{
+  return syscall(367, flags, event_f_flags);
+}
+
+// Add, remove, or modify an fanotify mark on a filesystem object.
+int fanotify_mark (int fanotify_fd, unsigned int flags,
+                   uint64_t mask, int dfd, const char *pathname)
+{
+
+  // On 32 bits platforms we have to convert the 64 bits mask into
+  // two 32 bits ints.
+  if (sizeof(void *) == 4) {
+    union {
+      uint64_t _64;
+      uint32_t _32[2];
+    } _mask;
+    _mask._64 = mask;
+    return syscall(368, fanotify_fd, flags, _mask._32[0], _mask._32[1],
+                   dfd, pathname);
+  }
+
+  return syscall(368, fanotify_fd, flags, mask, dfd, pathname);
+}
+
+class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher
+{
+public:
+  GonkDiskSpaceWatcher();
+  ~GonkDiskSpaceWatcher() {};
+
+  virtual void OnFileCanReadWithoutBlocking(int aFd);
+
+  // We should never write to the fanotify fd.
+  virtual void OnFileCanWriteWithoutBlocking(int aFd)
+  {
+    MOZ_NOT_REACHED("Must not write to fanotify fd");
+    MOZ_CRASH();
+  }
+
+  void DoStart();
+  void DoStop();
+
+private:
+  void NotifyUpdate();
+
+  uint64_t mLowThreshold;
+  uint64_t mHighThreshold;
+  TimeDuration mTimeout;
+  TimeStamp  mLastTimestamp;
+  uint64_t mLastFreeSpace;
+  uint32_t mSizeDelta;
+
+  bool mIsDiskFull;
+  uint64_t mFreeSpace;
+
+  int mFd;
+  MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+};
+
+static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr;
+
+#define WATCHER_PREF_LOW        "disk_space_watcher.low_threshold"
+#define WATCHER_PREF_HIGH       "disk_space_watcher.high_threshold"
+#define WATCHER_PREF_TIMEOUT    "disk_space_watcher.timeout"
+#define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta"
+
+static const char kWatchedPath[] = "/data";
+
+// Helper class to dispatch calls to xpcom on the main thread.
+class DiskSpaceNotifier : public nsRunnable
+{
+public:
+  DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) :
+    mIsDiskFull(aIsDiskFull),
+    mFreeSpace(aFreeSpace) {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace);
+    return NS_OK;
+  }
+
+private:
+  bool mIsDiskFull;
+  uint64_t mFreeSpace;
+};
+
+// Helper runnable to delete the watcher on the main thread.
+class DiskSpaceCleaner : public nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    delete gHalDiskSpaceWatcher;
+    return NS_OK;
+  }
+};
+
+GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
+  mLastFreeSpace(UINT64_MAX),
+  mIsDiskFull(false),
+  mFreeSpace(UINT64_MAX),
+  mFd(-1)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr);
+
+  // Default values: 5MB for low threshold, 10MB for high threshold, and
+  // a timeout of 5 seconds.
+  mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024;
+  mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024;
+  mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5));
+  mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024;
+}
+
+void
+GonkDiskSpaceWatcher::DoStart()
+{
+  NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
+               "Not on the correct message loop");
+
+  mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC);
+  if (mFd == -1) {
+    NS_WARNING("Error calling inotify_init()");
+    if (errno == ENOSYS) {
+      printf_stderr("Warning: No fanotify support in this device's kernel.\n");
+    }
+    return;
+  }
+
+  if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE,
+                    0, kWatchedPath) < 0) {
+    NS_WARNING("Error calling fanotify_mark");
+    close(mFd);
+    mFd = -1;
+    return;
+  }
+
+  if (!MessageLoopForIO::current()->WatchFileDescriptor(
+    mFd, /* persistent = */ true,
+    MessageLoopForIO::WATCH_READ,
+    &mReadWatcher, gHalDiskSpaceWatcher)) {
+      NS_WARNING("Unable to watch fanotify fd.");
+      close(mFd);
+      mFd = -1;
+  }
+}
+
+void
+GonkDiskSpaceWatcher::DoStop()
+{
+  NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
+               "Not on the correct message loop");
+
+  if (mFd != -1) {
+    mReadWatcher.StopWatchingFileDescriptor();
+    fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath);
+    close(mFd);
+    mFd = -1;
+  }
+
+  // Dispatch the cleanup to the main thread.
+  nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner();
+  NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+}
+
+// We are called off the main thread, so we proxy first to the main thread
+// before calling the xpcom object.
+void
+GonkDiskSpaceWatcher::NotifyUpdate()
+{
+  mLastTimestamp = TimeStamp::Now();
+  mLastFreeSpace = mFreeSpace;
+
+  nsCOMPtr<nsIRunnable> runnable =
+    new DiskSpaceNotifier(mIsDiskFull, mFreeSpace);
+  NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+}
+
+void
+GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd)
+{
+  struct fanotify_event_metadata* fem = nullptr;
+  char buf[4096];
+  struct statfs sfs;
+  int32_t len, rc;
+
+  do {
+    len = read(aFd, buf, sizeof(buf));
+  } while(len == -1 && errno == EINTR);
+
+  // We should get an exact multiple of fanotify_event_metadata
+  if (len <= 0 || (len % sizeof(*fem) != 0)) {
+    printf_stderr("About to crash: fanotify_event_metadata read error.");
+    MOZ_CRASH();
+  }
+
+  fem = reinterpret_cast<fanotify_event_metadata *>(buf);
+
+  while (FAN_EVENT_OK(fem, len)) {
+    rc = fstatfs(fem->fd, &sfs);
+    if (rc < 0) {
+      NS_WARNING("Unable to stat fan_notify fd");
+    } else {
+      bool firstRun = mFreeSpace == UINT64_MAX;
+      mFreeSpace = sfs.f_bavail * sfs.f_bsize;
+      // We change from full <-> free depending on the free space and the
+      // low and high thresholds.
+      // Once we are in 'full' mode we send updates for all size changes with
+      // a minimum of time between messages or when we cross a size change
+      // threshold.
+      if (firstRun) {
+        mIsDiskFull = mFreeSpace <= mLowThreshold;
+        // Always notify the current state at first run.
+        NotifyUpdate();
+      } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) {
+        mIsDiskFull = true;
+        NotifyUpdate();
+      } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) {
+        mIsDiskFull = false;
+        NotifyUpdate();
+      } else if (mIsDiskFull) {
+        if (mTimeout < TimeStamp::Now() - mLastTimestamp ||
+            mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) {
+          NotifyUpdate();
+        }
+      }
+    }
+    close(fem->fd);
+    fem = FAN_EVENT_NEXT(fem, len);
+  }
+}
+
+void
+StartDiskSpaceWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Bail out if called several times.
+  if (gHalDiskSpaceWatcher != nullptr) {
+    return;
+  }
+
+  gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher();
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart));
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gHalDiskSpaceWatcher) {
+    return;
+  }
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop));
+}
+
+} // namespace hal_impl
+} // namespace mozilla
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -1051,39 +1051,35 @@ EnsureKernelLowMemKillerParamsSet()
   // notify_trigger is a single integer.   If we set notify_trigger=Z, then
   // we'll get notified when there are fewer than Z pages of memory free.  (See
   // GonkMemoryPressureMonitoring.cpp.)
 
   // Build the adj and minfree strings.
   nsAutoCString adjParams;
   nsAutoCString minfreeParams;
 
-  const char* priorityClasses[] = {
-    "master",
-    "foregroundHigh",
-    "foreground",
-    "backgroundPerceivable",
-    "backgroundHomescreen",
-    "background"
-  };
-  for (size_t i = 0; i < NS_ARRAY_LENGTH(priorityClasses); i++) {
+  for (int i = 0; i < NUM_PROCESS_PRIORITY; i++) {
     // The system doesn't function correctly if we're missing these prefs, so
     // crash loudly.
 
+    ProcessPriority priority = static_cast<ProcessPriority>(i);
+
     int32_t oomScoreAdj;
-    if (!NS_SUCCEEDED(Preferences::GetInt(nsPrintfCString(
-          "hal.processPriorityManager.gonk.%sOomScoreAdjust",
-          priorityClasses[i]).get(), &oomScoreAdj))) {
+    if (!NS_SUCCEEDED(Preferences::GetInt(
+          nsPrintfCString("hal.processPriorityManager.gonk.%s.OomScoreAdjust",
+                          ProcessPriorityToString(priority)).get(),
+          &oomScoreAdj))) {
       MOZ_CRASH();
     }
 
     int32_t killUnderMB;
-    if (!NS_SUCCEEDED(Preferences::GetInt(nsPrintfCString(
-          "hal.processPriorityManager.gonk.%sKillUnderMB",
-          priorityClasses[i]).get(), &killUnderMB))) {
+    if (!NS_SUCCEEDED(Preferences::GetInt(
+          nsPrintfCString("hal.processPriorityManager.gonk.%s.KillUnderMB",
+                          ProcessPriorityToString(priority)).get(),
+          &killUnderMB))) {
       MOZ_CRASH();
     }
 
     // adj is in oom_adj units.
     adjParams.AppendPrintf("%d,", OomAdjOfOomScoreAdj(oomScoreAdj));
 
     // minfree is in pages.
     minfreeParams.AppendPrintf("%d,", killUnderMB * 1024 * 1024 / PAGE_SIZE);
@@ -1182,66 +1178,38 @@ SetNiceForPid(int aPid, int aNice)
 
   LOG("Changed nice for pid %d from %d to %d.",
       aPid, origProcPriority, aNice);
 
   closedir(tasksDir);
 }
 
 void
-SetProcessPriority(int aPid, ProcessPriority aPriority)
+SetProcessPriority(int aPid,
+                   ProcessPriority aPriority,
+                   ProcessCPUPriority aCPUPriority)
 {
-  HAL_LOG(("SetProcessPriority(pid=%d, priority=%d)", aPid, aPriority));
+  HAL_LOG(("SetProcessPriority(pid=%d, priority=%d, cpuPriority=%d)",
+           aPid, aPriority, aCPUPriority));
 
   // If this is the first time SetProcessPriority was called, set the kernel's
   // OOM parameters according to our prefs.
   //
   // We could/should do this on startup instead of waiting for the first
   // SetProcessPriorityCall.  But in practice, the master process needs to set
   // its priority early in the game, so we can reasonably rely on
   // SetProcessPriority being called early in startup.
   EnsureKernelLowMemKillerParamsSet();
 
-  const char* priorityStr = NULL;
-  switch (aPriority) {
-  case PROCESS_PRIORITY_BACKGROUND:
-    priorityStr = "background";
-    break;
-  case PROCESS_PRIORITY_BACKGROUND_HOMESCREEN:
-    priorityStr = "backgroundHomescreen";
-    break;
-  case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE:
-    priorityStr = "backgroundPerceivable";
-    break;
-  case PROCESS_PRIORITY_FOREGROUND:
-    priorityStr = "foreground";
-    break;
-  case PROCESS_PRIORITY_FOREGROUND_HIGH:
-    priorityStr = "foregroundHigh";
-    break;
-  case PROCESS_PRIORITY_MASTER:
-    priorityStr = "master";
-    break;
-  default:
-    // PROCESS_PRIORITY_UNKNOWN ends up in this branch, along with invalid enum
-    // values.
-    NS_ERROR("Invalid process priority!");
-    return;
-  }
-
-  // Notice that you can disable oom_adj and renice by deleting the prefs
-  // hal.processPriorityManager{foreground,background,master}{OomAdjust,Nice}.
-
   int32_t oomScoreAdj = 0;
   nsresult rv = Preferences::GetInt(nsPrintfCString(
-    "hal.processPriorityManager.gonk.%sOomScoreAdjust",
-    priorityStr).get(), &oomScoreAdj);
+    "hal.processPriorityManager.gonk.%s.OomScoreAdjust",
+    ProcessPriorityToString(aPriority)).get(), &oomScoreAdj);
 
   if (NS_SUCCEEDED(rv)) {
-
     int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
                                                        OOM_SCORE_ADJ_MAX);
     if(clampedOomScoreAdj != oomScoreAdj) {
       HAL_LOG(("Clamping OOM adjustment for pid %d to %d",
                aPid, clampedOomScoreAdj));
     } else {
       HAL_LOG(("Setting OOM adjustment for pid %d to %d",
                aPid, clampedOomScoreAdj));
@@ -1253,24 +1221,43 @@ SetProcessPriority(int aPid, ProcessPrio
     if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
                      nsPrintfCString("%d", clampedOomScoreAdj).get()))
     {
       int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
 
       WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
                   nsPrintfCString("%d", oomAdj).get());
     }
+  } else {
+    LOG("Unable to read oom_score_adj pref for priority %s; "
+        "are the prefs messed up?",
+        ProcessPriorityToString(aPriority));
+    MOZ_ASSERT(false);
   }
 
   int32_t nice = 0;
-  rv = Preferences::GetInt(nsPrintfCString(
-    "hal.processPriorityManager.gonk.%sNice", priorityStr).get(), &nice);
+
+  if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
+    rv = Preferences::GetInt(
+      nsPrintfCString("hal.processPriorityManager.gonk.%s.Nice",
+                      ProcessPriorityToString(aPriority)).get(),
+      &nice);
+  } else if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
+    rv = Preferences::GetInt("hal.processPriorityManager.gonk.LowCPUNice",
+                             &nice);
+  } else {
+    LOG("Unable to read niceness pref for priority %s; "
+        "are the prefs messed up?",
+        ProcessPriorityToString(aPriority));
+    MOZ_ASSERT(false);
+    rv = NS_ERROR_FAILURE;
+  }
+
   if (NS_SUCCEEDED(rv)) {
-    HAL_LOG(("Setting nice for pid %d to %d", aPid, nice));
-
+    LOG("Setting nice for pid %d to %d", aPid, nice);
     SetNiceForPid(aPid, nice);
   }
 }
 
 void
 FactoryReset()
 {
   nsCOMPtr<nsIRecoveryService> recoveryService =
new file mode 100644
--- /dev/null
+++ b/hal/gonk/fanotify.h
@@ -0,0 +1,118 @@
+#ifndef _LINUX_FANOTIFY_H
+#define _LINUX_FANOTIFY_H
+
+/* This is a Linux header generated by "make headers_install" */
+
+#include <linux/types.h>
+
+/* the following events that user-space can register for */
+#define FAN_ACCESS		0x00000001	/* File was accessed */
+#define FAN_MODIFY		0x00000002	/* File was modified */
+#define FAN_CLOSE_WRITE		0x00000008	/* Writtable file closed */
+#define FAN_CLOSE_NOWRITE	0x00000010	/* Unwrittable file closed */
+#define FAN_OPEN		0x00000020	/* File was opened */
+
+#define FAN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+
+#define FAN_OPEN_PERM		0x00010000	/* File open in perm check */
+#define FAN_ACCESS_PERM		0x00020000	/* File accessed in perm check */
+
+#define FAN_ONDIR		0x40000000	/* event occurred against dir */
+
+#define FAN_EVENT_ON_CHILD	0x08000000	/* interested in child events */
+
+/* helper events */
+#define FAN_CLOSE		(FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+
+/* flags used for fanotify_init() */
+#define FAN_CLOEXEC		0x00000001
+#define FAN_NONBLOCK		0x00000002
+
+/* These are NOT bitwise flags.  Both bits are used togther.  */
+#define FAN_CLASS_NOTIF		0x00000000
+#define FAN_CLASS_CONTENT	0x00000004
+#define FAN_CLASS_PRE_CONTENT	0x00000008
+#define FAN_ALL_CLASS_BITS	(FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \
+				 FAN_CLASS_PRE_CONTENT)
+
+#define FAN_UNLIMITED_QUEUE	0x00000010
+#define FAN_UNLIMITED_MARKS	0x00000020
+
+#define FAN_ALL_INIT_FLAGS	(FAN_CLOEXEC | FAN_NONBLOCK | \
+				 FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE |\
+				 FAN_UNLIMITED_MARKS)
+
+/* flags used for fanotify_modify_mark() */
+#define FAN_MARK_ADD		0x00000001
+#define FAN_MARK_REMOVE		0x00000002
+#define FAN_MARK_DONT_FOLLOW	0x00000004
+#define FAN_MARK_ONLYDIR	0x00000008
+#define FAN_MARK_MOUNT		0x00000010
+#define FAN_MARK_IGNORED_MASK	0x00000020
+#define FAN_MARK_IGNORED_SURV_MODIFY	0x00000040
+#define FAN_MARK_FLUSH		0x00000080
+
+#define FAN_ALL_MARK_FLAGS	(FAN_MARK_ADD |\
+				 FAN_MARK_REMOVE |\
+				 FAN_MARK_DONT_FOLLOW |\
+				 FAN_MARK_ONLYDIR |\
+				 FAN_MARK_MOUNT |\
+				 FAN_MARK_IGNORED_MASK |\
+				 FAN_MARK_IGNORED_SURV_MODIFY |\
+				 FAN_MARK_FLUSH)
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define FAN_ALL_EVENTS (FAN_ACCESS |\
+			FAN_MODIFY |\
+			FAN_CLOSE |\
+			FAN_OPEN)
+
+/*
+ * All events which require a permission response from userspace
+ */
+#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
+			     FAN_ACCESS_PERM)
+
+#define FAN_ALL_OUTGOING_EVENTS	(FAN_ALL_EVENTS |\
+				 FAN_ALL_PERM_EVENTS |\
+				 FAN_Q_OVERFLOW)
+
+#define FANOTIFY_METADATA_VERSION	3
+
+struct fanotify_event_metadata {
+	__u32 event_len;
+	__u8 vers;
+	__u8 reserved;
+	__u16 metadata_len;
+	__u64 mask;
+	__s32 fd;
+	__s32 pid;
+};
+
+struct fanotify_response {
+	__s32 fd;
+	__u32 response;
+};
+
+/* Legit userspace responses to a _PERM event */
+#define FAN_ALLOW	0x01
+#define FAN_DENY	0x02
+/* No fd set in event */
+#define FAN_NOFD	-1
+
+/* Helper functions to deal with fanotify_event_metadata buffers */
+#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
+
+#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
+				   (struct fanotify_event_metadata*)(((char *)(meta)) + \
+				   (meta)->event_len))
+
+#define FAN_EVENT_OK(meta, len)	((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
+				(long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
+				(long)(meta)->event_len <= (long)(len))
+
+#endif /* _LINUX_FANOTIFY_H */
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -59,20 +59,20 @@ struct NetworkInformation {
 };
 
 struct SwitchEvent {
   SwitchDevice device;
   SwitchState status;
 };
 
 struct WakeLockInformation {
+  nsString topic;
   uint32_t numLocks;
   uint32_t numHidden;
   uint64_t[] lockingProcesses;
-  nsString topic;
 };
 
 struct ScreenConfiguration {
   nsIntRect rect;
   ScreenOrientation orientation;
   uint32_t colorDepth;
   uint32_t pixelDepth;
 };
@@ -168,18 +168,16 @@ parent:
       returns (bool allowed);
     UnlockScreenOrientation();
  
     EnableSwitchNotifications(SwitchDevice aDevice);
     DisableSwitchNotifications(SwitchDevice aDevice);
     sync GetCurrentSwitchState(SwitchDevice aDevice)
       returns (SwitchState aState);
 
-    SetProcessPriority(int aPid, ProcessPriority aPriority);
-
     FactoryReset();
 
 child:
     NotifySensorChange(SensorData aSensorData);
 
 parent:
     EnableSensorNotifications(SensorType aSensor);
     DisableSensorNotifications(SensorType aSensor);
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -334,19 +334,21 @@ DisableAlarm()
 bool
 SetAlarm(int32_t aSeconds, int32_t aNanoseconds)
 {
   NS_RUNTIMEABORT("Alarms can't be programmed from sandboxed contexts.  Yet.");
   return false;
 }
 
 void
-SetProcessPriority(int aPid, ProcessPriority aPriority)
+SetProcessPriority(int aPid,
+                   ProcessPriority aPriority,
+                   ProcessCPUPriority aCPUPriority)
 {
-  Hal()->SendSetProcessPriority(aPid, aPriority);
+  NS_RUNTIMEABORT("Only the main process may set processes' priorities.");
 }
 
 void
 EnableFMRadio(const hal::FMRadioSettings& aSettings)
 {
   NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
 }
 
@@ -402,16 +404,28 @@ CancelFMRadioSeek()
 }
 
 void
 FactoryReset()
 {
   Hal()->SendFactoryReset();
 }
 
+void
+StartDiskSpaceWatcher()
+{
+  NS_RUNTIMEABORT("StartDiskSpaceWatcher() can't be called from sandboxed contexts.");
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  NS_RUNTIMEABORT("StopDiskSpaceWatcher() can't be called from sandboxed contexts.");
+}
+
 class HalParent : public PHalParent
                 , public BatteryObserver
                 , public NetworkObserver
                 , public ISensorObserver
                 , public WakeLockObserver
                 , public ScreenConfigurationObserver
                 , public SwitchObserver
                 , public SystemClockChangeObserver
@@ -777,25 +791,16 @@ public:
   virtual bool
   RecvGetCurrentSwitchState(const SwitchDevice& aDevice, hal::SwitchState *aState) MOZ_OVERRIDE
   {
     // Content has no reason to listen to switch events currently.
     *aState = hal::GetCurrentSwitchState(aDevice);
     return true;
   }
 
-  virtual bool
-  RecvSetProcessPriority(const int& aPid, const ProcessPriority& aPriority)
-  {
-    // TODO As a security check, we should ensure that aPid is either the pid
-    // of our child, or the pid of one of the child's children.
-    hal::SetProcessPriority(aPid, aPriority);
-    return true;
-  }
-
   void Notify(const int64_t& aClockDeltaMS)
   {
     unused << SendNotifySystemClockChange(aClockDeltaMS);
   }
 
   void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo)
   {
     unused << SendNotifySystemTimezoneChange(aSystemTimezoneChangeInfo);
--- a/js/xpconnect/src/XPCWrappedJS.cpp
+++ b/js/xpconnect/src/XPCWrappedJS.cpp
@@ -6,17 +6,16 @@
 
 /* Class that wraps JS objects to appear as XPCOM objects. */
 
 #include "xpcprivate.h"
 #include "nsAtomicRefcnt.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "nsTextFormatter.h"
-#include "nsCycleCollectorUtils.h"
 
 // NOTE: much of the fancy footwork is done in xpcstubs.cpp
 
 NS_IMETHODIMP
 NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::TraverseImpl
    (NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS) *that, void *p,
     nsCycleCollectionTraversalCallback &cb)
 {
@@ -146,34 +145,30 @@ nsXPCWrappedJS::QueryInterface(REFNSIID 
 // collection. Code in XPCJSRuntime watches for JS gc to happen and will do
 // the final release on wrappers whose JSObjects get finalized. Note that
 // even after tranistioning to this refcount-of-one state callers might do
 // an addref and cause us to re-root the JSObject and continue on more normally.
 
 nsrefcnt
 nsXPCWrappedJS::AddRef(void)
 {
-    if (!MOZ_LIKELY(NS_IsMainThread() || NS_IsCycleCollectorThread()))
-        MOZ_CRASH();
     nsrefcnt cnt = NS_AtomicIncrementRefcnt(mRefCnt);
     NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this));
 
     if (2 == cnt && IsValid()) {
         XPCJSRuntime* rt = mClass->GetRuntime();
         rt->AddWrappedJSRoot(this);
     }
 
     return cnt;
 }
 
 nsrefcnt
 nsXPCWrappedJS::Release(void)
 {
-    if (!MOZ_LIKELY(NS_IsMainThread() || NS_IsCycleCollectorThread()))
-        MOZ_CRASH();
     NS_PRECONDITION(0 != mRefCnt, "dup release");
 
     if (mMainThreadOnly && !NS_IsMainThread()) {
         // We'd like to abort here, but this can happen if someone uses a proxy
         // for the nsXPCWrappedJS.
         nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
         // If we can't get the main thread anymore we just leak, but this really
         // shouldn't happen.
@@ -277,20 +272,16 @@ CheckMainThreadOnly(nsXPCWrappedJS *aWra
 // static
 nsresult
 nsXPCWrappedJS::GetNewOrUsed(JSContext* cx,
                              JSObject* aJSObj,
                              REFNSIID aIID,
                              nsISupports* aOuter,
                              nsXPCWrappedJS** wrapperResult)
 {
-    // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
-    if (!MOZ_LIKELY(NS_IsMainThread() || NS_IsCycleCollectorThread()))
-        MOZ_CRASH();
-
     JS::RootedObject jsObj(cx, aJSObj);
     JSObject2WrappedJSMap* map;
     nsXPCWrappedJS* root = nullptr;
     nsXPCWrappedJS* wrapper = nullptr;
     nsXPCWrappedJSClass* clazz = nullptr;
     XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
     JSBool release_root = false;
 
@@ -563,20 +554,16 @@ nsXPCWrappedJS::GetInterfaceInfo(nsIInte
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCWrappedJS::CallMethod(uint16_t methodIndex,
                            const XPTMethodDescriptor* info,
                            nsXPTCMiniVariant* params)
 {
-    // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
-    if (!MOZ_LIKELY(NS_IsMainThread() || NS_IsCycleCollectorThread()))
-        MOZ_CRASH();
-
     if (!IsValid())
         return NS_ERROR_UNEXPECTED;
     if (NS_IsMainThread() != mMainThread) {
         NS_NAMED_LITERAL_STRING(kFmt, "Attempt to use JS function on a different thread calling %s.%s. JS objects may not be shared across threads.");
         PRUnichar* msg =
             nsTextFormatter::smprintf(kFmt.get(),
                                       GetClass()->GetInterfaceName(),
                                       info->name);
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_bug608142.js
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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/. */
+
+function run_test() { 
+    var tm = Components.classes["@mozilla.org/thread-manager;1"].getService();
+    var thr = tm.newThread(0);
+
+    var foundThreadError = false;
+ 
+    var listener = {
+        observe: function(message) {
+            if (/JS function on a different thread/.test(message.message))
+                foundThreadError = true;
+        }
+    };
+
+    var cs = Components.classes["@mozilla.org/consoleservice;1"].
+        getService(Components.interfaces.nsIConsoleService);
+    cs.registerListener(listener);
+
+    thr.dispatch({
+        run: function() {
+            do_check_true(false);
+        }
+    }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+
+    thr.shutdown();
+
+    cs.unregisterListener(listener);
+    do_check_true(foundThreadError);
+}
+
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -2,16 +2,17 @@
 head = 
 tail = 
 
 [test_bogus_files.js]
 [test_bug408412.js]
 [test_bug451678.js]
 [test_bug596580.js]
 [test_bug604362.js]
+[test_bug608142.js]
 [test_bug641378.js]
 [test_bug677864.js]
 [test_bug711404.js]
 [test_bug778409.js]
 [test_bug780370.js]
 [test_bug805807.js]
 [test_bug809652.js]
 [test_bug813901.js]
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3959,19 +3959,16 @@ pref("layers.offmainthreadcomposition.en
 // same effect as layers.offmainthreadcomposition.enabled, but specifically for
 // use with tests.
 pref("layers.offmainthreadcomposition.testing.enabled", false);
 // Whether to animate simple opacity and transforms on the compositor
 pref("layers.offmainthreadcomposition.animate-opacity", false);
 pref("layers.offmainthreadcomposition.animate-transform", false);
 pref("layers.offmainthreadcomposition.log-animations", false);
 
-// Whether to (try) to use a Composer2D if available on this platform.
-pref("layers.composer2d.enabled", false);
-
 #ifdef MOZ_X11
 #ifdef MOZ_WIDGET_GTK2
 pref("gfx.xrender.enabled",true);
 #endif
 #endif
 
 #ifdef XP_WIN
 // Whether to disable the automatic detection and use of direct2d.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/DiskSpaceWatcher.cpp
@@ -0,0 +1,158 @@
+/* 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 "DiskSpaceWatcher.h"
+#include "mozilla/Hal.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#define NS_DISKSPACEWATCHER_CID \
+  { 0xab218518, 0xf197, 0x4fb4, { 0x8b, 0x0f, 0x8b, 0xb3, 0x4d, 0xf2, 0x4b, 0xf4 } }
+
+using namespace mozilla;
+
+StaticRefPtr<DiskSpaceWatcher> gDiskSpaceWatcher;
+
+NS_IMPL_ISUPPORTS2(DiskSpaceWatcher, nsIDiskSpaceWatcher, nsIObserver)
+
+uint64_t DiskSpaceWatcher::sFreeSpace = 0;
+bool DiskSpaceWatcher::sIsDiskFull = false;
+
+DiskSpaceWatcher::DiskSpaceWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gDiskSpaceWatcher);
+}
+
+DiskSpaceWatcher::~DiskSpaceWatcher()
+{
+  MOZ_ASSERT(!gDiskSpaceWatcher);
+}
+
+already_AddRefed<DiskSpaceWatcher>
+DiskSpaceWatcher::FactoryCreate()
+{
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!Preferences::GetBool("disk_space_watcher.enabled", false)) {
+    return nullptr;
+  }
+
+  if (!gDiskSpaceWatcher) {
+    gDiskSpaceWatcher = new DiskSpaceWatcher();
+    ClearOnShutdown(&gDiskSpaceWatcher);
+  }
+
+  nsRefPtr<DiskSpaceWatcher> service = gDiskSpaceWatcher.get();
+  return service.forget();
+}
+
+NS_IMETHODIMP
+DiskSpaceWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+                          const PRUnichar* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!strcmp(aTopic, "profile-after-change")) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->AddObserver(this, "profile-before-change", false);
+    mozilla::hal::StartDiskSpaceWatcher();
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-before-change")) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->RemoveObserver(this, "profile-before-change");
+    mozilla::hal::StopDiskSpaceWatcher();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "DiskSpaceWatcher got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+/* readonly attribute bool isDiskFull; */
+NS_IMETHODIMP DiskSpaceWatcher::GetIsDiskFull(bool* aIsDiskFull)
+{
+  *aIsDiskFull = sIsDiskFull;
+  return NS_OK;
+}
+
+// GetFreeSpace is a macro on windows, and that messes up with the c++
+// compiler.
+#ifdef XP_WIN
+#undef GetFreeSpace
+#endif
+/* readonly attribute long freeSpace; */
+NS_IMETHODIMP DiskSpaceWatcher::GetFreeSpace(uint64_t* aFreeSpace)
+{
+  *aFreeSpace = sFreeSpace;
+  return NS_OK;
+}
+
+// static
+void DiskSpaceWatcher::UpdateState(bool aIsDiskFull, uint64_t aFreeSpace)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gDiskSpaceWatcher) {
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+
+  sIsDiskFull = aIsDiskFull;
+  sFreeSpace = aFreeSpace;
+
+  if (!observerService) {
+    return;
+  }
+
+  const PRUnichar stateFull[] = { 'f', 'u', 'l', 'l', 0 };
+  const PRUnichar stateFree[] = { 'f', 'r', 'e', 'e', 0 };
+
+  nsCOMPtr<nsISupports> subject;
+  CallQueryInterface(gDiskSpaceWatcher.get(), getter_AddRefs(subject));
+  MOZ_ASSERT(subject);
+  observerService->NotifyObservers(subject,
+                                   DISKSPACEWATCHER_OBSERVER_TOPIC,
+                                   sIsDiskFull ? stateFull : stateFree);
+  return;
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DiskSpaceWatcher,
+                                         DiskSpaceWatcher::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_DISKSPACEWATCHER_CID);
+
+static const mozilla::Module::CIDEntry kDiskSpaceWatcherCIDs[] = {
+  { &kNS_DISKSPACEWATCHER_CID, false, nullptr, DiskSpaceWatcherConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kDiskSpaceWatcherContracts[] = {
+  { "@mozilla.org/toolkit/disk-space-watcher;1", &kNS_DISKSPACEWATCHER_CID },
+  { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kDiskSpaceWatcherCategories[] = {
+  { "profile-after-change", "Disk Space Watcher Service", DISKSPACEWATCHER_CONTRACTID },
+  { nullptr }
+};
+
+static const mozilla::Module kDiskSpaceWatcherModule = {
+  mozilla::Module::kVersion,
+  kDiskSpaceWatcherCIDs,
+  kDiskSpaceWatcherContracts,
+  kDiskSpaceWatcherCategories
+};
+
+NSMODULE_DEFN(DiskSpaceWatcherModule) = &kDiskSpaceWatcherModule;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/DiskSpaceWatcher.h
@@ -0,0 +1,32 @@
+/* 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 __DISKSPACEWATCHER_H__
+
+#include "nsIDiskSpaceWatcher.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+
+class DiskSpaceWatcher MOZ_FINAL : public nsIDiskSpaceWatcher,
+                                   public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDISKSPACEWATCHER
+  NS_DECL_NSIOBSERVER
+
+  static already_AddRefed<DiskSpaceWatcher>
+  FactoryCreate();
+
+  static void UpdateState(bool aIsDiskFull, uint64_t aFreeSpace);
+
+private:
+  DiskSpaceWatcher();
+  ~DiskSpaceWatcher();
+
+  static uint64_t sFreeSpace;
+  static bool     sIsDiskFull;
+};
+
+#endif // __DISKSPACEWATCHER_H__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/Makefile.in
@@ -0,0 +1,23 @@
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LIBRARY_NAME   = diskspacewatcher
+EXPORT_LIBRARY = 1
+IS_COMPONENT   = 1
+MODULE_NAME    = DiskSpaceWatcherModule
+LIBXUL_LIBRARY = 1
+
+CPPSRCS = \
+  DiskSpaceWatcher.cpp \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+    'nsIDiskSpaceWatcher.idl',
+]
+
+EXPORTS += [
+    'DiskSpaceWatcher.h'
+]
+
+XPIDL_MODULE = 'diskspacewatcher'
+MODULE = 'toolkitcomps'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/nsIDiskSpaceWatcher.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(3aceba74-2ed5-4e99-8fe4-06e90e2b8ef0)]
+interface nsIDiskSpaceWatcher : nsISupports
+{
+  readonly attribute bool isDiskFull; // True if we are low on disk space.
+  readonly attribute unsigned long long freeSpace; // The free space currently available.
+};
+
+%{ C++
+#define DISKSPACEWATCHER_CONTRACTID "@mozilla.org/toolkit/disk-space-watcher;1"
+
+// The data for this notification will be either 'free' or 'full'.
+#define DISKSPACEWATCHER_OBSERVER_TOPIC "disk-space-watcher"
+%}
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -11,16 +11,17 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
 PARALLEL_DIRS += [
     'aboutmemory',
     'alerts',
     'apppicker',
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
+    'diskspacewatcher',
     'downloads',
     'exthelper',
     'filepicker',
     'find',
     'intl',
     'jsdownloads',
     'mediasniffer',
     'microformats',
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -174,16 +174,17 @@ COMPONENT_LIBS += \
   jsreflect \
   composer \
   telemetry \
   jsinspector \
   jsdebugger \
   storagecomps \
   rdf \
   windowds \
+  diskspacewatcher \
   $(NULL)
 
 ifdef BUILD_CTYPES
 COMPONENT_LIBS += \
   jsctypes \
   $(NULL)
 endif
 
--- a/toolkit/library/nsStaticXULComponents.cpp
+++ b/toolkit/library/nsStaticXULComponents.cpp
@@ -237,16 +237,17 @@
     MODULE(identity)                         \
     MODULE(nsServicesCryptoModule)           \
     MOZ_APP_COMPONENT_MODULES                \
     MODULE(nsTelemetryModule)                \
     MODULE(jsinspector)                      \
     MODULE(jsdebugger)                       \
     PEERCONNECTION_MODULE                    \
     GIO_MODULE                               \
+    MODULE(DiskSpaceWatcherModule)           \
     /* end of list */
 
 #define MODULE(_name) \
   NSMODULE_DECL(_name);
 
 XUL_MODULES
 
 #undef MODULE
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -177,17 +177,20 @@ nsWindow::nsWindow()
         // This is a hack to force initialization of the compositor
         // resources, if we're going to use omtc.
         //
         // NB: GetPlatform() will create the gfxPlatform, which wants
         // to know the color depth, which asks our native window.
         // This has to happen after other init has finished.
         gfxPlatform::GetPlatform();
         sUsingOMTC = ShouldUseOffMainThreadCompositing();
-        sUsingHwc = Preferences::GetBool("layers.composer2d.enabled", false);
+
+        property_get("ro.display.colorfill", propValue, "0");
+        sUsingHwc = Preferences::GetBool("layers.composer2d.enabled",
+                                         atoi(propValue) == 1);
 
         if (sUsingOMTC) {
           sOMTCSurface = new gfxImageSurface(gfxIntSize(1, 1),
                                              gfxASurface::ImageFormatRGB24);
         }
     }
 }