Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 01 Oct 2014 15:41:25 +0200
changeset 208207 4247b6b396a5aa25b8b7b58dad27e6cb4bc83684
parent 208206 bb7b8b0da990aa43009ffd3c330b1c5436b173be (current diff)
parent 208159 835ef55e175e82b47aa5fd1f71aeda7e89d5ebd6 (diff)
child 208208 f5ccdeb368435a8cb2fb0464ba9157112536fbb1
push id27580
push userkwierso@gmail.com
push dateWed, 01 Oct 2014 23:26:55 +0000
treeherderautoland@af6c928893c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to mozilla-inbound
accessible/ipc/DocAccessibleChild.cpp
accessible/ipc/DocAccessibleChild.h
accessible/ipc/DocAccessibleParent.cpp
accessible/ipc/DocAccessibleParent.h
accessible/ipc/PDocAccessible.ipdl
accessible/ipc/ProxyAccessible.cpp
accessible/ipc/ProxyAccessible.h
accessible/ipc/moz.build
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -7,26 +7,24 @@
 #include "AccessibleWrap.h"
 
 #include "Accessible-inl.h"
 #include "ApplicationAccessibleWrap.h"
 #include "InterfaceInitFuncs.h"
 #include "nsAccUtils.h"
 #include "nsIAccessibleRelation.h"
 #include "nsIAccessibleTable.h"
-#include "ProxyAccessible.h"
 #include "RootAccessible.h"
 #include "nsIAccessibleValue.h"
 #include "nsMai.h"
 #include "nsMaiHyperlink.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "prprf.h"
 #include "nsStateMap.h"
-#include "mozilla/a11y/Platform.h"
 #include "Relation.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "nsISimpleEnumerator.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "nsXPCOMStrings.h"
 #include "nsComponentManagerUtils.h"
@@ -130,23 +128,19 @@ static const GInterfaceInfo atk_if_infos
  */
 struct MaiAtkObject
 {
   AtkObject parent;
   /*
    * The AccessibleWrap whose properties and features are exported
    * via this object instance.
    */
-  uintptr_t accWrap;
+  AccessibleWrap* accWrap;
 };
 
-// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a
-// proxy.
-static const uintptr_t IS_PROXY = 1;
-
 struct MaiAtkObjectClass
 {
     AtkObjectClass parent_class;
 };
 
 static guint mai_atk_object_signals [LAST_SIGNAL] = { 0, };
 
 static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName);
@@ -249,17 +243,17 @@ AccessibleWrap::~AccessibleWrap()
     NS_ASSERTION(!mAtkObject, "ShutdownAtkObject() is not called");
 }
 
 void
 AccessibleWrap::ShutdownAtkObject()
 {
     if (mAtkObject) {
         if (IS_MAI_OBJECT(mAtkObject)) {
-            MAI_ATK_OBJECT(mAtkObject)->accWrap = 0;
+            MAI_ATK_OBJECT(mAtkObject)->accWrap = nullptr;
         }
         SetMaiHyperlink(nullptr);
         g_object_unref(mAtkObject);
         mAtkObject = nullptr;
     }
 }
 
 void
@@ -583,25 +577,26 @@ initializeCB(AtkObject *aAtkObj, gpointe
     /* AtkObjectClass has not a "initialize" function now,
      * maybe it has later
      */
 
     if (ATK_OBJECT_CLASS(parent_class)->initialize)
         ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData);
 
   /* initialize object */
-  MAI_ATK_OBJECT(aAtkObj)->accWrap = reinterpret_cast<uintptr_t>(aData);
+  MAI_ATK_OBJECT(aAtkObj)->accWrap =
+    static_cast<AccessibleWrap*>(aData);
 }
 
 void
 finalizeCB(GObject *aObj)
 {
     if (!IS_MAI_OBJECT(aObj))
         return;
-    NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == 0, "AccWrap NOT null");
+    NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == nullptr, "AccWrap NOT null");
 
     // call parent finalize function
     // finalize of GObjectClass will unref the accessible parent if has
     if (G_OBJECT_CLASS (parent_class)->finalize)
         G_OBJECT_CLASS (parent_class)->finalize(aObj);
 }
 
 const gchar*
@@ -663,43 +658,35 @@ getDescriptionCB(AtkObject *aAtkObj)
                                    NS_ConvertUTF16toUTF8(uniDesc).get());
 
     return aAtkObj->description;
 }
 
 AtkRole
 getRoleCB(AtkObject *aAtkObj)
 {
+  AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+  if (!accWrap)
+    return ATK_ROLE_INVALID;
+
+#ifdef DEBUG
+  NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
+      "Does not support nsIAccessibleText when it should");
+#endif
+
   if (aAtkObj->role != ATK_ROLE_INVALID)
     return aAtkObj->role;
 
-  AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
-  a11y::role role;
-  if (!accWrap) {
-    ProxyAccessible* proxy = GetProxy(aAtkObj);
-    if (!proxy)
-      return ATK_ROLE_INVALID;
-
-    role = proxy->Role();
-  } else {
-#ifdef DEBUG
-    NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
-                 "Does not support nsIAccessibleText when it should");
-#endif
-
-    role = accWrap->Role();
-  }
-
 #define ROLE(geckoRole, stringRole, atkRole, macRole, \
              msaaRole, ia2Role, nameRule) \
   case roles::geckoRole: \
     aAtkObj->role = atkRole; \
     break;
 
-  switch (role) {
+  switch (accWrap->Role()) {
 #include "RoleMap.h"
     default:
       MOZ_CRASH("Unknown role.");
   };
 
 #undef ROLE
 
   if (aAtkObj->role == ATK_ROLE_LIST_BOX && !IsAtkVersionAtLeast(2, 1))
@@ -954,80 +941,31 @@ refRelationSetCB(AtkObject *aAtkObj)
 }
 
 // Check if aAtkObj is a valid MaiAtkObject, and return the AccessibleWrap
 // for it.
 AccessibleWrap*
 GetAccessibleWrap(AtkObject* aAtkObj)
 {
   NS_ENSURE_TRUE(IS_MAI_OBJECT(aAtkObj), nullptr);
-
-  // Make sure its native is an AccessibleWrap not a proxy.
-  if (MAI_ATK_OBJECT(aAtkObj)->accWrap & IS_PROXY)
-    return nullptr;
-
-    AccessibleWrap* accWrap =
-      reinterpret_cast<AccessibleWrap*>(MAI_ATK_OBJECT(aAtkObj)->accWrap);
+  AccessibleWrap* accWrap = MAI_ATK_OBJECT(aAtkObj)->accWrap;
 
   // Check if the accessible was deconstructed.
   if (!accWrap)
     return nullptr;
 
   NS_ENSURE_TRUE(accWrap->GetAtkObject() == aAtkObj, nullptr);
 
   AccessibleWrap* appAccWrap = ApplicationAcc();
   if (appAccWrap != accWrap && !accWrap->IsValidObject())
     return nullptr;
 
   return accWrap;
 }
 
-ProxyAccessible*
-GetProxy(AtkObject* aObj)
-{
-  if (!aObj || !(MAI_ATK_OBJECT(aObj)->accWrap & IS_PROXY))
-    return nullptr;
-
-  return reinterpret_cast<ProxyAccessible*>(MAI_ATK_OBJECT(aObj)->accWrap
-      & ~IS_PROXY);
-}
-
-static uint16_t
-GetInterfacesForProxy(ProxyAccessible* aProxy)
-{
-  return MAI_INTERFACE_COMPONENT;
-}
-
-void
-a11y::ProxyCreated(ProxyAccessible* aProxy)
-{
-  GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy));
-  NS_ASSERTION(type, "why don't we have a type!");
-
-  AtkObject* obj =
-    reinterpret_cast<AtkObject *>
-    (g_object_new(type, nullptr));
-  if (!obj)
-    return;
-
-  atk_object_initialize(obj, aProxy);
-  obj->role = ATK_ROLE_INVALID;
-  obj->layer = ATK_LAYER_INVALID;
-  aProxy->SetWrapper(reinterpret_cast<uintptr_t>(obj) | IS_PROXY);
-}
-
-void
-a11y::ProxyDestroyed(ProxyAccessible* aProxy)
-{
-  auto obj = reinterpret_cast<MaiAtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
-  obj->accWrap = 0;
-  g_object_unref(obj);
-  aProxy->SetWrapper(0);
-}
-
 nsresult
 AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   nsresult rv = Accessible::HandleAccEvent(aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
     Accessible* accessible = aEvent->GetAccessible();
     NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
--- a/accessible/atk/AccessibleWrap.h
+++ b/accessible/atk/AccessibleWrap.h
@@ -92,15 +92,15 @@ private:
   enum EAvailableAtkSignals {
     eUnknown,
     eHaveNewAtkTextSignals,
     eNoNewAtkSignals
   };
 
   static EAvailableAtkSignals gAvailableAtkSignals;
 
-  uint16_t CreateMaiInterfaces();
+  uint16_t CreateMaiInterfaces(void);
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif /* __NS_ACCESSIBLE_WRAP_H__ */
--- a/accessible/atk/moz.build
+++ b/accessible/atk/moz.build
@@ -30,17 +30,16 @@ SOURCES += [
     'RootAccessibleWrap.cpp',
     'UtilInterface.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/generic',
     '/accessible/html',
-    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/other-licenses/atk-1.0',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_ENABLE_GTK']:
--- a/accessible/atk/nsMai.h
+++ b/accessible/atk/nsMai.h
@@ -8,39 +8,32 @@
 #define __NS_MAI_H__
 
 #include <atk/atk.h>
 #include <glib.h>
 #include <glib-object.h>
 
 #include "AccessibleWrap.h"
 
-namespace mozilla {
-namespace a11y {
-class ProxyAccessible;
-}
-}
-
 #define MAI_TYPE_ATK_OBJECT             (mai_atk_object_get_type ())
 #define MAI_ATK_OBJECT(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                                          MAI_TYPE_ATK_OBJECT, MaiAtkObject))
 #define MAI_ATK_OBJECT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), \
                                          MAI_TYPE_ATK_OBJECT, \
                                          MaiAtkObjectClass))
 #define IS_MAI_OBJECT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                                          MAI_TYPE_ATK_OBJECT))
 #define IS_MAI_OBJECT_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                                          MAI_TYPE_ATK_OBJECT))
 #define MAI_ATK_OBJECT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                                          MAI_TYPE_ATK_OBJECT, \
                                          MaiAtkObjectClass))
 GType mai_atk_object_get_type(void);
 GType mai_util_get_type();
 mozilla::a11y::AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj);
-mozilla::a11y::ProxyAccessible* GetProxy(AtkObject* aAtkObj);
 
 extern int atkMajorVersion, atkMinorVersion;
 
 /**
  * Return true if the loaded version of libatk-1.0.so is at least
  * aMajor.aMinor.0.
  */
 static inline bool
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -227,18 +227,16 @@ public:
   {
     return AccEvent::GetEventGroups() | (1U << eMutationEvent);
   }
 
   // MutationEvent
   bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
   bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
 
-  Accessible* Parent() const { return mParent; }
-
 protected:
   nsCOMPtr<nsINode> mNode;
   nsRefPtr<Accessible> mParent;
   nsRefPtr<AccTextChangeEvent> mTextChangeEvent;
 
   friend class EventQueue;
 };
 
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -3,17 +3,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 "DocManager.h"
 
 #include "ApplicationAccessible.h"
 #include "ARIAMap.h"
 #include "DocAccessible-inl.h"
-#include "DocAccessibleChild.h"
 #include "nsAccessibilityService.h"
 #include "RootAccessibleWrap.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 #include "mozilla/EventListenerManager.h"
@@ -23,18 +22,16 @@
 #include "nsIChannel.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIWebNavigation.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIWebProgress.h"
 #include "nsCoreUtils.h"
-#include "nsXULAppAPI.h"
-#include "mozilla/dom/ContentChild.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::dom;
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager
 ////////////////////////////////////////////////////////////////////////////////
@@ -416,22 +413,16 @@ DocManager::CreateDocOrRootAccessible(ns
     // the tree. The reorder event is delivered after the document tree is
     // constructed because event processing and tree construction are done by
     // the same document.
     // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
     // events processing.
     docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
                              ApplicationAcc());
 
-    if (XRE_GetProcessType() != GeckoProcessType_Default) {
-      DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
-      docAcc->SetIPCDoc(ipcDoc);
-    auto contentChild = dom::ContentChild::GetSingleton();
-    contentChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0);
-    }
   } else {
     parentDocAcc->BindChildDocument(docAcc);
   }
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eDocCreate)) {
     logging::DocCreate("document creation finished", aDocument);
     logging::Stack();
--- a/accessible/base/DocManager.h
+++ b/accessible/base/DocManager.h
@@ -12,17 +12,16 @@
 #include "nsWeakReference.h"
 #include "nsIPresShell.h"
 
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class DocAccessible;
-class DocAccessibleParent;
 
 /**
  * Manage the document accessible life cycle.
  */
 class DocManager : public nsIWebProgressListener,
                    public nsIDOMEventListener,
                    public nsSupportsWeakReference
 {
@@ -61,35 +60,16 @@ public:
    * Called by document accessible when it gets shutdown.
    */
   inline void NotifyOfDocumentShutdown(nsIDocument* aDocument)
   {
     mDocAccessibleCache.Remove(aDocument);
     RemoveListeners(aDocument);
   }
 
-  /*
-   * Notification that a top level document in a content process has gone away.
-   */
-  void RemoteDocShutdown(DocAccessibleParent* aDoc)
-  {
-    DebugOnly<bool> result = mRemoteDocuments.RemoveElement(aDoc);
-    MOZ_ASSERT(result, "Why didn't we find the document!");
-  }
-
-  /*
-   * Notify of a new top level document in a content process.
-   */
-  void RemoteDocAdded(DocAccessibleParent* aDoc)
-  {
-    MOZ_ASSERT(!mRemoteDocuments.Contains(aDoc),
-               "How did we already have the doc!");
-    mRemoteDocuments.AppendElement(aDoc);
-  }
-
 #ifdef DEBUG
   bool IsProcessingRefreshDriverNotification() const;
 #endif
 
 protected:
   DocManager();
   virtual ~DocManager() { }
 
@@ -159,21 +139,16 @@ private:
 
 #ifdef DEBUG
   static PLDHashOperator
     SearchIfDocIsRefreshing(const nsIDocument* aKey,
                             DocAccessible* aDocAccessible, void* aUserArg);
 #endif
 
   DocAccessibleHashtable mDocAccessibleCache;
-
-  /*
-   * The list of remote top level documents.
-   */
-  nsTArray<DocAccessibleParent*> mRemoteDocuments;
 };
 
 /**
  * Return the existing document accessible for the document if any.
  * Note this returns the doc accessible for the primary pres shell if there is
  * more than one.
  */
 inline DocAccessible*
--- a/accessible/base/EventQueue.cpp
+++ b/accessible/base/EventQueue.cpp
@@ -3,17 +3,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 "EventQueue.h"
 
 #include "Accessible-inl.h"
 #include "nsEventShell.h"
 #include "DocAccessible.h"
-#include "DocAccessibleChild.h"
 #include "nsAccessibilityService.h"
 #include "nsTextEquivUtils.h"
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::a11y;
@@ -551,20 +550,10 @@ EventQueue::ProcessEventQueue()
       }
     }
 
     if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
       mDocument->ShutdownChildrenInSubtree(event->mAccessible);
 
     if (!mDocument)
       return;
-
-    if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    DocAccessibleChild* ipcDoc = mDocument->IPCDoc();
-      if (event->mEventType == nsIAccessibleEvent::EVENT_SHOW)
-        ipcDoc->ShowEvent(downcast_accEvent(event));
-      else if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
-        ipcDoc->SendHideEvent(reinterpret_cast<uintptr_t>(event->GetAccessible()));
-      else
-        ipcDoc->SendEvent(event->GetEventType());
-    }
   }
 }
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -1,21 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "NotificationController.h"
 
 #include "DocAccessible-inl.h"
-#include "DocAccessibleChild.h"
 #include "TextLeafAccessible.h"
 #include "TextUpdater.h"
 
-#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Telemetry.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // NotificationCollector
@@ -214,29 +212,18 @@ NotificationController::WillRefresh(mozi
     if (childDoc->IsDefunct())
       continue;
 
     nsIContent* ownerContent = mDocument->DocumentNode()->
       FindContentForSubDocument(childDoc->DocumentNode());
     if (ownerContent) {
       Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
       if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
-        if (mDocument->AppendChildDocument(childDoc)) {
-          if (XRE_GetProcessType() != GeckoProcessType_Default) {
-            DocAccessibleChild* ipcDoc = new DocAccessibleChild(childDoc);
-            childDoc->SetIPCDoc(ipcDoc);
-            auto contentChild = dom::ContentChild::GetSingleton();
-            DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
-            uint64_t id = reinterpret_cast<uintptr_t>(outerDocAcc->UniqueID());
-            contentChild->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc,
-                                                        id);
-          }
-
+        if (mDocument->AppendChildDocument(childDoc))
           continue;
-        }
 
         outerDocAcc->RemoveChild(childDoc);
       }
 
       // Failed to bind the child document, destroy it.
       childDoc->Shutdown();
     }
   }
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -2,18 +2,16 @@
 /* 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/. */
 
 namespace mozilla {
 namespace a11y {
 
-class ProxyAccessible;
-
 enum EPlatformDisabledState {
   ePlatformIsForceEnabled = -1,
   ePlatformIsEnabled = 0,
   ePlatformIsDisabled = 1
 };
 
 /**
  * Return the platform disabled state.
@@ -44,22 +42,11 @@ bool ShouldA11yBeEnabled();
 void PlatformInit();
 
 /**
  * Shutdown platform accessibility.
  * Note this is called before internal accessibility support is shutdown.
  */
 void PlatformShutdown();
 
-/**
- * called when a new ProxyAccessible is created, so the platform may setup a
- * wrapper for it, or take other action.
- */
-void ProxyCreated(ProxyAccessible*);
-
-/**
- * Called just before a ProxyAccessible is destroyed so its wrapper can be
- * disposed of and other action taken.
- */
-void ProxyDestroyed(ProxyAccessible*);
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/base/Role.h
+++ b/accessible/base/Role.h
@@ -778,19 +778,17 @@ enum Role {
   /**
    * Represent a definition in a definition list (dd in HTML)
    */
   DEFINITION = 128,
 
   /**
    * Represent a keyboard or keypad key (ARIA role "key").
    */
-  KEY = 129,
-
-  LAST_ROLE = KEY
+  KEY = 129
 };
 
 } // namespace role
 
 typedef enum mozilla::a11y::roles::Role role;
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -55,17 +55,16 @@ UNIFIED_SOURCES += [
 if CONFIG['A11Y_LOG']:
     UNIFIED_SOURCES += [
         'Logging.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/accessible/generic',
     '/accessible/html',
-    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/dom/xbl',
     '/ipc/chromium/src',
     '/layout/generic',
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
@@ -89,10 +88,8 @@ else:
     LOCAL_INCLUDES += [
         '/accessible/other',
     ]
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_ENABLE_GTK']:
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
-
-include('/ipc/chromium/chromium-config.mozbuild')
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Accessible-inl.h"
 #include "AccIterator.h"
 #include "DocAccessible-inl.h"
-#include "DocAccessibleChild.h"
 #include "HTMLImageMapAccessible.h"
 #include "nsAccCache.h"
 #include "nsAccessiblePivot.h"
 #include "nsAccUtils.h"
 #include "nsEventShell.h"
 #include "nsTextEquivUtils.h"
 #include "Role.h"
 #include "RootAccessible.h"
@@ -79,17 +78,17 @@ DocAccessible::
   HyperTextAccessibleWrap(aRootContent, this), xpcAccessibleDocument(),
   // XXX aaronl should we use an algorithm for the initial cache size?
   mAccessibleCache(kDefaultCacheLength),
   mNodeToAccessibleMap(kDefaultCacheLength),
   mDocumentNode(aDocument),
   mScrollPositionChangedTicks(0),
   mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
   mVirtualCursor(nullptr),
-  mPresShell(aPresShell), mIPCDoc(nullptr)
+  mPresShell(aPresShell)
 {
   mGenericTypes |= eDocument;
   mStateFlags |= eNotNodeMapEntry;
 
   MOZ_ASSERT(mPresShell, "should have been given a pres shell");
   mPresShell->SetDocAccessible(this);
 
   // If this is a XUL Document, it should not implement nsHyperText
@@ -469,22 +468,16 @@ DocAccessible::Shutdown()
   // Walk the array backwards because child documents remove themselves from the
   // array as they are shutdown.
   int32_t childDocCount = mChildDocuments.Length();
   for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
     mChildDocuments[idx]->Shutdown();
 
   mChildDocuments.Clear();
 
-  // XXX thinking about ordering?
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    DocAccessibleChild::Send__delete__(mIPCDoc);
-    MOZ_ASSERT(!mIPCDoc);
-  }
-
   if (mVirtualCursor) {
     mVirtualCursor->RemoveObserver(this);
     mVirtualCursor = nullptr;
   }
 
   mPresShell->SetDocAccessible(nullptr);
   mPresShell = nullptr;  // Avoid reentrancy
 
@@ -1438,23 +1431,16 @@ DocAccessible::DoInitialUpdate()
   // Fire reorder event after the document tree is constructed. Note, since
   // this reorder event is processed by parent document then events targeted to
   // this document may be fired prior to this reorder event. If this is
   // a problem then consider to keep event processing per tab document.
   if (!IsRoot()) {
     nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
     ParentDocument()->FireDelayedEvent(reorderEvent);
   }
-
-  uint32_t childCount = ChildCount();
-  for (uint32_t i = 0; i < childCount; i++) {
-    Accessible* child = GetChildAt(i);
-    nsRefPtr<AccShowEvent> event = new AccShowEvent(child, child->GetContent());
-  FireDelayedEvent(event);
-  }
 }
 
 void
 DocAccessible::ProcessLoad()
 {
   mLoadState |= eCompletelyLoaded;
 
 #ifdef A11Y_LOG
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -28,17 +28,16 @@ class nsIScrollableView;
 
 const uint32_t kDefaultCacheLength = 128;
 
 namespace mozilla {
 namespace a11y {
 
 class DocManager;
 class NotificationController;
-class DocAccessibleChild;
 class RelatedAccIterator;
 template<class Class, class Arg>
 class TNotification;
 
 class DocAccessible : public HyperTextAccessibleWrap,
                       public xpcAccessibleDocument,
                       public nsIDocumentObserver,
                       public nsIObserver,
@@ -516,30 +515,16 @@ protected:
    * Rules: The root chrome document accessible is never an event target
    * (for example, Firefox UI window). If the sub document is loaded within its
    * parent document then the parent document is a target only (aka events
    * coalescence).
    */
   bool IsLoadEventTarget() const;
 
   /**
-   * If this document is in a content process return the object responsible for
-   * communicating with the main process for it.
-   */
-  DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
-
-  /*
-   * Set the object responsible for communicating with the main process on
-   * behalf of this document.
-   */
-  void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = aIPCDoc; }
-
-  friend class DocAccessibleChild;
-
-  /**
    * Used to fire scrolling end event after page scroll.
    *
    * @param aTimer    [in] the timer object
    * @param aClosure  [in] the document accessible where scrolling happens
    */
   static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
 
 protected:
@@ -652,19 +637,16 @@ protected:
    */
   nsRefPtr<NotificationController> mNotificationController;
   friend class EventQueue;
   friend class NotificationController;
 
 private:
 
   nsIPresShell* mPresShell;
-
-  // Exclusively owned by IPDL so don't manually delete it!
-  DocAccessibleChild* mIPCDoc;
 };
 
 inline DocAccessible*
 Accessible::AsDoc()
 {
   return IsDoc() ? static_cast<DocAccessible*>(this) : nullptr;
 }
 
--- a/accessible/generic/moz.build
+++ b/accessible/generic/moz.build
@@ -23,17 +23,16 @@ UNIFIED_SOURCES += [
     'RootAccessible.cpp',
     'TableCellAccessible.cpp',
     'TextLeafAccessible.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/html',
-    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/content/base/src',
     '/layout/generic',
     '/layout/xul',
 ]
 
 if CONFIG['MOZ_ENABLE_GTK']:
@@ -50,10 +49,8 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
         '/accessible/mac',
     ]
 else:
     LOCAL_INCLUDES += [
         '/accessible/other',
     ]
 
 FINAL_LIBRARY = 'xul'
-
-include('/ipc/chromium/chromium-config.mozbuild')
deleted file mode 100644
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "DocAccessibleChild.h"
-
-#include "Accessible-inl.h"
-
-namespace mozilla {
-namespace a11y {
-
-void
-SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree)
-{
-  uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
-  uint32_t role = aRoot->Role();
-  uint32_t childCount = aRoot->ChildCount();
-
-  nsString name;
-  aRoot->Name(name);
-  aTree.AppendElement(AccessibleData(id, role, childCount, name));
-  for (uint32_t i = 0; i < childCount; i++)
-    SerializeTree(aRoot->GetChildAt(i), aTree);
-}
-
-void
-DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
-{
-  Accessible* parent = aShowEvent->Parent();
-  uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
-  uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
-  nsTArray<AccessibleData> shownTree;
-  ShowEventData data(parentID, idxInParent, shownTree);
-  SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
-  SendShowEvent(data);
-}
-}
-}
deleted file mode 100644
--- a/accessible/ipc/DocAccessibleChild.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_a11y_DocAccessibleChild_h
-#define mozilla_a11y_DocAccessibleChild_h
-
-#include "mozilla/a11y/DocAccessible.h"
-#include "mozilla/a11y/PDocAccessibleChild.h"
-#include "nsISupportsImpl.h"
-
-namespace mozilla {
-namespace a11y {
-class AccShowEvent;
-
-  /*
-   * These objects handle content side communication for an accessible document,
-   * and their lifetime is the same as the document they represent.
-   */
-class DocAccessibleChild : public PDocAccessibleChild
-{
-public:
-  DocAccessibleChild(DocAccessible* aDoc) :
-    mDoc(aDoc)
-  { MOZ_COUNT_CTOR(DocAccessibleChild); }
-  ~DocAccessibleChild()
-  {
-    mDoc->SetIPCDoc(nullptr);
-    MOZ_COUNT_DTOR(DocAccessibleChild);
-  }
-
-  void ShowEvent(AccShowEvent* aShowEvent);
-
-private:
-  DocAccessible* mDoc;
-};
-
-}
-}
-
-#endif
deleted file mode 100644
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "DocAccessibleParent.h"
-#include "nsAutoPtr.h"
-#include "mozilla/a11y/Platform.h"
-
-namespace mozilla {
-namespace a11y {
-
-bool
-DocAccessibleParent::RecvShowEvent(const ShowEventData& aData)
-{
-  if (aData.NewTree().IsEmpty()) {
-    NS_ERROR("no children being added");
-    return false;
-  }
-
-  ProxyAccessible* parent = nullptr;
-  if (aData.ID()) {
-    ProxyEntry* e = mAccessibles.GetEntry(aData.ID());
-    if (e)
-      parent = e->mProxy;
-  } else {
-    parent = this;
-  }
-
-  // XXX This should really never happen, but sometimes we fail to fire the
-  // required show events.
-  if (!parent) {
-    NS_ERROR("adding child to unknown accessible");
-    return false;
-  }
-
-  uint32_t newChildIdx = aData.Idx();
-  if (newChildIdx > parent->ChildrenCount()) {
-    NS_ERROR("invalid index to add child at");
-    return false;
-  }
-
-  uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
-  MOZ_ASSERT(consumed == aData.NewTree().Length());
-  for (uint32_t i = 0; i < consumed; i++) {
-    uint64_t id = aData.NewTree()[i].ID();
-    MOZ_ASSERT(mAccessibles.GetEntry(id));
-  }
-
-  return consumed;
-}
-
-uint32_t
-DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
-                                const nsTArray<a11y::AccessibleData>& aNewTree,
-                                uint32_t aIdx, uint32_t aIdxInParent)
-{
-  if (aNewTree.Length() <= aIdx) {
-    NS_ERROR("bad index in serialized tree!");
-    return 0;
-  }
-
-  const AccessibleData& newChild = aNewTree[aIdx];
-  if (newChild.Role() > roles::LAST_ROLE) {
-    NS_ERROR("invalid role");
-    return 0;
-  }
-
-  auto role = static_cast<a11y::role>(newChild.Role());
-  ProxyAccessible* newProxy =
-    new ProxyAccessible(newChild.ID(), aParent, this, role, newChild.Name());
-  aParent->AddChildAt(aIdxInParent, newProxy);
-  mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
-  ProxyCreated(newProxy);
-
-  uint32_t accessibles = 1;
-  uint32_t kids = newChild.ChildrenCount();
-  for (uint32_t i = 0; i < kids; i++) {
-    uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
-    if (!consumed)
-      return 0;
-
-    accessibles += consumed;
-  }
-
-  MOZ_ASSERT(newProxy->ChildrenCount() == kids);
-
-  return accessibles;
-}
-
-bool
-DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID)
-{
-  ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
-  if (!rootEntry) {
-    NS_ERROR("invalid root being removed!");
-    return true;
-  }
-
-  ProxyAccessible* root = rootEntry->mProxy;
-  if (!root) {
-    NS_ERROR("invalid root being removed!");
-    return true;
-  }
-
-  root->Shutdown();
-
-  return true;
-}
-}
-}
deleted file mode 100644
--- a/accessible/ipc/DocAccessibleParent.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_a11y_DocAccessibleParent_h
-#define mozilla_a11y_DocAccessibleParent_h
-
-#include "nsAccessibilityService.h"
-#include "ProxyAccessible.h"
-#include "mozilla/a11y/PDocAccessibleParent.h"
-#include "nsClassHashtable.h"
-#include "nsHashKeys.h"
-#include "nsISupportsImpl.h"
-
-namespace mozilla {
-namespace a11y {
-
-/*
- * These objects live in the main process and comunicate with and represent
- * an accessible document in a content process.
- */
-class DocAccessibleParent : public ProxyAccessible,
-    public PDocAccessibleParent
-{
-public:
-  DocAccessibleParent() :
-    mParentDoc(nullptr)
-  { MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible); }
-  ~DocAccessibleParent()
-  {
-    MOZ_COUNT_DTOR_INHERITED(DocAccessibleParent, ProxyAccessible);
-    MOZ_ASSERT(mChildDocs.Length() == 0);
-    MOZ_ASSERT(!mParentDoc);
-  }
-
-  /*
-   * Called when a message from a document in a child process notifies the main
-   * process it is firing an event.
-   */
-  virtual bool RecvEvent(const uint32_t& aType) MOZ_OVERRIDE
-  {
-    return true;
-  }
-
-  virtual bool RecvShowEvent(const ShowEventData& aData) MOZ_OVERRIDE;
-  virtual bool RecvHideEvent(const uint64_t& aRootID) MOZ_OVERRIDE;
-
-  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(mChildDocs.IsEmpty(),
-               "why wheren't the child docs destroyed already?");
-    mParentDoc ? mParentDoc->RemoveChildDoc(this)
-      : GetAccService()->RemoteDocShutdown(this);
-  }
-
-  /*
-   * Return the main processes representation of the parent document (if any)
-   * of the document this object represents.
-   */
-  DocAccessibleParent* Parent() const { return mParentDoc; }
-
-  /*
-   * Called when a document in a content process notifies the main process of a
-   * new child document.
-   */
-  bool AddChildDoc(DocAccessibleParent* aChildDoc, uint64_t aParentID)
-  {
-    ProxyAccessible* outerDoc = mAccessibles.GetEntry(aParentID)->mProxy;
-    if (!outerDoc)
-      return false;
-
-    aChildDoc->mParent = outerDoc;
-    outerDoc->SetChildDoc(aChildDoc);
-    mChildDocs.AppendElement(aChildDoc);
-    aChildDoc->mParentDoc = this;
-    return true;
-  }
-
-  /*
-   * Called when the document in the content process this object represents
-   * notifies the main process a child document has been removed.
-   */
-  void RemoveChildDoc(DocAccessibleParent* aChildDoc)
-  {
-    aChildDoc->mParent->SetChildDoc(nullptr);
-    mChildDocs.RemoveElement(aChildDoc);
-    aChildDoc->mParentDoc = nullptr;
-    MOZ_ASSERT(aChildDoc->mChildDocs.Length() == 0);
-  }
-
-  void RemoveAccessible(ProxyAccessible* aAccessible)
-  {
-    MOZ_ASSERT(mAccessibles.GetEntry(aAccessible->ID()));
-    mAccessibles.RemoveEntry(aAccessible->ID());
-  }
-
-private:
-
-  class ProxyEntry : public PLDHashEntryHdr
-  {
-  public:
-    ProxyEntry(const void*) : mProxy(nullptr) {}
-    ProxyEntry(ProxyEntry&& aOther) :
-      mProxy(aOther.mProxy) { aOther.mProxy = nullptr; }
-    ~ProxyEntry() { delete mProxy; }
-
-    typedef uint64_t KeyType;
-    typedef const void* KeyTypePointer;
-
-    bool KeyEquals(const void* aKey) const
-    { return mProxy->ID() == (uint64_t)aKey; }
-
-    static const void* KeyToPointer(uint64_t aKey) { return (void*)aKey; }
-
-    static PLDHashNumber HashKey(const void* aKey) { return (uint64_t)aKey; }
-
-    enum { ALLOW_MEMMOVE = true };
-
-    ProxyAccessible* mProxy;
-  };
-
-  uint32_t AddSubtree(ProxyAccessible* aParent,
-                      const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
-                      uint32_t aIdxInParent);
-
-  nsTArray<DocAccessibleParent*> mChildDocs;
-  DocAccessibleParent* mParentDoc;
-
-  /*
-   * Conceptually this is a map from IDs to proxies, but we store the ID in the
-   * proxy object so we can't use a real map.
-   */
-  nsTHashtable<ProxyEntry> mAccessibles;
-};
-
-}
-}
-
-#endif
deleted file mode 100644
--- a/accessible/ipc/PDocAccessible.ipdl
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 protocol PContent;
-
-namespace mozilla {
-namespace a11y {
-
-struct AccessibleData
-{
-  uint64_t ID;
-  uint32_t Role;
-  uint32_t ChildrenCount;
-  nsString Name;
-};
-
-struct ShowEventData
-{
-  uint64_t ID;
-  uint32_t Idx;
-  AccessibleData[] NewTree;
-};
-
-protocol PDocAccessible
-{
-  manager PContent;
-
-parent:
-  __delete__();
-
-  /*
-   * Notify the parent process the document in the child process is firing an
-   * event.
-   */
-  Event(uint32_t type);
-  ShowEvent(ShowEventData data);
-  HideEvent(uint64_t aRootID);
-};
-
-}
-}
deleted file mode 100644
--- a/accessible/ipc/ProxyAccessible.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "ProxyAccessible.h"
-#include "DocAccessibleParent.h"
-#include "mozilla/a11y/Platform.h"
-
-namespace mozilla {
-namespace a11y {
-
-void
-ProxyAccessible::Shutdown()
-{
-  MOZ_ASSERT(!mOuterDoc);
-
-  uint32_t childCount = mChildren.Length();
-  for (uint32_t idx = 0; idx < childCount; idx++)
-    mChildren[idx]->Shutdown();
-
-  mChildren.Clear();
-  ProxyDestroyed(this);
-  mDoc->RemoveAccessible(this);
-}
-
-void
-ProxyAccessible::SetChildDoc(DocAccessibleParent* aParent)
-{
-  if (aParent) {
-    MOZ_ASSERT(mChildren.IsEmpty());
-    mChildren.AppendElement(aParent);
-    mOuterDoc = true;
-  } else {
-    MOZ_ASSERT(mChildren.Length() == 1);
-    mChildren.Clear();
-    mOuterDoc = false;
-  }
-}
-}
-}
deleted file mode 100644
--- a/accessible/ipc/ProxyAccessible.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_a11y_ProxyAccessible_h
-#define mozilla_a11y_ProxyAccessible_h
-
-#include "mozilla/a11y/Role.h"
-#include "nsString.h"
-#include "nsTArray.h"
-
-namespace mozilla {
-namespace a11y {
-
-class DocAccessibleParent;
-
-class ProxyAccessible
-{
-public:
-
-  ProxyAccessible(uint64_t aID, ProxyAccessible* aParent,
-                  DocAccessibleParent* aDoc, role aRole,
-                  const nsString& aName) :
-     mParent(aParent), mDoc(aDoc), mID(aID), mRole(aRole), mOuterDoc(false), mName(aName)
-  {
-    MOZ_COUNT_CTOR(ProxyAccessible);
-  }
-  ~ProxyAccessible() { MOZ_COUNT_DTOR(ProxyAccessible); }
-
-  void AddChildAt(uint32_t aIdx, ProxyAccessible* aChild)
-  { mChildren.InsertElementAt(aIdx, aChild); }
-
-  uint32_t ChildrenCount() const { return mChildren.Length(); }
-
-  void Shutdown();
-
-  void SetChildDoc(DocAccessibleParent*);
-
-  /**
-   * Get the role of the accessible we're proxying.
-   */
-  role Role() const { return mRole; }
-
-  /**
-   * Allow the platform to store a pointers worth of data on us.
-   */
-  uintptr_t GetWrapper() const { return mWrapper; }
-  void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
-
-  /*
-   * Return the ID of the accessible being proxied.
-   */
-  uint64_t ID() const { return mID; }
-
-protected:
-  ProxyAccessible() :
-    mParent(nullptr), mDoc(nullptr) { MOZ_COUNT_CTOR(ProxyAccessible); }
-
-protected:
-  ProxyAccessible* mParent;
-
-private:
-  nsTArray<ProxyAccessible*> mChildren;
-  DocAccessibleParent* mDoc;
-  uintptr_t mWrapper;
-  uint64_t mID;
-  role mRole : 31;
-  bool mOuterDoc : 1;
-  nsString mName;
-};
-
-}
-}
-
-#endif
deleted file mode 100644
--- a/accessible/ipc/moz.build
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- 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/.
-
-IPDL_SOURCES += ['PDocAccessible.ipdl']
-
-EXPORTS.mozilla.a11y += [
-    'DocAccessibleChild.h',
-    'DocAccessibleParent.h',
-    'ProxyAccessible.h'
-]
-
-SOURCES += [
-    'DocAccessibleChild.cpp',
-    'DocAccessibleParent.cpp',
-    'ProxyAccessible.cpp'
-]
-
-LOCAL_INCLUDES += [
-    '../base',
-    '../generic',
-    ]
-
-FINAL_LIBRARY = 'xul'
-
-include('/ipc/chromium/chromium-config.mozbuild')
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -28,25 +28,16 @@ PlatformInit()
 {
 }
 
 void
 PlatformShutdown()
 {
 }
 
-void
-ProxyCreated(ProxyAccessible*)
-{
-}
-
-void
-ProxyDestroyed(ProxyAccessible*)
-{
-}
 }
 }
 
 @interface GeckoNSApplication(a11y)
 -(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
 @end
 
 @implementation GeckoNSApplication(a11y)
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -10,14 +10,14 @@ if CONFIG['MOZ_ENABLE_GTK']:
     DIRS += ['atk']
 elif toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 else:
     DIRS += ['other']
 
-DIRS += ['base', 'generic', 'html', 'interfaces', 'ipc', 'jsat', 'xpcom']
+DIRS += ['base', 'generic', 'html', 'interfaces', 'jsat', 'xpcom']
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['xul']
 
 TEST_DIRS += ['tests/mochitest']
--- a/accessible/other/Platform.cpp
+++ b/accessible/other/Platform.cpp
@@ -13,18 +13,8 @@ void
 a11y::PlatformInit()
 {
 }
 
 void
 a11y::PlatformShutdown()
 {
 }
-
-void
-a11y::ProxyCreated(ProxyAccessible*)
-{
-}
-
-void
-a11y::ProxyDestroyed(ProxyAccessible*)
-{
-}
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -29,17 +29,8 @@ a11y::PlatformInit()
 void
 a11y::PlatformShutdown()
 {
   ::DestroyCaret();
 
   nsWinUtils::ShutdownWindowEmulation();
 }
 
-void
-a11y::ProxyCreated(ProxyAccessible*)
-{
-}
-
-void
-a11y::ProxyDestroyed(ProxyAccessible*)
-{
-}
--- a/b2g/config/dolphin/config.json
+++ b/b2g/config/dolphin/config.json
@@ -14,17 +14,21 @@
         "{objdir}/dist/b2g-*.tar.gz",
         "{workdir}/sources.xml"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/scx15_sp7715ga/*.img", "out/target/product/scx15_sp7715ga/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1"
     },
     "b2g_manifest": "dolphin.xml",
     "b2g_manifest_intree": true,
     "additional_source_tarballs": [],
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/config.json
+++ b/b2g/config/flame-kk/config.json
@@ -17,17 +17,21 @@
         "{objdir}/dist/b2g-update/*.mar"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/flame/*.img", "out/target/product/flame/"],
         ["{workdir}/boot.img", "out/target/product/flame/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly",
         "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
     },
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
--- a/b2g/config/flame/config.json
+++ b/b2g/config/flame/config.json
@@ -17,17 +17,21 @@
         "{objdir}/dist/b2g-update/*.mar"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/flame/*.img", "out/target/product/flame/"],
         ["{workdir}/boot.img", "out/target/product/flame/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly",
         "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
     },
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "7f097f40e32ecba580890ce1d3df2a493641bdec", 
+    "revision": "aa3ab2d389dce3ba351a897b4ae56f1fe9e1780d", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/config.json
+++ b/b2g/config/hamachi/config.json
@@ -11,16 +11,22 @@
         "{workdir}/sources.xml"
     ],
     "public_upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
         "{workdir}/sources.xml",
         "{workdir}/out/target/product/hamachi/*.mar"
     ],
+    "zip_files": [
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
+    ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly"
     },
     "b2g_manifest": "hamachi.xml",
     "b2g_manifest_intree": true,
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/config.json
+++ b/b2g/config/helix/config.json
@@ -16,17 +16,21 @@
         "{workdir}/sources.xml",
         "{objdir}/dist/b2g-update/*.mar"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/helix/*.img", "out/target/product/helix/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "ANDROIDFS_DIR": "{workdir}/helix-ics",
         "B2G_UPDATE_CHANNEL": "nightly"
     },
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/config.json
+++ b/b2g/config/nexus-4/config.json
@@ -17,17 +17,21 @@
         "{objdir}/dist/b2g-update/*.mar"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/mako/*.img", "out/target/product/mako/"],
         ["{workdir}/boot.img", "out/target/product/mako/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly"
     },
     "b2g_manifest": "nexus-4.xml",
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/config.json
+++ b/b2g/config/wasabi/config.json
@@ -11,17 +11,21 @@
         "{workdir}/sources.xml"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/wasabi/*.img", "out/target/product/wasabi/"],
         ["{workdir}/boot.img", "out/target/product/wasabi/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{workdir}/profile.sh",
+        ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+        ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+        ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
     ],
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1"
     },
     "b2g_manifest": "wasabi.xml",
     "b2g_manifest_intree": true,
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e280591881d44b80f456bc27e12d9114c218868"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a23d2c490b39c4699c9375e25c4acdf396a2fa85"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="4f9042d3a705307849a6f63961eaaaa2e1d85d77"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1608,16 +1608,17 @@ pref("loop.legal.ToS_url", "https://call
 pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/");
 pref("loop.do_not_disturb", false);
 pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/Firefox-Long.ogg");
 pref("loop.retry_delay.start", 60000);
 pref("loop.retry_delay.limit", 300000);
 pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
 pref("loop.feedback.product", "Loop");
 pref("loop.debug.loglevel", "Error");
+pref("loop.debug.dispatcher", false);
 pref("loop.debug.websocket", false);
 pref("loop.debug.sdk", false);
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
 
--- a/browser/base/content/newtab/updater.js
+++ b/browser/base/content/newtab/updater.js
@@ -15,34 +15,32 @@ let gUpdater = {
    * @param aCallback The callback to call when finished.
    */
   updateGrid: function Updater_updateGrid(aCallback) {
     let links = gLinks.getLinks().slice(0, gGrid.cells.length);
 
     // Find all sites that remain in the grid.
     let sites = this._findRemainingSites(links);
 
-    let self = this;
-
     // Remove sites that are no longer in the grid.
-    this._removeLegacySites(sites, function () {
+    this._removeLegacySites(sites, () => {
       // Freeze all site positions so that we can move their DOM nodes around
       // without any visual impact.
-      self._freezeSitePositions(sites);
+      this._freezeSitePositions(sites);
 
       // Move the sites' DOM nodes to their new position in the DOM. This will
       // have no visual effect as all the sites have been frozen and will
       // remain in their current position.
-      self._moveSiteNodes(sites);
+      this._moveSiteNodes(sites);
 
       // Now it's time to animate the sites actually moving to their new
       // positions.
-      self._rearrangeSites(sites, function () {
+      this._rearrangeSites(sites, () => {
         // Try to fill empty cells and finish.
-        self._fillEmptyCells(links, aCallback);
+        this._fillEmptyCells(links, aCallback);
 
         // Update other pages that might be open to keep them synced.
         gAllPages.update(gPage);
       });
     });
   },
 
   /**
--- a/browser/components/loop/content/conversation.html
+++ b/browser/components/loop/content/conversation.html
@@ -25,13 +25,19 @@
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
 
     <script type="text/javascript" src="loop/shared/js/utils.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/mixins.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
     <script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
+    <script type="text/javascript" src="loop/shared/js/actions.js"></script>
+    <script type="text/javascript" src="loop/shared/js/validate.js"></script>
+    <script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
+    <script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
+    <script type="text/javascript" src="loop/js/conversationViews.js"></script>
     <script type="text/javascript" src="loop/shared/js/websocket.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
+    <script type="text/javascript" src="loop/js/conversationViews.js"></script>
     <script type="text/javascript" src="loop/js/conversation.js"></script>
   </body>
 </html>
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -10,16 +10,22 @@ loop.Client = (function($) {
   "use strict";
 
   // The expected properties to be returned from the POST /call-url/ request.
   var expectedCallUrlProperties = ["callUrl", "expiresAt"];
 
   // The expected properties to be returned from the GET /calls request.
   var expectedCallProperties = ["calls"];
 
+  // THe expected properties to be returned from the POST /calls request.
+  var expectedPostCallProperties = [
+    "apiKey", "callId", "progressURL",
+    "sessionId", "sessionToken", "websocketToken"
+  ];
+
   /**
    * Loop server client.
    *
    * @param {Object} settings Settings object.
    */
   function Client(settings) {
     if (!settings) {
       settings = {};
@@ -205,16 +211,54 @@ loop.Client = (function($) {
           return;
         }
 
         this._requestCallUrlInternal(nickname, cb);
       }.bind(this));
     },
 
     /**
+     * Sets up an outgoing call, getting the relevant data from the server.
+     *
+     * Callback parameters:
+     * - err null on successful registration, non-null otherwise.
+     * - result an object of the obtained data for starting the call, if successful
+     *
+     * @param {Array} calleeIds an array of emails and phone numbers.
+     * @param {String} callType the type of call.
+     * @param {Function} cb Callback(err, result)
+     */
+    setupOutgoingCall: function(calleeIds, callType, cb) {
+      this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.FXA,
+        "/calls", "POST", {
+          calleeId: calleeIds,
+          callType: callType
+        },
+        function (err, responseText) {
+          if (err) {
+            this._failureHandler(cb, err);
+            return;
+          }
+
+          try {
+            var postData = JSON.parse(responseText);
+
+            var outgoingCallData = this._validate(postData,
+              expectedPostCallProperties);
+
+            cb(null, outgoingCallData);
+          } catch (err) {
+            console.log("Error requesting call info", err);
+            cb(err);
+          }
+        }.bind(this)
+      );
+    },
+
+    /**
      * Adds a value to a telemetry histogram, ignoring errors.
      *
      * @param  {string}  histogramId Name of the telemetry histogram to update.
      * @param  {integer} value       Value to add to the histogram.
      */
     _telemetryAdd: function(histogramId, value) {
       try {
         this.mozLoop.telemetryAdd(histogramId, value);
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -6,18 +6,19 @@
 
 /* jshint newcap:false, esnext:true */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.conversation = (function(mozL10n) {
   "use strict";
 
-  var sharedViews = loop.shared.views,
-      sharedModels = loop.shared.models;
+  var sharedViews = loop.shared.views;
+  var sharedModels = loop.shared.models;
+  var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
 
   var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
 
@@ -104,36 +105,33 @@ loop.conversation = (function(mozL10n) {
         props.secondary = videoButton;
       }
 
       return props;
     },
 
     render: function() {
       /* jshint ignore:start */
-      var btnClassAccept = "btn btn-accept";
-      var btnClassDecline = "btn btn-error btn-decline";
-      var conversationPanelClass = "incoming-call";
       var dropdownMenuClassesDecline = React.addons.classSet({
         "native-dropdown-menu": true,
         "conversation-window-dropdown": true,
         "visually-hidden": !this.state.showDeclineMenu
       });
       return (
-        React.DOM.div({className: conversationPanelClass}, 
+        React.DOM.div({className: "call-window"}, 
           React.DOM.h2(null, mozL10n.get("incoming_call_title2")), 
-          React.DOM.div({className: "btn-group incoming-call-action-group"}, 
+          React.DOM.div({className: "btn-group call-action-group"}, 
 
-            React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}), 
+            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
 
             React.DOM.div({className: "btn-chevron-menu-group"}, 
               React.DOM.div({className: "btn-group-chevron"}, 
                 React.DOM.div({className: "btn-group"}, 
 
-                  React.DOM.button({className: btnClassDecline, 
+                  React.DOM.button({className: "btn btn-error btn-decline", 
                           onClick: this._handleDecline}, 
                     mozL10n.get("incoming_call_cancel_button")
                   ), 
                   React.DOM.div({className: "btn-chevron", 
                        onClick: this._toggleDeclineMenu}
                   )
                 ), 
 
@@ -141,21 +139,21 @@ loop.conversation = (function(mozL10n) {
                   React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock}, 
                     mozL10n.get("incoming_call_cancel_and_block_button")
                   )
                 )
 
               )
             ), 
 
-            React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"}), 
+            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
 
             AcceptCallButton({mode: this._answerModeProps()}), 
 
-            React.DOM.div({className: "fx-embedded-incoming-call-button-spacer"})
+            React.DOM.div({className: "fx-embedded-call-button-spacer"})
 
           )
         )
       );
       /* jshint ignore:end */
     }
   });
 
@@ -365,18 +363,20 @@ loop.conversation = (function(mozL10n) {
      * call view if appropriate.
      */
     _setupWebSocket: function() {
       this._websocket = new loop.CallConnectionWebSocket({
         url: this.props.conversation.get("progressURL"),
         websocketToken: this.props.conversation.get("websocketToken"),
         callId: this.props.conversation.get("callId"),
       });
-      this._websocket.promiseConnect().then(function() {
-        this.setState({callStatus: "incoming"});
+      this._websocket.promiseConnect().then(function(progressStatus) {
+        this.setState({
+          callStatus: progressStatus === "terminated" ? "close" : "incoming"
+        });
       }.bind(this), function() {
         this._handleSessionError();
         return;
       }.bind(this));
 
       this._websocket.on("progress", this._handleWebSocketProgress, this);
     },
 
@@ -485,16 +485,65 @@ loop.conversation = (function(mozL10n) {
     _handleSessionError: function() {
       // XXX Not the ideal response, but bug 1047410 will be replacing
       // this by better "call failed" UI.
       this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
     },
   });
 
   /**
+   * Master controller view for handling if incoming or outgoing calls are
+   * in progress, and hence, which view to display.
+   */
+  var ConversationControllerView = React.createClass({displayName: 'ConversationControllerView',
+    propTypes: {
+      // XXX Old types required for incoming call view.
+      client: React.PropTypes.instanceOf(loop.Client).isRequired,
+      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
+                         .isRequired,
+      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
+                          .isRequired,
+      sdk: React.PropTypes.object.isRequired,
+
+      // XXX New types for OutgoingConversationView
+      store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
+    },
+
+    getInitialState: function() {
+      return this.props.store.attributes;
+    },
+
+    componentWillMount: function() {
+      this.props.store.on("change:outgoing", function() {
+        this.setState(this.props.store.attributes);
+      }, this);
+    },
+
+    render: function() {
+      // Don't display anything, until we know what type of call we are.
+      if (this.state.outgoing === undefined) {
+        return null;
+      }
+
+      if (this.state.outgoing) {
+        return (OutgoingConversationView({
+          store: this.props.store}
+        ));
+      }
+
+      return (IncomingConversationView({
+        client: this.props.client, 
+        conversation: this.props.conversation, 
+        notifications: this.props.notifications, 
+        sdk: this.props.sdk}
+      ));
+    }
+  });
+
+  /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     // Plug in an alternate client ID mechanism, as localStorage and cookies
@@ -504,45 +553,66 @@ loop.conversation = (function(mozL10n) {
         callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
       },
       set: function(guid, callback) {
         navigator.mozLoop.setLoopCharPref("ot.guid", guid);
         callback(null);
       }
     });
 
-    document.body.classList.add(loop.shared.utils.getTargetPlatform());
+    var dispatcher = new loop.Dispatcher();
+    var client = new loop.Client();
+    var conversationStore = new loop.store.ConversationStore({}, {
+      client: client,
+      dispatcher: dispatcher
+    });
 
-    var client = new loop.Client();
+    // XXX For now key this on the pref, but this should really be
+    // set by the information from the mozLoop API when we can get it (bug 1072323).
+    var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
+
+    // XXX Old class creation for the incoming conversation view, whilst
+    // we transition across (bug 1072323).
     var conversation = new sharedModels.ConversationModel(
       {},                // Model attributes
       {sdk: window.OT}   // Model dependencies
     );
     var notifications = new sharedModels.NotificationCollection();
 
+    // Obtain the callId and pass it through
+    var helper = new loop.shared.utils.Helper();
+    var locationHash = helper.locationHash();
+    var callId;
+    if (locationHash) {
+      callId = locationHash.match(/\#incoming\/(.*)/)[1]
+      conversation.set("callId", callId);
+    }
+
     window.addEventListener("unload", function(event) {
       // Handle direct close of dialog box via [x] control.
       navigator.mozLoop.releaseCallData(conversation.get("callId"));
     });
 
-    // Obtain the callId and pass it to the conversation
-    var helper = new loop.shared.utils.Helper();
-    var locationHash = helper.locationHash();
-    if (locationHash) {
-      conversation.set("callId", locationHash.match(/\#incoming\/(.*)/)[1]);
-    }
+    document.body.classList.add(loop.shared.utils.getTargetPlatform());
 
-    React.renderComponent(IncomingConversationView({
+    React.renderComponent(ConversationControllerView({
+      store: conversationStore, 
       client: client, 
       conversation: conversation, 
       notifications: notifications, 
       sdk: window.OT}
     ), document.querySelector('#main'));
+
+    dispatcher.dispatch(new loop.shared.actions.GatherCallData({
+      callId: callId,
+      calleeId: outgoingEmail
+    }));
   }
 
   return {
+    ConversationControllerView: ConversationControllerView,
     IncomingConversationView: IncomingConversationView,
     IncomingCallView: IncomingCallView,
     init: init
   };
 })(document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.conversation.init);
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -6,18 +6,19 @@
 
 /* jshint newcap:false, esnext:true */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.conversation = (function(mozL10n) {
   "use strict";
 
-  var sharedViews = loop.shared.views,
-      sharedModels = loop.shared.models;
+  var sharedViews = loop.shared.views;
+  var sharedModels = loop.shared.models;
+  var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
 
   var IncomingCallView = React.createClass({
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
 
@@ -104,36 +105,33 @@ loop.conversation = (function(mozL10n) {
         props.secondary = videoButton;
       }
 
       return props;
     },
 
     render: function() {
       /* jshint ignore:start */
-      var btnClassAccept = "btn btn-accept";
-      var btnClassDecline = "btn btn-error btn-decline";
-      var conversationPanelClass = "incoming-call";
       var dropdownMenuClassesDecline = React.addons.classSet({
         "native-dropdown-menu": true,
         "conversation-window-dropdown": true,
         "visually-hidden": !this.state.showDeclineMenu
       });
       return (
-        <div className={conversationPanelClass}>
+        <div className="call-window">
           <h2>{mozL10n.get("incoming_call_title2")}</h2>
-          <div className="btn-group incoming-call-action-group">
+          <div className="btn-group call-action-group">
 
-            <div className="fx-embedded-incoming-call-button-spacer"></div>
+            <div className="fx-embedded-call-button-spacer"></div>
 
             <div className="btn-chevron-menu-group">
               <div className="btn-group-chevron">
                 <div className="btn-group">
 
-                  <button className={btnClassDecline}
+                  <button className="btn btn-error btn-decline"
                           onClick={this._handleDecline}>
                     {mozL10n.get("incoming_call_cancel_button")}
                   </button>
                   <div className="btn-chevron"
                        onClick={this._toggleDeclineMenu}>
                   </div>
                 </div>
 
@@ -141,21 +139,21 @@ loop.conversation = (function(mozL10n) {
                   <li className="btn-block" onClick={this._handleDeclineBlock}>
                     {mozL10n.get("incoming_call_cancel_and_block_button")}
                   </li>
                 </ul>
 
               </div>
             </div>
 
-            <div className="fx-embedded-incoming-call-button-spacer"></div>
+            <div className="fx-embedded-call-button-spacer"></div>
 
             <AcceptCallButton mode={this._answerModeProps()} />
 
-            <div className="fx-embedded-incoming-call-button-spacer"></div>
+            <div className="fx-embedded-call-button-spacer"></div>
 
           </div>
         </div>
       );
       /* jshint ignore:end */
     }
   });
 
@@ -365,18 +363,20 @@ loop.conversation = (function(mozL10n) {
      * call view if appropriate.
      */
     _setupWebSocket: function() {
       this._websocket = new loop.CallConnectionWebSocket({
         url: this.props.conversation.get("progressURL"),
         websocketToken: this.props.conversation.get("websocketToken"),
         callId: this.props.conversation.get("callId"),
       });
-      this._websocket.promiseConnect().then(function() {
-        this.setState({callStatus: "incoming"});
+      this._websocket.promiseConnect().then(function(progressStatus) {
+        this.setState({
+          callStatus: progressStatus === "terminated" ? "close" : "incoming"
+        });
       }.bind(this), function() {
         this._handleSessionError();
         return;
       }.bind(this));
 
       this._websocket.on("progress", this._handleWebSocketProgress, this);
     },
 
@@ -485,16 +485,65 @@ loop.conversation = (function(mozL10n) {
     _handleSessionError: function() {
       // XXX Not the ideal response, but bug 1047410 will be replacing
       // this by better "call failed" UI.
       this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
     },
   });
 
   /**
+   * Master controller view for handling if incoming or outgoing calls are
+   * in progress, and hence, which view to display.
+   */
+  var ConversationControllerView = React.createClass({
+    propTypes: {
+      // XXX Old types required for incoming call view.
+      client: React.PropTypes.instanceOf(loop.Client).isRequired,
+      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
+                         .isRequired,
+      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
+                          .isRequired,
+      sdk: React.PropTypes.object.isRequired,
+
+      // XXX New types for OutgoingConversationView
+      store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
+    },
+
+    getInitialState: function() {
+      return this.props.store.attributes;
+    },
+
+    componentWillMount: function() {
+      this.props.store.on("change:outgoing", function() {
+        this.setState(this.props.store.attributes);
+      }, this);
+    },
+
+    render: function() {
+      // Don't display anything, until we know what type of call we are.
+      if (this.state.outgoing === undefined) {
+        return null;
+      }
+
+      if (this.state.outgoing) {
+        return (<OutgoingConversationView
+          store={this.props.store}
+        />);
+      }
+
+      return (<IncomingConversationView
+        client={this.props.client}
+        conversation={this.props.conversation}
+        notifications={this.props.notifications}
+        sdk={this.props.sdk}
+      />);
+    }
+  });
+
+  /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     // Plug in an alternate client ID mechanism, as localStorage and cookies
@@ -504,45 +553,66 @@ loop.conversation = (function(mozL10n) {
         callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
       },
       set: function(guid, callback) {
         navigator.mozLoop.setLoopCharPref("ot.guid", guid);
         callback(null);
       }
     });
 
-    document.body.classList.add(loop.shared.utils.getTargetPlatform());
+    var dispatcher = new loop.Dispatcher();
+    var client = new loop.Client();
+    var conversationStore = new loop.store.ConversationStore({}, {
+      client: client,
+      dispatcher: dispatcher
+    });
 
-    var client = new loop.Client();
+    // XXX For now key this on the pref, but this should really be
+    // set by the information from the mozLoop API when we can get it (bug 1072323).
+    var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
+
+    // XXX Old class creation for the incoming conversation view, whilst
+    // we transition across (bug 1072323).
     var conversation = new sharedModels.ConversationModel(
       {},                // Model attributes
       {sdk: window.OT}   // Model dependencies
     );
     var notifications = new sharedModels.NotificationCollection();
 
+    // Obtain the callId and pass it through
+    var helper = new loop.shared.utils.Helper();
+    var locationHash = helper.locationHash();
+    var callId;
+    if (locationHash) {
+      callId = locationHash.match(/\#incoming\/(.*)/)[1]
+      conversation.set("callId", callId);
+    }
+
     window.addEventListener("unload", function(event) {
       // Handle direct close of dialog box via [x] control.
       navigator.mozLoop.releaseCallData(conversation.get("callId"));
     });
 
-    // Obtain the callId and pass it to the conversation
-    var helper = new loop.shared.utils.Helper();
-    var locationHash = helper.locationHash();
-    if (locationHash) {
-      conversation.set("callId", locationHash.match(/\#incoming\/(.*)/)[1]);
-    }
+    document.body.classList.add(loop.shared.utils.getTargetPlatform());
 
-    React.renderComponent(<IncomingConversationView
+    React.renderComponent(<ConversationControllerView
+      store={conversationStore}
       client={client}
       conversation={conversation}
       notifications={notifications}
       sdk={window.OT}
     />, document.querySelector('#main'));
+
+    dispatcher.dispatch(new loop.shared.actions.GatherCallData({
+      callId: callId,
+      calleeId: outgoingEmail
+    }));
   }
 
   return {
+    ConversationControllerView: ConversationControllerView,
     IncomingConversationView: IncomingConversationView,
     IncomingCallView: IncomingCallView,
     init: init
   };
 })(document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.conversation.init);
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -0,0 +1,126 @@
+/** @jsx React.DOM */
+
+/* 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/. */
+
+/* global loop:true, React */
+
+var loop = loop || {};
+loop.conversationViews = (function(mozL10n) {
+
+  var CALL_STATES = loop.store.CALL_STATES;
+
+  /**
+   * Displays details of the incoming/outgoing conversation
+   * (name, link, audio/video type etc).
+   *
+   * Allows the view to be extended with different buttons and progress
+   * via children properties.
+   */
+  var ConversationDetailView = React.createClass({displayName: 'ConversationDetailView',
+    propTypes: {
+      calleeId: React.PropTypes.string,
+    },
+
+    render: function() {
+      document.title = this.props.calleeId;
+
+      return (
+        React.DOM.div({className: "call-window"}, 
+          React.DOM.h2(null, this.props.calleeId), 
+          React.DOM.div(null, this.props.children)
+        )
+      );
+    }
+  });
+
+  /**
+   * View for pending conversations. Displays a cancel button and appropriate
+   * pending/ringing strings.
+   */
+  var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
+    propTypes: {
+      callState: React.PropTypes.string,
+      calleeId: React.PropTypes.string,
+    },
+
+    render: function() {
+      var pendingStateString;
+      if (this.props.callState === CALL_STATES.ALERTING) {
+        pendingStateString = mozL10n.get("call_progress_ringing_description");
+      } else {
+        pendingStateString = mozL10n.get("call_progress_connecting_description");
+      }
+
+      return (
+        ConversationDetailView({calleeId: this.props.calleeId}, 
+
+          React.DOM.p({className: "btn-label"}, pendingStateString), 
+
+          React.DOM.div({className: "btn-group call-action-group"}, 
+            React.DOM.div({className: "fx-embedded-call-button-spacer"}), 
+              React.DOM.button({className: "btn btn-cancel"}, 
+                mozL10n.get("initiate_call_cancel_button")
+              ), 
+            React.DOM.div({className: "fx-embedded-call-button-spacer"})
+          )
+
+        )
+      );
+    }
+  });
+
+  /**
+   * Call failed view. Displayed when a call fails.
+   */
+  var CallFailedView = React.createClass({displayName: 'CallFailedView',
+    render: function() {
+      return (
+        React.DOM.div({className: "call-window"}, 
+          React.DOM.h2(null, mozL10n.get("generic_failure_title"))
+        )
+      );
+    }
+  });
+
+  /**
+   * Master View Controller for outgoing calls. This manages
+   * the different views that need displaying.
+   */
+  var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
+    propTypes: {
+      store: React.PropTypes.instanceOf(
+        loop.store.ConversationStore).isRequired
+    },
+
+    getInitialState: function() {
+      return this.props.store.attributes;
+    },
+
+    componentWillMount: function() {
+      this.props.store.on("change", function() {
+        this.setState(this.props.store.attributes);
+      }, this);
+    },
+
+    render: function() {
+      if (this.state.callState === CALL_STATES.TERMINATED) {
+        return (CallFailedView(null));
+      }
+
+      return (PendingConversationView({
+        callState: this.state.callState, 
+        calleeId: this.state.calleeId}
+      ))
+    }
+  });
+
+  return {
+    PendingConversationView: PendingConversationView,
+    ConversationDetailView: ConversationDetailView,
+    CallFailedView: CallFailedView,
+    OutgoingConversationView: OutgoingConversationView
+  };
+
+})(document.mozL10n || navigator.mozL10n);
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -0,0 +1,126 @@
+/** @jsx React.DOM */
+
+/* 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/. */
+
+/* global loop:true, React */
+
+var loop = loop || {};
+loop.conversationViews = (function(mozL10n) {
+
+  var CALL_STATES = loop.store.CALL_STATES;
+
+  /**
+   * Displays details of the incoming/outgoing conversation
+   * (name, link, audio/video type etc).
+   *
+   * Allows the view to be extended with different buttons and progress
+   * via children properties.
+   */
+  var ConversationDetailView = React.createClass({
+    propTypes: {
+      calleeId: React.PropTypes.string,
+    },
+
+    render: function() {
+      document.title = this.props.calleeId;
+
+      return (
+        <div className="call-window">
+          <h2>{this.props.calleeId}</h2>
+          <div>{this.props.children}</div>
+        </div>
+      );
+    }
+  });
+
+  /**
+   * View for pending conversations. Displays a cancel button and appropriate
+   * pending/ringing strings.
+   */
+  var PendingConversationView = React.createClass({
+    propTypes: {
+      callState: React.PropTypes.string,
+      calleeId: React.PropTypes.string,
+    },
+
+    render: function() {
+      var pendingStateString;
+      if (this.props.callState === CALL_STATES.ALERTING) {
+        pendingStateString = mozL10n.get("call_progress_ringing_description");
+      } else {
+        pendingStateString = mozL10n.get("call_progress_connecting_description");
+      }
+
+      return (
+        <ConversationDetailView calleeId={this.props.calleeId}>
+
+          <p className="btn-label">{pendingStateString}</p>
+
+          <div className="btn-group call-action-group">
+            <div className="fx-embedded-call-button-spacer"></div>
+              <button className="btn btn-cancel">
+                {mozL10n.get("initiate_call_cancel_button")}
+              </button>
+            <div className="fx-embedded-call-button-spacer"></div>
+          </div>
+
+        </ConversationDetailView>
+      );
+    }
+  });
+
+  /**
+   * Call failed view. Displayed when a call fails.
+   */
+  var CallFailedView = React.createClass({
+    render: function() {
+      return (
+        <div className="call-window">
+          <h2>{mozL10n.get("generic_failure_title")}</h2>
+        </div>
+      );
+    }
+  });
+
+  /**
+   * Master View Controller for outgoing calls. This manages
+   * the different views that need displaying.
+   */
+  var OutgoingConversationView = React.createClass({
+    propTypes: {
+      store: React.PropTypes.instanceOf(
+        loop.store.ConversationStore).isRequired
+    },
+
+    getInitialState: function() {
+      return this.props.store.attributes;
+    },
+
+    componentWillMount: function() {
+      this.props.store.on("change", function() {
+        this.setState(this.props.store.attributes);
+      }, this);
+    },
+
+    render: function() {
+      if (this.state.callState === CALL_STATES.TERMINATED) {
+        return (<CallFailedView />);
+      }
+
+      return (<PendingConversationView
+        callState={this.state.callState}
+        calleeId={this.state.calleeId}
+      />)
+    }
+  });
+
+  return {
+    PendingConversationView: PendingConversationView,
+    ConversationDetailView: ConversationDetailView,
+    CallFailedView: CallFailedView,
+    OutgoingConversationView: OutgoingConversationView
+  };
+
+})(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -218,60 +218,61 @@
   width: 50%;
 }
 
 /* Call ended view */
 .call-ended p {
   text-align: center;
 }
 
-/* Incoming call */
+/* General Call (incoming or outgoing). */
 
 /*
  * Height matches the height of the docked window
  * but the UI breaks when you pop out
  * Bug 1040985
  */
-.incoming-call {
+.call-window {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: space-between;
   min-height: 230px;
 }
 
-.incoming-call-action-group {
+.call-action-group {
   display: flex;
   padding: 2.5em 0 0 0;
   width: 100%;
   justify-content: space-around;
 }
 
-.incoming-call-action-group > .btn {
+.call-action-group > .btn {
   margin-left: .5em;
+  height: 26px;
 }
 
-.incoming-call-action-group .btn-group-chevron,
-.incoming-call-action-group .btn-group {
+.call-action-group .btn-group-chevron,
+.call-action-group .btn-group {
   width: 100%;
 }
 
 /* XXX Once we get the incoming call avatar, bug 1047435, the H2 should
  * disappear from our markup, and we should remove this rule entirely.
  */
-.incoming-call h2 {
+.call-window h2 {
   font-size: 1.5em;
   font-weight: normal;
 
   /* compensate for reset.css overriding this; values borrowed from
      Firefox Mac html.css */
   margin: 0.83em 0;
 }
 
-.fx-embedded-incoming-call-button-spacer {
+.fx-embedded-call-button-spacer {
   display: flex;
   flex: 1;
 }
 
 /* Expired call url page */
 
 .expired-url-info {
   width: 400px;
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+/* global loop:true */
+
+var loop = loop || {};
+loop.shared = loop.shared || {};
+loop.shared.actions = (function() {
+  "use strict";
+
+  /**
+   * Actions are events that are triggered by the user, e.g. clicking a button,
+   * or by an async event, e.g. status received.
+   *
+   * They should be dispatched to stores via the dispatcher.
+   */
+
+  function Action(name, schema, values) {
+    var validatedData = new loop.validate.Validator(schema || {})
+                                         .validate(values || {});
+    for (var prop in validatedData)
+      this[prop] = validatedData[prop];
+
+    this.name = name;
+  }
+
+  Action.define = function(name, schema) {
+    return Action.bind(null, name, schema);
+  };
+
+  return {
+    /**
+     * Used to trigger gathering of initial call data.
+     */
+    GatherCallData: Action.define("gatherCallData", {
+      // XXX This may change when bug 1072323 is implemented.
+      // Optional: Specify the calleeId for an outgoing call
+      calleeId: [String, null],
+      // Specify the callId for an incoming call.
+      callId: [String, null]
+    }),
+
+    /**
+     * Used to cancel call setup.
+     */
+    CancelCall: Action.define("cancelCall", {
+    }),
+
+    /**
+     * Used to initiate connecting of a call with the relevant
+     * sessionData.
+     */
+    ConnectCall: Action.define("connectCall", {
+      // This object contains the necessary details for the
+      // connection of the websocket, and the SDK
+      sessionData: Object
+    }),
+
+    /**
+     * Used for notifying of connection progress state changes.
+     * The connection refers to the overall connection flow as indicated
+     * on the websocket.
+     */
+    ConnectionProgress: Action.define("connectionProgress", {
+      // The new connection state
+      state: String
+    }),
+
+    /**
+     * Used for notifying of connection failures.
+     */
+    ConnectionFailure: Action.define("connectionFailure", {
+      // A string relating to the reason the connection failed.
+      reason: String
+    })
+  };
+})();
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -0,0 +1,244 @@
+/* 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/. */
+
+/* global loop:true */
+
+var loop = loop || {};
+loop.store = (function() {
+
+  var sharedActions = loop.shared.actions;
+  var sharedUtils = loop.shared.utils;
+
+  var CALL_STATES = {
+    // The initial state of the view.
+    INIT: "init",
+    // The store is gathering the call data from the server.
+    GATHER: "gather",
+    // The websocket has connected to the server and is waiting
+    // for the other peer to connect to the websocket.
+    CONNECTING: "connecting",
+    // The websocket has received information that we're now alerting
+    // the peer.
+    ALERTING: "alerting",
+    // The call was terminated due to an issue during connection.
+    TERMINATED: "terminated"
+  };
+
+
+  var ConversationStore = Backbone.Model.extend({
+    defaults: {
+      // The current state of the call
+      callState: CALL_STATES.INIT,
+      // The reason if a call was terminated
+      callStateReason: undefined,
+      // The error information, if there was a failure
+      error: undefined,
+      // True if the call is outgoing, false if not, undefined if unknown
+      outgoing: undefined,
+      // The id of the person being called for outgoing calls
+      calleeId: undefined,
+      // The call type for the call.
+      // XXX Don't hard-code, this comes from the data in bug 1072323
+      callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
+
+      // Call Connection information
+      // The call id from the loop-server
+      callId: undefined,
+      // The connection progress url to connect the websocket
+      progressURL: undefined,
+      // The websocket token that allows connection to the progress url
+      websocketToken: undefined,
+      // SDK API key
+      apiKey: undefined,
+      // SDK session ID
+      sessionId: undefined,
+      // SDK session token
+      sessionToken: undefined
+    },
+
+    /**
+     * Constructor
+     *
+     * Options:
+     * - {loop.Dispatcher} dispatcher The dispatcher for dispatching actions and
+     *                                registering to consume actions.
+     * - {Object} client              A client object for communicating with the server.
+     *
+     * @param  {Object} attributes Attributes object.
+     * @param  {Object} options    Options object.
+     */
+    initialize: function(attributes, options) {
+      options = options || {};
+
+      if (!options.dispatcher) {
+        throw new Error("Missing option dispatcher");
+      }
+      if (!options.client) {
+        throw new Error("Missing option client");
+      }
+
+      this.client = options.client;
+      this.dispatcher = options.dispatcher;
+
+      this.dispatcher.register(this, [
+        "connectionFailure",
+        "connectionProgress",
+        "gatherCallData",
+        "connectCall"
+      ]);
+    },
+
+    /**
+     * Handles the connection failure action, setting the state to
+     * terminated.
+     *
+     * @param {sharedActions.ConnectionFailure} actionData The action data.
+     */
+    connectionFailure: function(actionData) {
+      this.set({
+        callState: CALL_STATES.TERMINATED,
+        callStateReason: actionData.reason
+      });
+    },
+
+    /**
+     * Handles the connection progress action, setting the next state
+     * appropriately.
+     *
+     * @param {sharedActions.ConnectionProgress} actionData The action data.
+     */
+    connectionProgress: function(actionData) {
+      // XXX Turn this into a state machine?
+      if (actionData.state === "alerting" &&
+          (this.get("callState") === CALL_STATES.CONNECTING ||
+           this.get("callState") === CALL_STATES.GATHER)) {
+        this.set({
+          callState: CALL_STATES.ALERTING
+        });
+      }
+      if (actionData.state === "connecting" &&
+          this.get("callState") === CALL_STATES.GATHER) {
+        this.set({
+          callState: CALL_STATES.CONNECTING
+        });
+      }
+    },
+
+    /**
+     * Handles the gather call data action, setting the state
+     * and starting to get the appropriate data for the type of call.
+     *
+     * @param {sharedActions.GatherCallData} actionData The action data.
+     */
+    gatherCallData: function(actionData) {
+      this.set({
+        calleeId: actionData.calleeId,
+        outgoing: !!actionData.calleeId,
+        callId: actionData.callId,
+        callState: CALL_STATES.GATHER
+      });
+
+      if (this.get("outgoing")) {
+        this._setupOutgoingCall();
+      } // XXX Else, other types aren't supported yet.
+    },
+
+    /**
+     * Handles the connect call action, this saves the appropriate
+     * data and starts the connection for the websocket to notify the
+     * server of progress.
+     *
+     * @param {sharedActions.ConnectCall} actionData The action data.
+     */
+    connectCall: function(actionData) {
+      this.set(actionData.sessionData);
+      this._connectWebSocket();
+    },
+
+    /**
+     * Obtains the outgoing call data from the server and handles the
+     * result.
+     */
+    _setupOutgoingCall: function() {
+      // XXX For now, we only have one calleeId, so just wrap that in an array.
+      this.client.setupOutgoingCall([this.get("calleeId")],
+        this.get("callType"),
+        function(err, result) {
+          if (err) {
+            console.error("Failed to get outgoing call data", err);
+            this.dispatcher.dispatch(
+              new sharedActions.ConnectionFailure({reason: "setup"}));
+            return;
+          }
+
+          // Success, dispatch a new action.
+          this.dispatcher.dispatch(
+            new sharedActions.ConnectCall({sessionData: result}));
+        }.bind(this)
+      );
+    },
+
+    /**
+     * Sets up and connects the websocket to the server. The websocket
+     * deals with sending and obtaining status via the server about the
+     * setup of the call.
+     */
+    _connectWebSocket: function() {
+      this._websocket = new loop.CallConnectionWebSocket({
+        url: this.get("progressURL"),
+        callId: this.get("callId"),
+        websocketToken: this.get("websocketToken")
+      });
+
+      this._websocket.promiseConnect().then(
+        function() {
+          this.dispatcher.dispatch(new sharedActions.ConnectionProgress({
+            // This is the websocket call state, i.e. waiting for the
+            // other end to connect to the server.
+            state: "connecting"
+          }));
+        }.bind(this),
+        function(error) {
+          console.error("Websocket failed to connect", error);
+          this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
+            reason: "websocket-setup"
+          }));
+        }.bind(this)
+      );
+
+      this._websocket.on("progress", this._handleWebSocketProgress, this);
+    },
+
+    /**
+     * Used to handle any progressed received from the websocket. This will
+     * dispatch new actions so that the data can be handled appropriately.
+     */
+    _handleWebSocketProgress: function(progressData) {
+      var action;
+
+      switch(progressData.state) {
+        case "terminated":
+          action = new sharedActions.ConnectionFailure({
+            reason: progressData.reason
+          });
+          break;
+        case "alerting":
+          action = new sharedActions.ConnectionProgress({
+            state: progressData.state
+          });
+          break;
+        default:
+          console.warn("Received unexpected state in _handleWebSocketProgress", progressData.state);
+          return;
+      }
+
+      this.dispatcher.dispatch(action);
+    }
+  });
+
+  return {
+    CALL_STATES: CALL_STATES,
+    ConversationStore: ConversationStore
+  };
+})();
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/dispatcher.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+/* global loop:true */
+
+/**
+ * The dispatcher for actions. This dispatches actions to stores registered
+ * for those actions.
+ *
+ * If stores need to perform async operations for actions, they should return
+ * straight away, and set up a new action for the changes if necessary.
+ *
+ * It is an error if a returned promise rejects - they should always pass.
+ */
+var loop = loop || {};
+loop.Dispatcher = (function() {
+
+  function Dispatcher() {
+    this._eventData = {};
+    this._actionQueue = [];
+    this._debug = loop.shared.utils.getBoolPreference("debug.dispatcher");
+  }
+
+  Dispatcher.prototype = {
+    /**
+     * Register a store to receive notifications of specific actions.
+     *
+     * @param {Object} store The store object to register
+     * @param {Array} eventTypes An array of action names
+     */
+    register: function(store, eventTypes) {
+      eventTypes.forEach(function(type) {
+        if (this._eventData.hasOwnProperty(type)) {
+          this._eventData[type].push(store);
+        } else {
+          this._eventData[type] = [store];
+        }
+      }.bind(this));
+    },
+
+    /**
+     * Dispatches an action to all registered stores.
+     */
+    dispatch: function(action) {
+      // Always put it on the queue, to make it simpler.
+      this._actionQueue.push(action);
+      this._dispatchNextAction();
+    },
+
+    /**
+     * Dispatches the next action in the queue if one is not already active.
+     */
+    _dispatchNextAction: function() {
+      if (!this._actionQueue.length || this._active) {
+        return;
+      }
+
+      var action = this._actionQueue.shift();
+      var type = action.name;
+
+      var registeredStores = this._eventData[type];
+      if (!registeredStores) {
+        console.warn("No stores registered for event type ", type);
+        return;
+      }
+
+      this._active = true;
+
+      if (this._debug) {
+        console.log("[Dispatcher] Dispatching action", action);
+      }
+
+      registeredStores.forEach(function(store) {
+        store[type](action);
+      });
+
+      this._active = false;
+      this._dispatchNextAction();
+    }
+  };
+
+  return Dispatcher;
+})();
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -5,16 +5,24 @@
 /* global loop:true */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.utils = (function() {
   "use strict";
 
   /**
+   * Call types used for determining if a call is audio/video or audio-only.
+   */
+  var CALL_TYPES = {
+    AUDIO_VIDEO: "audio-video",
+    AUDIO_ONLY: "audio"
+  };
+
+  /**
    * Used for adding different styles to the panel
    * @returns {String} Corresponds to the client platform
    * */
   function getTargetPlatform() {
     var platform="unknown_platform";
 
     if (navigator.platform.indexOf("Win") !== -1) {
       platform = "windows";
@@ -72,13 +80,14 @@ loop.shared.utils = (function() {
     },
 
     locationHash: function() {
       return window.location.hash;
     }
   };
 
   return {
+    CALL_TYPES: CALL_TYPES,
     Helper: Helper,
     getTargetPlatform: getTargetPlatform,
     getBoolPreference: getBoolPreference
   };
 })();
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/validate.js
@@ -0,0 +1,127 @@
+/* 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/. */
+
+/* jshint unused:false */
+
+var loop = loop || {};
+loop.validate = (function() {
+  "use strict";
+
+  /**
+   * Computes the difference between two arrays.
+   *
+   * @param  {Array} arr1 First array
+   * @param  {Array} arr2 Second array
+   * @return {Array}      Array difference
+   */
+  function difference(arr1, arr2) {
+    return arr1.filter(function(item) {
+      return arr2.indexOf(item) === -1;
+    });
+  }
+
+  /**
+   * Retrieves the type name of an object or constructor. Fallback to "unknown"
+   * when it fails.
+   *
+   * @param  {Object} obj
+   * @return {String}
+   */
+  function typeName(obj) {
+    if (obj === null)
+      return "null";
+    if (typeof obj === "function")
+      return obj.name || obj.toString().match(/^function\s?([^\s(]*)/)[1];
+    if (typeof obj.constructor === "function")
+      return typeName(obj.constructor);
+    return "unknown";
+  }
+
+  /**
+   * Simple typed values validator.
+   *
+   * @constructor
+   * @param  {Object} schema Validation schema
+   */
+  function Validator(schema) {
+    this.schema = schema || {};
+  }
+
+  Validator.prototype = {
+    /**
+     * Validates all passed values against declared dependencies.
+     *
+     * @param  {Object} values  The values object
+     * @return {Object}         The validated values object
+     * @throws {TypeError}      If validation fails
+     */
+    validate: function(values) {
+      this._checkRequiredProperties(values);
+      this._checkRequiredTypes(values);
+      return values;
+    },
+
+    /**
+     * Checks if any of Object values matches any of current dependency type
+     * requirements.
+     *
+     * @param  {Object} values The values object
+     * @throws {TypeError}
+     */
+    _checkRequiredTypes: function(values) {
+      Object.keys(this.schema).forEach(function(name) {
+        var types = this.schema[name];
+        types = Array.isArray(types) ? types : [types];
+        if (!this._dependencyMatchTypes(values[name], types)) {
+          throw new TypeError("invalid dependency: " + name +
+                              "; expected " + types.map(typeName).join(", ") +
+                              ", got " + typeName(values[name]));
+        }
+      }, this);
+    },
+
+    /**
+     * Checks if a values object owns the required keys defined in dependencies.
+     * Values attached to these properties shouldn't be null nor undefined.
+     *
+     * @param  {Object} values The values object
+     * @throws {TypeError} If any dependency is missing.
+     */
+    _checkRequiredProperties: function(values) {
+      var definedProperties = Object.keys(values).filter(function(name) {
+        return typeof values[name] !== "undefined";
+      });
+      var diff = difference(Object.keys(this.schema), definedProperties);
+      if (diff.length > 0)
+        throw new TypeError("missing required " + diff.join(", "));
+    },
+
+    /**
+     * Checks if a given value matches any of the provided type requirements.
+     *
+     * @param  {Object} value  The value to check
+     * @param  {Array}  types  The list of types to check the value against
+     * @return {Boolean}
+     * @throws {TypeError} If the value doesn't match any types.
+     */
+    _dependencyMatchTypes: function(value, types) {
+      return types.some(function(Type) {
+        /*jshint eqeqeq:false*/
+        try {
+          return typeof Type === "undefined"         || // skip checking
+                 Type === null && value === null     || // null type
+                 value.constructor == Type           || // native type
+                 Type.prototype.isPrototypeOf(value) || // custom type
+                 typeName(value) === typeName(Type);    // type string eq.
+        } catch (e) {
+          return false;
+        }
+      });
+    }
+  };
+
+  return {
+    Validator: Validator
+  };
+})();
--- a/browser/components/loop/content/shared/js/websocket.js
+++ b/browser/components/loop/content/shared/js/websocket.js
@@ -94,20 +94,23 @@ loop.CallConnectionWebSocket = (function
       clearTimeout(this.connectDetails.timeout);
       delete this.connectDetails;
     },
 
     /**
      * Internal function called to resolve the connection promise.
      *
      * It will log an error if no promise is found.
+     *
+     * @param {String} progressState The current state of progress of the
+     *                               websocket.
      */
-    _completeConnection: function() {
+    _completeConnection: function(progressState) {
       if (this.connectDetails && this.connectDetails.resolve) {
-        this.connectDetails.resolve();
+        this.connectDetails.resolve(progressState);
         this._clearConnectionFlags();
         return;
       }
 
       console.error("Failed to complete connection promise - no promise available");
     },
 
     /**
@@ -222,17 +225,17 @@ loop.CallConnectionWebSocket = (function
 
       this._log("WS Receiving", event.data);
 
       var previousState = this._lastServerState;
       this._lastServerState = msg.state;
 
       switch(msg.messageType) {
         case "hello":
-          this._completeConnection();
+          this._completeConnection(msg.state);
           break;
         case "progress":
           this.trigger("progress:" + msg.state);
           this.trigger("progress", msg, previousState);
           break;
       }
     },
 
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -11,16 +11,17 @@ browser.jar:
   content/browser/loop/libs/l10n.js                 (content/libs/l10n.js)
 
   # Desktop script
   content/browser/loop/js/client.js                 (content/js/client.js)
   content/browser/loop/js/conversation.js           (content/js/conversation.js)
   content/browser/loop/js/otconfig.js               (content/js/otconfig.js)
   content/browser/loop/js/panel.js                  (content/js/panel.js)
   content/browser/loop/js/contacts.js               (content/js/contacts.js)
+  content/browser/loop/js/conversationViews.js      (content/js/conversationViews.js)
 
   # Shared styles
   content/browser/loop/shared/css/reset.css         (content/shared/css/reset.css)
   content/browser/loop/shared/css/common.css        (content/shared/css/common.css)
   content/browser/loop/shared/css/panel.css         (content/shared/css/panel.css)
   content/browser/loop/shared/css/conversation.css  (content/shared/css/conversation.css)
   content/browser/loop/shared/css/contacts.css      (content/shared/css/contacts.css)
 
@@ -47,21 +48,25 @@ browser.jar:
   content/browser/loop/shared/img/svg/glyph-signin-16x16.svg    (content/shared/img/svg/glyph-signin-16x16.svg)
   content/browser/loop/shared/img/svg/glyph-signout-16x16.svg   (content/shared/img/svg/glyph-signout-16x16.svg)
   content/browser/loop/shared/img/audio-call-avatar.svg         (content/shared/img/audio-call-avatar.svg)
   content/browser/loop/shared/img/icons-10x10.svg               (content/shared/img/icons-10x10.svg)
   content/browser/loop/shared/img/icons-14x14.svg               (content/shared/img/icons-14x14.svg)
   content/browser/loop/shared/img/icons-16x16.svg               (content/shared/img/icons-16x16.svg)
 
   # Shared scripts
+  content/browser/loop/shared/js/actions.js           (content/shared/js/actions.js)
+  content/browser/loop/shared/js/conversationStore.js (content/shared/js/conversationStore.js)
+  content/browser/loop/shared/js/dispatcher.js        (content/shared/js/dispatcher.js)
   content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
   content/browser/loop/shared/js/models.js            (content/shared/js/models.js)
   content/browser/loop/shared/js/mixins.js            (content/shared/js/mixins.js)
   content/browser/loop/shared/js/views.js             (content/shared/js/views.js)
   content/browser/loop/shared/js/utils.js             (content/shared/js/utils.js)
+  content/browser/loop/shared/js/validate.js          (content/shared/js/validate.js)
   content/browser/loop/shared/js/websocket.js         (content/shared/js/websocket.js)
 
   # Shared libs
 #ifdef DEBUG
   content/browser/loop/shared/libs/react-0.11.1.js    (content/shared/libs/react-0.11.1.js)
 #else
   content/browser/loop/shared/libs/react-0.11.1.js    (content/shared/libs/react-0.11.1-prod.js)
 #endif
--- a/browser/components/loop/test/desktop-local/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -248,10 +248,75 @@ describe("loop.Client", function() {
             sinon.assert.calledWith(mozLoop.telemetryAdd,
                                     "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
                                     false);
 
             done();
           });
         });
     });
+
+    describe("#setupOutgoingCall", function() {
+      var calleeIds, callType;
+
+      beforeEach(function() {
+        calleeIds = [
+          "fakeemail", "fake phone"
+        ];
+        callType = "audio";
+      });
+
+      it("should make a POST call to /calls", function() {
+        client.setupOutgoingCall(calleeIds, callType);
+
+        sinon.assert.calledOnce(hawkRequestStub);
+        sinon.assert.calledWith(hawkRequestStub,
+          mozLoop.LOOP_SESSION_TYPE.FXA,
+          "/calls",
+          "POST",
+          { calleeId: calleeIds, callType: callType }
+        );
+      });
+
+      it("should call the callback if the request is successful", function() {
+        var requestData = {
+          apiKey: "fake",
+          callId: "fakeCall",
+          progressURL: "fakeurl",
+          sessionId: "12345678",
+          sessionToken: "15263748",
+          websocketToken: "13572468"
+        };
+
+        hawkRequestStub.callsArgWith(4, null, JSON.stringify(requestData));
+
+        client.setupOutgoingCall(calleeIds, callType, callback);
+
+        sinon.assert.calledOnce(callback);
+        sinon.assert.calledWithExactly(callback, null, requestData);
+      });
+
+      it("should send an error when the request fails", function() {
+        hawkRequestStub.callsArgWith(4, fakeErrorRes);
+
+        client.setupOutgoingCall(calleeIds, callType, callback);
+
+        sinon.assert.calledOnce(callback);
+        sinon.assert.calledWithExactly(callback, sinon.match(function(err) {
+          return /400.*invalid token/.test(err.message);
+        }));
+      });
+
+      it("should send an error if the data is not valid", function() {
+        // Sets up the hawkRequest stub to trigger the callback with
+        // an error
+        hawkRequestStub.callsArgWith(4, null, "{}");
+
+        client.setupOutgoingCall(calleeIds, callType, callback);
+
+        sinon.assert.calledOnce(callback);
+        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
+          return /Invalid data received/.test(err.message);
+        }));
+      });
+    });
   });
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var expect = chai.expect;
+
+describe("loop.conversationViews", function () {
+  var sandbox, oldTitle, view;
+
+  var CALL_STATES = loop.store.CALL_STATES;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+
+    oldTitle = document.title;
+    sandbox.stub(document.mozL10n, "get", function(x) {
+      return x;
+    });
+  });
+
+  afterEach(function() {
+    document.title = oldTitle;
+    view = undefined;
+    sandbox.restore();
+  });
+
+  describe("ConversationDetailView", function() {
+    function mountTestComponent(props) {
+      return TestUtils.renderIntoDocument(
+        loop.conversationViews.ConversationDetailView(props));
+    }
+
+    it("should set the document title to the calledId", function() {
+      mountTestComponent({calleeId: "mrsmith"});
+
+      expect(document.title).eql("mrsmith");
+    });
+
+    it("should set display the calledId", function() {
+      view = mountTestComponent({calleeId: "mrsmith"});
+
+      expect(TestUtils.findRenderedDOMComponentWithTag(
+        view, "h2").props.children).eql("mrsmith");
+    });
+  });
+
+  describe("PendingConversationView", function() {
+    function mountTestComponent(props) {
+      return TestUtils.renderIntoDocument(
+        loop.conversationViews.PendingConversationView(props));
+    }
+
+    it("should set display connecting string when the state is not alerting",
+      function() {
+        view = mountTestComponent({
+          callState: CALL_STATES.CONNECTING,
+          calleeId: "mrsmith"
+        });
+
+        var label = TestUtils.findRenderedDOMComponentWithClass(
+          view, "btn-label").props.children;
+
+        expect(label).to.have.string("connecting");
+    });
+
+    it("should set display ringing string when the state is alerting",
+      function() {
+        view = mountTestComponent({
+          callState: CALL_STATES.ALERTING,
+          calleeId: "mrsmith"
+        });
+
+        var label = TestUtils.findRenderedDOMComponentWithClass(
+          view, "btn-label").props.children;
+
+        expect(label).to.have.string("ringing");
+    });
+  });
+
+  describe("OutgoingConversationView", function() {
+    var store;
+
+    function mountTestComponent() {
+      return TestUtils.renderIntoDocument(
+        loop.conversationViews.OutgoingConversationView({
+          store: store
+        }));
+    }
+
+    beforeEach(function() {
+      store = new loop.store.ConversationStore({}, {
+        dispatcher: new loop.Dispatcher(),
+        client: {}
+      });
+    });
+
+    it("should render the CallFailedView when the call state is 'terminated'",
+      function() {
+        store.set({callState: CALL_STATES.TERMINATED});
+
+        view = mountTestComponent();
+
+        TestUtils.findRenderedComponentWithType(view,
+          loop.conversationViews.CallFailedView);
+    });
+
+    it("should render the PendingConversationView when the call state is connecting",
+      function() {
+        store.set({callState: CALL_STATES.CONNECTING});
+
+        view = mountTestComponent();
+
+        TestUtils.findRenderedComponentWithType(view,
+          loop.conversationViews.PendingConversationView);
+    });
+
+    it("should update the rendered views when the state is changed.",
+      function() {
+        store.set({callState: CALL_STATES.CONNECTING});
+
+        view = mountTestComponent();
+
+        TestUtils.findRenderedComponentWithType(view,
+          loop.conversationViews.PendingConversationView);
+
+        store.set({callState: CALL_STATES.TERMINATED});
+
+        TestUtils.findRenderedComponentWithType(view,
+          loop.conversationViews.CallFailedView);
+    });
+  });
+});
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -36,17 +36,17 @@ describe("loop.conversation", function()
       doNotDisturb: true,
       getStrings: function() {
         return JSON.stringify({textContent: "fakeText"});
       },
       get locale() {
         return "en-US";
       },
       setLoopCharPref: sinon.stub(),
-      getLoopCharPref: sinon.stub(),
+      getLoopCharPref: sinon.stub().returns(null),
       getLoopBoolPref: sinon.stub(),
       getCallData: sinon.stub(),
       releaseCallData: sinon.stub(),
       startAlerting: sinon.stub(),
       stopAlerting: sinon.stub(),
       ensureRegistered: sinon.stub(),
       get appVersionInfo() {
         return {
@@ -63,25 +63,28 @@ describe("loop.conversation", function()
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     sandbox.restore();
   });
 
   describe("#init", function() {
-    var oldTitle;
-
     beforeEach(function() {
       sandbox.stub(React, "renderComponent");
       sandbox.stub(document.mozL10n, "initialize");
 
       sandbox.stub(loop.shared.models.ConversationModel.prototype,
         "initialize");
 
+      sandbox.stub(loop.Dispatcher.prototype, "dispatch");
+
+      sandbox.stub(loop.shared.utils.Helper.prototype,
+        "locationHash").returns("#incoming/42");
+
       window.OT = {
         overrideGuidStorage: sinon.stub()
       };
     });
 
     afterEach(function() {
       delete window.OT;
     });
@@ -89,27 +92,88 @@ describe("loop.conversation", function()
     it("should initalize L10n", function() {
       loop.conversation.init();
 
       sinon.assert.calledOnce(document.mozL10n.initialize);
       sinon.assert.calledWithExactly(document.mozL10n.initialize,
         navigator.mozLoop);
     });
 
-    it("should create the IncomingConversationView", function() {
+    it("should create the ConversationControllerView", function() {
       loop.conversation.init();
 
       sinon.assert.calledOnce(React.renderComponent);
       sinon.assert.calledWith(React.renderComponent,
         sinon.match(function(value) {
           return TestUtils.isDescriptorOfType(value,
-            loop.conversation.IncomingConversationView);
+            loop.conversation.ConversationControllerView);
       }));
     });
 
+    it("should trigger a gatherCallData action", function() {
+      loop.conversation.init();
+
+      sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
+      sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
+        new loop.shared.actions.GatherCallData({
+          calleeId: null,
+          callId: "42"
+        }));
+    });
+  });
+
+  describe("ConversationControllerView", function() {
+    var store, conversation, client, ccView, oldTitle, dispatcher;
+
+    function mountTestComponent() {
+      return TestUtils.renderIntoDocument(
+        loop.conversation.ConversationControllerView({
+          client: client,
+          conversation: conversation,
+          notifications: notifications,
+          sdk: {},
+          store: store
+        }));
+    }
+
+    beforeEach(function() {
+      oldTitle = document.title;
+      client = new loop.Client();
+      conversation = new loop.shared.models.ConversationModel({}, {
+        sdk: {}
+      });
+      dispatcher = new loop.Dispatcher();
+      store = new loop.store.ConversationStore({}, {
+        client: client,
+        dispatcher: dispatcher
+      });
+    });
+
+    afterEach(function() {
+      ccView = undefined;
+      document.title = oldTitle;
+    });
+
+    it("should display the OutgoingConversationView for outgoing calls", function() {
+      store.set({outgoing: true});
+
+      ccView = mountTestComponent();
+
+      TestUtils.findRenderedComponentWithType(ccView,
+        loop.conversationViews.OutgoingConversationView);
+    });
+
+    it("should display the IncomingConversationView for incoming calls", function() {
+      store.set({outgoing: false});
+
+      ccView = mountTestComponent();
+
+      TestUtils.findRenderedComponentWithType(ccView,
+        loop.conversation.IncomingConversationView);
+    });
   });
 
   describe("IncomingConversationView", function() {
     var conversation, client, icView, oldTitle;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.conversation.IncomingConversationView({
@@ -226,24 +290,35 @@ describe("loop.conversation", function()
               rejectWebSocketConnect = reject;
             });
 
             sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
           });
 
           it("should set the state to incoming on success", function(done) {
             icView = mountTestComponent();
-            resolveWebSocketConnect();
+            resolveWebSocketConnect("incoming");
 
             promise.then(function () {
               expect(icView.state.callStatus).eql("incoming");
               done();
             });
           });
 
+          it("should set the state to close on success if the progress " +
+            "state is terminated", function(done) {
+              icView = mountTestComponent();
+              resolveWebSocketConnect("terminated");
+
+              promise.then(function () {
+                expect(icView.state.callStatus).eql("close");
+                done();
+              });
+            });
+
           it("should display an error if the websocket failed to connect", function(done) {
             sandbox.stub(notifications, "errorL10n");
 
             icView = mountTestComponent();
             rejectWebSocketConnect();
 
             promise.then(function() {
             }, function () {
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -29,29 +29,35 @@
     /*global chai,mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
+  <script src="../../content/shared/js/conversationStore.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
+  <script src="../../content/shared/js/actions.js"></script>
+  <script src="../../content/shared/js/validate.js"></script>
+  <script src="../../content/shared/js/dispatcher.js"></script>
   <script src="../../content/js/client.js"></script>
+  <script src="../../content/js/conversationViews.js"></script>
   <script src="../../content/js/conversation.js"></script>
   <script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
   <script src="../../content/js/panel.js"></script>
 
   <!-- Test scripts -->
   <script src="client_test.js"></script>
   <script src="conversation_test.js"></script>
   <script src="panel_test.js"></script>
+  <script src="conversationViews_test.js"></script>
   <script>
     // Stop the default init functions running to avoid conflicts in tests
     document.removeEventListener('DOMContentLoaded', loop.panel.init);
     document.removeEventListener('DOMContentLoaded', loop.conversation.init);
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -0,0 +1,321 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var expect = chai.expect;
+
+describe("loop.ConversationStore", function () {
+  "use strict";
+
+  var CALL_STATES = loop.store.CALL_STATES;
+  var sharedActions = loop.shared.actions;
+  var sharedUtils = loop.shared.utils;
+  var sandbox, dispatcher, client, store, fakeSessionData;
+  var connectPromise, resolveConnectPromise, rejectConnectPromise;
+
+  function checkFailures(done, f) {
+    try {
+      f();
+      done();
+    } catch (err) {
+      done(err);
+    }
+  }
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+
+    dispatcher = new loop.Dispatcher();
+    client = {
+      setupOutgoingCall: sinon.stub()
+    };
+    store = new loop.store.ConversationStore({}, {
+      client: client,
+      dispatcher: dispatcher
+    });
+    fakeSessionData = {
+      apiKey: "fakeKey",
+      callId: "142536",
+      sessionId: "321456",
+      sessionToken: "341256",
+      websocketToken: "543216",
+      progressURL: "fakeURL"
+    };
+
+    var dummySocket = {
+      close: sinon.spy(),
+      send: sinon.spy()
+    };
+
+    connectPromise = new Promise(function(resolve, reject) {
+      resolveConnectPromise = resolve;
+      rejectConnectPromise = reject;
+    });
+
+    sandbox.stub(loop.CallConnectionWebSocket.prototype,
+      "promiseConnect").returns(connectPromise);
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("#initialize", function() {
+    it("should throw an error if the dispatcher is missing", function() {
+      expect(function() {
+        new loop.store.ConversationStore({}, {client: client});
+      }).to.Throw(/dispatcher/);
+    });
+
+    it("should throw an error if the client is missing", function() {
+      expect(function() {
+        new loop.store.ConversationStore({}, {dispatcher: dispatcher});
+      }).to.Throw(/client/);
+    });
+  });
+
+  describe("#connectionFailure", function() {
+    it("should set the state to 'terminated'", function() {
+      store.set({callState: CALL_STATES.ALERTING});
+
+      dispatcher.dispatch(
+        new sharedActions.ConnectionFailure({reason: "fake"}));
+
+      expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
+      expect(store.get("callStateReason")).eql("fake");
+    });
+  });
+
+  describe("#connectionProgress", function() {
+    describe("progress: connecting", function() {
+      it("should change the state from 'gather' to 'connecting'", function() {
+        store.set({callState: CALL_STATES.GATHER});
+
+        dispatcher.dispatch(
+          new sharedActions.ConnectionProgress({state: "connecting"}));
+
+        expect(store.get("callState")).eql(CALL_STATES.CONNECTING);
+      });
+    });
+
+    describe("progress: alerting", function() {
+      it("should set the state from 'gather' to 'alerting'", function() {
+        store.set({callState: CALL_STATES.GATHER});
+
+        dispatcher.dispatch(
+          new sharedActions.ConnectionProgress({state: "alerting"}));
+
+        expect(store.get("callState")).eql(CALL_STATES.ALERTING);
+      });
+
+      it("should set the state from 'connecting' to 'alerting'", function() {
+        store.set({callState: CALL_STATES.CONNECTING});
+
+        dispatcher.dispatch(
+          new sharedActions.ConnectionProgress({state: "alerting"}));
+
+        expect(store.get("callState")).eql(CALL_STATES.ALERTING);
+      });
+    });
+  });
+
+  describe("#gatherCallData", function() {
+    beforeEach(function() {
+      store.set({callState: CALL_STATES.INIT});
+    });
+
+    it("should set the state to 'gather'", function() {
+      dispatcher.dispatch(
+        new sharedActions.GatherCallData({
+          calleeId: "",
+          callId: "76543218"
+        }));
+
+      expect(store.get("callState")).eql(CALL_STATES.GATHER);
+    });
+
+    it("should save the basic call information", function() {
+      dispatcher.dispatch(
+        new sharedActions.GatherCallData({
+          calleeId: "fake",
+          callId: "123456"
+        }));
+
+      expect(store.get("calleeId")).eql("fake");
+      expect(store.get("callId")).eql("123456");
+      expect(store.get("outgoing")).eql(true);
+    });
+
+    describe("outgoing calls", function() {
+      var outgoingCallData;
+
+      beforeEach(function() {
+        outgoingCallData = {
+          calleeId: "fake",
+          callId: "135246"
+        };
+      });
+
+      it("should request the outgoing call data", function() {
+        dispatcher.dispatch(
+          new sharedActions.GatherCallData(outgoingCallData));
+
+        sinon.assert.calledOnce(client.setupOutgoingCall);
+        sinon.assert.calledWith(client.setupOutgoingCall,
+          ["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
+      });
+
+      describe("server response handling", function() {
+        beforeEach(function() {
+          sandbox.stub(dispatcher, "dispatch");
+        });
+
+        it("should dispatch a connect call action on success", function() {
+          var callData = {
+            apiKey: "fakeKey"
+          };
+
+          client.setupOutgoingCall.callsArgWith(2, null, callData);
+
+          store.gatherCallData(
+            new sharedActions.GatherCallData(outgoingCallData));
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          // Can't use instanceof here, as that matches any action
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("name", "connectCall"));
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("sessionData", callData));
+        });
+
+        it("should dispatch a connection failure action on failure", function() {
+          client.setupOutgoingCall.callsArgWith(2, {});
+
+          store.gatherCallData(
+            new sharedActions.GatherCallData(outgoingCallData));
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          // Can't use instanceof here, as that matches any action
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("name", "connectionFailure"));
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("reason", "setup"));
+        });
+      });
+    });
+  });
+
+  describe("#connectCall", function() {
+    it("should save the call session data", function() {
+      dispatcher.dispatch(
+        new sharedActions.ConnectCall({sessionData: fakeSessionData}));
+
+      expect(store.get("apiKey")).eql("fakeKey");
+      expect(store.get("callId")).eql("142536");
+      expect(store.get("sessionId")).eql("321456");
+      expect(store.get("sessionToken")).eql("341256");
+      expect(store.get("websocketToken")).eql("543216");
+      expect(store.get("progressURL")).eql("fakeURL");
+    });
+
+    it("should initialize the websocket", function() {
+      sandbox.stub(loop, "CallConnectionWebSocket").returns({
+        promiseConnect: function() { return connectPromise; },
+        on: sinon.spy()
+      });
+
+      dispatcher.dispatch(
+        new sharedActions.ConnectCall({sessionData: fakeSessionData}));
+
+      sinon.assert.calledOnce(loop.CallConnectionWebSocket);
+      sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
+        url: "fakeURL",
+        callId: "142536",
+        websocketToken: "543216"
+      });
+    });
+
+    it("should connect the websocket to the server", function() {
+      dispatcher.dispatch(
+        new sharedActions.ConnectCall({sessionData: fakeSessionData}));
+
+      sinon.assert.calledOnce(store._websocket.promiseConnect);
+    });
+
+    describe("WebSocket connection result", function() {
+      beforeEach(function() {
+        dispatcher.dispatch(
+          new sharedActions.ConnectCall({sessionData: fakeSessionData}));
+
+        sandbox.stub(dispatcher, "dispatch");
+      });
+
+      it("should dispatch a connection progress action on success", function(done) {
+        resolveConnectPromise();
+
+        connectPromise.then(function() {
+          checkFailures(done, function() {
+            sinon.assert.calledOnce(dispatcher.dispatch);
+            // Can't use instanceof here, as that matches any action
+            sinon.assert.calledWithMatch(dispatcher.dispatch,
+              sinon.match.hasOwn("name", "connectionProgress"));
+            sinon.assert.calledWithMatch(dispatcher.dispatch,
+              sinon.match.hasOwn("state", "connecting"));
+          });
+        }, function() {
+          done(new Error("Promise should have been resolve, not rejected"));
+        });
+      });
+
+      it("should dispatch a connection failure action on failure", function(done) {
+        rejectConnectPromise();
+
+        connectPromise.then(function() {
+          done(new Error("Promise should have been rejected, not resolved"));
+        }, function() {
+          checkFailures(done, function() {
+            sinon.assert.calledOnce(dispatcher.dispatch);
+            // Can't use instanceof here, as that matches any action
+            sinon.assert.calledWithMatch(dispatcher.dispatch,
+              sinon.match.hasOwn("name", "connectionFailure"));
+            sinon.assert.calledWithMatch(dispatcher.dispatch,
+              sinon.match.hasOwn("reason", "websocket-setup"));
+           });
+        });
+      });
+
+    });
+  });
+
+  describe("Events", function() {
+    describe("Websocket progress", function() {
+      beforeEach(function() {
+        dispatcher.dispatch(
+          new sharedActions.ConnectCall({sessionData: fakeSessionData}));
+
+        sandbox.stub(dispatcher, "dispatch");
+      });
+
+      it("should dispatch a connection failure action on 'terminate'", function() {
+        store._websocket.trigger("progress", {state: "terminated", reason: "reject"});
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        // Can't use instanceof here, as that matches any action
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionFailure"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("reason", "reject"));
+      });
+
+      it("should dispatch a connection progress action on 'alerting'", function() {
+        store._websocket.trigger("progress", {state: "alerting"});
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        // Can't use instanceof here, as that matches any action
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionProgress"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("state", "alerting"));
+      });
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/dispatcher_test.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var expect = chai.expect;
+
+describe("loop.Dispatcher", function () {
+  "use strict";
+
+  var sharedActions = loop.shared.actions;
+  var dispatcher, sandbox;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+    dispatcher = new loop.Dispatcher();
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("#register", function() {
+    it("should register a store against an action name", function() {
+      var object = { fake: true };
+
+      dispatcher.register(object, ["gatherCallData"]);
+
+      expect(dispatcher._eventData["gatherCallData"][0]).eql(object);
+    });
+
+    it("should register multiple store against an action name", function() {
+      var object1 = { fake: true };
+      var object2 = { fake2: true };
+
+      dispatcher.register(object1, ["gatherCallData"]);
+      dispatcher.register(object2, ["gatherCallData"]);
+
+      expect(dispatcher._eventData["gatherCallData"][0]).eql(object1);
+      expect(dispatcher._eventData["gatherCallData"][1]).eql(object2);
+    });
+  });
+
+  describe("#dispatch", function() {
+    var gatherStore1, gatherStore2, cancelStore1, connectStore1;
+    var gatherAction, cancelAction, connectAction, resolveCancelStore1;
+
+    beforeEach(function() {
+      gatherAction = new sharedActions.GatherCallData({
+        callId: "42",
+        calleeId: null
+      });
+
+      cancelAction = new sharedActions.CancelCall();
+      connectAction = new sharedActions.ConnectCall({
+        sessionData: {}
+      });
+
+      gatherStore1 = {
+        gatherCallData: sinon.stub()
+      };
+      gatherStore2 = {
+        gatherCallData: sinon.stub()
+      };
+      cancelStore1 = {
+        cancelCall: sinon.stub()
+      };
+      connectStore1 = {
+        connectCall: function() {}
+      };
+
+      dispatcher.register(gatherStore1, ["gatherCallData"]);
+      dispatcher.register(gatherStore2, ["gatherCallData"]);
+      dispatcher.register(cancelStore1, ["cancelCall"]);
+      dispatcher.register(connectStore1, ["connectCall"]);
+    });
+
+    it("should dispatch an action to the required object", function() {
+      dispatcher.dispatch(cancelAction);
+
+      sinon.assert.notCalled(gatherStore1.gatherCallData);
+
+      sinon.assert.calledOnce(cancelStore1.cancelCall);
+      sinon.assert.calledWithExactly(cancelStore1.cancelCall, cancelAction);
+
+      sinon.assert.notCalled(gatherStore2.gatherCallData);
+    });
+
+    it("should dispatch actions to multiple objects", function() {
+      dispatcher.dispatch(gatherAction);
+
+      sinon.assert.calledOnce(gatherStore1.gatherCallData);
+      sinon.assert.calledWithExactly(gatherStore1.gatherCallData, gatherAction);
+
+      sinon.assert.notCalled(cancelStore1.cancelCall);
+
+      sinon.assert.calledOnce(gatherStore2.gatherCallData);
+      sinon.assert.calledWithExactly(gatherStore2.gatherCallData, gatherAction);
+    });
+
+    it("should dispatch multiple actions", function() {
+      dispatcher.dispatch(cancelAction);
+      dispatcher.dispatch(gatherAction);
+
+      sinon.assert.calledOnce(cancelStore1.cancelCall);
+      sinon.assert.calledOnce(gatherStore1.gatherCallData);
+      sinon.assert.calledOnce(gatherStore2.gatherCallData);
+    });
+
+    describe("Queued actions", function() {
+      beforeEach(function() {
+        // Restore the stub, so that we can easily add a function to be
+        // returned. Unfortunately, sinon doesn't make this easy.
+        sandbox.stub(connectStore1, "connectCall", function() {
+          dispatcher.dispatch(gatherAction);
+
+          sinon.assert.notCalled(gatherStore1.gatherCallData);
+          sinon.assert.notCalled(gatherStore2.gatherCallData);
+        });
+      });
+
+      it("should not dispatch an action if the previous action hasn't finished", function() {
+        // Dispatch the first action. The action handler dispatches the second
+        // action - see the beforeEach above.
+        dispatcher.dispatch(connectAction);
+
+        sinon.assert.calledOnce(connectStore1.connectCall);
+      });
+
+      it("should dispatch an action when the previous action finishes", function() {
+        // Dispatch the first action. The action handler dispatches the second
+        // action - see the beforeEach above.
+        dispatcher.dispatch(connectAction);
+
+        sinon.assert.calledOnce(connectStore1.connectCall);
+        // These should be called, because the dispatcher synchronously queues actions.
+        sinon.assert.calledOnce(gatherStore1.gatherCallData);
+        sinon.assert.calledOnce(gatherStore2.gatherCallData);
+      });
+    });
+  });
+});
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -34,23 +34,30 @@
 
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
+  <script src="../../content/shared/js/validate.js"></script>
+  <script src="../../content/shared/js/actions.js"></script>
+  <script src="../../content/shared/js/dispatcher.js"></script>
+  <script src="../../content/shared/js/conversationStore.js"></script>
 
   <!-- Test scripts -->
   <script src="models_test.js"></script>
   <script src="mixins_test.js"></script>
   <script src="utils_test.js"></script>
   <script src="views_test.js"></script>
   <script src="websocket_test.js"></script>
   <script src="feedbackApiClient_test.js"></script>
+  <script src="validate_test.js"></script>
+  <script src="dispatcher_test.js"></script>
+  <script src="conversationStore_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/validate_test.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global chai, validate */
+
+var expect = chai.expect;
+
+describe("Validator", function() {
+  "use strict";
+
+  // test helpers
+  function create(dependencies, values) {
+    var validator = new loop.validate.Validator(dependencies);
+    return validator.validate.bind(validator, values);
+  }
+
+  // test types
+  function X(){}
+  function Y(){}
+
+  describe("#validate", function() {
+    it("should check for a single required dependency when no option passed",
+      function() {
+        expect(create({x: Number}, {}))
+          .to.Throw(TypeError, /missing required x$/);
+      });
+
+    it("should check for a missing required dependency, undefined passed",
+      function() {
+        expect(create({x: Number}, {x: undefined}))
+          .to.Throw(TypeError, /missing required x$/);
+      });
+
+    it("should check for multiple missing required dependencies", function() {
+      expect(create({x: Number, y: String}, {}))
+        .to.Throw(TypeError, /missing required x, y$/);
+    });
+
+    it("should check for required dependency types", function() {
+      expect(create({x: Number}, {x: "woops"})).to.Throw(
+        TypeError, /invalid dependency: x; expected Number, got String$/);
+    });
+
+    it("should check for a dependency to match at least one of passed types",
+      function() {
+        expect(create({x: [X, Y]}, {x: 42})).to.Throw(
+          TypeError, /invalid dependency: x; expected X, Y, got Number$/);
+        expect(create({x: [X, Y]}, {x: new Y()})).to.not.Throw();
+      });
+
+    it("should skip type check if required dependency type is undefined",
+      function() {
+        expect(create({x: undefined}, {x: /whatever/})).not.to.Throw();
+      });
+
+    it("should check for a String dependency", function() {
+      expect(create({foo: String}, {foo: 42})).to.Throw(
+        TypeError, /invalid dependency: foo/);
+    });
+
+    it("should check for a Number dependency", function() {
+      expect(create({foo: Number}, {foo: "x"})).to.Throw(
+        TypeError, /invalid dependency: foo/);
+    });
+
+    it("should check for a custom constructor dependency", function() {
+      expect(create({foo: X}, {foo: null})).to.Throw(
+        TypeError, /invalid dependency: foo; expected X, got null$/);
+    });
+
+    it("should check for a native constructor dependency", function() {
+      expect(create({foo: mozRTCSessionDescription}, {foo: "x"}))
+        .to.Throw(TypeError,
+                  /invalid dependency: foo; expected mozRTCSessionDescription/);
+    });
+
+    it("should check for a null dependency", function() {
+      expect(create({foo: null}, {foo: "x"})).to.Throw(
+        TypeError, /invalid dependency: foo; expected null, got String$/);
+    });
+  });
+});
--- a/browser/components/loop/test/shared/websocket_test.js
+++ b/browser/components/loop/test/shared/websocket_test.js
@@ -123,18 +123,21 @@ describe("loop.CallConnectionWebSocket",
       it("should resolve the promise when the 'hello' is received",
         function(done) {
           var promise = callWebSocket.promiseConnect();
 
           dummySocket.onmessage({
             data: '{"messageType":"hello", "state":"init"}'
           });
 
-          promise.then(function() {
+          promise.then(function(state) {
+            expect(state).eql("init");
             done();
+          }, function() {
+            done(new Error("shouldn't have rejected the promise"));
           });
         });
     });
 
     describe("#close", function() {
       it("should close the socket", function() {
         callWebSocket.promiseConnect();
 
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -27,20 +27,23 @@
       window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css';
     </script>
     <script src="../content/shared/libs/sdk.js"></script>
     <script src="../content/shared/libs/react-0.11.1.js"></script>
     <script src="../content/shared/libs/jquery-2.1.0.js"></script>
     <script src="../content/shared/libs/lodash-2.4.1.js"></script>
     <script src="../content/shared/libs/backbone-1.1.2.js"></script>
     <script src="../content/shared/js/feedbackApiClient.js"></script>
+    <script src="../content/shared/js/conversationStore.js"></script>
     <script src="../content/shared/js/utils.js"></script>
     <script src="../content/shared/js/models.js"></script>
     <script src="../content/shared/js/mixins.js"></script>
     <script src="../content/shared/js/views.js"></script>
+    <script src="../content/shared/js/websocket.js"></script>
+    <script src="../content/js/conversationViews.js"></script>
     <script src="../content/js/client.js"></script>
     <script src="../standalone/content/js/webapp.js"></script>
     <script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
     <script>
       if (!loop.contacts) {
         // For browsers that don't support ES6 without special flags (all but Fx
         // at the moment), we shim the contacts namespace with its most barebone
         // implementation.
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -133,18 +133,18 @@
   background-image: url("sample-img/video-screen-local.png");
   background-repeat: no-repeat;
 }
 
   .local-stream.local:not(.local-stream-audio) {
     background-size: cover;
   }
 
-.incoming-call-action-group .btn-group-chevron,
-.incoming-call-action-group .btn-group {
+.call-action-group .btn-group-chevron,
+.call-action-group .btn-group {
   /* Prevent box overflow due to long string */
   max-width: 120px;
 }
 
 .conversation .media.nested .remote {
   /* Height of obsolute box covers media control buttons. UI showcase only.
    * When tokbox inserts the markup into the page the problem goes away */
   bottom: auto;
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -10,16 +10,17 @@
 (function() {
   "use strict";
 
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
   var IncomingCallView = loop.conversation.IncomingCallView;
+  var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
   var CallUrlExpiredView    = loop.webapp.CallUrlExpiredView;
   var PendingConversationView = loop.webapp.PendingConversationView;
   var StartConversationView = loop.webapp.StartConversationView;
@@ -58,16 +59,22 @@
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({}, {
     sdk: mockSDK
   });
   mockConversationModel.startSession = noop;
 
+  var mockWebSocket = new loop.CallConnectionWebSocket({
+    url: "fake",
+    callId: "fakeId",
+    websocketToken: "fakeToken"
+  });
+
   var notifications = new loop.shared.models.NotificationCollection();
   var errNotifications = new loop.shared.models.NotificationCollection();
   errNotifications.error("Error!");
 
   var Example = React.createClass({displayName: 'Example',
     render: function() {
       var cx = React.addons.classSet;
       return (
@@ -218,22 +225,31 @@
                                      publishStream: noop})
               )
             )
           ), 
 
           Section({name: "PendingConversationView"}, 
             Example({summary: "Pending conversation view (connecting)", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
-                PendingConversationView(null)
+                PendingConversationView({websocket: mockWebSocket})
               )
             ), 
             Example({summary: "Pending conversation view (ringing)", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
-                PendingConversationView({callState: "ringing"})
+                PendingConversationView({websocket: mockWebSocket, callState: "ringing"})
+              )
+            )
+          ), 
+
+          Section({name: "PendingConversationView (Desktop)"}, 
+            Example({summary: "Connecting", dashed: "true", 
+                     style: {width: "260px", height: "265px"}}, 
+              React.DOM.div({className: "fx-embedded"}, 
+                DesktopPendingConversationView({callState: "gather", calleeId: "Mr Smith"})
               )
             )
           ), 
 
           Section({name: "StartConversationView"}, 
             Example({summary: "Start conversation view", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
                 StartConversationView({model: mockConversationModel, 
@@ -441,11 +457,14 @@
 
   window.addEventListener("DOMContentLoaded", function() {
     var body = document.body;
     body.className = loop.shared.utils.getTargetPlatform();
 
     React.renderComponent(App(null), body);
 
     _renderComponentsInIframes();
+
+    // Put the title back, in case views changed it.
+    document.title = "Loop UI Components Showcase";
   });
 
 })();
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -10,16 +10,17 @@
 (function() {
   "use strict";
 
   // 1. Desktop components
   // 1.1 Panel
   var PanelView = loop.panel.PanelView;
   // 1.2. Conversation Window
   var IncomingCallView = loop.conversation.IncomingCallView;
+  var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
 
   // 2. Standalone webapp
   var HomeView = loop.webapp.HomeView;
   var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
   var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
   var CallUrlExpiredView    = loop.webapp.CallUrlExpiredView;
   var PendingConversationView = loop.webapp.PendingConversationView;
   var StartConversationView = loop.webapp.StartConversationView;
@@ -58,16 +59,22 @@
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({}, {
     sdk: mockSDK
   });
   mockConversationModel.startSession = noop;
 
+  var mockWebSocket = new loop.CallConnectionWebSocket({
+    url: "fake",
+    callId: "fakeId",
+    websocketToken: "fakeToken"
+  });
+
   var notifications = new loop.shared.models.NotificationCollection();
   var errNotifications = new loop.shared.models.NotificationCollection();
   errNotifications.error("Error!");
 
   var Example = React.createClass({
     render: function() {
       var cx = React.addons.classSet;
       return (
@@ -218,22 +225,31 @@
                                      publishStream={noop} />
               </Example>
             </div>
           </Section>
 
           <Section name="PendingConversationView">
             <Example summary="Pending conversation view (connecting)" dashed="true">
               <div className="standalone">
-                <PendingConversationView />
+                <PendingConversationView websocket={mockWebSocket}/>
               </div>
             </Example>
             <Example summary="Pending conversation view (ringing)" dashed="true">
               <div className="standalone">
-                <PendingConversationView callState="ringing"/>
+                <PendingConversationView websocket={mockWebSocket} callState="ringing"/>
+              </div>
+            </Example>
+          </Section>
+
+          <Section name="PendingConversationView (Desktop)">
+            <Example summary="Connecting" dashed="true"
+                     style={{width: "260px", height: "265px"}}>
+              <div className="fx-embedded">
+                <DesktopPendingConversationView callState={"gather"} calleeId="Mr Smith" />
               </div>
             </Example>
           </Section>
 
           <Section name="StartConversationView">
             <Example summary="Start conversation view" dashed="true">
               <div className="standalone">
                 <StartConversationView model={mockConversationModel}
@@ -441,11 +457,14 @@
 
   window.addEventListener("DOMContentLoaded", function() {
     var body = document.body;
     body.className = loop.shared.utils.getTargetPlatform();
 
     React.renderComponent(<App />, body);
 
     _renderComponentsInIframes();
+
+    // Put the title back, in case views changed it.
+    document.title = "Loop UI Components Showcase";
   });
 
 })();
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -791,51 +791,58 @@ GK_ATOM(onmozfullscreenchange, "onmozful
 GK_ATOM(onmozfullscreenerror, "onmozfullscreenerror")
 GK_ATOM(onmozpointerlockchange, "onmozpointerlockchange")
 GK_ATOM(onmozpointerlockerror, "onmozpointerlockerror")
 GK_ATOM(onmoztimechange, "onmoztimechange")
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
 GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
 GK_ATOM(onmoznetworkupload, "onmoznetworkupload")
 GK_ATOM(onmoznetworkdownload, "onmoznetworkdownload")
+GK_ATOM(onnewrdsgroup, "onnewrdsgroup")
 GK_ATOM(onnoupdate, "onnoupdate")
 GK_ATOM(onobsolete, "onobsolete")
 GK_ATOM(ononline, "ononline")
 GK_ATOM(onoffline, "onoffline")
 GK_ATOM(onopen, "onopen")
 GK_ATOM(onotastatuschange, "onotastatuschange")
 GK_ATOM(onoverflow, "onoverflow")
 GK_ATOM(onoverflowchanged, "onoverflowchanged")
 GK_ATOM(onpagehide, "onpagehide")
 GK_ATOM(onpageshow, "onpageshow")
 GK_ATOM(onpaint, "onpaint")
 GK_ATOM(onpairedstatuschanged, "onpairedstatuschanged")
 GK_ATOM(onpairingconfirmationreq, "onpairingconfirmationreq")
 GK_ATOM(onpairingconsentreq, "onpairingconsentreq")
 GK_ATOM(onpaste, "onpaste")
 GK_ATOM(onpendingchange, "onpendingchange")
+GK_ATOM(onpichange, "onpichange")
 GK_ATOM(onpopuphidden, "onpopuphidden")
 GK_ATOM(onpopuphiding, "onpopuphiding")
 GK_ATOM(onpopupshowing, "onpopupshowing")
 GK_ATOM(onpopupshown, "onpopupshown")
+GK_ATOM(onpschange, "onpschange")
+GK_ATOM(onptychange, "onptychange")
 GK_ATOM(onradiostatechange, "onradiostatechange")
+GK_ATOM(onrdsdisabled, "onrdsdisabled")
+GK_ATOM(onrdsenabled, "onrdsenabled")
 GK_ATOM(onreaderror, "onreaderror")
 GK_ATOM(onreadsuccess, "onreadsuccess")
 GK_ATOM(onready, "onready")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onreceived, "onreceived")
 GK_ATOM(onremoteheld, "onremoteheld")
 GK_ATOM(onremoteresumed, "onremoteresumed")
 GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onresuming, "onresuming")
 GK_ATOM(onresize, "onresize")
+GK_ATOM(onrtchange, "onrtchange")
 GK_ATOM(onscostatuschanged, "onscostatuschanged")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
 GK_ATOM(onstatechange, "onstatechange")
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -5,16 +5,17 @@
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 
 #include "BluetoothServiceChildProcess.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ipc/BlobChild.h"
 
 #include "BluetoothChild.h"
 #include "MainThreadUtils.h"
 
 USING_BLUETOOTH_NAMESPACE
 
 namespace {
 
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -8,16 +8,17 @@
 #include "nsContentUtils.h"
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/FMRadioBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PFMRadioChild.h"
 #include "mozilla/dom/FMRadioService.h"
+#include "mozilla/dom/TypedArray.h"
 #include "DOMRequest.h"
 #include "nsDOMClassInfo.h"
 #include "nsIDocShell.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIAudioManager.h"
 
 #undef LOG
 #define LOG(args...) FM_LOG("FMRadio", args)
@@ -100,16 +101,17 @@ private:
   FMRadioRequestArgs::Type mType;
   nsWeakPtr mFMRadio;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(FMRadioRequest, DOMRequest)
 
 FMRadio::FMRadio()
   : mHeadphoneState(SWITCH_STATE_OFF)
+  , mRdsGroupMask(0)
   , mAudioChannelAgentEnabled(false)
   , mHasInternalAntenna(false)
   , mIsShutdown(false)
 {
   LOG("FMRadio is initialized.");
 
   SetIsDOMBinding();
 }
@@ -215,29 +217,57 @@ FMRadio::Notify(const FMRadioEventType& 
         if (mAudioChannelAgentEnabled) {
           mAudioChannelAgent->StopPlaying();
           mAudioChannelAgentEnabled = false;
         }
 
         DispatchTrustedEvent(NS_LITERAL_STRING("disabled"));
       }
       break;
+    case RDSEnabledChanged:
+      if (RdsEnabled()) {
+        DispatchTrustedEvent(NS_LITERAL_STRING("rdsenabled"));
+      } else {
+        DispatchTrustedEvent(NS_LITERAL_STRING("rdsdisabled"));
+      }
+      break;
+    case PIChanged:
+      DispatchTrustedEvent(NS_LITERAL_STRING("pichange"));
+      break;
+    case PSChanged:
+      DispatchTrustedEvent(NS_LITERAL_STRING("pschange"));
+      break;
+    case RadiotextChanged:
+      DispatchTrustedEvent(NS_LITERAL_STRING("rtchange"));
+      break;
+    case PTYChanged:
+      DispatchTrustedEvent(NS_LITERAL_STRING("ptychange"));
+      break;
+    case NewRDSGroup:
+      DispatchTrustedEvent(NS_LITERAL_STRING("newrdsgroup"));
+      break;
     default:
       MOZ_CRASH();
   }
 }
 
 /* static */
 bool
 FMRadio::Enabled()
 {
   return IFMRadioService::Singleton()->IsEnabled();
 }
 
 bool
+FMRadio::RdsEnabled()
+{
+  return IFMRadioService::Singleton()->IsRDSEnabled();
+}
+
+bool
 FMRadio::AntennaAvailable() const
 {
   return mHasInternalAntenna ? true : (mHeadphoneState != SWITCH_STATE_OFF) &&
     (mHeadphoneState != SWITCH_STATE_UNKNOWN);
 }
 
 Nullable<double>
 FMRadio::GetFrequency() const
@@ -260,16 +290,79 @@ FMRadio::FrequencyLowerBound() const
 }
 
 double
 FMRadio::ChannelWidth() const
 {
   return IFMRadioService::Singleton()->GetChannelWidth();
 }
 
+uint32_t
+FMRadio::RdsGroupMask() const
+{
+  return mRdsGroupMask;
+}
+
+void
+FMRadio::SetRdsGroupMask(uint32_t aRdsGroupMask)
+{
+  mRdsGroupMask = aRdsGroupMask;
+  IFMRadioService::Singleton()->SetRDSGroupMask(aRdsGroupMask);
+}
+
+Nullable<unsigned short>
+FMRadio::GetPi() const
+{
+  return IFMRadioService::Singleton()->GetPi();
+}
+
+Nullable<uint8_t>
+FMRadio::GetPty() const
+{
+  return IFMRadioService::Singleton()->GetPty();
+}
+
+void
+FMRadio::GetPs(DOMString& aPsname) const
+{
+  if (!IFMRadioService::Singleton()->GetPs(aPsname)) {
+    aPsname.SetNull();
+  }
+}
+
+void
+FMRadio::GetRt(DOMString& aRadiotext) const
+{
+  if (!IFMRadioService::Singleton()->GetRt(aRadiotext)) {
+    aRadiotext.SetNull();
+  }
+}
+
+void
+FMRadio::GetRdsgroup(JSContext* cx, JS::MutableHandle<JSObject*> retval)
+{
+  uint64_t group;
+  if (!IFMRadioService::Singleton()->GetRdsgroup(group)) {
+    return;
+  }
+
+  JSObject *rdsgroup = Uint16Array::Create(cx, this, 4);
+  uint16_t *data = JS_GetUint16ArrayData(rdsgroup);
+  data[3] = group & 0xFFFF;
+  group >>= 16;
+  data[2] = group & 0xFFFF;
+  group >>= 16;
+  data[1] = group & 0xFFFF;
+  group >>= 16;
+  data[0] = group & 0xFFFF;
+
+  JS::ExposeObjectToActiveJS(rdsgroup);
+  retval.set(rdsgroup);
+}
+
 already_AddRefed<DOMRequest>
 FMRadio::Enable(double aFrequency)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return nullptr;
   }
 
@@ -345,16 +438,42 @@ FMRadio::CancelSeek()
   }
 
   nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
   IFMRadioService::Singleton()->CancelSeek(r);
 
   return r.forget();
 }
 
+already_AddRefed<DOMRequest>
+FMRadio::EnableRDS()
+{
+  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
+  if (!win) {
+    return nullptr;
+  }
+
+  nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
+  IFMRadioService::Singleton()->EnableRDS(r);
+  return r.forget();
+}
+
+already_AddRefed<DOMRequest>
+FMRadio::DisableRDS()
+{
+  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
+  if (!win) {
+    return nullptr;
+  }
+
+  nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
+  FMRadioService::Singleton()->DisableRDS(r);
+  return r.forget();
+}
+
 NS_IMETHODIMP
 FMRadio::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString type;
   aEvent->GetType(type);
 
   if (!type.EqualsLiteral("visibilitychange")) {
     return NS_ERROR_FAILURE;
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -50,53 +50,81 @@ public:
   {
     return GetOwner();
   }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   static bool Enabled();
 
+  bool RdsEnabled();
+
   bool AntennaAvailable() const;
 
   Nullable<double> GetFrequency() const;
 
   double FrequencyUpperBound() const;
 
   double FrequencyLowerBound() const;
 
   double ChannelWidth() const;
 
+  uint32_t RdsGroupMask() const;
+
+  void SetRdsGroupMask(uint32_t aRdsGroupMask);
+
+  Nullable<unsigned short> GetPi() const;
+
+  Nullable<uint8_t> GetPty() const;
+
+  void GetPs(DOMString& aPsname) const;
+
+  void GetRt(DOMString& aRadiotext) const;
+
+  void GetRdsgroup(JSContext* cx, JS::MutableHandle<JSObject*> retval);
+
   already_AddRefed<DOMRequest> Enable(double aFrequency);
 
   already_AddRefed<DOMRequest> Disable();
 
   already_AddRefed<DOMRequest> SetFrequency(double aFrequency);
 
   already_AddRefed<DOMRequest> SeekUp();
 
   already_AddRefed<DOMRequest> SeekDown();
 
   already_AddRefed<DOMRequest> CancelSeek();
 
+  already_AddRefed<DOMRequest> EnableRDS();
+
+  already_AddRefed<DOMRequest> DisableRDS();
+
   IMPL_EVENT_HANDLER(enabled);
   IMPL_EVENT_HANDLER(disabled);
+  IMPL_EVENT_HANDLER(rdsenabled);
+  IMPL_EVENT_HANDLER(rdsdisabled);
   IMPL_EVENT_HANDLER(antennaavailablechange);
   IMPL_EVENT_HANDLER(frequencychange);
+  IMPL_EVENT_HANDLER(pichange);
+  IMPL_EVENT_HANDLER(ptychange);
+  IMPL_EVENT_HANDLER(pschange);
+  IMPL_EVENT_HANDLER(rtchange);
+  IMPL_EVENT_HANDLER(newrdsgroup);
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
 private:
   ~FMRadio();
 
   void SetCanPlay(bool aCanPlay);
   void EnableAudioChannelAgent();
 
   hal::SwitchState mHeadphoneState;
+  uint32_t mRdsGroupMask;
   bool mAudioChannelAgentEnabled;
   bool mHasInternalAntenna;
   bool mIsShutdown;
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 };
 
 END_FMRADIO_NAMESPACE
--- a/dom/fmradio/FMRadioCommon.h
+++ b/dom/fmradio/FMRadioCommon.h
@@ -25,17 +25,23 @@
 #define END_FMRADIO_NAMESPACE \
   } /* namespace dom */ } /* namespace mozilla */
 
 BEGIN_FMRADIO_NAMESPACE
 
 enum FMRadioEventType
 {
   FrequencyChanged,
-  EnabledChanged
+  EnabledChanged,
+  RDSEnabledChanged,
+  PIChanged,
+  PSChanged,
+  PTYChanged,
+  RadiotextChanged,
+  NewRDSGroup
 };
 
 typedef mozilla::Observer<FMRadioEventType>     FMRadioEventObserver;
 typedef mozilla::ObserverList<FMRadioEventType> FMRadioEventObserverList;
 
 END_FMRADIO_NAMESPACE
 
 #endif /* FMRADIOCOMMON_H_ */
--- a/dom/fmradio/FMRadioService.cpp
+++ b/dom/fmradio/FMRadioService.cpp
@@ -21,16 +21,18 @@
 
 #define BAND_87500_108000_kHz 1
 #define BAND_76000_108000_kHz 2
 #define BAND_76000_90000_kHz  3
 
 #define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
 #define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled"
 
+#define DOM_PARSED_RDS_GROUPS ((0x2 << 30) | (0x3 << 4) | (0x3 << 0))
+
 using namespace mozilla::hal;
 using mozilla::Preferences;
 
 BEGIN_FMRADIO_NAMESPACE
 
 // static
 IFMRadioService*
 IFMRadioService::Singleton()
@@ -44,19 +46,36 @@ IFMRadioService::Singleton()
 
 StaticRefPtr<FMRadioService> FMRadioService::sFMRadioService;
 
 FMRadioService::FMRadioService()
   : mPendingFrequencyInKHz(0)
   , mState(Disabled)
   , mHasReadAirplaneModeSetting(false)
   , mAirplaneModeEnabled(false)
+  , mRDSEnabled(false)
   , mPendingRequest(nullptr)
   , mObserverList(FMRadioEventObserverList())
+  , mRDSGroupMask(0)
+  , mLastPI(0)
+  , mPI(0)
+  , mPTY(0)
+  , mPISet(false)
+  , mPTYSet(false)
+  , mRDSLock("FMRadioService::mRDSLock")
+  , mPSNameState(0)
+  , mRadiotextAB(false)
+  , mRDSGroupSet(false)
+  , mPSNameSet(false)
+  , mRadiotextSet(false)
 {
+  memset(mPSName, 0, sizeof(mPSName));
+  memset(mRadiotext, 0, sizeof(mRadiotext));
+  memset(mTempPSName, 0, sizeof(mTempPSName));
+  memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
 
   // Read power state and frequency from Hal.
   mEnabled = IsFMRadioOn();
   if (mEnabled) {
     mPendingFrequencyInKHz = GetFMRadioFrequency();
     SetState(Enabled);
   }
 
@@ -105,20 +124,22 @@ FMRadioService::FMRadioService()
 
   if (obs && NS_FAILED(obs->AddObserver(this,
                                         MOZSETTINGS_CHANGED_ID,
                                         /* useWeak */ false))) {
     NS_WARNING("Failed to add settings change observer!");
   }
 
   RegisterFMRadioObserver(this);
+  RegisterFMRadioRDSObserver(this);
 }
 
 FMRadioService::~FMRadioService()
 {
+  UnregisterFMRadioRDSObserver(this);
   UnregisterFMRadioObserver(this);
 }
 
 class EnableRunnable MOZ_FINAL : public nsRunnable
 {
 public:
   EnableRunnable(uint32_t aUpperLimit, uint32_t aLowerLimit, uint32_t aSpaceType, uint32_t aPreemphasis)
     : mUpperLimit(aUpperLimit)
@@ -272,16 +293,31 @@ public:
 
     return NS_OK;
   }
 
 private:
   FMRadioSeekDirection mDirection;
 };
 
+class NotifyRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  NotifyRunnable(FMRadioEventType aType) : mType(aType) { }
+
+  NS_IMETHOD Run()
+  {
+    FMRadioService::Singleton()->NotifyFMRadioEvent(mType);
+    return NS_OK;
+  }
+
+private:
+  FMRadioEventType mType;
+};
+
 void
 FMRadioService::TransitionState(const FMRadioResponseType& aResponse,
                                 FMRadioState aState)
 {
   if (mPendingRequest) {
     mPendingRequest->SetReply(aResponse);
     NS_DispatchToMainThread(mPendingRequest);
   }
@@ -369,16 +405,23 @@ FMRadioService::RoundFrequency(double aF
 
 bool
 FMRadioService::IsEnabled() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   return IsFMRadioOn();
 }
 
+bool
+FMRadioService::IsRDSEnabled() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  return mRDSEnabled;
+}
+
 double
 FMRadioService::GetFrequency() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   if (IsEnabled()) {
     int32_t frequencyInKHz = GetFMRadioFrequency();
     return frequencyInKHz / 1000.0;
   }
@@ -402,16 +445,64 @@ FMRadioService::GetFrequencyLowerBound()
 
 double
 FMRadioService::GetChannelWidth() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   return mChannelWidthInKHz / 1000.0;
 }
 
+Nullable<unsigned short>
+FMRadioService::GetPi() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  if (!mPISet) {
+    return Nullable<unsigned short>();
+  }
+  return Nullable<unsigned short>(mPI);
+}
+
+Nullable<uint8_t>
+FMRadioService::GetPty() const
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  if (!mPTYSet) {
+    return Nullable<uint8_t>();
+  }
+  return Nullable<uint8_t>(mPTY);
+}
+
+bool
+FMRadioService::GetPs(nsString& aPSName)
+{
+  MutexAutoLock lock(mRDSLock);
+  if (mPSNameSet) {
+    aPSName = nsString(mPSName);
+  }
+  return mPSNameSet;
+}
+
+bool
+FMRadioService::GetRt(nsString& aRadiotext)
+{
+  MutexAutoLock lock(mRDSLock);
+  if (mRadiotextSet) {
+    aRadiotext = nsString(mRadiotext);
+  }
+  return mRadiotextSet;
+}
+
+bool
+FMRadioService::GetRdsgroup(uint64_t& aRDSGroup)
+{
+  MutexAutoLock lock(mRDSLock);
+  aRDSGroup = mRDSGroup;
+  return mRDSGroupSet;
+}
+
 void
 FMRadioService::Enable(double aFrequencyInMHz,
                        FMRadioReplyRunnable* aReplyRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
   MOZ_ASSERT(aReplyRunnable);
 
   switch (mState) {
@@ -674,16 +765,57 @@ FMRadioService::CancelSeek(FMRadioReplyR
 
   TransitionState(
     ErrorResponse(NS_LITERAL_STRING("Seek action is cancelled")), Enabled);
 
   aReplyRunnable->SetReply(SuccessResponse());
   NS_DispatchToMainThread(aReplyRunnable);
 }
 
+void
+FMRadioService::SetRDSGroupMask(uint32_t aRDSGroupMask)
+{
+  mRDSGroupMask = aRDSGroupMask;
+  if (IsFMRadioOn()) {
+    hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS);
+  }
+}
+
+void
+FMRadioService::EnableRDS(FMRadioReplyRunnable* aReplyRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(aReplyRunnable);
+
+  mRDSEnabled = true;
+  if (IsFMRadioOn()) {
+    hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS);
+  }
+
+  aReplyRunnable->SetReply(SuccessResponse());
+  NS_DispatchToMainThread(aReplyRunnable);
+  NS_DispatchToMainThread(new NotifyRunnable(RDSEnabledChanged));
+}
+
+void
+FMRadioService::DisableRDS(FMRadioReplyRunnable* aReplyRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(aReplyRunnable);
+
+  mRDSEnabled = false;
+  if (IsFMRadioOn()) {
+    hal::DisableRDS();
+  }
+
+  aReplyRunnable->SetReply(SuccessResponse());
+  NS_DispatchToMainThread(aReplyRunnable);
+  NS_DispatchToMainThread(new NotifyRunnable(RDSEnabledChanged));
+}
+
 NS_IMETHODIMP
 FMRadioService::Observe(nsISupports* aSubject,
                         const char* aTopic,
                         const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sFMRadioService);
 
@@ -755,20 +887,28 @@ FMRadioService::Notify(const FMRadioOper
       // event, to make sure the FM app will get the right frequency when the
       // `EnabledChange` event is sent.
       mPendingFrequencyInKHz = GetFMRadioFrequency();
       UpdatePowerState();
 
       // The frequency was changed from '0' to some meaningful number, so we
       // should send the `FrequencyChanged` event manually.
       NotifyFMRadioEvent(FrequencyChanged);
+
+      if (mRDSEnabled) {
+        hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS);
+      }
       break;
     case FM_RADIO_OPERATION_DISABLE:
       MOZ_ASSERT(mState == Disabling);
 
+      mPISet = false;
+      mPTYSet = false;
+      memset(mPSName, 0, sizeof(mPSName));
+      memset(mRadiotext, 0, sizeof(mRadiotext));
       TransitionState(SuccessResponse(), Disabled);
       UpdatePowerState();
       break;
     case FM_RADIO_OPERATION_SEEK:
 
       // Seek action might be cancelled by SetFrequency(), we need to check if
       // the current state is Seeking.
       if (mState == Seeking) {
@@ -780,16 +920,284 @@ FMRadioService::Notify(const FMRadioOper
     case FM_RADIO_OPERATION_TUNE:
       UpdateFrequency();
       break;
     default:
       MOZ_CRASH();
   }
 }
 
+/* This is defined by the RDS standard */
+static const uint16_t sRDSToUnicodeMap[256] = {
+  // The lower half differs from ASCII in 0x1F, 0x24, 0x5E, 0x7E
+  // Most control characters are replaced with 0x20 (space)
+  // 0x0-
+  0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+  0x0020, 0x0009, 0x000A, 0x000B, 0x0020, 0x00D0, 0x0020, 0x0020,
+
+  // 0x1-
+  0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
+  0x0020, 0x0020, 0x0020, 0x001B, 0x0020, 0x0020, 0x0020, 0x00AD,
+
+  // 0x2-
+  0x0020, 0x0021, 0x0022, 0x0023, 0x00A4, 0x0025, 0x0026, 0x0027,
+  0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+
+  // 0x3-
+  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+  0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+
+  // 0x4-
+  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+  0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+
+  // 0x5-
+  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+  0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x2015, 0x005F,
+
+  // 0x6-
+  0x2551, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+  0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+
+  // 0x7-
+  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+  0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x00AF, 0x007F,
+
+  // 0x8-
+  0x00E1, 0x00E0, 0x00E9, 0x00E8, 0x00ED, 0x00EC, 0x00F3, 0x00F2,
+  0x00FA, 0x00F9, 0x00D1, 0x00C7, 0x015E, 0x00DF, 0x00A1, 0x0132,
+
+  // 0x9-
+  0x00E2, 0x00E4, 0x00EA, 0x00EB, 0x00EE, 0x00EF, 0x00F4, 0x00F6,
+  0x00FB, 0x00FC, 0x00F1, 0x00E7, 0x015F, 0x011F, 0x0131, 0x0133,
+
+  // 0xA-
+  0x00AA, 0x03B1, 0x00A9, 0x2030, 0x011E, 0x011B, 0x0148, 0x0151,
+  0x03C0, 0x20AC, 0x00A3, 0x0024, 0x2190, 0x2191, 0x2192, 0x2193,
+
+  // 0xB-
+  0x00BA, 0x00B9, 0x00B2, 0x00B3, 0x00B1, 0x0130, 0x0144, 0x0171,
+  0x03BC, 0x00BF, 0x00F7, 0x00B0, 0x00BC, 0x00BD, 0x00BE, 0x00A7,
+
+  // 0xC-
+  0x00C1, 0x00C0, 0x00C9, 0x00C8, 0x00CD, 0x00CC, 0x00D3, 0x00D2,
+  0x00DA, 0x00D9, 0x0158, 0x010C, 0x0160, 0x017D, 0x00D0, 0x013F,
+
+  // 0xD-
+  0x00C2, 0x00C4, 0x00CA, 0x00CB, 0x00CE, 0x00CF, 0x00D4, 0x00D6,
+  0x00DB, 0x00DC, 0x0159, 0x010D, 0x0161, 0x017E, 0x0111, 0x0140,
+
+  // 0xE-
+  0x00C3, 0x00C5, 0x00C6, 0x0152, 0x0177, 0x00DD, 0x00D5, 0x00D8,
+  0x00DE, 0x014A, 0x0154, 0x0106, 0x015A, 0x0179, 0x0166, 0x00F0,
+
+  // 0xF-
+  0x00E3, 0x00E5, 0x00E6, 0x0153, 0x0175, 0x00FD, 0x00F5, 0x00F8,
+  0x00FE, 0x014B, 0x0155, 0x0107, 0x015B, 0x017A, 0x0167, 0x0020,
+};
+
+void
+FMRadioService::Notify(const FMRadioRDSGroup& aRDSGroup)
+{
+  uint16_t blocks[4];
+  blocks[0] = aRDSGroup.blockA();
+  blocks[1] = aRDSGroup.blockB();
+  blocks[2] = aRDSGroup.blockC();
+  blocks[3] = aRDSGroup.blockD();
+
+  /* Bit 11 in block B determines whether this is a type B group. */
+  uint16_t lastPI = blocks[1] & (1 << 11) ? blocks[2] : mLastPI;
+
+  /* Update PI if it's not set or if we get two PI with the new value. */
+  if ((mPI != blocks[0] && lastPI == blocks[0]) || !mPISet) {
+    mPI = blocks[0];
+    if (!mPISet) {
+      mPSNameState = 0;
+      mRadiotextState = 0;
+      memset(mTempPSName, 0, sizeof(mTempPSName));
+      memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
+    }
+    mPISet = true;
+    NS_DispatchToMainThread(new NotifyRunnable(PIChanged));
+  }
+  mLastPI = blocks[0];
+
+  /* PTY is also updated using the same logic as PI */
+  uint16_t pty = (blocks[1] >> 5) & 0x1F;
+  if ((mPTY != pty && pty == mLastPTY) || !mPTYSet) {
+    mPTY = pty;
+    mPTYSet = true;
+    NS_DispatchToMainThread(new NotifyRunnable(PTYChanged));
+  }
+  mLastPTY = pty;
+
+  uint16_t grouptype = blocks[1] >> 11;
+  switch (grouptype) {
+    case 0: // 0a
+    case 1: // 0b
+    {
+      uint16_t segmentAddr = (blocks[1] & 0x3);
+      // mPSNameState is a bitmask that lets us ensure all segments
+      // are received before updating the PS name.
+      if (!segmentAddr) {
+        mPSNameState = 1;
+      } else {
+        mPSNameState |= 1 << segmentAddr;
+      }
+
+      uint16_t offset = segmentAddr << 1;
+      mTempPSName[offset] = sRDSToUnicodeMap[blocks[3] >> 8];
+      mTempPSName[offset + 1] = sRDSToUnicodeMap[blocks[3] & 0xFF];
+
+      if (mPSNameState != 0xF) {
+        break;
+      }
+
+      mPSNameState = 0;
+      if (memcmp(mTempPSName, mPSName, sizeof(mTempPSName))) {
+        MutexAutoLock lock(mRDSLock);
+        mPSNameSet = true;
+        memcpy(mPSName, mTempPSName, sizeof(mTempPSName));
+        NS_DispatchToMainThread(new NotifyRunnable(PSChanged));
+      }
+      break;
+    }
+    case 4: // 2a Radiotext
+    {
+      uint16_t segmentAddr = (blocks[1] & 0xF);
+      bool textAB = blocks[1] & (1 << 5);
+      if (textAB != mRadiotextAB) {
+        mRadiotextState = 0;
+        memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
+        mRadiotextAB = textAB;
+        MutexAutoLock lock(mRDSLock);
+        memset(mRadiotext, 0, sizeof(mRadiotext));
+        NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
+      }
+
+      // mRadiotextState is a bitmask that lets us ensure all segments
+      // are received before updating the radiotext.
+      if (!segmentAddr) {
+        mRadiotextState = 1;
+      } else {
+        mRadiotextState |= 1 << segmentAddr;
+      }
+
+      uint8_t segment[4];
+      segment[0] = blocks[2] >> 8;
+      segment[1] = blocks[2] & 0xFF;
+      segment[2] = blocks[3] >> 8;
+      segment[3] = blocks[3] & 0xFF;
+
+      uint16_t offset = segmentAddr << 2;
+      bool done = false;
+      for (int i = 0; i < 4; i++) {
+        if (segment[i] == '\r') {
+          mTempRadiotext[offset++] = 0;
+          done = true;
+        } else {
+          mTempRadiotext[offset++] = sRDSToUnicodeMap[segment[i]];
+        }
+      }
+      if (offset == 64) {
+        done = true;
+      }
+
+      if (!done ||
+          (mRadiotextState + 1) != (1 << ((blocks[1] & 0xF) + 1)) ||
+          !memcmp(mTempRadiotext, mRadiotext, sizeof(mTempRadiotext))) {
+        break;
+      }
+
+      MutexAutoLock lock(mRDSLock);
+      mRadiotextSet = true;
+      memcpy(mRadiotext, mTempRadiotext, sizeof(mTempRadiotext));
+      NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
+      break;
+    }
+    case 5: // 2b Radiotext
+    {
+      uint16_t segmentAddr = (blocks[1] & 0xF);
+      bool textAB = blocks[1] & (1 << 5);
+      if (textAB != mRadiotextAB) {
+        mRadiotextState = 0;
+        memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
+        mRadiotextAB = textAB;
+        MutexAutoLock lock(mRDSLock);
+        memset(mRadiotext, 0, sizeof(mRadiotext));
+        NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
+      }
+
+      if (!segmentAddr) {
+        mRadiotextState = 1;
+      } else {
+        mRadiotextState |= 1 << segmentAddr;
+      }
+      uint8_t segment[2];
+      segment[0] = blocks[3] >> 8;
+      segment[1] = blocks[3] & 0xFF;
+
+      uint16_t offset = segmentAddr << 1;
+      bool done = false;
+      for (int i = 0; i < 2; i++) {
+        if (segment[i] == '\r') {
+          mTempRadiotext[offset++] = 0;
+          done = true;
+        } else {
+          mTempRadiotext[offset++] = sRDSToUnicodeMap[segment[i]];
+        }
+      }
+      if (offset == 32) {
+        done = true;
+      }
+
+      if (!done ||
+          (mRadiotextState + 1) != (1 << ((blocks[1] & 0xF) + 1)) ||
+          !memcmp(mTempRadiotext, mRadiotext, sizeof(mTempRadiotext))) {
+        break;
+      }
+
+      MutexAutoLock lock(mRDSLock);
+      mRadiotextSet = true;
+      memcpy(mRadiotext, mTempRadiotext, sizeof(mTempRadiotext));
+      NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
+      break;
+    }
+    case 31: // 15b Fast Tuning and Switching
+    {
+      uint16_t secondPty = (blocks[3] >> 5) & 0x1F;
+      if (pty == mPTY || pty != secondPty) {
+        break;
+      }
+      mPTY = pty;
+      NS_DispatchToMainThread(new NotifyRunnable(PTYChanged));
+      break;
+    }
+  }
+
+  // Only notify users of raw RDS groups that they're interested in.
+  // We always receive DOM_PARSED_RDS_GROUPS when RDS is enabled.
+  if (!(mRDSGroupMask & (1 << grouptype))) {
+    return;
+  }
+
+  uint64_t newgroup = blocks[0];
+  newgroup <<= 16;
+  newgroup |= blocks[1];
+  newgroup <<= 16;
+  newgroup |= blocks[2];
+  newgroup <<= 16;
+  newgroup |= blocks[3];
+
+  MutexAutoLock lock(mRDSLock);
+  mRDSGroup = newgroup;
+  mRDSGroupSet = true;
+  NS_DispatchToMainThread(new NotifyRunnable(NewRDSGroup));
+}
+
 void
 FMRadioService::UpdatePowerState()
 {
   bool enabled = IsFMRadioOn();
   if (enabled != mEnabled) {
     mEnabled = enabled;
     NotifyFMRadioEvent(EnabledChanged);
   }
@@ -797,16 +1205,23 @@ FMRadioService::UpdatePowerState()
 
 void
 FMRadioService::UpdateFrequency()
 {
   int32_t frequency = GetFMRadioFrequency();
   if (mPendingFrequencyInKHz != frequency) {
     mPendingFrequencyInKHz = frequency;
     NotifyFMRadioEvent(FrequencyChanged);
+    mPISet = false;
+    mPTYSet = false;
+    memset(mPSName, 0, sizeof(mPSName));
+    memset(mRadiotext, 0, sizeof(mRadiotext));
+    mRDSGroupSet = false;
+    mPSNameSet = false;
+    mRadiotextSet = false;
   }
 }
 
 // static
 FMRadioService*
 FMRadioService::Singleton()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
--- a/dom/fmradio/FMRadioService.h
+++ b/dom/fmradio/FMRadioService.h
@@ -2,19 +2,21 @@
 /* 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_fmradioservice_h__
 #define mozilla_dom_fmradioservice_h__
 
+#include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PFMRadioRequest.h"
 #include "FMRadioCommon.h"
 #include "mozilla/Hal.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 #include "nsIObserver.h"
 #include "nsXULAppAPI.h"
 
 BEGIN_FMRADIO_NAMESPACE
 
@@ -90,27 +92,36 @@ protected:
  */
 class IFMRadioService
 {
 protected:
   virtual ~IFMRadioService() { }
 
 public:
   virtual bool IsEnabled() const = 0;
+  virtual bool IsRDSEnabled() const = 0;
   virtual double GetFrequency() const = 0;
   virtual double GetFrequencyUpperBound() const = 0;
   virtual double GetFrequencyLowerBound() const = 0;
   virtual double GetChannelWidth() const = 0;
+  virtual Nullable<unsigned short> GetPi() const = 0;
+  virtual Nullable<uint8_t> GetPty() const = 0;
+  virtual bool GetPs(nsString& aPsname) = 0;
+  virtual bool GetRt(nsString& aRadiotext) = 0;
+  virtual bool GetRdsgroup(uint64_t& aRDSGroup) = 0;
 
   virtual void Enable(double aFrequency, FMRadioReplyRunnable* aReplyRunnable) = 0;
   virtual void Disable(FMRadioReplyRunnable* aReplyRunnable) = 0;
   virtual void SetFrequency(double aFrequency, FMRadioReplyRunnable* aReplyRunnable) = 0;
   virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection,
                     FMRadioReplyRunnable* aReplyRunnable) = 0;
   virtual void CancelSeek(FMRadioReplyRunnable* aReplyRunnable) = 0;
+  virtual void SetRDSGroupMask(uint32_t aRDSGroupMask) = 0;
+  virtual void EnableRDS(FMRadioReplyRunnable* aReplyRunnable) = 0;
+  virtual void DisableRDS(FMRadioReplyRunnable* aReplyRunnable) = 0;
 
   /**
    * Register handler to receive the FM Radio events, including:
    *   - StateChangedEvent
    *   - FrequencyChangedEvent
    *
    * Called by FMRadio and FMRadioParent.
    */
@@ -133,50 +144,63 @@ enum FMRadioState
   Disabling,
   Enabling,
   Enabled,
   Seeking
 };
 
 class FMRadioService MOZ_FINAL : public IFMRadioService
                                , public hal::FMRadioObserver
+                               , public hal::FMRadioRDSObserver
                                , public nsIObserver
 {
   friend class ReadAirplaneModeSettingTask;
   friend class EnableRunnable;
   friend class DisableRunnable;
+  friend class NotifyRunnable;
 
 public:
   static FMRadioService* Singleton();
   virtual ~FMRadioService();
 
   NS_DECL_ISUPPORTS
 
   virtual bool IsEnabled() const MOZ_OVERRIDE;
+  virtual bool IsRDSEnabled() const MOZ_OVERRIDE;
   virtual double GetFrequency() const MOZ_OVERRIDE;
   virtual double GetFrequencyUpperBound() const MOZ_OVERRIDE;
   virtual double GetFrequencyLowerBound() const MOZ_OVERRIDE;
   virtual double GetChannelWidth() const MOZ_OVERRIDE;
+  virtual Nullable<unsigned short> GetPi() const MOZ_OVERRIDE;
+  virtual Nullable<uint8_t> GetPty() const MOZ_OVERRIDE;
+  virtual bool GetPs(nsString& aPsname) MOZ_OVERRIDE;
+  virtual bool GetRt(nsString& aRadiotext) MOZ_OVERRIDE;
+  virtual bool GetRdsgroup(uint64_t& aRDSGroup) MOZ_OVERRIDE;
 
   virtual void Enable(double aFrequency,
                       FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Disable(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void SetFrequency(double aFrequency,
                             FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection,
                     FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void CancelSeek(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
+  virtual void SetRDSGroupMask(uint32_t aRDSGroupMask) MOZ_OVERRIDE;
+  virtual void EnableRDS(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
+  virtual void DisableRDS(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
 
   virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
 
   virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
 
   /* FMRadioObserver */
   void Notify(const hal::FMRadioOperationInformation& aInfo) MOZ_OVERRIDE;
+  /* FMRadioRDSObserver */
+  void Notify(const hal::FMRadioRDSGroup& aRDSGroup) MOZ_OVERRIDE;
 
   NS_DECL_NSIOBSERVER
 
 protected:
   FMRadioService();
 
 private:
   int32_t RoundFrequency(double aFrequencyInMHz);
@@ -192,26 +216,51 @@ private:
   bool mEnabled;
 
   int32_t mPendingFrequencyInKHz;
 
   FMRadioState mState;
 
   bool mHasReadAirplaneModeSetting;
   bool mAirplaneModeEnabled;
+  bool mRDSEnabled;
 
   uint32_t mUpperBoundInKHz;
   uint32_t mLowerBoundInKHz;
   uint32_t mChannelWidthInKHz;
   uint32_t mPreemphasis;
 
   nsCOMPtr<nsIThread> mTuneThread;
   nsRefPtr<FMRadioReplyRunnable> mPendingRequest;
 
   FMRadioEventObserverList mObserverList;
 
   static StaticRefPtr<FMRadioService> sFMRadioService;
+
+  uint32_t mRDSGroupMask;
+
+  uint16_t mLastPI;
+  uint16_t mLastPTY;
+  Atomic<uint32_t> mPI;
+  Atomic<uint32_t> mPTY;
+  Atomic<bool> mPISet;
+  Atomic<bool> mPTYSet;
+
+  /* Protects mPSName, mRadiotext, and mRDSGroup */
+  Mutex mRDSLock;
+  char16_t mPSName[9];
+  char16_t mRadiotext[65];
+  uint64_t mRDSGroup;
+
+  uint8_t mPSNameState;
+  uint16_t mRadiotextState;
+  uint16_t mTempPSName[8];
+  uint16_t mTempRadiotext[64];
+  bool mRadiotextAB;
+  bool mRDSGroupSet;
+  bool mPSNameSet;
+  bool mRadiotextSet;
 };
 
 END_FMRADIO_NAMESPACE
 
 #endif // mozilla_dom_fmradioservice_h__
 
--- a/dom/fmradio/ipc/FMRadioChild.cpp
+++ b/dom/fmradio/ipc/FMRadioChild.cpp
@@ -11,17 +11,23 @@
 using namespace mozilla::hal;
 
 BEGIN_FMRADIO_NAMESPACE
 
 StaticAutoPtr<FMRadioChild> FMRadioChild::sFMRadioChild;
 
 FMRadioChild::FMRadioChild()
   : mEnabled(false)
+  , mRDSEnabled(false)
+  , mRDSGroupSet(false)
+  , mPSNameSet(false)
+  , mRadiotextSet(false)
   , mFrequency(0)
+  , mRDSGroup(0)
+  , mRDSGroupMask(0)
   , mObserverList(FMRadioEventObserverList())
 {
   MOZ_COUNT_CTOR(FMRadioChild);
 
   ContentChild::GetSingleton()->SendPFMRadioConstructor(this);
 
   StatusInfo statusInfo;
   SendGetStatusInfo(&statusInfo);
@@ -39,16 +45,22 @@ FMRadioChild::~FMRadioChild()
 }
 
 bool
 FMRadioChild::IsEnabled() const
 {
   return mEnabled;
 }
 
+bool
+FMRadioChild::IsRDSEnabled() const
+{
+  return mRDSEnabled;
+}
+
 double
 FMRadioChild::GetFrequency() const
 {
   return mFrequency;
 }
 
 
 double
@@ -64,16 +76,53 @@ FMRadioChild::GetFrequencyLowerBound() c
 }
 
 double
 FMRadioChild::GetChannelWidth() const
 {
   return mChannelWidth;
 }
 
+Nullable<unsigned short>
+FMRadioChild::GetPi() const
+{
+  return mPI;
+}
+
+Nullable<uint8_t>
+FMRadioChild::GetPty() const
+{
+  return mPTY;
+}
+
+bool
+FMRadioChild::GetPs(nsString& aPSName)
+{
+  if (mPSNameSet) {
+    aPSName = mPSName;
+  }
+  return mPSNameSet;
+}
+
+bool
+FMRadioChild::GetRt(nsString& aRadiotext)
+{
+  if (mRadiotextSet) {
+    aRadiotext = mRadiotext;
+  }
+  return mRadiotextSet;
+}
+
+bool
+FMRadioChild::GetRdsgroup(uint64_t& aRDSGroup)
+{
+  aRDSGroup = mRDSGroup;
+  return mRDSGroupSet;
+}
+
 void
 FMRadioChild::Enable(double aFrequency, FMRadioReplyRunnable* aReplyRunnable)
 {
   SendRequest(aReplyRunnable, EnableRequestArgs(aFrequency));
 }
 
 void
 FMRadioChild::Disable(FMRadioReplyRunnable* aReplyRunnable)
@@ -96,16 +145,35 @@ FMRadioChild::Seek(FMRadioSeekDirection 
 }
 
 void
 FMRadioChild::CancelSeek(FMRadioReplyRunnable* aReplyRunnable)
 {
   SendRequest(aReplyRunnable, CancelSeekRequestArgs());
 }
 
+void
+FMRadioChild::SetRDSGroupMask(uint32_t aRDSGroupMask)
+{
+  mRDSGroupMask = aRDSGroupMask;
+  SendSetRDSGroupMask(aRDSGroupMask);
+}
+
+void
+FMRadioChild::EnableRDS(FMRadioReplyRunnable* aReplyRunnable)
+{
+  SendRequest(aReplyRunnable, EnableRDSArgs());
+}
+
+void
+FMRadioChild::DisableRDS(FMRadioReplyRunnable* aReplyRunnable)
+{
+  SendRequest(aReplyRunnable, DisableRDSArgs());
+}
+
 inline void
 FMRadioChild::NotifyFMRadioEvent(FMRadioEventType aType)
 {
   mObserverList.Broadcast(aType);
 }
 
 void
 FMRadioChild::AddObserver(FMRadioEventObserver* aObserver)
@@ -127,30 +195,125 @@ FMRadioChild::SendRequest(FMRadioReplyRu
   SendPFMRadioRequestConstructor(childRequest, aArgs);
 }
 
 bool
 FMRadioChild::RecvNotifyFrequencyChanged(const double& aFrequency)
 {
   mFrequency = aFrequency;
   NotifyFMRadioEvent(FrequencyChanged);
+
+  if (!mPI.IsNull()) {
+    mPI.SetNull();
+    NotifyFMRadioEvent(PIChanged);
+  }
+  if (!mPTY.IsNull()) {
+    mPTY.SetNull();
+    NotifyFMRadioEvent(PTYChanged);
+  }
+  if (mPSNameSet) {
+    mPSNameSet = false;
+    mPSName.Truncate();
+    NotifyFMRadioEvent(PSChanged);
+  }
+  if (mRadiotextSet) {
+    mRadiotextSet = false;
+    mRadiotext.Truncate();
+    NotifyFMRadioEvent(RadiotextChanged);
+  }
+  mRDSGroupSet = false;
   return true;
 }
 
 bool
 FMRadioChild::RecvNotifyEnabledChanged(const bool& aEnabled,
                                        const double& aFrequency)
 {
   mEnabled = aEnabled;
   mFrequency = aFrequency;
+  if (!mEnabled) {
+    mPI.SetNull();
+    mPTY.SetNull();
+    mPSName.Truncate();
+    mRadiotext.Truncate();
+    mRDSGroupSet = false;
+    mPSNameSet = false;
+    mRadiotextSet = false;
+  }
   NotifyFMRadioEvent(EnabledChanged);
   return true;
 }
 
 bool
+FMRadioChild::RecvNotifyRDSEnabledChanged(const bool& aEnabled)
+{
+  mRDSEnabled = aEnabled;
+  NotifyFMRadioEvent(RDSEnabledChanged);
+  return true;
+}
+
+bool
+FMRadioChild::RecvNotifyPIChanged(const bool& aValid,
+                                  const uint16_t& aCode)
+{
+  if (aValid) {
+    mPI.SetValue(aCode);
+  } else {
+    mPI.SetNull();
+  }
+  NotifyFMRadioEvent(PIChanged);
+  return true;
+}
+
+bool
+FMRadioChild::RecvNotifyPTYChanged(const bool& aValid,
+                                   const uint8_t& aPTY)
+{
+  if (aValid) {
+    mPTY.SetValue(aPTY);
+  } else {
+    mPTY.SetNull();
+  }
+  NotifyFMRadioEvent(PTYChanged);
+  return true;
+}
+
+bool
+FMRadioChild::RecvNotifyPSChanged(const nsString& aPSName)
+{
+  mPSNameSet = true;
+  mPSName = aPSName;
+  NotifyFMRadioEvent(PSChanged);
+  return true;
+}
+
+bool
+FMRadioChild::RecvNotifyRadiotextChanged(const nsString& aRadiotext)
+{
+  mRadiotextSet = true;
+  mRadiotext = aRadiotext;
+  NotifyFMRadioEvent(RadiotextChanged);
+  return true;
+}
+
+bool
+FMRadioChild::RecvNotifyNewRDSGroup(const uint64_t& aGroup)
+{
+  uint16_t grouptype = (aGroup >> 43) & 0x1F;
+  if (!(mRDSGroupMask & (1 << grouptype))) {
+    return true;
+  }
+
+  mRDSGroupSet = true;
+  mRDSGroup = aGroup;
+  NotifyFMRadioEvent(NewRDSGroup);
+  return true;
+}
+
+bool
 FMRadioChild::Recv__delete__()
 {
   return true;
 }
 
 PFMRadioRequestChild*
 FMRadioChild::AllocPFMRadioRequestChild(const FMRadioRequestArgs& aArgs)
 {
--- a/dom/fmradio/ipc/FMRadioChild.h
+++ b/dom/fmradio/ipc/FMRadioChild.h
@@ -29,29 +29,38 @@ public:
   static FMRadioChild* Singleton();
   ~FMRadioChild();
 
   void SendRequest(FMRadioReplyRunnable* aReplyRunnable,
                    FMRadioRequestArgs aArgs);
 
   /* IFMRadioService */
   virtual bool IsEnabled() const MOZ_OVERRIDE;
+  virtual bool IsRDSEnabled() const MOZ_OVERRIDE;
   virtual double GetFrequency() const MOZ_OVERRIDE;
   virtual double GetFrequencyUpperBound() const MOZ_OVERRIDE;
   virtual double GetFrequencyLowerBound() const MOZ_OVERRIDE;
   virtual double GetChannelWidth() const MOZ_OVERRIDE;
+  virtual Nullable<unsigned short> GetPi() const MOZ_OVERRIDE;
+  virtual Nullable<uint8_t> GetPty() const MOZ_OVERRIDE;
+  virtual bool GetPs(nsString& aPSName) MOZ_OVERRIDE;
+  virtual bool GetRt(nsString& aRadiotext) MOZ_OVERRIDE;
+  virtual bool GetRdsgroup(uint64_t& aRDSGroup) MOZ_OVERRIDE;
 
   virtual void Enable(double aFrequency,
                       FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Disable(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void SetFrequency(double frequency,
                             FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection,
                     FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void CancelSeek(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
+  virtual void SetRDSGroupMask(uint32_t aRDSGroupMask) MOZ_OVERRIDE;
+  virtual void EnableRDS(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
+  virtual void DisableRDS(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
 
   virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
 
   virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
 
   /* PFMRadioChild */
   virtual bool
@@ -59,34 +68,64 @@ public:
 
   virtual bool
   RecvNotifyFrequencyChanged(const double& aFrequency) MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyEnabledChanged(const bool& aEnabled,
                            const double& aFrequency) MOZ_OVERRIDE;
 
+  virtual bool
+  RecvNotifyRDSEnabledChanged(const bool& aEnabled) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyPIChanged(const bool& aValid,
+                      const uint16_t& aCode) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyPTYChanged(const bool& aValid,
+                       const uint8_t& aPTY) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyPSChanged(const nsString& aPSName) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyRadiotextChanged(const nsString& aRadiotext) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyNewRDSGroup(const uint64_t& aGroup) MOZ_OVERRIDE;
+
   virtual PFMRadioRequestChild*
   AllocPFMRadioRequestChild(const FMRadioRequestArgs& aArgs) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPFMRadioRequestChild(PFMRadioRequestChild* aActor) MOZ_OVERRIDE;
 
 private:
   FMRadioChild();
 
   void Init();
 
   inline void NotifyFMRadioEvent(FMRadioEventType aType);
 
   bool mEnabled;
+  bool mRDSEnabled;
+  bool mRDSGroupSet;
+  bool mPSNameSet;
+  bool mRadiotextSet;
   double mFrequency;
   double mUpperBound;
   double mLowerBound;
   double mChannelWidth;
+  Nullable<unsigned short> mPI;
+  Nullable<uint8_t> mPTY;
+  nsAutoString mPSName;
+  nsAutoString mRadiotext;
+  uint64_t mRDSGroup;
+  uint32_t mRDSGroupMask;
 
   FMRadioEventObserverList mObserverList;
 
 private:
   static StaticAutoPtr<FMRadioChild> sFMRadioChild;
 };
 
 END_FMRADIO_NAMESPACE
--- a/dom/fmradio/ipc/FMRadioParent.cpp
+++ b/dom/fmradio/ipc/FMRadioParent.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FMRadioParent.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/DebugOnly.h"
 #include "FMRadioRequestParent.h"
 #include "FMRadioService.h"
 
 BEGIN_FMRADIO_NAMESPACE
 
 FMRadioParent::FMRadioParent()
 {
   MOZ_COUNT_CTOR(FMRadioParent);
@@ -64,16 +65,22 @@ FMRadioParent::AllocPFMRadioRequestParen
       break;
     case FMRadioRequestArgs::TSeekRequestArgs:
       IFMRadioService::Singleton()->Seek(
         aArgs.get_SeekRequestArgs().direction(), requestParent);
       break;
     case FMRadioRequestArgs::TCancelSeekRequestArgs:
       IFMRadioService::Singleton()->CancelSeek(requestParent);
       break;
+    case FMRadioRequestArgs::TEnableRDSArgs:
+      IFMRadioService::Singleton()->EnableRDS(requestParent);
+      break;
+    case FMRadioRequestArgs::TDisableRDSArgs:
+      IFMRadioService::Singleton()->DisableRDS(requestParent);
+      break;
     default:
       MOZ_CRASH();
   }
 
   // Balanced in DeallocPFMRadioRequestParent
   return requestParent.forget().take();
 }
 
@@ -93,23 +100,67 @@ FMRadioParent::Notify(const FMRadioEvent
       unused << SendNotifyFrequencyChanged(
         IFMRadioService::Singleton()->GetFrequency());
       break;
     case EnabledChanged:
       unused << SendNotifyEnabledChanged(
         IFMRadioService::Singleton()->IsEnabled(),
         IFMRadioService::Singleton()->GetFrequency());
       break;
+    case RDSEnabledChanged:
+      unused << SendNotifyRDSEnabledChanged(
+        IFMRadioService::Singleton()->IsRDSEnabled());
+      break;
+    case PIChanged: {
+      Nullable<unsigned short> pi =
+        IFMRadioService::Singleton()->GetPi();
+      unused << SendNotifyPIChanged(!pi.IsNull(),
+                                    pi.IsNull() ? 0 : pi.Value());
+      break;
+    }
+    case PTYChanged: {
+      Nullable<uint8_t> pty = IFMRadioService::Singleton()->GetPty();
+      unused << SendNotifyPTYChanged(!pty.IsNull(),
+                                     pty.IsNull() ? 0 : pty.Value());
+      break;
+    }
+    case PSChanged: {
+      nsAutoString psname;
+      IFMRadioService::Singleton()->GetPs(psname);
+      unused << SendNotifyPSChanged(psname);
+      break;
+    }
+    case RadiotextChanged: {
+      nsAutoString radiotext;
+      IFMRadioService::Singleton()->GetRt(radiotext);
+      unused << SendNotifyRadiotextChanged(radiotext);
+      break;
+    }
+    case NewRDSGroup: {
+      uint64_t group;
+      DebugOnly<bool> rdsgroupset =
+        IFMRadioService::Singleton()->GetRdsgroup(group);
+      MOZ_ASSERT(rdsgroupset);
+      unused << SendNotifyNewRDSGroup(group);
+      break;
+    }
     default:
       NS_RUNTIMEABORT("not reached");
       break;
   }
 }
 
 bool
 FMRadioParent::RecvEnableAudio(const bool& aAudioEnabled)
 {
   IFMRadioService::Singleton()->EnableAudio(aAudioEnabled);
   return true;
 }
 
+bool
+FMRadioParent::RecvSetRDSGroupMask(const uint32_t& aRDSGroupMask)
+{
+  IFMRadioService::Singleton()->SetRDSGroupMask(aRDSGroupMask);
+  return true;
+}
+
 END_FMRADIO_NAMESPACE
 
--- a/dom/fmradio/ipc/FMRadioParent.h
+++ b/dom/fmradio/ipc/FMRadioParent.h
@@ -34,14 +34,17 @@ public:
   virtual bool
   DeallocPFMRadioRequestParent(PFMRadioRequestParent* aActor) MOZ_OVERRIDE;
 
   /* FMRadioEventObserver */
   virtual void Notify(const FMRadioEventType& aType) MOZ_OVERRIDE;
 
   virtual bool
   RecvEnableAudio(const bool& aAudioEnabled) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvSetRDSGroupMask(const uint32_t& aRDSGroupMask) MOZ_OVERRIDE;
 };
 
 END_FMRADIO_NAMESPACE
 
 #endif // mozilla_dom_fmradioparent_h__
 
--- a/dom/fmradio/ipc/PFMRadio.ipdl
+++ b/dom/fmradio/ipc/PFMRadio.ipdl
@@ -29,23 +29,33 @@ struct SeekRequestArgs
 {
   FMRadioSeekDirection direction;
 };
 
 struct CancelSeekRequestArgs
 {
 };
 
+struct EnableRDSArgs
+{
+};
+
+struct DisableRDSArgs
+{
+};
+
 union FMRadioRequestArgs
 {
   EnableRequestArgs;
   DisableRequestArgs;
   SetFrequencyRequestArgs;
   SeekRequestArgs;
   CancelSeekRequestArgs;
+  EnableRDSArgs;
+  DisableRDSArgs;
 };
 
 struct StatusInfo
 {
   bool enabled;
   double frequency;
   double upperBound;
   double lowerBound;
@@ -61,16 +71,40 @@ child:
   /**
    * Sent when the frequency is changed.
    */
   NotifyFrequencyChanged(double frequency);
   /**
    * Sent when the power state of FM radio HW is changed.
    */
   NotifyEnabledChanged(bool enabled, double frequency);
+  /**
+   * Sent when RDS is enabled or disabled.
+   */
+  NotifyRDSEnabledChanged(bool enabled);
+  /**
+   * Sent when we have a new PI code.
+   */
+  NotifyPIChanged(bool valid, uint16_t code);
+  /**
+   * Sent when we have a new PTY
+   */
+  NotifyPTYChanged(bool valid, uint8_t pty);
+  /**
+   * Sent when we have a new PS name.
+   */
+  NotifyPSChanged(nsString psname);
+  /**
+   * Sent when we have new radiotext.
+   */
+  NotifyRadiotextChanged(nsString radiotext);
+  /**
+   * Sent when a full RDS group is received.
+   */
+  NotifyNewRDSGroup(uint64_t data);
 
   __delete__();
 
 parent:
   /**
    * Get the current status infomation of FM radio HW synchronously.
    * Sent when the singleton object of FMRadioChild is initialized.
    */
@@ -86,13 +120,18 @@ parent:
    * is more error prone.
    */
   PFMRadioRequest(FMRadioRequestArgs requestType);
 
   /**
    * Enable/Disable audio
    */
   EnableAudio(bool audioEnabled);
+
+  /**
+   * Set RDS group mask
+   */
+  SetRDSGroupMask(uint32_t groupMask);
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -14,17 +14,16 @@
 
 #include "ContentChild.h"
 
 #include "BlobChild.h"
 #include "CrashReporterChild.h"
 #include "TabChild.h"
 
 #include "mozilla/Attributes.h"
-#include "mozilla/a11y/DocAccessibleChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
@@ -703,30 +702,16 @@ ContentChild::InitXPCOM()
     // This object is held alive by the observer service.
     nsRefPtr<SystemMessageHandledObserver> sysMsgObserver =
         new SystemMessageHandledObserver();
     sysMsgObserver->Init();
 
     InitOnContentProcessCreated();
 }
 
-a11y::PDocAccessibleChild*
-ContentChild::AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&)
-{
-  MOZ_ASSERT(false, "should never call this!");
-  return nullptr;
-}
-
-bool
-ContentChild::DeallocPDocAccessibleChild(a11y::PDocAccessibleChild* aChild)
-{
-  delete static_cast<mozilla::a11y::DocAccessibleChild*>(aChild);
-  return true;
-}
-
 PMemoryReportRequestChild*
 ContentChild::AllocPMemoryReportRequestChild(const uint32_t& aGeneration,
                                              const bool &aAnonymize,
                                              const bool &aMinimizeMemoryUsage,
                                              const MaybeFileDesc& aDMDFile)
 {
     MemoryReportRequestChild *actor =
         new MemoryReportRequestChild(aGeneration, aAnonymize, aDMDFile);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -375,18 +375,16 @@ public:
                                          const bool& aIsForBrowser) MOZ_OVERRIDE;
 
     virtual bool RecvPBrowserConstructor(PBrowserChild* aCctor,
                                          const IPCTabContext& aContext,
                                          const uint32_t& aChromeFlags,
                                          const uint64_t& aID,
                                          const bool& aIsForApp,
                                          const bool& aIsForBrowser) MOZ_OVERRIDE;
-    virtual PDocAccessibleChild* AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&) MOZ_OVERRIDE;
-    virtual bool DeallocPDocAccessibleChild(PDocAccessibleChild*) MOZ_OVERRIDE;
 
 private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     /**
      * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -25,18 +25,16 @@
 #include <set>
 
 #include "AppProcessChecker.h"
 #include "AudioChannelService.h"
 #include "BlobParent.h"
 #include "CrashReporterParent.h"
 #include "IHistory.h"
 #include "mozIApplication.h"
-#include "mozilla/a11y/DocAccessibleParent.h"
-#include "nsAccessibilityService.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #include "mozilla/dom/PContentBridgeParent.h"
@@ -2697,44 +2695,16 @@ ContentParent::Observe(nsISupports* aSub
     }
 #endif
     else if (!strcmp(aTopic, "app-theme-changed")) {
         unused << SendOnAppThemeChanged();
     }
     return NS_OK;
 }
 
-  a11y::PDocAccessibleParent*
-ContentParent::AllocPDocAccessibleParent(PDocAccessibleParent* aParent, const uint64_t&)
-{
-  return new a11y::DocAccessibleParent();
-}
-
-bool
-ContentParent::DeallocPDocAccessibleParent(PDocAccessibleParent* aParent)
-{
-  delete static_cast<a11y::DocAccessibleParent*>(aParent);
-  return true;
-}
-
-bool
-ContentParent::RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, const uint64_t& aParentID)
-{
-  auto doc = static_cast<a11y::DocAccessibleParent*>(aDoc);
-  if (aParentDoc) {
-    MOZ_ASSERT(aParentID);
-    auto parentDoc = static_cast<a11y::DocAccessibleParent*>(aParentDoc);
-    return parentDoc->AddChildDoc(doc, aParentID);
-  } else {
-    MOZ_ASSERT(!aParentID);
-    GetAccService()->RemoteDocAdded(doc);
-  }
-  return true;
-}
-
 PCompositorParent*
 ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
                                       base::ProcessId aOtherProcess)
 {
     return CompositorParent::Create(aTransport, aOtherProcess);
 }
 
 PImageBridgeParent*
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -665,21 +665,16 @@ private:
                           const nsCString& aOrigin,
                           const nsString& aDatabaseName,
                           const int64_t& aFileId,
                           int32_t* aRefCnt,
                           int32_t* aDBRefCnt,
                           int32_t* aSliceRefCnt,
                           bool* aResult) MOZ_OVERRIDE;
 
-    virtual PDocAccessibleParent* AllocPDocAccessibleParent(PDocAccessibleParent*, const uint64_t&) MOZ_OVERRIDE;
-    virtual bool DeallocPDocAccessibleParent(PDocAccessibleParent*) MOZ_OVERRIDE;
-    virtual bool RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc,
-                                               PDocAccessibleParent* aParentDoc, const uint64_t& aParentID) MOZ_OVERRIDE;
-
     // If you add strong pointers to cycle collected objects here, be sure to
     // release these objects in ShutDownProcess.  See the comment there for more
     // details.
 
     GeckoChildProcessHost* mSubprocess;
     ContentParent* mOpener;
 
     uint64_t mChildID;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -9,17 +9,16 @@ include protocol PBackground;
 include protocol PBlob;
 include protocol PBluetooth;
 include protocol PBrowser;
 include protocol PCellBroadcast;
 include protocol PCompositor;
 include protocol PContentBridge;
 include protocol PCycleCollectWithLogs;
 include protocol PCrashReporter;
-include protocol PDocAccessible;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PImageBridge;
 include protocol PMemoryReportRequest;
@@ -328,17 +327,16 @@ intr protocol PContent
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
     manages PBrowser;
     manages PCellBroadcast;
     manages PCrashReporter;
     manages PCycleCollectWithLogs;
-    manages PDocAccessible;
     manages PDeviceStorageRequest;
     manages PFileSystemRequest;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
     manages PHal;
     manages PMemoryReportRequest;
     manages PMobileConnection;
@@ -486,24 +484,16 @@ child:
 
     /**
      * Notify windows in the child to apply a new app style.
      */
     OnAppThemeChanged();
 
 parent:
     /**
-     * Tell the parent process a new accessible document has been created.
-     * aParentDoc is the accessible document it was created in if any, and
-     * aParentAcc is the id of the accessible in that document the new document
-     * is a child of.
-     */
-    PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc);
-
-    /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
      * isForBrowser|, we're loading <browser> for an app.  When
      * |isForBrowser|, we're loading <browser>.  When |!isForApp &&
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -477,29 +477,29 @@ NetworkManager.prototype = {
     return isValid;
   },
 
   addHostRoute: function(network, host) {
     if (!this.isValidatedNetwork(network)) {
       return Promise.reject("Invalid network interface.");
     }
 
-    return this.resolveHostname(host)
+    return this.resolveHostname(network, host)
       .then((ipAddresses) => this._updateRoutes(true,
                                                 ipAddresses,
                                                 network.name,
                                                 network.getGateways()));
   },
 
   removeHostRoute: function(network, host) {
     if (!this.isValidatedNetwork(network)) {
       return Promise.reject("Invalid network interface.");
     }
 
-    return this.resolveHostname(host)
+    return this.resolveHostname(network, host)
       .then((ipAddresses) => this._updateRoutes(false,
                                                 ipAddresses,
                                                 network.name,
                                                 network.getGateways()));
   },
 
 #ifdef MOZ_B2G_RIL
   isNetworkTypeSecondaryMobile: function(type) {
@@ -589,28 +589,28 @@ NetworkManager.prototype = {
     let oldActive = this.active;
 
     if (this._overriddenActive) {
       debug("We have an override for the active network: " +
             this._overriddenActive.name);
       // The override was just set, so reconfigure the network.
       if (this.active != this._overriddenActive) {
         this.active = this._overriddenActive;
-        gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
+        this._setDefaultRouteAndDNS(this.active, oldActive);
         Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null);
       }
       return;
     }
 
     // The active network is already our preferred type.
     if (this.active &&
         this.active.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED &&
         this.active.type == this._preferredNetworkType) {
       debug("Active network is already our preferred type.");
-      gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
+      this._setDefaultRouteAndDNS(this.active, oldActive);
       return;
     }
 
     // Find a suitable network interface to activate.
     this.active = null;
 #ifdef MOZ_B2G_RIL
     let defaultDataNetwork;
 #endif
@@ -636,35 +636,35 @@ NetworkManager.prototype = {
       // and DNS on seconary APN.
       if (defaultDataNetwork &&
           this.isNetworkTypeSecondaryMobile(this.active.type) &&
           this.active.type != this.preferredNetworkType) {
         this.active = defaultDataNetwork;
       }
       // Don't set default route on secondary APN
       if (this.isNetworkTypeSecondaryMobile(this.active.type)) {
-        gNetworkService.setDNS(this.active);
+        gNetworkService.setDNS(this.active, function() {});
       } else {
 #endif // MOZ_B2G_RIL
-        gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
+        this._setDefaultRouteAndDNS(this.active, oldActive);
 #ifdef MOZ_B2G_RIL
       }
 #endif
     }
 
     if (this.active != oldActive) {
       Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null);
     }
 
     if (this._manageOfflineStatus) {
       Services.io.offline = !this.active;
     }
   },
 
-  resolveHostname: function(hostname) {
+  resolveHostname: function(network, hostname) {
     // Sanity check for null, undefined and empty string... etc.
     if (!hostname) {
       return Promise.reject(new Error("hostname is empty: " + hostname));
     }
 
     if (hostname.match(this.REGEXP_IPV4) ||
         hostname.match(this.REGEXP_IPV6)) {
       return Promise.resolve([hostname]);
@@ -689,18 +689,28 @@ NetworkManager.prototype = {
       }
 
       if (DEBUG) debug("hostname is resolved: " + hostname);
       if (DEBUG) debug("Addresses: " + JSON.stringify(retval));
 
       deferred.resolve(retval);
     };
 
+    // Bug 1058282 - Explicitly request ipv4 to get around 8.8.8.8 probe at
+    // http://androidxref.com/4.3_r2.1/xref/bionic/libc/netbsd/net/getaddrinfo.c#1923
+    //
+    // Whenever MMS connection is the only network interface, there is no
+    // default route so that any ip probe will fail.
+    let flags = 0;
+    if (network.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
+      flags |= Ci.nsIDNSService.RESOLVE_DISABLE_IPV6;
+    }
+
     // TODO: Bug 992772 - Resolve the hostname with specified networkInterface.
-    gDNSService.asyncResolve(hostname, 0, onLookupComplete, Services.tm.mainThread);
+    gDNSService.asyncResolve(hostname, flags, onLookupComplete, Services.tm.mainThread);
 
     return deferred.promise;
   },
 
   convertConnectionType: function(network) {
     // If there is internal interface change (e.g., MOBILE_MMS, MOBILE_SUPL),
     // the function will return null so that it won't trigger type change event
     // in NetworkInformation API.
@@ -1292,17 +1302,25 @@ NetworkManager.prototype = {
     if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
       debug("Postpone the event and handle it when state is idle.");
       this.wantConnectionEvent = callback;
       return;
     }
     this.wantConnectionEvent = null;
 
     callback.call(this);
-  }
+  },
+
+  _setDefaultRouteAndDNS: function(network, oldInterface) {
+    gNetworkService.setDefaultRoute(network, oldInterface, function(success) {
+      gNetworkService.setDNS(network, function(result) {
+        gNetworkService.setNetworkProxy(network);
+      });
+    });
+  },
 };
 
 let CaptivePortalDetectionHelper = (function() {
 
   const EVENT_CONNECT = "Connect";
   const EVENT_DISCONNECT = "Disconnect";
   let _ongoingInterface = null;
   let _available = ("nsICaptivePortalDetector" in Ci);
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -183,17 +183,16 @@ NetworkService.prototype = {
 
     let params = {
       cmd: "setNetworkInterfaceAlarm",
       ifname: networkName,
       threshold: threshold
     };
 
     params.report = true;
-    params.isAsync = true;
 
     this.controlMessage(params, function(result) {
       if (!isError(result.resultCode)) {
         callback.networkUsageAlarmResult(null);
         return;
       }
 
       this._enableNetworkInterfaceAlarm(networkName, threshold, callback);
@@ -205,17 +204,16 @@ NetworkService.prototype = {
 
     let params = {
       cmd: "enableNetworkInterfaceAlarm",
       ifname: networkName,
       threshold: threshold
     };
 
     params.report = true;
-    params.isAsync = true;
 
     this.controlMessage(params, function(result) {
       if (!isError(result.resultCode)) {
         callback.networkUsageAlarmResult(null);
         return;
       }
       callback.networkUsageAlarmResult(result.reason);
     });
@@ -225,34 +223,32 @@ NetworkService.prototype = {
     if(DEBUG) debug("disableNetworkInterfaceAlarm for " + networkName);
 
     let params = {
       cmd: "disableNetworkInterfaceAlarm",
       ifname: networkName,
     };
 
     params.report = true;
-    params.isAsync = true;
 
     this.controlMessage(params, function(result) {
       callback(result);
     });
   },
 
   setWifiOperationMode: function(interfaceName, mode, callback) {
     if(DEBUG) debug("setWifiOperationMode on " + interfaceName + " to " + mode);
 
     let params = {
       cmd: "setWifiOperationMode",
       ifname: interfaceName,
       mode: mode
     };
 
     params.report = true;
-    params.isAsync = true;
 
     this.controlMessage(params, function(result) {
       if (isError(result.resultCode)) {
         callback.wifiOperationModeResult("netd command error");
       } else {
         callback.wifiOperationModeResult(null);
       }
     });
@@ -272,42 +268,42 @@ NetworkService.prototype = {
         ifname: network.name,
         ip: ip,
         prefixLength: prefixLength
       };
       this.controlMessage(options);
     }
   },
 
-  setDNS: function(networkInterface) {
-    if(DEBUG) debug("Going DNS to " + networkInterface.name);
+  setDNS: function(networkInterface, callback) {
+    if (DEBUG) debug("Going DNS to " + networkInterface.name);
     let dnses = networkInterface.getDnses();
     let options = {
       cmd: "setDNS",
       ifname: networkInterface.name,
       domain: "mozilla." + networkInterface.name + ".doman",
       dnses: dnses
     };
-    this.controlMessage(options);
+    this.controlMessage(options, function(result) {
+      callback.setDnsResult(result.success ? null : result.reason);
+    });
   },
 
-  setDefaultRouteAndDNS: function(network, oldInterface) {
-    if(DEBUG) debug("Going to change route and DNS to " + network.name);
+  setDefaultRoute: function(network, oldInterface, callback) {
+    if (DEBUG) debug("Going to change default route to " + network.name);
     let gateways = network.getGateways();
-    let dnses = network.getDnses();
     let options = {
-      cmd: "setDefaultRouteAndDNS",
+      cmd: "setDefaultRoute",
       ifname: network.name,
       oldIfname: (oldInterface && oldInterface !== network) ? oldInterface.name : null,
-      gateways: gateways,
-      domain: "mozilla." + network.name + ".doman",
-      dnses: dnses
+      gateways: gateways
     };
-    this.controlMessage(options);
-    this.setNetworkProxy(network);
+    this.controlMessage(options, function(result) {
+      callback.nativeCommandResult(!result.error);
+    });
   },
 
   removeDefaultRoute: function(network) {
     if(DEBUG) debug("Remove default route for " + network.name);
     let gateways = network.getGateways();
     let options = {
       cmd: "removeDefaultRoute",
       ifname: network.name,
@@ -410,17 +406,16 @@ NetworkService.prototype = {
 
   // Enable/Disable DHCP server.
   setDhcpServer: function(enabled, config, callback) {
     if (null === config) {
       config = {};
     }
 
     config.cmd = "setDhcpServer";
-    config.isAsync = true;
     config.enabled = enabled;
 
     this.controlMessage(config, function setDhcpServerResult(response) {
       if (!response.success) {
         callback.dhcpServerResult('Set DHCP server error');
         return;
       }
       callback.dhcpServerResult(null);
@@ -432,17 +427,16 @@ NetworkService.prototype = {
     // config should've already contained:
     //   .ifname
     //   .internalIfname
     //   .externalIfname
     config.wifictrlinterfacename = WIFI_CTRL_INTERFACE;
     config.cmd = "setWifiTethering";
 
     // The callback function in controlMessage may not be fired immediately.
-    config.isAsync = true;
     this.controlMessage(config, function setWifiTetheringResult(data) {
       let code = data.resultCode;
       let reason = data.resultReason;
       let enable = data.enable;
       let enableString = enable ? "Enable" : "Disable";
 
       if(DEBUG) debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
 
@@ -453,17 +447,16 @@ NetworkService.prototype = {
       }
     });
   },
 
   // Enable/disable USB tethering by sending commands to netd.
   setUSBTethering: function(enable, config, callback) {
     config.cmd = "setUSBTethering";
     // The callback function in controlMessage may not be fired immediately.
-    config.isAsync = true;
     this.controlMessage(config, function setUsbTetheringResult(data) {
       let code = data.resultCode;
       let reason = data.resultReason;
       let enable = data.enable;
       let enableString = enable ? "Enable" : "Disable";
 
       if(DEBUG) debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
 
@@ -486,41 +479,99 @@ NetworkService.prototype = {
     // Ask net work to report the result when this value is set to true.
     if (callback) {
       params.report = true;
     } else {
       params.report = false;
     }
 
     // The callback function in controlMessage may not be fired immediately.
-    params.isAsync = true;
     //this._usbTetheringAction = TETHERING_STATE_ONGOING;
     this.controlMessage(params, function(data) {
       callback.enableUsbRndisResult(data.result, data.enable);
     });
   },
 
   updateUpStream: function(previous, current, callback) {
     let params = {
       cmd: "updateUpStream",
-      isAsync: true,
       preInternalIfname: previous.internalIfname,
       preExternalIfname: previous.externalIfname,
       curInternalIfname: current.internalIfname,
       curExternalIfname: current.externalIfname
     };
 
     this.controlMessage(params, function(data) {
       let code = data.resultCode;
       let reason = data.resultReason;
       if(DEBUG) debug("updateUpStream result: Code " + code + " reason " + reason);
       callback.updateUpStreamResult(!isError(code), data.curExternalIfname);
     });
   },
 
+  configureInterface: function(config, callback) {
+    let params = {
+      cmd: "configureInterface",
+      ifname: config.ifname,
+      ipaddr: config.ipaddr,
+      mask: config.mask,
+      gateway_long: config.gateway,
+      dns1_long: config.dns1,
+      dns2_long: config.dns2,
+    };
+
+    this.controlMessage(params, function(result) {
+      callback.nativeCommandResult(!result.error);
+    });
+  },
+
+  dhcpRequest: function(interfaceName, callback) {
+    let params = {
+      cmd: "dhcpRequest",
+      ifname: interfaceName
+    };
+
+    this.controlMessage(params, function(result) {
+      callback.dhcpRequestResult(!result.error, result.error ? null : result);
+    });
+  },
+
+  enableInterface: function(interfaceName, callback) {
+    let params = {
+      cmd: "enableInterface",
+      ifname: interfaceName
+    };
+
+    this.controlMessage(params, function(result) {
+      callback.nativeCommandResult(!result.error);
+    });
+  },
+
+  disableInterface: function(interfaceName, callback) {
+    let params = {
+      cmd: "disableInterface",
+      ifname: interfaceName
+    };
+
+    this.controlMessage(params, function(result) {
+      callback.nativeCommandResult(!result.error);
+    });
+  },
+
+  resetConnections: function(interfaceName, callback) {
+    let params = {
+      cmd: "resetConnections",
+      ifname: interfaceName
+    };
+
+    this.controlMessage(params, function(result) {
+      callback.nativeCommandResult(!result.error);
+    });
+  },
+
   shutdown: false,
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "xpcom-shutdown":
         debug("NetworkService shutdown");
         this.shutdown = true;
         Services.obs.removeObserver(this, "xpcom-shutdown");
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -96,17 +96,24 @@ struct CurrentCommand {
 
 typedef Tuple3<NetdCommand*, CommandChain*, CommandCallback> QueueData;
 
 #define GET_CURRENT_NETD_COMMAND   (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a)
 #define GET_CURRENT_CHAIN          (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].b)
 #define GET_CURRENT_CALLBACK       (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].c)
 #define GET_CURRENT_COMMAND        (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a->mData)
 
-#define CNT_OF_ARRAY(a) (sizeof(a) / sizeof(a[0]))
+// A macro for native function call return value check.
+// For native function call, non-zero return value means failure.
+#define RETURN_IF_FAILED(rv) do { \
+  if (SUCCESS != rv) { \
+    return rv; \
+  } \
+} while (0);
+
 
 static NetworkUtils* gNetworkUtils;
 static nsTArray<QueueData> gCommandQueue;
 static CurrentCommand gCurrentCommand;
 static bool gPending = false;
 static nsTArray<nsCString> gReason;
 static NetworkParams *gWifiTetheringParms = 0;
 
@@ -388,16 +395,50 @@ void NetworkUtils::next(CommandChain* aC
   if (!f) {
     delete aChain;
     return;
   }
 
   (*f)(aChain, next, aResult);
 }
 
+CommandResult::CommandResult(int32_t aResultCode)
+  : mIsPending(false)
+{
+  // This is usually not a netd command. We treat the return code
+  // typical linux convention, which uses 0 to indicate success.
+  mResult.mError = (aResultCode == SUCCESS ? false : true);
+  mResult.mResultCode = aResultCode;
+  if (aResultCode != SUCCESS) {
+    // The returned value is sometimes negative, make sure we pass a positive
+    // error number to strerror.
+    enum { STRERROR_R_BUF_SIZE = 1024, };
+    char strerrorBuf[STRERROR_R_BUF_SIZE];
+    strerror_r(abs(aResultCode), strerrorBuf, STRERROR_R_BUF_SIZE);
+    mResult.mReason = NS_ConvertUTF8toUTF16(strerrorBuf);
+  }
+  mResult.mRet = true;
+}
+
+CommandResult::CommandResult(const mozilla::dom::NetworkResultOptions& aResult)
+  : mResult(aResult)
+  , mIsPending(false)
+{
+}
+
+CommandResult::CommandResult(const Pending&)
+  : mIsPending(true)
+{
+}
+
+bool CommandResult::isPending() const
+{
+  return mIsPending;
+}
+
 /**
  * Send command to netd.
  */
 void NetworkUtils::nextNetdCommand()
 {
   if (gCommandQueue.IsEmpty() || gPending) {
     return;
   }
@@ -1025,85 +1066,89 @@ NetworkUtils::NetworkUtils(MessageCallba
 
 NetworkUtils::~NetworkUtils()
 {
 }
 
 #define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aOptions.prop).get()
 #define GET_FIELD(prop) aOptions.prop
 
+// Hoist this type definition to global to avoid template
+// instantiation error on gcc 4.4 used by ICS emulator.
+typedef CommandResult (NetworkUtils::*CommandHandler)(NetworkParams&);
+struct CommandHandlerEntry
+{
+  const char* mCommandName;
+  CommandHandler mCommandHandler;
+};
+
 void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
 {
-  typedef int32_t (NetworkUtils::*CommandHandler)(NetworkParams&);
-
-  const static struct {
-    const char* mCommandName;
-    CommandHandler mCommandHandler;
-  } COMMAND_HANDLER_TABLE[] = {
+  const static CommandHandlerEntry
+    COMMAND_HANDLER_TABLE[] = {
 
     // For command 'testCommand', BUILD_ENTRY(testCommand) will generate
     // {"testCommand", NetworkUtils::testCommand}
     #define BUILD_ENTRY(c) {#c, &NetworkUtils::c}
 
     BUILD_ENTRY(removeNetworkRoute),
     BUILD_ENTRY(setDNS),
-    BUILD_ENTRY(setDefaultRouteAndDNS),
+    BUILD_ENTRY(setDefaultRoute),
     BUILD_ENTRY(removeDefaultRoute),
     BUILD_ENTRY(addHostRoute),
     BUILD_ENTRY(removeHostRoute),
     BUILD_ENTRY(removeHostRoutes),
     BUILD_ENTRY(addSecondaryRoute),
     BUILD_ENTRY(removeSecondaryRoute),
     BUILD_ENTRY(setNetworkInterfaceAlarm),
     BUILD_ENTRY(enableNetworkInterfaceAlarm),
     BUILD_ENTRY(disableNetworkInterfaceAlarm),
     BUILD_ENTRY(setWifiOperationMode),
     BUILD_ENTRY(setDhcpServer),
     BUILD_ENTRY(setWifiTethering),
     BUILD_ENTRY(setUSBTethering),
     BUILD_ENTRY(enableUsbRndis),
     BUILD_ENTRY(updateUpStream),
+    BUILD_ENTRY(configureInterface),
+    BUILD_ENTRY(dhcpRequest),
+    BUILD_ENTRY(enableInterface),
+    BUILD_ENTRY(disableInterface),
+    BUILD_ENTRY(resetConnections),
 
     #undef BUILD_ENTRY
   };
 
   // Loop until we find the command name which matches aOptions.mCmd.
   CommandHandler handler = nullptr;
-  for (size_t i = 0; i < CNT_OF_ARRAY(COMMAND_HANDLER_TABLE); i++) {
+  for (size_t i = 0; i < mozilla::ArrayLength(COMMAND_HANDLER_TABLE); i++) {
     if (aOptions.mCmd.EqualsASCII(COMMAND_HANDLER_TABLE[i].mCommandName)) {
       handler = COMMAND_HANDLER_TABLE[i].mCommandHandler;
       break;
     }
   }
 
   if (!handler) {
     // Command not found in COMMAND_HANDLER_TABLE.
     WARN("unknown message: %s", NS_ConvertUTF16toUTF8(aOptions.mCmd).get());
     return;
   }
 
-  // Command matches! Dispatch to the handler.
-  int32_t ret = 0;
-  ret = (this->*handler)(aOptions);
-
-  if (!aOptions.mIsAsync) {
-    // The requested command is synchronous, which implies the actual result
-    // from netd is not important to the client. So, just notify the
-    // registered callback.
-    NetworkResultOptions result;
-    result.mError = ret == SUCCESS ? false : true;
-    result.mResultCode = ret;
-    if (ret != SUCCESS) {
-      // The returned value is sometimes negative, make sure we pass a positive
-      // error number to strerror.
-      result.mReason = NS_ConvertUTF8toUTF16(strerror(abs(ret)));
-    }
-
-    result.mRet = true;
-    postMessage(aOptions, result);
+  // The handler would return one of the following 3 values
+  // to be wrapped to CommandResult:
+  //
+  //   1) |int32_t| for mostly synchronous native function calls.
+  //   2) |NetworkResultOptions| to populate additional results. (e.g. dhcpRequest)
+  //   3) |CommandResult::Pending| to indicate the result is not
+  //      obtained yet.
+  //
+  // If the handler returns "Pending", the handler should take the
+  // responsibility for posting result to main thread.
+  CommandResult commandResult = (this->*handler)(aOptions);
+  if (!commandResult.isPending()) {
+    postMessage(aOptions, commandResult.mResult);
   }
 }
 
 /**
  * Handle received data from netd.
  */
 void NetworkUtils::onNetdMessage(NetdCommand* aCommand)
 {
@@ -1178,36 +1223,36 @@ void NetworkUtils::onNetdMessage(NetdCom
   if (isComplete(code)) {
     nextNetdCommand();
   }
 }
 
 /**
  * Start/Stop DHCP server.
  */
-int32_t NetworkUtils::setDhcpServer(NetworkParams& aOptions)
+CommandResult NetworkUtils::setDhcpServer(NetworkParams& aOptions)
 {
   if (aOptions.mEnabled) {
     aOptions.mWifiStartIp = aOptions.mStartIp;
     aOptions.mWifiEndIp = aOptions.mEndIp;
     aOptions.mIp = aOptions.mServerIp;
     aOptions.mPrefix = aOptions.mMaskLength;
     aOptions.mLink = NS_ConvertUTF8toUTF16("up");
 
     RUN_CHAIN(aOptions, sStartDhcpServerChain, setDhcpServerFail)
   } else {
     RUN_CHAIN(aOptions, sStopDhcpServerChain, setDhcpServerFail)
   }
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
 /**
  * Set DNS servers for given network interface.
  */
-int32_t NetworkUtils::setDNS(NetworkParams& aOptions)
+CommandResult NetworkUtils::setDNS(NetworkParams& aOptions)
 {
   uint32_t length = aOptions.mDnses.Length();
 
   if (length > 0) {
     for (uint32_t i = 0; i < length; i++) {
       NS_ConvertUTF16toUTF8 autoDns(aOptions.mDnses[i]);
 
       char dns_prop_key[PROPERTY_VALUE_MAX];
@@ -1229,101 +1274,193 @@ int32_t NetworkUtils::setDNS(NetworkPara
 
   char num[PROPERTY_VALUE_MAX];
   snprintf(num, PROPERTY_VALUE_MAX - 1, "%d", atoi(dnschange) + 1);
   property_set("net.dnschange", num);
 
   // DNS needs to be set through netd since JellyBean (4.3).
   if (SDK_VERSION >= 18) {
     RUN_CHAIN(aOptions, sSetDnsChain, setDnsFail)
+    return CommandResult::Pending();
   }
 
   return SUCCESS;
 }
 
+CommandResult NetworkUtils::configureInterface(NetworkParams& aOptions)
+{
+  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+  return mNetUtils->do_ifc_configure(
+    autoIfname.get(),
+    aOptions.mIpaddr,
+    aOptions.mMask,
+    aOptions.mGateway_long,
+    aOptions.mDns1_long,
+    aOptions.mDns2_long
+  );
+}
+
+CommandResult NetworkUtils::dhcpRequest(NetworkParams& aOptions) {
+    mozilla::dom::NetworkResultOptions result;
+
+    NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+    char ipaddr[PROPERTY_VALUE_MAX];
+    char gateway[PROPERTY_VALUE_MAX];
+    uint32_t prefixLength;
+    char dns1[PROPERTY_VALUE_MAX];
+    char dns2[PROPERTY_VALUE_MAX];
+    char server[PROPERTY_VALUE_MAX];
+    uint32_t lease;
+    char vendorinfo[PROPERTY_VALUE_MAX];
+    int32_t ret = mNetUtils->do_dhcp_do_request(autoIfname.get(),
+                                                ipaddr,
+                                                gateway,
+                                                &prefixLength,
+                                                dns1,
+                                                dns2,
+                                                server,
+                                                &lease,
+                                                vendorinfo);
+
+    RETURN_IF_FAILED(ret);
+
+    result.mIpaddr_str = NS_ConvertUTF8toUTF16(ipaddr);
+    result.mGateway_str = NS_ConvertUTF8toUTF16(gateway);
+    result.mDns1_str = NS_ConvertUTF8toUTF16(dns1);
+    result.mDns2_str = NS_ConvertUTF8toUTF16(dns2);
+    result.mServer_str = NS_ConvertUTF8toUTF16(server);
+    result.mVendor_str = NS_ConvertUTF8toUTF16(vendorinfo);
+    result.mLease = lease;
+    result.mMask = makeMask(prefixLength);
+
+    uint32_t inet4; // only support IPv4 for now.
+
+#define INET_PTON(var, field)                                                 \
+  PR_BEGIN_MACRO                                                              \
+    inet_pton(AF_INET, var, &inet4);                                          \
+    result.field = inet4;                                                    \
+  PR_END_MACRO
+
+    INET_PTON(ipaddr, mIpaddr);
+    INET_PTON(gateway, mGateway);
+
+    if (dns1[0] != '\0') {
+      INET_PTON(dns1, mDns1);
+    }
+
+    if (dns2[0] != '\0') {
+      INET_PTON(dns2, mDns2);
+    }
+
+    INET_PTON(server, mServer);
+
+    char inet_str[64];
+    if (inet_ntop(AF_INET, &result.mMask, inet_str, sizeof(inet_str))) {
+      result.mMask_str = NS_ConvertUTF8toUTF16(inet_str);
+    }
+
+    return result;
+}
+
+CommandResult NetworkUtils::enableInterface(NetworkParams& aOptions) {
+  return mNetUtils->do_ifc_enable(
+    NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
+}
+
+CommandResult NetworkUtils::disableInterface(NetworkParams& aOptions) {
+  return mNetUtils->do_ifc_disable(
+    NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
+}
+
+CommandResult NetworkUtils::resetConnections(NetworkParams& aOptions) {
+  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+  return mNetUtils->do_ifc_reset_connections(
+    NS_ConvertUTF16toUTF8(aOptions.mIfname).get(),
+    RESET_ALL_ADDRESSES);
+}
+
 /**
  * Set default route and DNS servers for given network interface.
  */
-int32_t NetworkUtils::setDefaultRouteAndDNS(NetworkParams& aOptions)
+CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
 {
   NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
 
   if (!aOptions.mOldIfname.IsEmpty()) {
     // Remove IPv4's default route.
-    mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname));
+    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname)));
     // Remove IPv6's default route.
-    mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL);
+    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
   }
 
   uint32_t length = aOptions.mGateways.Length();
   if (length > 0) {
     for (uint32_t i = 0; i < length; i++) {
       NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
 
       int type = getIpType(autoGateway.get());
       if (type != AF_INET && type != AF_INET6) {
         continue;
       }
 
       if (type == AF_INET6) {
-        mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, autoGateway.get());
+        RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, autoGateway.get()));
       } else { /* type == AF_INET */
-        mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(autoGateway.get()));
+        RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(autoGateway.get())));
       }
     }
   } else {
     // Set default froute from system properties.
     char key[PROPERTY_KEY_MAX];
     char gateway[PROPERTY_KEY_MAX];
 
     snprintf(key, sizeof key - 1, "net.%s.gw", autoIfname.get());
     property_get(key, gateway, "");
 
     int type = getIpType(gateway);
     if (type != AF_INET && type != AF_INET6) {
       return EAFNOSUPPORT;
     }
 
     if (type == AF_INET6) {
-      mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, gateway);
+      RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, gateway));
     } else { /* type == AF_INET */
-      mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(gateway));
+      RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(gateway)));
     }
   }
 
-  setDNS(aOptions);
   return SUCCESS;
 }
 
 /**
  * Remove default route for given network interface.
  */
-int32_t NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
 {
   uint32_t length = aOptions.mGateways.Length();
   for (uint32_t i = 0; i < length; i++) {
     NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
 
     int type = getIpType(autoGateway.get());
     if (type != AF_INET && type != AF_INET6) {
       return EAFNOSUPPORT;
     }
 
-    mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
-                                   type == AF_INET ? "0.0.0.0" : "::",
-                                   0, autoGateway.get());
+    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
+                                                    type == AF_INET ? "0.0.0.0" : "::",
+                                                    0, autoGateway.get()));
   }
 
   return SUCCESS;
 }
 
 /**
  * Add host route for given network interface.
  */
-int32_t NetworkUtils::addHostRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::addHostRoute(NetworkParams& aOptions)
 {
   NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
   NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
   NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
   int type, prefix;
 
   type = getIpType(autoHostname.get());
   if (type != AF_INET && type != AF_INET6) {
@@ -1337,17 +1474,17 @@ int32_t NetworkUtils::addHostRoute(Netwo
   prefix = type == AF_INET ? 32 : 128;
   return mNetUtils->do_ifc_add_route(autoIfname.get(), autoHostname.get(),
                                      prefix, autoGateway.get());
 }
 
 /**
  * Remove host route for given network interface.
  */
-int32_t NetworkUtils::removeHostRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::removeHostRoute(NetworkParams& aOptions)
 {
   NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
   NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
   NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
   int type, prefix;
 
   type = getIpType(autoHostname.get());
   if (type != AF_INET && type != AF_INET6) {
@@ -1361,22 +1498,22 @@ int32_t NetworkUtils::removeHostRoute(Ne
   prefix = type == AF_INET ? 32 : 128;
   return mNetUtils->do_ifc_remove_route(autoIfname.get(), autoHostname.get(),
                                         prefix, autoGateway.get());
 }
 
 /**
  * Remove the routes associated with the named interface.
  */
-int32_t NetworkUtils::removeHostRoutes(NetworkParams& aOptions)
+CommandResult NetworkUtils::removeHostRoutes(NetworkParams& aOptions)
 {
   return mNetUtils->do_ifc_remove_host_routes(GET_CHAR(mIfname));
 }
 
-int32_t NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
 {
   NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
   NS_ConvertUTF16toUTF8 autoIp(aOptions.mIp);
 
   int type = getIpType(autoIp.get());
   if (type != AF_INET && type != AF_INET6) {
     return EAFNOSUPPORT;
   }
@@ -1401,100 +1538,100 @@ int32_t NetworkUtils::removeNetworkRoute
     }
 
     char subnetStr[INET6_ADDRSTRLEN];
     if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
       return EINVAL;
     }
 
     // Remove default route.
-    mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL);
+    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
 
     // Remove subnet route.
-    mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL);
+    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL));
     return SUCCESS;
   }
 
   /* type == AF_INET */
   uint32_t ip = inet_addr(autoIp.get());
   uint32_t netmask = makeMask(prefixLength);
   uint32_t subnet = ip & netmask;
   const char* gateway = "0.0.0.0";
   struct in_addr addr;
   addr.s_addr = subnet;
   const char* dst = inet_ntoa(addr);
 
-  mNetUtils->do_ifc_remove_default_route(autoIfname.get());
-  mNetUtils->do_ifc_remove_route(autoIfname.get(), dst, prefixLength, gateway);
+  RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(autoIfname.get()));
+  RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), dst, prefixLength, gateway));
   return SUCCESS;
 }
 
-int32_t NetworkUtils::addSecondaryRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::addSecondaryRoute(NetworkParams& aOptions)
 {
   char command[MAX_COMMAND_SIZE];
   snprintf(command, MAX_COMMAND_SIZE - 1,
            "interface route add %s secondary %s %s %s",
            GET_CHAR(mIfname),
            GET_CHAR(mIp),
            GET_CHAR(mPrefix),
            GET_CHAR(mGateway));
 
   doCommand(command, nullptr, nullptr);
   return SUCCESS;
 }
 
-int32_t NetworkUtils::removeSecondaryRoute(NetworkParams& aOptions)
+CommandResult NetworkUtils::removeSecondaryRoute(NetworkParams& aOptions)
 {
   char command[MAX_COMMAND_SIZE];
   snprintf(command, MAX_COMMAND_SIZE - 1,
            "interface route remove %s secondary %s %s %s",
            GET_CHAR(mIfname),
            GET_CHAR(mIp),
            GET_CHAR(mPrefix),
            GET_CHAR(mGateway));
 
   doCommand(command, nullptr, nullptr);
   return SUCCESS;
 }
 
-int32_t NetworkUtils::setNetworkInterfaceAlarm(NetworkParams& aOptions)
+CommandResult NetworkUtils::setNetworkInterfaceAlarm(NetworkParams& aOptions)
 {
   DEBUG("setNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
   RUN_CHAIN(aOptions, sNetworkInterfaceSetAlarmChain, networkInterfaceAlarmFail);
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
-int32_t NetworkUtils::enableNetworkInterfaceAlarm(NetworkParams& aOptions)
+CommandResult NetworkUtils::enableNetworkInterfaceAlarm(NetworkParams& aOptions)
 {
   DEBUG("enableNetworkInterfaceAlarm: %s", GET_CHAR(mIfname));
   RUN_CHAIN(aOptions, sNetworkInterfaceEnableAlarmChain, networkInterfaceAlarmFail);
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
-int32_t NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions)
+CommandResult NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions)
 {
   DEBUG("disableNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
   RUN_CHAIN(aOptions, sNetworkInterfaceDisableAlarmChain, networkInterfaceAlarmFail);
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
 /**
  * handling main thread's reload Wifi firmware request
  */
-int32_t NetworkUtils::setWifiOperationMode(NetworkParams& aOptions)
+CommandResult NetworkUtils::setWifiOperationMode(NetworkParams& aOptions)
 {
   DEBUG("setWifiOperationMode: %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
   RUN_CHAIN(aOptions, sWifiOperationModeChain, wifiOperationModeFail);
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
 /**
  * handling main thread's enable/disable WiFi Tethering request
  */
-int32_t NetworkUtils::setWifiTethering(NetworkParams& aOptions)
+CommandResult NetworkUtils::setWifiTethering(NetworkParams& aOptions)
 {
   bool enable = aOptions.mEnable;
   IFProperties interfaceProperties;
   getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);
 
   if (strcmp(interfaceProperties.dns1, "")) {
     int type = getIpType(interfaceProperties.dns1);
     if (type != AF_INET6) {
@@ -1513,20 +1650,20 @@ int32_t NetworkUtils::setWifiTethering(N
     DEBUG("Starting Wifi Tethering on %s <-> %s",
            GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
     RUN_CHAIN(aOptions, sWifiEnableChain, wifiTetheringFail)
   } else {
     DEBUG("Stopping Wifi Tethering on %s <-> %s",
            GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
     RUN_CHAIN(aOptions, sWifiDisableChain, wifiTetheringFail)
   }
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
-int32_t NetworkUtils::setUSBTethering(NetworkParams& aOptions)
+CommandResult NetworkUtils::setUSBTethering(NetworkParams& aOptions)
 {
   bool enable = aOptions.mEnable;
   IFProperties interfaceProperties;
   getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);
 
   if (strcmp(interfaceProperties.dns1, "")) {
     int type = getIpType(interfaceProperties.dns1);
     if (type != AF_INET6) {
@@ -1545,61 +1682,59 @@ int32_t NetworkUtils::setUSBTethering(Ne
     DEBUG("Starting USB Tethering on %s <-> %s",
            GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
     RUN_CHAIN(aOptions, sUSBEnableChain, usbTetheringFail)
   } else {
     DEBUG("Stopping USB Tethering on %s <-> %s",
            GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
     RUN_CHAIN(aOptions, sUSBDisableChain, usbTetheringFail)
   }
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
 void NetworkUtils::escapeQuote(nsCString& aString)
 {
   aString.ReplaceSubstring("\\", "\\\\");
   aString.ReplaceSubstring("\"", "\\\"");
 }
 
-void NetworkUtils::checkUsbRndisState(NetworkParams& aOptions)
+CommandResult NetworkUtils::checkUsbRndisState(NetworkParams& aOptions)
 {
   static uint32_t retry = 0;
 
   char currentState[PROPERTY_VALUE_MAX];
   property_get(SYS_USB_STATE_PROPERTY, currentState, nullptr);
 
   nsTArray<nsCString> stateFuncs;
   split(currentState, USB_CONFIG_DELIMIT, stateFuncs);
   bool rndisPresent = stateFuncs.Contains(nsCString(USB_FUNCTION_RNDIS));
 
   if (aOptions.mEnable == rndisPresent) {
     NetworkResultOptions result;
     result.mEnable = aOptions.mEnable;
     result.mResult = true;
-    postMessage(aOptions, result);
     retry = 0;
-    return;
+    return result;
   }
   if (retry < USB_FUNCTION_RETRY_TIMES) {
     retry++;
     usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
-    checkUsbRndisState(aOptions);
-    return;
+    return checkUsbRndisState(aOptions);
   }
 
   NetworkResultOptions result;
   result.mResult = false;
-  postMessage(aOptions, result);
   retry = 0;
+  return result;
 }
 
 /**
  * Modify usb function's property to turn on USB RNDIS function
  */
-int32_t NetworkUtils::enableUsbRndis(NetworkParams& aOptions)
+CommandResult NetworkUtils::enableUsbRndis(NetworkParams& aOptions)
 {
   bool report = aOptions.mReport;
 
   // For some reason, rndis doesn't play well with diag,modem,nmea.
   // So when turning rndis on, we set sys.usb.config to either "rndis"
   // or "rndis,adb". When turning rndis off, we go back to
   // persist.sys.usb.config.
   //
@@ -1645,28 +1780,28 @@ int32_t NetworkUtils::enableUsbRndis(Net
   join(configFuncs, USB_CONFIG_DELIMIT, PROPERTY_VALUE_MAX, newConfig);
   if (strcmp(currentConfig, newConfig)) {
     property_set(SYS_USB_CONFIG_PROPERTY, newConfig);
   }
 
   // Trigger the timer to check usb state and report the result to NetworkManager.
   if (report) {
     usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
-    checkUsbRndisState(aOptions);
+    return checkUsbRndisState(aOptions);
   }
   return SUCCESS;
 }
 
 /**
  * handling upstream interface change event.
  */
-int32_t NetworkUtils::updateUpStream(NetworkParams& aOptions)
+CommandResult NetworkUtils::updateUpStream(NetworkParams& aOptions)
 {
   RUN_CHAIN(aOptions, sUpdateUpStreamChain, updateUpStreamFail)
-  return SUCCESS;
+  return CommandResult::Pending();
 }
 
 void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
 {
   NetworkResultOptions result;
   switch(code) {
     case NETD_COMMAND_INTERFACE_CHANGE:
       result.mTopic = NS_ConvertUTF8toUTF16("netd-interface-change");
--- a/dom/system/gonk/NetworkUtils.h
+++ b/dom/system/gonk/NetworkUtils.h
@@ -23,58 +23,16 @@ typedef void (*ErrorCallback)(NetworkPar
                               mozilla::dom::NetworkResultOptions& aResult);
 
 class NetworkParams
 {
 public:
   NetworkParams() {
   }
 
-  NetworkParams(const NetworkParams& aOther) {
-    mIp = aOther.mIp;
-    mCmd = aOther.mCmd;
-    mDomain = aOther.mDomain;
-    mGateway = aOther.mGateway;
-    mGateways = aOther.mGateways;
-    mId = aOther.mId;
-    mIfname = aOther.mIfname;
-    mPrefixLength = aOther.mPrefixLength;
-    mOldIfname = aOther.mOldIfname;
-    mMode = aOther.mMode;
-    mReport = aOther.mReport;
-    mIsAsync = aOther.mIsAsync;
-    mEnabled = aOther.mEnabled;
-    mWifictrlinterfacename = aOther.mWifictrlinterfacename;
-    mInternalIfname = aOther.mInternalIfname;
-    mExternalIfname = aOther.mExternalIfname;
-    mEnable = aOther.mEnable;
-    mSsid = aOther.mSsid;
-    mSecurity = aOther.mSecurity;
-    mKey = aOther.mKey;
-    mPrefix = aOther.mPrefix;
-    mLink = aOther.mLink;
-    mInterfaceList = aOther.mInterfaceList;
-    mWifiStartIp = aOther.mWifiStartIp;
-    mWifiEndIp = aOther.mWifiEndIp;
-    mUsbStartIp = aOther.mUsbStartIp;
-    mUsbEndIp = aOther.mUsbEndIp;
-    mDns1 = aOther.mDns1;
-    mDns2 = aOther.mDns2;
-    mDnses = aOther.mDnses;
-    mStartIp = aOther.mStartIp;
-    mEndIp = aOther.mEndIp;
-    mServerIp = aOther.mServerIp;
-    mMaskLength = aOther.mMaskLength;
-    mPreInternalIfname = aOther.mPreInternalIfname;
-    mPreExternalIfname = aOther.mPreExternalIfname;
-    mCurInternalIfname = aOther.mCurInternalIfname;
-    mCurExternalIfname = aOther.mCurExternalIfname;
-    mThreshold = aOther.mThreshold;
-  }
-
   NetworkParams(const mozilla::dom::NetworkCommandOptions& aOther) {
 
 #define COPY_SEQUENCE_FIELD(prop, type)                                                      \
     if (aOther.prop.WasPassed()) {                                                           \
       mozilla::dom::Sequence<type > const & currentValue = aOther.prop.InternalValue();      \
       uint32_t length = currentValue.Length();                                               \
       for (uint32_t idx = 0; idx < length; idx++) {                                          \
         prop.AppendElement(currentValue[idx]);                                               \
@@ -107,17 +65,16 @@ public:
     COPY_OPT_STRING_FIELD(mGateway, EmptyString())
     COPY_SEQUENCE_FIELD(mGateways, nsString)
     COPY_OPT_STRING_FIELD(mIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mIp, EmptyString())
     COPY_OPT_FIELD(mPrefixLength, 0)
     COPY_OPT_STRING_FIELD(mOldIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mMode, EmptyString())
     COPY_OPT_FIELD(mReport, false)
-    COPY_OPT_FIELD(mIsAsync, false)
     COPY_OPT_FIELD(mEnabled, false)
     COPY_OPT_STRING_FIELD(mWifictrlinterfacename, EmptyString())
     COPY_OPT_STRING_FIELD(mInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mExternalIfname, EmptyString())
     COPY_OPT_FIELD(mEnable, false)
     COPY_OPT_STRING_FIELD(mSsid, EmptyString())
     COPY_OPT_STRING_FIELD(mSecurity, EmptyString())
     COPY_OPT_STRING_FIELD(mKey, EmptyString())
@@ -135,16 +92,21 @@ public:
     COPY_OPT_STRING_FIELD(mEndIp, EmptyString())
     COPY_OPT_STRING_FIELD(mServerIp, EmptyString())
     COPY_OPT_STRING_FIELD(mMaskLength, EmptyString())
     COPY_OPT_STRING_FIELD(mPreInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mPreExternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mCurInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mCurExternalIfname, EmptyString())
     COPY_OPT_FIELD(mThreshold, -1)
+    COPY_OPT_FIELD(mIpaddr, 0)
+    COPY_OPT_FIELD(mMask, 0)
+    COPY_OPT_FIELD(mGateway_long, 0)
+    COPY_OPT_FIELD(mDns1_long, 0)
+    COPY_OPT_FIELD(mDns2_long, 0)
 
 #undef COPY_SEQUENCE_FIELD
 #undef COPY_OPT_STRING_FIELD
 #undef COPY_OPT_FIELD
 #undef COPY_FIELD
   }
 
   int32_t mId;
@@ -153,17 +115,16 @@ public:
   nsString mGateway;
   nsTArray<nsString> mGateways;
   nsString mIfname;
   nsString mIp;
   uint32_t mPrefixLength;
   nsString mOldIfname;
   nsString mMode;
   bool mReport;
-  bool mIsAsync;
   bool mEnabled;
   nsString mWifictrlinterfacename;
   nsString mInternalIfname;
   nsString mExternalIfname;
   bool mEnable;
   nsString mSsid;
   nsString mSecurity;
   nsString mKey;
@@ -181,16 +142,21 @@ public:
   nsString mEndIp;
   nsString mServerIp;
   nsString mMaskLength;
   nsString mPreInternalIfname;
   nsString mPreExternalIfname;
   nsString mCurInternalIfname;
   nsString mCurExternalIfname;
   long mThreshold;
+  long mIpaddr;
+  long mMask;
+  long mGateway_long;
+  long mDns1_long;
+  long mDns2_long;
 };
 
 // CommandChain store the necessary information to execute command one by one.
 // Including :
 // 1. Command parameters.
 // 2. Command list.
 // 3. Error callback function.
 // 4. Index of current execution command.
@@ -230,49 +196,73 @@ public:
 private:
   uint32_t mIndex;
   NetworkParams mParams;
   CommandFunc* mCommands;
   uint32_t mLength;
   ErrorCallback mError;
 };
 
+// A helper class to easily construct a resolved
+// or a pending result for command execution.
+class CommandResult
+{
+public:
+  struct Pending {};
+
+public:
+  CommandResult(int32_t aResultCode);
+  CommandResult(const mozilla::dom::NetworkResultOptions& aResult);
+  CommandResult(const Pending&);
+  bool isPending() const;
+
+  mozilla::dom::NetworkResultOptions mResult;
+
+private:
+  bool mIsPending;
+};
+
 class NetworkUtils MOZ_FINAL
 {
 public:
   NetworkUtils(MessageCallback aCallback);
   ~NetworkUtils();
 
   void ExecuteCommand(NetworkParams aOptions);
   void onNetdMessage(mozilla::ipc::NetdCommand* aCommand);
 
   MessageCallback getMessageCallback() { return mMessageCallback; }
 
 private:
   /**
    * Commands supported by NetworkUtils.
    */
-  int32_t setDNS(NetworkParams& aOptions);
-  int32_t setDefaultRouteAndDNS(NetworkParams& aOptions);
-  int32_t addHostRoute(NetworkParams& aOptions);
-  int32_t removeDefaultRoute(NetworkParams& aOptions);
-  int32_t removeHostRoute(NetworkParams& aOptions);
-  int32_t removeHostRoutes(NetworkParams& aOptions);
-  int32_t removeNetworkRoute(NetworkParams& aOptions);
-  int32_t addSecondaryRoute(NetworkParams& aOptions);
-  int32_t removeSecondaryRoute(NetworkParams& aOptions);
-  int32_t setNetworkInterfaceAlarm(NetworkParams& aOptions);
-  int32_t enableNetworkInterfaceAlarm(NetworkParams& aOptions);
-  int32_t disableNetworkInterfaceAlarm(NetworkParams& aOptions);
-  int32_t setWifiOperationMode(NetworkParams& aOptions);
-  int32_t setDhcpServer(NetworkParams& aOptions);
-  int32_t setWifiTethering(NetworkParams& aOptions);
-  int32_t setUSBTethering(NetworkParams& aOptions);
-  int32_t enableUsbRndis(NetworkParams& aOptions);
-  int32_t updateUpStream(NetworkParams& aOptions);
+  CommandResult configureInterface(NetworkParams& aOptions);
+  CommandResult dhcpRequest(NetworkParams& aOptions);
+  CommandResult enableInterface(NetworkParams& aOptions);
+  CommandResult disableInterface(NetworkParams& aOptions);
+  CommandResult resetConnections(NetworkParams& aOptions);
+  CommandResult setDefaultRoute(NetworkParams& aOptions);
+  CommandResult addHostRoute(NetworkParams& aOptions);
+  CommandResult removeDefaultRoute(NetworkParams& aOptions);
+  CommandResult removeHostRoute(NetworkParams& aOptions);
+  CommandResult removeHostRoutes(NetworkParams& aOptions);
+  CommandResult removeNetworkRoute(NetworkParams& aOptions);
+  CommandResult setDNS(NetworkParams& aOptions);
+  CommandResult addSecondaryRoute(NetworkParams& aOptions);
+  CommandResult removeSecondaryRoute(NetworkParams& aOptions);
+  CommandResult setNetworkInterfaceAlarm(NetworkParams& aOptions);
+  CommandResult enableNetworkInterfaceAlarm(NetworkParams& aOptions);
+  CommandResult disableNetworkInterfaceAlarm(NetworkParams& aOptions);
+  CommandResult setWifiOperationMode(NetworkParams& aOptions);
+  CommandResult setDhcpServer(NetworkParams& aOptions);
+  CommandResult setWifiTethering(NetworkParams& aOptions);
+  CommandResult setUSBTethering(NetworkParams& aOptions);
+  CommandResult enableUsbRndis(NetworkParams& aOptions);
+  CommandResult updateUpStream(NetworkParams& aOptions);
 
   /**
    * function pointer array holds all netd commands should be executed
    * in sequence to accomplish a given command by other module.
    */
   static CommandFunc sWifiEnableChain[];
   static CommandFunc sWifiDisableChain[];
   static CommandFunc sWifiFailChain[];
@@ -355,17 +345,17 @@ private:
   /**
    * Notify broadcast message to main thread.
    */
   void sendBroadcastMessage(uint32_t code, char* reason);
 
   /**
    * Utility functions.
    */
-  void checkUsbRndisState(NetworkParams& aOptions);
+  CommandResult checkUsbRndisState(NetworkParams& aOptions);
   void dumpParams(NetworkParams& aOptions, const char* aType);
 
   static void escapeQuote(nsCString& aString);
   inline uint32_t netdResponseType(uint32_t code);
   inline bool isBroadcastMessage(uint32_t code);
   inline bool isError(uint32_t code);
   inline bool isComplete(uint32_t code);
   inline bool isProceeding(uint32_t code);
--- a/dom/system/gonk/NetworkWorker.cpp
+++ b/dom/system/gonk/NetworkWorker.cpp
@@ -28,34 +28,19 @@ StaticRefPtr<NetworkWorker> gNetworkWork
 // The singleton networkutils class, that can be used on any thread.
 static nsAutoPtr<NetworkUtils> gNetworkUtils;
 
 // Runnable used dispatch command result on the main thread.
 class NetworkResultDispatcher : public nsRunnable
 {
 public:
   NetworkResultDispatcher(const NetworkResultOptions& aResult)
+    : mResult(aResult)
   {
     MOZ_ASSERT(!NS_IsMainThread());
-
-#define COPY_FIELD(prop) mResult.prop = aResult.prop;
-    COPY_FIELD(mId)
-    COPY_FIELD(mRet)
-    COPY_FIELD(mBroadcast)
-    COPY_FIELD(mTopic)
-    COPY_FIELD(mReason)
-    COPY_FIELD(mResultCode)
-    COPY_FIELD(mResultReason)
-    COPY_FIELD(mError)
-    COPY_FIELD(mEnable)
-    COPY_FIELD(mResult)
-    COPY_FIELD(mSuccess)
-    COPY_FIELD(mCurExternalIfname)
-    COPY_FIELD(mCurInternalIfname)
-#undef COPY_FIELD
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (gNetworkWorker) {
       gNetworkWorker->DispatchNetworkResult(mResult);
--- a/dom/system/gonk/nsINetworkService.idl
+++ b/dom/system/gonk/nsINetworkService.idl
@@ -96,20 +96,75 @@ interface nsIUpdateUpStreamCallback : ns
    * @param success
    *        Boolean to indicate the operation is successful or not.
    * @param externalIfname
    *        The external interface name.
    */
   void updateUpStreamResult(in boolean success, in DOMString externalIfname);
 };
 
+[scriptable, function, uuid(eedca6c0-1310-11e4-9191-0800200c9a66)]
+interface nsISetDnsCallback : nsISupports
+{
+  /**
+   * Callback function used to report the result of setting DNS server.
+   *
+   * @param error
+   *        An error message if the operation wasn't successful,
+   *        or `null` if it was.
+   */
+  void setDnsResult(in jsval error);
+};
+
+[scriptable, function, uuid(5d0e1a60-1187-11e4-9191-0800200c9a66)]
+interface nsINativeCommandCallback : nsISupports
+{
+  /**
+   * Callback function used to report the result of a network native command.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   */
+  void nativeCommandResult(in boolean success);
+};
+
+[scriptable, function, uuid(694abb80-1187-11e4-9191-0800200c9a66)]
+interface nsIDhcpRequestCallback : nsISupports
+{
+  /**
+   * Callback function used to report the result of DHCP client request.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   *
+   * @param dhcpInfo
+   *        An object to represent the successful DHCP request:
+   *
+   *          - gateway_str: string
+   *          - dns1_str:    string
+   *          - dns2_str:    string
+   *          - mask_str:    string
+   *          - server_str:  string
+   *          - vendor_str:  string
+   *          - lease:       long
+   *          - mask:        long
+   *          - ipaddr:      long
+   *          - gateway:     long
+   *          - dns1:        long
+   *          - dns2:        long
+   *          - server:      long
+   */
+  void dhcpRequestResult(in boolean success, in jsval dhcpInfo);
+};
+
+
 /**
  * Provide network services.
  */
-[scriptable, uuid(ddb38428-0cf2-4c6a-a3c9-5e2f00fc54db)]
+[scriptable, uuid(9f1d78e0-1314-11e4-9191-0800200c9a66)]
 interface nsINetworkService : nsISupports
 {
   /**
    * Enable or disable Wifi Tethering
    *
    * @param enabled
    *        Boolean that indicates whether tethering should be enabled (true) or disabled (false).
    * @param config
@@ -224,29 +279,36 @@ interface nsINetworkService : nsISupport
    */
   void resetRoutingTable(in nsINetworkInterface networkInterface);
 
   /**
    * Set DNS.
    *
    * @param networkInterface
    *        The network interface which contains the DNS we want to set.
+   *
+   * @param callback
+   *        Callback to notify the result of setting DNS server.
    */
-  void setDNS(in nsINetworkInterface networkInterface);
+  void setDNS(in nsINetworkInterface networkInterface,
+              in nsISetDnsCallback callback);
 
   /**
-   * Set default route and DNS.
+   * Set default route.
    *
    * @param networkInterface
-   *        The network interface we want to set to the default route and dns.
+   *        The network interface we want to set to the default route.
    * @param oldInterface
    *        The previous network interface.
+   * @param callback
+   *        Callback to notify the result of setting default route.
    */
-  void setDefaultRouteAndDNS(in nsINetworkInterface networkInterface,
-                             in nsINetworkInterface oldInterface);
+  void setDefaultRoute(in nsINetworkInterface networkInterface,
+                       in nsINetworkInterface oldInterface,
+                       in nsINativeCommandCallback callback);
 
   /**
    * Remove default route.
    *
    * @param networkInterface
    *        The network interface we want remove from the default route.
    */
   void removeDefaultRoute(in nsINetworkInterface networkInterface);
@@ -336,9 +398,76 @@ interface nsINetworkService : nsISupport
    * @param current
    *        The current internal and external interface.
    * @param callback
    *        Callback function to report the result.
    */
   void updateUpStream(in jsval previous,
                       in jsval current,
                       in nsIUpdateUpStreamCallback callback);
+
+  /**
+   * Configure a network interface.
+   *
+   * @param config
+   *        An object containing the detail that we want to configure the interface:
+   *
+   *          - ifname:  string
+   *          - ipaddr:  long
+   *          - mask:    long
+   *          - gateway: long
+   *          - dns1:    long
+   *          - dns2:    long
+   *
+   * @param callback
+   *        Callback to notify the result of configurating network interface.
+   */
+  void configureInterface(in jsval config,
+                          in nsINativeCommandCallback callback);
+
+  /**
+   * Issue a DHCP client request.
+   *
+   * @param networkInterface
+   *        The network interface which we wnat to do the DHCP request on.
+   *
+   * @param callback
+   *        Callback to notify the result of the DHCP request.
+   */
+  void dhcpRequest(in DOMString interfaceName,
+                   in nsIDhcpRequestCallback callback);
+
+  /**
+   * Enable a network interface.
+   *
+   * @param networkInterface
+   *        The network interface name which we want to enable.
+   *
+   * @param callback
+   *        Callback to notify the result of disabling network interface.
+   */
+  void enableInterface(in DOMString interfaceName,
+                       in nsINativeCommandCallback callback);
+
+  /**
+   * Disable a network interface.
+   *
+   * @param networkInterface
+   *        The network interface name which we want to disable.
+   *
+   * @param callback
+   *        Callback to notify the result of disabling network interface.
+   */
+  void disableInterface(in DOMString interfaceName,
+                        in nsINativeCommandCallback callback);
+
+  /**
+   * Reset all connections
+   *
+   * @param networkInterface
+   *        The network interface name which we want to reset.
+   *
+   * @param callback
+   *        Callback to notify the result of resetting connections.
+   */
+  void resetConnections(in DOMString interfaceName,
+                        in nsINativeCommandCallback callback);
 };
--- a/dom/webidl/FMRadio.webidl
+++ b/dom/webidl/FMRadio.webidl
@@ -1,16 +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/. */
 
 interface FMRadio : EventTarget {
   /* Indicates if the FM radio is enabled. */
   readonly attribute boolean enabled;
 
+  /* Indicates if RDS reception is enabled */
+  readonly attribute boolean rdsEnabled;
+
   /* Indicates if the antenna is plugged and available. */
   readonly attribute boolean antennaAvailable;
 
   /**
    * Current frequency in MHz. The value will be null if the FM radio is
    * disabled.
    */
   readonly attribute double? frequency;
@@ -26,31 +29,92 @@ interface FMRadio : EventTarget {
    * is, any two radio channels' frequencies differ by at least channelWidth
    * MHz. Usually, the value is one of:
    *  - 0.05 MHz
    *  - 0.1  MHz
    *  - 0.2  MHz
    */
   readonly attribute double channelWidth;
 
+  /**
+   * This is a mask consisting of bits corresponding to
+   * (1 << groupcode) that can be specified to receive
+   * raw RDS groups of specific group types. Note that
+   * groupcode corresponds to the upper 5 bits in block B.
+   */
+  attribute unsigned long rdsGroupMask;
+
+  /**
+   * The Program Identification (PI) code.
+   * Available if RDS is enabled on both the station and on this device.
+   * The value is null otherwise.
+   */
+  readonly attribute unsigned short? pi;
+
+  /**
+   * The Program Type (PTY) code.
+   * Available if RDS is enabled on both the station and on this device.
+   * The value is null otherwise.
+   */
+  readonly attribute octet? pty;
+
+  /**
+   * The Program Service (PS) name.
+   * Available if RDS is enabled on the station and on this device
+   */
+  readonly attribute DOMString? ps;
+
+  /**
+   * The radiotext, as provided by group 2A/2B.
+   * Available if RDS is enabled on the station and on this device
+   */
+  readonly attribute DOMString? rt;
+
+  /**
+   * The last RDS group received.
+   * Available if RDS is enabled on the station and on this device
+   */
+  readonly attribute Uint16Array? rdsgroup;
+
   /* Fired when the FM radio is enabled. */
   attribute EventHandler onenabled;
 
   /* Fired when the FM radio is disabled. */
   attribute EventHandler ondisabled;
 
+  /* Fired when the RDS is enabled. */
+  attribute EventHandler onrdsenabled;
+
+  /* Fired when the RDS is disabled. */
+  attribute EventHandler onrdsdisabled;
+
   /**
    * Fired when the antenna becomes available or unavailable, i.e., fired when
    * the antennaAvailable attribute changes.
    */
   attribute EventHandler onantennaavailablechange;
 
   /* Fired when the FM radio's frequency is changed. */
   attribute EventHandler onfrequencychange;
 
+  /* Fired when the PI code changes */
+  attribute EventHandler onpichange;
+
+  /* Fired when the PTY changes */
+  attribute EventHandler onptychange;
+
+  /* Fired when the PS name changes */
+  attribute EventHandler onpschange;
+
+  /* Fired when the radiotext changes */
+  attribute EventHandler onrtchange;
+
+  /* Fired when we get a new RDS group */
+  attribute EventHandler onnewrdsgroup;
+
   /**
    * Power the FM radio off. The disabled event will be fired if this request
    * completes successfully.
    */
   DOMRequest disable();
 
   /**
    * Power the FM radio on, and tune the radio to the given frequency in MHz.
@@ -89,10 +153,24 @@ interface FMRadio : EventTarget {
    */
   DOMRequest seekDown();
 
   /**
    * Cancel the seek action. If the radio is not currently seeking up or down,
    * error will be fired.
    */
   DOMRequest cancelSeek();
+
+  /**
+   * Enable RDS reception.
+   *
+   * If the radio is off, RDS will be enabled when the radio is turned on.
+   */
+  DOMRequest enableRDS();
+
+  /**
+   * Disable RDS reception.
+   *
+   * If the radio is off, RDS will not be enabled when the radio is turned on.
+   */
+  DOMRequest disableRDS();
 };
 
--- a/dom/webidl/NetworkOptions.webidl
+++ b/dom/webidl/NetworkOptions.webidl
@@ -17,17 +17,16 @@ dictionary NetworkCommandOptions
   unsigned long prefixLength;         // for "removeNetworkRoute".
   DOMString domain;                   // for "setDNS"
   sequence<DOMString> dnses;          // for "setDNS", "setDefaultRouteAndDNS".
   DOMString oldIfname;                // for "setDefaultRouteAndDNS".
   DOMString gateway;                  // for "addSecondaryRoute", "removeSecondaryRoute".
   sequence<DOMString> gateways;       // for "setDefaultRouteAndDNS", "removeDefaultRoute".
   DOMString mode;                     // for "setWifiOperationMode".
   boolean report;                     // for "setWifiOperationMode".
-  boolean isAsync;                    // for "setWifiOperationMode".
   boolean enabled;                    // for "setDhcpServer".
   DOMString wifictrlinterfacename;    // for "setWifiTethering".
   DOMString internalIfname;           // for "setWifiTethering".
   DOMString externalIfname;           // for "setWifiTethering".
   boolean enable;                     // for "setWifiTethering".
   DOMString ssid;                     // for "setWifiTethering".
   DOMString security;                 // for "setWifiTethering".
   DOMString key;                      // for "setWifiTethering".
@@ -45,16 +44,22 @@ dictionary NetworkCommandOptions
   DOMString startIp;                  // for "setDhcpServer".
   DOMString endIp;                    // for "setDhcpServer".
   DOMString serverIp;                 // for "setDhcpServer".
   DOMString maskLength;               // for "setDhcpServer".
   DOMString preInternalIfname;        // for "updateUpStream".
   DOMString preExternalIfname;        // for "updateUpStream".
   DOMString curInternalIfname;        // for "updateUpStream".
   DOMString curExternalIfname;        // for "updateUpStream".
+
+  long ipaddr;                        // for "ifc_configure".
+  long mask;                          // for "ifc_configure".
+  long gateway_long;                  // for "ifc_configure".
+  long dns1_long;                     // for "ifc_configure".
+  long dns2_long;                     // for "ifc_configure".
 };
 
 /**
 * This dictionary holds the parameters sent back to NetworkService.js.
 */
 dictionary NetworkResultOptions
 {
   long id = 0;                        // opaque id.
@@ -68,9 +73,27 @@ dictionary NetworkResultOptions
   boolean error = false;              // for all commands.
 
   boolean enable = false;             // for "setWifiTethering", "setUSBTethering"
                                       //     "enableUsbRndis".
   boolean result = false;             // for "enableUsbRndis".
   boolean success = false;            // for "setDhcpServer".
   DOMString curExternalIfname = "";   // for "updateUpStream".
   DOMString curInternalIfname = "";   // for "updateUpStream".
+
+  DOMString reply = "";               // for "command".
+  DOMString route = "";               // for "ifc_get_default_route".
+  DOMString ipaddr_str = "";          // The following are for the result of
+                                      // dhcp_do_request.
+  DOMString gateway_str = "";
+  DOMString dns1_str = "";
+  DOMString dns2_str = "";
+  DOMString mask_str = "";
+  DOMString server_str = "";
+  DOMString vendor_str = "";
+  long      lease = 0;
+  long      mask = 0;
+  long      ipaddr = 0;
+  long      gateway = 0;
+  long      dns1 = 0;
+  long      dns2 = 0;
+  long      server = 0;
 };
--- a/dom/webidl/WifiOptions.webidl
+++ b/dom/webidl/WifiOptions.webidl
@@ -5,61 +5,27 @@
 /**
   * This dictionnary holds the parameters sent to the wifi service.
   */
 dictionary WifiCommandOptions
 {
   long      id = 0;       // opaque id.
   DOMString cmd = "";     // the command name.
   DOMString request;      // for "command"
-  DOMString ifname;       // for "ifc_reset_connections", "ifc_enable",
-                          // "ifc_disable", "ifc_remove_host_routes",
-                          // "ifc_remove_default_route", "dhcp_stop",
-                          // "dhcp_release_lease", "ifc_get_default_route",
-                          // "ifc_add_host_route", "ifc_set_default_route",
-                          // "ifc_configure", "dhcp_do_request",
-                          // "dhcp_do_request_renew".
-  long route;             // for "ifc_add_host_route", "ifc_set_default_route".
-  long ipaddr;            // for "ifc_configure".
-  long mask;              // for "ifc_configure".
-  long gateway;           // for "ifc_configure".
-  long dns1;              // for "ifc_configure".
-  long dns2;              // for "ifc_configure".
-  DOMString key;          // for "property_get", "property_set".
-  DOMString value;        // for "property_set".
-  DOMString defaultValue; // for "property_get".
 };
 
 /**
   * This dictionnary holds the parameters sent back to WifiWorker.js
   */
 dictionary WifiResultOptions
 {
   long      id = 0;             // opaque id.
   long      status = 0;         // the return status of the command.
                                 // Used by most commands.
   DOMString reply = "";         // for "command".
-  DOMString route = "";         // for "ifc_get_default_route".
-  DOMString error = "";         // for "dhcp_get_errmsg".
-  DOMString value = "";         // for "property_get".
-  DOMString ipaddr_str = "";    // The following are for the result of
-                                // dhcp_do_request.
-  DOMString gateway_str = "";
-  DOMString dns1_str = "";
-  DOMString dns2_str = "";
-  DOMString mask_str = "";
-  DOMString server_str = "";
-  DOMString vendor_str = "";
-  long      lease = 0;
-  long      mask = 0;
-  long      ipaddr = 0;
-  long      gateway = 0;
-  long      dns1 = 0;
-  long      dns2 = 0;
-  long      server = 0;
 };
 
 
 /**
   * This dictionary holds the callback parameter sent back from WifiCertService
   * to WifiWorker, and should only be passed around in chrome process.
   */
 dictionary WifiCertServiceResultOptions
--- a/dom/wifi/WifiNetUtil.jsm
+++ b/dom/wifi/WifiNetUtil.jsm
@@ -25,126 +25,45 @@ this.WifiNetUtil = function(controlMessa
   function debug(msg) {
     if (DEBUG) {
       dump('-------------- NetUtil: ' + msg);
     }
   }
 
   var util = {};
 
-  util.configureInterface = function(cfg, callback) {
-    let message = { cmd:     "ifc_configure",
-                    ifname:  cfg.ifname,
-                    ipaddr:  cfg.ipaddr,
-                    mask:    cfg.mask,
-                    gateway: cfg.gateway,
-                    dns1:    cfg.dns1,
-                    dns2:    cfg.dns2 };
-
-    controlMessage(message, function(data) {
-      callback(!data.status);
-    });
-  };
-
   util.runDhcp = function (ifname, callback) {
-    controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
-      var dhcpInfo = data.status ? null : data;
+    gNetworkService.dhcpRequest(ifname, function(success, dhcpInfo) {
       util.runIpConfig(ifname, dhcpInfo, callback);
     });
   };
 
   util.stopDhcp = function (ifname, callback) {
     // This function does exactly what dhcp_stop does. Unforunately, if we call
     // this function twice before the previous callback is returned. We may block
     // our self waiting for the callback. It slows down the wifi startup procedure.
     // Therefore, we have to roll our own version here.
     let dhcpService = DHCP_PROP + "_" + ifname;
     let suffix = (ifname.substr(0, 3) === "p2p") ? "p2p" : ifname;
     let processName = DHCP + "_" + suffix;
     stopProcess(dhcpService, processName, callback);
   };
 
-  util.enableInterface = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_enable", ifname: ifname }, function (data) {
-      callback(!data.status);
-    });
-  };
-
-  util.disableInterface = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_disable", ifname: ifname }, function (data) {
-      callback(!data.status);
-    });
-  };
-
   util.startDhcpServer = function (config, callback) {
     gNetworkService.setDhcpServer(true, config, function (error) {
       callback(!error);
     });
   };
 
   util.stopDhcpServer = function (callback) {
     gNetworkService.setDhcpServer(false, null, function (error) {
       callback(!error);
     });
   };
 
-  util.addHostRoute = function (ifname, route, callback) {
-    controlMessage({ cmd: "ifc_add_host_route", ifname: ifname, route: route }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.removeHostRoutes = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_remove_host_routes", ifname: ifname }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.setDefaultRoute = function (ifname, route, callback) {
-    controlMessage({ cmd: "ifc_set_default_route", ifname: ifname, route: route }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.getDefaultRoute = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_get_default_route", ifname: ifname }, function(data) {
-      callback(!data.route);
-    });
-  };
-
-  util.removeDefaultRoute = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_remove_default_route", ifname: ifname }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.resetConnections = function (ifname, callback) {
-    controlMessage({ cmd: "ifc_reset_connections", ifname: ifname }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.releaseDhcpLease = function (ifname, callback) {
-    controlMessage({ cmd: "dhcp_release_lease", ifname: ifname }, function(data) {
-      callback(!data.status);
-    });
-  };
-
-  util.getDhcpError = function (callback) {
-    controlMessage({ cmd: "dhcp_get_errmsg" }, function(data) {
-      callback(data.error);
-    });
-  };
-
-  util.runDhcpRenew = function (ifname, callback) {
-    controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
-      callback(data.status ? null : data);
-    });
-  };
-
   util.runIpConfig = function (name, data, callback) {
     if (!data) {
       debug("IP config failed to run");
       callback({ info: data });
       return;
     }
 
     setProperty("net." + name + ".dns1", ipToString(data.dns1),
--- a/dom/wifi/WifiP2pManager.jsm
+++ b/dom/wifi/WifiP2pManager.jsm
@@ -16,16 +16,20 @@ Cu.import("resource://gre/modules/system
 XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
                                    "@mozilla.org/system-message-internal;1",
                                    "nsISystemMessagesInternal");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
                                    "@mozilla.org/network/manager;1",
                                    "nsINetworkManager");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+                                   "@mozilla.org/network/service;1",
+                                   "nsINetworkService");
+
 this.EXPORTED_SYMBOLS = ["WifiP2pManager"];
 
 const EVENT_IGNORED                      = -1;
 const EVENT_UNKNOWN                      = -2;
 
 // Events from supplicant for p2p.
 const EVENT_P2P_DEVICE_FOUND             = 0;
 const EVENT_P2P_DEVICE_LOST              = 1;
@@ -642,17 +646,17 @@ function P2pStateMachine(aP2pCommand, aN
             onFailure();
             return;
           }
 
           debug('P2P is enabled! Enabling net interface...');
 
           // Step 4: Enable p2p0 net interface. wpa_supplicant may have
           //         already done it for us.
-          aNetUtil.enableInterface(P2P_INTERFACE_NAME, function (success) {
+          gNetworkService.enableInterface(P2P_INTERFACE_NAME, function (success) {
             onSuccess();
           });
         });
       });
     },
 
     handleEvent: function(aEvent) {
       // We won't receive any event since all of them will be blocked.
@@ -1313,17 +1317,17 @@ function P2pStateMachine(aP2pCommand, aN
     enter: function() {
       _sm.pause();
       aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless.
         debug('Stop DHCP server result: ' + success);
         aP2pCommand.p2pDisable(function(success) {
           debug('P2P function disabled');
           aP2pCommand.closeSupplicantConnection(function (status) {
             debug('Supplicant connection closed');
-            aNetUtil.disableInterface(P2P_INTERFACE_NAME, function (success){
+            gNetworkService.disableInterface(P2P_INTERFACE_NAME, function (success){
               debug('Disabled interface: ' + P2P_INTERFACE_NAME);
               _onDisabled(true);
               _sm.gotoState(stateDisabled);
             });
           });
         });
       });
     },
--- a/dom/wifi/WifiProxyService.cpp
+++ b/dom/wifi/WifiProxyService.cpp
@@ -84,46 +84,20 @@ private:
   nsCString mInterface;
 };
 
 // Runnable used dispatch the Command result on the main thread.
 class WifiResultDispatcher : public nsRunnable
 {
 public:
   WifiResultDispatcher(WifiResultOptions& aResult, const nsACString& aInterface)
-    : mInterface(aInterface)
+    : mResult(aResult)
+    , mInterface(aInterface)
   {
     MOZ_ASSERT(!NS_IsMainThread());
-
-    // XXX: is there a better way to copy webidl dictionnaries?
-    // the copy constructor is private.
-#define COPY_FIELD(prop) mResult.prop = aResult.prop;
-
-    COPY_FIELD(mId)
-    COPY_FIELD(mStatus)
-    COPY_FIELD(mReply)
-    COPY_FIELD(mRoute)
-    COPY_FIELD(mError)
-    COPY_FIELD(mValue)
-    COPY_FIELD(mIpaddr_str)
-    COPY_FIELD(mGateway_str)
-    COPY_FIELD(mDns1_str)
-    COPY_FIELD(mDns2_str)
-    COPY_FIELD(mMask_str)
-    COPY_FIELD(mServer_str)
-    COPY_FIELD(mVendor_str)
-    COPY_FIELD(mLease)
-    COPY_FIELD(mMask)
-    COPY_FIELD(mIpaddr)
-    COPY_FIELD(mGateway)
-    COPY_FIELD(mDns1)
-    COPY_FIELD(mDns2)
-    COPY_FIELD(mServer)
-
-#undef COPY_FIELD
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
     gWifiProxyService->DispatchWifiResult(mResult, mInterface);
     return NS_OK;
   }
--- a/dom/wifi/WifiUtils.cpp
+++ b/dom/wifi/WifiUtils.cpp
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WifiUtils.h"
 #include <dlfcn.h>
 #include <errno.h>
 #include <cutils/properties.h>
 #include "prinit.h"
 #include "js/CharacterEncoding.h"
-#include "mozilla/dom/network/NetUtils.h"
 
 using namespace mozilla::dom;
 
 #define BUFFER_SIZE        4096
 #define COMMAND_SIZE       256
 #define PROPERTY_VALUE_MAX 80
 
 // Intentionally not trying to dlclose() this handle. That's playing
@@ -375,24 +374,27 @@ public:
     USE_DLFUNC(wifi_command)
     return wifi_command(command, buf, len);
   }
 };
 
 // Concrete class to use to access the wpa supplicant.
 WpaSupplicant::WpaSupplicant()
 {
-  if (NetUtils::SdkVersion() < 16) {
+  char propVersion[PROPERTY_VALUE_MAX];
+  property_get("ro.build.version.sdk", propVersion, "0");
+  mSdkVersion = strtol(propVersion, nullptr, 10);
+
+  if (mSdkVersion < 16) {
     mImpl = new ICSWpaSupplicantImpl();
-  } else if (NetUtils::SdkVersion() < 19) {
+  } else if (mSdkVersion < 19) {
     mImpl = new JBWpaSupplicantImpl();
   } else {
     mImpl = new KKWpaSupplicantImpl();
   }
-  mNetUtils = new NetUtils();
   mWifiHotspotUtils = new WifiHotspotUtils();
 };
 
 void WpaSupplicant::WaitForEvent(nsAString& aEvent, const nsCString& aInterface)
 {
   CHECK_HWLIB()
 
   char buffer[BUFFER_SIZE];
@@ -413,19 +415,16 @@ uint32_t WpaSupplicant::MakeMask(uint32_
   return ntohl(mask);
 }
 
 bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions,
                                    WifiResultOptions& aResult,
                                    const nsCString& aInterface)
 {
   CHECK_HWLIB(false)
-  if (!mNetUtils->GetSharedLibrary()) {
-    return false;
-  }
 
   if (!mWifiHotspotUtils->GetSharedLibrary()) {
     return false;
   }
 
   // Always correlate the opaque ids.
   aResult.mId = aOptions.mId;
 
@@ -450,91 +449,16 @@ bool WpaSupplicant::ExecuteCommand(Comma
   } else if (aOptions.mCmd.EqualsLiteral("unload_driver")) {
     aResult.mStatus = mImpl->do_wifi_unload_driver();
   } else if (aOptions.mCmd.EqualsLiteral("start_supplicant")) {
     aResult.mStatus = mImpl->do_wifi_start_supplicant(GetWifiP2pSupported() ? 1 : 0);
   } else if (aOptions.mCmd.EqualsLiteral("stop_supplicant")) {
     aResult.mStatus = mImpl->do_wifi_stop_supplicant(0);
   } else if (aOptions.mCmd.EqualsLiteral("connect_to_supplicant")) {
     aResult.mStatus = mImpl->do_wifi_connect_to_supplicant(aInterface.get());
-  } else if (aOptions.mCmd.EqualsLiteral("ifc_enable")) {
-    aResult.mStatus = mNetUtils->do_ifc_enable(GET_CHAR(mIfname));
-  } else if (aOptions.mCmd.EqualsLiteral("ifc_disable")) {
-    aResult.mStatus = mNetUtils->do_ifc_disable(GET_CHAR(mIfname));
-  } else if (aOptions.mCmd.EqualsLiteral("ifc_configure")) {
-    aResult.mStatus = mNetUtils->do_ifc_configure(
-      GET_CHAR(mIfname), aOptions.mIpaddr, aOptions.mMask,
-      aOptions.mGateway, aOptions.mDns1, aOptions.mDns2
-    );
-  } else if (aOptions.mCmd.EqualsLiteral("ifc_reset_connections")) {
-    aResult.mStatus = mNetUtils->do_ifc_reset_connections(
-      GET_CHAR(mIfname), RESET_ALL_ADDRESSES
-    );
-  } else if (aOptions.mCmd.EqualsLiteral("dhcp_stop")) {
-    aResult.mStatus = mNetUtils->do_dhcp_stop(GET_CHAR(mIfname));
-  } else if (aOptions.mCmd.EqualsLiteral("dhcp_do_request")) {
-    char ipaddr[PROPERTY_VALUE_MAX];
-    char gateway[PROPERTY_VALUE_MAX];
-    uint32_t prefixLength;
-    char dns1[PROPERTY_VALUE_MAX];
-    char dns2[PROPERTY_VALUE_MAX];
-    char server[PROPERTY_VALUE_MAX];
-    uint32_t lease;
-    char vendorinfo[PROPERTY_VALUE_MAX];
-    aResult.mStatus =
-      mNetUtils->do_dhcp_do_request(GET_CHAR(mIfname),
-                                    ipaddr,
-                                    gateway,
-                                    &prefixLength,
-                                    dns1,
-                                    dns2,
-                                    server,
-                                    &lease,
-                                    vendorinfo);
-
-    if (aResult.mStatus == -1) {
-      // Early return since we failed.
-      return true;
-    }
-
-    aResult.mIpaddr_str = NS_ConvertUTF8toUTF16(ipaddr);
-    aResult.mGateway_str = NS_ConvertUTF8toUTF16(gateway);
-    aResult.mDns1_str = NS_ConvertUTF8toUTF16(dns1);
-    aResult.mDns2_str = NS_ConvertUTF8toUTF16(dns2);
-    aResult.mServer_str = NS_ConvertUTF8toUTF16(server);
-    aResult.mVendor_str = NS_ConvertUTF8toUTF16(vendorinfo);
-    aResult.mLease = lease;
-    aResult.mMask = MakeMask(prefixLength);
-
-    uint32_t inet4; // only support IPv4 for now.
-
-#define INET_PTON(var, field)                                                 \
-  PR_BEGIN_MACRO                                                              \
-    inet_pton(AF_INET, var, &inet4);                                          \
-    aResult.field = inet4;                                                    \
-  PR_END_MACRO
-
-    INET_PTON(ipaddr, mIpaddr);
-    INET_PTON(gateway, mGateway);
-
-    if (dns1[0] != '\0') {
-      INET_PTON(dns1, mDns1);
-    }
-
-    if (dns2[0] != '\0') {
-      INET_PTON(dns2, mDns2);
-    }
-
-    INET_PTON(server, mServer);
-
-    //aResult.mask_str = netHelpers.ipToString(obj.mask);
-    char inet_str[64];
-    if (inet_ntop(AF_INET, &aResult.mMask, inet_str, sizeof(inet_str))) {
-      aResult.mMask_str = NS_ConvertUTF8toUTF16(inet_str);
-    }
   } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) {
     size_t len = BUFFER_SIZE - 1;
     char buffer[BUFFER_SIZE];
     NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
     aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(),
                                                                  buffer,
                                                                  &len);
     nsString value;
@@ -590,17 +514,17 @@ void
 WpaSupplicant::CheckBuffer(char* buffer, int32_t length,
                            nsAString& aEvent)
 {
   if (length <= 0 || length >= (BUFFER_SIZE - 1)) {
     NS_WARNING("WpaSupplicant::CheckBuffer: Invalid buffer length");
     return;
   }
 
-  if (NetUtils::SdkVersion() < 18) {
+  if (mSdkVersion < 18) {
     buffer[length] = 0;
     LossyConvertUTF8toUTF16(buffer, length, aEvent);
     return;
   }
 
   // After Android JB4.3, the SSIDs have been converted into printable form.
   // In most of cases, SSIDs do not use unprintable characters, but IEEE 802.11
   // standard does not limit the used character set, so anything could be used
--- a/dom/wifi/WifiUtils.h
+++ b/dom/wifi/WifiUtils.h
@@ -8,81 +8,44 @@
  */
 
 #ifndef WifiUtils_h
 #define WifiUtils_h
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/dom/WifiOptionsBinding.h"
-#include "mozilla/dom/network/NetUtils.h"
 #include "WifiHotspotUtils.h"
 
 // Needed to add a copy constructor to WifiCommandOptions.
 struct CommandOptions
 {
 public:
-  CommandOptions(const CommandOptions& aOther) {
-    mId = aOther.mId;
-    mCmd = aOther.mCmd;
-    mRequest = aOther.mRequest;
-    mIfname = aOther.mIfname;
-    mRoute = aOther.mRoute;
-    mIpaddr = aOther.mIpaddr;
-    mMask = aOther.mMask;
-    mGateway = aOther.mGateway;
-    mDns1 = aOther.mDns1;
-    mDns2 = aOther.mDns2;
-    mKey = aOther.mKey;
-    mValue = aOther.mValue;
-    mDefaultValue = aOther.mDefaultValue;
-  }
-
   CommandOptions(const mozilla::dom::WifiCommandOptions& aOther) {
 
 #define COPY_OPT_FIELD(prop, defaultValue)            \
     if (aOther.prop.WasPassed()) {                    \
       prop = aOther.prop.Value();                     \
     } else {                                          \
       prop = defaultValue;                            \
     }
 
 #define COPY_FIELD(prop) prop = aOther.prop;
     COPY_FIELD(mId)
     COPY_FIELD(mCmd)
     COPY_OPT_FIELD(mRequest, EmptyString())
-    COPY_OPT_FIELD(mIfname, EmptyString())
-    COPY_OPT_FIELD(mIpaddr, 0)
-    COPY_OPT_FIELD(mRoute, 0)
-    COPY_OPT_FIELD(mMask, 0)
-    COPY_OPT_FIELD(mGateway, 0)
-    COPY_OPT_FIELD(mDns1, 0)
-    COPY_OPT_FIELD(mDns2, 0)
-    COPY_OPT_FIELD(mKey, EmptyString())
-    COPY_OPT_FIELD(mValue, EmptyString())
-    COPY_OPT_FIELD(mDefaultValue, EmptyString())
 
 #undef COPY_OPT_FIELD
 #undef COPY_FIELD
   }
 
   // All the fields, not Optional<> anymore to get copy constructors.
   nsString mCmd;
-  nsString mDefaultValue;
-  int32_t mDns1;
-  int32_t mDns2;
-  int32_t mGateway;
   int32_t mId;
-  nsString mIfname;
-  int32_t mIpaddr;
-  nsString mKey;
-  int32_t mMask;
   nsString mRequest;
-  int32_t mRoute;
-  nsString mValue;
 };
 
 // Abstract class that exposes libhardware_legacy functions we need for
 // wifi management.
 // We use the ICS signatures here since they are likely more future-proof.
 class WpaSupplicantImpl
 {
 public:
@@ -125,17 +88,18 @@ public:
   // conversion
   void WaitForEvent(nsAString& aEvent, const nsCString& aInterface);
   bool ExecuteCommand(CommandOptions aOptions,
                       mozilla::dom::WifiResultOptions& result,
                       const nsCString& aInterface);
 
 private:
   nsAutoPtr<WpaSupplicantImpl> mImpl;
-  nsAutoPtr<NetUtils> mNetUtils;
   nsAutoPtr<WifiHotspotUtils> mWifiHotspotUtils;
 
+  uint32_t mSdkVersion;
+
 protected:
   void CheckBuffer(char* buffer, int32_t length, nsAString& aEvent);
   uint32_t MakeMask(uint32_t len);
 };
 
 #endif // WifiUtils_h
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -410,18 +410,18 @@ var WifiManager = (function() {
 
       // If the ssid of current connection is the same as configured ssid
       // It means we need update current connection to use static IP address.
       if (setNetworkKey == curNetworkKey) {
         // Use configureInterface directly doesn't work, the network iterface
         // and routing table is changed but still cannot connect to network
         // so the workaround here is disable interface the enable again to
         // trigger network reconnect with static ip.
-        netUtil.disableInterface(manager.ifname, function (ok) {
-          netUtil.enableInterface(manager.ifname, function (ok) {
+        gNetworkService.disableInterface(manager.ifname, function (ok) {
+          gNetworkService.enableInterface(manager.ifname, function (ok) {
           });
         });
       }
     });
   }
 
   var dhcpInfo = null;
 
@@ -437,22 +437,22 @@ var WifiManager = (function() {
     staticIpInfo = staticIpConfig[key];
 
     // Stop dhcpd when use static IP
     if (dhcpInfo != null) {
       netUtil.stopDhcp(manager.ifname, function() {});
     }
 
     // Set ip, mask length, gateway, dns to network interface
-    netUtil.configureInterface( { ifname: ifname,
-                                  ipaddr: staticIpInfo.ipaddr,
-                                  mask: staticIpInfo.maskLength,
-                                  gateway: staticIpInfo.gateway,
-                                  dns1: staticIpInfo.dns1,
-                                  dns2: staticIpInfo.dns2 }, function (data) {
+    gNetworkService.configureInterface( { ifname: ifname,
+                                          ipaddr: staticIpInfo.ipaddr,
+                                          mask: staticIpInfo.maskLength,
+                                          gateway: staticIpInfo.gateway,
+                                          dns1: staticIpInfo.dns1,
+                                          dns2: staticIpInfo.dns2 }, function (data) {
       netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
         dhcpInfo = data.info;
         notify("networkconnected", data);
       });
     });
   }
 
   var suppressEvents = false;
@@ -582,27 +582,27 @@ var WifiManager = (function() {
 
     retryTimer = null;
     connectTries = 0;
     notify("supplicantlost", { success: false });
   }
 
   manager.connectionDropped = function(callback) {
     // Reset network interface when connection drop
-    netUtil.configureInterface( { ifname: manager.ifname,
-                                  ipaddr: 0,
-                                  mask: 0,
-                                  gateway: 0,
-                                  dns1: 0,
-                                  dns2: 0 }, function (data) {
+    gNetworkService.configureInterface( { ifname: manager.ifname,
+                                          ipaddr: 0,
+                                          mask: 0,
+                                          gateway: 0,
+                                          dns1: 0,
+                                          dns2: 0 }, function (data) {
     });
 
     // If we got disconnected, kill the DHCP client in preparation for
     // reconnection.
-    netUtil.resetConnections(manager.ifname, function() {
+    gNetworkService.resetConnections(manager.ifname, function() {
       netUtil.stopDhcp(manager.ifname, function() {
         callback();
       });
     });
   }
 
   manager.start = function() {
     debug("detected SDK version " + sdkVersion);
@@ -884,17 +884,17 @@ var WifiManager = (function() {
     function tryStopSupplicant () {
       let status = libcutils.property_get(SUPP_PROP);
       if (status !== "running") {
         callback();
         return;
       }
       suppressEvents = true;
       wifiCommand.killSupplicant(function() {
-        netUtil.disableInterface(manager.ifname, function (ok) {
+        gNetworkService.disableInterface(manager.ifname, function (ok) {
           suppressEvents = false;
           callback();
         });
       });
     }
   }
 
   // Initial state.
@@ -991,17 +991,17 @@ var WifiManager = (function() {
                   unloadDriver(WIFI_FIRMWARE_STATION, function() {
                     callback(status);
                   });
                   manager.state = "UNINITIALIZED";
                   return;
                 }
 
                 manager.supplicantStarted = true;
-                netUtil.enableInterface(manager.ifname, function (ok) {
+                gNetworkService.enableInterface(manager.ifname, function (ok) {
                   callback(ok ? 0 : -1);
                 });
               });
             }
 
             function doStartSupplicant() {
               cancelWaitForDriverReadyTimer();
 
@@ -1035,17 +1035,17 @@ var WifiManager = (function() {
       // supplicant gracefully, then we need to continue telling it to die
       // until it does.
       let doDisableWifi = function() {
         manager.stopSupplicantCallback = (function () {
           wifiCommand.stopSupplicant(function (status) {
             wifiCommand.closeSupplicantConnection(function() {
               manager.connectToSupplicant = false;
               manager.state = "UNINITIALIZED";
-              netUtil.disableInterface(manager.ifname, function (ok) {
+              gNetworkService.disableInterface(manager.ifname, function (ok) {
                 unloadDriver(WIFI_FIRMWARE_STATION, callback);
               });
             });
           });
         }).bind(this);
 
         let terminateEventCallback = (function() {
           handleEvent("CTRL-EVENT-TERMINATING");
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -999,22 +999,25 @@ ProcessPriorityToString(ProcessPriority 
     break;
   }
 
   MOZ_ASSERT(false);
   return "???";
 }
 
 static StaticAutoPtr<ObserverList<FMRadioOperationInformation> > sFMRadioObservers;
+static StaticAutoPtr<ObserverList<FMRadioRDSGroup> > sFMRadioRDSObservers;
 
 static void
 InitializeFMRadioObserver()
 {
   if (!sFMRadioObservers) {
     sFMRadioObservers = new ObserverList<FMRadioOperationInformation>;
+    sFMRadioRDSObservers = new ObserverList<FMRadioRDSGroup>;
+    ClearOnShutdown(&sFMRadioRDSObservers);
     ClearOnShutdown(&sFMRadioObservers);
   }
 }
 
 void
 RegisterFMRadioObserver(FMRadioObserver* aFMRadioObserver) {
   AssertMainThread();
   InitializeFMRadioObserver();
@@ -1030,16 +1033,37 @@ UnregisterFMRadioObserver(FMRadioObserve
 
 void
 NotifyFMRadioStatus(const FMRadioOperationInformation& aFMRadioState) {
   InitializeFMRadioObserver();
   sFMRadioObservers->Broadcast(aFMRadioState);
 }
 
 void
+RegisterFMRadioRDSObserver(FMRadioRDSObserver* aFMRadioRDSObserver) {
+  AssertMainThread();
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->AddObserver(aFMRadioRDSObserver);
+}
+
+void
+UnregisterFMRadioRDSObserver(FMRadioRDSObserver* aFMRadioRDSObserver) {
+  AssertMainThread();
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->RemoveObserver(aFMRadioRDSObserver);
+}
+
+
+void
+NotifyFMRadioRDSGroup(const FMRadioRDSGroup& aRDSGroup) {
+  InitializeFMRadioObserver();
+  sFMRadioRDSObservers->Broadcast(aRDSGroup);
+}
+
+void
 EnableFMRadio(const FMRadioSettings& aInfo) {
   AssertMainThread();
   PROXY_IF_SANDBOXED(EnableFMRadio(aInfo));
 }
 
 void
 DisableFMRadio() {
   AssertMainThread();
@@ -1083,16 +1107,28 @@ GetFMRadioSignalStrength() {
 }
 
 void
 CancelFMRadioSeek() {
   AssertMainThread();
   PROXY_IF_SANDBOXED(CancelFMRadioSeek());
 }
 
+void
+EnableRDS(uint32_t aMask) {
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(EnableRDS(aMask));
+}
+
+void
+DisableRDS() {
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(DisableRDS());
+}
+
 FMRadioSettings
 GetFMBandSettings(FMRadioCountry aCountry) {
   FMRadioSettings settings;
 
   switch (aCountry) {
     case FM_RADIO_COUNTRY_US:
     case FM_RADIO_COUNTRY_EU:
       settings.upperLimit() = 108000;
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -499,22 +499,38 @@ void SetCurrentThreadPriority(hal::Threa
 void RegisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
  * Unregister the observer for the FM radio.
  */
 void UnregisterFMRadioObserver(hal::FMRadioObserver* aRadioObserver);
 
 /**
+ * Register an observer for the FM radio.
+ */
+void RegisterFMRadioRDSObserver(hal::FMRadioRDSObserver* aRDSObserver);
+
+/**
+ * Unregister the observer for the FM radio.
+ */
+void UnregisterFMRadioRDSObserver(hal::FMRadioRDSObserver* aRDSObserver);
+
+/**
  * Notify observers that a call to EnableFMRadio, DisableFMRadio, or FMRadioSeek
  * has completed, and indicate what the call returned.
  */
 void NotifyFMRadioStatus(const hal::FMRadioOperationInformation& aRadioState);
 
 /**
+ * Notify observers of new RDS data
+ * This can be called on any thread.
+ */
+void NotifyFMRadioRDSGroup(const hal::FMRadioRDSGroup& aRDSGroup);
+
+/**
  * Enable the FM radio and configure it according to the settings in aInfo.
  */
 void EnableFMRadio(const hal::FMRadioSettings& aInfo);
 
 /**
  * Disable the FM radio.
  */
 void DisableFMRadio();
@@ -561,16 +577,26 @@ uint32_t GetFMRadioSignalStrength();
 void CancelFMRadioSeek();
 
 /**
  * Get FM radio band settings by country.
  */
 hal::FMRadioSettings GetFMBandSettings(hal::FMRadioCountry aCountry);
 
 /**
+ * Enable RDS data reception
+ */
+void EnableRDS(uint32_t aMask);
+
+/**
+ * Disable RDS data reception
+ */
+void DisableRDS();
+
+/**
  * 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);
 
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -220,17 +220,19 @@ enum FMRadioCountry {
   FM_RADIO_COUNTRY_CH,  //Switzerland
   FM_RADIO_COUNTRY_TW,  //Taiwan
   FM_RADIO_COUNTRY_TR,  //Turkey
   FM_RADIO_COUNTRY_UA,  //Ukraine
   FM_RADIO_COUNTRY_USER_DEFINED,
   NUM_FM_RADIO_COUNTRY
 };
 
+class FMRadioRDSGroup;
 typedef Observer<FMRadioOperationInformation> FMRadioObserver;
+typedef Observer<FMRadioRDSGroup> FMRadioRDSObserver;
 } // namespace hal
 } // namespace mozilla
 
 namespace IPC {
 
 /**
  * Light type serializer.
  */
--- a/hal/fallback/FallbackFMRadio.cpp
+++ b/hal/fallback/FallbackFMRadio.cpp
@@ -52,10 +52,18 @@ GetFMRadioSignalStrength()
 {
   return 0;
 }
 
 void
 CancelFMRadioSeek()
 {}
 
+void
+EnableRDS(uint32_t aMask)
+{}
+
+void
+DisableRDS()
+{}
+
 } // hal_impl
 } // namespace mozilla
--- a/hal/gonk/GonkFMRadio.cpp
+++ b/hal/gonk/GonkFMRadio.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/FileUtils.h"
 
 #include <cutils/properties.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/videodev2.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/epoll.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
 
 /* Bionic might not have the newer version of the v4l2 headers that
  * define these controls, so we define them here if they're not found.
  */
 #ifndef V4L2_CTRL_CLASS_FM_RX
@@ -37,27 +38,47 @@
 #define V4L2_CID_FM_RX_CLASS_BASE (V4L2_CTRL_CLASS_FM_RX | 0x900)
 #define V4L2_CID_TUNE_DEEMPHASIS  (V4L2_CID_FM_RX_CLASS_BASE + 1)
 #define V4L2_DEEMPHASIS_DISABLED  0
 #define V4L2_DEEMPHASIS_50_uS     1
 #define V4L2_DEEMPHASIS_75_uS     2
 #define V4L2_CID_RDS_RECEPTION    (V4L2_CID_FM_RX_CLASS_BASE + 2)
 #endif
 
+#ifndef V4L2_RDS_BLOCK_MSK
+struct v4l2_rds_data {
+  uint8_t lsb;
+  uint8_t msb;
+  uint8_t block;
+} __attribute__ ((packed));
+#define V4L2_RDS_BLOCK_MSK 0x7
+#define V4L2_RDS_BLOCK_A 0
+#define V4L2_RDS_BLOCK_B 1
+#define V4L2_RDS_BLOCK_C 2
+#define V4L2_RDS_BLOCK_D 3
+#define V4L2_RDS_BLOCK_C_ALT 4
+#define V4L2_RDS_BLOCK_INVALID 7
+#define V4L2_RDS_BLOCK_CORRECTED 0x40
+#define V4L2_RDS_BLOCK_ERROR 0x80
+#endif
+
 namespace mozilla {
 namespace hal_impl {
 
 uint32_t GetFMRadioFrequency();
 
 static int sRadioFD;
 static bool sRadioEnabled;
+static bool sRDSEnabled;
 static pthread_t sRadioThread;
+static pthread_t sRDSThread;
 static hal::FMRadioSettings sRadioSettings;
 static int sMsmFMVersion;
 static bool sMsmFMMode;
+static bool sRDSSupported;
 
 static int
 setControl(uint32_t id, int32_t value)
 {
   struct v4l2_control control;
   control.id = id;
   control.value = value;
   return ioctl(sRadioFD, VIDIOC_S_CTRL, &control);
@@ -306,16 +327,18 @@ EnableFMRadio(const hal::FMRadioSettings
     return;
   }
 
   if (!(cap.capabilities & V4L2_CAP_TUNER)) {
     HAL_LOG("/dev/radio0 doesn't support the tuner interface");
     hal::NotifyFMRadioStatus(info);
     return;
   }
+
+  sRDSSupported = cap.capabilities & V4L2_CAP_RDS_CAPTURE;
   sRadioSettings = aInfo;
 
   if (sMsmFMMode) {
     sRadioFD = fd.forget();
     sMsmFMVersion = cap.version;
     if (pthread_create(&sRadioThread, nullptr, runMsmFMRadio, nullptr)) {
       HAL_LOG("Couldn't create radio thread");
       hal::NotifyFMRadioStatus(info);
@@ -361,16 +384,19 @@ EnableFMRadio(const hal::FMRadioSettings
 }
 
 void
 DisableFMRadio()
 {
   if (!sRadioEnabled)
     return;
 
+  if (sRDSEnabled)
+    hal::DisableRDS();
+
   sRadioEnabled = false;
 
   if (sMsmFMMode) {
     int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
     if (rc < 0) {
       HAL_LOG("Unable to turn off radio");
     }
 
@@ -489,10 +515,191 @@ GetFMRadioSignalStrength()
 
   return tuner.signal;
 }
 
 void
 CancelFMRadioSeek()
 {}
 
+/* Runs on the rds thread */
+static void*
+readRDSDataThread(void* data)
+{
+  v4l2_rds_data rdsblocks[16];
+  uint16_t blocks[4];
+
+  ScopedClose pipefd((int)data);
+
+  ScopedClose epollfd(epoll_create(2));
+  if (epollfd < 0) {
+    HAL_LOG("Could not create epoll FD for RDS thread (%d)", errno);
+    return nullptr;
+  }
+
+  epoll_event event = {
+    EPOLLIN,
+    { 0 }
+  };
+
+  event.data.fd = pipefd;
+  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pipefd, &event) < 0) {
+    HAL_LOG("Could not set up epoll FD for RDS thread (%d)", errno);
+    return nullptr;
+  }
+
+  event.data.fd = sRadioFD;
+  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sRadioFD, &event) < 0) {
+    HAL_LOG("Could not set up epoll FD for RDS thread (%d)", errno);
+    return nullptr;
+  }
+
+  epoll_event events[2] = {{ 0 }};
+  int event_count;
+  uint32_t block_bitmap = 0;
+  while ((event_count = epoll_wait(epollfd, events, 2, -1)) > 0 ||
+         errno == EINTR) {
+    bool RDSDataAvailable = false;
+    for (int i = 0; i < event_count; i++) {
+      if (events[i].data.fd == pipefd) {
+        if (!sRDSEnabled)
+          return nullptr;
+        char tmp[32];
+        TEMP_FAILURE_RETRY(read(pipefd, tmp, sizeof(tmp)));
+      } else if (events[i].data.fd == sRadioFD) {
+        RDSDataAvailable = true;
+      }
+    }
+
+    if (!RDSDataAvailable)
+      continue;
+
+    ssize_t len =
+      TEMP_FAILURE_RETRY(read(sRadioFD, rdsblocks, sizeof(rdsblocks)));
+    if (len < 0) {
+      HAL_LOG("Unexpected error while reading RDS data %d", errno);
+      return nullptr;
+    }
+
+    int blockcount = len / sizeof(rdsblocks[0]);
+    int lastblock = -1;
+    for (int i = 0; i < blockcount; i++) {
+      if ((rdsblocks[i].block & V4L2_RDS_BLOCK_MSK) == V4L2_RDS_BLOCK_INVALID ||
+           rdsblocks[i].block & V4L2_RDS_BLOCK_ERROR) {
+        block_bitmap |= 1 << V4L2_RDS_BLOCK_INVALID;
+        continue;
+      }
+
+      int blocknum = rdsblocks[i].block & V4L2_RDS_BLOCK_MSK;
+      // In some cases, the full set of bits in an RDS group isn't
+      // needed, in which case version B RDS groups can be sent.
+      // Version B groups replace block C with block C' (V4L2_RDS_BLOCK_C_ALT).
+      // Block C' always stores the PI code, so receivers can find the PI
+      // code more quickly/reliably.
+      // However, we only process whole RDS groups, so it doesn't matter here.
+      if (blocknum == V4L2_RDS_BLOCK_C_ALT)
+        blocknum = V4L2_RDS_BLOCK_C;
+      if (blocknum > V4L2_RDS_BLOCK_D) {
+        HAL_LOG("Unexpected RDS block number %d. This is a driver bug.",
+                blocknum);
+        continue;
+      }
+
+      if (blocknum == V4L2_RDS_BLOCK_A)
+        block_bitmap = 0;
+
+      // Skip the group if we skipped a block.
+      // This stops us from processing blocks sent out of order.
+      if (block_bitmap != ((1 << blocknum) - 1)) {
+        block_bitmap |= 1 << V4L2_RDS_BLOCK_INVALID;
+        continue;
+      }
+
+      block_bitmap |= 1 << blocknum;
+
+      lastblock = blocknum;
+      blocks[blocknum] = (rdsblocks[i].msb << 8) | rdsblocks[i].lsb;
+
+      // Make sure we have all 4 blocks and that they're valid
+      if (block_bitmap != 0x0F)
+        continue;
+
+      hal::FMRadioRDSGroup group;
+      group.blockA() = blocks[V4L2_RDS_BLOCK_A];
+      group.blockB() = blocks[V4L2_RDS_BLOCK_B];
+      group.blockC() = blocks[V4L2_RDS_BLOCK_C];
+      group.blockD() = blocks[V4L2_RDS_BLOCK_D];
+      NotifyFMRadioRDSGroup(group);
+    }
+  }
+
+  return nullptr;
+}
+
+static int sRDSPipeFD;
+
+void
+EnableRDS(uint32_t aMask)
+{
+  if (!sRadioEnabled || !sRDSSupported)
+    return;
+
+  if (sMsmFMMode)
+    setControl(V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, aMask);
+
+  if (sRDSEnabled)
+    return;
+
+  int pipefd[2];
+  int rc = pipe2(pipefd, O_NONBLOCK);
+  if (rc < 0) {
+    HAL_LOG("Could not create RDS thread signaling pipes (%d)", rc);
+    return;
+  }
+
+  ScopedClose writefd(pipefd[1]);
+  ScopedClose readfd(pipefd[0]);
+
+  rc = setControl(V4L2_CID_RDS_RECEPTION, true);
+  if (rc < 0) {
+    HAL_LOG("Could not enable RDS reception (%d)", rc);
+    return;
+  }
+
+  sRDSPipeFD = writefd;
+
+  sRDSEnabled = true;
+
+  rc = pthread_create(&sRDSThread, nullptr,
+                      readRDSDataThread, (void*)pipefd[0]);
+  if (rc) {
+    HAL_LOG("Could not start RDS reception thread (%d)", rc);
+    setControl(V4L2_CID_RDS_RECEPTION, false);
+    sRDSEnabled = false;
+    return;
+  }
+
+  readfd.forget();
+  writefd.forget();
+}
+
+void
+DisableRDS()
+{
+  if (!sRadioEnabled || !sRDSEnabled)
+    return;
+
+  int rc = setControl(V4L2_CID_RDS_RECEPTION, false);
+  if (rc < 0) {
+    HAL_LOG("Could not disable RDS reception (%d)", rc);
+  }
+
+  sRDSEnabled = false;
+
+  write(sRDSPipeFD, "x", 1);
+
+  pthread_join(sRDSThread, nullptr);
+
+  close(sRDSPipeFD);
+}
+
 } // hal_impl
 } // namespace mozilla
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -64,16 +64,23 @@ struct ScreenConfiguration {
 };
 
 struct FMRadioOperationInformation {
   FMRadioOperation operation;
   FMRadioOperationStatus status;
   uint32_t frequency;
 };
 
+struct FMRadioRDSGroup {
+  uint16_t blockA;
+  uint16_t blockB;
+  uint16_t blockC;
+  uint16_t blockD;
+};
+
 struct FMRadioSettings {
   FMRadioCountry country;
   uint32_t upperLimit;
   uint32_t lowerLimit;
   uint32_t spaceType;
   uint32_t preEmphasis;
 };
 
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -422,16 +422,28 @@ GetFMRadioSignalStrength()
 
 void
 CancelFMRadioSeek()
 {
   NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
 }
 
 void
+EnableRDS(uint32_t aMask)
+{
+  NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
+}
+
+void
+DisableRDS()
+{
+  NS_RUNTIMEABORT("FM radio cannot be called from sandboxed contexts.");
+}
+
+void
 FactoryReset(FactoryResetReason& aReason)
 {
   if (aReason == FactoryResetReason::Normal) {
     Hal()->SendFactoryReset(NS_LITERAL_STRING("normal"));
   } else if (aReason == FactoryResetReason::Wipe) {
     Hal()->SendFactoryReset(NS_LITERAL_STRING("wipe"));
   }
 }
--- a/mobile/android/base/menu/MenuItemActionBar.java
+++ b/mobile/android/base/menu/MenuItemActionBar.java
@@ -1,31 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.menu;
 
+import org.mozilla.gecko.NewTabletUI;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.widget.ImageButton;
 
 public class MenuItemActionBar extends ImageButton
                                implements GeckoMenuItem.Layout {
     private static final String LOGTAG = "GeckoMenuItemActionBar";
 
     public MenuItemActionBar(Context context) {
         this(context, null);
     }
 
     public MenuItemActionBar(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.menuItemActionBarStyle);
+        // TODO: Remove this branch (and associated attr) when old tablet is removed.
+        this(context, attrs, (NewTabletUI.isEnabled(context)) ?
+                R.attr.menuItemActionBarStyleNewTablet : R.attr.menuItemActionBarStyle);
     }
 
     public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
 
     @Override
     public void initialize(GeckoMenuItem item) {
--- a/mobile/android/base/resources/layout-large-v11/new_tablet_browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/new_tablet_browser_toolbar.xml
@@ -46,22 +46,20 @@
     <!-- Values of marginLeft are used to animate the forward button so don't change its value. -->
     <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button.Container"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"
                   android:paddingLeft="6dip"
                   android:paddingRight="4dip"/>
 
-    <!-- TODO: The reload asset is too small (bug 1072466) so we have white-space above and below the menu item.
-         We add marginTop to center and compensate: remove this when the final asset is added. -->
     <LinearLayout android:id="@+id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="match_parent"
-                  android:layout_marginTop="2dp"
+                  android:gravity="center_vertical"
                   android:layout_marginLeft="6dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/tabs"/>
 
     <org.mozilla.gecko.widget.ThemedImageButton
             android:id="@+id/tabs"
             style="@style/UrlBar.ImageButton"
             android:layout_toLeftOf="@id/menu"
--- a/mobile/android/base/resources/values-large-v11/dimens.xml
+++ b/mobile/android/base/resources/values-large-v11/dimens.xml
@@ -10,14 +10,16 @@
 
     <!-- If you update one of these values, update the other. -->
     <dimen name="back_button_width">42dp</dimen>
     <dimen name="back_button_width_half">21dp</dimen>
 
     <dimen name="forward_default_offset">-13dip</dimen>
     <dimen name="new_tablet_forward_default_offset">-6dp</dimen>
 
+    <dimen name="new_tablet_browser_toolbar_menu_item_padding">19dp</dimen>
+
     <dimen name="tabs_counter_size">26sp</dimen>
     <dimen name="panel_grid_view_column_width">200dp</dimen>
 
     <dimen name="tab_strip_favicon_size">16dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -65,16 +65,23 @@
     <style name="Widget.MenuItemActionBar">
         <item name="android:layout_width">@dimen/browser_toolbar_height</item>
         <item name="android:layout_height">@dimen/browser_toolbar_height</item>
         <item name="android:padding">@dimen/browser_toolbar_button_padding</item>
         <item name="android:background">@drawable/action_bar_button</item>
         <item name="android:scaleType">fitCenter</item>
     </style>
 
+    <style name="Widget.MenuItemActionBar.NewTablet">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:padding">@dimen/new_tablet_browser_toolbar_menu_item_padding</item>
+        <item name="android:scaleType">center</item>
+    </style>
+
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView">
         <item name="android:scrollbarStyle">outsideOverlay</item>
     </style>
 
     <style name="Widget.TopSitesGridView" parent="Widget.GridView">
         <item name="android:paddingLeft">5dp</item>
         <item name="android:paddingRight">5dp</item>
         <item name="android:paddingBottom">30dp</item>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -10,16 +10,19 @@
 
         <!-- Style for GeckoMenu ListView -->
         <attr name="geckoMenuListViewStyle" format="reference"/>
 
         <!-- Style for MenuItemActionBar -->
         <attr name="menuItemActionBarStyle" format="reference"/>
 
         <!-- Style for MenuItemActionBar -->
+        <attr name="menuItemActionBarStyleNewTablet" format="reference"/>
+
+        <!-- Style for MenuItemActionBar -->
         <attr name="menuItemActionModeStyle" format="reference"/>
 
         <!-- Style for MenuItemActionView -->
         <attr name="menuItemActionViewStyle" format="reference"/>
 
         <!-- Style for MenuItemDefault -->
         <attr name="menuItemDefaultStyle" format="reference"/>
 
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -88,16 +88,17 @@
         <item name="android:spinnerStyle">@style/Widget.Spinner</item>
         <item name="android:windowBackground">@android:color/white</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
         <item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
         <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
+        <item name="menuItemActionBarStyleNewTablet">@style/Widget.MenuItemActionBar.NewTablet</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
         <item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
         <item name="panelGridViewStyle">@style/Widget.PanelGridView</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
     </style>
 
--- a/mobile/android/base/tests/BaseRobocopTest.java
+++ b/mobile/android/base/tests/BaseRobocopTest.java
@@ -10,16 +10,18 @@ import org.mozilla.gecko.Assert;
 import org.mozilla.gecko.FennecInstrumentationTestRunner;
 import org.mozilla.gecko.FennecMochitestAssert;
 import org.mozilla.gecko.FennecNativeDriver;
 import org.mozilla.gecko.FennecTalosAssert;
 
 import org.mozilla.gecko.AppConstants;
 
 import android.app.Activity;
+import android.content.Context;
+import android.os.PowerManager;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
 @SuppressWarnings("unchecked")
 public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<Activity> {
     public enum Type {
         MOCHITEST,
         TALOS
@@ -102,9 +104,18 @@ public abstract class BaseRobocopTest ex
         if (getTestType() == Type.TALOS) {
             mAsserter = new FennecTalosAssert();
         } else {
             mAsserter = new FennecMochitestAssert();
         }
         mAsserter.setLogFile(mLogFile);
         mAsserter.setTestName(getClass().getName());
     }
+
+    /**
+     * Ensure that the screen on the test device is powered on during tests.
+     */
+    public void throwIfScreenNotOn() {
+        final PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
+        mAsserter.ok(pm.isScreenOn(),
+            "Robocop tests need the test device screen to be powered on.", "");
+    }
 }
--- a/mobile/android/base/tests/BaseTest.java
+++ b/mobile/android/base/tests/BaseTest.java
@@ -116,16 +116,19 @@ abstract class BaseTest extends BaseRobo
         setActivityIntent(i);
         mActivity = getActivity();
         // Set up Robotium.solo and Driver objects
         mSolo = new Solo(getInstrumentation(), mActivity);
         mDriver = new FennecNativeDriver(mActivity, mSolo, mRootPath);
         mActions = new FennecNativeActions(mActivity, mSolo, getInstrumentation(), mAsserter);
         mDevice = new Device();
         mDatabaseHelper = new DatabaseHelper(mActivity, mAsserter);
+
+        // Ensure Robocop tests are run with Display powered on.
+        throwIfScreenNotOn();
     }
 
     protected void initializeProfile() {
         final GeckoProfile profile;
         if (mProfile.startsWith("/")) {
             profile = GeckoProfile.get(getActivity(), "default", mProfile);
         } else {
             profile = GeckoProfile.get(getActivity(), mProfile);
--- a/mobile/android/base/tests/UITest.java
+++ b/mobile/android/base/tests/UITest.java
@@ -69,16 +69,19 @@ abstract class UITest extends BaseRoboco
         mActions = new FennecNativeActions(activity, mSolo, getInstrumentation(), mAsserter);
 
         mBaseHostnameUrl = mConfig.get("host").replaceAll("(/$)", "");
         mBaseIpUrl = mConfig.get("rawhost").replaceAll("(/$)", "");
 
         // Helpers depend on components so initialize them first.
         initComponents();
         initHelpers();
+
+        // Ensure Robocop tests are run with Display powered on.
+        throwIfScreenNotOn();
     }
 
     @Override
     public void tearDown() throws Exception {
         try {
             mAsserter.endTest();
             // request a force quit of the browser and wait for it to take effect
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null));
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -556,22 +556,22 @@ this.SocialService = {
   },
 
   _showInstallNotification: function(aDOMDocument, aAddonInstaller) {
     let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
     let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
     let requestingWindow = aDOMDocument.defaultView.top;
     let chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject;
     let browser = chromeWin.gBrowser.getBrowserForDocument(aDOMDocument);
-    let requestingURI =  Services.io.newURI(aDOMDocument.location.href, null, null);
+    let requestingURI =  Services.io.newURI(aAddonInstaller.addon.manifest.origin, null, null);
 
     let productName = brandBundle.GetStringFromName("brandShortName");
 
     let message = browserBundle.formatStringFromName("service.install.description",
-                                                     [aAddonInstaller.addon.manifest.name, productName], 2);
+                                                     [requestingURI.host, productName], 2);
 
     let action = {
       label: browserBundle.GetStringFromName("service.install.ok.label"),
       accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"),
       callback: function() {
         aAddonInstaller.install();
       },
     };
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -1986,16 +1986,24 @@ ProcessReplaceRequest()
   if (rv) {
     LOG(("Moving destDir to tmpDir failed, err: %d", rv));
     return rv;
   }
 
   LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
        newDir, destDir));
   rv = rename_file(newDir, destDir, true);
+#ifdef XP_MACOSX
+  if (rv) {
+    LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
+         newDir, destDir));
+    copy_recursive_skiplist<0> skiplist;
+    rv = ensure_copy_recursive(newDir, destDir, skiplist);
+  }
+#endif
   if (rv) {
     LOG(("Moving newDir to destDir failed, err: %d", rv));
     LOG(("Now, try to move tmpDir back to destDir"));
     ensure_remove_recursive(destDir);
     int rv2 = rename_file(tmpDir, destDir, true);
     if (rv2) {
       LOG(("Moving tmpDir back to destDir failed, err: %d", rv2));
     }
@@ -2172,18 +2180,17 @@ UpdateThreadFunc(void *param)
     // updater application again in order to apply the update without
     // staging.
     // The MOZ_NO_REPLACE_FALLBACK environment variable is used to
     // bypass this fallback, and is used in the updater tests.
     // The only special thing which we should do here is to remove the
     // staged directory as it won't be useful any more.
     ensure_remove_recursive(gWorkingDirPath);
     WriteStatusFile(sUsingService ? "pending-service" : "pending");
-    char processUpdates[] = "MOZ_PROCESS_UPDATES=";
-    putenv(processUpdates); // We need to use -process-updates again in the tests
+    putenv(const_cast<char*>("MOZ_PROCESS_UPDATES=")); // We need to use -process-updates again in the tests
     reportRealResults = false; // pretend success
   }
 
   if (reportRealResults) {
     if (rv) {
       LOG(("failed: %d", rv));
     }
     else {
--- a/toolkit/themes/osx/mozapps/update/updates.css
+++ b/toolkit/themes/osx/mozapps/update/updates.css
@@ -35,16 +35,17 @@ wizardpage {
 /* Don't use top margin - it can cause a scrollbar on the incompatible page */
 .wizard-buttons {
   padding: 0;
   -moz-appearance: statusbar;
 }
 
 .wizard-buttons button {
   -moz-appearance: toolbarbutton;
+  color: ButtonText;
   min-height: 22px;
   margin: 0 6px;
   padding: 0;
   text-shadow: @loweredShadow@;
 }
 
 .loadingBox {
   padding: 3px 5px 3px 5px;