Merge central into services
authorGregory Szorc <gps@mozilla.com>
Fri, 10 May 2013 17:25:17 -0700
changeset 131518 179e29a23c5672c16d2a4fa4752ff85a75c8ab2e
parent 131517 9d7444c9f21b04684e0d73aa52ac9dca73a5fddb (current diff)
parent 131516 1d7c615b4b6220629b8a3ca923e3952787dcc2d3 (diff)
child 131536 013cc654fe527de195bca5400951a060ecb2fadd
child 131645 e7739a472f09934bfeb5e04dff63b4a39bd832ce
push id24661
push usergszorc@mozilla.com
push dateSat, 11 May 2013 00:25:45 +0000
treeherdermozilla-central@179e29a23c56 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
179e29a23c56 / 23.0a1 / 20130511031123 / files
nightly linux64
179e29a23c56 / 23.0a1 / 20130511031123 / files
nightly mac
179e29a23c56 / 23.0a1 / 20130511031123 / files
nightly win32
179e29a23c56 / 23.0a1 / 20130511031123 / files
nightly win64
179e29a23c56 / 23.0a1 / 20130511031123 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge central into services
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/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);
         }
     }
 }