Merge m-c to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 01 Oct 2014 15:44:37 +0200
changeset 231477 bb23019a3bf1410adc8a4ca6b323fb0ca3b2d818
parent 231476 96e8ff499c1f4b80415ad0f74a2ed2a687b28d66 (current diff)
parent 231374 835ef55e175e82b47aa5fd1f71aeda7e89d5ebd6 (diff)
child 231478 6e70456fcd3059bbe9f331716cfdff912a9f06e4
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [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 b2g-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
media/libnestegg/README
testing/web-platform/meta/performance-timeline/idlharness.html.ini
--- 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/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/build/automationutils.py
+++ b/build/automationutils.py
@@ -191,17 +191,17 @@ def dumpLeakLog(leakLogFile, filter = Fa
   # Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out.
   # Only check whether an actual leak was reported.
   if filter and not "0 TOTAL " in leakReport:
     return
 
   # Simply copy the log.
   log.info(leakReport.rstrip("\n"))
 
-def processSingleLeakFile(leakLogFileName, processType, leakThreshold):
+def processSingleLeakFile(leakLogFileName, processType, leakThreshold, ignoreMissingLeaks):
   """Process a single leak log.
   """
 
   #                  Per-Inst  Leaked      Total  Rem ...
   #   0 TOTAL              17     192  419115886    2 ...
   # 833 nsTimerImpl        60     120      24726    2 ...
   lineRe = re.compile(r"^\s*\d+\s+(?P<name>\S+)\s+"
                       r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
@@ -268,21 +268,24 @@ def processSingleLeakFile(leakLogFileNam
 
   logAsWarning = False
 
   if totalBytesLeaked is None:
     # We didn't see a line with name 'TOTAL'
     if crashedOnPurpose:
       log.info("TEST-INFO | leakcheck | %s deliberate crash and thus no leak log"
                % processString)
+    elif ignoreMissingLeaks:
+      log.info("TEST-INFO | leakcheck | %s ignoring missing output line for total leaks"
+               % processString)
     else:
-      # TODO: This should be a TEST-UNEXPECTED-FAIL, but was changed to a warning
-      # due to too many intermittent failures (see bug 831223).
-      log.info("WARNING | leakcheck | %s missing output line for total leaks!"
+      log.info("TEST-UNEXPECTED-FAIL | leakcheck | %s missing output line for total leaks!"
                % processString)
+      log.info("TEST-INFO | leakcheck | missing output line from log file %s"
+               % leakLogFileName)
     return
 
   if totalBytesLeaked == 0:
     log.info("TEST-PASS | leakcheck | %s no leaks detected!" % processString)
     return
 
   # totalBytesLeaked was seen and is non-zero.
   if totalBytesLeaked > leakThreshold:
@@ -301,17 +304,17 @@ def processSingleLeakFile(leakLogFileNam
 
   if logAsWarning:
     log.warning("%s | leakcheck | %s %d bytes leaked (%s)"
                 % (prefix, processString, totalBytesLeaked, leakedObjectSummary))
   else:
     log.info("%s | leakcheck | %s %d bytes leaked (%s)"
              % (prefix, processString, totalBytesLeaked, leakedObjectSummary))
 
-def processLeakLog(leakLogFile, leakThresholds):
+def processLeakLog(leakLogFile, leakThresholds, ignoreMissingLeaks):
   """Process the leak log, including separate leak logs created
   by child processes.
 
   Use this function if you want an additional PASS/FAIL summary.
   It must be used with the |XPCOM_MEM_BLOAT_LOG| environment variable.
 
   The base of leakLogFile for a non-default process needs to end with
     _proctype_pid12345.log
@@ -358,17 +361,18 @@ def processLeakLog(leakLogFile, leakThre
       if m:
         processType = m.group(1)
       else:
         processType = "default"
       if not processType in knownProcessTypes:
         log.info("TEST-UNEXPECTED-FAIL | leakcheck | Leak log with unknown process type %s"
                  % processType)
       leakThreshold = leakThresholds.get(processType, 0)
-      processSingleLeakFile(thisFile, processType, leakThreshold)
+      processSingleLeakFile(thisFile, processType, leakThreshold,
+                            processType in ignoreMissingLeaks)
 
 def replaceBackSlashes(input):
   return input.replace('\\', '/')
 
 class KeyValueParseError(Exception):
   """error when parsing strings of serialized key-values"""
   def __init__(self, msg, errors=()):
     self.errors = errors
--- a/build/gyp.mozbuild
+++ b/build/gyp.mozbuild
@@ -26,17 +26,17 @@ gyp_vars = {
     'build_libvpx': 0,
     'build_libyuv': 0,
     'libyuv_dir': '/media/libyuv',
     'yuv_disable_avx2': 0 if CONFIG['HAVE_X86_AVX2'] else 1,
     # don't use openssl
     'use_openssl': 0,
 
     # saves 4MB when webrtc_trace is off
-    'enable_lazy_trace_alloc': 0,
+    'enable_lazy_trace_alloc': 1 if CONFIG['RELEASE_BUILD'] else 0,
 
     'use_x11': 1 if CONFIG['MOZ_X11'] else 0,
     'use_glib': 1 if CONFIG['GLIB_LIBS'] else 0,
 
      # turn off mandatory use of NEON and instead use NEON detection
     'arm_neon': 0,
     'arm_neon_optional': 1,
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -9173,24 +9173,16 @@ nsresult
 nsDocument::CloneDocHelper(nsDocument* clone) const
 {
   clone->mIsStaticDocument = mCreatingStaticClone;
 
   // Init document
   nsresult rv = clone->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Set URI/principal
-  clone->nsDocument::SetDocumentURI(nsIDocument::GetDocumentURI());
-  clone->SetChromeXHRDocURI(mChromeXHRDocURI);
-  // Must set the principal first, since SetBaseURI checks it.
-  clone->SetPrincipal(NodePrincipal());
-  clone->mDocumentBaseURI = mDocumentBaseURI;
-  clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
-
   if (mCreatingStaticClone) {
     nsCOMPtr<nsILoadGroup> loadGroup;
 
     // |mDocumentContainer| is the container of the document that is being
     // created and not the original container. See CreateStaticClone function().
     nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
     if (docLoader) {
       docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
@@ -9205,16 +9197,28 @@ nsDocument::CloneDocHelper(nsDocument* c
     clone->mChannel = channel;
     if (uri) {
       clone->ResetToURI(uri, loadGroup, NodePrincipal());
     }
 
     clone->SetContainer(mDocumentContainer);
   }
 
+  // Now ensure that our clone has the same URI, base URI, and principal as us.
+  // We do this after the mCreatingStaticClone block above, because that block
+  // can set the base URI to an incorrect value in cases when base URI
+  // information came from the channel.  So we override explicitly, and do it
+  // for all these properties, in case ResetToURI messes with any of the rest of
+  // them.
+  clone->nsDocument::SetDocumentURI(nsIDocument::GetDocumentURI());
+  clone->SetChromeXHRDocURI(mChromeXHRDocURI);
+  clone->SetPrincipal(NodePrincipal());
+  clone->mDocumentBaseURI = mDocumentBaseURI;
+  clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
+
   // Set scripting object
   bool hasHadScriptObject = true;
   nsIScriptGlobalObject* scriptObject =
     GetScriptHandlingObject(hasHadScriptObject);
   NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
   if (scriptObject) {
     clone->SetScriptHandlingObject(scriptObject);
   } else {
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -1085,17 +1085,17 @@ nsXMLHttpRequest::GetResponseURL(nsAStri
   nsCOMPtr<nsIURI> responseUrl;
   mChannel->GetURI(getter_AddRefs(responseUrl));
 
   if (!responseUrl) {
     return;
   }
 
   nsAutoCString temp;
-  responseUrl->GetSpec(temp);
+  responseUrl->GetSpecIgnoringRef(temp);
   CopyUTF8toUTF16(temp, aUrl);
 }
 
 /* readonly attribute unsigned long status; */
 NS_IMETHODIMP
 nsXMLHttpRequest::GetStatus(uint32_t *aStatus)
 {
   *aStatus = Status();
--- a/content/base/test/file_XHRResponseURL.js
+++ b/content/base/test/file_XHRResponseURL.js
@@ -184,16 +184,21 @@ function testSuccessResponse() {
     },
     {
       message: "request to cross-origin redirects to another cross-origin and finally go to the other cross-origin URL",
       requestURL: "http://example.com/tests/content/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.org/tests/content/base/test/file_XHRResponseURL.sjs?url=http://test1.example.com/tests/content/base/test/file_XHRResponseURL.text"),
       responseURL: "http://test1.example.com/tests/content/base/test/file_XHRResponseURL.text",
       skip: isInWorker(),
       reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
+    {
+      message: "request URL has fragment",
+      requestURL: "http://mochi.test:8888/tests/content/base/test/file_XHRResponseURL.text#fragment",
+      responseURL: "http://mochi.test:8888/tests/content/base/test/file_XHRResponseURL.text"
+    },
 
     // tests for non-http(s) URL
     {
       message: "request to data: URL",
       requestURL: "data:text/plain,data",
       responseURL: "data:text/plain,data"
     },
     {
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -58,17 +58,17 @@ PRLogModuleInfo* gMediaStreamGraphLog;
 #  endif
 #else
 #  define LIFECYCLE_LOG(...)
 #endif
 
 /**
  * The singleton graph instance.
  */
-static MediaStreamGraphImpl* gGraph;
+static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs;
 
 MediaStreamGraphImpl::~MediaStreamGraphImpl()
 {
   NS_ASSERTION(IsEmpty(),
                "All streams should have been destroyed by messages from the main thread");
   STREAM_LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this));
   LIFECYCLE_LOG("MediaStreamGraphImpl::~MediaStreamGraphImpl\n");
 }
@@ -1628,19 +1628,20 @@ MediaStreamGraphImpl::RunInStableState(b
         // synchronously because it spins the event loop waiting for threads
         // to shut down, and we don't want to do that in a stable state handler.
         mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
         LIFECYCLE_LOG("Sending MediaStreamGraphShutDownRunnable %p", this);
         nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this );
         NS_DispatchToMainThread(event);
 
         LIFECYCLE_LOG("Disconnecting MediaStreamGraph %p", this);
-        if (this == gGraph) {
+        MediaStreamGraphImpl* graph;
+        if (gGraphs.Get(mAudioChannel, &graph) && graph == this) {
           // null out gGraph if that's the graph being shut down
-          gGraph = nullptr;
+          gGraphs.Remove(mAudioChannel);
         }
       }
     } else {
       if (mLifecycleState <= LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
         MessageBlock* block = mBackMessageQueue.AppendElement();
         block->mMessages.SwapElements(mCurrentTaskMessageQueue);
         block->mGraphUpdateIndex = mNextGraphUpdateIndex;
         ++mNextGraphUpdateIndex;
@@ -1781,19 +1782,22 @@ MediaStreamGraphImpl::AppendMessage(Cont
 #endif
     aMessage->RunDuringShutdown();
 #ifdef DEBUG
     mCanRunMessagesSynchronously = true;
 #endif
     delete aMessage;
     if (IsEmpty() &&
         mLifecycleState >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) {
-      if (gGraph == this) {
-        gGraph = nullptr;
+
+      MediaStreamGraphImpl* graph;
+      if (gGraphs.Get(mAudioChannel, &graph) && graph == this) {
+        gGraphs.Remove(mAudioChannel);
       }
+
       Destroy();
     }
     return;
   }
 
   mCurrentTaskMessageQueue.AppendElement(aMessage);
   EnsureRunInStableState();
 }
@@ -2731,16 +2735,17 @@ MediaStreamGraphImpl::MediaStreamGraphIm
 #endif
   , mMemoryReportMonitor("MSGIMemory")
   , mSelfRef(MOZ_THIS_IN_INITIALIZER_LIST())
   , mAudioStreamSizes()
   , mNeedsMemoryReport(false)
 #ifdef DEBUG
   , mCanRunMessagesSynchronously(false)
 #endif
+  , mAudioChannel(static_cast<uint32_t>(aChannel))
 {
 #ifdef PR_LOGGING
   if (!gMediaStreamGraphLog) {
     gMediaStreamGraphLog = PR_NewLogModule("MediaStreamGraph");
   }
 #endif
 
   if (mRealtime) {
@@ -2769,50 +2774,65 @@ MediaStreamGraphImpl::Destroy()
   // Clear the self reference which will destroy this instance.
   mSelfRef = nullptr;
 }
 
 NS_IMPL_ISUPPORTS(MediaStreamGraphShutdownObserver, nsIObserver)
 
 static bool gShutdownObserverRegistered = false;
 
+namespace {
+
+PLDHashOperator
+ForceShutdownEnumerator(const uint32_t& /* aAudioChannel */,
+                        MediaStreamGraphImpl* aGraph,
+                        void* /* aUnused */)
+{
+  aGraph->ForceShutDown();
+  return PL_DHASH_NEXT;
+}
+
+} // anonymous namespace
+
 NS_IMETHODIMP
 MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject,
                                           const char *aTopic,
                                           const char16_t *aData)
 {
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
-    if (gGraph) {
-      gGraph->ForceShutDown();
-    }
+    gGraphs.EnumerateRead(ForceShutdownEnumerator, nullptr);
     nsContentUtils::UnregisterShutdownObserver(this);
     gShutdownObserverRegistered = false;
   }
   return NS_OK;
 }
 
 MediaStreamGraph*
 MediaStreamGraph::GetInstance(DOMMediaStream::TrackTypeHints aHint, dom::AudioChannel aChannel)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
-  if (!gGraph) {
+  uint32_t channel = static_cast<uint32_t>(aChannel);
+  MediaStreamGraphImpl* graph = nullptr;
+
+  if (!gGraphs.Get(channel, &graph)) {
     if (!gShutdownObserverRegistered) {
       gShutdownObserverRegistered = true;
       nsContentUtils::RegisterShutdownObserver(new MediaStreamGraphShutdownObserver());
     }
 
     CubebUtils::InitPreferredSampleRate();
 
-    gGraph = new MediaStreamGraphImpl(true, CubebUtils::PreferredSampleRate(), aHint, aChannel);
+    graph = new MediaStreamGraphImpl(true, CubebUtils::PreferredSampleRate(), aHint, aChannel);
+    gGraphs.Put(channel, graph);
 
-    STREAM_LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph));
+    STREAM_LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", graph));
   }
 
-  return gGraph;
+  return graph;
 }
 
 MediaStreamGraph*
 MediaStreamGraph::CreateNonRealtimeInstance(TrackRate aSampleRate)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
   MediaStreamGraphImpl* graph = new MediaStreamGraphImpl(false, aSampleRate);
@@ -2973,17 +2993,20 @@ MediaStreamGraph::CreateAudioNodeStream(
   }
   graph->AppendMessage(new CreateMessage(stream));
   return stream;
 }
 
 bool
 MediaStreamGraph::IsNonRealtime() const
 {
-  return this != gGraph;
+  const MediaStreamGraphImpl* impl = static_cast<const MediaStreamGraphImpl*>(this);
+  MediaStreamGraphImpl* graph;
+
+  return !gGraphs.Get(impl->AudioChannel(), &graph) || graph != impl;
 }
 
 void
 MediaStreamGraph::StartNonRealtimeProcessing(TrackRate aRate, uint32_t aTicksToProcess)
 {
   NS_ASSERTION(NS_IsMainThread(), "main thread only");
 
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
--- a/content/media/MediaStreamGraphImpl.h
+++ b/content/media/MediaStreamGraphImpl.h
@@ -649,16 +649,18 @@ public:
    * Hold a ref to the Latency logger
    */
   nsRefPtr<AsyncLatencyLogger> mLatencyLog;
   AudioMixer mMixer;
 #ifdef MOZ_WEBRTC
   nsRefPtr<AudioOutputObserver> mFarendObserverRef;
 #endif
 
+  uint32_t AudioChannel() const { return mAudioChannel; }
+
 private:
   virtual ~MediaStreamGraphImpl();
 
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
   /**
    * Used to signal that a memory report has been requested.
    */
@@ -682,13 +684,16 @@ private:
 
 #ifdef DEBUG
   /**
    * Used to assert when AppendMessage() runs ControlMessages synchronously.
    */
   bool mCanRunMessagesSynchronously;
 #endif
 
+  // We use uint32_t instead AudioChannel because this is just used as key for
+  // the hashtable gGraphs.
+  uint32_t mAudioChannel;
 };
 
 }
 
 #endif /* MEDIASTREAMGRAPHIMPL_H_ */
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -163,19 +163,21 @@ static const int32_t MAX_VIDEO_WIDTH = 4
 static const int32_t MAX_VIDEO_HEIGHT = 3000;
 
 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
 // Note that aDisplay must be validated by IsValidVideoRegion()
 // before being used!
 void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
 
 // The amount of virtual memory reserved for thread stacks.
-#if (defined(XP_WIN) || defined(LINUX)) && !defined(MOZ_ASAN)
-#define MEDIA_THREAD_STACK_SIZE (128 * 1024)
-#elif defined(XP_MACOSX) && !defined(MOZ_ASAN)
+#if defined(MOZ_ASAN)
+// Use the system default in ASAN builds, because the default is assumed to be
+// larger than the size we want to use and is hopefully sufficient for ASAN.
+#define MEDIA_THREAD_STACK_SIZE nsIThreadManager::DEFAULT_STACK_SIZE
+#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)
 #define MEDIA_THREAD_STACK_SIZE (256 * 1024)
 #else
 // All other platforms use their system defaults.
 #define MEDIA_THREAD_STACK_SIZE nsIThreadManager::DEFAULT_STACK_SIZE
 #endif
 
 // Downmix multichannel Audio samples to Stereo.
 // Input are the buffer contains multichannel data,
--- a/content/media/gmp/GMPChild.cpp
+++ b/content/media/gmp/GMPChild.cpp
@@ -258,18 +258,22 @@ GMPChild::LoadPluginLibrary(const std::s
     return false;
   }
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
   nsAutoCString nativePath;
   libFile->GetNativePath(nativePath);
 
   // Enable sandboxing here -- we know the plugin file's path, but
   // this process's execution hasn't been affected by its content yet.
-  MOZ_ASSERT(mozilla::CanSandboxMediaPlugin());
-  mozilla::SetMediaPluginSandbox(nativePath.get());
+  if (mozilla::CanSandboxMediaPlugin()) {
+    mozilla::SetMediaPluginSandbox(nativePath.get());
+  } else {
+    printf_stderr("GMPChild::LoadPluginLibrary: Loading media plugin %s unsandboxed.\n",
+                  nativePath.get());
+  }
 #endif // XP_LINUX && MOZ_GMP_SANDBOX
 
   libFile->Load(&mLib);
 #endif // XP_MACOSX && MOZ_GMP_SANDBOX
 
   if (!mLib) {
     NS_WARNING("Failed to link Gecko Media Plugin library.");
     return false;
--- a/content/media/gmp/GMPParent.cpp
+++ b/content/media/gmp/GMPParent.cpp
@@ -13,16 +13,19 @@
 #include "nsCharSeparatedTokenizer.h"
 #include "nsThreadUtils.h"
 #include "nsIRunnable.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "mozilla/unused.h"
 #include "nsIObserverService.h"
 #include "GMPTimerParent.h"
 #include "runnable_utils.h"
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/Sandbox.h"
+#endif
 
 #include "mozilla/dom/CrashReporterParent.h"
 using mozilla::dom::CrashReporterParent;
 
 #ifdef MOZ_CRASHREPORTER
 using CrashReporter::AnnotationTable;
 using CrashReporter::GetIDFromMinidump;
 #endif
@@ -847,16 +850,27 @@ GMPParent::ReadGMPMetaData()
         nsCCharSeparatedTokenizer tagTokens(ts, ':');
         while (tagTokens.hasMoreTokens()) {
           const nsDependentCSubstring tag(tagTokens.nextToken());
           cap->mAPITags.AppendElement(tag);
         }
       }
     }
 
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+    if (cap->mAPIName.EqualsLiteral("eme-decrypt") &&
+        !mozilla::CanSandboxMediaPlugin()) {
+      printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
+                    " but this system can't sandbox it; not loading.\n",
+                    mDisplayName.get());
+      delete cap;
+      return NS_ERROR_FAILURE;
+    }
+#endif
+
     mCapabilities.AppendElement(cap);
   }
 
   if (mCapabilities.IsEmpty()) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -430,16 +430,24 @@ GeckoMediaPluginService::GetGMPVideoEnco
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::GetGMPDecryptor(nsTArray<nsCString>* aTags,
                                          const nsAString& aOrigin,
                                          GMPDecryptorProxy** aDecryptor)
 {
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+  if (!mozilla::CanSandboxMediaPlugin()) {
+    NS_WARNING("GeckoMediaPluginService::GetGMPDecryptor: "
+               "EME decryption not available without sandboxing support.");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+#endif
+
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   NS_ENSURE_ARG(aTags && aTags->Length() > 0);
   NS_ENSURE_ARG(aDecryptor);
 
   if (mShuttingDownOnGMPThread) {
     return NS_ERROR_FAILURE;
   }
 
@@ -620,21 +628,16 @@ GeckoMediaPluginService::PathRunnable::R
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::AddPluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
-  if (!mozilla::CanSandboxMediaPlugin()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-#endif
   nsCOMPtr<nsIThread> thread;
   nsresult rv = GetThread(getter_AddRefs(thread));
   if (NS_FAILED(rv)) {
     return rv;
   }
   nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, true);
   thread->Dispatch(r, NS_DISPATCH_NORMAL);
   return NS_OK;
--- a/content/media/gtest/TestWebMWriter.cpp
+++ b/content/media/gtest/TestWebMWriter.cpp
@@ -235,18 +235,17 @@ struct WebMioData {
 
 static int webm_read(void* aBuffer, size_t aLength, void* aUserData)
 {
   NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
   WebMioData* ioData = static_cast<WebMioData*>(aUserData);
 
   // Check the read length.
   if (aLength > ioData->data.Length()) {
-    NS_ERROR("Invalid read length");
-    return -1;
+    return 0;
   }
 
   // Check eos.
   if (ioData->offset.value() >= ioData->data.Length()) {
     return 0;
   }
 
   size_t oldOffset = ioData->offset.value();
@@ -287,17 +286,17 @@ static int webm_seek(int64_t aOffset, in
       return -1;
   }
 
   if (!ioData->offset.isValid()) {
     NS_ERROR("Invalid offset");
     return -1;
   }
 
-  return 1;
+  return 0;
 }
 
 static int64_t webm_tell(void* aUserData)
 {
   NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
   WebMioData* ioData = static_cast<WebMioData*>(aUserData);
   return ioData->offset.isValid() ? ioData->offset.value() : -1;
 }
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -284,17 +284,25 @@ SourceBuffer::StartUpdating()
   mUpdating = true;
   QueueAsyncSimpleEvent("updatestart");
 }
 
 void
 SourceBuffer::StopUpdating()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mUpdating);
+  if (!mUpdating) {
+    // The buffer append algorithm has been interrupted by abort().
+    //
+    // If the sequence appendBuffer(), abort(), appendBuffer() occurs before
+    // the first StopUpdating() runnable runs, then a second StopUpdating()
+    // runnable will be scheduled, but still only one (the first) will queue
+    // events.
+    return;
+  }
   mUpdating = false;
   QueueAsyncSimpleEvent("update");
   QueueAsyncSimpleEvent("updateend");
 }
 
 void
 SourceBuffer::AbortUpdating()
 {
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -643,22 +643,16 @@ AudioContext::Unmute() const
 }
 
 AudioChannel
 AudioContext::MozAudioChannelType() const
 {
   return mDestination->MozAudioChannelType();
 }
 
-void
-AudioContext::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
-{
-  mDestination->SetMozAudioChannelType(aValue, aRv);
-}
-
 AudioChannel
 AudioContext::TestAudioChannelInAudioNodeStream()
 {
   MediaStream* stream = mDestination->Stream();
   MOZ_ASSERT(stream);
 
   return stream->AudioChannelType();
 }
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -217,17 +217,16 @@ public:
   uint32_t MaxChannelCount() const;
 
   void Mute() const;
   void Unmute() const;
 
   JSObject* GetGlobalJSObject() const;
 
   AudioChannel MozAudioChannelType() const;
-  void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
   AudioChannel TestAudioChannelInAudioNodeStream();
 
   void UpdateNodeCount(int32_t aDelta);
 
   double DOMTimeToStreamTime(double aTime) const
   {
     return aTime - ExtraCurrentTime();
--- a/content/media/webaudio/OscillatorNode.cpp
+++ b/content/media/webaudio/OscillatorNode.cpp
@@ -371,40 +371,43 @@ public:
                      uint32_t aEnd)
   {
     MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
 
     uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
     float* higherWaveData = nullptr;
     float* lowerWaveData = nullptr;
     float tableInterpolationFactor;
-    float rate = 1.0 / mSource->SampleRate();
- 
+    // Phase increment at frequency of 1 Hz.
+    // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
+    float basePhaseIncrement =
+      static_cast<float>(periodicWaveSize) / mSource->SampleRate();
+
     for (uint32_t i = aStart; i < aEnd; ++i) {
       UpdateParametersIfNeeded(ticks, i);
       mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
                                                      lowerWaveData,
                                                      higherWaveData,
                                                      tableInterpolationFactor);
-      // mPhase runs 0..periodicWaveSize here instead of 0..2*M_PI.
-      mPhase += periodicWaveSize * mFinalFrequency * rate;
       mPhase = fmod(mPhase, periodicWaveSize);
       // Bilinear interpolation between adjacent samples in each table.
       uint32_t j1 = floor(mPhase);
       uint32_t j2 = j1 + 1;
       if (j2 >= periodicWaveSize) {
         j2 -= periodicWaveSize;
       }
       float sampleInterpolationFactor = mPhase - j1;
-      float lower = sampleInterpolationFactor * lowerWaveData[j1] +
-                    (1 - sampleInterpolationFactor) * lowerWaveData[j2];
-      float higher = sampleInterpolationFactor * higherWaveData[j1] +
-                    (1 - sampleInterpolationFactor) * higherWaveData[j2];
-      aOutput[i] = tableInterpolationFactor * lower +
-                   (1 - tableInterpolationFactor) * higher;
+      float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] +
+                    sampleInterpolationFactor * lowerWaveData[j2];
+      float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] +
+                    sampleInterpolationFactor * higherWaveData[j2];
+      aOutput[i] = (1.0f - tableInterpolationFactor) * lower +
+                   tableInterpolationFactor * higher;
+
+      mPhase += basePhaseIncrement * mFinalFrequency;
     }
   }
 
   void ComputeSilence(AudioChunk *aOutput)
   {
     aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
   }
 
--- a/content/media/webaudio/test/test_mozaudiochannel.html
+++ b/content/media/webaudio/test/test_mozaudiochannel.html
@@ -13,37 +13,33 @@
 
 function test_basic() {
   var ac = new AudioContext();
   ok(ac, "AudioContext created");
 
   // Default
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  // random wrong channel
-  ac.mozAudioChannelType = "foo";
+  // Unpermitted channels
+  ac = new AudioContext("content");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  // Unpermitted channels
-  ac.mozAudioChannelType = "content";
-  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
-
-  ac.mozAudioChannelType = "notification";
+  ac = new AudioContext("notification");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  ac.mozAudioChannelType = "alarm";
+  ac = new AudioContext("alarm");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  ac.mozAudioChannelType = "telephony";
+  ac = new AudioContext("telephony");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  ac.mozAudioChannelType = "ringer";
+  ac = new AudioContext("ringer");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
-  ac.mozAudioChannelType = "publicnotification";
+  ac = new AudioContext("publicnotification");
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
   runTest();
 }
 
 function test_permission(aChannel) {
   var ac = new AudioContext();
   ok(ac, "AudioContext created");
@@ -51,17 +47,17 @@ function test_permission(aChannel) {
   is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
 
   var channel = SpecialPowers.wrap(ac).testAudioChannelInAudioNodeStream();
   is(channel, "normal", "AudioNodeStream is using the correct default audio channel.");
 
   SpecialPowers.pushPermissions(
     [{ "type": "audio-channel-" + aChannel, "allow": true, "context": document }],
     function() {
-      ac.mozAudioChannelType = aChannel;
+      var ac = new AudioContext(aChannel);
       is(ac.mozAudioChannelType, aChannel, "Default ac channel == '" + aChannel + "'");
 
       var channel = SpecialPowers.wrap(ac).testAudioChannelInAudioNodeStream();
       is(channel, aChannel, "AudioNodeStream is using the correct new audio channel.");
 
       runTest();
     }
   );
--- a/content/media/webaudio/test/test_periodicWave.html
+++ b/content/media/webaudio/test/test_periodicWave.html
@@ -6,29 +6,89 @@
   <script type="text/javascript" src="webaudio.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
+
+// real and imag are used in separate PeriodicWaves to make their peak values
+// easy to determine.
+const realMax = 99;
+var real = new Float32Array(realMax + 1);
+real[1] = 2.0; // fundamental
+real[realMax] = 3.0;
+const realPeak = real[1] + real[realMax];
+const realFundamental = 19.0;
+var imag = new Float32Array(4);
+imag[0] = 6.0; // should be ignored.
+imag[3] = 0.5;
+const imagPeak = imag[3];
+const imagFundamental = 551.0;
+
+const testLength = 4096;
+
 addLoadEvent(function() {
   var ac = new AudioContext();
-  var real = new Float32Array(4096);
-  var imag = new Float32Array(4096);
-  var table = ac.createPeriodicWave(real, imag);
+  ac.createPeriodicWave(new Float32Array(4096), new Float32Array(4096));
   expectException(function() {
     ac.createPeriodicWave(new Float32Array(512), imag);
   }, DOMException.NOT_SUPPORTED_ERR);
   expectException(function() {
     ac.createPeriodicWave(new Float32Array(0), new Float32Array(0));
   }, DOMException.NOT_SUPPORTED_ERR);
   expectException(function() {
     ac.createPeriodicWave(new Float32Array(4097), new Float32Array(4097));
   }, DOMException.NOT_SUPPORTED_ERR);
-  SimpleTest.finish();
+
+  runTest();
 });
 
+var gTest = {
+  createGraph: function(context) {
+    var merger = context.createChannelMerger();
+
+    var osc0 = context.createOscillator();
+    var osc1 = context.createOscillator();
+
+    osc0.setPeriodicWave(context.
+                         createPeriodicWave(real,
+                                            new Float32Array(real.length)));
+    osc1.setPeriodicWave(context.
+                         createPeriodicWave(new Float32Array(imag.length),
+                                            imag));
+
+    osc0.frequency.value = realFundamental;
+    osc1.frequency.value = imagFundamental;
+
+    osc0.start();
+    osc1.start();
+
+    osc0.connect(merger, 0, 0);
+    osc1.connect(merger, 0, 1);
+
+    return merger;
+  },
+  createExpectedBuffers: function(context) {
+    var buffer = context.createBuffer(2, testLength, context.sampleRate);
+
+    for (var i = 0; i < buffer.length; ++i) {
+
+      buffer.getChannelData(0)[i] = 1.0 / realPeak *
+        (real[1] * Math.cos(2 * Math.PI * realFundamental * i /
+                            context.sampleRate) +
+         real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i /
+                            context.sampleRate));
+
+      buffer.getChannelData(1)[i] = 1.0 / imagPeak *
+         imag[3] * Math.sin(2 * Math.PI * 3 * imagFundamental * i /
+                            context.sampleRate);
+    }
+    return buffer;
+  },
+};
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bindings/test/TestInterfaceJS.js
+++ b/dom/bindings/test/TestInterfaceJS.js
@@ -51,11 +51,14 @@ TestInterfaceJS.prototype = {
   pingPongUnion: function(x) { return x; },
   pingPongUnionContainingNull: function(x) { return x; },
   pingPongNullableUnion: function(x) { return x; },
   returnBadUnion: function(x) { return 3; },
 
   get cachedAttr() { return this._cachedAttr; },
   setCachedAttr: function(n) { this._cachedAttr = n; },
   clearCachedAttrCache: function () { this.__DOM_IMPL__._clearCachedCachedAttrValue(); },
+
+  testSequenceOverload: function(arg) {},
+  testSequenceUnion: function(arg) {},
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJS])
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -45,8 +45,10 @@ skip-if = (toolkit == 'gonk' && debug) #
 skip-if = debug == false
 [test_scalarvaluestring.html]
 skip-if = debug == false
 [test_sequence_wrapping.html]
 [test_setWithNamedGetterNoNamedSetter.html]
 [test_throwing_method_noDCE.html]
 [test_treat_non_object_as_null.html]
 [test_traceProtos.html]
+[test_sequence_detection.html]
+skip-if = debug == false
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_sequence_detection.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1066432
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1066432</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1066432 **/
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
+    var testInterfaceJS = new TestInterfaceJS();
+    ok(testInterfaceJS, "got a TestInterfaceJS object");
+    try {
+      testInterfaceJS.testSequenceOverload(
+        { "@@iterator": 5, [Symbol.iterator]: Array.prototype[Symbol.iterator] });
+      ok(false, "Should have thrown in the overload case");
+    } catch (e) {
+      ise(e.name, "TypeError", "Should get a TypeError for the overload case");
+      ok(e.message.contains("not iterable"),
+         "Should have a message about being non-iterable in the overload case");
+    }
+
+    try {
+      testInterfaceJS.testSequenceUnion(
+        { "@@iterator": 5, [Symbol.iterator]: Array.prototype[Symbol.iterator] });
+      ok(false, "Should have thrown in the union case");
+    } catch (e) {
+      ise(e.name, "TypeError", "Should get a TypeError for the union case");
+      ok(e.message.contains("not iterable"),
+         "Should have a message about being non-iterable in the union case");
+    }
+
+    SimpleTest.finish();
+  });
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1066432">Mozilla Bug 1066432</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -819,36 +819,36 @@ private:
     oaepParams.mgf = mMgfMechanism;
     oaepParams.hashAlg = mHashMechanism;
 
     SECItem param;
     param.type = siBuffer;
     param.data = (unsigned char*) &oaepParams;
     param.len = sizeof(oaepParams);
 
-    uint32_t outLen;
+    uint32_t outLen = 0;
     if (mEncrypt) {
       // PK11_PubEncrypt() checks the plaintext's length and fails if it is too
       // long to encrypt, i.e. if it is longer than (k - 2hLen - 2) with 'k'
       // being the length in octets of the RSA modulus n and 'hLen' being the
       // output length in octets of the chosen hash function.
       // <https://tools.ietf.org/html/rfc3447#section-7.1>
       rv = MapSECStatus(PK11_PubEncrypt(
              mPubKey.get(), CKM_RSA_PKCS_OAEP, &param,
              mResult.Elements(), &outLen, mResult.Length(),
              mData.Elements(), mData.Length(), nullptr));
     } else {
       rv = MapSECStatus(PK11_PrivDecrypt(
              mPrivKey.get(), CKM_RSA_PKCS_OAEP, &param,
              mResult.Elements(), &outLen, mResult.Length(),
              mData.Elements(), mData.Length()));
     }
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+
     mResult.SetLength(outLen);
-
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
     return NS_OK;
   }
 };
 
 class HmacTask : public WebCryptoTask
 {
 public:
   HmacTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
--- 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"
@@ -339,16 +337,17 @@ struct nsIConsoleService::COMTypeInfo<ns
   static const nsIID kIID;
 };
 const nsIID nsIConsoleService::COMTypeInfo<nsConsoleService, void>::kIID = NS_ICONSOLESERVICE_IID;
 
 namespace mozilla {
 namespace dom {
 
 #ifdef MOZ_NUWA_PROCESS
+int32_t ContentParent::sNuwaPid = 0;
 bool ContentParent::sNuwaReady = false;
 #endif
 
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 
 class MemoryReportRequestParent : public PMemoryReportRequestParent
 {
 public:
@@ -584,16 +583,17 @@ ContentParent::RunNuwaProcess()
         new ContentParent(/* aApp = */ nullptr,
                           /* aOpener = */ nullptr,
                           /* aIsForBrowser = */ false,
                           /* aIsForPreallocated = */ true,
                           PROCESS_PRIORITY_BACKGROUND,
                           /* aIsNuwaProcess = */ true);
     nuwaProcess->Init();
 #ifdef MOZ_NUWA_PROCESS
+    sNuwaPid = nuwaProcess->Pid();
     sNuwaReady = false;
 #endif
     return nuwaProcess.forget();
 }
 
 // PreallocateAppProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within
 // GetNewOrPreallocatedAppProcess.
@@ -1987,16 +1987,17 @@ ContentParent::~ContentParent()
         // that sAppContentParents->Get(mAppManifestURL) != this.
         MOZ_ASSERT(!sAppContentParents ||
                    sAppContentParents->Get(mAppManifestURL) != this);
     }
 
 #ifdef MOZ_NUWA_PROCESS
     if (IsNuwaProcess()) {
         sNuwaReady = false;
+        sNuwaPid = 0;
     }
 #endif
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority,
                             bool aSetupOffMainThreadCompositing,
                             bool aSendRegisteredChrome)
@@ -2694,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*
@@ -3724,16 +3697,22 @@ ContentParent::DoSendAsyncMessage(JSCont
     ClonedMessageData data;
     if (!BuildClonedMessageDataForParent(this, aData, data)) {
         return false;
     }
     InfallibleTArray<CpowEntry> cpows;
     if (aCpows && !GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
         return false;
     }
+#ifdef MOZ_NUWA_PROCESS
+    if (IsNuwaProcess() && IsNuwaReady()) {
+        // Nuwa won't receive frame messages after it is frozen.
+        return true;
+    }
+#endif
     return SendAsyncMessage(nsString(aMessage), data, cpows, Principal(aPrincipal));
 }
 
 bool
 ContentParent::CheckPermission(const nsAString& aPermission)
 {
     return AssertAppProcessPermission(this, NS_ConvertUTF16toUTF8(aPermission).get());
 }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -74,16 +74,20 @@ class ContentParent MOZ_FINAL : public P
     typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
     typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent;
     typedef mozilla::ipc::TestShellParent TestShellParent;
     typedef mozilla::ipc::URIParams URIParams;
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
 public:
 #ifdef MOZ_NUWA_PROCESS
+    static int32_t NuwaPid() {
+        return sNuwaPid;
+    }
+
     static bool IsNuwaReady() {
         return sNuwaReady;
     }
 #endif
     virtual bool IsContentParent() MOZ_OVERRIDE { return true; }
     /**
      * Start up the content-process machinery.  This might include
      * scheduling pre-launch tasks.
@@ -661,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;
@@ -725,16 +724,17 @@ private:
 
 #ifdef MOZ_X11
     // Dup of child's X socket, used to scope its resources to this
     // object instead of the child process's lifetime.
     ScopedClose mChildXSocketFdDup;
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
+    static int32_t sNuwaPid;
     static bool sNuwaReady;
 #endif
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver {
--- 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/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -296,17 +296,16 @@ function RTCPeerConnection() {
   this._onGetStatsFailure = null;
   this._onReplaceTrackSender = null;
   this._onReplaceTrackWithTrack = null;
   this._onReplaceTrackSuccess = null;
   this._onReplaceTrackFailure = null;
 
   this._localType = null;
   this._remoteType = null;
-  this._trickleIce = false;
   this._peerIdentity = null;
 
   /**
    * Everytime we get a request from content, we put it in the queue. If there
    * are no pending operations though, we will execute it immediately. In
    * PeerConnectionObserver, whenever we are notified that an operation has
    * finished, we will check the queue for the next operation and execute if
    * neccesary. The _pending flag indicates whether an operation is currently in
@@ -321,17 +320,16 @@ RTCPeerConnection.prototype = {
   classDescription: "mozRTCPeerConnection",
   classID: PC_CID,
   contractID: PC_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
   init: function(win) { this._win = win; },
 
   __init: function(rtcConfig) {
-    this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice");
     if (!rtcConfig.iceServers ||
         !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
       rtcConfig.iceServers =
         JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"));
     }
     this._mustValidateRTCConfiguration(rtcConfig,
         "RTCPeerConnection constructor passed invalid RTCConfiguration");
     if (_globalPCList._networkdown) {
@@ -360,18 +358,17 @@ RTCPeerConnection.prototype = {
     // Add a reference to the PeerConnection to global list (before init).
     this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
     _globalPCList.addPC(this);
 
     this._queueOrRun({
       func: this._initialize,
       args: [rtcConfig],
-      // If not trickling, suppress start.
-      wait: !this._trickleIce
+      wait: false
     });
   },
 
   _initialize: function(rtcConfig) {
     this._impl.initialize(this._observer, this._win, rtcConfig,
                           Services.tm.currentThread);
     this._initIdp();
     _globalPCList.notifyLifecycleObservers(this, "initialized");
@@ -495,17 +492,21 @@ RTCPeerConnection.prototype = {
   // spec. See Bug 831756.
   _checkClosed: function() {
     if (this._closed) {
       throw new this._win.DOMError("", "Peer connection is closed");
     }
   },
 
   dispatchEvent: function(event) {
-    this.__DOM_IMPL__.dispatchEvent(event);
+    // PC can close while events are firing if there is an async dispatch
+    // in c++ land
+    if (!this._closed) {
+      this.__DOM_IMPL__.dispatchEvent(event);
+    }
   },
 
   // Log error message to web console and window.onerror, if present.
   logErrorAndCallOnError: function(msg, file, line) {
     this.logMsg(msg, file, line, Ci.nsIScriptError.exceptionFlag);
 
     // Safely call onerror directly if present (necessary for testing)
     try {
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1932,16 +1932,20 @@ PeerConnectionWrapper.prototype = {
    *
    * @param {object} candidate
    *        The mozRTCIceCandidate to be added or stored
    */
   storeOrAddIceCandidate : function PCW_storeOrAddIceCandidate(candidate) {
     var self = this;
 
     self._remote_ice_candidates.push(candidate);
+    if (self.signalingstate === 'closed') {
+      info("Received ICE candidate for closed PeerConnection - discarding");
+      return;
+    }
     if (self.remoteDescriptionSet) {
       self.addIceCandidate(candidate);
     } else {
       self._ice_candidates_to_add.push(candidate);
     }
   },
 
   /**
@@ -2561,25 +2565,19 @@ PeerConnectionWrapper.prototype = {
     }
     return false;
   },
 
   /**
    * Closes the connection
    */
   close : function PCW_close() {
-    // It might be that a test has already closed the pc. In those cases
-    // we should not fail.
-    try {
-      this._pc.close();
-      info(this + ": Closed connection.");
-    }
-    catch (e) {
-      info(this + ": Failure in closing connection - " + e.message);
-    }
+    this._ice_candidates_to_add = [];
+    this._pc.close();
+    info(this + ": Closed connection.");
   },
 
   /**
    * Register all events during the setup of the data channel
    *
    * @param {Function} onDataChannelOpened
    *        Callback to execute when the data channel has been opened
    */
--- a/dom/storage/DOMStorageIPC.cpp
+++ b/dom/storage/DOMStorageIPC.cpp
@@ -584,18 +584,25 @@ DOMStorageDBParent::RecvAsyncFlush()
 
 // DOMStorageObserverSink
 
 nsresult
 DOMStorageDBParent::Observe(const char* aTopic,
                             const nsACString& aScopePrefix)
 {
   if (mIPCOpen) {
-    mozilla::unused << SendObserve(nsDependentCString(aTopic),
-                                   nsCString(aScopePrefix));
+#ifdef MOZ_NUWA_PROCESS
+    if (!(static_cast<ContentParent*>(Manager())->IsNuwaProcess() &&
+          ContentParent::IsNuwaReady())) {
+#endif
+      mozilla::unused << SendObserve(nsDependentCString(aTopic),
+                                     nsCString(aScopePrefix));
+#ifdef MOZ_NUWA_PROCESS
+    }
+#endif
   }
 
   return NS_OK;
 }
 
 namespace { // anon
 
 // Results must be sent back on the main thread
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -73,18 +73,18 @@ interface AudioContext : EventTarget {
     [NewObject, Throws]
     PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag);
 
 };
 
 // Mozilla extensions
 partial interface AudioContext {
   // Read AudioChannel.webidl for more information about this attribute.
-  [Pref="media.useAudioChannelService", SetterThrows]
-  attribute AudioChannel mozAudioChannelType;
+  [Pref="media.useAudioChannelService"]
+  readonly attribute AudioChannel mozAudioChannelType;
 
   // These 2 events are dispatched when the AudioContext object is muted by
   // the AudioChannelService. It's call 'interrupt' because when this event is
   // dispatched on a HTMLMediaElement, the audio stream is paused.
   [Pref="media.useAudioChannelService"]
   attribute EventHandler onmozinterruptbegin;
 
   [Pref="media.useAudioChannelService"]
--- a/dom/webidl/TestInterfaceJS.webidl
+++ b/dom/webidl/TestInterfaceJS.webidl
@@ -37,9 +37,15 @@ interface TestInterfaceJS {
   (DOMString or TestInterfaceJS?) pingPongUnionContainingNull((TestInterfaceJS? or DOMString) something);
   (TestInterfaceJS or long)? pingPongNullableUnion((TestInterfaceJS or long)? something);
   (Location or TestInterfaceJS) returnBadUnion();
 
   [Cached, Pure]
   readonly attribute short cachedAttr;
   void setCachedAttr(short n);
   void clearCachedAttrCache();
+
+  // Test for sequence overloading and union behavior
+  void testSequenceOverload(sequence<DOMString> arg);
+  void testSequenceOverload(DOMString arg);
+
+  void testSequenceUnion((sequence<DOMString> or DOMString) arg);
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2200,17 +2200,17 @@ RuntimeService::CancelWorkersForWindow(n
     if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(aWindow))) {
       return;
     }
     JSContext* cx = jsapi.cx();
 
     for (uint32_t index = 0; index < workers.Length(); index++) {
       WorkerPrivate*& worker = workers[index];
 
-      if (worker->IsSharedWorker()) {
+      if (worker->IsSharedWorker() || worker->IsServiceWorker()) {
         worker->CloseSharedWorkersForWindow(aWindow);
       } else if (!worker->Cancel(cx)) {
         JS_ReportPendingException(cx);
       }
     }
   }
 }
 
--- a/dom/xslt/xslt/txStylesheet.cpp
+++ b/dom/xslt/xslt/txStylesheet.cpp
@@ -123,17 +123,17 @@ txStylesheet::findTemplate(const txXPath
     if (aImportedBy) {
         ImportFrame* curr = static_cast<ImportFrame*>(frameIter.next());
         while (curr != aImportedBy) {
                curr = static_cast<ImportFrame*>(frameIter.next());
         }
         endFrame = aImportedBy->mFirstNotImported;
     }
 
-#ifdef PR_LOGGING
+#if defined(PR_LOGGING) && defined(TX_TO_STRING)
     txPattern* match = 0;
 #endif
 
     ImportFrame* frame;
     while (!matchTemplate &&
            (frame = static_cast<ImportFrame*>(frameIter.next())) &&
            frame != endFrame) {
 
@@ -144,17 +144,17 @@ txStylesheet::findTemplate(const txXPath
         if (templates) {
             // Find template with highest priority
             uint32_t i, len = templates->Length();
             for (i = 0; i < len && !matchTemplate; ++i) {
                 MatchableTemplate& templ = (*templates)[i];
                 if (templ.mMatch->matches(aNode, aContext)) {
                     matchTemplate = templ.mFirstInstruction;
                     *aImportFrame = frame;
-#ifdef PR_LOGGING
+#if defined(PR_LOGGING) && defined(TX_TO_STRING)
                     match = templ.mMatch;
 #endif
                 }
             }
         }
     }
 
 #ifdef PR_LOGGING
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -59,18 +59,17 @@ MOZ_END_ENUM_CLASS(LayersBackend)
 
 MOZ_BEGIN_ENUM_CLASS(BufferMode, int8_t)
   BUFFER_NONE,
   BUFFERED
 MOZ_END_ENUM_CLASS(BufferMode)
 
 MOZ_BEGIN_ENUM_CLASS(DrawRegionClip, int8_t)
   DRAW,
-  DRAW_SNAPPED,
-  CLIP_NONE
+  NONE
 MOZ_END_ENUM_CLASS(DrawRegionClip)
 
 MOZ_BEGIN_ENUM_CLASS(SurfaceMode, int8_t)
   SURFACE_NONE = 0,
   SURFACE_OPAQUE,
   SURFACE_SINGLE_CHANNEL_ALPHA,
   SURFACE_COMPONENT_ALPHA
 MOZ_END_ENUM_CLASS(SurfaceMode)
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -690,17 +690,17 @@ RotatedContentBuffer::BeginPaint(Painted
     mBufferRotation = nsIntPoint(0,0);
   }
   NS_ASSERTION(canHaveRotation || mBufferRotation == nsIntPoint(0,0),
                "Rotation disabled, but we have nonzero rotation?");
 
   nsIntRegion invalidate;
   invalidate.Sub(aLayer->GetValidRegion(), destBufferRect);
   result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
-  result.mClip = DrawRegionClip::DRAW_SNAPPED;
+  result.mClip = DrawRegionClip::DRAW;
   result.mMode = mode;
 
   return result;
 }
 
 DrawTarget*
 RotatedContentBuffer::BorrowDrawTargetForPainting(PaintState& aPaintState,
                                                   DrawIterator* aIter /* = nullptr */)
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -224,17 +224,17 @@ public:
    * depend on the buffer type.  mDidSelfCopy is true if we kept our buffer
    * but used MovePixels() to shift its content.
    */
   struct PaintState {
     PaintState()
       : mRegionToDraw()
       , mRegionToInvalidate()
       , mMode(SurfaceMode::SURFACE_NONE)
-      , mClip(DrawRegionClip::CLIP_NONE)
+      , mClip(DrawRegionClip::NONE)
       , mContentType(gfxContentType::SENTINEL)
       , mDidSelfCopy(false)
     {}
 
     nsIntRegion mRegionToDraw;
     nsIntRegion mRegionToInvalidate;
     SurfaceMode mMode;
     DrawRegionClip mClip;
--- a/gfx/layers/basic/BasicPaintedLayer.cpp
+++ b/gfx/layers/basic/BasicPaintedLayer.cpp
@@ -86,17 +86,17 @@ BasicPaintedLayer::PaintThebes(gfxContex
                                             &needsClipToVisibleRegion);
         if (effectiveOperator != CompositionOp::OP_OVER) {
           needsClipToVisibleRegion = true;
         }
       } else {
         groupContext = aContext;
       }
       SetAntialiasingFlags(this, groupContext->GetDrawTarget());
-      aCallback(this, groupContext, toDraw, DrawRegionClip::CLIP_NONE, nsIntRegion(), aCallbackData);
+      aCallback(this, groupContext, toDraw, DrawRegionClip::NONE, nsIntRegion(), aCallbackData);
       if (needsGroup) {
         aContext->PopGroupToSource();
         if (needsClipToVisibleRegion) {
           gfxUtils::ClipToRegion(aContext, toDraw);
         }
         AutoSetOperator setOptimizedOperator(aContext, ThebesOp(effectiveOperator));
         PaintWithMask(aContext, opacity, aMaskLayer);
       }
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -438,30 +438,47 @@ ClientLayerManager::RunOverfillCallback(
 void
 ClientLayerManager::MakeSnapshotIfRequired()
 {
   if (!mShadowTarget) {
     return;
   }
   if (mWidget) {
     if (CompositorChild* remoteRenderer = GetRemoteRenderer()) {
+      // The compositor doesn't draw to a different sized surface
+      // when there's a rotation. Instead we rotate the result
+      // when drawing into dt
+      nsIntRect outerBounds;
+      mWidget->GetBounds(outerBounds);
+
       nsIntRect bounds = ToOutsideIntRect(mShadowTarget->GetClipExtents());
+      if (mTargetRotation) {
+        bounds = RotateRect(bounds, outerBounds, mTargetRotation);
+      }
+
       SurfaceDescriptor inSnapshot;
       if (!bounds.IsEmpty() &&
           mForwarder->AllocSurfaceDescriptor(bounds.Size().ToIntSize(),
                                              gfxContentType::COLOR_ALPHA,
                                              &inSnapshot) &&
           remoteRenderer->SendMakeSnapshot(inSnapshot, bounds)) {
         RefPtr<DataSourceSurface> surf = GetSurfaceForDescriptor(inSnapshot);
         DrawTarget* dt = mShadowTarget->GetDrawTarget();
+
         Rect dstRect(bounds.x, bounds.y, bounds.width, bounds.height);
         Rect srcRect(0, 0, bounds.width, bounds.height);
+
+        gfx::Matrix rotate = ComputeTransformForUnRotation(outerBounds, mTargetRotation);
+
+        gfx::Matrix oldMatrix = dt->GetTransform();
+        dt->SetTransform(oldMatrix * rotate);
         dt->DrawSurface(surf, dstRect, srcRect,
                         DrawSurfaceOptions(),
                         DrawOptions(1.0f, CompositionOp::OP_OVER));
+        dt->SetTransform(oldMatrix);
       }
       mForwarder->DestroySharedSurface(&inSnapshot);
     }
   }
   mShadowTarget = nullptr;
 }
 
 void
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -929,17 +929,17 @@ ClientTiledLayerBuffer::PaintThebes(cons
     if (PR_IntervalNow() - start > 3) {
       printf_stderr("Slow alloc %i\n", PR_IntervalNow() - start);
     }
     start = PR_IntervalNow();
 #endif
     PROFILER_LABEL("ClientTiledLayerBuffer", "PaintThebesSingleBufferDraw",
       js::ProfileEntry::Category::GRAPHICS);
 
-    mCallback(mPaintedLayer, ctxt, aPaintRegion, DrawRegionClip::CLIP_NONE, nsIntRegion(), mCallbackData);
+    mCallback(mPaintedLayer, ctxt, aPaintRegion, DrawRegionClip::NONE, nsIntRegion(), mCallbackData);
   }
 
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   if (PR_IntervalNow() - start > 30) {
     const nsIntRect bounds = aPaintRegion.GetBounds();
     printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
     if (aPaintRegion.IsComplex()) {
       printf_stderr("Complex region\n");
@@ -1299,17 +1299,17 @@ ClientTiledLayerBuffer::ValidateTile(Til
     ctxt->NewPath();
     ctxt->Clip(gfxRect(bounds.x, bounds.y, bounds.width, bounds.height));
     ctxt->SetMatrix(
       ctxt->CurrentMatrix().Translate(-unscaledTileOrigin.x,
                                       -unscaledTileOrigin.y).
                             Scale(mResolution, mResolution));
     mCallback(mPaintedLayer, ctxt,
               tileRegion.GetBounds(),
-              DrawRegionClip::CLIP_NONE,
+              DrawRegionClip::NONE,
               nsIntRegion(), mCallbackData);
 
   }
 
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   DrawDebugOverlay(drawTarget, aTileOrigin.x * mResolution,
                    aTileOrigin.y * mResolution, GetTileLength(), GetTileLength());
 #endif
--- a/gfx/layers/d3d9/PaintedLayerD3D9.cpp
+++ b/gfx/layers/d3d9/PaintedLayerD3D9.cpp
@@ -532,17 +532,17 @@ PaintedLayerD3D9::DrawRegion(nsIntRegion
     gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(destinationSurface,
                                                            IntSize(destinationSurface->GetSize().width,
                                                                    destinationSurface->GetSize().height));
 
   nsRefPtr<gfxContext> context = new gfxContext(dt);
 
   context->SetMatrix(context->CurrentMatrix().Translate(-bounds.x, -bounds.y));
   LayerManagerD3D9::CallbackInfo cbInfo = mD3DManager->GetCallbackInfo();
-  cbInfo.Callback(this, context, aRegion, DrawRegionClip::CLIP_NONE, nsIntRegion(), cbInfo.CallbackData);
+  cbInfo.Callback(this, context, aRegion, DrawRegionClip::NONE, nsIntRegion(), cbInfo.CallbackData);
 
   for (uint32_t i = 0; i < aReadbackUpdates.Length(); ++i) {
     NS_ASSERTION(aMode == SurfaceMode::SURFACE_OPAQUE,
                  "Transparent surfaces should not be used for readback");
     const ReadbackProcessor::Update& update = aReadbackUpdates[i];
     nsIntPoint offset = update.mLayer->GetBackgroundLayerOffset();
     nsRefPtr<gfxContext> ctx =
         update.mLayer->GetSink()->BeginUpdate(update.mUpdateRect + offset,
--- a/ipc/glue/MessageLink.cpp
+++ b/ipc/glue/MessageLink.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/ipc/MessageLink.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/dom/ContentParent.h"
 #endif
 
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsXULAppAPI.h"
 
@@ -65,16 +66,19 @@ MessageLink::~MessageLink()
 #endif
 }
 
 ProcessLink::ProcessLink(MessageChannel *aChan)
   : MessageLink(aChan)
   , mTransport(nullptr)
   , mIOLoop(nullptr)
   , mExistingListener(nullptr)
+#ifdef MOZ_NUWA_PROCESS
+  , mIsToNuwaProcess(false)
+#endif
 {
 }
 
 ProcessLink::~ProcessLink()
 {
 #ifdef DEBUG
     mTransport = nullptr;
     mIOLoop = nullptr;
@@ -163,16 +167,36 @@ ProcessLink::EchoMessage(Message *msg)
 }
 
 void
 ProcessLink::SendMessage(Message *msg)
 {
     mChan->AssertWorkerThread();
     mChan->mMonitor->AssertCurrentThreadOwns();
 
+#ifdef MOZ_NUWA_PROCESS
+    if (mIsToNuwaProcess && mozilla::dom::ContentParent::IsNuwaReady()) {
+        switch (msg->type()) {
+        case mozilla::dom::PContent::Msg_NuwaFork__ID:
+        case mozilla::dom::PContent::Reply_AddNewProcess__ID:
+        case mozilla::dom::PContent::Msg_NotifyPhoneStateChange__ID:
+        case GOODBYE_MESSAGE_TYPE:
+            break;
+        default:
+#ifdef DEBUG
+            MOZ_CRASH();
+#else
+            // In optimized build, message will be dropped.
+            printf_stderr("Sending message to frozen Nuwa");
+            return;
+#endif
+        }
+    }
+#endif
+
     mIOLoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(mTransport, &Transport::Send, msg));
 }
 
 void
 ProcessLink::SendClose()
 {
@@ -355,16 +379,20 @@ ProcessLink::OnChannelConnected(int32_t 
           mChan->mMonitor->Notify();
           notifyChannel = true;
         }
     }
 
     if (mExistingListener)
         mExistingListener->OnChannelConnected(peer_pid);
 
+#ifdef MOZ_NUWA_PROCESS
+    mIsToNuwaProcess = (peer_pid == mozilla::dom::ContentParent::NuwaPid());
+#endif
+
     if (notifyChannel) {
       mChan->OnChannelConnected(peer_pid);
     }
 }
 
 void
 ProcessLink::OnChannelError()
 {
--- a/ipc/glue/MessageLink.h
+++ b/ipc/glue/MessageLink.h
@@ -165,16 +165,19 @@ class ProcessLink
 
     virtual bool Unsound_IsClosed() const MOZ_OVERRIDE;
     virtual uint32_t Unsound_NumQueuedMessages() const MOZ_OVERRIDE;
 
   protected:
     Transport* mTransport;
     MessageLoop* mIOLoop;       // thread where IO happens
     Transport::Listener* mExistingListener; // channel's previous listener
+#ifdef MOZ_NUWA_PROCESS
+    bool mIsToNuwaProcess;
+#endif
 };
 
 class ThreadLink : public MessageLink
 {
   public:
     ThreadLink(MessageChannel *aChan, MessageChannel *aTargetChan);
     virtual ~ThreadLink();
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2011,16 +2011,22 @@ CheckSideEffects(ExclusiveContext *cx, B
                 /*
                  * Not a use of an unshadowed named function expression's given
                  * name, so this expression could invoke a getter that has side
                  * effects.
                  */
                 *answer = true;
             }
         }
+
+        if (pn->isHoistedLetUse()) {
+            // Hoisted uses of lexical bindings throw on access.
+            *answer = true;
+        }
+
         if (pn->isKind(PNK_DOT)) {
             /* Dotted property references in general can call getters. */
             *answer = true;
         }
         return CheckSideEffects(cx, bce, pn->maybeExpr(), answer);
 
       case PN_NULLARY:
         if (pn->isKind(PNK_DEBUGGER))
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/letTDZDelete.js
@@ -0,0 +1,23 @@
+function assertThrowsReferenceError(f) {
+  var e = null;
+  try {
+    f();
+  } catch (ex) {
+    e = ex;
+  }
+  assertEq(e instanceof ReferenceError, true);
+}
+
+assertThrowsReferenceError(function () { delete x; let x; });
+
+// FIXME do this unconditionally once bug 611388 lands.
+function constIsLexical() {
+  try {
+    (function () { z++; const z; })();
+    return false;
+  } catch (e) {
+    return true;
+  }
+}
+if (constIsLexical())
+  assertThrowsReferenceError(function () { delete x; const x; });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/letTDZEffectful.js
@@ -0,0 +1,24 @@
+function assertThrowsReferenceError(f) {
+  var e = null;
+  try {
+    f();
+  } catch (ex) {
+    e = ex;
+  }
+  assertEq(e instanceof ReferenceError, true);
+}
+
+// TDZ is effectful, don't optimize out x.
+assertThrowsReferenceError(function () { x; let x; });
+
+// FIXME do this unconditionally once bug 611388 lands.
+function constIsLexical() {
+  try {
+    (function () { z++; const z; })();
+    return false;
+  } catch (e) {
+    return true;
+  }
+}
+if (constIsLexical())
+  assertThrowsReferenceError(function () { x; const x; });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1070465.js
@@ -0,0 +1,5 @@
+// |jit-test| error: ReferenceError
+{
+  while (x && 0) {}
+  let x
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1073702.js
@@ -0,0 +1,10 @@
+try {
+  let x = ((function f(y) {
+    if (y > 0) {
+      f(-1)
+    }
+    x
+  })(1))
+} catch (e) {
+  assertEq(e instanceof ReferenceError, true);
+}
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -7906,23 +7906,38 @@ ICSetPropNativeAddCompiler::generateStub
     scratch = regs.takeAny();
 
     // Changing object shape.  Write the object's new shape.
     Address shapeAddr(objReg, JSObject::offsetOfShape());
     EmitPreBarrier(masm, shapeAddr, MIRType_Shape);
     masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
     masm.storePtr(scratch, shapeAddr);
 
-    // Change the object's type if required.
+    // Try to change the object's type.
     Label noTypeChange;
+
+    // Check if the cache has a new type to change to.
     masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewType()), scratch);
     masm.branchTestPtr(Assembler::Zero, scratch, scratch, &noTypeChange);
+
+    // Check if the old type still has a newScript.
+    masm.loadPtr(Address(objReg, JSObject::offsetOfType()), scratch);
+    masm.branchPtr(Assembler::Equal,
+                   Address(scratch, types::TypeObject::offsetOfNewScript()),
+                   ImmWord(0),
+                   &noTypeChange);
+
+    // Reload the new type from the cache.
+    masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewType()), scratch);
+
+    // Change the object's type.
     Address typeAddr(objReg, JSObject::offsetOfType());
     EmitPreBarrier(masm, typeAddr, MIRType_TypeObject);
     masm.storePtr(scratch, typeAddr);
+
     masm.bind(&noTypeChange);
 
     Register holderReg;
     regs.add(R0);
     regs.takeUnchecked(objReg);
     if (isFixedSlot_) {
         holderReg = objReg;
     } else {
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -2578,21 +2578,37 @@ GenerateAddSlot(JSContext *cx, MacroAsse
     Shape *newShape = obj->lastProperty();
     Address shapeAddr(object, JSObject::offsetOfShape());
     if (cx->zone()->needsIncrementalBarrier())
         masm.callPreBarrier(shapeAddr, MIRType_Shape);
     masm.storePtr(ImmGCPtr(newShape), shapeAddr);
 
     if (oldType != obj->type()) {
         // Changing object's type from a partially to fully initialized type,
-        // per the acquired properties analysis.
+        // per the acquired properties analysis. Only change the type if the
+        // old type still has a newScript.
+        Label noTypeChange, skipPop;
+
+        masm.push(object);
+        masm.loadPtr(Address(object, JSObject::offsetOfType()), object);
+        masm.branchPtr(Assembler::Equal,
+                       Address(object, types::TypeObject::offsetOfNewScript()),
+                       ImmWord(0),
+                       &noTypeChange);
+        masm.pop(object);
+
         Address typeAddr(object, JSObject::offsetOfType());
         if (cx->zone()->needsIncrementalBarrier())
             masm.callPreBarrier(typeAddr, MIRType_TypeObject);
         masm.storePtr(ImmGCPtr(obj->type()), typeAddr);
+
+        masm.jump(&skipPop);
+        masm.bind(&noTypeChange);
+        masm.pop(object);
+        masm.bind(&skipPop);
     }
 
     // Set the value on the object. Since this is an add, obj->lastProperty()
     // must be the shape of the property we are adding.
     if (obj->isFixedSlot(newShape->slot())) {
         Address addr(object, JSObject::getFixedSlotOffset(newShape->slot()));
         masm.storeConstantOrRegister(value, addr);
     } else {
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -568,25 +568,42 @@ jit::MakeSingletonTypeSet(types::Compile
     JS_ASSERT(constraints);
     types::TypeObjectKey *objType = types::TypeObjectKey::get(obj);
     objType->hasFlags(constraints, types::OBJECT_FLAG_UNKNOWN_PROPERTIES);
 
     LifoAlloc *alloc = GetIonContext()->temp->lifoAlloc();
     return alloc->new_<types::TemporaryTypeSet>(alloc, types::Type::ObjectType(obj));
 }
 
+static types::TemporaryTypeSet *
+MakeUnknownTypeSet()
+{
+    LifoAlloc *alloc = GetIonContext()->temp->lifoAlloc();
+    return alloc->new_<types::TemporaryTypeSet>(alloc, types::Type::UnknownType());
+}
+
 MConstant::MConstant(const js::Value &vp, types::CompilerConstraintList *constraints)
   : value_(vp)
 {
     setResultType(MIRTypeFromValue(vp));
     if (vp.isObject()) {
         // Create a singleton type set for the object. This isn't necessary for
         // other types as the result type encodes all needed information.
         setResultTypeSet(MakeSingletonTypeSet(constraints, &vp.toObject()));
     }
+    if (vp.isMagic() && vp.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
+        // JS_UNINITIALIZED_LEXICAL does not escape to script and is not
+        // observed in type sets. However, it may flow around freely during
+        // Ion compilation. Give it an unknown typeset to poison any type sets
+        // it merges with.
+        //
+        // TODO We could track uninitialized lexicals more precisely by tracking
+        // them in type sets.
+        setResultTypeSet(MakeUnknownTypeSet());
+    }
 
     setMovable();
 }
 
 MConstant::MConstant(JSObject *obj)
   : value_(ObjectValue(*obj))
 {
     setResultType(MIRType_Object);
--- a/js/src/jsapi-tests/README
+++ b/js/src/jsapi-tests/README
@@ -2,27 +2,29 @@
 
 The tests in this directory exercise the JSAPI.
 
 
 --- Building and running the tests
 
 If you built JS, you already built the tests.
 
-If you did `make check` in your JS objdir, you already ran them.
-
 The tests are built by default when you build JS. All the tests are compiled
 into a single binary named jsapi-tests. They all run in a single process.
 
+To run the tests:
+
+    cd $OBJDIR/dist/bin
+    ./jsapi-tests
+
 To run the tests in a debugger:
 
-    cd $OBJDIR/jsapi-tests
+    cd $OBJDIR/dist/bin
     gdb ./jsapi-tests
 
-
 --- Creating new tests
 
  1. You can either add to an existing test*.cpp file or make a new one.
     Copy an existing test and replace the body with your test code.
     The test harness provides `cx`, `rt`, and `global` for your use.
 
  2. If you made a new .cpp file, add it to the CPPSRCS list in Makefile.in.
 
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -24,16 +24,17 @@ UNIFIED_SOURCES += [
     'testDefineGetterSetterNonEnumerable.cpp',
     'testDefineProperty.cpp',
     'testDefinePropertyIgnoredAttributes.cpp',
     'testEnclosingFunction.cpp',
     'testErrorCopying.cpp',
     'testException.cpp',
     'testExternalStrings.cpp',
     'testFindSCCs.cpp',
+    'testForOfIterator.cpp',
     'testFreshGlobalEvalRedefinition.cpp',
     'testFuncCallback.cpp',
     'testFunctionProperties.cpp',
     'testGCAllocator.cpp',
     'testGCExactRooting.cpp',
     'testGCFinalizeCallback.cpp',
     'testGCHeapPostBarriers.cpp',
     'testGCOutOfMemory.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testForOfIterator.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* 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 "jsapi-tests/tests.h"
+
+BEGIN_TEST(testForOfIterator_basicNonIterable)
+{
+    JS::RootedValue v(cx);
+    // Hack to make it simple to produce an object that has a property
+    // named Symbol.iterator.
+    EVAL("var obj = { '@@iterator': 5, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; obj;", &v);
+    JS::ForOfIterator iter(cx);
+    bool ok = iter.init(v);
+    CHECK(!ok);
+    JS_ClearPendingException(cx);
+    return true;
+}
+END_TEST(testForOfIterator_basicNonIterable)
+
+BEGIN_TEST(testForOfIterator_bug515273_part1)
+{
+    JS::RootedValue v(cx);
+
+    // Hack to make it simple to produce an object that has a property
+    // named Symbol.iterator.
+    EVAL("var obj = { '@@iterator': 5, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; obj;", &v);
+
+    JS::ForOfIterator iter(cx);
+    bool ok = iter.init(v, JS::ForOfIterator::AllowNonIterable);
+    CHECK(!ok);
+    JS_ClearPendingException(cx);
+    return true;
+}
+END_TEST(testForOfIterator_bug515273_part1)
+
+BEGIN_TEST(testForOfIterator_bug515273_part2)
+{
+    JS::RootedObject obj(cx,
+			 JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    CHECK(obj);
+    JS::RootedValue v(cx, JS::ObjectValue(*obj));
+
+    JS::ForOfIterator iter(cx);
+    bool ok = iter.init(v, JS::ForOfIterator::AllowNonIterable);
+    CHECK(ok);
+    CHECK(!iter.valueIsIterable());
+    return true;
+}
+END_TEST(testForOfIterator_bug515273_part2)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5186,20 +5186,20 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForO
     explicit ForOfIterator(JSContext *cx) : cx_(cx), iterator(cx_), index(NOT_ARRAY) { }
 
     enum NonIterableBehavior {
         ThrowOnNonIterable,
         AllowNonIterable
     };
 
     /*
-     * Initialize the iterator.  If AllowNonIterable is passed then if iterable
-     * does not have a callable @@iterator init() will just return true instead
-     * of throwing.  Callers should then check valueIsIterable() before
-     * continuing with the iteration.
+     * Initialize the iterator.  If AllowNonIterable is passed then if getting
+     * the @@iterator property from iterable returns undefined init() will just
+     * return true instead of throwing.  Callers must then check
+     * valueIsIterable() before continuing with the iteration.
      */
     bool init(JS::HandleValue iterable,
               NonIterableBehavior nonIterableBehavior = ThrowOnNonIterable);
 
     /*
      * This method assumes that |iterator| is already an iterator.  It will not
      * check for, and call @@iterator.  Callers should make sure that the passed
      * in value is in fact an iterator.
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1231,16 +1231,20 @@ struct TypeObject : public gc::TenuredCe
     static inline uint32_t offsetOfClasp() {
         return offsetof(TypeObject, clasp_);
     }
 
     static inline uint32_t offsetOfProto() {
         return offsetof(TypeObject, proto_);
     }
 
+    static inline uint32_t offsetOfNewScript() {
+        return offsetof(TypeObject, newScript_);
+    }
+
   private:
     inline uint32_t basePropertyCount() const;
     inline void setBasePropertyCount(uint32_t count);
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(TypeObject, proto_) == offsetof(js::shadow::TypeObject, proto));
     }
 };
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -1361,23 +1361,27 @@ ForOfIterator::init(HandleValue iterable
     if (!args.init(0))
         return false;
     args.setThis(ObjectValue(*iterableObj));
 
     RootedValue callee(cx);
     if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee))
         return false;
 
-    // Throw if obj[@@iterator] isn't callable if we were asked to do so.
+    // If obj[@@iterator] is undefined and we were asked to allow non-iterables,
+    // bail out now without setting iterator.  This will make valueIsIterable(),
+    // which our caller should check, return false.
+    if (nonIterableBehavior == AllowNonIterable && callee.isUndefined())
+        return true;
+
+    // Throw if obj[@@iterator] isn't callable.
     // js::Invoke is about to check for this kind of error anyway, but it would
     // throw an inscrutable error message about |method| rather than this nice
     // one about |obj|.
     if (!callee.isObject() || !callee.toObject().isCallable()) {
-        if (nonIterableBehavior == AllowNonIterable)
-            return true;
         char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, NullPtr());
         if (!bytes)
             return false;
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes);
         js_free(bytes);
         return false;
     }
 
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -249,27 +249,30 @@ FetchName(JSContext *cx, HandleObject ob
         if (shape->isDataDescriptor() && shape->hasDefaultGetter()) {
             /* Fast path for Object instance properties. */
             JS_ASSERT(shape->hasSlot());
             vp.set(obj2->nativeGetSlot(shape->slot()));
         } else if (!NativeGet(cx, normalized, obj2, shape, vp)) {
             return false;
         }
     }
-    return true;
+
+    // NAME operations are the slow paths already, so unconditionally check
+    // for uninitialized lets.
+    return CheckUninitializedLexical(cx, name, vp);
 }
 
 inline bool
 FetchNameNoGC(JSObject *pobj, Shape *shape, MutableHandleValue vp)
 {
     if (!shape || !pobj->isNative() || !shape->isDataDescriptor() || !shape->hasDefaultGetter())
         return false;
 
     vp.set(pobj->nativeGetSlot(shape->slot()));
-    return true;
+    return !IsUninitializedLexical(vp);
 }
 
 inline bool
 GetIntrinsicOperation(JSContext *cx, jsbytecode *pc, MutableHandleValue vp)
 {
     RootedPropertyName name(cx, cx->currentScript()->getName(pc));
     return GlobalObject::getIntrinsicValue(cx, cx->global(), name, vp);
 }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -293,27 +293,19 @@ NameOperation(JSContext *cx, Interpreter
     RootedPropertyName nameRoot(cx, name);
     RootedShape shapeRoot(cx);
 
     if (!LookupName(cx, nameRoot, objRoot, &scopeRoot, &pobjRoot, &shapeRoot))
         return false;
 
     /* Kludge to allow (typeof foo == "undefined") tests. */
     JSOp op2 = JSOp(pc[JSOP_NAME_LENGTH]);
-    if (op2 == JSOP_TYPEOF) {
-        if (!FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
-            return false;
-    } else {
-        if (!FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
-            return false;
-    }
-
-    // NAME operations are the slow paths already, so unconditionally check
-    // for uninitialized lets.
-    return CheckUninitializedLexical(cx, nameRoot, vp);
+    if (op2 == JSOP_TYPEOF)
+        return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
+    return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
 }
 
 static inline bool
 SetPropertyOperation(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue lval,
                      HandleValue rval)
 {
     JS_ASSERT(*pc == JSOP_SETPROP);
 
@@ -3559,17 +3551,17 @@ js::GetScopeName(JSContext *cx, HandleOb
         if (AtomToPrintableString(cx, name, &printable))
             js_ReportIsNotDefined(cx, printable.ptr());
         return false;
     }
 
     if (!JSObject::getProperty(cx, obj, obj, name, vp))
         return false;
 
-    // See note in NameOperation.
+    // See note in FetchName.
     return CheckUninitializedLexical(cx, name, vp);
 }
 
 /*
  * Alternate form for NAME opcodes followed immediately by a TYPEOF,
  * which do not report an exception on (typeof foo == "undefined") tests.
  */
 bool
@@ -3584,17 +3576,17 @@ js::GetScopeNameForTypeOf(JSContext *cx,
     if (!shape) {
         vp.set(UndefinedValue());
         return true;
     }
 
     if (!JSObject::getProperty(cx, obj, obj, name, vp))
         return false;
 
-    // See note in NameOperation.
+    // See note in FetchName.
     return CheckUninitializedLexical(cx, name, vp);
 }
 
 JSObject *
 js::Lambda(JSContext *cx, HandleFunction fun, HandleObject parent)
 {
     MOZ_ASSERT(!fun->isArrow());
 
@@ -3869,16 +3861,23 @@ js::DeleteNameOperation(JSContext *cx, H
         return false;
 
     if (!scope) {
         // Return true for non-existent names.
         res.setBoolean(true);
         return true;
     }
 
+    // NAME operations are the slow paths already, so unconditionally check
+    // for uninitialized lets.
+    if (pobj == scope && IsUninitializedLexicalSlot(scope, shape)) {
+        ReportUninitializedLexical(cx, name);
+        return false;
+    }
+
     bool succeeded;
     RootedId id(cx, NameToId(name));
     if (!JSObject::deleteGeneric(cx, scope, id, &succeeded))
         return false;
     res.setBoolean(succeeded);
     return true;
 }
 
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -4366,17 +4366,17 @@ FrameLayerBuilder::PaintItems(nsTArray<C
 /**
  * Returns true if it is preferred to draw the list of display
  * items separately for each rect in the visible region rather
  * than clipping to a complex region.
  */
 static bool ShouldDrawRectsSeparately(gfxContext* aContext, DrawRegionClip aClip)
 {
   if (!gfxPrefs::LayoutPaintRectsSeparately() ||
-      aClip == DrawRegionClip::CLIP_NONE) {
+      aClip == DrawRegionClip::NONE) {
     return false;
   }
 
   DrawTarget *dt = aContext->GetDrawTarget();
   return dt->GetBackendType() == BackendType::DIRECT2D;
 }
 
 static void DrawForcedBackgroundColor(gfxContext* aContext, Layer* aLayer, nscolor aBackgroundColor)
@@ -4448,19 +4448,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
   PaintedDisplayItemLayerUserData* userData =
     static_cast<PaintedDisplayItemLayerUserData*>
       (aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
   NS_ASSERTION(userData, "where did our user data go?");
 
   bool shouldDrawRectsSeparately = ShouldDrawRectsSeparately(aContext, aClip);
 
   if (!shouldDrawRectsSeparately) {
-    if (aClip == DrawRegionClip::DRAW_SNAPPED) {
-      gfxUtils::ClipToRegionSnapped(aContext, aRegionToDraw);
-    } else if (aClip == DrawRegionClip::DRAW) {
+    if (aClip == DrawRegionClip::DRAW) {
       gfxUtils::ClipToRegion(aContext, aRegionToDraw);
     }
 
     DrawForcedBackgroundColor(aContext, aLayer, userData->mForcedBackgroundColor);
   }
 
   // make the origin of the context coincide with the origin of the
   // PaintedLayer
@@ -4481,17 +4479,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
   nsRefPtr<nsRenderingContext> rc = new nsRenderingContext();
   rc->Init(presContext->DeviceContext(), aContext);
 
   if (shouldDrawRectsSeparately) {
     nsIntRegionRectIterator it(aRegionToDraw);
     while (const nsIntRect* iterRect = it.Next()) {
       gfxContextAutoSaveRestore save(aContext);
       aContext->NewPath();
-      aContext->Rectangle(*iterRect, aClip == DrawRegionClip::DRAW_SNAPPED);
+      aContext->Rectangle(*iterRect);
       aContext->Clip();
 
       DrawForcedBackgroundColor(aContext, aLayer, userData->mForcedBackgroundColor);
 
       // Apply the residual transform if it has been enabled, to ensure that
       // snapping when we draw into aContext exactly matches the ideal transform.
       // See above for why this is OK.
       aContext->SetMatrix(
@@ -4517,19 +4515,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
                              entry->mCommonClipCount);
   }
 
   bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager();
 
   if (presContext->GetPaintFlashing() && isActiveLayerManager) {
     gfxContextAutoSaveRestore save(aContext);
     if (shouldDrawRectsSeparately) {
-      if (aClip == DrawRegionClip::DRAW_SNAPPED) {
-        gfxUtils::ClipToRegionSnapped(aContext, aRegionToDraw);
-      } else if (aClip == DrawRegionClip::DRAW) {
+      if (aClip == DrawRegionClip::DRAW) {
         gfxUtils::ClipToRegion(aContext, aRegionToDraw);
       }
     }
     FlashPaint(aContext);
   }
 
   if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
     nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -3064,16 +3064,26 @@ ElementRestyler::RestyleSelf(nsIFrame* a
         } else if (newContext->IsShared()) {
           LOG_RESTYLE("not swapping style structs, since the new context is "
                       "shared");
         } else {
           LOG_RESTYLE("swapping style structs between %p and %p",
                       oldContext.get(), newContext.get());
           oldContext->SwapStyleData(newContext, equalStructs);
           *aSwappedStructs |= equalStructs;
+#ifdef RESTYLE_LOGGING
+          uint32_t structs = RestyleManager::StructsToLog() & equalStructs;
+          if (structs) {
+            LOG_RESTYLE_INDENT();
+            LOG_RESTYLE("old style context now has: %s",
+                        oldContext->GetCachedStyleDataAsString(structs).get());
+            LOG_RESTYLE("new style context now has: %s",
+                        newContext->GetCachedStyleDataAsString(structs).get());
+          }
+#endif
         }
         LOG_RESTYLE("setting new style context");
         aSelf->SetStyleContext(newContext);
       }
     } else {
       LOG_RESTYLE("not setting new style context, since we'll reframe");
     }
   }
@@ -3631,16 +3641,49 @@ RestyleManager::ComputeStyleChangeFor(ns
         NS_ASSERTION(!cont->GetPrevContinuation(),
                      "continuing frame had more severe impact than first-in-flow");
         return;
       }
     }
   }
 }
 
+#ifdef RESTYLE_LOGGING
+uint32_t
+RestyleManager::StructsToLog()
+{
+  static bool initialized = false;
+  static uint32_t structs;
+  if (!initialized) {
+    structs = 0;
+    const char* value = getenv("MOZ_DEBUG_RESTYLE_STRUCTS");
+    if (value) {
+      nsCString s(value);
+      while (!s.IsEmpty()) {
+        int32_t index = s.FindChar(',');
+        nsStyleStructID sid;
+        bool found;
+        if (index == -1) {
+          found = nsStyleContext::LookupStruct(s, sid);
+          s.Truncate();
+        } else {
+          found = nsStyleContext::LookupStruct(Substring(s, 0, index), sid);
+          s = Substring(s, index + 1);
+        }
+        if (found) {
+          structs |= nsCachedStyleData::GetBitForSID(sid);
+        }
+      }
+    }
+    initialized = true;
+  }
+  return structs;
+}
+#endif
+
 #ifdef DEBUG
 /* static */ nsCString
 RestyleManager::RestyleHintToString(nsRestyleHint aHint)
 {
   nsCString result;
   bool any = false;
   const char* names[] = { "Self", "Subtree", "LaterSiblings", "CSSTransitions",
                           "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -381,16 +381,23 @@ public:
     return enabled;
   }
 
   static bool AnimationRestyleLoggingEnabled() {
     static bool animations = getenv("MOZ_DEBUG_RESTYLE_ANIMATIONS") != 0;
     return animations;
   }
 
+  // Set MOZ_DEBUG_RESTYLE_STRUCTS to a comma-separated string of
+  // style struct names -- such as "Font,SVGReset" -- to log the style context
+  // tree and those cached struct pointers before each restyle.  This
+  // function returns a bitfield of the structs named in the
+  // environment variable.
+  static uint32_t StructsToLog();
+
   static nsCString StructNamesToString(uint32_t aSIDs);
   int32_t& LoggingDepth() { return mLoggingDepth; }
 #endif
 
 private:
   /* aMinHint is the minimal change that should be made to the element */
   // XXXbz do we really need the aPrimaryFrame argument here?
   void RestyleElement(Element*        aElement,
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -157,17 +157,27 @@ RestyleTracker::ProcessOneRestyle(Elemen
   NS_PRECONDITION(aElement->GetCrossShadowCurrentDoc() == Document(),
                   "Element has unexpected document");
 
   LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
               RestyleManager::RestyleHintToString(aRestyleHint).get(),
               RestyleManager::ChangeHintToString(aChangeHint).get());
 
   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
   if (aRestyleHint & ~eRestyle_LaterSiblings) {
+#ifdef RESTYLE_LOGGING
+    if (ShouldLogRestyle() && primaryFrame &&
+        RestyleManager::StructsToLog() != 0) {
+      LOG_RESTYLE("style context tree before restyle:");
+      LOG_RESTYLE_INDENT();
+      primaryFrame->StyleContext()->LogStyleContextTree(
+          LoggingDepth(), RestyleManager::StructsToLog());
+    }
+#endif
     mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
                                     *this, aRestyleHint);
   } else if (aChangeHint &&
              (primaryFrame ||
               (aChangeHint & nsChangeHint_ReconstructFrame))) {
     // Don't need to recompute style; just apply the hint
     nsStyleChangeList changeList;
     changeList.AppendChange(primaryFrame, aElement, aChangeHint);
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -1219,30 +1219,31 @@ nsBidiPresUtils::ResolveParagraphWithinB
   ResolveParagraph(aBlockFrame, aBpd);
   aBpd->ResetData();
 }
 
 void
 nsBidiPresUtils::ReorderFrames(nsIFrame*   aFirstFrameOnLine,
                                int32_t     aNumFramesOnLine,
                                WritingMode aLineWM,
-                               nscoord&    aLineWidth)
+                               nscoord&    aLineWidth,
+                               nscoord     aStart)
 {
   // If this line consists of a line frame, reorder the line frame's children.
   if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) {
     aFirstFrameOnLine = aFirstFrameOnLine->GetFirstPrincipalChild();
     if (!aFirstFrameOnLine)
       return;
     // All children of the line frame are on the first line. Setting aNumFramesOnLine
     // to -1 makes InitLogicalArrayFromLine look at all of them.
     aNumFramesOnLine = -1;
   }
 
   BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
-  RepositionInlineFrames(&bld, aFirstFrameOnLine, aLineWM, aLineWidth);
+  RepositionInlineFrames(&bld, aFirstFrameOnLine, aLineWM, aLineWidth, aStart);
 }
 
 nsIFrame*
 nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame)
 {
   nsIFrame* firstLeaf = aFrame;
   while (!IsBidiLeaf(firstLeaf)) {
     nsIFrame* firstChild = firstLeaf->GetFirstPrincipalChild();
@@ -1274,30 +1275,32 @@ nsBidiPresUtils::GetFrameBaseLevel(nsIFr
     firstLeaf = firstLeaf->GetFirstPrincipalChild();
   }
   return NS_GET_BASE_LEVEL(firstLeaf);
 }
 
 void
 nsBidiPresUtils::IsFirstOrLast(nsIFrame*             aFrame,
                                nsContinuationStates* aContinuationStates,
+                               bool                  aSpanDirMatchesLineDir,
                                bool&                 aIsFirst /* out */,
                                bool&                 aIsLast /* out */)
 {
   /*
    * Since we lay out frames in the line's direction, visiting a frame with
    * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
    * of its continuation chain frames on the line.
    * To determine if it's the last visual frame of its continuation chain on
    * the line or not, we count the number of frames of the chain on the line,
    * and then reduce it when we lay out a frame of the chain. If this value
    * becomes 1 it means that it's the last visual frame of its continuation
    * chain on this line.
    */
 
+  bool firstInLineOrder, lastInLineOrder;
   nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame);
   nsFrameContinuationState* firstFrameState;
 
   if (!frameState->mFirstVisualFrame) {
     // aFrame is the first visual frame of its continuation chain
     nsFrameContinuationState* contState;
     nsIFrame* frame;
 
@@ -1322,26 +1325,40 @@ nsBidiPresUtils::IsFirstOrLast(nsIFrame*
     for (frame = aFrame->GetNextContinuation();
          frame && (contState = aContinuationStates->GetEntry(frame));
          frame = frame->GetNextContinuation()) {
       frameState->mFrameCount++;
       contState->mFirstVisualFrame = aFrame;
     }
     frameState->mHasContOnNextLines = (frame != nullptr);
 
-    aIsFirst = !frameState->mHasContOnPrevLines;
+    firstInLineOrder = true;
     firstFrameState = frameState;
   } else {
     // aFrame is not the first visual frame of its continuation chain
-    aIsFirst = false;
+    firstInLineOrder = false;
     firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame);
   }
 
-  aIsLast = (firstFrameState->mFrameCount == 1 &&
-             !firstFrameState->mHasContOnNextLines);
+  lastInLineOrder = (firstFrameState->mFrameCount == 1);
+
+  if (aSpanDirMatchesLineDir) {
+    aIsFirst = firstInLineOrder;
+    aIsLast = lastInLineOrder;
+  } else {
+    aIsFirst = lastInLineOrder;
+    aIsLast = firstInLineOrder;
+  }
+
+  if (frameState->mHasContOnPrevLines) {
+    aIsFirst = false;
+  }
+  if (firstFrameState->mHasContOnNextLines) {
+    aIsLast = false;
+  }
 
   if ((aIsFirst || aIsLast) &&
       (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
     // For ib splits, don't treat anything except the last part as
     // endmost or anything except the first part as startmost.
     // As an optimization, only get the first continuation once.
     nsIFrame* firstContinuation = aFrame->FirstContinuation();
     if (firstContinuation->FrameIsNonLastInIBSplit()) {
@@ -1351,72 +1368,81 @@ nsBidiPresUtils::IsFirstOrLast(nsIFrame*
     if (firstContinuation->FrameIsNonFirstInIBSplit()) {
       // We are not startmost
       aIsFirst = false;
     }
   }
 
   // Reduce number of remaining frames of the continuation chain on the line.
   firstFrameState->mFrameCount--;
+
+  nsInlineFrame* testFrame = do_QueryFrame(aFrame);
+
+  if (testFrame) {
+    aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
+
+    if (aIsFirst) {
+      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+    } else {
+      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+    }
+
+    if (aIsLast) {
+      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+    } else {
+      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+    }
+  }
 }
 
 void
 nsBidiPresUtils::RepositionFrame(nsIFrame*             aFrame,
                                  bool                  aIsEvenLevel,
                                  nscoord&              aStart,
                                  nsContinuationStates* aContinuationStates,
-                                 WritingMode           aLineWM,
-                                 nscoord&              aLineWidth)
+                                 WritingMode           aContainerWM,
+                                 nscoord&              aContainerWidth)
 {
   if (!aFrame)
     return;
 
   bool isFirst, isLast;
+  WritingMode frameWM = aFrame->GetWritingMode();
   IsFirstOrLast(aFrame,
                 aContinuationStates,
+                aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
                 isFirst /* out */,
                 isLast /* out */);
 
-  WritingMode frameWM = aFrame->GetWritingMode();
-  nsInlineFrame* testFrame = do_QueryFrame(aFrame);
-
-  if (testFrame) {
-    aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
+  // We only need the margin if the frame is first or last in its own
+  // writing mode, but we're traversing the frames in the order of the
+  // container's writing mode. To get the right values, we set start and
+  // end margins on a logical margin in the frame's writing mode, and
+  // then convert the margin to the container's writing mode to set the
+  // coordinates.
 
-    if (isFirst) {
-      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
-    } else {
-      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
-    }
-
-    if (isLast) {
-      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
-    } else {
-      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
-    }
-  }
   // This method is called from nsBlockFrame::PlaceLine via the call to
   // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
   // have been reflowed, which is required for GetUsedMargin/Border/Padding
-  LogicalMargin margin(aLineWM, aFrame->GetUsedMargin());
-  if (isFirst) {
-    aStart += margin.IStart(aLineWM);
+  LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
+  LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
+  if (!isFirst) {
+    frameMargin.IStart(frameWM) = 0;
+    borderPadding.IStart(frameWM) = 0;
   }
+  if (!isLast) {
+    frameMargin.IEnd(frameWM) = 0;
+    borderPadding.IEnd(frameWM) = 0;
+  }
+  LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
+  aStart += margin.IStart(aContainerWM);
 
   nscoord start = aStart;
-  nscoord frameISize = aFrame->ISize(aLineWM);
 
-  if (!IsBidiLeaf(aFrame))
-  {
-    nscoord iCoord = 0;
-    LogicalMargin borderPadding(frameWM, aFrame->GetUsedBorderAndPadding());
-    if (isFirst) {
-      iCoord += borderPadding.IStart(frameWM);
-    }
-
+  if (!IsBidiLeaf(aFrame)) {
     // If the resolved direction of the container is different from the
     // direction of the frame, we need to traverse the child list in reverse
     // order, to make it O(n) we store the list locally and iterate the list
     // in reverse
     bool reverseOrder = aIsEvenLevel != frameWM.IsBidiLTR();
     nsTArray<nsIFrame*> childList;
     nsIFrame *frame = aFrame->GetFirstPrincipalChild();
     if (frame && reverseOrder) {
@@ -1425,45 +1451,43 @@ nsBidiPresUtils::RepositionFrame(nsIFram
         childList.AppendElement(frame);
         frame = frame->GetNextSibling();
       }
       frame = childList[childList.Length() - 1];
     }
 
     // Reposition the child frames
     int32_t index = 0;
+    nscoord iCoord = borderPadding.IStart(frameWM);
+
     while (frame) {
       RepositionFrame(frame,
                       aIsEvenLevel,
                       iCoord,
                       aContinuationStates,
                       frameWM,
-                      frameISize);
+                      aFrame->GetLogicalSize(aContainerWM).Width(aContainerWM));
       index++;
       frame = reverseOrder ?
                 childList[childList.Length() - index - 1] :
                 frame->GetNextSibling();
     }
 
-    if (isLast) {
-      iCoord += borderPadding.IEnd(frameWM);
-    }
-    aStart += iCoord;
+    aStart += iCoord + borderPadding.IEnd(frameWM);
   } else {
-    aStart += frameISize;
+    aStart += aFrame->ISize(aContainerWM);
   }
 
-  LogicalRect logicalRect(aLineWM, aFrame->GetRect(), aLineWidth);
-  logicalRect.IStart(aLineWM) = start;
-  logicalRect.ISize(aLineWM) = aStart - start;
-  aFrame->SetRect(aLineWM, logicalRect, aLineWidth);
+  LogicalRect logicalRect = aFrame->GetLogicalRect(aContainerWM,
+                                                   aContainerWidth);
+  logicalRect.IStart(aContainerWM) = start;
+  logicalRect.ISize(aContainerWM) = aStart - start;
+  aFrame->SetRect(aContainerWM, logicalRect, aContainerWidth);
 
-  if (isLast) {
-    aStart += margin.IEnd(aLineWM);
-  }
+  aStart += margin.IEnd(aContainerWM);
 }
 
 void
 nsBidiPresUtils::InitContinuationStates(nsIFrame*              aFrame,
                                         nsContinuationStates*  aContinuationStates)
 {
   nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame);
   state->mFirstVisualFrame = nullptr;
@@ -1480,30 +1504,20 @@ nsBidiPresUtils::InitContinuationStates(
     }
   }
 }
 
 void
 nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld,
                                         nsIFrame* aFirstChild,
                                         WritingMode aLineWM,
-                                        nscoord& aLineWidth)
+                                        nscoord& aLineWidth,
+                                        nscoord& aStart)
 {
-  nscoord startSpace = 0;
-
-  // This method is called from nsBlockFrame::PlaceLine via the call to
-  // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
-  // have been reflowed, which is required for GetUsedMargin/Border/Padding
-  LogicalMargin margin(aLineWM, aFirstChild->GetUsedMargin());
-  if (!aFirstChild->GetPrevContinuation() &&
-      !aFirstChild->FrameIsNonFirstInIBSplit())
-    startSpace = margin.IStart(aLineWM);
-
-  nscoord start = LogicalRect(aLineWM, aFirstChild->GetRect(),
-                              aLineWidth).IStart(aLineWM) - startSpace;
+  nscoord start = aStart;
   nsIFrame* frame;
   int32_t count = aBld->mVisualFrames.Length();
   int32_t index;
   nsContinuationStates continuationStates;
 
   // Initialize continuation states to (nullptr, 0) for
   // each frame on the line.
   for (index = 0; index < count; index++) {
--- a/layout/base/nsBidiPresUtils.h
+++ b/layout/base/nsBidiPresUtils.h
@@ -155,17 +155,18 @@ public:
    * Reorder this line using Bidi engine.
    * Update frame array, following the new visual sequence.
    * 
    * @lina 05/02/2000
    */
   static void ReorderFrames(nsIFrame*            aFirstFrameOnLine,
                             int32_t              aNumFramesOnLine,
                             mozilla::WritingMode aLineWM,
-                            nscoord&             aLineWidth);
+                            nscoord&             aLineWidth,
+                            nscoord              aStart);
 
   /**
    * Format Unicode text, taking into account bidi capabilities
    * of the platform. The formatting includes: reordering, Arabic shaping,
    * symmetric and numeric swapping, removing control characters.
    *
    * @lina 06/18/2000 
    */
@@ -392,61 +393,72 @@ private:
    *                             OUT value will be the ending position of aFrame
    *                             (after adding its inline-end margin)
    * @param aContinuationStates  A map from nsIFrame* to nsFrameContinuationState
    */
   static void RepositionFrame(nsIFrame*              aFrame,
                               bool                   aIsEvenLevel,
                               nscoord&               aStart,
                               nsContinuationStates*  aContinuationStates,
-                              mozilla::WritingMode   aLineWM,
-                              nscoord&               aLineWidth);
+                              mozilla::WritingMode   aContainerWM,
+                              nscoord&               aContainerWidth);
 
   /*
    * Initialize the continuation state(nsFrameContinuationState) to
    * (nullptr, 0) for aFrame and its descendants.
    *
    * @param aFrame               The frame which itself and its descendants will
    *                             be initialized
    * @param aContinuationStates  A map from nsIFrame* to nsFrameContinuationState
    */
   static void InitContinuationStates(nsIFrame*              aFrame,
                                      nsContinuationStates*  aContinuationStates);
 
   /*
-   * Determine if aFrame is leftmost or rightmost, and set aIsLeftMost and
-   * aIsRightMost values. Also set continuation states of aContinuationStates.
+   * Determine if aFrame is first or last, and set aIsFirst and
+   * aIsLast values. Also set continuation states of
+   * aContinuationStates.
+   *
+   * A frame is first if it's the first appearance of its continuation
+   * chain on the line and the chain is on its first line.
+   * A frame is last if it's the last appearance of its continuation
+   * chain on the line and the chain is on its last line.
    *
-   * A frame is leftmost if it's the first appearance of its continuation chain
-   * on the line and the chain is on its first line if it's LTR or the chain is
-   * on its last line if it's RTL.
-   * A frame is rightmost if it's the last appearance of its continuation chain
-   * on the line and the chain is on its first line if it's RTL or the chain is
-   * on its last line if it's LTR.
+   * N.B: "First appearance" and "Last appearance" in the previous
+   * paragraph refer to the frame's inline direction, not necessarily
+   * the line's.
    *
-   * @param aContinuationStates  A map from nsIFrame* to nsFrameContinuationState
-   * @param[out] aIsLeftMost     TRUE means aFrame is leftmost frame or continuation
-   * @param[out] aIsRightMost    TRUE means aFrame is rightmost frame or continuation
+   * @param aContinuationStates        A map from nsIFrame* to
+   *                                    nsFrameContinuationState
+   * @param[in] aSpanDirMatchesLineDir TRUE means that the inline
+   *                                    direction of aFrame is the same
+   *                                    as its container
+   * @param[out] aIsFirst              TRUE means aFrame is first frame
+   *                                    or continuation
+   * @param[out] aIsLast               TRUE means aFrame is last frame
+   *                                    or continuation
    */
    static void IsFirstOrLast(nsIFrame*              aFrame,
                              nsContinuationStates*  aContinuationStates,
+                             bool                   aSpanInLineOrder /* in */,
                              bool&                  aIsFirst /* out */,
                              bool&                  aIsLast /* out */);
 
   /**
    *  Adjust frame positions following their visual order
    *
    *  @param aFirstChild the first kid
    *
    *  @lina 04/11/2000
    */
   static void RepositionInlineFrames(BidiLineData* aBld,
                                      nsIFrame* aFirstChild,
                                      mozilla::WritingMode aLineWM,
-                                     nscoord& aLineWidth);
+                                     nscoord& aLineWidth,
+                                     nscoord& aStart);
   
   /**
    * Helper method for Resolve()
    * Truncate a text frame to the end of a single-directional run and possibly
    * create a continuation frame for the remainder of its content.
    *
    * @param aFrame       the original frame
    * @param aNewFrame    [OUT] the new frame that was created
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -2593,30 +2593,32 @@ nsLineLayout::TextAlignLine(nsLineBox* a
 
       case NS_STYLE_TEXT_ALIGN_CENTER:
       case NS_STYLE_TEXT_ALIGN_MOZ_CENTER:
         dx = remainingISize / 2;
         break;
     }
   }
 
-  if (dx) {
+  if (mPresContext->BidiEnabled() &&
+      (!mPresContext->IsVisualMode() || !lineWM.IsBidiLTR())) {
+    nsBidiPresUtils::ReorderFrames(psd->mFirstFrame->mFrame,
+                                   aLine->GetChildCount(),
+                                   lineWM, mContainerWidth,
+                                   psd->mIStart + mTextIndent + dx);
+    if (dx) {
+      aLine->IndentBy(dx, mContainerWidth);
+    }
+  } else if (dx) {
     for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
       pfd->mBounds.IStart(lineWM) += dx;
       pfd->mFrame->SetRect(lineWM, pfd->mBounds, mContainerWidth);
     }
     aLine->IndentBy(dx, mContainerWidth);
   }
-
-  if (mPresContext->BidiEnabled() &&
-      (!mPresContext->IsVisualMode() || !lineWM.IsBidiLTR())) {
-    nsBidiPresUtils::ReorderFrames(psd->mFirstFrame->mFrame,
-                                   aLine->GetChildCount(),
-                                   lineWM, mContainerWidth);
-  }
 }
 
 void
 nsLineLayout::RelativePositionFrames(nsOverflowAreas& aOverflowAreas)
 {
   RelativePositionFrames(mRootSpan, aOverflowAreas);
 }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/1069941-inline-bidi-border-1-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test for bug 1069941 -- borders</title>
+</head>
+<body>
+<div dir="ltr">
+  <span dir="ltr" style="color:transparent;background:gray;border-left:10px solid teal;">+٥</span>
+</div>
+<div dir="ltr">
+  <span dir="ltr" style="color:transparent;background:gray;border-right:10px solid teal;">+٥</span>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/1069941-inline-bidi-border-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test for bug 1069941 -- borders</title>
+</head>
+<body>
+<div dir="ltr">
+  <span dir="rtl" style="color:transparent;background:gray;border-left:10px solid teal;">+٥</span>
+</div>
+<div dir="ltr">
+  <span dir="rtl" style="color:transparent;background:gray;border-right:10px solid teal;">+٥</span>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/1069941-inline-bidi-margin-1-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test for bug 1069941 -- margins</title>
+<style type="text/css">
+ .outer {
+   display: inline-block;
+   border: 1px solid lime;
+ }
+ .inner {
+   color:transparent;
+   margin-left: 50px;
+   border: 2px solid teal;
+ }
+</style>
+</head>
+<body>
+<span class="outer"><span class="inner">(12]</span></span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/1069941-inline-bidi-margin-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test for bug 1069941 -- margins</title>
+<style type="text/css">
+ .outer {
+   display: inline-block;
+   border: 1px solid lime;
+ }
+ .inner {
+   color:transparent;
+   margin-left: 50px;
+   border: 2px solid teal;
+ }
+</style>
+</head>
+<body>
+<span class="outer"><span class="inner" dir="rtl">[12)</span></span>
+</body>
+</html>
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -137,8 +137,11 @@ skip-if(B2G) == 726420-1.html 726420-1-r
 == 746987-3.html 746987-3-ref.html
 == 746987-4.html 746987-4-ref.html
 == 779003-1.html 779003-1-ref.html
 == 779003-1-dynamic.html 779003-1-ref.html
 == 847242-1.html 847242-1-ref.html
 skip-if(B2G&&browserIsRemote) == 869833-1.xul 869833-1-ref.xul
 == 922530-1.html 922530-1-ref.html
 == 922550-1.html 922550-1-ref.html
+== 1069941-inline-bidi-border-1.html 1069941-inline-bidi-border-1-ref.html
+== 1069941-inline-bidi-margin-1.html 1069941-inline-bidi-margin-1-ref.html
+
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -1043,16 +1043,31 @@ nsStyleContext::StructName(nsStyleStruct
     case eStyleStruct_##name_:                                                \
       return #name_;
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
     default:
       return "Unknown";
   }
 }
+
+/* static */ bool
+nsStyleContext::LookupStruct(const nsACString& aName, nsStyleStructID& aResult)
+{
+  if (false)
+    ;
+#define STYLE_STRUCT(name_, checkdata_cb_)                                    \
+  else if (aName.EqualsLiteral(#name_))                                       \
+    aResult = eStyleStruct_##name_;
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+  else
+    return false;
+  return true;
+}
 #endif
 
 bool
 nsStyleContext::HasSameCachedStyleData(nsStyleContext* aOther,
                                        nsStyleStructID aSID)
 {
   return GetCachedStyleData(aSID) == aOther->GetCachedStyleData(aSID);
 }
@@ -1157,8 +1172,101 @@ nsStyleContext::DoClearCachedInheritedSt
   }
 
   if (aStructs == 0) {
     return;
   }
 
   ClearCachedInheritedStyleDataOnDescendants(aStructs);
 }
+
+#ifdef RESTYLE_LOGGING
+nsCString
+nsStyleContext::GetCachedStyleDataAsString(uint32_t aStructs)
+{
+  nsCString structs;
+  for (nsStyleStructID i = nsStyleStructID(0);
+       i < nsStyleStructID_Length;
+       i = nsStyleStructID(i + 1)) {
+    if (aStructs & nsCachedStyleData::GetBitForSID(i)) {
+      const void* data = GetCachedStyleData(i);
+      if (!structs.IsEmpty()) {
+        structs.Append(' ');
+      }
+      structs.AppendPrintf("%s=%p", StructName(i), data);
+      if (HasCachedInheritedStyleData(i)) {
+        structs.AppendLiteral("(dependent)");
+      } else {
+        structs.AppendLiteral("(owned)");
+      }
+    }
+  }
+  return structs;
+}
+
+int32_t&
+nsStyleContext::LoggingDepth()
+{
+  static int32_t depth = 0;
+  return depth;
+}
+
+void
+nsStyleContext::LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs)
+{
+  LoggingDepth() = aLoggingDepth;
+  LogStyleContextTree(true, aStructs);
+}
+
+void
+nsStyleContext::LogStyleContextTree(bool aFirst, uint32_t aStructs)
+{
+  nsCString structs = GetCachedStyleDataAsString(aStructs);
+  if (!structs.IsEmpty()) {
+    structs.Append(' ');
+  }
+
+  nsCString pseudo;
+  if (mPseudoTag) {
+    nsAutoString pseudoTag;
+    mPseudoTag->ToString(pseudoTag);
+    AppendUTF16toUTF8(pseudoTag, pseudo);
+    pseudo.Append(' ');
+  }
+
+  nsCString flags;
+  if (IsStyleIfVisited()) {
+    flags.AppendLiteral("IS_STYLE_IF_VISITED ");
+  }
+  if (UsesGrandancestorStyle()) {
+    flags.AppendLiteral("USES_GRANDANCESTOR_STYLE ");
+  }
+  if (IsShared()) {
+    flags.AppendLiteral("IS_SHARED ");
+  }
+
+  nsCString parent;
+  if (aFirst) {
+    parent.AppendPrintf("parent=%p ", mParent);
+  }
+
+  LOG_RESTYLE("%p(%d) %s%s%s%s",
+              this, mRefCnt,
+              structs.get(), pseudo.get(), flags.get(), parent.get());
+
+  LOG_RESTYLE_INDENT();
+
+  if (nullptr != mChild) {
+    nsStyleContext* child = mChild;
+    do {
+      child->LogStyleContextTree(false, aStructs);
+      child = child->mNextSibling;
+    } while (mChild != child);
+  }
+  if (nullptr != mEmptyChild) {
+    nsStyleContext* child = mEmptyChild;
+    do {
+      child->LogStyleContextTree(false, aStructs);
+      child = child->mNextSibling;
+    } while (mEmptyChild != child);
+  }
+}
+#endif
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -3,16 +3,17 @@
  * 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/. */
 
 /* the interface (to internal code) for retrieving computed style data */
 
 #ifndef _nsStyleContext_h_
 #define _nsStyleContext_h_
 
+#include "mozilla/RestyleLogging.h"
 #include "nsRuleNode.h"
 #include "nsCSSPseudoElements.h"
 
 class nsIAtom;
 class nsPresContext;
 
 /**
  * An nsStyleContext represents the computed style data for an element.
@@ -392,16 +393,23 @@ public:
    * structs indicated in aStructs.
    */
   void ClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
 
 #ifdef DEBUG
   void List(FILE* out, int32_t aIndent);
   static void AssertStyleStructMaxDifferenceValid();
   static const char* StructName(nsStyleStructID aSID);
+  static bool LookupStruct(const nsACString& aName, nsStyleStructID& aResult);
+#endif
+
+#ifdef RESTYLE_LOGGING
+  nsCString GetCachedStyleDataAsString(uint32_t aStructs);
+  void LogStyleContextTree(int32_t aLoggingDepth, uint32_t aStructs);
+  int32_t& LoggingDepth();
 #endif
 
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~nsStyleContext();
 
   void AddChild(nsStyleContext* aChild);
   void RemoveChild(nsStyleContext* aChild);
@@ -445,16 +453,25 @@ private:
   // Helper for ClearCachedInheritedStyleDataOnDescendants.
   void DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
 
 #ifdef DEBUG
   void AssertStructsNotUsedElsewhere(nsStyleContext* aDestroyingContext,
                                      int32_t aLevels) const;
 #endif
 
+#ifdef RESTYLE_LOGGING
+  void LogStyleContextTree(bool aFirst, uint32_t aStructs);
+
+  // This only gets called under call trees where we've already checked
+  // that PresContext()->RestyleManager()->ShouldLogRestyle() returned true.
+  // It exists here just to satisfy LOG_RESTYLE's expectations.
+  bool ShouldLogRestyle() { return true; }
+#endif
+
   nsStyleContext* mParent; // STRONG
 
   // Children are kept in two circularly-linked lists.  The list anchor
   // is not part of the list (null for empty), and we point to the first
   // child.
   // mEmptyChild for children whose rule node is the root rule node, and
   // mChild for other children.  The order of children is not
   // meaningful.
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -159,16 +159,17 @@ class RemoteOptions(ReftestOptions):
         # httpd-path is specified by standard makefile targets and may be specified
         # on the command line to select a particular version of httpd.js. If not
         # specified, try to select the one from hostutils.zip, as required in bug 882932.
         if not options.httpdPath:
             options.httpdPath = os.path.join(options.utilityPath, "components")
 
         # Android does not run leak tests, but set some reasonable defaults to avoid errors.
         options.leakThresholds = {}
+        options.ignoreMissingLeaks = []
 
         # TODO: Copied from main, but I think these are no longer used in a post xulrunner world
         #options.xrePath = options.remoteTestRoot + self.automation._product + '/xulrunner'
         #options.utilityPath = options.testRoot + self.automation._product + '/bin'
         return options
 
 class ReftestServer:
     """ Web server used to serve Reftests, for closer fidelity to the real web.
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -339,17 +339,17 @@ class RefTest(object):
                                  cmdlineArgs,
                                  utilityPath = options.utilityPath,
                                  xrePath=options.xrePath,
                                  debuggerInfo=debuggerInfo,
                                  symbolsPath=options.symbolsPath,
                                  # give the JS harness 30 seconds to deal
                                  # with its own timeouts
                                  timeout=options.timeout + 30.0)
-      processLeakLog(self.leakLogFile, options.leakThresholds)
+      processLeakLog(self.leakLogFile, options.leakThresholds, options.ignoreMissingLeaks)
       self.automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
     finally:
       self.cleanup(profileDir)
     return status
 
   def copyExtraFilesToProfile(self, options, profile):
     "Copy extra files or dirs specified on the command line to the testing profile."
     profileDir = profile.profile
@@ -508,16 +508,18 @@ class ReftestOptions(OptionParser):
         self.error("cannot specify thisChunk or totalChunks with parallel tests")
       if options.focusFilterMode != "all":
         self.error("cannot specify focusFilterMode with parallel tests")
       if options.debugger is not None:
         self.error("cannot specify a debugger with parallel tests")
 
       options.leakThresholds = {"default": options.defaultLeakThreshold}
 
+      options.ignoreMissingLeaks = []
+
     return options
 
 def main():
   automation = Automation()
   parser = ReftestOptions(automation)
   reftest = RefTest(automation)
 
   options, args = parser.parse_args()
old mode 100644
new mode 100755
rename from media/libnestegg/README
rename to media/libnestegg/README.md
--- a/media/libnestegg/README
+++ b/media/libnestegg/README.md
@@ -1,6 +1,8 @@
+[![Build Status](https://travis-ci.org/kinetiknz/nestegg.svg?branch=master)](https://travis-ci.org/kinetiknz/nestegg)
+
 See INSTALL for build instructions.
 
 Licensed under an ISC-style license.  See LICENSE for details.
 
 The source under the halloc/ directory is licensed under a BSD license.  See
 halloc/halloc.h for details.
--- a/media/libnestegg/README_MOZILLA
+++ b/media/libnestegg/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the nestegg
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The nestegg git repository is: git://github.com/kinetiknz/nestegg.git
 
-The git commit ID used was 46ab96bcc8b099704cc8a15993f80fe0269a5284.
+The git commit ID used was 59220ae3e801cbad0f8160129c4df315469af671.
--- a/media/libnestegg/include/nestegg.h
+++ b/media/libnestegg/include/nestegg.h
@@ -376,16 +376,19 @@ int nestegg_has_cues(nestegg * context);
  * @retval 0 The file is not a WebM file.
  * @retval 1 The file is a WebM file. */
 int nestegg_sniff(unsigned char const * buffer, size_t length);
 
 /**
  * Set the underlying allocation function for library allocations.
  *
  * @param realloc_func The desired function.
+ * @retval 0 realloc_func(p, 0) does not act as free()
+ * @retval 1 realloc_func(p, 0) acts as free()
+ * @retval -1 malloc failed during realloc_func test
  */
-void nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t));
+int nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t));
 
 #if defined(__cplusplus)
 }
 #endif
 
 #endif /* NESTEGG_671cac2a_365d_ed69_d7a3_4491d3538d79 */
--- a/media/libnestegg/src/halloc.c
+++ b/media/libnestegg/src/halloc.c
@@ -41,33 +41,36 @@ typedef struct hblock
  */
 realloc_t halloc_allocator = NULL;
 
 #define allocator halloc_allocator
 
 /*
  *	static methods
  */
-static void _set_allocator(void);
+int halloc_set_allocator(realloc_t realloc_func);
 static void * _realloc(void * ptr, size_t n);
 
 static int  _relate(hblock_t * b, hblock_t * p);
 static void _free_children(hblock_t * p);
 
 /*
  *	Core API
  */
 void * halloc(void * ptr, size_t len)
 {
 	hblock_t * p;
 
 	/* set up default allocator */
 	if (! allocator)
 	{
-		_set_allocator();
+		if (halloc_set_allocator(realloc) == 0)
+		{
+			halloc_set_allocator(_realloc);
+		}
 		assert(allocator);
 	}
 
 	/* calloc */
 	if (! ptr)
 	{
 		if (! len)
 			return NULL;
@@ -167,42 +170,42 @@ char * h_strdup(const char * str)
 	size_t len = strlen(str);
 	char * ptr = halloc(0, len + 1);
 	return ptr ? (ptr[len] = 0, memcpy(ptr, str, len)) : NULL;
 }
 
 /*
  *	static stuff
  */
-static void _set_allocator(void)
+int halloc_set_allocator(realloc_t realloc_func)
 {
 	void * p;
 	assert(! allocator);
 	
 	/*
 	 *	the purpose of the test below is to check the behaviour
 	 *	of realloc(ptr, 0), which is defined in the standard
 	 *	as an implementation-specific. if it returns zero,
 	 *	then it's equivalent to free(). it can however return
 	 *	non-zero, in which case it cannot be used for freeing
 	 *	memory blocks and we'll need to supply our own version
 	 *
 	 *	Thanks to Stan Tobias for pointing this tricky part out.
 	 */
-	allocator = realloc;
-	if (! (p = malloc(1)))
+	if (! (p = realloc_func(NULL, 1)))
 		/* hmm */
-		return;
+		return -1;
 		
-	if ((p = realloc(p, 0)))
+	if ((p = realloc_func(p, 0)))
 	{
-		/* realloc cannot be used as free() */
-		allocator = _realloc;
-		free(p);
+		/* realloc_func cannot be used as free() */
+		return 0;
 	}
+	allocator = realloc_func;
+	return 1;
 }
 
 static void * _realloc(void * ptr, size_t n)
 {
 	/*
 	 *	free'ing realloc()
 	 */
 	if (n)
--- a/media/libnestegg/src/nestegg.c
+++ b/media/libnestegg/src/nestegg.c
@@ -316,19 +316,28 @@ struct frame {
 
 struct block_additional {
   unsigned int id;
   unsigned char * data;
   size_t length;
   struct block_additional * next;
 };
 
+#define NE_IO_BUFSZ 16384
+
+struct nestegg_io_buf {
+  nestegg_io io;
+  unsigned char buffer[NE_IO_BUFSZ];
+  size_t bufsz;
+  int offset;
+};
+
 /* Public (opaque) Structures */
 struct nestegg {
-  nestegg_io * io;
+  struct nestegg_io_buf * io;
   nestegg_log log;
   struct pool_ctx * alloc_pool;
   uint64_t last_id;
   uint64_t last_size;
   int last_valid;
   struct list_node * ancestor;
   struct ebml ebml;
   struct segment segment;
@@ -541,53 +550,127 @@ ne_pool_alloc(size_t size, struct pool_c
 
 static void *
 ne_alloc(size_t size)
 {
   return calloc(1, size);
 }
 
 static int
-ne_io_read(nestegg_io * io, void * buffer, size_t length)
+ne_io_read(struct nestegg_io_buf * io, void * buffer, size_t length)
 {
-  return io->read(buffer, length, io->userdata);
+  int64_t off;
+  int r;
+  size_t avail;
+
+  assert(io->offset == -1 || (io->offset >= 0 && (unsigned int) io->offset < io->bufsz));
+
+  /* Too big to buffer, invalidate buffer and read through */
+  if (length > io->bufsz) {
+    if (io->offset != -1) {
+      r = io->io.seek(-(io->bufsz - io->offset), NESTEGG_SEEK_CUR, io->io.userdata);
+      if (r != 0) {
+        return -1;
+      }
+    }
+    io->offset = -1;
+    return io->io.read(buffer, length, io->io.userdata);
+  }
+
+  /* Buffer invalid */
+  if (io->offset == -1) {
+    off = io->io.tell(io->io.userdata);
+    if (off == -1) {
+      return -1;
+    }
+    /* Refill buffer */
+    r = io->io.read(io->buffer, io->bufsz, io->io.userdata);
+    if (r != 1) {
+      /* Read truncated due to being within io->bufsz of EOS, reset read
+         position and switch to read through mode */
+      io->offset = -1;
+      io->bufsz = 0;
+      if (r == 0) {
+        r = io->io.seek(off, NESTEGG_SEEK_SET, io->io.userdata);
+      }
+      if (r == 0) {
+        return io->io.read(buffer, length, io->io.userdata);
+      }
+      return -1;
+    }
+    if (r == 1) {
+      io->offset = 0;
+    }
+  }
+
+  /* Service request with what we have */
+  avail = length;
+  if (io->bufsz - io->offset < length) {
+    avail = io->bufsz - io->offset;
+  }
+  memcpy(buffer, io->buffer + io->offset, avail);
+  io->offset += avail;
+
+  if ((unsigned int) io->offset == io->bufsz) {
+    io->offset = -1;
+  }
+
+  /* Still more to read, invalidate buffer and read more */
+  if (length - avail > 0) {
+    return ne_io_read(io, (char *) buffer + avail, length - avail);
+  }
+
+  return 1;
 }
 
 static int
-ne_io_seek(nestegg_io * io, int64_t offset, int whence)
+ne_io_seek(struct nestegg_io_buf * io, int64_t offset, int whence)
+{
+  /* Invalidate buffer */
+  io->offset = -1;
+
+  return io->io.seek(offset, whence, io->io.userdata);
+}
+
+static int64_t
+ne_io_tell(struct nestegg_io_buf * io)
 {
-  return io->seek(offset, whence, io->userdata);
+  int64_t off;
+
+  off = io->io.tell(io->io.userdata);
+  if (off == -1) {
+    return -1;
+  }
+  if (io->offset == -1) {
+    return off;
+  }
+  assert(off >= (int64_t) io->bufsz - io->offset);
+  return off - io->bufsz + (unsigned int) io->offset;
 }
 
 static int
-ne_io_read_skip(nestegg_io * io, size_t length)
+ne_io_read_skip(struct nestegg_io_buf * io, size_t length)
 {
   size_t get;
   unsigned char buf[8192];
   int r = 1;
 
   while (length > 0) {
     get = length < sizeof(buf) ? length : sizeof(buf);
     r = ne_io_read(io, buf, get);
     if (r != 1)
       break;
     length -= get;
   }
 
   return r;
 }
 
-static int64_t
-ne_io_tell(nestegg_io * io)
-{
-  return io->tell(io->userdata);
-}
-
 static int
-ne_bare_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length, enum vint_mask maskflag)
+ne_bare_read_vint(struct nestegg_io_buf * io, uint64_t * value, uint64_t * length, enum vint_mask maskflag)
 {
   int r;
   unsigned char b;
   size_t maxlen = 8;
   unsigned int count = 1, mask = 1 << 7;
 
   r = ne_io_read(io, &b, 1);
   if (r != 1)
@@ -614,29 +697,29 @@ ne_bare_read_vint(nestegg_io * io, uint6
     *value <<= 8;
     *value |= b;
   }
 
   return 1;
 }
 
 static int
-ne_read_id(nestegg_io * io, uint64_t * value, uint64_t * length)
+ne_read_id(struct nestegg_io_buf * io, uint64_t * value, uint64_t * length)
 {
   return ne_bare_read_vint(io, value, length, MASK_NONE);
 }
 
 static int
-ne_read_vint(nestegg_io * io, uint64_t * value, uint64_t * length)
+ne_read_vint(struct nestegg_io_buf * io, uint64_t * value, uint64_t * length)
 {
   return ne_bare_read_vint(io, value, length, MASK_FIRST_BIT);
 }
 
 static int
-ne_read_svint(nestegg_io * io, int64_t * value, uint64_t * length)
+ne_read_svint(struct nestegg_io_buf * io, int64_t * value, uint64_t * length)
 {
   int r;
   uint64_t uvalue;
   uint64_t ulength;
   int64_t svint_subtr[] = {
     0x3f, 0x1fff,
     0xfffff, 0x7ffffff,
     0x3ffffffffLL, 0x1ffffffffffLL,
@@ -648,17 +731,17 @@ ne_read_svint(nestegg_io * io, int64_t *
     return r;
   *value = uvalue - svint_subtr[ulength - 1];
   if (length)
     *length = ulength;
   return r;
 }
 
 static int
-ne_read_uint(nestegg_io * io, uint64_t * val, uint64_t length)
+ne_read_uint(struct nestegg_io_buf * io, uint64_t * val, uint64_t length)
 {
   unsigned char b;
   int r;
 
   if (length == 0 || length > 8)
     return -1;
   r = ne_io_read(io, &b, 1);
   if (r != 1)
@@ -670,44 +753,44 @@ ne_read_uint(nestegg_io * io, uint64_t *
       return r;
     *val <<= 8;
     *val |= b;
   }
   return 1;
 }
 
 static int
-ne_read_int(nestegg_io * io, int64_t * val, uint64_t length)
+ne_read_int(struct nestegg_io_buf * io, int64_t * val, uint64_t length)
 {
   int r;
   uint64_t uval, base;
 
   r = ne_read_uint(io, &uval, length);
   if (r != 1)
     return r;
 
   if (length < sizeof(int64_t)) {
     base = 1;
     base <<= length * 8 - 1;
     if (uval >= base) {
-        base = 1;
-        base <<= length * 8;
+      base = 1;
+      base <<= length * 8;
     } else {
       base = 0;
     }
     *val = uval - base;
   } else {
     *val = (int64_t) uval;
   }
 
   return 1;
 }
 
 static int
-ne_read_float(nestegg_io * io, double * val, uint64_t length)
+ne_read_float(struct nestegg_io_buf * io, double * val, uint64_t length)
 {
   union {
     uint64_t u;
     float f;
     double d;
   } value;
   int r;
 
@@ -731,19 +814,19 @@ ne_read_string(nestegg * ctx, char ** va
   int r;
 
   if (length > LIMIT_STRING)
     return -1;
   str = ne_pool_alloc(length + 1, ctx->alloc_pool);
   if (!str)
     return -1;
   if (length) {
-      r = ne_io_read(ctx->io, (unsigned char *) str, length);
-      if (r != 1)
-        return r;
+    r = ne_io_read(ctx->io, (unsigned char *) str, length);
+    if (r != 1)
+      return r;
   }
   str[length] = '\0';
   *val = str;
   return 1;
 }
 
 static int
 ne_read_binary(nestegg * ctx, struct ebml_binary * val, uint64_t length)
@@ -1013,18 +1096,19 @@ ne_read_simple(nestegg * ctx, struct ebm
   case TYPE_STRING:
     r = ne_read_string(ctx, &storage->v.s, length);
     break;
   case TYPE_BINARY:
     r = ne_read_binary(ctx, &storage->v.b, length);
     break;
   case TYPE_MASTER:
   case TYPE_UNKNOWN:
+  default:
+    r = 0;
     assert(0);
-    r = 0;
     break;
   }
 
   if (r == 1)
     storage->read = 1;
 
   return r;
 }
@@ -1131,17 +1215,17 @@ ne_xiph_lace_value(unsigned char ** np)
   }
 
   *np = p;
 
   return value;
 }
 
 static int
-ne_read_xiph_lace_value(nestegg_io * io, uint64_t * value, size_t * consumed)
+ne_read_xiph_lace_value(struct nestegg_io_buf * io, uint64_t * value, size_t * consumed)
 {
   int r;
   uint64_t lace;
 
   r = ne_read_uint(io, &lace, 1);
   if (r != 1)
     return r;
   *consumed += 1;
@@ -1154,17 +1238,17 @@ ne_read_xiph_lace_value(nestegg_io * io,
     *consumed += 1;
     *value += lace;
   }
 
   return 1;
 }
 
 static int
-ne_read_xiph_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes)
+ne_read_xiph_lacing(struct nestegg_io_buf * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes)
 {
   int r;
   size_t i = 0;
   uint64_t sum = 0;
 
   while (--n) {
     r = ne_read_xiph_lace_value(io, &sizes[i], read);
     if (r != 1)
@@ -1177,17 +1261,17 @@ ne_read_xiph_lacing(nestegg_io * io, siz
     return -1;
 
   /* Last frame is the remainder of the block. */
   sizes[i] = block - *read - sum;
   return 1;
 }
 
 static int
-ne_read_ebml_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes)
+ne_read_ebml_lacing(struct nestegg_io_buf * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes)
 {
   int r;
   uint64_t lace, sum, length;
   int64_t slace;
   size_t i = 0;
 
   r = ne_read_vint(io, &lace, &length);
   if (r != 1)
@@ -1787,61 +1871,61 @@ ne_init_cue_points(nestegg * ctx, int64_
  * sniff_buffer. */
 struct sniff_buffer {
   unsigned char const * buffer;
   size_t length;
   int64_t offset;
 };
 
 static int
-ne_buffer_read(void * buffer, size_t length, void * user_data)
+ne_buffer_read(void * buffer, size_t length, void * userdata)
 {
-  struct sniff_buffer * sb = user_data;
+  struct sniff_buffer * sb = userdata;
 
   int rv = 1;
   size_t available = sb->length - sb->offset;
 
   if (available < length)
     return 0;
 
   memcpy(buffer, sb->buffer + sb->offset, length);
   sb->offset += length;
 
   return rv;
 }
 
 static int
-ne_buffer_seek(int64_t offset, int whence, void * user_data)
+ne_buffer_seek(int64_t offset, int whence, void * userdata)
 {
-  struct sniff_buffer * sb = user_data;
+  struct sniff_buffer * sb = userdata;
   int64_t o = sb->offset;
 
   switch(whence) {
-    case NESTEGG_SEEK_SET:
-      o = offset;
-      break;
-    case NESTEGG_SEEK_CUR:
-      o += offset;
-      break;
-    case NESTEGG_SEEK_END:
-      o = sb->length + offset;
-      break;
+  case NESTEGG_SEEK_SET:
+    o = offset;
+    break;
+  case NESTEGG_SEEK_CUR:
+    o += offset;
+    break;
+  case NESTEGG_SEEK_END:
+    o = sb->length + offset;
+    break;
   }
 
   if (o < 0 || o > (int64_t) sb->length)
     return -1;
 
   sb->offset = o;
   return 0;
 }
 
 static int64_t
-ne_buffer_tell(void * user_data)
+ne_buffer_tell(void * userdata)
 {
-  struct sniff_buffer * sb = user_data;
+  struct sniff_buffer * sb = userdata;
   return sb->offset;
 }
 
 static int
 ne_match_webm(nestegg_io io, int64_t max_offset)
 {
   int r;
   uint64_t id;
@@ -1855,17 +1939,19 @@ ne_match_webm(nestegg_io io, int64_t max
   if (!ctx)
     return -1;
 
   ctx->io = ne_alloc(sizeof(*ctx->io));
   if (!ctx->io) {
     nestegg_destroy(ctx);
     return -1;
   }
-  *ctx->io = io;
+  ctx->io->io = io;
+  ctx->io->bufsz = NE_IO_BUFSZ;
+  ctx->io->offset = -1;
   ctx->alloc_pool = ne_pool_init();
   if (!ctx->alloc_pool) {
     nestegg_destroy(ctx);
     return -1;
   }
   ctx->log = ne_null_log_callback;
 
   r = ne_peek_element(ctx, &id, NULL);
@@ -1913,17 +1999,19 @@ nestegg_init(nestegg ** context, nestegg
   if (!ctx)
     return -1;
 
   ctx->io = ne_alloc(sizeof(*ctx->io));
   if (!ctx->io) {
     nestegg_destroy(ctx);
     return -1;
   }
-  *ctx->io = io;
+  ctx->io->io = io;
+  ctx->io->bufsz = NE_IO_BUFSZ;
+  ctx->io->offset = -1;
   ctx->log = callback;
   ctx->alloc_pool = ne_pool_init();
   if (!ctx->alloc_pool) {
     nestegg_destroy(ctx);
     return -1;
   }
 
   if (!ctx->log)
@@ -2259,45 +2347,45 @@ nestegg_track_codec_data(nestegg * ctx, 
   *data = NULL;
   *length = 0;
 
   entry = ne_find_track_entry(ctx, track);
   if (!entry)
     return -1;
 
   if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS
-    && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS)
+      && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS)
     return -1;
 
   if (ne_get_binary(entry->codec_private, &codec_private) != 0)
     return -1;
 
   if (nestegg_track_codec_id(ctx, track) == NESTEGG_CODEC_VORBIS) {
-      p = codec_private.data;
-      count = *p++ + 1;
-
-      if (count > 3)
+    p = codec_private.data;
+    count = *p++ + 1;
+
+    if (count > 3)
+      return -1;
+
+    i = 0;
+    total = 0;
+    while (--count) {
+      sizes[i] = ne_xiph_lace_value(&p);
+      total += sizes[i];
+      i += 1;
+    }
+    sizes[i] = codec_private.length - total - (p - codec_private.data);
+
+    for (i = 0; i < item; ++i) {
+      if (sizes[i] > LIMIT_FRAME)
         return -1;
-
-      i = 0;
-      total = 0;
-      while (--count) {
-        sizes[i] = ne_xiph_lace_value(&p);
-        total += sizes[i];
-        i += 1;
-      }
-      sizes[i] = codec_private.length - total - (p - codec_private.data);
-
-      for (i = 0; i < item; ++i) {
-        if (sizes[i] > LIMIT_FRAME)
-          return -1;
-        p += sizes[i];
-      }
-      *data = p;
-      *length = sizes[item];
+      p += sizes[i];
+    }
+    *data = p;
+    *length = sizes[item];
   } else {
     *data = codec_private.data;
     *length = codec_private.length;
   }
 
   return 0;
 }
 
@@ -2489,17 +2577,17 @@ nestegg_free_packet(nestegg_packet * pkt
 
   while (pkt->block_additional) {
     block_additional = pkt->block_additional;
     pkt->block_additional = block_additional->next;
     free(block_additional->data);
     free(block_additional);
   }
 
- free(pkt);
+  free(pkt);
 }
 
 int
 nestegg_packet_track(nestegg_packet * pkt, unsigned int * track)
 {
   *track = pkt->track;
   return 0;
 }
@@ -2583,33 +2671,36 @@ nestegg_packet_additional_data(nestegg_p
 
   return -1;
 }
 
 int
 nestegg_has_cues(nestegg * ctx)
 {
   return ctx->segment.cues.cue_point.head ||
-         ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES);
+    ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES);
 }
 
 int
 nestegg_sniff(unsigned char const * buffer, size_t length)
 {
   nestegg_io io;
-  struct sniff_buffer user_data;
-
-  user_data.buffer = buffer;
-  user_data.length = length;
-  user_data.offset = 0;
+  struct sniff_buffer userdata;
+
+  userdata.buffer = buffer;
+  userdata.length = length;
+  userdata.offset = 0;
 
   io.read = ne_buffer_read;
   io.seek = ne_buffer_seek;
   io.tell = ne_buffer_tell;
-  io.userdata = &user_data;
+  io.userdata = &userdata;
   return ne_match_webm(io, length);
 }
 
-void
+/* From halloc.c */
+int halloc_set_allocator(realloc_t realloc_func);
+
+int
 nestegg_set_halloc_func(void * (* realloc_func)(void *, size_t))
 {
-  halloc_allocator = realloc_func;
+  return halloc_set_allocator(realloc_func);
 }
--- a/media/libnestegg/update.sh
+++ b/media/libnestegg/update.sh
@@ -2,17 +2,17 @@
 cp $1/include/nestegg/nestegg.h include
 cp $1/src/nestegg.c src
 cp $1/halloc/halloc.h src
 cp $1/halloc/src/align.h src
 cp $1/halloc/src/halloc.c src
 cp $1/halloc/src/hlist.h src
 cp $1/halloc/src/macros.h src
 cp $1/LICENSE .
-cp $1/README .
+cp $1/README.md .
 cp $1/AUTHORS .
 if [ -d $1/.git ]; then
   rev=$(cd $1 && git rev-parse --verify HEAD)
   dirty=$(cd $1 && git diff-index --name-only HEAD)
 fi
 
 if [ -n "$rev" ]; then
   version=$rev
--- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
+++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
@@ -2544,22 +2544,22 @@ cc_boolean vcmCheckAttribs(cc_uint32_t m
             rcap->max_fs = t_uint;
         }
 
         if ( ccsdpAttrGetFmtpMaxCpb(sdp_p, level, 0, fmtp_inst, &t_uint) == SDP_SUCCESS )
         {
             rcap->max_cpb = t_uint;
         }
 
-        if ( ccsdpAttrGetFmtpMaxCpb(sdp_p, level, 0, fmtp_inst, &t_uint) == SDP_SUCCESS )
+        if ( ccsdpAttrGetFmtpMaxDpb(sdp_p, level, 0, fmtp_inst, &t_uint) == SDP_SUCCESS )
         {
             rcap->max_dpb = t_uint;
         }
 
-        if ( ccsdpAttrGetFmtpMaxCpb(sdp_p, level, 0, fmtp_inst, &t_uint) == SDP_SUCCESS )
+        if ( ccsdpAttrGetFmtpMaxBr(sdp_p, level, 0, fmtp_inst, &t_uint) == SDP_SUCCESS )
         {
             rcap->max_br = t_uint;
         }
 
         rcap->tias_bw = ccsdpGetBandwidthValue(sdp_p, level, fmtp_inst);
         if ( rcap->tias_bw == 0 )
         {
             // received bandwidth of 0 reject this
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1119,16 +1119,17 @@ public abstract class GeckoApp
             enableStrictMode();
         }
 
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
         mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
 
         final Intent intent = getIntent();
+        final String action = intent.getAction();
         final String args = intent.getStringExtra("args");
 
         earlyStartJavaSampler(intent);
 
         // GeckoLoader wants to dig some environment variables out of the
         // incoming intent, so pass it in here. GeckoLoader will do its
         // business later and dispose of the reference.
         GeckoLoader.setLastIntent(intent);
@@ -1158,17 +1159,23 @@ public abstract class GeckoApp
                 }
 
                 if (profileName != null || profilePath != null) {
                     mProfile = GeckoProfile.get(this, profileName, profilePath);
                 }
             }
         }
 
-        BrowserDB.initialize(getProfile().getName());
+        // Speculatively pre-fetch the profile in the background.
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                getProfile();
+            }
+        });
 
         // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
         try {
             Class.forName("android.os.AsyncTask");
         } catch (ClassNotFoundException e) {}
 
         MemoryMonitor.getInstance().init(getApplicationContext());
 
@@ -1200,16 +1207,40 @@ public abstract class GeckoApp
             return;
         }
 
         if (GeckoThread.isCreated()) {
             // This happens when the GeckoApp activity is destroyed by Android
             // without killing the entire application (see Bug 769269).
             mIsRestoringActivity = true;
             Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
+
+        } else {
+            final String uri = getURIFromIntent(intent);
+
+            GeckoThread.setArgs(args);
+            GeckoThread.setAction(action);
+            GeckoThread.setUri(TextUtils.isEmpty(uri) ? null : uri);
+        }
+
+        if (!ACTION_DEBUG.equals(action) &&
+                GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching,
+                                                   GeckoThread.LaunchState.Launched)) {
+            GeckoThread.createAndStart();
+
+        } else if (ACTION_DEBUG.equals(action) &&
+                GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching,
+                                                   GeckoThread.LaunchState.WaitForDebugger)) {
+            ThreadUtils.getUiHandler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    GeckoThread.setLaunchState(GeckoThread.LaunchState.Launched);
+                    GeckoThread.createAndStart();
+                }
+            }, 1000 * 5 /* 5 seconds */);
         }
 
         Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
         if (stateBundle != null) {
             // Use the state bundle if it was given as an intent extra. This is
             // only intended to be used internally via Robocop, so a boolean
             // is read from a private shared pref to prevent other apps from
             // injecting states.
@@ -1363,16 +1394,19 @@ public abstract class GeckoApp
             }
         }
 
         if (mLayerView == null) {
             LayerView layerView = (LayerView) findViewById(R.id.layer_view);
             layerView.initializeView(EventDispatcher.getInstance());
             mLayerView = layerView;
             GeckoAppShell.setLayerView(layerView);
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createObjectEvent(
+                GeckoEvent.ACTION_OBJECT_LAYER_CLIENT, layerView.getLayerClientObject()));
+
             // bind the GeckoEditable instance to the new LayerView
             GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
         }
     }
 
     /**
      * Loads the initial tab at Fennec startup.
      *
@@ -1420,16 +1454,18 @@ public abstract class GeckoApp
         // Start migrating as early as possible, can do this in
         // parallel with Gecko load.
         checkMigrateProfile();
 
         Tabs.registerOnTabsChangedListener(this);
 
         initializeChrome();
 
+        BrowserDB.initialize(getProfile().getName());
+
         // If we are doing a restore, read the session data and send it to Gecko
         if (!mIsRestoringActivity) {
             String restoreMessage = null;
             if (mShouldRestore) {
                 try {
                     // restoreSessionTabs() will create simple tab stubs with the
                     // URL and title for each page, but we also need to restore
                     // session history. restoreSessionTabs() will inject the IDs
@@ -1462,35 +1498,16 @@ public abstract class GeckoApp
         // If we're not restoring, move the session file so it can be read for
         // the last tabs section.
         if (!mShouldRestore) {
             getProfile().moveSessionFile();
         }
 
         Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
 
-        if (!mIsRestoringActivity) {
-            GeckoThread.setArgs(intent.getStringExtra("args"));
-            GeckoThread.setAction(intent.getAction());
-            GeckoThread.setUri(passedUri);
-        }
-        if (!ACTION_DEBUG.equals(action) &&
-            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
-            GeckoThread.createAndStart();
-        } else if (ACTION_DEBUG.equals(action) &&
-            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) {
-            ThreadUtils.getUiHandler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching);
-                    GeckoThread.createAndStart();
-                }
-            }, 1000 * 5 /* 5 seconds */);
-        }
-
         // Check if launched from data reporting notification.
         if (ACTION_LAUNCH_SETTINGS.equals(action)) {
             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
             // Copy extras.
             settingsIntent.putExtras(intent);
             startActivity(settingsIntent);
         }
 
@@ -1575,17 +1592,16 @@ public abstract class GeckoApp
         }, 50);
 
         if (mIsRestoringActivity) {
             GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
             if (selectedTab != null)
                 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
             geckoConnected();
-            GeckoAppShell.setLayerClient(mLayerView.getLayerClientObject());
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
         }
 
         if (ACTION_ALERT_CALLBACK.equals(action)) {
             processAlertCallback(intent);
         } else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
             NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
         }
@@ -1642,17 +1658,17 @@ public abstract class GeckoApp
             restoreData.put("sessionString", sessionString);
             return restoreData.toString();
 
         } catch (JSONException e) {
             throw new SessionRestoreException(e);
         }
     }
 
-    public GeckoProfile getProfile() {
+    public synchronized GeckoProfile getProfile() {
         // fall back to default profile if we didn't load a specific one
         if (mProfile == null) {
             mProfile = GeckoProfile.get(this);
         }
         return mProfile;
     }
 
     /**
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -200,18 +200,16 @@ public class GeckoAppShell
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void registerJavaUiThread();
     public static native void nativeInit();
 
     // helper methods
-    //    public static native void setSurfaceView(GeckoSurfaceView sv);
-    public static native void setLayerClient(Object client);
     public static native void onResume();
     public static void callObserver(String observerKey, String topic, String data) {
         sendEventToGecko(GeckoEvent.createCallObserverEvent(observerKey, topic, data));
     }
     public static void removeObserver(String observerKey) {
         sendEventToGecko(GeckoEvent.createRemoveObserverEvent(observerKey));
     }
     public static native Message getNextMessageFromQueue(MessageQueue queue);
@@ -337,19 +335,16 @@ public class GeckoAppShell
                 return true;
             }
         };
         Looper.myQueue().addIdleHandler(idleHandler);
 
         // run gecko -- it will spawn its own thread
         GeckoAppShell.nativeInit();
 
-        if (sLayerView != null)
-            GeckoAppShell.setLayerClient(sLayerView.getLayerClientObject());
-
         // First argument is the .apk path
         String combinedArgs = apkPath + " -greomni " + apkPath;
         if (args != null)
             combinedArgs += " " + args;
         if (url != null)
             combinedArgs += " -url " + url;
         if (type != null)
             combinedArgs += " " + type;
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -71,16 +71,17 @@ public class GeckoEvent {
     // Make sure to keep these values in sync with the enum in
     // AndroidGeckoEvent in widget/android/AndroidJavaWrappers.h
     @JNITarget
     private enum NativeGeckoEvent {
         NATIVE_POKE(0),
         KEY_EVENT(1),
         MOTION_EVENT(2),
         SENSOR_EVENT(3),
+        PROCESS_OBJECT(4),
         LOCATION_EVENT(5),
         IME_EVENT(6),
         SIZE_CHANGED(8),
         APP_BACKGROUNDING(9),
         APP_FOREGROUNDING(10),
         LOAD_URI(12),
         NOOP(15),
         BROADCAST(19),
@@ -178,16 +179,18 @@ public class GeckoEvent {
     public static final int ACTION_MAGNIFY_END = 13;
 
     public static final int ACTION_GAMEPAD_ADDED = 1;
     public static final int ACTION_GAMEPAD_REMOVED = 2;
 
     public static final int ACTION_GAMEPAD_BUTTON = 1;
     public static final int ACTION_GAMEPAD_AXES = 2;
 
+    public static final int ACTION_OBJECT_LAYER_CLIENT = 1;
+
     private final int mType;
     private int mAction;
     private boolean mAckNeeded;
     private long mTime;
     private Point[] mPoints;
     private int[] mPointIndicies;
     private int mPointerIndex; // index of the point that has changed
     private float[] mOrientations;
@@ -240,16 +243,18 @@ public class GeckoEvent {
     private int mID;
     private int mGamepadButton;
     private boolean mGamepadButtonPressed;
     private float mGamepadButtonValue;
     private float[] mGamepadValues;
 
     private String[] mPrefNames;
 
+    private Object mObject;
+
     private GeckoEvent(NativeGeckoEvent event) {
         mType = event.value;
     }
 
     public static GeckoEvent createAppBackgroundingEvent() {
         return GeckoEvent.get(NativeGeckoEvent.APP_BACKGROUNDING);
     }
 
@@ -592,16 +597,23 @@ public class GeckoEvent {
             event.mFlags = GeckoHalDefines.SENSOR_LIGHT;
             event.mMetaState = HalSensorAccuracyFor(s.accuracy);
             event.mX = s.values[0];
             break;
         }
         return event;
     }
 
+    public static GeckoEvent createObjectEvent(final int action, final Object object) {
+        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PROCESS_OBJECT);
+        event.mAction = action;
+        event.mObject = object;
+        return event;
+    }
+
     public static GeckoEvent createLocationEvent(Location l) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOCATION_EVENT);
         event.mLocation = l;
         return event;
     }
 
     public static GeckoEvent createIMEEvent(ImeAction action) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
--- a/mobile/android/base/GeckoProfile.java
+++ b/mobile/android/base/GeckoProfile.java
@@ -12,17 +12,17 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Hashtable;
 
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
-import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.LocalBrowserDB;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.INIParser;
 import org.mozilla.gecko.util.INISection;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -204,17 +204,17 @@ public final class GeckoProfile {
             if (!GuestSession.isSecureKeyguardLocked(context)) {
                 profile.lock();
             }
 
             /*
              * Now do the things that createProfileDirectory normally does --
              * right now that's kicking off DB init.
              */
-            profile.enqueueInitialization();
+            profile.enqueueInitialization(profile.getDir());
 
             return profile;
         } catch (Exception ex) {
             Log.e(LOGTAG, "Error creating guest profile", ex);
         }
         return null;
     }
 
@@ -645,17 +645,17 @@ public final class GeckoProfile {
             Telemetry.startUISession(TelemetryContract.Session.FIRSTRUN);
         }
 
         parser.addSection(profileSection);
         parser.write();
 
         // Trigger init for non-webapp profiles.
         if (!mIsWebAppProfile) {
-            enqueueInitialization();
+            enqueueInitialization(profileDir);
         }
 
         // Write out profile creation time, mirroring the logic in nsToolkitProfileService.
         try {
             FileOutputStream stream = new FileOutputStream(profileDir.getAbsolutePath() + File.separator + "times.json");
             OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
             try {
                 writer.append("{\"created\": " + System.currentTimeMillis() + "}\n");
@@ -679,32 +679,43 @@ public final class GeckoProfile {
      * directory completes.
      *
      * It queues up work to be done in the background to prepare the profile,
      * such as adding default bookmarks.
      *
      * This is public for use *from tests only*!
      */
     @RobocopTarget
-    public void enqueueInitialization() {
+    public void enqueueInitialization(final File profileDir) {
         Log.i(LOGTAG, "Enqueuing profile init.");
         final Context context = mApplicationContext;
 
         // Add everything when we're done loading the distribution.
         final Distribution distribution = Distribution.getInstance(context);
         distribution.addOnDistributionReadyCallback(new Runnable() {
             @Override
             public void run() {
                 Log.d(LOGTAG, "Running post-distribution task: bookmarks.");
 
                 final ContentResolver cr = context.getContentResolver();
 
-                // We pass the number of added bookmarks to ensure that the
-                // indices of the distribution and default bookmarks are
-                // contiguous. Because there are always at least as many
-                // bookmarks as there are favicons, we can also guarantee that
-                // the favicon IDs won't overlap.
-                final int offset = BrowserDB.addDistributionBookmarks(cr, distribution, 0);
-                BrowserDB.addDefaultBookmarks(context, cr, offset);
+                // Because we are running in the background, we want to synchronize on the
+                // GeckoProfile instance so that we don't race with main thread operations
+                // such as locking/unlocking/removing the profile.
+                synchronized (GeckoProfile.this) {
+                    // Skip initialization if the profile directory has been removed.
+                    if (!profileDir.exists()) {
+                        return;
+                    }
+
+                    // We pass the number of added bookmarks to ensure that the
+                    // indices of the distribution and default bookmarks are
+                    // contiguous. Because there are always at least as many
+                    // bookmarks as there are favicons, we can also guarantee that
+                    // the favicon IDs won't overlap.
+                    final LocalBrowserDB db = new LocalBrowserDB(getName());
+                    final int offset = db.addDistributionBookmarks(cr, distribution, 0);
+                    db.addDefaultBookmarks(context, cr, offset);
+                }
             }
         });
     }
 }
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -131,35 +131,39 @@ public class GeckoThread extends Thread 
         }
         if (GeckoApp.ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
             return "-bookmark";
         }
         return null;
     }
 
     private String addCustomProfileArg(String args) {
-        String profile = "";
-        String guest = "";
+        String profileArg = "";
+        String guestArg = "";
         if (GeckoAppShell.getGeckoInterface() != null) {
-            if (GeckoAppShell.getGeckoInterface().getProfile().inGuestMode()) {
+            final GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
+
+            if (profile.inGuestMode()) {
                 try {
-                    profile = " -profile " + GeckoAppShell.getGeckoInterface().getProfile().getDir().getCanonicalPath();
-                } catch (IOException ioe) { Log.e(LOGTAG, "error getting guest profile path", ioe); }
+                    profileArg = " -profile " + profile.getDir().getCanonicalPath();
+                } catch (final IOException ioe) {
+                    Log.e(LOGTAG, "error getting guest profile path", ioe);
+                }
 
                 if (args == null || !args.contains(BrowserApp.GUEST_BROWSING_ARG)) {
-                    guest = " " + BrowserApp.GUEST_BROWSING_ARG;
+                    guestArg = " " + BrowserApp.GUEST_BROWSING_ARG;
                 }
             } else if (!GeckoProfile.sIsUsingCustomProfile) {
-                // If nothing was passed in in the intent, force Gecko to use the default profile for
-                // for this activity
-                profile = " -P " + GeckoAppShell.getGeckoInterface().getProfile().getName();
+                // If nothing was passed in the intent, make sure the default profile exists and
+                // force Gecko to use the default profile for this activity
+                profileArg = " -P " + profile.forceCreate().getName();
             }
         }
 
-        return (args != null ? args : "") + profile + guest;
+        return (args != null ? args : "") + profileArg + guestArg;
     }
 
     @Override
     public void run() {
         Looper.prepare();
         ThreadUtils.sGeckoThread = this;
         ThreadUtils.sGeckoHandler = new Handler();
         ThreadUtils.sGeckoQueue = Looper.myQueue();
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -169,16 +169,18 @@ public class GeckoView extends LayerView
         initializeView(EventDispatcher.getInstance());
 
         if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
             // This is the first launch, so finish initialization and go.
             GeckoProfile profile = GeckoProfile.get(context).forceCreate();
             BrowserDB.initialize(profile.getName());
 
             GeckoAppShell.setLayerView(this);
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createObjectEvent(
+                GeckoEvent.ACTION_OBJECT_LAYER_CLIENT, getLayerClientObject()));
             GeckoThread.createAndStart();
         } else if(GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             // If Gecko is already running, that means the Activity was
             // destroyed, so we need to re-attach Gecko to this GeckoView.
             connectToGecko();
         }
     }
 
@@ -242,17 +244,16 @@ public class GeckoView extends LayerView
     }
 
     private void connectToGecko() {
         GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
         Tab selectedTab = Tabs.getInstance().getSelectedTab();
         if (selectedTab != null)
             Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
         geckoConnected();
-        GeckoAppShell.setLayerClient(getLayerClientObject());
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
     }
 
     private void handleReady(final JSONObject message) {
         connectToGecko();
 
         if (mChromeDelegate != null) {
             mChromeDelegate.onReady(this);
--- a/mobile/android/base/db/SuggestedSites.java
+++ b/mobile/android/base/db/SuggestedSites.java
@@ -152,33 +152,39 @@ public class SuggestedSites {
             json.put(JSON_KEY_BG_COLOR, bgColor);
 
             return json;
         }
     }
 
     final Context context;
     final Distribution distribution;
-    final File file;
+    private File cachedFile;
     private Map<String, Site> cachedSites;
     private Set<String> cachedBlacklist;
 
     public SuggestedSites(Context appContext) {
         this(appContext, null);
     }
 
     public SuggestedSites(Context appContext, Distribution distribution) {
-        this(appContext, distribution,
-             GeckoProfile.get(appContext).getFile(FILENAME));
+        this(appContext, distribution, null);
     }
 
     public SuggestedSites(Context appContext, Distribution distribution, File file) {
         this.context = appContext;
         this.distribution = distribution;
-        this.file = file;
+        this.cachedFile = file;
+    }
+
+    synchronized File getFile() {
+        if (cachedFile == null) {
+            cachedFile = GeckoProfile.get(context).getFile(FILENAME);
+        }
+        return cachedFile;
     }
 
     private static boolean isNewLocale(Context context, Locale requestedLocale) {
         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
 
         String locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE, null);
         if (locale == null) {
             // Initialize config with the current locale
@@ -301,16 +307,17 @@ public class SuggestedSites {
                     sites = new LinkedHashMap<String, Site>();
                 }
                 sites.putAll(loadFromResource());
 
                 // Update cached list of sites.
                 setCachedSites(sites);
 
                 // Save the result to disk.
+                final File file = getFile();
                 synchronized (file) {
                     saveSites(file, sites);
                 }
 
                 // Then notify any active loaders about the changes.
                 final ContentResolver cr = context.getContentResolver();
                 cr.notifyChange(BrowserContract.SuggestedSites.CONTENT_URI, null);
             }
@@ -344,16 +351,17 @@ public class SuggestedSites {
             }
         }
 
         return null;
     }
 
     private Map<String, Site> loadFromProfile() {
         try {
+            final File file = getFile();
             synchronized (file) {
                 return loadSites(file);
             }
         } catch (FileNotFoundException e) {
             maybeWaitForDistribution();
         } catch (IOException e) {
             // Fall through, return null.
         }
@@ -457,17 +465,17 @@ public class SuggestedSites {
             return cursor;
         }
 
         final boolean isNewLocale = isNewLocale(context, locale);
 
         // Force the suggested sites file in profile dir to be re-generated
         // if the locale has changed.
         if (isNewLocale) {
-            file.delete();
+            getFile().delete();
         }
 
         if (cachedSites == null || isNewLocale) {
             Log.d(LOGTAG, "No cached sites, refreshing.");
             refresh();
         }
 
         // Return empty cursor if there was an error when
--- 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,29 +116,32 @@ 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);
         }
 
         // In Robocop tests, we typically don't get initialized correctly, because
         // GeckoProfile doesn't create the profile directory.
-        profile.enqueueInitialization();
+        profile.enqueueInitialization(profile.getDir());
     }
 
     @Override
     protected void runTest() throws Throwable {
         try {
             super.runTest();
         } catch (Throwable t) {
             // save screenshot -- written to /mnt/sdcard/Robotium-Screenshots
--- 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/mobile/android/base/util/GeckoBackgroundThread.java
+++ b/mobile/android/base/util/GeckoBackgroundThread.java
@@ -7,49 +7,70 @@ package org.mozilla.gecko.util;
 import android.os.Handler;
 import android.os.Looper;
 
 import java.util.concurrent.SynchronousQueue;
 
 final class GeckoBackgroundThread extends Thread {
     private static final String LOOPER_NAME = "GeckoBackgroundThread";
 
-    // Guarded by 'this'.
-    private static Handler sHandler;
-    private SynchronousQueue<Handler> mHandlerQueue = new SynchronousQueue<Handler>();
+    // Guarded by 'GeckoBackgroundThread.class'.
+    private static Handler handler;
+    private static Thread thread;
+
+    // The initial Runnable to run on the new thread. Its purpose
+    // is to avoid us having to wait for the new thread to start.
+    private Runnable initialRunnable;
 
     // Singleton, so private constructor.
-    private GeckoBackgroundThread() {
-        super();
+    private GeckoBackgroundThread(final Runnable initialRunnable) {
+        this.initialRunnable = initialRunnable;
     }
 
     @Override
     public void run() {
         setName(LOOPER_NAME);
         Looper.prepare();
-        try {
-            mHandlerQueue.put(new Handler());
-        } catch (InterruptedException ie) {}
+
+        synchronized (GeckoBackgroundThread.class) {
+            handler = new Handler();
+            GeckoBackgroundThread.class.notify();
+        }
+
+        if (initialRunnable != null) {
+            initialRunnable.run();
+            initialRunnable = null;
+        }
 
         Looper.loop();
     }
 
+    private static void startThread(final Runnable initialRunnable) {
+        thread = new GeckoBackgroundThread(initialRunnable);
+        ThreadUtils.setBackgroundThread(thread);
+
+        thread.setDaemon(true);
+        thread.start();
+    }
+
     // Get a Handler for a looper thread, or create one if it doesn't yet exist.
     /*package*/ static synchronized Handler getHandler() {
-        if (sHandler == null) {
-            GeckoBackgroundThread lt = new GeckoBackgroundThread();
-            ThreadUtils.setBackgroundThread(lt);
-            lt.start();
+        if (thread == null) {
+            startThread(null);
+        }
+
+        while (handler == null) {
             try {
-                sHandler = lt.mHandlerQueue.take();
-            } catch (InterruptedException ie) {}
+                GeckoBackgroundThread.class.wait();
+            } catch (final InterruptedException e) {
+            }
         }
-        return sHandler;
+        return handler;
     }
 
-    /*package*/ static void post(Runnable runnable) {
-        Handler handler = getHandler();
-        if (handler == null) {
-            throw new IllegalStateException("No handler! Must have been interrupted. Not posting.");
+    /*package*/ static synchronized void post(final Runnable runnable) {
+        if (thread == null) {
+            startThread(runnable);
+            return;
         }
-        handler.post(runnable);
+        getHandler().post(runnable);
     }
 }
--- a/mobile/android/base/util/ThreadUtils.java
+++ b/mobile/android/base/util/ThreadUtils.java
@@ -184,24 +184,26 @@ public final class ThreadUtils {
         }
         return false;
     }
 
     public static boolean isOnUiThread() {
         return isOnThread(getUiThread());
     }
 
+    @RobocopTarget
     public static boolean isOnBackgroundThread() {
         if (sBackgroundThread == null) {
             return false;
         }
 
         return isOnThread(sBackgroundThread);
     }
 
+    @RobocopTarget
     public static boolean isOnThread(Thread thread) {
         return (Thread.currentThread().getId() == thread.getId());
     }
 
     /**
      * Reduces the priority of the Gecko thread, allowing other operations
      * (such as those related to the UI and database) to take precedence.
      *
--- a/mobile/android/chrome/content/aboutPrivateBrowsing.js
+++ b/mobile/android/chrome/content/aboutPrivateBrowsing.js
@@ -4,13 +4,13 @@
 
 "use strict";
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
   document.addEventListener("DOMContentLoaded", function () {
     document.body.setAttribute("class", "normal");
   }, false);
 }
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -93,11 +93,13 @@ else
   MOZ_ANDROID_MLS_STUMBLER=
 fi
 
 # Enable adding to the system downloads list in pre-release builds.
 if test ! "$RELEASE_BUILD"; then
   MOZ_ANDROID_DOWNLOADS_INTEGRATION=1
 fi
 
-
 # Enable generational GC on mobile.
 JSGC_GENERATIONAL=1
+
+# Use the low-memory GC tuning.
+JS_GC_SMALL_CHUNK_SIZE=1
--- a/mobile/android/tests/browser/junit3/moz.build
+++ b/mobile/android/tests/browser/junit3/moz.build
@@ -7,16 +7,19 @@
 DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
 
 jar = add_java_jar('browser-junit3')
 jar.sources += [
     'src/BrowserTestCase.java',
     'src/harness/BrowserInstrumentationTestRunner.java',
     'src/harness/BrowserTestListener.java',
     'src/TestDistribution.java',
+    'src/TestGeckoBackgroundThread.java',
+    'src/TestGeckoMenu.java',
+    'src/TestGeckoProfilesProvider.java',
     'src/TestGeckoSharedPrefs.java',
     'src/TestImageDownloader.java',
     'src/TestJarReader.java',
     'src/TestRawResource.java',
     'src/TestSuggestedSites.java',
     'src/TestTopSitesCursorWrapper.java',
 ]
 jar.generated_sources = [] # None yet -- try to keep it this way.
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/junit3/src/TestGeckoBackgroundThread.java
@@ -0,0 +1,55 @@
+/* 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;
+
+import org.mozilla.gecko.util.ThreadUtils;
+
+public class TestGeckoBackgroundThread extends BrowserTestCase {
+
+    private boolean finishedTest;
+    private boolean ranFirstRunnable;
+
+    public void testGeckoBackgroundThread() throws InterruptedException {
+
+        final Thread testThread = Thread.currentThread();
+
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                // Must *not* be on thread that posted the Runnable.
+                assertFalse(ThreadUtils.isOnThread(testThread));
+
+                // Must be on background thread.
+                assertTrue(ThreadUtils.isOnBackgroundThread());
+
+                ranFirstRunnable = true;
+            }
+        });
+
+        // Post a second Runnable to make sure it still runs on the background thread,
+        // and it only runs after the first Runnable has run.
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                // Must still be on background thread.
+                assertTrue(ThreadUtils.isOnBackgroundThread());
+
+                // This Runnable must be run after the first Runnable had finished.
+                assertTrue(ranFirstRunnable);
+
+                synchronized (TestGeckoBackgroundThread.this) {
+                    finishedTest = true;
+                    TestGeckoBackgroundThread.this.notify();
+                }
+            }
+        });
+
+        synchronized (this) {
+            while (!finishedTest) {
+                wait();
+            }
+        }
+    }
+}
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -130,17 +130,17 @@ pref("dom.workers.sharedWorkers.enabled"
 
 // Service workers
 pref("dom.serviceWorkers.enabled", false);
 
 // Whether nonzero values can be returned from performance.timing.*
 pref("dom.enable_performance", true);
 
 // Whether resource timing will be gathered and returned by performance.GetEntries*
-pref("dom.enable_resource_timing", false);
+pref("dom.enable_resource_timing", true);
 
 // Whether the Gamepad API is enabled
 pref("dom.gamepad.enabled", true);
 #ifdef RELEASE_BUILD
 pref("dom.gamepad.non_standard_events.enabled", false);
 #else
 pref("dom.gamepad.non_standard_events.enabled", true);
 #endif
@@ -336,17 +336,16 @@ pref("media.getusermedia.browser.enabled
 // Desktop is typically VGA capture or more; and qm_select will not drop resolution
 // below 1/2 in each dimension (or so), so QVGA (320x200) is the lowest here usually.
 pref("media.peerconnection.video.min_bitrate", 200);
 pref("media.peerconnection.video.start_bitrate", 300);
 pref("media.peerconnection.video.max_bitrate", 2000);
 #endif
 pref("media.navigator.permission.disabled", false);
 pref("media.peerconnection.default_iceservers", "[{\"url\": \"stun:stun.services.mozilla.com\"}]");
-pref("media.peerconnection.trickle_ice", true);
 pref("media.peerconnection.use_document_iceservers", true);
 // Do not enable identity before ensuring that the UX cannot be spoofed
 // see Bug 884573 for details
 // Do not enable identity before fixing domain comparison: see Bug 958741
 // Do not enable identity before fixing origin spoofing: see Bug 968335
 pref("media.peerconnection.identity.enabled", false);
 pref("media.peerconnection.identity.timeout", 10000);
 // These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -91,35 +91,16 @@ Java_org_mozilla_gecko_GeckoAppShell_nat
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_nativeInit", &f_Java_org_mozilla_gecko_GeckoAppShell_nativeInit);
 #endif
 
 #ifdef JNI_STUBS
 
-typedef void (*Java_org_mozilla_gecko_GeckoAppShell_setLayerClient_t)(JNIEnv *, jclass, jobject);
-static Java_org_mozilla_gecko_GeckoAppShell_setLayerClient_t f_Java_org_mozilla_gecko_GeckoAppShell_setLayerClient;
-extern "C" NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv * arg0, jclass arg1, jobject arg2) {
-    if (!f_Java_org_mozilla_gecko_GeckoAppShell_setLayerClient) {
-        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
-                       "JNI Function called before it was loaded");
-        return ;
-    }
-     f_Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(arg0, arg1, arg2);
-}
-#endif
-
-#ifdef JNI_BINDINGS
-  xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_setLayerClient", &f_Java_org_mozilla_gecko_GeckoAppShell_setLayerClient);
-#endif
-
-#ifdef JNI_STUBS
-
 typedef void (*Java_org_mozilla_gecko_GeckoAppShell_onResume_t)(JNIEnv *, jclass);
 static Java_org_mozilla_gecko_GeckoAppShell_onResume_t f_Java_org_mozilla_gecko_GeckoAppShell_onResume;
 extern "C" NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv * arg0, jclass arg1) {
     if (!f_Java_org_mozilla_gecko_GeckoAppShell_onResume) {
         arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
                        "JNI Function called before it was loaded");
         return ;
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -610,16 +610,20 @@ class MochitestOptions(optparse.OptionPa
                 if not os.path.isfile(f):
                     self.error('Missing binary %s required for --use-test-media-devices')
 
         options.leakThresholds = {
             "default": options.defaultLeakThreshold,
             "tab": 10000, # See dependencies of bug 1051230.
         }
 
+        # Bug 1051230 - Leak logging does not yet work for tab processes on desktop.
+        # Bug 1065098 - The geckomediaplugin process fails to produce a leak log for some reason.
+        options.ignoreMissingLeaks = ["tab", "geckomediaplugin"]
+
         return options
 
 
 class B2GOptions(MochitestOptions):
     b2g_options = [
         [["--b2gpath"],
         { "action": "store",
           "type": "string",
@@ -814,13 +818,19 @@ class B2GOptions(MochitestOptions):
         tempSSL = options.sslPort
         tempIP = options.webServer
         options = MochitestOptions.verifyOptions(self, options, mochitest)
         options.webServer = tempIP
         options.app = temp
         options.sslPort = tempSSL
         options.httpPort = tempPort
 
+        # Bug 1071866 - B2G Mochitests do not always produce a leak log.
+        options.ignoreMissingLeaks.append("default")
+
+        # Bug 1070068 - Leak logging does not work for tab processes on B2G.
+        assert "tab" in options.ignoreMissingLeaks, "Ignore failures for tab processes on B2G"
+
         return options
 
     def elf_arm(self, filename):
         data = open(filename, 'rb').read(20)
         return data[:4] == "\x7fELF" and ord(data[18]) == 40 # EM_ARM
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1837,17 +1837,17 @@ class Mochitest(MochitestUtilsMixin):
         self.log.error("Automation Error: Received unexpected exception while running application\n")
         status = 1
 
     finally:
       if options.vmwareRecording:
         self.stopVMwareRecording();
       self.stopServers()
 
-    processLeakLog(self.leak_report_file, options.leakThresholds)
+    processLeakLog(self.leak_report_file, options.leakThresholds, options.ignoreMissingLeaks)
 
     if self.nsprLogs:
       with zipfile.ZipFile("%s/nsprlog.zip" % browserEnv["MOZ_UPLOAD_DIR"], "w", zipfile.ZIP_DEFLATED) as logzip:
         for logfile in glob.glob("%s/nspr*.log*" % tempfile.gettempdir()):
           logzip.write(logfile)
           os.remove(logfile)
 
     self.log.info("runtests.py | Running tests: end.")
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -197,17 +197,17 @@ class B2GMochitest(MochitestUtilsMixin):
             if status is None:
                 # the runner has timed out
                 status = 124
 
             local_leak_file = tempfile.NamedTemporaryFile()
             self.app_ctx.dm.getFile(self.leak_report_file, local_leak_file.name)
             self.app_ctx.dm.removeFile(self.leak_report_file)
 
-            processLeakLog(local_leak_file.name, options.leakThresholds)
+            processLeakLog(local_leak_file.name, options.leakThresholds, options.ignoreMissingLeaks)
         except KeyboardInterrupt:
             self.log.info("runtests.py | Received keyboard interrupt.\n");
             status = -1
         except:
             traceback.print_exc()
             self.log.error("Automation Error: Received unexpected exception while running application\n")
             if hasattr(self, 'runner'):
                 self.runner.check_for_crashes()
--- a/testing/mozbase/mozversion/mozversion/mozversion.py
+++ b/testing/mozbase/mozversion/mozversion/mozversion.py
@@ -226,18 +226,21 @@ class RemoteB2GVersion(B2GVersion):
             'ro.product.device': 'device_id'}
         for line in build_props.split('\n'):
             if not line.strip().startswith('#') and '=' in line:
                 key, value = [s.strip() for s in line.split('=', 1)]
                 if key in desired_props.keys():
                     self._info[desired_props[key]] = value
 
         if self._info.get('device_id', '').lower() == 'flame':
-            self._info['device_firmware_version_base'] = dm._runCmd(
-                ['shell', 'getprop', 't2m.sw.version']).output[0]
+            for prop in ['ro.boot.bootloader', 't2m.sw.version']:
+                value = dm.shellCheckOutput(['getprop', prop])
+                if value:
+                    self._info['device_firmware_version_base'] = value
+                    break
 
 
 def get_version(binary=None, sources=None, dm_type=None, host=None,
                 device_serial=None):
     """
     Returns the application version information as a dict. You can specify
     a path to the binary of the application or an Android APK file (to get
     version information for Firefox for Android). If this is omitted then the
--- a/testing/web-platform/meta/media-source/SourceBuffer-abort-updating.html.ini
+++ b/testing/web-platform/meta/media-source/SourceBuffer-abort-updating.html.ini
@@ -1,9 +1,5 @@
 [SourceBuffer-abort-updating.html]
   type: testharness
-  expected: ERROR
-  [SourceBuffer#abort() (video/webm; codecs="vorbis,vp8") : Check the algorithm when the updating attribute is true.]
-    expected: FAIL
-
   [SourceBuffer#abort() (video/mp4) : Check the algorithm when the updating attribute is true.]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/performance-timeline/idlharness.html.ini
+++ /dev/null
@@ -1,26 +0,0 @@
-[idlharness.html]
-  type: testharness
-  [Performance interface: operation getEntries()]
-    expected: FAIL
-
-  [Performance interface: operation getEntriesByType(DOMString)]
-    expected: FAIL
-
-  [Performance interface: operation getEntriesByName(DOMString,DOMString)]
-    expected: FAIL
-
-  [Performance interface: window.performance must inherit property "getEntries" with the proper type (0)]
-    expected: FAIL
-
-  [Performance interface: window.performance must inherit property "getEntriesByType" with the proper type (1)]
-    expected: FAIL
-
-  [Performance interface: calling getEntriesByType(DOMString) on window.performance with too few arguments must throw TypeError]
-    expected: FAIL
-
-  [Performance interface: window.performance must inherit property "getEntriesByName" with the proper type (2)]
-    expected: FAIL
-
-  [Performance interface: calling getEntriesByName(DOMString,DOMString) on window.performance with too few arguments must throw TypeError]
-    expected: FAIL
-
--- a/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini
+++ b/testing/web-platform/meta/resource-timing/test_resource_timing.html.ini
@@ -1,11 +1,4 @@
 [test_resource_timing.html]
   type: testharness
-  [window.performance.getEntriesByName() is defined]
-    expected: FAIL
-
-  [window.performance.getEntriesByType() is defined]
+  [window.performance.getEntriesByName("http://.../resource_timing_test0.xml") returns a PerformanceEntry object]
     expected: FAIL
-
-  [window.performance.getEntries() is defined]
-    expected: FAIL
-
--- a/testing/web-platform/tests/resource-timing/test_resource_timing.html
+++ b/testing/web-platform/tests/resource-timing/test_resource_timing.html
@@ -281,17 +281,18 @@
                                     actualEntry.requestStart + " >= " + actualEntry.connectEnd + "<br/>" +
                                     "responseStart >= requestStart: " +
                                     actualEntry.responseStart + " >= " + actualEntry.requestStart + "<br/>" +
                                     "responseEnd >= responseStart: " +
                                     actualEntry.responseEnd + " >= " + actualEntry.responseStart + "<br/>";
 
             pass =  (actualEntry.redirectStart == 0) &&
                     (actualEntry.redirectEnd == 0) &&
-                    (actualEntry.secureConnectionStart == undefined) &&
+                    (actualEntry.secureConnectionStart == undefined ||
+                     actualEntry.secureConnectionStart == 0) &&
                     validate_timeline(actualEntry);
 
             (pass) ? test_true(true,
                                "PerformanceEntry returned by window.performance.getEntriesByName(\"" +
                                actualEntry.name.replace(URL_PREFIX, "http://.../") + "\") has correct order of " +
                                "timing attributes.")
                      :
                      test_true(false,
--- 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/components/url-classifier/nsUrlClassifierPrefixSet.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp
@@ -306,35 +306,36 @@ nsUrlClassifierPrefixSet::LoadFromFd(Aut
     int32_t toRead = indexSize*sizeof(uint32_t);
     read = PR_Read(fileFd, mIndexPrefixes.Elements(), toRead);
     NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
     read = PR_Read(fileFd, indexStarts.Elements(), toRead);
     NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
     if (indexSize != 0 && indexStarts[0] != 0) {
       return NS_ERROR_FILE_CORRUPTED;
     }
-    if (deltaSize > 0) {
-      for (uint32_t i = 0; i < indexSize; i++) {
-        mIndexDeltas.AppendElement();
-        uint32_t numInDelta = i == indexSize - 1 ? deltaSize - indexStarts[i]
-                                                 : indexStarts[i + 1] - indexStarts[i];
+    for (uint32_t i = 0; i < indexSize; i++) {
+      mIndexDeltas.AppendElement();
+      uint32_t numInDelta = i == indexSize - 1 ? deltaSize - indexStarts[i]
+                               : indexStarts[i + 1] - indexStarts[i];
+      if (numInDelta > 0) {
         mIndexDeltas[i].SetLength(numInDelta);
         mTotalPrefixes += numInDelta;
         toRead = numInDelta * sizeof(uint16_t);
         read = PR_Read(fileFd, mIndexDeltas[i].Elements(), toRead);
         NS_ENSURE_TRUE(read == toRead, NS_ERROR_FILE_CORRUPTED);
       }
     }
 
     mHasPrefixes = true;
   } else {
     LOG(("Version magic mismatch, not loading"));
     return NS_ERROR_FILE_CORRUPTED;
   }
 
+  MOZ_ASSERT(mIndexPrefixes.Length() == mIndexDeltas.Length());
   LOG(("Loading PrefixSet successful"));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsUrlClassifierPrefixSet::LoadFromFile(nsIFile* aFile)
 {
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -52,16 +52,18 @@ function cleanUp() {
   delFile("urlclassifier3.sqlite");
   delFile("safebrowsing/classifier.hashkey");
   delFile("safebrowsing/test-phish-simple.sbstore");
   delFile("safebrowsing/test-malware-simple.sbstore");
   delFile("safebrowsing/test-phish-simple.cache");
   delFile("safebrowsing/test-malware-simple.cache");
   delFile("safebrowsing/test-phish-simple.pset");
   delFile("safebrowsing/test-malware-simple.pset");
+  delFile("testLarge.pset");
+  delFile("testNoDelta.pset");
 }
 
 var allTables = "test-phish-simple,test-malware-simple";
 
 var dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(Ci.nsIUrlClassifierDBService);
 var streamUpdater = Cc["@mozilla.org/url-classifier/streamupdater;1"]
                     .getService(Ci.nsIUrlClassifierStreamUpdater);
 
--- a/toolkit/components/url-classifier/tests/unit/test_prefixset.js
+++ b/toolkit/components/url-classifier/tests/unit/test_prefixset.js
@@ -135,17 +135,17 @@ function testReSetPrefixes() {
   for (let i = 0; i < prefixes.length; i++) {
     do_check_false(wrappedProbe(pset, prefixes[i]));
   }
 
 
   checkContents(pset, secondPrefixes);
 }
 
-function testLargeSet() {
+function testLoadSaveLargeSet() {
   let N = 1000;
   let arr = [];
 
   for (let i = 0; i < N; i++) {
     let randInt = Math.floor(Math.random() * Math.pow(2, 32));
     arr.push(randInt);
   }
 
@@ -153,16 +153,30 @@ function testLargeSet() {
 
   let pset = newPset();
   pset.setPrefixes(arr, arr.length);
 
   doExpectedLookups(pset, arr, 1);
   doRandomLookups(pset, arr, 1000);
 
   checkContents(pset, arr);
+
+  // Now try to save, restore, and redo the lookups
+  var file = dirSvc.get('ProfLD', Ci.nsIFile);
+  file.append("testLarge.pset");
+
+  pset.storeToFile(file);
+
+  let psetLoaded = newPset();
+  psetLoaded.loadFromFile(file);
+
+  doExpectedLookups(psetLoaded, arr, 1);
+  doRandomLookups(psetLoaded, arr, 1000);
+
+  checkContents(psetLoaded, arr);
 }
 
 function testTinySet() {
   let pset = Cc["@mozilla.org/url-classifier/prefixset;1"]
                .createInstance(Ci.nsIUrlClassifierPrefixSet);
   let prefixes = [1];
   pset.setPrefixes(prefixes, prefixes.length);
 
@@ -171,22 +185,47 @@ function testTinySet() {
   checkContents(pset, prefixes);
 
   prefixes = [];
   pset.setPrefixes(prefixes, prefixes.length);
   do_check_false(wrappedProbe(pset, 1));
   checkContents(pset, prefixes);
 }
 
+function testLoadSaveNoDelta() {
+  let N = 100;
+  let arr = [];
+
+  for (let i = 0; i < N; i++) {
+    // construct a tree without deltas by making the distance
+    // between entries larger than 16 bits
+    arr.push(((1 << 16) + 1) * i);
+  }
+
+  let pset = newPset();
+  pset.setPrefixes(arr, arr.length);
+
+  doExpectedLookups(pset, arr, 1);
+
+  var file = dirSvc.get('ProfLD', Ci.nsIFile);
+  file.append("testNoDelta.pset");
+
+  pset.storeToFile(file);
+  pset.loadFromFile(file);
+
+  doExpectedLookups(pset, arr, 1);
+}
+
 let tests = [testBasicPset,
              testSimplePset,
              testReSetPrefixes,
-             testLargeSet,
+             testLoadSaveLargeSet,
              testDuplicates,
-             testTinySet];
+             testTinySet,
+             testLoadSaveNoDelta];
 
 function run_test() {
   // None of the tests use |executeSoon| or any sort of callbacks, so we can
   // just run them in succession.
   for (let i = 0; i < tests.length; i++) {
     dump("Running " + tests[i].name + "\n");
     tests[i]();
   }
--- a/toolkit/modules/PrivateBrowsingUtils.jsm
+++ b/toolkit/modules/PrivateBrowsingUtils.jsm
@@ -29,17 +29,25 @@ this.PrivateBrowsingUtils = {
   },
 
   // This should be used only in frame scripts.
   isContentWindowPrivate: function pbu_isWindowPrivate(aWindow) {
     return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
   },
 
   isBrowserPrivate: function(aBrowser) {
-    return this.isWindowPrivate(aBrowser.ownerDocument.defaultView);
+    let chromeWin = aBrowser.ownerDocument.defaultView;
+    if (chromeWin.gMultiProcessBrowser) {
+      // In e10s we have to look at the chrome window's private
+      // browsing status since the only alternative is to check the
+      // content window, which is in another process.
+      return this.isWindowPrivate(chromeWin);
+    } else {
+      return this.privacyContextFromWindow(aBrowser.contentWindow).usePrivateBrowsing;
+    }
   },
 
   privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) {
     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIWebNavigation)
                   .QueryInterface(Ci.nsILoadContext);
   },
 
--- 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;
--- a/tools/mercurial/hgsetup/config.py
+++ b/tools/mercurial/hgsetup/config.py
@@ -1,15 +1,16 @@
 # 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/.
 
 from __future__ import unicode_literals
 
 from configobj import ConfigObj
+import codecs
 import re
 import os
 
 
 HOST_FINGERPRINTS = {
     'bitbucket.org': '45:ad:ae:1a:cf:0e:73:47:06:07:e0:88:f5:cc:10:e5:fa:1c:f7:99',
     'bugzilla.mozilla.org': '47:13:a2:14:0c:46:45:53:12:0d:e5:36:16:a5:60:26:3e:da:3a:60',
     'hg.mozilla.org': 'af:27:b9:34:47:4e:e5:98:01:f6:83:2b:51:c9:aa:d8:df:fb:1a:27',
@@ -38,17 +39,17 @@ class MercurialConfig(object):
             self.config_path = infile
         else:
             infile = None
 
         # Mercurial configuration files allow an %include directive to include
         # other files, this is not supported by ConfigObj, so throw a useful
         # error saying this.
         if os.path.exists(infile):
-            with open(infile, 'r') as f:
+            with codecs.open(infile, 'r', encoding='utf-8') as f:
                 for line in f:
                     if line.startswith('%include'):
                         raise HgIncludeException(
                             '%include directive is not supported by MercurialConfig')
 
         # write_empty_values is necessary to prevent built-in extensions (which
         # have no value) from being dropped on write.
         # list_values aren't needed by Mercurial and disabling them prevents
--- a/widget/WidgetUtils.h
+++ b/widget/WidgetUtils.h
@@ -21,11 +21,18 @@ enum ScreenRotation {
   ROTATION_270,
 
   ROTATION_COUNT
 };
 
 gfx::Matrix ComputeTransformForRotation(const nsIntRect& aBounds,
                                         ScreenRotation aRotation);
 
+gfx::Matrix ComputeTransformForUnRotation(const nsIntRect& aBounds,
+                                          ScreenRotation aRotation);
+
+nsIntRect RotateRect(nsIntRect aRect,
+                     const nsIntRect& aBounds,
+                     ScreenRotation aRotation);
+
 } // namespace mozilla
 
 #endif // mozilla_WidgetUtils_h
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -115,22 +115,16 @@ Java_org_mozilla_gecko_GeckoAppShell_run
     if (!AndroidBridge::Bridge()) {
         return -1;
     }
 
     return AndroidBridge::Bridge()->RunDelayedUiThreadTasks();
 }
 
 NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv *jenv, jclass, jobject obj)
-{
-    AndroidBridge::Bridge()->SetLayerClient(jenv, obj);
-}
-
-NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *jenv, jclass jc)
 {
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->OnResume();
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *jenv, jclass, jstring jStackTrace)
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -66,16 +66,17 @@ jfieldID AndroidGeckoEvent::jByteBufferF
 jfieldID AndroidGeckoEvent::jWidthField = 0;
 jfieldID AndroidGeckoEvent::jHeightField = 0;
 jfieldID AndroidGeckoEvent::jIDField = 0;
 jfieldID AndroidGeckoEvent::jGamepadButtonField = 0;
 jfieldID AndroidGeckoEvent::jGamepadButtonPressedField = 0;
 jfieldID AndroidGeckoEvent::jGamepadButtonValueField = 0;
 jfieldID AndroidGeckoEvent::jGamepadValuesField = 0;
 jfieldID AndroidGeckoEvent::jPrefNamesField = 0;
+jfieldID AndroidGeckoEvent::jObjectField = 0;
 
 jclass AndroidGeckoEvent::jDomKeyLocationClass = 0;
 jfieldID AndroidGeckoEvent::jDomKeyLocationValueField = 0;
 
 jclass AndroidPoint::jPointClass = 0;
 jfieldID AndroidPoint::jXField = 0;
 jfieldID AndroidPoint::jYField = 0;
 
@@ -178,16 +179,17 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jWidthField = getField("mWidth", "I");
     jHeightField = getField("mHeight", "I");
     jIDField = getField("mID", "I");
     jGamepadButtonField = getField("mGamepadButton", "I");
     jGamepadButtonPressedField = getField("mGamepadButtonPressed", "Z");
     jGamepadButtonValueField = getField("mGamepadButtonValue", "F");
     jGamepadValuesField = getField("mGamepadValues", "[F");
     jPrefNamesField = getField("mPrefNames", "[Ljava/lang/String;");
+    jObjectField = getField("mObject", "Ljava/lang/Object;");
 
     // Init GeckoEvent.DomKeyLocation enum
     jDomKeyLocationClass = getClassGlobalRef("org/mozilla/gecko/GeckoEvent$DomKeyLocation");
     jDomKeyLocationValueField = getField("value", "I");
 }
 
 void
 AndroidLocation::InitLocationClass(JNIEnv *jEnv)
@@ -498,16 +500,23 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
         case SENSOR_EVENT:
              mX = jenv->GetDoubleField(jobj, jXField);
              mY = jenv->GetDoubleField(jobj, jYField);
              mZ = jenv->GetDoubleField(jobj, jZField);
              mFlags = jenv->GetIntField(jobj, jFlagsField);
              mMetaState = jenv->GetIntField(jobj, jMetaStateField);
              break;
 
+        case PROCESS_OBJECT: {
+            const jobject obj = jenv->GetObjectField(jobj, jObjectField);
+            mObject.Init(obj, jenv);
+            jenv->DeleteLocalRef(obj);
+            break;
+        }
+
         case LOCATION_EVENT: {
             jobject location = jenv->GetObjectField(jobj, jLocationField);
             mGeoPosition = AndroidLocation::CreateGeoPosition(jenv, location);
             break;
         }
 
         case LOAD_URI: {
             ReadCharactersField(jenv);
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -547,16 +547,17 @@ public:
     int Width() { return mWidth; }
     int Height() { return mHeight; }
     int ID() { return mID; }
     int GamepadButton() { return mGamepadButton; }
     bool GamepadButtonPressed() { return mGamepadButtonPressed; }
     float GamepadButtonValue() { return mGamepadButtonValue; }
     const nsTArray<float>& GamepadValues() { return mGamepadValues; }
     int RequestId() { return mCount; } // for convenience
+    const AutoGlobalWrappedJavaObject& Object() { return mObject; }
     bool CanCoalesceWith(AndroidGeckoEvent* ae);
     WidgetTouchEvent MakeTouchEvent(nsIWidget* widget);
     MultiTouchInput MakeMultiTouchInput(nsIWidget* widget);
     WidgetMouseEvent MakeMouseEvent(nsIWidget* widget);
     void UnionRect(nsIntRect const& aRect);
     nsIObserver *Observer() { return mObserver; }
     mozilla::layers::ScrollableLayerGuid ApzGuid();
 
@@ -596,16 +597,17 @@ protected:
     int mGamepadButton;
     bool mGamepadButtonPressed;
     float mGamepadButtonValue;
     nsTArray<float> mGamepadValues;
     nsCOMPtr<nsIObserver> mObserver;
     nsTArray<nsString> mPrefNames;
     MultiTouchInput mApzInput;
     mozilla::layers::ScrollableLayerGuid mApzGuid;
+    AutoGlobalWrappedJavaObject mObject;
 
     void ReadIntArray(nsTArray<int> &aVals,
                       JNIEnv *jenv,
                       jfieldID field,
                       int32_t count);
     void ReadFloatArray(nsTArray<float> &aVals,
                         JNIEnv *jenv,
                         jfieldID field,
@@ -680,25 +682,28 @@ protected:
     static jfieldID jHeightField;
 
     static jfieldID jIDField;
     static jfieldID jGamepadButtonField;
     static jfieldID jGamepadButtonPressedField;
     static jfieldID jGamepadButtonValueField;
     static jfieldID jGamepadValuesField;
 
+    static jfieldID jObjectField;
+
     static jclass jDomKeyLocationClass;
     static jfieldID jDomKeyLocationValueField;
 
 public:
     enum {
         NATIVE_POKE = 0,
         KEY_EVENT = 1,
         MOTION_EVENT = 2,
         SENSOR_EVENT = 3,
+        PROCESS_OBJECT = 4,
         LOCATION_EVENT = 5,
         IME_EVENT = 6,
         SIZE_CHANGED = 8,
         APP_BACKGROUNDING = 9,
         APP_FOREGROUNDING = 10,
         LOAD_URI = 12,
         NOOP = 15,
         FORCED_RESIZE = 16, // used internally in nsAppShell/nsWindow
@@ -761,16 +766,21 @@ public:
         ACTION_GAMEPAD_ADDED = 1,
         ACTION_GAMEPAD_REMOVED = 2
     };
 
     enum {
         ACTION_GAMEPAD_BUTTON = 1,
         ACTION_GAMEPAD_AXES = 2
     };
+
+    enum {
+        ACTION_OBJECT_LAYER_CLIENT = 1,
+        dummy_object_enum_list_end
+    };
 };
 
 class nsJNIString : public nsString
 {
 public:
     nsJNIString(jstring jstr, JNIEnv *jenv);
 };
 
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -307,16 +307,30 @@ nsAppShell::ProcessNextNativeEvent(bool 
         }
 
         const hal::SensorAccuracyType &accuracy = (hal::SensorAccuracyType) curEvent->MetaState();
         hal::SensorData sdata(type, PR_Now(), values, accuracy);
         hal::NotifySensorChange(sdata);
       }
       break;
 
+    case AndroidGeckoEvent::PROCESS_OBJECT: {
+      JNIEnv* const env = AndroidBridge::Bridge()->GetJNIEnv();
+      AutoLocalJNIFrame frame(env, 1);
+
+      switch (curEvent->Action()) {
+      case AndroidGeckoEvent::ACTION_OBJECT_LAYER_CLIENT:
+        // SetLayerClient expects a local reference
+        const jobject obj = env->NewLocalRef(curEvent->Object().wrappedObject());
+        AndroidBridge::Bridge()->SetLayerClient(env, obj);
+        break;
+      }
+      break;
+    }
+
     case AndroidGeckoEvent::LOCATION_EVENT: {
         if (!gLocationCallback)
             break;
 
         nsGeoPosition* p = curEvent->GeoPosition();
         if (p)
             gLocationCallback->Update(curEvent->GeoPosition());
         else
--- a/widget/xpwidgets/WidgetUtils.cpp
+++ b/widget/xpwidgets/WidgetUtils.cpp
@@ -32,9 +32,61 @@ ComputeTransformForRotation(const nsIntR
         transform.PreRotate(floatPi * 3 / 2);
         break;
     default:
         MOZ_CRASH("Unknown rotation");
     }
     return transform;
 }
 
+gfx::Matrix
+ComputeTransformForUnRotation(const nsIntRect& aBounds,
+                              ScreenRotation aRotation)
+{
+    gfx::Matrix transform;
+    static const gfx::Float floatPi = static_cast<gfx::Float>(M_PI);
+
+    switch (aRotation) {
+    case ROTATION_0:
+        break;
+    case ROTATION_90:
+        transform.PreTranslate(0, aBounds.height);
+        transform.PreRotate(floatPi * 3 / 2);
+        break;
+    case ROTATION_180:
+        transform.PreTranslate(aBounds.width, aBounds.height);
+        transform.PreRotate(floatPi);
+        break;
+    case ROTATION_270:
+        transform.PreTranslate(aBounds.width, 0);
+        transform.PreRotate(floatPi / 2);
+        break;
+    default:
+        MOZ_CRASH("Unknown rotation");
+    }
+    return transform;
+}
+
+nsIntRect RotateRect(nsIntRect aRect,
+                     const nsIntRect& aBounds,
+                     ScreenRotation aRotation)
+{
+  switch (aRotation) {
+    case ROTATION_0:
+      return aRect;
+    case ROTATION_90:
+      return nsIntRect(aRect.y,
+                       aBounds.width - aRect.x - aRect.width,
+                       aRect.height, aRect.width);
+    case ROTATION_180:
+      return nsIntRect(aBounds.width - aRect.x - aRect.width,
+                       aBounds.height - aRect.y - aRect.height,