Merge m-c to graphics, a=merge
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 24 May 2017 08:14:30 -0400
changeset 409346 e4db9580e41b486ee1dbd603bd2e011003f5fb1f
parent 409345 462bbf385a1804ca10f617faf5eb8697c55c46e0 (current diff)
parent 408461 291a11111bdd05c5cd55dd552da4b1285ceba9b2 (diff)
child 409347 9c8bcb590b888c5c077e3a05625800ed0d6786a8
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.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 graphics, a=merge MozReview-Commit-ID: 2pUMxaNAKCC
browser/base/content/test/appUpdate/.eslintrc.js
browser/base/content/test/appUpdate/browser.ini
browser/base/content/test/appUpdate/browser_updatesBackgroundWindow.js
browser/base/content/test/appUpdate/browser_updatesBackgroundWindowFailures.js
browser/base/content/test/appUpdate/browser_updatesBasicPrompt.js
browser/base/content/test/appUpdate/browser_updatesBasicPromptNoStaging.js
browser/base/content/test/appUpdate/browser_updatesCantApply.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js
browser/base/content/test/appUpdate/browser_updatesCompleteAndPartialPatchesWithBadSizes.js
browser/base/content/test/appUpdate/browser_updatesCompletePatchApplyFailure.js
browser/base/content/test/appUpdate/browser_updatesCompletePatchWithBadCompleteSize.js
browser/base/content/test/appUpdate/browser_updatesDownloadFailures.js
browser/base/content/test/appUpdate/browser_updatesMalformedXml.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailure.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteAvailable.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchApplyFailureWithCompleteValidationFailure.js
browser/base/content/test/appUpdate/browser_updatesPartialPatchWithBadPartialSize.js
browser/base/content/test/appUpdate/downloadPage.html
browser/base/content/test/appUpdate/head.js
browser/base/content/test/appUpdate/testConstants.js
browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
browser/components/migration/tests/unit/test_Edge_availability.js
browser/extensions/formautofill/test/unit/test_enabledStatus.js
devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
gfx/layers/wr/WebRenderBridgeParent.cpp
xpcom/threads/nsIIncrementalRunnable.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -73,17 +73,17 @@ browser/extensions/pdfjs/content/web**
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # generated or library files in activity-stream
 browser/extensions/activity-stream/data/content/activity-stream.bundle.js
 browser/extensions/activity-stream/vendor/**
 # imported from chromium
 browser/extensions/mortar/**
-
+
 # devtools/ exclusions
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/devtools.js
 !devtools/client/framework/devtools-browser.js
 !devtools/client/framework/selection.js
@@ -168,16 +168,17 @@ devtools/server/actors/utils/automation-
 # Ignore devtools files testing sourcemaps / code style
 devtools/client/debugger/test/mochitest/code_binary_search.js
 devtools/client/debugger/test/mochitest/code_math.min.js
 devtools/client/debugger/test/mochitest/code_math_bogus_map.js
 devtools/client/debugger/test/mochitest/code_ugly*
 devtools/client/debugger/test/mochitest/code_worker-source-map.js
 devtools/client/framework/test/code_ugly*
 devtools/client/inspector/markup/test/events_bundle.js
+devtools/client/netmonitor/test/xhr_bundle.js
 devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js
 devtools/server/tests/unit/setBreakpoint*
 devtools/server/tests/unit/sourcemapped.js
 
 # dom/ exclusions
 dom/animation/**
 dom/archivereader/**
 dom/asmjscache/**
--- a/AUTHORS
+++ b/AUTHORS
@@ -285,16 +285,17 @@ David Rajchenbach-Teller <dteller@mozill
 David Savage
 David S. Miller <davem@redhat.com>
 David Woodhouse <dwmw2@infradead.org>
 David Zbarsky <dzbarsky@gmail.com>
 Dean Tessman <dean_tessman@hotmail.com>
 <deneen@alum.bucknell.edu>
 Denis Antrushin <adu@sparc.spb.su>
 Denis Issoupov <denis@macadamian.com>
+Dennis Ek <contact@dennisek.se>
 Dennis Handly
 Derrick Rice <derrick.rice@gmail.com>
 <desale@netscape.com>
 diablohn
 Diane Trout <diane@ghic.org>
 Dietrich Ayala <dietrich@mozilla.com>
 Digital Creations 2, Inc
 Disruptive Innovations
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -69,17 +69,25 @@ void ProxyCreated(ProxyAccessible* aProx
 void ProxyDestroyed(ProxyAccessible*);
 
 /**
  * Callied when an event is fired on a proxied accessible.
  */
 void ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType);
 void ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
                            bool aEnabled);
+
+#if defined(XP_WIN)
+void ProxyFocusEvent(ProxyAccessible* aTarget,
+                     const LayoutDeviceIntRect& aCaretRect);
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget,
+                         const LayoutDeviceIntRect& aCaretRect);
+#else
 void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset);
+#endif
 void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
                           int32_t aStart, uint32_t aLen, bool aIsInsert,
                           bool aFromUser);
 void ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
                         bool aInsert, bool aFromUser);
 void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
                          uint32_t aType);
 } // namespace a11y
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -886,16 +886,22 @@ Accessible::HandleAccEvent(AccEvent* aEv
         case nsIAccessibleEvent::EVENT_SELECTION_ADD:
         case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
           AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
           uint64_t widgetID = selEvent->Widget()->IsDoc() ? 0 :
             reinterpret_cast<uintptr_t>(selEvent->Widget());
           ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
           break;
         }
+#if defined(XP_WIN)
+        case nsIAccessibleEvent::EVENT_FOCUS: {
+          ipcDoc->SendFocusEvent(id);
+          break;
+        }
+#endif
         default:
           ipcDoc->SendEvent(id, aEvent->GetEventType());
       }
     }
   }
 
   if (nsCoreUtils::AccEventObserversExist()) {
     nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -266,29 +266,37 @@ DocAccessibleParent::RecvStateChangeEven
     new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra,
                                aEnabled);
   nsCoreUtils::DispatchAccEvent(Move(event));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
+DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID,
+#if defined(XP_WIN)
+                                        const LayoutDeviceIntRect& aCaretRect,
+#endif // defined (XP_WIN)
+                                        const int32_t& aOffset)
 {
   if (mShutdown) {
     return IPC_OK();
   }
 
   ProxyAccessible* proxy = GetAccessible(aID);
   if (!proxy) {
     NS_ERROR("unknown caret move event target!");
     return IPC_OK();
   }
 
+#if defined(XP_WIN)
+  ProxyCaretMoveEvent(proxy, aCaretRect);
+#else
   ProxyCaretMoveEvent(proxy, aOffset);
+#endif
 
   if (!nsCoreUtils::AccEventObserversExist()) {
     return IPC_OK();
   }
 
   xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
   xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
   nsIDOMNode* node = nullptr;
@@ -703,12 +711,43 @@ DocAccessibleParent::RecvGetWindowedPlug
   aPluginCOMProxy->Set(IAccessibleHolder::COMPtrType(rawAccPlugin));
 
   return IPC_OK();
 #else
   return IPC_FAIL(this, "Message unsupported in this build configuration");
 #endif
 }
 
+mozilla::ipc::IPCResult
+DocAccessibleParent::RecvFocusEvent(const uint64_t& aID,
+                                    const LayoutDeviceIntRect& aCaretRect)
+{
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
+  ProxyAccessible* proxy = GetAccessible(aID);
+  if (!proxy) {
+    NS_ERROR("no proxy for event!");
+    return IPC_OK();
+  }
+
+  ProxyFocusEvent(proxy, aCaretRect);
+
+  if (!nsCoreUtils::AccEventObserversExist()) {
+    return IPC_OK();
+  }
+
+  xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+  xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+  nsIDOMNode* node = nullptr;
+  bool fromUser = true; // XXX fix me
+  RefPtr<xpcAccEvent> event = new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS,
+                                              xpcAcc, doc, node, fromUser);
+  nsCoreUtils::DispatchAccEvent(Move(event));
+
+  return IPC_OK();
+}
+
 #endif // defined(XP_WIN)
 
 } // a11y
 } // mozilla
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -75,29 +75,35 @@ public:
   virtual mozilla::ipc::IPCResult RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
     override;
   virtual mozilla::ipc::IPCResult RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser)
     override;
   virtual mozilla::ipc::IPCResult RecvStateChangeEvent(const uint64_t& aID,
                                                        const uint64_t& aState,
                                                        const bool& aEnabled) override final;
 
-  virtual mozilla::ipc::IPCResult RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
-    override final;
+  virtual mozilla::ipc::IPCResult RecvCaretMoveEvent(const uint64_t& aID,
+#if defined(XP_WIN)
+                                                     const LayoutDeviceIntRect& aCaretRect,
+#endif
+                                                     const int32_t& aOffset) override final;
 
   virtual mozilla::ipc::IPCResult RecvTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                                                       const int32_t& aStart, const uint32_t& aLen,
                                                       const bool& aIsInsert,
                                                       const bool& aFromUser) override;
 
 #if defined(XP_WIN)
   virtual mozilla::ipc::IPCResult RecvSyncTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                                                           const int32_t& aStart, const uint32_t& aLen,
                                                           const bool& aIsInsert,
                                                           const bool& aFromUser) override;
+
+  virtual mozilla::ipc::IPCResult RecvFocusEvent(const uint64_t& aID,
+                                                 const LayoutDeviceIntRect& aCaretRect) override;
 #endif // defined(XP_WIN)
 
   virtual mozilla::ipc::IPCResult RecvSelectionEvent(const uint64_t& aID,
                                                      const uint64_t& aWidgetID,
                                                      const uint32_t& aType) override;
 
   virtual mozilla::ipc::IPCResult RecvRoleChangedEvent(const uint32_t& aRole) override final;
 
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -154,25 +154,74 @@ DocAccessibleChild::SendStateChangeEvent
     return PDocAccessibleChild::SendStateChangeEvent(aID, aState, aEnabled);
   }
 
   PushDeferredEvent(MakeUnique<SerializedStateChange>(this, aID, aState,
                                                       aEnabled));
   return true;
 }
 
+LayoutDeviceIntRect
+DocAccessibleChild::GetCaretRectFor(const uint64_t& aID)
+{
+  Accessible* target;
+
+  if (aID) {
+    target = reinterpret_cast<Accessible*>(aID);
+  } else {
+    target = mDoc;
+  }
+
+  MOZ_ASSERT(target);
+
+  HyperTextAccessible* text = target->AsHyperText();
+  if (!text) {
+    return LayoutDeviceIntRect();
+  }
+
+  nsIWidget* widget = nullptr;
+  return text->GetCaretRect(&widget);
+}
+
+bool
+DocAccessibleChild::SendFocusEvent(const uint64_t& aID)
+{
+  return SendFocusEvent(aID, GetCaretRectFor(aID));
+}
+
+bool
+DocAccessibleChild::SendFocusEvent(const uint64_t& aID,
+                                   const LayoutDeviceIntRect& aCaretRect)
+{
+  if (IsConstructedInParentProcess()) {
+    return PDocAccessibleChild::SendFocusEvent(aID, aCaretRect);
+  }
+
+  PushDeferredEvent(MakeUnique<SerializedFocus>(this, aID, aCaretRect));
+  return true;
+}
+
 bool
 DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID,
                                        const int32_t& aOffset)
 {
+  return SendCaretMoveEvent(aID, GetCaretRectFor(aID), aOffset);
+}
+
+bool
+DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID,
+                                       const LayoutDeviceIntRect& aCaretRect,
+                                       const int32_t& aOffset)
+{
   if (IsConstructedInParentProcess()) {
-    return PDocAccessibleChild::SendCaretMoveEvent(aID, aOffset);
+    return PDocAccessibleChild::SendCaretMoveEvent(aID, aCaretRect, aOffset);
   }
 
-  PushDeferredEvent(MakeUnique<SerializedCaretMove>(this, aID, aOffset));
+  PushDeferredEvent(MakeUnique<SerializedCaretMove>(this, aID, aCaretRect,
+                                                    aOffset));
   return true;
 }
 
 bool
 DocAccessibleChild::SendTextChangeEvent(const uint64_t& aID,
                                         const nsString& aStr,
                                         const int32_t& aStart,
                                         const uint32_t& aLen,
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -38,16 +38,22 @@ public:
 
   IAccessible* GetParentIAccessible() const { return mParentProxy.get(); }
 
   bool SendEvent(const uint64_t& aID, const uint32_t& type);
   bool SendHideEvent(const uint64_t& aRootID, const bool& aFromUser);
   bool SendStateChangeEvent(const uint64_t& aID, const uint64_t& aState,
                             const bool& aEnabled);
   bool SendCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset);
+  bool SendCaretMoveEvent(const uint64_t& aID,
+                          const LayoutDeviceIntRect& aCaretRect,
+                          const int32_t& aOffset);
+  bool SendFocusEvent(const uint64_t& aID);
+  bool SendFocusEvent(const uint64_t& aID,
+                      const LayoutDeviceIntRect& aCaretRect);
   bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                            const int32_t& aStart, const uint32_t& aLen,
                            const bool& aIsInsert, const bool& aFromUser);
   bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID,
                           const uint32_t& aType);
   bool SendRoleChangedEvent(const uint32_t& aRole);
 
   bool ConstructChildDocInParentProcess(DocAccessibleChild* aNewChildDoc,
@@ -60,16 +66,18 @@ protected:
   virtual void MaybeSendShowEvent(ShowEventData& aData, bool aFromUser) override;
 
 private:
   void RemoveDeferredConstructor();
 
   bool IsConstructedInParentProcess() const { return mIsRemoteConstructed; }
   void SetConstructedInParentProcess() { mIsRemoteConstructed = true; }
 
+  LayoutDeviceIntRect GetCaretRectFor(const uint64_t& aID);
+
   /**
    * DocAccessibleChild should not fire events until it has asynchronously
    * received the COM proxy for its parent. OTOH, content a11y may still be
    * attempting to fire events during this window of time. If this object does
    * not yet have its parent proxy, instead of immediately sending the events to
    * our parent, we enqueue them to mDeferredEvents. As soon as
    * RecvParentCOMProxy is called, we play back mDeferredEvents.
    */
@@ -154,29 +162,49 @@ private:
     uint64_t  mID;
     uint64_t  mState;
     bool      mEnabled;
   };
 
   struct SerializedCaretMove final : public DeferredEvent
   {
     SerializedCaretMove(DocAccessibleChild* aTarget, uint64_t aID,
-                        int32_t aOffset)
+                        const LayoutDeviceIntRect& aCaretRect, int32_t aOffset)
       : DeferredEvent(aTarget)
       , mID(aID)
+      , mCaretRect(aCaretRect)
       , mOffset(aOffset)
     {}
 
     void Dispatch(DocAccessibleChild* aIPCDoc) override
     {
-      Unused << aIPCDoc->SendCaretMoveEvent(mID, mOffset);
+      Unused << aIPCDoc->SendCaretMoveEvent(mID, mCaretRect, mOffset);
     }
 
-    uint64_t  mID;
-    int32_t   mOffset;
+    uint64_t            mID;
+    LayoutDeviceIntRect mCaretRect;
+    int32_t             mOffset;
+  };
+
+  struct SerializedFocus final : public DeferredEvent
+  {
+    SerializedFocus(DocAccessibleChild* aTarget, uint64_t aID,
+                    const LayoutDeviceIntRect& aCaretRect)
+      : DeferredEvent(aTarget)
+      , mID(aID)
+      , mCaretRect(aCaretRect)
+    {}
+
+    void Dispatch(DocAccessibleChild* aIPCDoc) override
+    {
+      Unused << aIPCDoc->SendFocusEvent(mID, mCaretRect);
+    }
+
+    uint64_t            mID;
+    LayoutDeviceIntRect mCaretRect;
   };
 
   struct SerializedTextChange final : public DeferredEvent
   {
     SerializedTextChange(DocAccessibleChild* aTarget, uint64_t aID,
                          const nsString& aStr, int32_t aStart, uint32_t aLen,
                          bool aIsInsert, bool aFromUser)
       : DeferredEvent(aTarget)
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -4,16 +4,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/. */
 
 include protocol PFileDescriptorSet;
 include protocol PBrowser;
 
 using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
 using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
+using mozilla::LayoutDeviceIntRect from "Units.h";
 
 namespace mozilla {
 namespace a11y {
 
 struct AccessibleData
 {
   uint64_t ID;
   int32_t MsaaID;
@@ -45,23 +46,25 @@ parent:
   /*
    * Notify the parent process the document in the child process is firing an
    * event.
    */
   async Event(uint64_t aID, uint32_t type);
   async ShowEvent(ShowEventData data, bool aFromUser);
   async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
-  async CaretMoveEvent(uint64_t aID, int32_t aOffset);
+  async CaretMoveEvent(uint64_t aID, LayoutDeviceIntRect aCaretRect,
+                       int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
   sync SyncTextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart,
                            uint32_t aLen, bool aIsInsert, bool aFromUser);
   async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
   async RoleChangedEvent(uint32_t aRole);
+  async FocusEvent(uint64_t aID, LayoutDeviceIntRect aCaretRect);
 
   /*
    * Tell the parent document to bind the existing document as a new child
    * document.
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
 
   sync GetWindowedPluginIAccessible(WindowsHandle aHwnd)
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -77,17 +77,17 @@ AccessibleWrap::AccessibleWrap(nsIConten
   Accessible(aContent, aDoc)
   , mID(kNoID)
 {
 }
 
 AccessibleWrap::~AccessibleWrap()
 {
   if (mID != kNoID) {
-    sIDGen.ReleaseID(this);
+    sIDGen.ReleaseID(WrapNotNull(this));
   }
 }
 
 ITypeInfo* AccessibleWrap::gTypeInfo = nullptr;
 
 NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible)
 
 void
@@ -129,17 +129,17 @@ AccessibleWrap::QueryInterface(REFIID ii
 
     *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
   } else if (IID_IServiceProvider == iid)
     *ppv = new ServiceProvider(this);
   else if (IID_ISimpleDOMNode == iid && !IsProxy()) {
     if (IsDefunct() || (!HasOwnContent() && !IsDoc()))
       return E_NOINTERFACE;
 
-    *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(GetNode()));
+    *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(WrapNotNull(this)));
   }
 
   if (nullptr == *ppv) {
     HRESULT hr = ia2Accessible::QueryInterface(iid, ppv);
     if (SUCCEEDED(hr))
       return hr;
   }
 
@@ -1554,29 +1554,53 @@ AccessibleWrap::UpdateSystemCaretFor(Acc
   ::DestroyCaret();
 
   HyperTextAccessible* text = aAccessible->AsHyperText();
   if (!text)
     return;
 
   nsIWidget* widget = nullptr;
   LayoutDeviceIntRect caretRect = text->GetCaretRect(&widget);
-  HWND caretWnd;
-  if (caretRect.IsEmpty() || !(caretWnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW))) {
+
+  if (!widget) {
+    return;
+  }
+
+  HWND caretWnd = reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+  UpdateSystemCaretFor(caretWnd, caretRect);
+}
+
+/* static */ void
+AccessibleWrap::UpdateSystemCaretFor(ProxyAccessible* aProxy,
+                                     const LayoutDeviceIntRect& aCaretRect)
+{
+  ::DestroyCaret();
+
+  // The HWND should be the real widget HWND, not an emulated HWND.
+  // We get the HWND from the proxy's outer doc to bypass window emulation.
+  Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+  UpdateSystemCaretFor(GetHWNDFor(outerDoc), aCaretRect);
+}
+
+/* static */ void
+AccessibleWrap::UpdateSystemCaretFor(HWND aCaretWnd,
+                                     const LayoutDeviceIntRect& aCaretRect)
+{
+  if (!aCaretWnd || aCaretRect.IsEmpty()) {
     return;
   }
 
   // Create invisible bitmap for caret, otherwise its appearance interferes
   // with Gecko caret
-  nsAutoBitmap caretBitMap(CreateBitmap(1, caretRect.height, 1, 1, nullptr));
-  if (::CreateCaret(caretWnd, caretBitMap, 1, caretRect.height)) {  // Also destroys the last caret
-    ::ShowCaret(caretWnd);
+  nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.height, 1, 1, nullptr));
+  if (::CreateCaret(aCaretWnd, caretBitMap, 1, aCaretRect.height)) {  // Also destroys the last caret
+    ::ShowCaret(aCaretWnd);
     RECT windowRect;
-    ::GetWindowRect(caretWnd, &windowRect);
-    ::SetCaretPos(caretRect.x - windowRect.left, caretRect.y - windowRect.top);
+    ::GetWindowRect(aCaretWnd, &windowRect);
+    ::SetCaretPos(aCaretRect.x - windowRect.left, aCaretRect.y - windowRect.top);
   }
 }
 
 ITypeInfo*
 AccessibleWrap::GetTI(LCID lcid)
 {
   if (gTypeInfo)
     return gTypeInfo;
@@ -1680,8 +1704,19 @@ AccessibleWrap::DispatchTextChangeToHand
   HRESULT hr = ASYNC_INVOKE(invoker, OnTextChange, PtrToLong(hwnd), msaaId,
                             isInsert, &textSegment);
 
   ::SysFreeString(textSegment.text);
 
   return SUCCEEDED(hr);
 }
 
+/* static */ void
+AccessibleWrap::AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc)
+{
+  aSdnAcc->SetUniqueID(sIDGen.GetID());
+}
+
+/* static */ void
+AccessibleWrap::ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc)
+{
+  sIDGen.ReleaseID(aSdnAcc);
+}
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -167,17 +167,24 @@ public: // construction, destruction
   /**
    * System caret support: update the Windows caret position. 
    * The system caret works more universally than the MSAA caret
    * For example, Window-Eyes, JAWS, ZoomText and Windows Tablet Edition use it
    * We will use an invisible system caret.
    * Gecko is still responsible for drawing its own caret
    */
   void UpdateSystemCaretFor(Accessible* aAccessible);
+  static void UpdateSystemCaretFor(ProxyAccessible* aProxy,
+                                   const LayoutDeviceIntRect& aCaretRect);
 
+private:
+  static void UpdateSystemCaretFor(HWND aCaretWnd,
+                                   const LayoutDeviceIntRect& aCaretRect);
+
+public:
   /**
    * Find an accessible by the given child ID in cached documents.
    */
   MOZ_MUST_USE already_AddRefed<IAccessible>
   GetIAccessibleFor(const VARIANT& aVarChild, bool* aIsDefunct);
 
   virtual void GetNativeInterface(void **aOutAccessible) override;
 
@@ -189,16 +196,20 @@ public: // construction, destruction
 
   static uint32_t GetContentProcessIdFor(dom::ContentParentId aIPCContentId);
   static void ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId);
 
   static void SetHandlerControl(DWORD aPid, RefPtr<IHandlerControl> aCtrl);
 
   bool DispatchTextChangeToHandler(bool aIsInsert, const nsString& aText,
                                    int32_t aStart, uint32_t aLen);
+
+  static void AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc);
+  static void ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc);
+
 protected:
   virtual ~AccessibleWrap();
 
   uint32_t mID;
 
   HRESULT
   ResolveChild(const VARIANT& aVarChild, IAccessible** aOutInterface);
 
--- a/accessible/windows/msaa/MsaaIdGenerator.cpp
+++ b/accessible/windows/msaa/MsaaIdGenerator.cpp
@@ -2,22 +2,25 @@
 /* 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 "MsaaIdGenerator.h"
 
 #include "mozilla/a11y/AccessibleWrap.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 #include "nsDataHashtable.h"
 #include "nsIXULRuntime.h"
+#include "sdnAccessible.h"
 
 // These constants may be adjusted to modify the proportion of the Child ID
 // allocated to the content ID vs proportion allocated to the unique ID. They
 // must always sum to 31, ie. the width of a 32-bit integer less the sign bit.
 
 // NB: kNumContentProcessIDBits must be large enough to successfully hold the
 // maximum permitted number of e10s content processes. If the e10s maximum
 // number of content processes changes, then kNumContentProcessIDBits must also
@@ -90,32 +93,48 @@ constexpr MsaaIdGenerator::MsaaIdGenerat
 uint32_t
 MsaaIdGenerator::GetID()
 {
   uint32_t id = mIDSet.GetID();
   MOZ_ASSERT(id <= ((1UL << kNumUniqueIDBits) - 1UL));
   return detail::BuildMsaaID(id, ResolveContentProcessID());
 }
 
-void
-MsaaIdGenerator::ReleaseID(AccessibleWrap* aAccWrap)
+bool
+MsaaIdGenerator::ReleaseID(uint32_t aID)
 {
-  MOZ_ASSERT(aAccWrap);
-  uint32_t id = aAccWrap->GetExistingID();
-  MOZ_ASSERT(id != AccessibleWrap::kNoID);
-  detail::MsaaIDCracker cracked(id);
+  MOZ_ASSERT(aID != AccessibleWrap::kNoID);
+  detail::MsaaIDCracker cracked(aID);
   if (cracked.GetContentProcessId() != ResolveContentProcessID()) {
+    return false;
+  }
+  mIDSet.ReleaseID(cracked.GetUniqueId());
+  return true;
+}
+
+void
+MsaaIdGenerator::ReleaseID(NotNull<AccessibleWrap*> aAccWrap)
+{
+  if (!ReleaseID(aAccWrap->GetExistingID())) {
     // This may happen if chrome holds a proxy whose ID was originally generated
     // by a content process. Since ReleaseID only has meaning in the process
     // that originally generated that ID, we ignore ReleaseID calls for any ID
     // that did not come from the current process.
     MOZ_ASSERT(aAccWrap->IsProxy());
-    return;
   }
-  mIDSet.ReleaseID(cracked.GetUniqueId());
+}
+
+void
+MsaaIdGenerator::ReleaseID(NotNull<sdnAccessible*> aSdnAcc)
+{
+  Maybe<uint32_t> id = aSdnAcc->ReleaseUniqueID();
+  if (id.isSome()) {
+    DebugOnly<bool> released = ReleaseID(id.value());
+    MOZ_ASSERT(released);
+  }
 }
 
 bool
 MsaaIdGenerator::IsChromeID(uint32_t aID)
 {
   detail::MsaaIDCracker cracked(aID);
   return cracked.GetContentProcessId() == 0;
 }
--- a/accessible/windows/msaa/MsaaIdGenerator.h
+++ b/accessible/windows/msaa/MsaaIdGenerator.h
@@ -5,21 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_MsaaIdGenerator_h
 #define mozilla_a11y_MsaaIdGenerator_h
 
 #include "mozilla/a11y/IDSet.h"
 
 #include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/NotNull.h"
 
 namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap;
+class sdnAccessible;
 
 /**
  * This class is responsible for generating child IDs used by our MSAA
  * implementation. Since e10s requires us to differentiate IDs based on the
  * originating process of the accessible, a portion of the ID's bits are
  * allocated to storing that information. The remaining bits represent the
  * unique ID of the accessible, within that content process.
  *
@@ -28,27 +30,29 @@ class AccessibleWrap;
  * are allocated for each purpose.
  */
 class MsaaIdGenerator
 {
 public:
   constexpr MsaaIdGenerator();
 
   uint32_t GetID();
-  void ReleaseID(AccessibleWrap* aAccWrap);
+  void ReleaseID(NotNull<AccessibleWrap*> aAccWrap);
+  void ReleaseID(NotNull<sdnAccessible*> aSdnAcc);
   bool IsChromeID(uint32_t aID);
   bool IsIDForThisContentProcess(uint32_t aID);
   bool IsIDForContentProcess(uint32_t aID,
                              dom::ContentParentId aIPCContentProcessId);
   bool IsSameContentProcessFor(uint32_t aFirstID, uint32_t aSecondID);
 
   uint32_t GetContentProcessIDFor(dom::ContentParentId aIPCContentProcessID);
   void ReleaseContentProcessIDFor(dom::ContentParentId aIPCContentProcessID);
 
 private:
+  bool ReleaseID(uint32_t aID);
   uint32_t ResolveContentProcessID();
 
 private:
   IDSet     mIDSet;
 };
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -111,18 +111,29 @@ a11y::ProxyEvent(ProxyAccessible* aTarge
 void
 a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t, bool)
 {
   AccessibleWrap::FireWinEvent(WrapperFor(aTarget),
                                nsIAccessibleEvent::EVENT_STATE_CHANGE);
 }
 
 void
-a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+a11y::ProxyFocusEvent(ProxyAccessible* aTarget,
+                      const LayoutDeviceIntRect& aCaretRect)
 {
+  AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect);
+  AccessibleWrap::FireWinEvent(WrapperFor(aTarget),
+                               nsIAccessibleEvent::EVENT_FOCUS);
+}
+
+void
+a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget,
+                          const LayoutDeviceIntRect& aCaretRect)
+{
+  AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect);
   AccessibleWrap::FireWinEvent(WrapperFor(aTarget),
                                nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED);
 }
 
 void
 a11y::ProxyTextChangeEvent(ProxyAccessible* aText, const nsString& aStr,
                            int32_t aStart, uint32_t aLen, bool aInsert, bool)
 {
--- a/accessible/windows/sdn/sdnAccessible-inl.h
+++ b/accessible/windows/sdn/sdnAccessible-inl.h
@@ -16,19 +16,31 @@ namespace mozilla {
 namespace a11y {
 
 inline DocAccessible*
 sdnAccessible::GetDocument() const
 {
   return GetExistingDocAccessible(mNode->OwnerDoc());
 }
 
-inline Accessible*
-sdnAccessible::GetAccessible() const
+inline AccessibleWrap*
+sdnAccessible::GetAccessible()
 {
+  if (mWrap) {
+    return mWrap;
+  }
+
   DocAccessible* document = GetDocument();
-  return document ? document->GetAccessibleEvenIfNotInMap(mNode) : nullptr;
+  if (!document) {
+    return nullptr;
+  }
+
+  // Once we have an accessible, we should hold a reference to it so that we
+  // may preserve object identity.
+  mWrap =
+    static_cast<AccessibleWrap*>(document->GetAccessibleEvenIfNotInMap(mNode));
+  return mWrap;
 }
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_sdnAccessible_inl_h_
--- a/accessible/windows/sdn/sdnAccessible.cpp
+++ b/accessible/windows/sdn/sdnAccessible.cpp
@@ -20,30 +20,37 @@
 #include "nsRange.h"
 
 #include "mozilla/dom/BorrowedAttrInfo.h"
 #include "mozilla/dom/Element.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
+sdnAccessible::~sdnAccessible()
+{
+  if (mUniqueId.isSome()) {
+    AccessibleWrap::ReleaseChildID(WrapNotNull(this));
+  }
+}
+
 STDMETHODIMP
 sdnAccessible::QueryInterface(REFIID aREFIID, void** aInstancePtr)
 {
   if (!aInstancePtr)
     return E_FAIL;
   *aInstancePtr = nullptr;
 
   if (aREFIID == IID_ISimpleDOMNode) {
     *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
     AddRef();
     return S_OK;
   }
 
-  AccessibleWrap* accessible = static_cast<AccessibleWrap*>(GetAccessible());
+  AccessibleWrap* accessible = GetAccessible();
   if (accessible)
     return accessible->QueryInterface(aREFIID, aInstancePtr);
 
   // IUnknown* is the canonical one if and only if this accessible doesn't have
   // an accessible.
   if (aREFIID == IID_IUnknown) {
     *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
     AddRef();
@@ -93,21 +100,25 @@ sdnAccessible::get_nodeInfo(BSTR __RPC_F
 
   *aNameSpaceID = mNode->IsNodeOfType(nsINode::eCONTENT) ?
     static_cast<short>(mNode->AsContent()->GetNameSpaceID()) : 0;
 
   // This is a unique ID for every content node. The 3rd party accessibility
   // application can compare this to the childID we return for events such as
   // focus events, to correlate back to data nodes in their internal object
   // model.
-  Accessible* accessible = GetAccessible();
+  AccessibleWrap* accessible = GetAccessible();
   if (accessible) {
     *aUniqueID = AccessibleWrap::GetChildIDFor(accessible);
   } else {
-    *aUniqueID = - NS_PTR_TO_INT32(static_cast<void*>(this));
+    if (mUniqueId.isNothing()) {
+      AccessibleWrap::AssignChildIDTo(WrapNotNull(this));
+    }
+    MOZ_ASSERT(mUniqueId.isSome());
+    *aUniqueID = mUniqueId.value();
   }
 
   *aNumChildren = mNode->GetChildCount();
 
   return S_OK;
 }
 
 STDMETHODIMP
--- a/accessible/windows/sdn/sdnAccessible.h
+++ b/accessible/windows/sdn/sdnAccessible.h
@@ -7,45 +7,66 @@
 #ifndef mozilla_a11y_sdnAccessible_h_
 #define mozilla_a11y_sdnAccessible_h_
 
 #include "ISimpleDOM.h"
 #include "AccessibleWrap.h"
 #include "IUnknownImpl.h"
 
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
 
 namespace mozilla {
 namespace a11y {
 
 class sdnAccessible final : public ISimpleDOMNode
 {
 public:
   explicit sdnAccessible(nsINode* aNode) :
     mNode(aNode)
   {
     if (!mNode)
       MOZ_CRASH();
   }
-  ~sdnAccessible() { }
+
+  explicit sdnAccessible(NotNull<AccessibleWrap*> aAccWrap)
+    : mNode(aAccWrap->GetNode())
+    , mWrap(aAccWrap)
+  {
+  }
+
+  ~sdnAccessible();
 
   /**
-   * Retrun if the object is defunct.
+   * Return if the object is defunct.
    */
   bool IsDefunct() const { return !GetDocument(); }
 
   /**
    * Return a document accessible it belongs to if any.
    */
   DocAccessible* GetDocument() const;
 
   /*
    * Return associated accessible if any.
    */
-  Accessible* GetAccessible() const;
+  AccessibleWrap* GetAccessible();
+
+  void SetUniqueID(uint32_t aNewUniqueId)
+  {
+    mUniqueId = Some(aNewUniqueId);
+  }
+
+  Maybe<uint32_t> ReleaseUniqueID()
+  {
+    Maybe<uint32_t> result = mUniqueId;
+    mUniqueId = Nothing();
+    return result;
+  }
 
   //IUnknown
   DECL_IUNKNOWN
 
   virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo(
     /* [out] */ BSTR __RPC_FAR* aNodeName,
     /* [out] */ short __RPC_FAR* aNameSpaceID,
     /* [out] */ BSTR __RPC_FAR* aNodeValue,
@@ -106,14 +127,16 @@ public:
   virtual /* [local][propget] */ HRESULT STDMETHODCALLTYPE get_localInterface(
     /* [retval][out] */ void __RPC_FAR *__RPC_FAR* aLocalInterface);
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_language(
     /* [out][retval] */ BSTR __RPC_FAR* aLanguage);
 
 private:
   nsCOMPtr<nsINode> mNode;
+  RefPtr<AccessibleWrap> mWrap;
+  Maybe<uint32_t> mUniqueId;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_sdnAccessible_h_
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -133,16 +133,20 @@ pref("app.update.log", false);
 pref("app.update.backgroundMaxErrors", 10);
 
 // Whether or not app updates are enabled
 pref("app.update.enabled", true);
 
 // Whether or not to use the doorhanger application update UI.
 pref("app.update.doorhanger", true);
 
+// Ids of the links to the "What's new" update documentation
+pref("app.update.link.updateAvailableWhatsNew", "update-available-whats-new");
+pref("app.update.link.updateManualWhatsNew", "update-manual-whats-new");
+
 // How many times we should let downloads fail before prompting the user to
 // download a fresh installer.
 pref("app.update.download.promptMaxAttempts", 2);
 
 // How many times we should let an elevation prompt fail before prompting the user to
 // download a fresh installer.
 pref("app.update.elevation.promptMaxAttempts", 2);
 
@@ -230,21 +234,17 @@ pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
 // At startup, check if we're the default browser and prompt user if not.
 pref("browser.shell.checkDefaultBrowser", true);
 pref("browser.shell.shortcutFavicons",true);
 pref("browser.shell.mostRecentDateSetAsDefault", "");
-#ifdef RELEASE_OR_BETA
-pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", false);
-#else
 pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
-#endif
 pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", false);
 pref("browser.shell.defaultBrowserCheckCount", 0);
 pref("browser.defaultbrowser.notificationbar", false);
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",            "chrome://branding/locale/browserconfig.properties");
@@ -313,17 +313,17 @@ pref("browser.urlbar.restrict.searches",
 pref("browser.urlbar.match.title", "#");
 pref("browser.urlbar.match.url", "@");
 
 // The default behavior for the urlbar can be configured to use any combination
 // of the match filters with each additional filter adding more results (union).
 pref("browser.urlbar.suggest.history",              true);
 pref("browser.urlbar.suggest.bookmark",             true);
 pref("browser.urlbar.suggest.openpage",             true);
-pref("browser.urlbar.suggest.searches",             false);
+pref("browser.urlbar.suggest.searches",             true);
 pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
 // The suggestion opt-in notification will be shown on 4 different days.
 pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4);
 pref("browser.urlbar.lastSuggestionsPromptDate", 20160601);
 // The suggestion opt-out hint will be hidden after being shown 4 times.
 pref("browser.urlbar.timesBeforeHidingSuggestionsHint", 4);
 
 // Limit the number of characters sent to the current search engine to fetch
@@ -332,21 +332,17 @@ pref("browser.urlbar.maxCharsForSearchSu
 
 // Restrictions to current suggestions can also be applied (intersection).
 // Typed suggestion works only if history is set to true.
 pref("browser.urlbar.suggest.history.onlyTyped",    false);
 
 pref("browser.urlbar.formatting.enabled", true);
 pref("browser.urlbar.trimURLs", true);
 
-#if defined(NIGHTLY_BUILD)
 pref("browser.urlbar.oneOffSearches", true);
-#else
-pref("browser.urlbar.oneOffSearches", false);
-#endif
 
 // If changed to true, copying the entire URL from the location bar will put the
 // human readable (percent-decoded) URL on the clipboard.
 pref("browser.urlbar.decodeURLsOnCopy", false);
 
 pref("browser.altClickSave", false);
 
 // Enable logging downloads operations to the Console.
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -510,21 +510,16 @@ const gExtensionsNotifications = {
 
     button.addEventListener("click", callback);
     PanelUI.addonNotificationContainer.appendChild(button);
   },
 
   updateAlerts() {
     let sideloaded = ExtensionsUI.sideloaded;
     let updates = ExtensionsUI.updates;
-    if (sideloaded.size + updates.size == 0) {
-      PanelUI.removeNotification("addon-alert");
-    } else {
-      PanelUI.showBadgeOnlyNotification("addon-alert");
-    }
 
     let container = PanelUI.addonNotificationContainer;
 
     while (container.firstChild) {
       container.firstChild.remove();
     }
 
     let items = 0;
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -138,17 +138,16 @@ var gSync = {
           return;
         }
         this.onClientsSynced();
         break;
     }
   },
 
   updateAllUI(state) {
-    this.updatePanelBadge(state);
     this.updatePanelPopup(state);
     this.updateStateBroadcasters(state);
     this.updateSyncButtonsTooltip(state);
     this.updateSyncStatus(state);
   },
 
   updatePanelPopup(state) {
     let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
@@ -198,25 +197,16 @@ var gSync = {
         if (this.appMenuAvatar.style.listStyleImage === bgImage) {
           this.appMenuAvatar.style.removeProperty("list-style-image");
         }
       };
       img.src = state.avatarURL;
     }
   },
 
-  updatePanelBadge(state) {
-    if (state.status == UIState.STATUS_LOGIN_FAILED ||
-        state.status == UIState.STATUS_NOT_VERIFIED) {
-      PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
-    } else {
-      PanelUI.removeNotification("fxa-needs-authentication");
-    }
-  },
-
   updateStateBroadcasters(state) {
     const status = state.status;
 
     // Start off with a clean slate
     document.getElementById("sync-reauth-state").hidden = true;
     document.getElementById("sync-setup-state").hidden = true;
     document.getElementById("sync-syncnow-state").hidden = true;
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -219,17 +219,17 @@ toolbar[overflowable] > .customization-t
   overflow: hidden;
 }
 
 toolbar:not([overflowing]) > .overflow-button,
 toolbar[customizing] > .overflow-button {
   display: none;
 }
 
-#nav-bar[nonemptyoverflow] > .overflow-button,
+window:not([chromehidden~="toolbar"]) #nav-bar[nonemptyoverflow] > .overflow-button,
 #nav-bar[customizing][photon-structure] > .overflow-button {
   display: -moz-box;
 }
 
 /* The ids are ugly, but this should be reasonably performant, and
  * using a tagname as the last item would be less so.
  */
 #widget-overflow-list:empty + #widget-overflow-fixed-separator,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1632,18 +1632,16 @@ var gBrowserInit = {
     // initialize the sync UI
     gSync.init();
 
     if (AppConstants.MOZ_DATA_REPORTING)
       gDataNotificationInfoBar.init();
 
     gBrowserThumbnails.init();
 
-    gMenuButtonUpdateBadge.init();
-
     gExtensionsNotifications.init();
 
     let wasMinimized = window.windowState == window.STATE_MINIMIZED;
     window.addEventListener("sizemodechange", () => {
       let isMinimized = window.windowState == window.STATE_MINIMIZED;
       if (wasMinimized != isMinimized) {
         wasMinimized = isMinimized;
         UpdatePopupNotificationsVisibility();
@@ -1795,18 +1793,16 @@ var gBrowserInit = {
     CompactTheme.uninit();
 
     TrackingProtection.uninit();
 
     RefreshBlocker.uninit();
 
     CaptivePortalWatcher.uninit();
 
-    gMenuButtonUpdateBadge.uninit();
-
     SidebarUI.uninit();
 
     // Now either cancel delayedStartup, or clean up the services initialized from
     // it.
     if (this._boundDelayedStartup) {
       this._cancelDelayedStartup();
     } else {
       if (Win7Features)
@@ -2852,234 +2848,16 @@ function UpdatePopupNotificationsVisibil
   PopupNotifications.anchorVisibilityChange();
 }
 
 function PageProxyClickHandler(aEvent) {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
-// Setup the hamburger button badges for updates, if enabled.
-var gMenuButtonUpdateBadge = {
-  kTopics: [
-    "update-staged",
-    "update-downloaded",
-    "update-available",
-    "update-error",
-  ],
-
-  timeouts: [],
-
-  get enabled() {
-    return Services.prefs.getBoolPref("app.update.doorhanger", false);
-  },
-
-  get badgeWaitTime() {
-    return Services.prefs.getIntPref("app.update.badgeWaitTime", 4 * 24 * 3600); // 4 days
-  },
-
-  init() {
-    if (this.enabled) {
-      this.kTopics.forEach(t => {
-        Services.obs.addObserver(this, t);
-      });
-    }
-  },
-
-  uninit() {
-    if (this.enabled) {
-      this.kTopics.forEach(t => {
-        Services.obs.removeObserver(this, t);
-      });
-    }
-
-    this.reset();
-  },
-
-  reset() {
-    PanelUI.removeNotification(/^update-/);
-    this.clearCallbacks();
-  },
-
-  clearCallbacks() {
-    this.timeouts.forEach(t => clearTimeout(t));
-    this.timeouts = [];
-  },
-
-  addTimeout(time, callback) {
-    this.timeouts.push(setTimeout(() => {
-      this.clearCallbacks();
-      callback();
-    }, time));
-  },
-
-  replaceReleaseNotes(update, whatsNewId) {
-    let whatsNewLink = document.getElementById(whatsNewId);
-    if (update && update.detailsURL) {
-      whatsNewLink.href = update.detailsURL;
-    } else {
-      whatsNewLink.href = Services.urlFormatter.formatURLPref("app.update.url.details");
-    }
-  },
-
-  requestRestart() {
-    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
-                     .createInstance(Ci.nsISupportsPRBool);
-    Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
-
-    if (!cancelQuit.data) {
-      Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
-    }
-  },
-
-  openManualUpdateUrl() {
-    let manualUpdateUrl = Services.urlFormatter.formatURLPref("app.update.url.manual");
-    openUILinkIn(manualUpdateUrl, "tab");
-  },
-
-  showUpdateNotification(type, dismissed, mainAction) {
-    let action = {
-      callback(fromDoorhanger) {
-        if (fromDoorhanger) {
-          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER").add(type);
-        } else {
-          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_MENU").add(type);
-        }
-        mainAction();
-      }
-    };
-
-    let secondaryAction = {
-      callback() {
-        Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_DISMISSED").add(type);
-      },
-      dismiss: true
-    };
-
-    PanelUI.showNotification("update-" + type, action, [secondaryAction], { dismissed });
-    Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_SHOWN").add(type);
-  },
-
-  showRestartNotification(dismissed) {
-    this.showUpdateNotification("restart", dismissed, () => gMenuButtonUpdateBadge.requestRestart());
-  },
-
-  showUpdateAvailableNotification(update, dismissed) {
-    this.replaceReleaseNotes(update, "update-available-whats-new");
-    this.showUpdateNotification("available", dismissed, () => {
-      let updateService = Cc["@mozilla.org/updates/update-service;1"]
-                          .getService(Ci.nsIApplicationUpdateService);
-      updateService.downloadUpdate(update, true);
-    });
-  },
-
-  showManualUpdateNotification(update, dismissed) {
-    this.replaceReleaseNotes(update, "update-manual-whats-new");
-
-    this.showUpdateNotification("manual", dismissed, () => gMenuButtonUpdateBadge.openManualUpdateUrl());
-  },
-
-  handleUpdateError(update, status) {
-    switch (status) {
-      case "download-attempt-failed":
-        this.clearCallbacks();
-        this.showUpdateAvailableNotification(update, false);
-        break;
-      case "download-attempts-exceeded":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-      case "elevation-attempt-failed":
-        this.clearCallbacks();
-        this.showRestartNotification(update, false);
-        break;
-      case "elevation-attempts-exceeded":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-      case "check-attempts-exceeded":
-      case "unknown":
-        // Background update has failed, let's show the UI responsible for
-        // prompting the user to update manually.
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-    }
-  },
-
-  handleUpdateStagedOrDownloaded(update, status) {
-    switch (status) {
-      case "applied":
-      case "pending":
-      case "applied-service":
-      case "pending-service":
-      case "success":
-        this.clearCallbacks();
-
-        let badgeWaitTimeMs = this.badgeWaitTime * 1000;
-        let doorhangerWaitTimeMs = update.promptWaitTime * 1000;
-
-        if (badgeWaitTimeMs < doorhangerWaitTimeMs) {
-          this.addTimeout(badgeWaitTimeMs, () => {
-            this.showRestartNotification(true);
-
-            // doorhangerWaitTimeMs is relative to when we initially received
-            // the event. Since we've already waited badgeWaitTimeMs, subtract
-            // that from doorhangerWaitTimeMs.
-            let remainingTime = doorhangerWaitTimeMs - badgeWaitTimeMs;
-            this.addTimeout(remainingTime, () => {
-              this.showRestartNotification(false);
-            });
-          });
-        } else {
-          this.addTimeout(doorhangerWaitTimeMs, () => {
-            this.showRestartNotification(false);
-          });
-        }
-        break;
-    }
-  },
-
-  handleUpdateAvailable(update, status) {
-    switch (status) {
-      case "show-prompt":
-        // If an update is available and had the showPrompt flag set, then
-        // show an update available doorhanger.
-        this.clearCallbacks();
-        this.showUpdateAvailableNotification(update, false);
-        break;
-      case "cant-apply":
-        this.clearCallbacks();
-        this.showManualUpdateNotification(update, false);
-        break;
-    }
-  },
-
-  observe(subject, topic, status) {
-    if (!this.enabled) {
-      return;
-    }
-
-    let update = subject && subject.QueryInterface(Ci.nsIUpdate);
-
-    switch (topic) {
-      case "update-available":
-        this.handleUpdateAvailable(update, status);
-        break;
-      case "update-staged":
-      case "update-downloaded":
-        this.handleUpdateStagedOrDownloaded(update, status);
-        break;
-      case "update-error":
-        this.handleUpdateError(update, status);
-        break;
-    }
-  }
-};
-
 // Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED   = 2;
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED = 3;
 const TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND    = 4;
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND      = 5;
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
@@ -5683,29 +5461,35 @@ const nodeToTooltipMap = {
   "downloads-button": "downloads.tooltip",
   "fullscreen-button": "fullscreenButton.tooltip",
   "new-window-button": "newWindowButton.tooltip",
   "new-tab-button": "newTabButton.tooltip",
   "tabs-newtab-button": "newTabButton.tooltip",
   "reload-button": "reloadButton.tooltip",
   "stop-button": "stopButton.tooltip",
   "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
+  "appMenu-cut-button": "cut-button.tooltip",
+  "appMenu-copy-button": "copy-button.tooltip",
+  "appMenu-paste-button": "paste-button.tooltip",
 };
 const nodeToShortcutMap = {
   "bookmarks-menu-button": "manBookmarkKb",
   "context-reload": "key_reload",
   "context-stop": "key_stop",
   "downloads-button": "key_openDownloads",
   "fullscreen-button": "key_fullScreen",
   "new-window-button": "key_newNavigator",
   "new-tab-button": "key_newNavigatorTab",
   "tabs-newtab-button": "key_newNavigatorTab",
   "reload-button": "key_reload",
   "stop-button": "key_stop",
   "urlbar-zoom-button": "key_fullZoomReset",
+  "appMenu-cut-button": "key_cut",
+  "appMenu-copy-button": "key_copy",
+  "appMenu-paste-button": "key_paste",
 };
 
 if (AppConstants.platform == "macosx") {
   nodeToTooltipMap["print-button"] = "printButton.tooltip";
   nodeToShortcutMap["print-button"] = "printKb";
 }
 
 const gDynamicTooltipCache = new Map();
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -23,19 +23,16 @@ with Files("newtab/**"):
     BUG_COMPONENT = ("Firefox", "New Tab Page")
 
 with Files("pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("test/alerts/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
-with Files("test/appUpdate/**"):
-    BUG_COMPONENT = ("Toolkit", "Application Update")
-
 with Files("test/captivePortal/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/chrome/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/forms/**"):
     BUG_COMPONENT = ("Core", "Layout: Form Controls")
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -92,24 +92,20 @@ tabpanels {
  * memory that to-be-restored tabs would otherwise consume simply by setting
  * their browsers to 'display: none' as that will prevent them from having to
  * create a presentation and the like.
  */
 browser[pending] {
   display: none;
 }
 
-browser[pendingtabchild],
+browser[blank],
 browser[pendingpaint] {
   opacity: 0;
 }
 
-tabbrowser[pendingtabchild] {
-  background-color: #ffffff !important;
-}
-
 tabbrowser[pendingpaint] {
   background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
   background-repeat: no-repeat;
   background-position: center center;
   background-color: #f9f9f9 !important;
   background-size: 30px;
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -905,27 +905,27 @@
                 aURI = makeURI(aURI);
               }
               PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI);
             }
 
             let sizedIconUrl = browser.mIconURL || "";
             if (sizedIconUrl != aTab.getAttribute("image")) {
               if (sizedIconUrl) {
-                aTab.setAttribute("image", sizedIconUrl);
                 if (!browser.mIconLoadingPrincipal ||
                     !browser.mIconLoadingPrincipal.equals(loadingPrincipal)) {
                   aTab.setAttribute("iconLoadingPrincipal",
                     this.serializationHelper.serializeToString(loadingPrincipal));
                   browser.mIconLoadingPrincipal = loadingPrincipal;
                 }
+                aTab.setAttribute("image", sizedIconUrl);
               } else {
-                aTab.removeAttribute("image");
                 aTab.removeAttribute("iconLoadingPrincipal");
                 delete browser.mIconLoadingPrincipal;
+                aTab.removeAttribute("image");
               }
               this._tabAttrModified(aTab, ["image"]);
             }
 
             this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
           ]]>
         </body>
       </method>
@@ -1241,36 +1241,20 @@
                   if (prompts.length) {
                     // NB: This code assumes that the beforeunload prompt
                     //     is the top-most prompt on the tab.
                     prompts[prompts.length - 1].abortPrompt();
                   }
                 });
               }
 
-              oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
-              if (this.isFindBarInitialized(oldTab)) {
-                let findBar = this.getFindBar(oldTab);
-                oldTab._findBarFocused = (!findBar.hidden &&
-                  findBar._findField.getAttribute("focused") == "true");
+              if (!gMultiProcessBrowser) {
+                this._adjustFocusBeforeTabSwitch(oldTab, this.mCurrentTab);
+                this._adjustFocusAfterTabSwitch(this.mCurrentTab);
               }
-
-              // If focus is in the tab bar, retain it there.
-              if (document.activeElement == oldTab) {
-                // We need to explicitly focus the new tab, because
-                // tabbox.xml does this only in some cases.
-                this.mCurrentTab.focus();
-              } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
-                // Clear focus so that _adjustFocusAfterTabSwitch can detect if
-                // some element has been focused and respect that.
-                document.activeElement.blur();
-              }
-
-              if (!gMultiProcessBrowser)
-                this._adjustFocusAfterTabSwitch(this.mCurrentTab);
             }
 
             updateUserContextUIIndicator();
             gIdentityHandler.updateSharingIndicator();
 
             this.tabContainer._setPositionalAttributes();
 
             if (!gMultiProcessBrowser) {
@@ -1284,16 +1268,55 @@
             }
 
             if (!aForceUpdate)
               TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
           ]]>
         </body>
       </method>
 
+      <method name="_adjustFocusBeforeTabSwitch">
+        <parameter name="oldTab"/>
+        <parameter name="newTab"/>
+        <body><![CDATA[
+          if (this._previewMode) {
+            return;
+          }
+
+          let oldBrowser = oldTab.linkedBrowser;
+          let newBrowser = newTab.linkedBrowser;
+
+          oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+
+          if (this.isFindBarInitialized(oldTab)) {
+            let findBar = this.getFindBar(oldTab);
+            oldTab._findBarFocused = (!findBar.hidden &&
+              findBar._findField.getAttribute("focused") == "true");
+          }
+
+          // If focus is in the tab bar, retain it there.
+          if (document.activeElement == oldTab) {
+            // We need to explicitly focus the new tab, because
+            // tabbox.xml does this only in some cases.
+            newTab.focus();
+          } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
+
+            let keepFocusOnUrlBar = newBrowser &&
+                                    newBrowser._urlbarFocused &&
+                                    gURLBar &&
+                                    gURLBar.focused;
+            if (!keepFocusOnUrlBar) {
+              // Clear focus so that _adjustFocusAfterTabSwitch can detect if
+              // some element has been focused and respect that.
+              document.activeElement.blur();
+            }
+          }
+        ]]></body>
+      </method>
+
       <method name="_adjustFocusAfterTabSwitch">
         <parameter name="newTab"/>
         <body><![CDATA[
         // Don't steal focus from the tab bar.
         if (document.activeElement == newTab)
           return;
 
         let newBrowser = this.getBrowserForTab(newTab);
@@ -1309,22 +1332,25 @@
 
         // Focus the location bar if it was previously focused for that tab.
         // In full screen mode, only bother making the location bar visible
         // if the tab is a blank one.
         if (newBrowser._urlbarFocused && gURLBar) {
           // Explicitly close the popup if the URL bar retains focus
           gURLBar.closePopup();
 
-          if (!window.fullScreen) {
-            gURLBar.focus();
+          // If the user happened to type into the URL bar for this browser
+          // by the time we got here, focusing will cause the text to be
+          // selected which could cause them to overwrite what they've
+          // already typed in.
+          if (gURLBar.focused && newBrowser.userTypedValue) {
             return;
           }
 
-          if (isTabEmpty(this.mCurrentTab)) {
+          if (!window.fullScreen || isTabEmpty(this.mCurrentTab)) {
             focusAndSelectUrlBar();
             return;
           }
         }
 
         // Focus the find bar if it was previously focused for that tab.
         if (gFindBarInitialized && !gFindBar.hidden &&
             this.selectedTab._findBarFocused) {
@@ -1526,16 +1552,17 @@
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aNextTabParentId;
+            var aFocusUrlBar;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -1551,16 +1578,17 @@
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aNextTabParentId          = params.nextTabParentId;
+              aFocusUrlBar              = params.focusUrlBar;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
 
             var tab = this.addTab(aURI, {
                                   triggeringPrincipal: aTriggeringPrincipal,
@@ -1578,17 +1606,18 @@
                                   createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
                                   isPrerendered: aIsPrerendered,
-                                  nextTabParentId: aNextTabParentId });
+                                  nextTabParentId: aNextTabParentId,
+                                  focusUrlBar: aFocusUrlBar });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
         </body>
       </method>
 
@@ -2283,16 +2312,17 @@
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
             var aNextTabParentId;
             var aNoInitialLabel;
+            var aFocusUrlBar;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal;
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -2312,16 +2342,17 @@
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
               aNextTabParentId          = params.nextTabParentId;
               aNoInitialLabel           = params.noInitialLabel;
+              aFocusUrlBar              = params.focusUrlBar;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2373,16 +2404,19 @@
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
             let animate = !aSkipAnimation &&
                           this.tabContainer.getAttribute("overflow") != "true" &&
                           Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled");
             if (!animate) {
               t.setAttribute("fadein", "true");
+
+              // Call _handleNewTab asynchronously as it needs to know if the
+              // new tab is selected.
               setTimeout(function(tabContainer) {
                 tabContainer._handleNewTab(t);
               }, 0, this.tabContainer);
             }
 
             // invalidate cache
             this._visibleTabs = null;
 
@@ -2435,16 +2469,21 @@
                                         userContextId: aUserContextId,
                                         sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                         opener: aOpener,
                                         isPrerendered: aIsPrerendered,
                                         nextTabParentId: aNextTabParentId });
             }
 
             t.linkedBrowser = b;
+
+            if (aFocusUrlBar) {
+              b._urlbarFocused = true;
+            }
+
             this._tabForBrowser.set(b, t);
             t.permanentKey = b.permanentKey;
             t._browserParams = { uriIsAboutBlank,
                                  remoteType,
                                  usingPreloadedContent };
 
             // If the caller opts in, create a lazy browser.
             if (aCreateLazyBrowser) {
@@ -3831,17 +3870,16 @@
             return this._switcher;
           }
 
           let switcher = {
             // How long to wait for a tab's layers to load. After this
             // time elapses, we're free to put up the spinner and start
             // trying to load a different tab.
             TAB_SWITCH_TIMEOUT: 400 /* ms */,
-            NEWNESS_THRESHOLD: 1000 /* ms */,
 
             // When the user hasn't switched tabs for this long, we unload
             // layers for all tabs that aren't in use.
             UNLOAD_DELAY: 300 /* ms */,
 
             // The next three tabs form the principal state variables.
             // See the assertions in postActions for their invariants.
 
@@ -3863,22 +3901,16 @@
 
             tabbrowser: this,  // Reference to gBrowser.
             loadTimer: null,   // TAB_SWITCH_TIMEOUT nsITimer instance.
             unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
-            // Holds a collection of <xul:browser>'s for this tabbrowser
-            // that we cannot force paint since their TabChild's haven't
-            // been constructed yet. Instead, we show blank tabs for them
-            // when attempting to switch to them.
-            pendingTabChild: new WeakSet(),
-
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
             // Keep an exact list of content processes (tabParent) in which
             // we're actively suppressing the display port. This gives a robust
             // way to make sure we don't forget to un-suppress.
             activeSuppressDisplayport: new Set(),
 
@@ -3943,16 +3975,29 @@
                   this.onLayersReady(browser);
                 }
               } else if (state == this.STATE_UNLOADING) {
                 browser.docShellIsActive = false;
                 if (!tabParent) {
                   this.onLayersCleared(browser);
                 }
               }
+
+              if (!tab.linkedBrowser.isRemoteBrowser) {
+                // setTabState is potentially re-entrant in the non-remote case,
+                // so we must re-get the state for this assertion.
+                let nonRemoteState = this.getTabState(tab);
+                // Non-remote tabs can never stay in the STATE_LOADING
+                // or STATE_UNLOADING states. By the time this function
+                // exits, a non-remote tab must be in STATE_LOADED or
+                // STATE_UNLOADED, since the painting and the layer
+                // upload happen synchronously.
+                this.assert(nonRemoteState == this.STATE_UNLOADED ||
+                            nonRemoteState == this.STATE_LOADED);
+              }
             },
 
             get minimized() {
               return window.windowState == window.STATE_MINIMIZED;
             },
 
             init() {
               this.log("START");
@@ -3964,17 +4009,16 @@
 
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
               window.addEventListener("sizemodechange", this);
               window.addEventListener("SwapDocShells", this, true);
               window.addEventListener("EndSwapDocShells", this, true);
-              window.addEventListener("MozTabChildNotReady", this, true);
 
               let tab = this.requestedTab;
               let browser = tab.linkedBrowser;
               let tabIsLoaded = !browser.isRemoteBrowser ||
                                 browser.frameLoader.tabParent.hasPresented;
 
               if (!this.minimized) {
                 this.log("Initial tab is loaded?: " + tabIsLoaded);
@@ -3995,17 +4039,16 @@
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
               window.removeEventListener("sizemodechange", this);
               window.removeEventListener("SwapDocShells", this, true);
               window.removeEventListener("EndSwapDocShells", this, true);
-              window.removeEventListener("MozTabChildNotReady", this, true);
 
               this.tabbrowser._switcher = null;
 
               this.activeSuppressDisplayport.forEach(function(tabParent) {
                 tabParent.suppressDisplayport(false);
               });
               this.activeSuppressDisplayport.clear();
             },
@@ -4022,18 +4065,16 @@
               this.assert(this.lastVisibleTab === this.requestedTab);
               this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
               toBrowser.setAttribute("primary", "true");
 
-              this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
-
               let fromBrowser = this.originalTab.linkedBrowser;
               // It's possible that the tab we're switching from closed
               // before we were able to finalize, in which case, fromBrowser
               // doesn't exist.
               if (fromBrowser) {
                 fromBrowser.removeAttribute("primary");
               }
 
@@ -4045,20 +4086,36 @@
               });
               this.tabbrowser.dispatchEvent(event);
             },
 
             // This function is called after all the main state changes to
             // make sure we display the right tab.
             updateDisplay() {
               let requestedTabState = this.getTabState(this.requestedTab);
-
-              let shouldBeBlank =
-                this.pendingTabChild.has(this.requestedTab.linkedBrowser) &&
-                requestedTabState == this.STATE_LOADING;
+              let requestedBrowser = this.requestedTab.linkedBrowser;
+
+              // It is more desirable to show a blank tab when appropriate than
+              // the tab switch spinner - especially since the spinner is usually
+              // preceded by a perceived lag of TAB_SWITCH_TIMEOUT ms in the
+              // tab switch. We can hide this lag, and hide the time being spent
+              // constructing TabChild's, layer trees, etc, by showing a blank
+              // tab instead and focusing it immediately.
+              let shouldBeBlank = false;
+              if (requestedBrowser.isRemoteBrowser) {
+                // If a tab is remote, we can show a blank tab instead of a
+                // spinner if we know it has never presented before, or if it
+                // has just crashed and we haven't started showing the tab crashed
+                // page yet.
+                let fl = requestedBrowser.frameLoader;
+                shouldBeBlank = (!fl.tabParent || !fl.tabParent.hasPresented);
+              }
+
+              this.log("Tab should be blank: " + shouldBeBlank);
+              this.log("Requested tab is remote?: " + requestedBrowser.isRemoteBrowser);
 
               // Figure out which tab we actually want visible right now.
               let showTab = null;
               if (requestedTabState != this.STATE_LOADED &&
                   this.lastVisibleTab && this.loadTimer && !shouldBeBlank) {
                 // If we can't show the requestedTab, and lastVisibleTab is
                 // available, show it.
                 showTab = this.lastVisibleTab;
@@ -4066,26 +4123,24 @@
                 // Show the requested tab. If it's not available, we'll show the spinner or a blank tab.
                 showTab = this.requestedTab;
               }
 
               // First, let's deal with blank tabs, which we show instead
               // of the spinner when the tab is not currently set up
               // properly in the content process.
               if (!shouldBeBlank && this.blankTab) {
-                this.tabbrowser.removeAttribute("pendingtabchild");
-                this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                this.blankTab.linkedBrowser.removeAttribute("blank");
                 this.blankTab = null;
               } else if (shouldBeBlank && this.blankTab !== showTab) {
                 if (this.blankTab) {
-                  this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                  this.blankTab.linkedBrowser.removeAttribute("blank");
                 }
                 this.blankTab = showTab;
-                this.tabbrowser.setAttribute("pendingtabchild", "true");
-                this.blankTab.linkedBrowser.setAttribute("pendingtabchild", "true");
+                this.blankTab.linkedBrowser.setAttribute("blank", "true");
               }
 
               // Show or hide the spinner as needed.
               let needSpinner = this.getTabState(showTab) != this.STATE_LOADED &&
                                 !this.minimized &&
                                 !shouldBeBlank;
 
               if (!needSpinner && this.spinnerTab) {
@@ -4101,16 +4156,17 @@
                 }
                 this.spinnerTab = showTab;
                 this.tabbrowser.setAttribute("pendingpaint", "true");
                 this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
               }
 
               // Switch to the tab we've decided to make visible.
               if (this.visibleTab !== showTab) {
+                this.tabbrowser._adjustFocusBeforeTabSwitch(this.visibleTab, showTab);
                 this.visibleTab = showTab;
 
                 this.maybeVisibleTabs.add(showTab);
 
                 let tabs = this.tabbrowser.mTabBox.tabs;
                 let tabPanel = this.tabbrowser.mPanelContainer;
                 let showPanel = tabs.getRelatedElement(showTab);
                 let index = Array.indexOf(tabPanel.childNodes, showPanel);
@@ -4196,16 +4252,28 @@
               this.assert(!this.loadingTab ||
                           this.getTabState(this.loadingTab) == this.STATE_LOADING);
 
               // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
               // the timer is set only when we're loading something.
               this.assert(!this.loadTimer || this.loadingTab);
               this.assert(!this.loadingTab || this.loadTimer);
 
+              // If we're switching to a non-remote tab, there's no need to wait
+              // for it to send layers to the compositor, as this will happen
+              // synchronously. Clearing this here means that in the next step,
+              // we can load the non-remote browser immediately.
+              if (!this.requestedTab.linkedBrowser.isRemoteBrowser) {
+                this.loadingTab = null;
+                if (this.loadTimer) {
+                  this.clearTimer(this.loadTimer);
+                  this.loadTimer = null;
+                }
+              }
+
               // If we're not loading anything, try loading the requested tab.
               let requestedState = this.getTabState(this.requestedTab);
               if (!this.loadTimer && !this.minimized &&
                   (requestedState == this.STATE_UNLOADED ||
                    requestedState == this.STATE_UNLOADING)) {
                 this.loadRequestedTab();
               }
 
@@ -4229,16 +4297,20 @@
 
               // It's possible for updateDisplay to trigger one of our own event
               // handlers, which might cause finish() to already have been called.
               // Check for that before calling finish() again.
               if (!this.tabbrowser._switcher) {
                 return;
               }
 
+              if (this.blankTab) {
+                this.maybeFinishTabSwitch();
+              }
+
               if (numPending == 0) {
                 this.finish();
               }
 
               this.logState("done");
             },
 
             // Fires when we're ready to unload unused tabs.
@@ -4282,17 +4354,16 @@
               this.preActions();
               this.loadTimer = null;
               this.loadingTab = null;
               this.postActions();
             },
 
             // Fires when the layers become available for a tab.
             onLayersReady(browser) {
-              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.logState(`onLayersReady(${tab._tPos}, ${browser.isRemoteBrowser})`);
 
               this.assert(this.getTabState(tab) == this.STATE_LOADING ||
                           this.getTabState(tab) == this.STATE_LOADED);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
@@ -4309,17 +4380,16 @@
             // around.
             onPaint() {
               this.maybeVisibleTabs.clear();
               this.maybeFinishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared(browser) {
-              this.pendingTabChild.delete(browser);
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
                 this.logState(`onLayersCleared(${tab._tPos})`);
                 this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
                             this.getTabState(tab) == this.STATE_UNLOADED);
                 this.setTabState(tab, this.STATE_UNLOADED);
               }
             },
@@ -4333,21 +4403,17 @@
                 if (this.getTabState(tab) == this.STATE_LOADING) {
                   this.onLayersReady(tab.linkedBrowser);
                 } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
                   this.onLayersCleared(tab.linkedBrowser);
                 }
               } else if (this.getTabState(tab) == this.STATE_LOADED) {
                 // A tab just changed from non-remote to remote, which means
                 // that it's gone back into the STATE_LOADING state until
-                // it sends up a layer tree. We also add the browser to
-                // the pendingTabChild set since this browser is unlikely
-                // to have its TabChild set up right away, and we want to
-                // make it appear "blank" instead of showing a spinner for it.
-                this.pendingTabChild.add(tab.linkedBrowser);
+                // it sends up a layer tree.
                 this.setTabState(tab, this.STATE_LOADING);
               }
             },
 
             // Called when a tab has been removed, and the browser node is
             // about to be removed from the DOM.
             onTabRemoved(tab) {
               if (this.lastVisibleTab == tab) {
@@ -4385,40 +4451,31 @@
 
             onSwapDocShells(ourBrowser, otherBrowser) {
               // This event fires before the swap. ourBrowser is from
               // our window. We save the state of otherBrowser since ourBrowser
               // needs to take on that state at the end of the swap.
 
               let otherTabbrowser = otherBrowser.ownerGlobal.gBrowser;
               let otherState;
-              let pendingTabChild = false;
               if (otherTabbrowser && otherTabbrowser._switcher) {
                 let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
                 let otherSwitcher = otherTabbrowser._switcher;
                 otherState = otherSwitcher.getTabState(otherTab);
-                pendingTabChild = otherSwitcher.pendingTabChild.has(otherBrowser);
-
-                if (pendingTabChild) {
-                  this.assert(otherState == this.STATE_LOADING);
-                }
-
-                otherSwitcher.pendingTabChild.delete(otherBrowser);
               } else {
                 otherState = (otherBrowser.docShellIsActive
                               ? this.STATE_LOADED
                               : this.STATE_UNLOADED);
               }
 
               if (!this.swapMap) {
                 this.swapMap = new WeakMap();
               }
               this.swapMap.set(otherBrowser, {
                 state: otherState,
-                pendingTabChild,
               });
             },
 
             onEndSwapDocShells(ourBrowser, otherBrowser) {
               // The swap has happened. We reset the loadingTab in
               // case it has been swapped. We also set ourBrowser's state
               // to whatever otherBrowser's state was before the swap.
 
@@ -4428,76 +4485,51 @@
                 // ready yet. Typically it will already be ready
                 // though. If it's not, we're probably in a new window,
                 // in which case we have no other tabs to display anyway.
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
               this.loadingTab = null;
 
-              let { state: otherState, pendingTabChild } =
-                this.swapMap.get(otherBrowser);
+              let { state: otherState } = this.swapMap.get(otherBrowser);
 
               this.swapMap.delete(otherBrowser);
 
               let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
               if (ourTab) {
                 this.setTabStateNoAction(ourTab, otherState);
-                if (pendingTabChild) {
-                  this.pendingTabChild.add(ourTab.linkedBrowser);
-                }
               }
             },
 
             shouldActivateDocShell(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               let state = this.getTabState(tab);
               return state == this.STATE_LOADING || state == this.STATE_LOADED;
             },
 
             activateBrowserForPrintPreview(browser) {
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.setTabState(tab, this.STATE_LOADING);
             },
 
-            // The tab for this browser isn't currently set
-            // up in the content process, so we have no chance
-            // of painting it right away. We'll paint a blank
-            // tab instead.
-            onTabChildNotReady(browser) {
-              this.assert(browser.isRemoteBrowser);
-
-              let tab = this.tabbrowser.getTabForBrowser(browser);
-
-              this.assert(this.getTabState(tab) == this.STATE_LOADING);
-
-              this.logState(`onTabChildNotReady(${tab._tPos})`);
-              this.pendingTabChild.add(browser);
-              this.maybeFinishTabSwitch();
-
-              if (this.loadingTab === tab) {
-                this.clearTimer(this.loadTimer);
-                this.loadTimer = null;
-                this.loadingTab = null;
-              }
-            },
-
             // Called when the user asks to switch to a given tab.
             requestTab(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
 
               this.requestedTab = tab;
 
               let browser = this.requestedTab.linkedBrowser;
               let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+
               if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
                 fl.tabParent.suppressDisplayport(true);
                 this.activeSuppressDisplayport.add(fl.tabParent);
               }
 
               this.preActions();
 
               if (this.unloadTimer) {
@@ -4530,18 +4562,16 @@
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               } else if (event.type == "sizemodechange") {
                 this.onSizeModeChange();
               } else if (event.type == "SwapDocShells") {
                 this.onSwapDocShells(event.originalTarget, event.detail);
               } else if (event.type == "EndSwapDocShells") {
                 this.onEndSwapDocShells(event.originalTarget, event.detail);
-              } else if (event.type == "MozTabChildNotReady") {
-                this.onTabChildNotReady(event.originalTarget);
               }
 
               this.postActions();
               this._processing = false;
             },
 
             /*
              * Telemetry and Profiler related helpers for recording tab switch
@@ -4576,41 +4606,22 @@
                 this.switchInProgress = false;
               }
             },
 
             spinnerDisplayed() {
               this.assert(!this.spinnerTab);
               let browser = this.requestedTab.linkedBrowser;
               this.assert(browser.isRemoteBrowser);
+              this.assert(browser.frameLoader.tabParent.hasPresented);
               TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
               // We have a second, similar probe for capturing recordings of
               // when the spinner is displayed for very long periods.
               TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
               this.addMarker("AsyncTabSwitch:SpinnerShown");
-
-              // What kind of tab is about to display this spinner? We have three basic
-              // kinds:
-              //
-              // 1) A tab that we've presented before
-              // 2) A tab that we've never presented before, and it's quite new
-              // 3) A tab that we've never presented before, but it's not so new
-              //
-              // Being "new" in this sense means being a tab that was created less than
-              // NEWNESS_THRESHOLD ms ago.
-
-              let histogram = Services.telemetry.getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
-              if (browser.frameLoader.tabParent.hasPresented) {
-                // We've presented this tab before.
-                histogram.add("seen");
-              } else if (Date.now() - this.requestedTab.creationTime < this.NEWNESS_THRESHOLD) {
-                histogram.add("unseenNew");
-              } else {
-                histogram.add("unseenOld");
-              }
             },
 
             spinnerHidden() {
               this.assert(this.spinnerTab);
               this.log("DEBUG: spinner time = " +
                        TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
               TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
               TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
@@ -6010,16 +6021,17 @@
         ]]></body>
       </method>
 
       <method name="adjustTabstrip">
         <body><![CDATA[
           // If we're overflowing, tab widths don't change anymore, so we can
           // return early to avoid flushing layout.
           if (this.getAttribute("overflow") == "true") {
+            this.setAttribute("closebuttons", "activetab");
             return;
           }
 
           let numTabs = this.childNodes.length -
                         this.tabbrowser._removingTabs.length;
           if (numTabs > 2) {
             // This is an optimization to avoid layout flushes by calling
             // getBoundingClientRect() when we just opened a second tab. In
@@ -7177,20 +7189,16 @@
       </xul:stack>
     </content>
 
     <implementation>
       <constructor><![CDATA[
         if (!("_lastAccessed" in this)) {
           this.updateLastAccessed();
         }
-
-        if (!("_creationTime" in this)) {
-          this._creationTime = Date.now();
-        }
       ]]></constructor>
 
       <property name="_visuallySelected">
         <setter>
           <![CDATA[
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
@@ -7285,22 +7293,16 @@
       </property>
       <method name="updateLastAccessed">
         <parameter name="aDate"/>
         <body><![CDATA[
           this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
         ]]></body>
       </method>
 
-      <property name="creationTime">
-        <getter>
-          return this._creationTime;
-        </getter>
-      </property>
-
       <field name="mOverCloseButton">false</field>
       <property name="_overPlayingIcon" readonly="true">
         <getter><![CDATA[
           let iconVisible = this.hasAttribute("soundplaying") ||
                             this.hasAttribute("muted") ||
                             this.hasAttribute("blocked");
           let soundPlayingIcon =
             document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
--- a/browser/base/content/test/performance/browser_tabopen_reflows.js
+++ b/browser/base/content/test/performance/browser_tabopen_reflows.js
@@ -13,56 +13,34 @@
  * for tips on how to do that.
  */
 const EXPECTED_REFLOWS = [
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 
   [
     "select@chrome://global/content/bindings/textbox.xml",
     "focusAndSelectUrlBar@chrome://browser/content/browser.js",
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
-  ],
-
-  [
-    "openLinkIn@chrome://browser/content/utilityOverlay.js",
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
-    "BrowserOpenTab@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
   ],
 ];
 
-if (!gMultiProcessBrowser) {
-  EXPECTED_REFLOWS.push(
-    [
-      "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
-      "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml",
-      "onselect@chrome://browser/content/browser.xul",
-    ],
-  );
-}
-
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
 add_task(async function() {
   // If we've got a preloaded browser, get rid of it so that it
   // doesn't interfere with the test if it's loading. We have to
   // do this before we disable preloading or changing the new tab
--- a/browser/base/content/test/sync/browser.ini
+++ b/browser/base/content/test/sync/browser.ini
@@ -1,9 +1,10 @@
 [browser_sync.js]
 [browser_fxa_web_channel.js]
 support-files=
   browser_fxa_web_channel.html
+[browser_fxa_badge.js]
 [browser_aboutAccounts.js]
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
   accounts_testRemoteCommands.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/sync/browser_fxa_badge.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/AppMenuNotifications.jsm");
+
+add_task(async function test_unconfigured_no_badge() {
+  const oldUIState = UIState.get;
+
+  UIState.get = () => ({
+    status: UIState.STATUS_NOT_CONFIGURED
+  });
+  Services.obs.notifyObservers(null, UIState.ON_UPDATE);
+  checkFxABadge(false);
+
+  UIState.get = oldUIState;
+});
+
+add_task(async function test_signedin_no_badge() {
+  const oldUIState = UIState.get;
+
+  UIState.get = () => ({
+    status: UIState.STATUS_SIGNED_IN,
+    email: "foo@bar.com"
+  });
+  Services.obs.notifyObservers(null, UIState.ON_UPDATE);
+  checkFxABadge(false);
+
+  UIState.get = oldUIState;
+});
+
+add_task(async function test_unverified_badge_shown() {
+  const oldUIState = UIState.get;
+
+  UIState.get = () => ({
+    status: UIState.STATUS_NOT_VERIFIED,
+    email: "foo@bar.com"
+  });
+  Services.obs.notifyObservers(null, UIState.ON_UPDATE);
+  checkFxABadge(true);
+
+  UIState.get = oldUIState;
+});
+
+add_task(async function test_loginFailed_badge_shown() {
+  const oldUIState = UIState.get;
+
+  UIState.get = () => ({
+    status: UIState.STATUS_LOGIN_FAILED,
+    email: "foo@bar.com"
+  });
+  Services.obs.notifyObservers(null, UIState.ON_UPDATE);
+  checkFxABadge(true);
+
+  UIState.get = oldUIState;
+});
+
+function checkFxABadge(shouldBeShown) {
+  let isShown = false;
+  for (let notification of AppMenuNotifications.notifications) {
+    if (notification.id == "fxa-needs-authentication") {
+      isShown = true;
+      break;
+    }
+  }
+  is(isShown, shouldBeShown, "Fxa badge shown matches expected value.");
+}
--- a/browser/base/content/test/sync/browser_sync.js
+++ b/browser/base/content/test/sync/browser_sync.js
@@ -21,17 +21,16 @@ add_task(async function test_ui_state_si
     displayName: "Foo Bar",
     avatarURL: "https://foo.bar",
     lastSync: new Date(),
     syncing: false
   };
 
   gSync.updateAllUI(state);
 
-  checkFxABadge(false);
   let statusBarTooltip = gSync.appMenuStatus.getAttribute("signedinTooltiptext");
   let lastSyncTooltip = gSync.formatLastSyncDate(new Date(state.lastSync));
   checkPanelUIStatusBar({
     label: "Foo Bar",
     tooltip: statusBarTooltip,
     fxastatus: "signedin",
     avatarURL: "https://foo.bar",
     syncing: false,
@@ -69,17 +68,16 @@ add_task(async function test_ui_state_sy
 
 add_task(async function test_ui_state_unconfigured() {
   let state = {
     status: UIState.STATUS_NOT_CONFIGURED
   };
 
   gSync.updateAllUI(state);
 
-  checkFxABadge(false);
   let signedOffLabel = gSync.appMenuStatus.getAttribute("defaultlabel");
   let statusBarTooltip = gSync.appMenuStatus.getAttribute("signedinTooltiptext");
   checkPanelUIStatusBar({
     label: signedOffLabel,
     tooltip: statusBarTooltip
   });
   checkRemoteTabsPanel("PanelUI-remotetabs-setupsync");
   checkMenuBarItem("sync-setup");
@@ -90,17 +88,16 @@ add_task(async function test_ui_state_un
     status: UIState.STATUS_NOT_VERIFIED,
     email: "foo@bar.com",
     lastSync: new Date(),
     syncing: false
   };
 
   gSync.updateAllUI(state);
 
-  checkFxABadge(true);
   let expectedLabel = gSync.appMenuStatus.getAttribute("unverifiedlabel");
   let tooltipText = gSync.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
   checkPanelUIStatusBar({
     label: expectedLabel,
     tooltip: tooltipText,
     fxastatus: "unverified",
     avatarURL: null,
     syncing: false,
@@ -113,17 +110,16 @@ add_task(async function test_ui_state_un
 add_task(async function test_ui_state_loginFailed() {
   let state = {
     status: UIState.STATUS_LOGIN_FAILED,
     email: "foo@bar.com"
   };
 
   gSync.updateAllUI(state);
 
-  checkFxABadge(true);
   let expectedLabel = gSync.appMenuStatus.getAttribute("errorlabel");
   let tooltipText = gSync.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
   checkPanelUIStatusBar({
     label: expectedLabel,
     tooltip: tooltipText,
     fxastatus: "login-failed",
     avatarURL: null,
     syncing: false,
@@ -143,27 +139,16 @@ add_task(async function test_FormatLastS
 add_task(async function test_FormatLastSyncDateMonthAgo() {
   let monthAgo = new Date();
   monthAgo.setMonth(monthAgo.getMonth() - 1);
   let monthAgoString = gSync.formatLastSyncDate(monthAgo);
   is(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: "long", day: "numeric"}),
      "The date is correctly formatted");
 });
 
-function checkFxABadge(shouldBeShown) {
-  let isShown = false;
-  for (let notification of PanelUI.notifications) {
-    if (notification.id == "fxa-needs-authentication") {
-      isShown = true;
-      break;
-    }
-  }
-  is(isShown, shouldBeShown, "the fxa badge has the right visibility");
-}
-
 function checkPanelUIStatusBar({label, tooltip, fxastatus, avatarURL, syncing, syncNowTooltip}) {
   let prefix = gPhotonStructure ? "appMenu" : "PanelUI"
   let labelNode = document.getElementById(`${prefix}-fxa-label`);
   let tooltipNode = document.getElementById(`${prefix}-fxa-status`);
   let statusNode = document.getElementById(`${prefix}-fxa-container`);
   let avatar = document.getElementById(`${prefix}-fxa-avatar`);
 
   is(labelNode.getAttribute("label"), label, "fxa label has the right value");
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -4,18 +4,16 @@ support-files =
   test_bug1358314.html
 
 [browser_abandonment_telemetry.js]
 [browser_allow_process_switches_despite_related_browser.js]
 [browser_contextmenu_openlink_after_tabnavigated.js]
 [browser_tabCloseProbes.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
-[browser_tabSpinnerTypeProbe.js]
-skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSwitchPrintPreview.js]
 skip-if = os == 'mac'
 [browser_navigatePinnedTab.js]
 [browser_new_web_tab_in_file_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_opened_file_tab_navigated_to_web.js]
 [browser_reload_deleted_file.js]
 [browser_tabswitch_updatecommands.js]
--- a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
+++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
@@ -1,89 +1,88 @@
 "use strict";
 
 /**
  * Tests the FX_TAB_SWITCH_SPINNER_VISIBLE_MS and
  * FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS telemetry probes
  */
-let gMinHangTime = 500; // ms
-let gMaxHangTime = 5 * 1000; // ms
-
-/**
- * Make a data URI for a generic webpage with a script that hangs for a given
- * amount of time.
- * @param  {?Number} aHangMs Number of milliseconds that the hang should last.
- *                   Defaults to 0.
- * @return {String}  The data URI generated.
- */
-function makeDataURI(aHangMs = 0) {
-  return `data:text/html,
-    <html>
-      <head>
-        <meta charset="utf-8"/>
-        <title>Tab Spinner Test</title>
-        <script>
-          function hang() {
-            let hangDuration = ${aHangMs};
-            if (hangDuration > 0) {
-              let startTime = window.performance.now();
-              while(window.performance.now() - startTime < hangDuration) {}
-            }
-          }
-        </script>
-      </head>
-      <body>
-        <h1 id='header'>Tab Spinner Test</h1>
-      </body>
-    </html>`;
-}
+const MIN_HANG_TIME = 500; // ms
+const MAX_HANG_TIME = 5 * 1000; // ms
 
 /**
  * Returns the sum of all values in an array.
  * @param  {Array}  aArray An array of integers
  * @return {Number} The sum of the integers in the array
  */
 function sum(aArray) {
   return aArray.reduce(function(previousValue, currentValue) {
     return previousValue + currentValue;
   });
 }
 
 /**
+ * Causes the content process for a remote <xul:browser> to run
+ * some busy JS for aMs milliseconds.
+ *
+ * @param {<xul:browser>} browser
+ *        The browser that's running in the content process that we're
+ *        going to hang.
+ * @param {int} aMs
+ *        The amount of time, in milliseconds, to hang the content process.
+ *
+ * @return {Promise}
+ *        Resolves once the hang is done.
+ */
+function hangContentProcess(browser, aMs) {
+  return ContentTask.spawn(browser, aMs, function*(ms) {
+    let then = Date.now();
+    while (Date.now() - then < ms) {
+      // Let's burn some CPU...
+    }
+  });
+}
+
+/**
  * A generator intended to be run as a Task. It tests one of the tab spinner
  * telemetry probes.
  * @param {String} aProbe The probe to test. Should be one of:
  *                  - FX_TAB_SWITCH_SPINNER_VISIBLE_MS
  *                  - FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS
  */
 async function testProbe(aProbe) {
   info(`Testing probe: ${aProbe}`);
   let histogram = Services.telemetry.getHistogramById(aProbe);
   let buckets = histogram.snapshot().ranges.filter(function(value) {
-    return (value > gMinHangTime && value < gMaxHangTime);
+    return (value > MIN_HANG_TIME && value < MAX_HANG_TIME);
   });
   let delayTime = buckets[0]; // Pick a bucket arbitrarily
 
   // The tab spinner does not show up instantly. We need to hang for a little
   // bit of extra time to account for the tab spinner delay.
   delayTime += gBrowser.selectedTab.linkedBrowser.getTabBrowser()._getSwitcher().TAB_SWITCH_TIMEOUT;
-  let dataURI1 = makeDataURI(delayTime);
-  let dataURI2 = makeDataURI();
 
-  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI1);
+  // In order for a spinner to be shown, the tab must have presented before.
+  let origTab = gBrowser.selectedTab;
+  let hangTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  let hangBrowser = hangTab.linkedBrowser;
+  ok(hangBrowser.isRemoteBrowser, "New tab should be remote.");
+  ok(hangBrowser.frameLoader.tabParent.hasPresented, "New tab has presented.");
+
+  // Now switch back to the original tab and set up our hang.
+  await BrowserTestUtils.switchTab(gBrowser, origTab);
+
+  let tabHangPromise = hangContentProcess(hangBrowser, delayTime);
   histogram.clear();
-  // Queue a hang in the content process when the
-  // event loop breathes next.
-  ContentTask.spawn(tab1.linkedBrowser, null, async function() {
-    content.wrappedJSObject.hang();
-  });
-  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI2);
+  let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, hangTab);
+  await tabHangPromise;
+  await hangTabSwitch;
+
+  // Now we should have a hang in our histogram.
   let snapshot = histogram.snapshot();
-  await BrowserTestUtils.removeTab(tab2);
-  await BrowserTestUtils.removeTab(tab1);
+  await BrowserTestUtils.removeTab(hangTab);
   ok(sum(snapshot.counts) > 0,
    `Spinner probe should now have a value in some bucket`);
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["dom.ipc.processCount", 1],
deleted file mode 100644
--- a/browser/base/content/test/tabs/browser_tabSpinnerTypeProbe.js
+++ /dev/null
@@ -1,187 +0,0 @@
-"use strict";
-
-// Keep this in sync with the order in Histograms.json for
-// FX_TAB_SWITCH_SPINNER_TYPE
-const CATEGORIES = [
-  "seen",
-  "unseenOld",
-  "unseenNew",
-];
-
-add_task(async function setup() {
-  await SpecialPowers.pushPrefEnv({
-    set: [
-      // We can interrupt JS to paint now, which is great for
-      // users, but bad for testing spinners. We temporarily
-      // disable that feature for this test so that we can
-      // easily get ourselves into a predictable tab spinner
-      // state.
-      ["browser.tabs.remote.force-paint", false],
-    ]
-  });
-});
-
-/**
- * Causes the content process for a remote <xul:browser> to run
- * some busy JS for aMs milliseconds.
- *
- * @param {<xul:browser>} browser
- *        The browser that's running in the content process that we're
- *        going to hang.
- * @param {int} aMs
- *        The amount of time, in milliseconds, to hang the content process.
- *
- * @return {Promise}
- *        Resolves once the hang is done.
- */
-function hangContentProcess(browser, aMs) {
-  return ContentTask.spawn(browser, aMs, async function(ms) {
-    let then = Date.now();
-    while (Date.now() - then < ms) {
-      // Let's burn some CPU...
-    }
-  });
-}
-
-/**
- * Takes a Telemetry histogram snapshot and makes sure
- * that the index for that value (as defined by CATEGORIES)
- * has a count of 1, and that it's the only value that
- * has been incremented.
- *
- * @param snapshot (Object)
- *        The Telemetry histogram snapshot to examine.
- * @param category (String)
- *        The category in CATEGORIES whose index we expect to have
- *        been set to 1.
- */
-function assertOnlyOneTypeSet(snapshot, category) {
-  let categoryIndex = CATEGORIES.indexOf(category);
-  Assert.equal(snapshot.counts[categoryIndex], 1,
-               `Should have seen the ${category} count increment.`);
-  // Use Array.prototype.reduce to sum up all of the
-  // snapshot.count entries
-  Assert.equal(snapshot.counts.reduce((a, b) => a + b), 1,
-               "Should only be 1 collected value.");
-}
-
-Assert.ok(gMultiProcessBrowser,
-  "These tests only makes sense in an e10s-enabled window.");
-
-let gHistogram = Services.telemetry
-                         .getHistogramById("FX_TAB_SWITCH_SPINNER_TYPE");
-
-/**
- * This test tests that the "seen" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've
- * presented before.
- */
-add_task(async function test_seen_spinner_type_probe() {
-  let originalTab = gBrowser.selectedTab;
-
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    // We'll switch away from the current tab, then hang it, and then switch
-    // back to it. This should add to the "seen" type for the histogram.
-    let testTab = gBrowser.selectedTab;
-    await BrowserTestUtils.switchTab(gBrowser, originalTab);
-    gHistogram.clear();
-
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, testTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "seen");
-    gHistogram.clear();
-  });
-});
-
-/**
- * This test tests that the "unseenOld" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've never
- * seen before, and enough time has passed since its creation that we consider
- * it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
- * the exact definition).
- */
-add_task(async function test_unseenOld_spinner_type_probe() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    const NEWNESS_THRESHOLD = gBrowser._getSwitcher().NEWNESS_THRESHOLD;
-
-    // First, create a new background tab, ensuring that it's in the same process
-    // as the current one.
-    let bgTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
-      sameProcessAsFrameLoader: browser.frameLoader,
-      inBackground: true,
-    });
-
-    await BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
-
-    // Now, let's fudge with the creationTime of the background tab so that
-    // it seems old. We'll also add a fudge-factor to the NEWNESS_THRESHOLD of 100ms
-    // to try to avoid any potential timing issues.
-    bgTab._creationTime = bgTab._creationTime - NEWNESS_THRESHOLD - 100;
-
-    // Okay, tab should seem sufficiently old now so that a spinner in it should
-    // qualify for "unseenOld". Let's hang it and switch to it.
-    gHistogram.clear();
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "unseenOld");
-
-    await BrowserTestUtils.removeTab(bgTab);
-  });
-});
-
-/**
- * This test tests that the "unseenNew" category for the FX_TAB_SWITCH_SPINNER_TYPE
- * probe works. This means that we show a spinner for a tab that we've never
- * seen before, and not enough time has passed since its creation that we consider
- * it "old" (See the NEWNESS_THRESHOLD constant in the async tabswitcher for
- * the exact definition).
- */
-add_task(async function test_unseenNew_spinner_type_probe() {
-  await BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: "http://example.com",
-  }, async function(browser) {
-    // First, create a new background tab, ensuring that it's in the same process
-    // as the current one.
-    let bgTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
-      sameProcessAsFrameLoader: browser.frameLoader,
-      inBackground: true,
-    });
-
-    await BrowserTestUtils.browserLoaded(bgTab.linkedBrowser);
-
-    // Now, let's fudge with the creationTime of the background tab so that
-    // it seems very new (created 1 minute into the future).
-    bgTab._creationTime = Date.now() + (60 * 1000);
-
-    // Okay, tab should seem sufficiently new now so that a spinner in it should
-    // qualify for "unseenNew". Let's hang it and switch to it.
-    gHistogram.clear();
-    let tabHangPromise = hangContentProcess(browser, 1000);
-    let hangTabSwitch = BrowserTestUtils.switchTab(gBrowser, bgTab);
-    await tabHangPromise;
-    await hangTabSwitch;
-
-    // Okay, we should have gotten an entry in our Histogram for that one.
-    let snapshot = gHistogram.snapshot();
-    assertOnlyOneTypeSet(snapshot, "unseenNew");
-
-    await BrowserTestUtils.removeTab(bgTab);
-  });
-});
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -391,16 +391,18 @@ function openLinkIn(url, where, params) 
     // 'where' is "tab" or "tabshifted", so we'll load the link in a new tab.
     loadInBackground = aInBackground;
     if (loadInBackground == null) {
       loadInBackground =
         aFromChrome ? false : getBoolPref("browser.tabs.loadInBackground");
     }
   }
 
+  let focusUrlBar = false;
+
   switch (where) {
   case "current":
     let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
 
     if (aAllowThirdPartyFixup) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
     }
@@ -432,30 +434,33 @@ function openLinkIn(url, where, params) 
       postData: aPostData,
       userContextId: aUserContextId
     });
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
+    focusUrlBar = !loadInBackground && w.isBlankPageURL(url);
+
     let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
       referrerURI: aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipTabAnimation,
       allowMixedContent: aAllowMixedContent,
       noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aPrincipal,
       triggeringPrincipal: aTriggeringPrincipal,
+      focusUrlBar,
     });
     targetBrowser = tabUsedForLoad.linkedBrowser;
 
     if (params.frameOuterWindowID != undefined && w) {
       // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
       // event if it contains the expected frameOuterWindowID params.
       // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
       // opening a new tab using the keyboard shortcut).
@@ -466,24 +471,20 @@ function openLinkIn(url, where, params) 
           sourceTabBrowser: w.gBrowser.selectedBrowser,
           sourceFrameOuterWindowID: params.frameOuterWindowID,
         },
       }, "webNavigation-createdNavigationTarget");
     }
     break;
   }
 
-  // Focus the content, but only if the browser used for the load is selected.
-  if (targetBrowser == w.gBrowser.selectedBrowser) {
+  if (!focusUrlBar && targetBrowser == w.gBrowser.selectedBrowser) {
+    // Focus the content, but only if the browser used for the load is selected.
     targetBrowser.focus();
   }
-
-  if (!loadInBackground && w.isBlankPageURL(url)) {
-    w.focusAndSelectUrlBar();
-  }
 }
 
 // Used as an onclick handler for UI elements with link-like behavior.
 // e.g. onclick="checkForMiddleClick(this, event);"
 function checkForMiddleClick(node, event) {
   // We should be using the disabled property here instead of the attribute,
   // but some elements that this function is used with don't support it (e.g.
   // menuitem).
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -36,33 +36,23 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/tabcrashed/browser.ini',
     'content/test/tabPrompts/browser.ini',
     'content/test/tabs/browser.ini',
     'content/test/urlbar/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
 ]
 
-if CONFIG['MOZ_UPDATER']:
-    BROWSER_CHROME_MANIFESTS += ['content/test/appUpdate/browser.ini']
-
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
     DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
-TEST_HARNESS_FILES.testing.mochitest.browser.browser.base.content.test.appUpdate += [
-    '/toolkit/mozapps/update/tests/chrome/update.sjs',
-    '/toolkit/mozapps/update/tests/data/shared.js',
-    '/toolkit/mozapps/update/tests/data/sharedUpdateXML.js',
-    '/toolkit/mozapps/update/tests/data/simple.mar',
-]
-
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -127,16 +127,18 @@ GetAboutModuleName(nsIURI *aURI)
 }
 
 NS_IMETHODIMP
 AboutRedirector::NewChannel(nsIURI* aURI,
                             nsILoadInfo* aLoadInfo,
                             nsIChannel** result)
 {
   NS_ENSURE_ARG_POINTER(aURI);
+  NS_ENSURE_ARG_POINTER(aLoadInfo);
+
   NS_ASSERTION(result, "must not be null");
 
   nsAutoCString path = GetAboutModuleName(aURI);
 
   nsresult rv;
   nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -167,36 +169,32 @@ AboutRedirector::NewChannel(nsIURI* aURI
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), url);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // If tempURI links to an external URI (i.e. something other than
-      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
-      // channel which forces the channel owner to reflect the displayed
+      // chrome:// or resource://) then set the result principal URI on the
+      // load info which forces the channel prncipal to reflect the displayed
       // URL rather then being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsLoadFlags loadFlags = isUIResource
-                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
-
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo,
-                                 nullptr, // aLoadGroup
-                                 nullptr, // aCallbacks
-                                 loadFlags);
+                                 aLoadInfo);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      if (!isUIResource) {
+        aLoadInfo->SetResultPrincipalURI(tempURI);
+      }
       tempChannel->SetOriginalURI(aURI);
 
       NS_ADDREF(*result = tempChannel);
       return rv;
     }
   }
 
   return NS_ERROR_ILLEGAL_VALUE;
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -194,54 +194,58 @@ const CustomizableWidgets = [
         items.firstChild.remove();
       }
 
       // Get all statically placed buttons to supply them with keyboard shortcuts.
       let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
       for (let i = 0, l = staticButtons.length; i < l; ++i)
         CustomizableUI.addShortcut(staticButtons[i]);
 
-      PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                         .asyncExecuteLegacyQueries([query], 1, options, {
-        handleResult(aResultSet) {
-          let onItemCommand = function(aItemCommandEvent) {
-            // Only handle the click event for middle clicks, we're using the command
-            // event otherwise.
-            if (aItemCommandEvent.type == "click" &&
-                aItemCommandEvent.button != 1) {
-              return;
+      aEvent.detail.addBlocker(new Promise((resolve, reject) => {
+        PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                           .asyncExecuteLegacyQueries([query], 1, options, {
+          handleResult(aResultSet) {
+            let onItemCommand = function(aItemCommandEvent) {
+              // Only handle the click event for middle clicks, we're using the command
+              // event otherwise.
+              if (aItemCommandEvent.type == "click" &&
+                  aItemCommandEvent.button != 1) {
+                return;
+              }
+              let item = aItemCommandEvent.target;
+              win.openUILink(item.getAttribute("targetURI"), aItemCommandEvent);
+              CustomizableUI.hidePanelForNode(item);
+            };
+            let fragment = doc.createDocumentFragment();
+            let row;
+            while ((row = aResultSet.getNextRow())) {
+              let uri = row.getResultByIndex(1);
+              let title = row.getResultByIndex(2);
+
+              let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+              item.setAttribute("label", title || uri);
+              item.setAttribute("targetURI", uri);
+              item.setAttribute("class", "subviewbutton");
+              item.addEventListener("command", onItemCommand);
+              item.addEventListener("click", onItemCommand);
+              item.setAttribute("image", "page-icon:" + uri);
+              fragment.appendChild(item);
             }
-            let item = aItemCommandEvent.target;
-            win.openUILink(item.getAttribute("targetURI"), aItemCommandEvent);
-            CustomizableUI.hidePanelForNode(item);
-          };
-          let fragment = doc.createDocumentFragment();
-          let row;
-          while ((row = aResultSet.getNextRow())) {
-            let uri = row.getResultByIndex(1);
-            let title = row.getResultByIndex(2);
-
-            let item = doc.createElementNS(kNSXUL, "toolbarbutton");
-            item.setAttribute("label", title || uri);
-            item.setAttribute("targetURI", uri);
-            item.setAttribute("class", "subviewbutton");
-            item.addEventListener("command", onItemCommand);
-            item.addEventListener("click", onItemCommand);
-            item.setAttribute("image", "page-icon:" + uri);
-            fragment.appendChild(item);
-          }
-          items.appendChild(fragment);
-        },
-        handleError(aError) {
-          log.debug("History view tried to show but had an error: " + aError);
-        },
-        handleCompletion(aReason) {
-          log.debug("History view is being shown!");
-        },
-      });
+            items.appendChild(fragment);
+          },
+          handleError(aError) {
+            log.debug("History view tried to show but had an error: " + aError);
+            reject();
+          },
+          handleCompletion(aReason) {
+            log.debug("History view is being shown!");
+            resolve();
+          },
+        });
+      }));
 
       let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
       while (recentlyClosedTabs.firstChild) {
         recentlyClosedTabs.firstChild.remove();
       }
 
       let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
       while (recentlyClosedWindows.firstChild) {
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -401,21 +401,30 @@ this.PanelMultiView = class {
       let previousViewNode = aPreviousView || this._currentSubView;
       let playTransition = (!!previousViewNode && previousViewNode != viewNode);
 
       let dwu, previousRect;
       if (playTransition || this.panelViews) {
         dwu = this._dwu;
         previousRect = previousViewNode.__lastKnownBoundingRect =
           dwu.getBoundsWithoutFlushing(previousViewNode);
-        if (this.panelViews && !this._mainViewWidth) {
-          this._mainViewWidth = previousRect.width;
-          let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild).top;
-          let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild).bottom;
-          this._viewVerticalPadding = previousRect.height - (bottom - top);
+        if (this.panelViews) {
+          // Here go the measures that have the same caching lifetime as the width
+          // of the main view, i.e. 'forever', during the instance lifetime.
+          if (!this._mainViewWidth) {
+            this._mainViewWidth = previousRect.width;
+            let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild).top;
+            let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild).bottom;
+            this._viewVerticalPadding = previousRect.height - (bottom - top);
+          }
+          // Here go the measures that have the same caching lifetime as the height
+          // of the main view, i.e. whilst the panel is shown and/ or visible.
+          if (!this._mainViewHeight) {
+            this._mainViewHeight = previousRect.height;
+          }
         }
       }
 
       // Emit the ViewShowing event so that the widget definition has a chance
       // to lazily populate the subview with things.
       let detail = {
         blockers: new Set(),
         addBlocker(aPromise) {
@@ -486,17 +495,17 @@ this.PanelMultiView = class {
         // the panel's not even open.
         if (this._panel.state != "open") {
           onTransitionEnd();
           return;
         }
 
         if (aAnchor)
           aAnchor.setAttribute("open", true);
-        this._viewContainer.style.height = previousRect.height + "px";
+        this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
         this._viewContainer.style.width = previousRect.width + "px";
 
         this._transitioning = true;
         this._viewContainer.setAttribute("transition-reverse", reverse);
         let nodeToAnimate = reverse ? previousViewNode : viewNode;
 
         if (!reverse) {
           // We set the margin here to make sure the view is positioned next
@@ -533,67 +542,70 @@ this.PanelMultiView = class {
               viewRect.height = [viewNode.header, ...viewNode.children].reduce((acc, node) => {
                 return acc + dwu.getBoundsWithoutFlushing(node).height;
               }, this._viewVerticalPadding);
             }
           }
 
           // Set the viewContainer dimensions to make sure only the current view
           // is visible.
-          this._viewContainer.style.height = viewRect.height + "px";
+          this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
           this._viewContainer.style.width = viewRect.width + "px";
 
           // The 'magic' part: build up the amount of pixels to move right or left.
           let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
           let movementX = reverse ? viewRect.width : previousRect.width;
           let moveX = (moveToLeft ? "" : "-") + movementX;
           nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
           // We're setting the width property to prevent flickering during the
           // sliding animation with smaller views.
           nodeToAnimate.style.width = viewRect.width + "px";
 
           let listener;
-          let seen = 0;
           this._viewContainer.addEventListener("transitionend", listener = ev => {
-            if (ev.target == this._viewContainer && ev.propertyName == "height") {
-              // Myeah, panel layout auto-resizing is a funky thing. We'll wait
-              // another few milliseconds to remove the width and height 'fixtures',
-              // to be sure we don't flicker annoyingly.
-              // NB: HACK! Bug 1363756 is there to fix this.
-              window.setTimeout(() => {
-                this._viewContainer.style.removeProperty("height");
-                this._viewContainer.style.removeProperty("width");
-              }, 500);
-              ++seen;
-            } else if (ev.target == nodeToAnimate && ev.propertyName == "transform") {
-              onTransitionEnd();
-              this._transitioning = false;
-              this._resetKeyNavigation(previousViewNode);
+            // It's quite common that `height` on the view container doesn't need
+            // to transition, so we make sure to do all the work on the transform
+            // transition-end, because that is guaranteed to happen.
+            if (ev.target != nodeToAnimate || ev.propertyName != "transform")
+              return;
+
+            this._viewContainer.removeEventListener("transitionend", listener);
+            onTransitionEnd();
+            this._transitioning = false;
+            this._resetKeyNavigation(previousViewNode);
 
-              // Take another breather, just like before, to wait for the 'current'
-              // attribute removal to take effect. This prevents a flicker.
-              // The cleanup we do doesn't affect the display anymore, so we're not
-              // too fussed about the timing here.
-              window.addEventListener("MozAfterPaint", () => {
-                nodeToAnimate.style.removeProperty("border-inline-start");
-                nodeToAnimate.style.removeProperty("transition");
-                nodeToAnimate.style.removeProperty("transform");
-                nodeToAnimate.style.removeProperty("width");
+            // Myeah, panel layout auto-resizing is a funky thing. We'll wait
+            // another few milliseconds to remove the width and height 'fixtures',
+            // to be sure we don't flicker annoyingly.
+            // NB: HACK! Bug 1363756 is there to fix this.
+            window.setTimeout(() => {
+              // Only remove the height when the view is larger than the main
+              // view, otherwise it'll snap back to its own height.
+              if (viewRect.height > this._mainViewHeight)
+                this._viewContainer.style.removeProperty("height");
+              this._viewContainer.style.removeProperty("width");
+            }, 500);
 
-                if (!reverse)
-                  viewNode.style.removeProperty("margin-inline-start");
-                if (aAnchor)
-                  aAnchor.removeAttribute("open");
+            // Take another breather, just like before, to wait for the 'current'
+            // attribute removal to take effect. This prevents a flicker.
+            // The cleanup we do doesn't affect the display anymore, so we're not
+            // too fussed about the timing here.
+            window.addEventListener("MozAfterPaint", () => {
+              nodeToAnimate.style.removeProperty("border-inline-start");
+              nodeToAnimate.style.removeProperty("transition");
+              nodeToAnimate.style.removeProperty("transform");
+              nodeToAnimate.style.removeProperty("width");
 
-                this._viewContainer.removeAttribute("transition-reverse");
-              }, { once: true });
-              ++seen;
-            }
-            if (seen == 2)
-              this._viewContainer.removeEventListener("transitionend", listener);
+              if (!reverse)
+                viewNode.style.removeProperty("margin-inline-start");
+              if (aAnchor)
+                aAnchor.removeAttribute("open");
+
+              this._viewContainer.removeAttribute("transition-reverse");
+            }, { once: true });
           });
         }, { once: true });
       } else if (!this.panelViews) {
         this._shiftMainView(aAnchor);
 
         this._mainViewHeight = this._viewStack.clientHeight;
 
         let newHeight = this._heightOfSubview(viewNode, this._subViews);
@@ -715,16 +727,17 @@ this.PanelMultiView = class {
         this._mainView.style.removeProperty("height");
         this.showMainView();
         if (!this.panelViews) {
           this._mainViewObserver.disconnect();
         } else {
           this.window.removeEventListener("keydown", this);
           this._panel.removeEventListener("mousemove", this);
           this._resetKeyNavigation();
+          this._mainViewHeight = 0;
         }
         break;
     }
   }
 
   /**
    * Allow for navigating subview buttons using the arrow keys and the Enter key.
    * The Up and Down keys can be used to navigate the list up and down and the
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -539,16 +539,32 @@
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newPrivateWindow.label;"
                        key="key_privatebrowsing"
                        command="Tools:PrivateBrowsing"/>
         <toolbarseparator/>
+        <toolbaritem id="appMenu-edit-controls" class="toolbaritem-combined-buttons" closemenu="none">
+          <label value="&editMenu.label;"/>
+          <toolbarbutton id="appMenu-cut-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_cut"
+                         tooltip="dynamic-shortcut-tooltip"/>
+          <toolbarbutton id="appMenu-copy-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_copy"
+                         tooltip="dynamic-shortcut-tooltip"/>
+          <toolbarbutton id="appMenu-paste-button"
+                         class="subviewbutton subviewbutton-iconic"
+                         command="cmd_paste"
+                         tooltip="dynamic-shortcut-tooltip"/>
+        </toolbaritem>
+        <toolbarseparator/>
         <toolbarbutton id="appMenu-open-file-button"
                        class="subviewbutton"
                        label="&openFileCmd.label;"
                        key="openFileKb"
                        command="Browser:OpenFile"
                        />
         <toolbarbutton id="appMenu-save-file-button"
                        class="subviewbutton"
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -5,16 +5,18 @@
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
                                   "resource:///modules/ScrollbarSampler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
                                   "resource://gre/modules/ShortcutUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
+                                  "resource://gre/modules/AppMenuNotifications.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure",
   "browser.photon.structure.enabled", false);
 
 /**
  * Maintains the state and dispatches events for the main menu panel.
  */
 
@@ -41,38 +43,39 @@ const PanelUI = {
 
       overflowFixedList: gPhotonStructure ? "widget-overflow-fixed-list" : "",
       overflowPanel: gPhotonStructure ? "widget-overflow" : "",
       navbar: "nav-bar",
     };
   },
 
   _initialized: false,
+  _notifications: null,
+
   init() {
     this._initElements();
 
-    this.notifications = [];
     this.menuButton.addEventListener("mousedown", this);
     this.menuButton.addEventListener("keypress", this);
     this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
 
     Services.obs.addObserver(this, "fullscreen-nav-toolbox");
-    Services.obs.addObserver(this, "panelUI-notification-main-action");
-    Services.obs.addObserver(this, "panelUI-notification-dismissed");
+    Services.obs.addObserver(this, "appMenu-notifications");
 
     window.addEventListener("fullscreen", this);
     window.addEventListener("activate", this);
     window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.addListener(this);
 
     for (let event of this.kEvents) {
       this.notificationPanel.addEventListener(event, this);
     }
 
     this._initPhotonPanel();
+    Services.obs.notifyObservers(null, "appMenu-notifications-request", "refresh");
 
     this._initialized = true;
   },
 
   reinit() {
     this._removeEventListeners();
     // If the Photon pref changes, we need to re-init our element references.
     this._initElements();
@@ -137,18 +140,17 @@ const PanelUI = {
 
   uninit() {
     this._removeEventListeners();
     for (let event of this.kEvents) {
       this.notificationPanel.removeEventListener(event, this);
     }
 
     Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
-    Services.obs.removeObserver(this, "panelUI-notification-main-action");
-    Services.obs.removeObserver(this, "panelUI-notification-dismissed");
+    Services.obs.removeObserver(this, "appMenu-notifications");
 
     window.removeEventListener("fullscreen", this);
     window.removeEventListener("activate", this);
     this.menuButton.removeEventListener("mousedown", this);
     this.menuButton.removeEventListener("keypress", this);
     window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.removeListener(this);
     this._overlayScrollListenerBoundFn = null;
@@ -227,104 +229,41 @@ const PanelUI = {
         anchor = this._getPanelAnchor(anchor);
         this.panel.openPopup(anchor);
       }, (reason) => {
         console.error("Error showing the PanelUI menu", reason);
       });
     });
   },
 
-  showNotification(id, mainAction, secondaryActions = [], options = {}) {
-    let notification = new PanelUINotification(id, mainAction, secondaryActions, options);
-    let existingIndex = this.notifications.findIndex(n => n.id == id);
-    if (existingIndex != -1) {
-      this.notifications.splice(existingIndex, 1);
-    }
-
-    // We don't want to clobber doorhanger notifications just to show a badge,
-    // so don't dismiss any of them and the badge will show once the doorhanger
-    // gets resolved.
-    if (!options.badgeOnly && !options.dismissed) {
-      this.notifications.forEach(n => { n.dismissed = true; });
-    }
-
-    // Since notifications are generally somewhat pressing, the ideal case is that
-    // we never have two notifications at once. However, in the event that we do,
-    // it's more likely that the older notification has been sitting around for a
-    // bit, and so we don't want to hide the new notification behind it. Thus,
-    // we want our notifications to behave like a stack instead of a queue.
-    this.notifications.unshift(notification);
-    this._updateNotifications();
-    return notification;
-  },
-
-  showBadgeOnlyNotification(id) {
-    return this.showNotification(id, null, null, { badgeOnly: true });
-  },
-
-  removeNotification(id) {
-    let notifications;
-    if (typeof id == "string") {
-      notifications = this.notifications.filter(n => n.id == id);
-    } else {
-      // If it's not a string, assume RegExp
-      notifications = this.notifications.filter(n => id.test(n.id));
-    }
-    // _updateNotifications can be expensive if it forces attachment of XBL
-    // bindings that haven't been used yet, so return early if we haven't found
-    // any notification to remove, as callers may expect this removeNotification
-    // method to be a no-op for non-existent notifications.
-    if (!notifications.length) {
-      return;
-    }
-
-    notifications.forEach(n => {
-      this._removeNotification(n);
-    });
-    this._updateNotifications();
-  },
-
-  dismissNotification(id) {
-    let notifications;
-    if (typeof id == "string") {
-      notifications = this.notifications.filter(n => n.id == id);
-    } else {
-      // If it's not a string, assume RegExp
-      notifications = this.notifications.filter(n => id.test(n.id));
-    }
-
-    notifications.forEach(n => n.dismissed = true);
-    this._updateNotifications();
-  },
-
   /**
    * If the menu panel is being shown, hide it.
    */
   hide() {
     if (document.documentElement.hasAttribute("customizing")) {
       return;
     }
 
     this.panel.hidePopup();
   },
 
   observe(subject, topic, status) {
     switch (topic) {
       case "fullscreen-nav-toolbox":
-        this._updateNotifications();
-        break;
-      case "panelUI-notification-main-action":
-        if (subject != window) {
-          this.removeNotification(status);
+        if (this._notifications) {
+          this._updateNotifications(false);
         }
         break;
-      case "panelUI-notification-dismissed":
-        if (subject != window) {
-          this.dismissNotification(status);
+      case "appMenu-notifications":
+        // Don't initialize twice.
+        if (status == "init" && this._notifications) {
+          break;
         }
+        this._notifications = AppMenuNotifications.notifications;
+        this._updateNotifications(true);
         break;
     }
   },
 
   handleEvent(aEvent) {
     // Ignore context menus and menu button menus showing and hiding:
     if (aEvent.type.startsWith("popup") &&
         aEvent.target != this.panel) {
@@ -371,26 +310,16 @@ const PanelUI = {
   },
 
   get isNotificationPanelOpen() {
     let panelState = this.notificationPanel.state;
 
     return panelState == "showing" || panelState == "open";
   },
 
-  get activeNotification() {
-    if (this.notifications.length > 0) {
-      const doorhanger =
-        this.notifications.find(n => !n.dismissed && !n.options.badgeOnly);
-      return doorhanger || this.notifications[0];
-    }
-
-    return null;
-  },
-
   /**
    * Registering the menu panel is done lazily for performance reasons. This
    * method is exposed so that CustomizationMode can force panel-readyness in the
    * event that customization mode is started before the panel has been opened
    * by the user.
    *
    * @param aCustomizing (optional) set to true if this was called while entering
    *        customization mode. If that's the case, we trust that customization
@@ -744,64 +673,71 @@ const PanelUI = {
   },
 
   _hidePopup() {
     if (this.isNotificationPanelOpen) {
       this.notificationPanel.hidePopup();
     }
   },
 
-  _updateNotifications() {
-    if (!this.notifications.length) {
-      this._clearAllNotifications();
-      this._hidePopup();
+  _updateNotifications(notificationsChanged) {
+    let notifications = this._notifications;
+    if (!notifications || !notifications.length) {
+      if (notificationsChanged) {
+        this._clearAllNotifications();
+        this._hidePopup();
+      }
       return;
     }
 
     if (window.fullScreen && FullScreen.navToolboxHidden) {
       this._hidePopup();
       return;
     }
 
     let doorhangers =
-      this.notifications.filter(n => !n.dismissed && !n.options.badgeOnly);
+      notifications.filter(n => !n.dismissed && !n.options.badgeOnly);
 
     if (this.panel.state == "showing" || this.panel.state == "open") {
       // If the menu is already showing, then we need to dismiss all notifications
       // since we don't want their doorhangers competing for attention
       doorhangers.forEach(n => { n.dismissed = true; })
       this._hidePopup();
       this._clearBadge();
-      if (!this.notifications[0].options.badgeOnly) {
-        this._showBannerItem(this.notifications[0]);
+      if (!notifications[0].options.badgeOnly) {
+        this._showBannerItem(notifications[0]);
       }
     } else if (doorhangers.length > 0) {
       // Only show the doorhanger if the window is focused and not fullscreen
       if (window.fullScreen || Services.focus.activeWindow !== window) {
         this._hidePopup();
         this._showBadge(doorhangers[0]);
         this._showBannerItem(doorhangers[0]);
       } else {
         this._clearBadge();
         this._showNotificationPanel(doorhangers[0]);
       }
     } else {
       this._hidePopup();
-      this._showBadge(this.notifications[0]);
-      this._showBannerItem(this.notifications[0]);
+      this._showBadge(notifications[0]);
+      this._showBannerItem(notifications[0]);
     }
   },
 
   _showNotificationPanel(notification) {
     this._refreshNotificationPanel(notification);
 
     if (this.isNotificationPanelOpen) {
       return;
     }
 
+    if (notification.options.beforeShowDoorhanger) {
+      notification.options.beforeShowDoorhanger(document);
+    }
+
     let anchor = this._getPanelAnchor(this.menuButton);
 
     this.notificationPanel.hidden = false;
     this.notificationPanel.openPopup(anchor, "bottomcenter topright");
   },
 
   _clearNotificationPanel() {
     for (let popupnotification of this.notificationPanel.children) {
@@ -859,89 +795,41 @@ const PanelUI = {
 
   _clearBannerItem() {
     if (this._panelBannerItem) {
       this._panelBannerItem.notification = null;
       this._panelBannerItem.hidden = true;
     }
   },
 
-  _removeNotification(notification) {
-    // This notification may already be removed, in which case let's just fail
-    // silently.
-    let notifications = this.notifications;
-    if (!notifications)
-      return;
-
-    var index = notifications.indexOf(notification);
-    if (index == -1)
-      return;
-
-    // Remove the notification
-    notifications.splice(index, 1);
-  },
-
   _onNotificationButtonEvent(event, type) {
     let notificationEl = getNotificationFromElement(event.originalTarget);
 
     if (!notificationEl)
       throw "PanelUI._onNotificationButtonEvent: couldn't find notification element";
 
     if (!notificationEl.notification)
       throw "PanelUI._onNotificationButtonEvent: couldn't find notification";
 
     let notification = notificationEl.notification;
 
-    let action = notification.mainAction;
-
     if (type == "secondarybuttoncommand") {
-      action = notification.secondaryActions[0];
+      AppMenuNotifications.callSecondaryAction(window, notification);
+    } else {
+      AppMenuNotifications.callMainAction(window, notification, true);
     }
-
-    let dismiss = true;
-    if (action) {
-      try {
-        if (action === notification.mainAction) {
-          action.callback(true);
-          this._notify(notification.id, "main-action");
-        } else {
-          action.callback();
-        }
-      } catch (error) {
-        Cu.reportError(error);
-      }
-
-      dismiss = action.dismiss;
-    }
-
-    if (dismiss) {
-      notification.dismissed = true;
-      this._notify(notification.id, "dismissed");
-    } else {
-      this._removeNotification(notification);
-    }
-    this._updateNotifications();
   },
 
   _onBannerItemSelected(event) {
     let target = event.originalTarget;
     if (!target.notification)
       throw "menucommand target has no associated action/notification";
 
     event.stopPropagation();
-
-    try {
-      target.notification.mainAction.callback(false);
-      this._notify(target.notification.id, "main-action");
-    } catch (error) {
-      Cu.reportError(error);
-    }
-
-    this._removeNotification(target.notification);
-    this._updateNotifications();
+    AppMenuNotifications.callMainAction(window, target.notification, false);
   },
 
   _getPopupId(notification) { return "appMenu-" + notification.id + "-notification"; },
 
   _getBadgeStatus(notification) { return notification.id; },
 
   _getPanelAnchor(candidate) {
     let iconAnchor =
@@ -960,40 +848,28 @@ const PanelUI = {
       let keyId = button.getAttribute("key");
       let key = document.getElementById(keyId);
       if (!key) {
         continue;
       }
       button.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(key));
     }
   },
-
-  _notify(status, topic) {
-    Services.obs.notifyObservers(window, "panelUI-notification-" + topic, status);
-  }
 };
 
 XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
 
 /**
  * Gets the currently selected locale for display.
  * @return  the selected locale
  */
 function getLocale() {
   return Services.locale.getAppLocaleAsLangTag();
 }
 
-function PanelUINotification(id, mainAction, secondaryActions = [], options = {}) {
-  this.id = id;
-  this.mainAction = mainAction;
-  this.secondaryActions = secondaryActions;
-  this.options = options;
-  this.dismissed = this.options.dismissed || false;
-}
-
 function getNotificationFromElement(aElement) {
   // Need to find the associated notification object, which is a bit tricky
   // since it isn't associated with the element directly - this is kind of
   // gross and very dependent on the structure of the popupnotification
   // binding's content.
   let notificationEl;
   let parent = aElement;
   while (parent && (parent = aElement.ownerDocument.getBindingParent(parent))) {
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -146,14 +146,16 @@ skip-if = os == "mac"
 [browser_1096763_seen_widgets_post_reset.js]
 [browser_1161838_inserted_new_default_buttons.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_customizemode_contextmenu_menubuttonstate.js]
 [browser_exit_background_customize_mode.js]
 [browser_overflow_use_subviews.js]
 [browser_panel_toggle.js]
 [browser_panelUINotifications.js]
+[browser_panelUINotifications_fullscreen.js]
+[browser_panelUINotifications_multiWindow.js]
 [browser_switch_to_customize_mode.js]
 [browser_synced_tabs_menu.js]
 [browser_check_tooltips_in_navbar.js]
 [browser_editcontrols_update.js]
 subsuite = clipboard
 [browser_remote_tabs_button.js]
--- a/browser/components/customizableui/test/browser_editcontrols_update.js
+++ b/browser/components/customizableui/test/browser_editcontrols_update.js
@@ -96,16 +96,21 @@ add_task(async function test_panelui_cus
   await startCustomizing();
   let navbar = document.getElementById("nav-bar").customizationTarget;
   simulateItemDrag(document.getElementById("edit-controls"), navbar);
   await endCustomizing();
 
   // updateEditUIVisibility should be called when customization ends but isn't. See bug 1359790.
   updateEditUIVisibility();
 
+  // The URL bar may have been focused to begin with, which means
+  // that subsequent calls to focus it won't result in command
+  // updates, so we'll make sure to blur it.
+  gURLBar.blur();
+
   let overridePromise = expectCommandUpdate(1);
   gURLBar.select();
   gURLBar.focus();
   gURLBar.value = "other";
   await overridePromise;
   checkState(false, "Update when edit-controls on toolbar and focused");
 
   overridePromise = expectCommandUpdate(1);
--- a/browser/components/customizableui/test/browser_panelUINotifications.js
+++ b/browser/components/customizableui/test/browser_panelUINotifications.js
@@ -1,10 +1,12 @@
 "use strict";
 
+Cu.import("resource://gre/modules/AppMenuNotifications.jsm");
+
 /**
  * Tests that when we click on the main call-to-action of the doorhanger, the provided
  * action is called, and the doorhanger removed.
  */
 add_task(async function testMainActionCalled() {
   let options = {
     gBrowser: window.gBrowser,
     url: "about:blank"
@@ -13,17 +15,17 @@ add_task(async function testMainActionCa
   await BrowserTestUtils.withNewTab(options, function(browser) {
     let doc = browser.ownerDocument;
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     let mainActionCalled = false;
     let mainAction = {
       callback: () => { mainActionCalled = true; }
     };
-    PanelUI.showNotification("update-manual", mainAction);
+    AppMenuNotifications.showNotification("update-manual", mainAction);
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
@@ -31,118 +33,16 @@ add_task(async function testMainActionCa
 
     ok(mainActionCalled, "Main action callback was called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
   });
 });
 
 /**
- * Tests that when we try to show a notification in a background window, it
- * does not display until the window comes back into the foreground. However,
- * it should display a badge.
- */
-add_task(async function testDoesNotShowDoorhangerForBackgroundWindow() {
-  let options = {
-    gBrowser: window.gBrowser,
-    url: "about:blank"
-  };
-
-  await BrowserTestUtils.withNewTab(options, async function(browser) {
-    let doc = browser.ownerDocument;
-
-    let win = await BrowserTestUtils.openNewBrowserWindow();
-    let mainActionCalled = false;
-    let mainAction = {
-      callback: () => { mainActionCalled = true; }
-    };
-    PanelUI.showNotification("update-manual", mainAction);
-    is(PanelUI.notificationPanel.state, "closed", "The background window's doorhanger is closed.");
-    is(PanelUI.menuButton.hasAttribute("badge-status"), true, "The background window has a badge.");
-
-    await BrowserTestUtils.closeWindow(win);
-    await SimpleTest.promiseFocus(window);
-    isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
-    let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
-    is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
-    let doorhanger = notifications[0];
-    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
-
-    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
-    button.click();
-
-    ok(mainActionCalled, "Main action callback was called");
-    is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
-    is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
-  });
-});
-
-/**
- * Tests that when we try to show a notification in a background window and in
- * a foreground window, if the foreground window's main action is called, the
- * background window's doorhanger will be removed.
- */
-add_task(async function testBackgroundWindowNotificationsAreRemovedByForeground() {
-  let options = {
-    gBrowser: window.gBrowser,
-    url: "about:blank"
-  };
-
-  await BrowserTestUtils.withNewTab(options, async function(browser) {
-    let win = await BrowserTestUtils.openNewBrowserWindow();
-    PanelUI.showNotification("update-manual", {callback() {}});
-    win.PanelUI.showNotification("update-manual", {callback() {}});
-    let doc = win.gBrowser.ownerDocument;
-    let notifications = [...win.PanelUI.notificationPanel.children].filter(n => !n.hidden);
-    let doorhanger = notifications[0];
-    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
-    button.click();
-
-    await BrowserTestUtils.closeWindow(win);
-    await SimpleTest.promiseFocus(window);
-
-    is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
-    is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
-  });
-});
-
-/**
- * Tests that when we try to show a notification in a background window and in
- * a foreground window, if the foreground window's doorhanger is dismissed,
- * the background window's doorhanger will also be dismissed once the window
- * regains focus.
- */
-add_task(async function testBackgroundWindowNotificationsAreDismissedByForeground() {
-  let options = {
-    gBrowser: window.gBrowser,
-    url: "about:blank"
-  };
-
-  await BrowserTestUtils.withNewTab(options, async function(browser) {
-    let win = await BrowserTestUtils.openNewBrowserWindow();
-    PanelUI.showNotification("update-manual", {callback() {}});
-    win.PanelUI.showNotification("update-manual", {callback() {}});
-    let doc = win.gBrowser.ownerDocument;
-    let notifications = [...win.PanelUI.notificationPanel.children].filter(n => !n.hidden);
-    let doorhanger = notifications[0];
-    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
-    button.click();
-
-    await BrowserTestUtils.closeWindow(win);
-    await SimpleTest.promiseFocus(window);
-
-    is(PanelUI.notificationPanel.state, "closed", "The background window's doorhanger is closed.");
-    is(PanelUI.menuButton.hasAttribute("badge-status"), true,
-       "The dismissed notification should still have a badge status");
-
-    PanelUI.removeNotification(/.*/);
-  });
-});
-
-/**
  * This tests that when we click the secondary action for a notification,
  * it will display the badge for that notification on the PanelUI menu button.
  * Once we click on this button, we should see an item in the menu which will
  * call our main action.
  */
 add_task(async function testSecondaryActionWorkflow() {
   let options = {
     gBrowser: window.gBrowser,
@@ -153,17 +53,17 @@ add_task(async function testSecondaryAct
     let doc = browser.ownerDocument;
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     let mainActionCalled = false;
     let mainAction = {
       callback: () => { mainActionCalled = true; },
     };
-    PanelUI.showNotification("update-manual", mainAction);
+    AppMenuNotifications.showNotification("update-manual", mainAction);
 
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
@@ -181,39 +81,39 @@ add_task(async function testSecondaryAct
 
     await PanelUI.hide();
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is shown on PanelUI button.");
 
     await PanelUI.show();
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
-    PanelUI.removeNotification(/.*/);
+    AppMenuNotifications.removeNotification(/.*/);
   });
 });
 
 /**
  * We want to ensure a few things with this:
  * - Adding a doorhanger will make a badge disappear
  * - once the notification for the doorhanger is resolved (removed, not just dismissed),
  *   then we display any other badges that are remaining.
  */
 add_task(async function testInteractionWithBadges() {
   await BrowserTestUtils.withNewTab("about:blank", async function(browser) {
     let doc = browser.ownerDocument;
 
-    PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
+    AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     let mainActionCalled = false;
     let mainAction = {
       callback: () => { mainActionCalled = true; },
     };
-    PanelUI.showNotification("update-manual", mainAction);
+    AppMenuNotifications.showNotification("update-manual", mainAction);
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
@@ -229,96 +129,96 @@ add_task(async function testInteractionW
     let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
     is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
-    PanelUI.removeNotification(/.*/);
+    AppMenuNotifications.removeNotification(/.*/);
     is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
   });
 });
 
 /**
  * This tests that adding a badge will not dismiss any existing doorhangers.
  */
 add_task(async function testAddingBadgeWhileDoorhangerIsShowing() {
   await BrowserTestUtils.withNewTab("about:blank", function(browser) {
     let doc = browser.ownerDocument;
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     let mainActionCalled = false;
     let mainAction = {
       callback: () => { mainActionCalled = true; }
     };
-    PanelUI.showNotification("update-manual", mainAction);
-    PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
+    AppMenuNotifications.showNotification("update-manual", mainAction);
+    AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
 
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is hidden on PanelUI button.");
     isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
     let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     let doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
     let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
     mainActionButton.click();
 
     ok(mainActionCalled, "Main action callback was called");
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Fxa badge is shown on PanelUI button.");
-    PanelUI.removeNotification(/.*/);
+    AppMenuNotifications.removeNotification(/.*/);
     is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
   });
 });
 
 /**
  * Tests that badges operate like a stack.
  */
 add_task(async function testMultipleBadges() {
   await BrowserTestUtils.withNewTab("about:blank", async function(browser) {
     let doc = browser.ownerDocument;
     let menuButton = doc.getElementById("PanelUI-menu-button");
 
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
     is(menuButton.hasAttribute("badge"), false, "Should not have the badge attribute set");
 
-    PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
+    AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
     is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
-    PanelUI.showBadgeOnlyNotification("update-succeeded");
+    AppMenuNotifications.showBadgeOnlyNotification("update-succeeded");
     is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
 
-    PanelUI.showBadgeOnlyNotification("update-failed");
+    AppMenuNotifications.showBadgeOnlyNotification("update-failed");
     is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
-    PanelUI.showBadgeOnlyNotification("download-severe");
+    AppMenuNotifications.showBadgeOnlyNotification("download-severe");
     is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
 
-    PanelUI.showBadgeOnlyNotification("download-warning");
+    AppMenuNotifications.showBadgeOnlyNotification("download-warning");
     is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
 
-    PanelUI.removeNotification(/^download-/);
+    AppMenuNotifications.removeNotification(/^download-/);
     is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
-    PanelUI.removeNotification(/^update-/);
+    AppMenuNotifications.removeNotification(/^update-/);
     is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
-    PanelUI.removeNotification(/^fxa-/);
+    AppMenuNotifications.removeNotification(/^fxa-/);
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
 
     await PanelUI.show();
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
     PanelUI.hide();
 
-    PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
-    PanelUI.showBadgeOnlyNotification("update-succeeded");
-    PanelUI.removeNotification(/.*/);
+    AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
+    AppMenuNotifications.showBadgeOnlyNotification("update-succeeded");
+    AppMenuNotifications.removeNotification(/.*/);
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
   });
 });
 
 /**
  * Tests that non-badges also operate like a stack.
  */
 add_task(async function testMultipleNonBadges() {
@@ -331,28 +231,28 @@ add_task(async function testMultipleNonB
         called: false,
         callback: () => { updateManualAction.called = true; },
     };
     let updateRestartAction = {
         called: false,
         callback: () => { updateRestartAction.called = true; },
     };
 
-    PanelUI.showNotification("update-manual", updateManualAction);
+    AppMenuNotifications.showNotification("update-manual", updateManualAction);
 
     let notifications;
     let doorhanger;
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
 
-    PanelUI.showNotification("update-restart", updateRestartAction);
+    AppMenuNotifications.showNotification("update-restart", updateRestartAction);
 
     isnot(PanelUI.notificationPanel.state, "closed", "Doorhanger is showing.");
     notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
     is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
     doorhanger = notifications[0];
     is(doorhanger.id, "appMenu-update-restart-notification", "PanelUI is displaying the update-restart notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
@@ -377,47 +277,8 @@ add_task(async function testMultipleNonB
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is hidden on PanelUI button.");
     is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(updateManualAction.called, "update-manual main action callback was called");
   });
 });
-
-add_task(async function testFullscreen() {
-  let doc = document;
-
-  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
-  let mainActionCalled = false;
-  let mainAction = {
-    callback: () => { mainActionCalled = true; }
-  };
-  PanelUI.showNotification("update-manual", mainAction);
-
-  isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
-  let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
-  is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
-  let doorhanger = notifications[0];
-  is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
-
-  let popuphiddenPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popuphidden");
-  EventUtils.synthesizeKey("VK_F11", {});
-  await popuphiddenPromise;
-  await new Promise(executeSoon);
-  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
-
-  window.FullScreen.showNavToolbox();
-  is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
-
-  let popupshownPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
-  EventUtils.synthesizeKey("VK_F11", {});
-  await popupshownPromise;
-  await new Promise(executeSoon);
-  isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
-  isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is not displaying on PanelUI button.");
-
-  let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
-  mainActionButton.click();
-  ok(mainActionCalled, "Main action callback was called");
-  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
-  is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
-});
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_panelUINotifications_fullscreen.js
@@ -0,0 +1,42 @@
+"use strict";
+
+Cu.import("resource://gre/modules/AppMenuNotifications.jsm");
+
+add_task(async function testFullscreen() {
+  let doc = document;
+
+  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
+  let mainActionCalled = false;
+  let mainAction = {
+    callback: () => { mainActionCalled = true; }
+  };
+  AppMenuNotifications.showNotification("update-manual", mainAction);
+
+  isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
+  let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
+  is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
+  let doorhanger = notifications[0];
+  is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+
+  let popuphiddenPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popuphidden");
+  EventUtils.synthesizeKey("VK_F11", {});
+  await popuphiddenPromise;
+  await new Promise(executeSoon);
+  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
+
+  FullScreen.showNavToolbox();
+  is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
+
+  let popupshownPromise = BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
+  EventUtils.synthesizeKey("VK_F11", {});
+  await popupshownPromise;
+  await new Promise(executeSoon);
+  isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
+  isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is not displaying on PanelUI button.");
+
+  let mainActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
+  mainActionButton.click();
+  ok(mainActionCalled, "Main action callback was called");
+  is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
+  is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_panelUINotifications_multiWindow.js
@@ -0,0 +1,130 @@
+"use strict";
+
+Cu.import("resource://gre/modules/AppMenuNotifications.jsm");
+
+/**
+ * Tests that when we try to show a notification in a background window, it
+ * does not display until the window comes back into the foreground. However,
+ * it should display a badge.
+ */
+add_task(async function testDoesNotShowDoorhangerForBackgroundWindow() {
+  let options = {
+    gBrowser: window.gBrowser,
+    url: "about:blank"
+  };
+
+  await BrowserTestUtils.withNewTab(options, async function(browser) {
+    let doc = browser.ownerDocument;
+
+    let win = await BrowserTestUtils.openNewBrowserWindow();
+    let mainActionCalled = false;
+    let mainAction = {
+      callback: () => { mainActionCalled = true; }
+    };
+    AppMenuNotifications.showNotification("update-manual", mainAction);
+    is(PanelUI.notificationPanel.state, "closed", "The background window's doorhanger is closed.");
+    is(PanelUI.menuButton.hasAttribute("badge-status"), true, "The background window has a badge.");
+
+    await BrowserTestUtils.closeWindow(win);
+    await SimpleTest.promiseFocus(window);
+    isnot(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is showing.");
+    let notifications = [...PanelUI.notificationPanel.children].filter(n => !n.hidden);
+    is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
+    let doorhanger = notifications[0];
+    is(doorhanger.id, "appMenu-update-manual-notification", "PanelUI is displaying the update-manual notification.");
+
+    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
+    button.click();
+
+    ok(mainActionCalled, "Main action callback was called");
+    is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
+    is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+  });
+});
+
+/**
+ * Tests that when we try to show a notification in a background window and in
+ * a foreground window, if the foreground window's main action is called, the
+ * background window's doorhanger will be removed.
+ */
+add_task(async function testBackgroundWindowNotificationsAreRemovedByForeground() {
+  let options = {
+    gBrowser: window.gBrowser,
+    url: "about:blank"
+  };
+
+  await BrowserTestUtils.withNewTab(options, async function(browser) {
+    let win = await BrowserTestUtils.openNewBrowserWindow();
+    AppMenuNotifications.showNotification("update-manual", {callback() {}});
+    let doc = win.gBrowser.ownerDocument;
+    let notifications = [...win.PanelUI.notificationPanel.children].filter(n => !n.hidden);
+    is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
+    let doorhanger = notifications[0];
+    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "button");
+    button.click();
+
+    await BrowserTestUtils.closeWindow(win);
+    await SimpleTest.promiseFocus(window);
+
+    is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
+    is(PanelUI.menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+  });
+});
+
+/**
+ * Tests that when we try to show a notification in a background window and in
+ * a foreground window, if the foreground window's doorhanger is dismissed,
+ * the background window's doorhanger will also be dismissed once the window
+ * regains focus.
+ */
+add_task(async function testBackgroundWindowNotificationsAreDismissedByForeground() {
+  let options = {
+    gBrowser: window.gBrowser,
+    url: "about:blank"
+  };
+
+  await BrowserTestUtils.withNewTab(options, async function(browser) {
+    let win = await BrowserTestUtils.openNewBrowserWindow();
+    AppMenuNotifications.showNotification("update-manual", {callback() {}});
+    let doc = win.gBrowser.ownerDocument;
+    let notifications = [...win.PanelUI.notificationPanel.children].filter(n => !n.hidden);
+    is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
+    let doorhanger = notifications[0];
+    let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
+    button.click();
+
+    await BrowserTestUtils.closeWindow(win);
+    await SimpleTest.promiseFocus(window);
+
+    is(PanelUI.notificationPanel.state, "closed", "The background window's doorhanger is closed.");
+    is(PanelUI.menuButton.hasAttribute("badge-status"), true,
+       "The dismissed notification should still have a badge status");
+
+    AppMenuNotifications.removeNotification(/.*/);
+  });
+});
+
+/**
+ * Tests that when we open a new window while a notification is showing, the
+ * notification also shows on the new window.
+ */
+add_task(async function testOpenWindowAfterShowingNotification() {
+  AppMenuNotifications.showNotification("update-manual", {callback() {}});
+
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  let doc = win.gBrowser.ownerDocument;
+  let notifications = [...win.PanelUI.notificationPanel.children].filter(n => !n.hidden);
+  is(notifications.length, 1, "PanelUI doorhanger is only displaying one notification.");
+  let doorhanger = notifications[0];
+  let button = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
+  button.click();
+
+  await BrowserTestUtils.closeWindow(win);
+  await SimpleTest.promiseFocus(window);
+
+  is(PanelUI.notificationPanel.state, "closed", "The background window's doorhanger is closed.");
+  is(PanelUI.menuButton.hasAttribute("badge-status"), true,
+     "The dismissed notification should still have a badge status");
+
+  AppMenuNotifications.removeNotification(/.*/);
+});
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -36,16 +36,20 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
+                                  "resource://gre/modules/AppMenuNotifications.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+                                  "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
@@ -1198,16 +1202,28 @@ DownloadsIndicatorDataCtor.prototype = {
    */
   _updateViews() {
     // Do not update the status indicators during batch loads of download items.
     if (this._loading) {
       return;
     }
 
     this._refreshProperties();
+
+    let widgetGroup = CustomizableUI.getWidget("downloads-button");
+    let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
+    if (inMenu) {
+      if (this._attention == DownloadsCommon.ATTENTION_NONE) {
+        AppMenuNotifications.removeNotification(/^download-/);
+      } else {
+        let badgeClass = "download-" + this._attention;
+        AppMenuNotifications.showBadgeOnlyNotification(badgeClass);
+      }
+    }
+
     this._views.forEach(this._updateView, this);
   },
 
   /**
    * Updates the specified view with the current aggregate values.
    *
    * @param aView
    *        DownloadsIndicatorView object to be updated.
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -436,25 +436,18 @@ const DownloadsIndicatorView = {
     // For arrow-Styled indicator, suppress success attention if we have
     // progress in toolbar
     let suppressAttention = !inMenu &&
       this._attention == DownloadsCommon.ATTENTION_SUCCESS &&
       this._percentComplete >= 0;
 
     if (suppressAttention || this._attention == DownloadsCommon.ATTENTION_NONE) {
       this.indicator.removeAttribute("attention");
-      if (inMenu) {
-        PanelUI.removeNotification(/^download-/);
-      }
     } else {
       this.indicator.setAttribute("attention", this._attention);
-      if (inMenu) {
-        let badgeClass = "download-" + this._attention;
-        PanelUI.showBadgeOnlyNotification(badgeClass);
-      }
     }
   },
   _attention: DownloadsCommon.ATTENTION_NONE,
 
   // User interface event functions
 
   onWindowUnload() {
     // This function is registered as an event listener, we can't use "this".
--- a/browser/components/extensions/test/browser/browser_ext_geckoProfiler_symbolicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_geckoProfiler_symbolicate.js
@@ -32,17 +32,17 @@ let getExtension = () => {
           "id": "profilertest@mozilla.com",
         },
       },
     },
   });
 };
 
 add_task(async function testProfilerControl() {
-  SpecialPowers.pushPrefEnv({
+  await SpecialPowers.pushPrefEnv({
     set: [
       [
         "extensions.geckoProfiler.symbols.url",
         "http://mochi.test:8888/browser/browser/components/extensions/test/browser/profilerSymbols.sjs?path=",
       ],
       [
         "extensions.geckoProfiler.acceptedExtensionIds",
         "profilertest@mozilla.com",
deleted file mode 100644
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ /dev/null
@@ -1,20 +0,0 @@
-"use strict";
-
-const EDGE_AVAILABLE_MIGRATIONS =
-  MigrationUtils.resourceTypes.COOKIES |
-  MigrationUtils.resourceTypes.BOOKMARKS |
-  MigrationUtils.resourceTypes.HISTORY |
-  MigrationUtils.resourceTypes.PASSWORDS;
-
-add_task(function* () {
-  let migrator = MigrationUtils.getMigrator("edge");
-  Cu.import("resource://gre/modules/AppConstants.jsm");
-  Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
-               "Edge should be available for migration if and only if we're on Win 10+");
-  if (migrator) {
-    let migratableData = migrator.getMigrateData(null, false);
-    Assert.equal(migratableData, EDGE_AVAILABLE_MIGRATIONS,
-                 "All the data types we expect should be available");
-  }
-});
-
--- a/browser/components/migration/tests/unit/xpcshell.ini
+++ b/browser/components/migration/tests/unit/xpcshell.ini
@@ -7,17 +7,16 @@ support-files =
   AppData/**
 
 [test_automigration.js]
 [test_Chrome_bookmarks.js]
 [test_Chrome_cookies.js]
 skip-if = os != "mac" # Relies on ULibDir
 [test_Chrome_passwords.js]
 skip-if = os != "win"
-[test_Edge_availability.js]
 [test_Edge_db_migration.js]
 skip-if = os != "win" || os_version == "5.1" || os_version == "5.2" # Relies on post-XP bits of ESEDB
 [test_fx_telemetry.js]
 [test_IE_bookmarks.js]
 skip-if = os != "win"
 [test_IE_cookies.js]
 skip-if = os != "win"
 [test_IE7_passwords.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -17,44 +17,47 @@ Cu.import("resource://gre/modules/AsyncP
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils", "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-service;1", "nsIAlertsService");
 XPCOMUtils.defineLazyGetter(this, "WeaveService", () =>
   Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject
 );
 
 // lazy module getters
 
-/* global AboutHome:false, AboutNewTab:false, AddonManager:false,
+/* global AboutHome:false, AboutNewTab:false, AddonManager:false, AppMenuNotifications:false,
           AsyncShutdown:false, AutoCompletePopup:false, BookmarkHTMLUtils:false,
           BookmarkJSONUtils:false, BrowserUITelemetry:false, BrowserUsageTelemetry:false,
           ContentClick:false, ContentPrefServiceParent:false, ContentSearch:false,
           DateTimePickerHelper:false, DirectoryLinksProvider:false,
           ExtensionsUI:false, Feeds:false,
           FileUtils:false, FormValidationHandler:false, Integration:false,
           LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
           NetUtil:false, NewTabUtils:false, OS:false,
           PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
           PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
           RemotePrompt:false, SessionStore:false,
           ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
-          Task:false, UITour:false, WebChannel:false,
+          Task:false, UITour:false, UIState:false, UpdateListener:false, WebChannel:false,
           WindowsRegistry:false, webrtcUI:false */
 
+
+
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 
 let initializedModules = {};
 
 [
   ["AboutHome", "resource:///modules/AboutHome.jsm", "init"],
   ["AboutNewTab", "resource:///modules/AboutNewTab.jsm"],
   ["AddonManager", "resource://gre/modules/AddonManager.jsm"],
+  ["AppMenuNotifications", "resource://gre/modules/AppMenuNotifications.jsm"],
   ["AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"],
   ["AutoCompletePopup", "resource://gre/modules/AutoCompletePopup.jsm"],
   ["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
   ["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
   ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
   ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
   ["ContentClick", "resource:///modules/ContentClick.jsm"],
   ["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm", "alwaysInit"],
@@ -83,17 +86,19 @@ let initializedModules = {};
   ["ReaderParent", "resource:///modules/ReaderParent.jsm"],
   ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
   ["RemotePrompt", "resource:///modules/RemotePrompt.jsm"],
   ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
   ["ShellService", "resource:///modules/ShellService.jsm"],
   ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
+  ["UIState", "resource://services-sync/UIState.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
+  ["UpdateListener", "resource://gre/modules/UpdateListener.jsm", "init"],
   ["WebChannel", "resource://gre/modules/WebChannel.jsm"],
   ["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm", "init"],
 ].forEach(([name, resource, init]) => {
   if (init) {
     XPCOMUtils.defineLazyGetter(this, name, () => {
       Cu.import(resource, initializedModules);
       initializedModules[name][init]();
@@ -119,16 +124,23 @@ XPCOMUtils.defineLazyGetter(this, "gBran
 
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/browser.properties");
 });
 
 const global = this;
 
 const listeners = {
+  observers: {
+    "update-staged": ["UpdateListener"],
+    "update-downloaded": ["UpdateListener"],
+    "update-available": ["UpdateListener"],
+    "update-error": ["UpdateListener"],
+  },
+
   ppmm: {
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "ContentPrefs:FunctionCall": ["ContentPrefServiceParent"],
     "ContentPrefs:AddObserverForName": ["ContentPrefServiceParent"],
     "ContentPrefs:RemoveObserverForName": ["ContentPrefServiceParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN ContentPrefServiceParent.init
     "FeedConverter:addLiveBookmark": ["Feeds"],
     "WCCR:setAutoHandler": ["Feeds"],
@@ -160,29 +172,43 @@ const listeners = {
     "rtcpeer:CancelRequest": ["webrtcUI"],
     "rtcpeer:Request": ["webrtcUI"],
     "webrtc:CancelRequest": ["webrtcUI"],
     "webrtc:Request": ["webrtcUI"],
     "webrtc:StopRecording": ["webrtcUI"],
     "webrtc:UpdateBrowserIndicators": ["webrtcUI"],
   },
 
+  observe(subject, topic, data) {
+    for (let module of this.observers[topic]) {
+      try {
+        global[module].observe(subject, topic, data);
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+  },
+
   receiveMessage(modules, data) {
     let val;
     for (let module of modules[data.name]) {
       try {
         val = global[module].receiveMessage(data) || val;
       } catch (e) {
         Cu.reportError(e);
       }
     }
     return val;
   },
 
   init() {
+    for (let observer of Object.keys(this.observers)) {
+      Services.obs.addObserver(this, observer);
+    }
+
     let receiveMessageMM = this.receiveMessage.bind(this, this.mm);
     for (let message of Object.keys(this.mm)) {
       Services.mm.addMessageListener(message, receiveMessageMM);
     }
 
     let receiveMessagePPMM = this.receiveMessage.bind(this, this.ppmm);
     for (let message of Object.keys(this.ppmm)) {
       Services.ppmm.addMessageListener(message, receiveMessagePPMM);
@@ -463,16 +489,19 @@ BrowserGlue.prototype = {
               break;
             }
           }
         });
         break;
       case "test-initialize-sanitizer":
         this._sanitizer.onStartup();
         break;
+      case "sync-ui-state:update":
+        this._updateFxaBadges();
+        break;
     }
   },
 
   // initialization (called on application startup)
   _init: function BG__init() {
     let os = Services.obs;
     os.addObserver(this, "notifications-open-settings");
     os.addObserver(this, "prefservice:after-app-defaults");
@@ -501,16 +530,17 @@ BrowserGlue.prototype = {
     os.addObserver(this, "profile-before-change");
     if (AppConstants.MOZ_TELEMETRY_REPORTING) {
       os.addObserver(this, "keyword-search");
     }
     os.addObserver(this, "browser-search-engine-modified");
     os.addObserver(this, "restart-in-safe-mode");
     os.addObserver(this, "flash-plugin-hang");
     os.addObserver(this, "xpi-signature-changed");
+    os.addObserver(this, "sync-ui-state:update");
 
     this._flashHangCount = 0;
     this._firstWindowReady = new Promise(resolve => this._firstWindowLoaded = resolve);
 
     if (AppConstants.platform == "macosx") {
       // Handles prompting to inform about incompatibilites when accessibility
       // and e10s are active together.
       E10SAccessibilityCheck.init();
@@ -553,16 +583,17 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "handle-xul-text-link");
     os.removeObserver(this, "profile-before-change");
     if (AppConstants.MOZ_TELEMETRY_REPORTING) {
       os.removeObserver(this, "keyword-search");
     }
     os.removeObserver(this, "browser-search-engine-modified");
     os.removeObserver(this, "flash-plugin-hang");
     os.removeObserver(this, "xpi-signature-changed");
+    os.removeObserver(this, "sync-ui-state:update");
   },
 
   _onAppDefaults: function BG__onAppDefaults() {
     // apply distribution customizations (prefs)
     // other customizations are applied in _finalUIStartup()
     this._distributionCustomizer.applyPrefDefaults();
   },
 
@@ -578,31 +609,16 @@ BrowserGlue.prototype = {
 
     // apply distribution customizations
     // prefs are applied in _onAppDefaults()
     this._distributionCustomizer.applyCustomizations();
 
     // handle any UI migration
     this._migrateUI();
 
-    // This is support code for the location bar search suggestions; passing
-    // from opt-in to opt-out should respect the user's choice, thus we need
-    // to cache that choice in a pref for future use.
-    // Note: this is not in migrateUI because we need to uplift it. This
-    // code is also short-lived, since we can remove it as soon as opt-out
-    // search suggestions shipped in release (Bug 1344928).
-    try {
-      let urlbarPrefs = Services.prefs.getBranch("browser.urlbar.");
-      if (!urlbarPrefs.prefHasUserValue("searchSuggestionsChoice") &&
-          urlbarPrefs.getBoolPref("userMadeSearchSuggestionsChoice")) {
-        urlbarPrefs.setBoolPref("searchSuggestionsChoice",
-                                urlbarPrefs.getBoolPref("suggest.searches"));
-      }
-    } catch (ex) { /* missing any of the prefs is not critical */ }
-
     listeners.init();
 
     PageThumbs.init();
 
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
@@ -1688,17 +1704,17 @@ BrowserGlue.prototype = {
         return;
       this._openPreferences("sync", { origin: "doorhanger" });
     }
     AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 45;
+    const UI_VERSION = 46;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -1991,16 +2007,32 @@ BrowserGlue.prototype = {
       const LEGACY_PREF = "browser.shell.skipDefaultBrowserCheck";
       if (Services.prefs.prefHasUserValue(LEGACY_PREF)) {
         Services.prefs.setBoolPref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun",
                                    !Services.prefs.getBoolPref(LEGACY_PREF));
         Services.prefs.clearUserPref(LEGACY_PREF);
       }
     }
 
+    if (currentUIVersion < 46) {
+      // Search suggestions are now on by default.
+      // For privacy reasons, we want to respect previously made user's choice
+      // regarding the feature, so if it's known reflect that choice into the
+      // current pref.
+      // Note that in case of downgrade/upgrade we won't guarantee anything.
+      try {
+        Services.prefs.setBoolPref(
+          "browser.urlbar.suggest.searches",
+          Services.prefs.getBoolPref("browser.urlbar.searchSuggestionsChoice")
+        );
+      } catch (ex) {
+        // The pref is not set, nothing to do.
+      }
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
@@ -2310,16 +2342,26 @@ BrowserGlue.prototype = {
         win.openUILinkIn("https://support.mozilla.org/kb/flash-protected-mode-autodisabled", "tab");
       }
     }];
     let nb = win.document.getElementById("global-notificationbox");
     nb.appendNotification(message, "flash-hang", null,
                           nb.PRIORITY_INFO_MEDIUM, buttons);
   },
 
+  _updateFxaBadges() {
+    let state = UIState.get();
+    if (state.status == UIState.STATUS_LOGIN_FAILED ||
+        state.status == UIState.STATUS_NOT_VERIFIED) {
+      AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
+    } else {
+      AppMenuNotifications.removeNotification("fxa-needs-authentication");
+    }
+  },
+
   // for XPCOM
   classID:          Components.ID("{eab9012e-5f74-4cbc-b2b5-a590235513cc}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIBrowserGlue]),
 
   // redefine the default factory for XPCOMUtils
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -314,18 +314,17 @@
                 onsyncfrompreference="return gPrivacyPane.readSavePasswords();"
                 flex="1" />
       <button id="passwordExceptions"
               class="accessory-button"
               label="&passwordExceptions.label;"
               accesskey="&passwordExceptions.accesskey;"
               preference="pref.privacy.disable_button.view_passwords_exceptions"/>
     </hbox>
-    <hbox id="showPasswordBox">
-      <hbox id="showPasswordsBox"/>
+    <hbox id="showPasswordBox" pack="end">
       <button id="showPasswords"
               class="accessory-button"
               label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
               preference="pref.privacy.disable_button.view_passwords"/>
     </hbox>
   </vbox>
   <hbox id="masterPasswordRow">
     <checkbox id="useMasterPassword"
--- a/browser/components/sessionstore/test/browser_906076_lazy_tabs.js
+++ b/browser/components/sessionstore/test/browser_906076_lazy_tabs.js
@@ -13,16 +13,29 @@ const TEST_STATE = {
       { entries: [{ url: "http://example.com" }] },
       { entries: [{ url: "http://example.com" }] },
       { entries: [{ url: "http://example.com" }] },
       { entries: [{ url: "http://example.com" }] },
     ]
   }]
 };
 
+const TEST_STATE_2 = {
+  windows: [{
+    tabs: [
+      { entries: [{ url: "about:robots" }]
+      },
+      { entries: [],
+        userTypedValue: "http://example.com",
+        userTypedClear: 1
+      }
+    ]
+  }]
+};
+
 function countNonLazyTabs(win) {
   win = win || window;
   let count = 0;
   for (let browser of win.gBrowser.browsers) {
     if (browser.isConnected) {
       count++;
     }
   }
@@ -77,12 +90,23 @@ add_task(async function test() {
       is(countNonLazyTabs(newWindow), 1, "Window still has only 1 non-lazy tab");
 
       resolve();
     }, { once: true });
 
     newWindow.close();
   });
 
+  // Bug 1365933.
+  info("Check that session with tab having empty entries array gets restored properly");
+  await promiseBrowserState(TEST_STATE_2);
+
+  is(gBrowser.tabs.length, 2, "Window has 2 tabs");
+  is(gBrowser.selectedBrowser.currentURI.spec, "about:robots", "Tab has the expected URL");
+
+  gBrowser.selectedTab = gBrowser.tabs[1];
+  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+  is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/", "Tab has the expected URL");
+
   // Cleanup.
   await promiseBrowserState(backupState);
 });
 
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -300,22 +300,28 @@ var FormAutofillContent = {
 
   init() {
     FormAutofillUtils.defineLazyLogGetter(this, "FormAutofillContent");
 
     Services.cpmm.addMessageListener("FormAutofill:enabledStatus", this);
     Services.cpmm.addMessageListener("FormAutofill:savedFieldNames", this);
     Services.obs.addObserver(this, "earlyformsubmit");
 
-    if (Services.cpmm.initialProcessData.autofillEnabled) {
+    let autofillEnabled = Services.cpmm.initialProcessData.autofillEnabled;
+    if (autofillEnabled ||
+        // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
+        // autocomplete is registered before the focusin so register it in this case as long as the
+        // pref is true.
+        (autofillEnabled === undefined &&
+         Services.prefs.getBoolPref("extensions.formautofill.addresses.enabled"))) {
       ProfileAutocomplete.ensureRegistered();
     }
 
     this.savedFieldNames =
-      Services.cpmm.initialProcessData.autofillSavedFieldNames || new Set();
+      Services.cpmm.initialProcessData.autofillSavedFieldNames;
   },
 
   _onFormSubmit(handler) {
     // TODO: Handle form submit event for profile saving(bug 990219) and metrics(bug 1341569).
   },
 
   notify(formElement) {
     this.log.debug("notified for form early submission");
@@ -395,16 +401,22 @@ var FormAutofillContent = {
 
   getAllFieldNames(element) {
     let formDetails = this.getFormDetails(element);
     return formDetails.map(record => record.fieldName);
   },
 
   identifyAutofillFields(doc) {
     this.log.debug("identifyAutofillFields:", "" + doc.location);
+
+    if (!this.savedFieldNames) {
+      this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
+      Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
+    }
+
     let forms = [];
 
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
       // We only consider text-like fields for now until we support radio and
       // checkbox buttons in the future.
       if (!field.mozIsTextField(true)) {
         continue;
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -34,58 +34,61 @@ this.EXPORTED_SYMBOLS = ["FormAutofillPa
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
-                                  "resource://formautofill/ProfileStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillPreferences",
                                   "resource://formautofill/FormAutofillPreferences.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 const ENABLED_PREF = "extensions.formautofill.addresses.enabled";
 
 function FormAutofillParent() {
+  // Lazily load the storage JSM to avoid disk I/O until absolutely needed.
+  // Once storage is loaded we need to update saved field names and inform content processes.
+  XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+    let {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+    log.debug("Loading profileStorage");
+
+    profileStorage.initialize().then(function onStorageInitialized() {
+      // Update the saved field names to compute the status and update child processes.
+      this._updateSavedFieldNames();
+    }.bind(this));
+
+    return profileStorage;
+  });
 }
 
 FormAutofillParent.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
   /**
-   * Whether Form Autofill is enabled in preferences.
-   * Caches the latest value of this._getStatus().
+   * Cache of the Form Autofill status (considering preferences and storage).
    */
-  _enabled: false,
+  _active: null,
 
   /**
    * Initializes ProfileStorage and registers the message handler.
    */
   async init() {
-    log.debug("init");
-    await profileStorage.initialize();
-
     Services.obs.addObserver(this, "advanced-pane-loaded");
+    Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
-
-    // Force to trigger the onStatusChanged function for setting listeners properly
-    // while initizlization
-    this._setStatus(this._getStatus());
-    this._updateSavedFieldNames();
   },
 
   observe(subject, topic, data) {
     log.debug("observe:", topic, "with data:", data);
     switch (topic) {
       case "advanced-pane-loaded": {
         let useOldOrganization = Services.prefs.getBoolPref("browser.preferences.useOldOrganization",
                                                             false);
@@ -98,115 +101,114 @@ FormAutofillParent.prototype = {
         let insertBeforeNode = useOldOrganization ?
                                document.getElementById("locationBarGroup") :
                                document.getElementById("masterPasswordRow");
         parentNode.insertBefore(prefGroup, insertBeforeNode);
         break;
       }
 
       case "nsPref:changed": {
-        // Observe pref changes and update _enabled cache if status is changed.
-        let currentStatus = this._getStatus();
-        if (currentStatus !== this._enabled) {
-          this._setStatus(currentStatus);
-        }
+        // Observe pref changes and update _active cache if status is changed.
+        this._updateStatus();
         break;
       }
 
       case "formautofill-storage-changed": {
         // Early exit if the action is not "add" nor "remove"
         if (data != "add" && data != "remove") {
           break;
         }
 
         this._updateSavedFieldNames();
-        let currentStatus = this._getStatus();
-        if (currentStatus !== this._enabled) {
-          this._setStatus(currentStatus);
-        }
         break;
       }
 
       default: {
         throw new Error(`FormAutofillParent: Unexpected topic observed: ${topic}`);
       }
     }
   },
 
   /**
    * Broadcast the status to frames when the form autofill status changes.
    */
   _onStatusChanged() {
-    log.debug("_onStatusChanged: Status changed to", this._enabled);
-    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
+    log.debug("_onStatusChanged: Status changed to", this._active);
+    Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._active);
     // Sync process data autofillEnabled to make sure the value up to date
     // no matter when the new content process is initialized.
-    Services.ppmm.initialProcessData.autofillEnabled = this._enabled;
+    Services.ppmm.initialProcessData.autofillEnabled = this._active;
   },
 
   /**
-   * Query pref and storage status to determine the overall status for
+   * Query preference and storage status to determine the overall status of the
    * form autofill feature.
    *
-   * @returns {boolean} status of form autofill feature
+   * @returns {boolean} whether form autofill is active (enabled and has data)
    */
-  _getStatus() {
+  _computeStatus() {
     if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
       return false;
     }
 
-    return profileStorage.addresses.getAll({noComputedFields: true}).length > 0;
+    return Services.ppmm.initialProcessData.autofillSavedFieldNames.size > 0;
   },
 
   /**
-   * Set status and trigger _onStatusChanged.
-   *
-   * @param {boolean} newStatus The latest status we want to set for _enabled
+   * Update the status and trigger _onStatusChanged, if necessary.
    */
-  _setStatus(newStatus) {
-    this._enabled = newStatus;
-    this._onStatusChanged();
+  _updateStatus() {
+    let wasActive = this._active;
+    this._active = this._computeStatus();
+    if (this._active !== wasActive) {
+      this._onStatusChanged();
+    }
   },
 
   /**
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   receiveMessage({name, data, target}) {
     switch (name) {
+      case "FormAutofill:InitStorage": {
+        this.profileStorage.initialize();
+        break;
+      }
       case "FormAutofill:GetAddresses": {
         this._getAddresses(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          profileStorage.addresses.update(data.guid, data.address);
+          this.profileStorage.addresses.update(data.guid, data.address);
         } else {
-          profileStorage.addresses.add(data.address);
+          this.profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => profileStorage.addresses.remove(guid));
+        data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
    * @private
    */
   _uninit() {
-    profileStorage._saveImmediately();
+    this.profileStorage._saveImmediately();
 
+    Services.ppmm.removeMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
     Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
@@ -220,41 +222,43 @@ FormAutofillParent.prototype = {
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   _getAddresses({searchString, info}, target) {
     let addresses = [];
 
     if (info && info.fieldName) {
-      addresses = profileStorage.addresses.getByFilter({searchString, info});
+      addresses = this.profileStorage.addresses.getByFilter({searchString, info});
     } else {
-      addresses = profileStorage.addresses.getAll();
+      addresses = this.profileStorage.addresses.getAll();
     }
 
     target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
+    log.debug("_updateSavedFieldNames");
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    profileStorage.addresses.getAll().forEach((address) => {
+    this.profileStorage.addresses.getAll().forEach((address) => {
       Object.keys(address).forEach((fieldName) => {
         if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
     // Remove the internal guid and metadata fields.
-    profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
+    this.profileStorage.INTERNAL_FIELDS.forEach((fieldName) => {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
     });
 
     Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
                                         Services.ppmm.initialProcessData.autofillSavedFieldNames);
+    this._updateStatus();
   },
 };
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -25,55 +25,46 @@ function insertStyleSheet(domWindow, url
 
   if (CACHED_STYLESHEETS.has(domWindow)) {
     CACHED_STYLESHEETS.get(domWindow).push(styleSheet);
   } else {
     CACHED_STYLESHEETS.set(domWindow, [styleSheet]);
   }
 }
 
-let windowListener = {
-  onOpenWindow(window) {
-    let domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+function onMaybeOpenPopup(evt) {
+  let domWindow = evt.target.ownerGlobal;
+  if (CACHED_STYLESHEETS.has(domWindow)) {
+    // This window already has autofill stylesheets.
+    return;
+  }
 
-    domWindow.addEventListener("load", function onWindowLoaded() {
-      insertStyleSheet(domWindow, STYLESHEET_URI);
-    }, {once: true});
-  },
-};
+  insertStyleSheet(domWindow, STYLESHEET_URI);
+}
 
 function startup() {
   if (!Services.prefs.getBoolPref("extensions.formautofill.experimental")) {
     return;
   }
 
+  // Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
+  Services.mm.addMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
+
   let parent = new FormAutofillParent();
-  let enumerator = Services.wm.getEnumerator("navigator:browser");
-  // Load stylesheet to already opened windows
-  while (enumerator.hasMoreElements()) {
-    let win = enumerator.getNext();
-    let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-
-    insertStyleSheet(domWindow, STYLESHEET_URI);
-  }
-
-  Services.wm.addListener(windowListener);
-
   parent.init().catch(Cu.reportError);
   Services.ppmm.loadProcessScript("data:,new " + function() {
     Components.utils.import("resource://formautofill/FormAutofillContent.jsm");
   }, true);
   Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
 }
 
 function shutdown() {
-  Services.wm.removeListener(windowListener);
+  Services.mm.removeMessageListener("FormAutoComplete:MaybeOpenPopup", onMaybeOpenPopup);
 
   let enumerator = Services.wm.getEnumerator("navigator:browser");
-
   while (enumerator.hasMoreElements()) {
     let win = enumerator.getNext();
     let domWindow = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
     let cachedStyleSheets = CACHED_STYLESHEETS.get(domWindow);
 
     if (!cachedStyleSheets) {
       continue;
     }
rename from browser/extensions/formautofill/test/unit/test_enabledStatus.js
rename to browser/extensions/formautofill/test/unit/test_activeStatus.js
--- a/browser/extensions/formautofill/test/unit/test_enabledStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -2,101 +2,94 @@
  * Test for status handling in Form Autofill Parent.
  */
 
 "use strict";
 
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
-add_task(async function test_enabledStatus_init() {
+add_task(async function test_activeStatus_init() {
   let formAutofillParent = new FormAutofillParent();
-  sinon.spy(formAutofillParent, "_setStatus");
+  sinon.spy(formAutofillParent, "_updateStatus");
 
-  // Default status is false before initialization
-  do_check_eq(formAutofillParent._enabled, false);
+  // Default status is null before initialization
+  do_check_eq(formAutofillParent._active, null);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
 
   await formAutofillParent.init();
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  // init shouldn't call updateStatus since that requires storage which will
+  // lead to startup time regressions.
+  do_check_eq(formAutofillParent._updateStatus.called, false);
+  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, undefined);
+
+  // Initialize profile storage
+  await formAutofillParent.profileStorage.initialize();
+  // Upon first initializing profile storage, status should be computed.
+  do_check_eq(formAutofillParent._updateStatus.called, true);
   do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, false);
 
   formAutofillParent._uninit();
 });
 
-add_task(function* test_enabledStatus_observe() {
+add_task(async function test_activeStatus_observe() {
   let formAutofillParent = new FormAutofillParent();
-  sinon.stub(formAutofillParent, "_getStatus");
-  sinon.spy(formAutofillParent, "_setStatus");
-  sinon.stub(formAutofillParent, "_updateSavedFieldNames");
+  sinon.stub(formAutofillParent, "_computeStatus");
+  sinon.spy(formAutofillParent, "_onStatusChanged");
 
-  // _enabled = _getStatus() => No need to trigger onStatusChanged
-  formAutofillParent._enabled = true;
-  formAutofillParent._getStatus.returns(true);
+  // _active = _computeStatus() => No need to trigger _onStatusChanged
+  formAutofillParent._active = true;
+  formAutofillParent._computeStatus.returns(true);
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
-  do_check_eq(formAutofillParent._setStatus.called, false);
+  do_check_eq(formAutofillParent._onStatusChanged.called, false);
 
-  // _enabled != _getStatus() => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(false);
+  // _active != _computeStatus() => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(false);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile added => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile added => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile removed => Need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile removed => Need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "remove");
-  do_check_eq(formAutofillParent._setStatus.called, true);
+  do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile updated => no need to trigger onStatusChanged
-  formAutofillParent._getStatus.returns(!formAutofillParent._enabled);
-  formAutofillParent._setStatus.reset();
+  // profile updated => no need to trigger _onStatusChanged
+  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+  formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "update");
-  do_check_eq(formAutofillParent._setStatus.called, false);
+  do_check_eq(formAutofillParent._onStatusChanged.called, false);
 });
 
-add_task(function* test_enabledStatus_getStatus() {
+add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
   sinon.stub(profileStorage.addresses, "getAll");
   profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 
-  profileStorage.addresses.getAll.returns(["test-profile"]);
+  profileStorage.addresses.getAll.returns([{"given-name": "John"}]);
+  formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
-  do_check_eq(formAutofillParent._getStatus(), true);
+  do_check_eq(formAutofillParent._computeStatus(), true);
 
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
-  do_check_eq(formAutofillParent._getStatus(), false);
+  do_check_eq(formAutofillParent._computeStatus(), false);
 });
-
-add_task(function* test_enabledStatus_setStatus() {
-  let formAutofillParent = new FormAutofillParent();
-  sinon.spy(formAutofillParent, "_onStatusChanged");
-
-  formAutofillParent._setStatus(true);
-  do_check_eq(formAutofillParent._enabled, true);
-  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, true);
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-
-  formAutofillParent._onStatusChanged.reset();
-  formAutofillParent._setStatus(false);
-  do_check_eq(formAutofillParent._enabled, false);
-  do_check_eq(Services.ppmm.initialProcessData.autofillEnabled, false);
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-});
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -7,16 +7,17 @@
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 add_task(async function test_profileSavedFieldNames_init() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
+  await formAutofillParent.profileStorage.initialize();
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
 
   formAutofillParent._uninit();
 });
 
 add_task(async function test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -11,21 +11,21 @@ support-files =
 [heuristics/third_party/test_HomeDepot.js]
 [heuristics/third_party/test_Macys.js]
 [heuristics/third_party/test_NewEgg.js]
 [heuristics/third_party/test_OfficeDepot.js]
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
+[test_activeStatus.js]
 [test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_creditCardRecords.js]
-[test_enabledStatus.js]
 [test_findLabelElements.js]
 [test_getFormInputDetails.js]
 [test_isCJKName.js]
 [test_markAsAutofillField.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
 [test_savedFieldNames.js]
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -483,16 +483,25 @@ editBookmark.removeBookmarks.label=Remov
 # Post Update Notifications
 pu.notifyButton.label=Details…
 pu.notifyButton.accesskey=D
 # LOCALIZATION NOTE %S will be replaced by the short name of the application.
 puNotifyText=%S has been updated
 puAlertTitle=%S Updated
 puAlertText=Click here for details
 
+# Application menu
+
+# LOCALIZATION NOTE (cut-button.tooltip): %S is the keyboard shortcut.
+cut-button.tooltip = Cut (%S)
+# LOCALIZATION NOTE (copy-button.tooltip): %S is the keyboard shortcut.
+copy-button.tooltip = Copy (%S)
+# LOCALIZATION NOTE (paste-button.tooltip): %S is the keyboard shortcut.
+paste-button.tooltip = Paste (%S)
+
 # Geolocation UI
 
 geolocation.allowLocation=Allow Location Access
 geolocation.allowLocation.accesskey=A
 geolocation.dontAllowLocation=Don’t Allow
 geolocation.dontAllowLocation.accesskey=n
 geolocation.shareWithSite3=Will you allow %S to access your location?
 geolocation.shareWithFile3=Will you allow this local file to access your location?
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -9,16 +9,18 @@ this.EXPORTED_SYMBOLS = ["ExtensionsUI"]
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
+                                  "resource://gre/modules/AppMenuNotifications.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
@@ -66,55 +68,64 @@ this.ExtensionsUI = {
       if (!this.sideloadListener) {
         this.sideloadListener = {
           onEnabled: addon => {
             if (!this.sideloaded.has(addon)) {
               return;
             }
 
             this.sideloaded.delete(addon);
-            this.emit("change");
+              this._updateNotifications();
 
             if (this.sideloaded.size == 0) {
               AddonManager.removeAddonListener(this.sideloadListener);
               this.sideloadListener = null;
             }
           },
         };
         AddonManager.addAddonListener(this.sideloadListener);
       }
 
       for (let addon of sideloaded) {
         this.sideloaded.add(addon);
       }
-      this.emit("change");
+        this._updateNotifications();
     } else {
       // This and all the accompanying about:newaddon code can eventually
       // be removed.  See bug 1331521.
       let win = RecentWindow.getMostRecentBrowserWindow();
       for (let addon of sideloaded) {
         win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
       }
     }
   },
 
+  _updateNotifications() {
+    if (this.sideloaded.size + this.updates.size == 0) {
+      AppMenuNotifications.removeNotification("addon-alert");
+    } else {
+      AppMenuNotifications.showBadgeOnlyNotification("addon-alert");
+    }
+    this.emit("change");
+  },
+
   showAddonsManager(browser, strings, icon, histkey) {
     let global = browser.selectedBrowser.ownerGlobal;
     return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
       let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
       return this.showPermissionsPrompt(aomBrowser, strings, icon, histkey);
     });
   },
 
   showSideloaded(browser, addon) {
     addon.markAsSeen();
     this.sideloaded.delete(addon);
-    this.emit("change");
+    this._updateNotifications();
 
     let strings = this._buildStrings({
       addon,
       permissions: addon.userPermissions,
       type: "sideload",
     });
     this.showAddonsManager(browser, strings, addon.iconURL, "sideload")
         .then(answer => {
@@ -128,17 +139,17 @@ this.ExtensionsUI = {
           if (answer) {
             info.resolve();
           } else {
             info.reject();
           }
           // At the moment, this prompt will re-appear next time we do an update
           // check.  See bug 1332360 for proposal to avoid this.
           this.updates.delete(info);
-          this.emit("change");
+          this._updateNotifications();
         });
   },
 
   observe(subject, topic, data) {
     if (topic == "webextension-permission-prompt") {
       let {target, info} = subject.wrappedJSObject;
 
       // Dismiss the progress notification.  Note that this is bad if
@@ -200,17 +211,17 @@ this.ExtensionsUI = {
       let update = {
         strings,
         addon: info.addon,
         resolve: info.resolve,
         reject: info.reject,
       };
 
       this.updates.add(update);
-      this.emit("change");
+      this._updateNotifications();
     } else if (topic == "webextension-install-notify") {
       let {target, addon, callback} = subject.wrappedJSObject;
       this.showInstallNotification(target, addon).then(() => {
         if (callback) {
           callback();
         }
       });
     } else if (topic == "webextension-optional-permission-prompt") {
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -260,41 +260,47 @@ menuitem.bookmark-item {
 #restore-button {
   list-style-image: url("chrome://global/skin/icons/Restore.gif");
 }
 #close-button {
   list-style-image: url("chrome://global/skin/icons/Close.gif");
 }
 
 /* Location bar */
-#main-window {
-  --urlbar-border-color: ThreeDShadow;
-  --urlbar-border-color-hover: var(--urlbar-border-color);
-}
-
-#navigator-toolbox:-moz-lwtheme {
-  --urlbar-border-color: rgba(0,0,0,.3);
-}
 
 #urlbar {
   /* override textbox[enablehistory="true"] styling: */
   background-color: -moz-field;
 }
 
 %include ../shared/urlbar-searchbar.inc.css
 
 %ifdef MOZ_PHOTON_THEME
 
+#urlbar:not(:-moz-lwtheme):not([focused="true"]),
+.searchbar-textbox:not(:-moz-lwtheme):not([focused="true"]) {
+  border-color: ThreeDShadow;
+}
+
 #urlbar[focused="true"],
 .searchbar-textbox[focused="true"] {
   border-color: Highlight;
 }
 
 %else
 
+#main-window {
+  --urlbar-border-color: ThreeDShadow;
+  --urlbar-border-color-hover: var(--urlbar-border-color);
+}
+
+#navigator-toolbox:-moz-lwtheme {
+  --urlbar-border-color: rgba(0,0,0,.3);
+}
+
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
   padding: 0;
   border: 1px solid var(--urlbar-border-color);
   border-radius: 2px;
   background-clip: padding-box;
   margin: 0 3px;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -521,32 +521,27 @@ toolbarpaletteitem[place="palette"] > #p
 }
 
 /* ::::: nav-bar-inner ::::: */
 
 %include ../shared/urlbar-searchbar.inc.css
 
 %ifdef MOZ_PHOTON_THEME
 
-#main-window {
-  --urlbar-border-color: hsla(240, 5%, 5%, .25);
-  --urlbar-border-color-hover: hsla(240, 5%, 5%, .35);
+#urlbar,
+.searchbar-textbox {
+  font-size: 1.25em;
 }
 
 #urlbar[focused="true"],
 .searchbar-textbox[focused="true"] {
   border-color: -moz-mac-focusring;
   box-shadow: var(--focus-ring-box-shadow);
 }
 
-#urlbar,
-.searchbar-textbox {
-  font-size: 1.25em;
-}
-
 %else
 
 #urlbar,
 .searchbar-textbox {
   font: icon;
   -moz-appearance: none;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2),
               inset 0 0 1px hsla(0,0%,0%,.05),
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1291,16 +1291,42 @@ photonpanelmultiview .subviewbutton[chec
   -moz-context-properties: fill;
 }
 
 photonpanelmultiview .subviewbutton > .menu-iconic-left {
   -moz-appearance: none;
   margin-inline-end: 0;
 }
 
+photonpanelmultiview .toolbaritem-combined-buttons:-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem="true"]) {
+  -moz-box-align: center;
+  -moz-box-orient: horizontal;
+  border: 0;
+  margin-inline-end: 8px;
+}
+
+photonpanelmultiview .toolbaritem-combined-buttons > label {
+  -moz-box-flex: 1;
+  font: menu;
+  margin: 0;
+  padding-inline-start: 36px; /* 12px toolbarbutton padding + 16px icon + 8px label padding start */
+}
+
+photonpanelmultiview .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton {
+  -moz-box-flex: 0;
+  height: auto;
+  margin-inline-start: 18px;
+  min-width: auto;
+  padding: 4px;
+}
+
+photonpanelmultiview .PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton > .toolbarbutton-text {
+  display: none;
+}
+
 /* END photon adjustments */
 
 panelview .toolbarbutton-1,
 .widget-overflow-list > .toolbarbutton-1:not(:first-child),
 .widget-overflow-list > toolbaritem:not(:first-child) {
   margin-top: 6px;
 }
 
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -93,17 +93,19 @@
   skin/classic/browser/fxa/android.png                         (../shared/fxa/android.png)
   skin/classic/browser/fxa/android@2x.png                      (../shared/fxa/android@2x.png)
   skin/classic/browser/fxa/ios.png                             (../shared/fxa/ios.png)
   skin/classic/browser/fxa/ios@2x.png                          (../shared/fxa/ios@2x.png)
 
 
   skin/classic/browser/addons.svg                     (../shared/icons/addons.svg)
   skin/classic/browser/back.svg                       (../shared/icons/back.svg)
+#ifndef MOZ_PHOTON_THEME
   skin/classic/browser/back-large.svg                 (../shared/icons/back-large.svg)
+#endif
   skin/classic/browser/bookmark.svg                   (../shared/icons/bookmark.svg)
   skin/classic/browser/bookmark-hollow.svg            (../shared/icons/bookmark-hollow.svg)
   skin/classic/browser/bookmarksMenu.svg              (../shared/icons/bookmarksMenu.svg)
   skin/classic/browser/characterEncoding.svg          (../shared/icons/characterEncoding.svg)
   skin/classic/browser/chevron.svg                    (../shared/icons/chevron.svg)
   skin/classic/browser/containers.svg                       (../shared/icons/containers.svg)
   skin/classic/browser/developer.svg                  (../shared/icons/developer.svg)
   skin/classic/browser/download.svg                   (../shared/icons/download.svg)
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -221,8 +221,23 @@ toolbarpaletteitem[place="palette"] > #z
   list-style-image: url(chrome://browser/skin/menu-icons/find.svg);
   -moz-context-properties: fill;
 }
 
 #appMenu-help-button {
   list-style-image: url(chrome://browser/skin/menu-icons/help.svg);
   -moz-context-properties: fill;
 }
+
+#appMenu-cut-button {
+  list-style-image: url(chrome://browser/skin/edit-cut.svg);
+  -moz-context-properties: fill;
+}
+
+#appMenu-copy-button {
+  list-style-image: url(chrome://browser/skin/edit-copy.svg);
+  -moz-context-properties: fill;
+}
+
+#appMenu-paste-button {
+  list-style-image: url(chrome://browser/skin/edit-paste.svg);
+  -moz-context-properties: fill;
+}
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -19,17 +19,21 @@ toolbar[brighttext] :-moz-any(@primaryTo
 #reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 %endif
 #nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 #back-button {
+%ifdef MOZ_PHOTON_THEME
+  list-style-image: url("chrome://browser/skin/back.svg");
+%else
   list-style-image: url("chrome://browser/skin/back-large.svg");
+%endif
 }
 
 #forward-button {
   list-style-image: url("chrome://browser/skin/forward.svg");
 }
 
 %ifdef MOZ_PHOTON_THEME
 #reload-button {
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -3,43 +3,43 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %ifdef MOZ_PHOTON_THEME
 
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
   background-clip: content-box;
-  border: 1px solid var(--urlbar-border-color);
+  border: 1px solid hsla(240,5%,5%,.25);
   border-radius: var(--toolbarbutton-border-radius);
-  box-shadow: 0 1px 4px hsla(0, 0%, 0%, .05);
+  box-shadow: 0 1px 4px rgba(0,0,0,.05);
   padding: 0;
   margin: 0 5px;
   min-height: 30px;
 }
 
+#urlbar:hover,
+.searchbar-textbox:hover {
+  border-color: hsla(240,5%,5%,.35);
+  box-shadow: 0 1px 6px rgba(0,0,0,.1), 0 0 1px rgba(0,0,0,.1);
+}
+
 #urlbar:-moz-lwtheme,
 .searchbar-textbox:-moz-lwtheme {
   background-color: hsla(0,0%,100%,.8);
   color: black;
 }
 
 #urlbar:-moz-lwtheme:hover,
 #urlbar:-moz-lwtheme[focused="true"],
 .searchbar-textbox:-moz-lwtheme:hover,
 .searchbar-textbox:-moz-lwtheme[focused="true"] {
   background-color: white;
 }
 
-#urlbar:hover,
-.searchbar-textbox:hover {
-  border: 1px solid var(--urlbar-border-color-hover);
-  box-shadow: 0 1px 6px hsla(0, 0%, 0%, .1), 0 0 1px 0 rgba(0, 0, 0, .1);
-}
-
 %endif
 
 #urlbar-container {
   -moz-box-align: center;
 }
 
 #urlbar-search-splitter {
   /* The splitter width should equal the location and search bars' combined
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -647,48 +647,48 @@ toolbar[brighttext] #close-button {
   #restore-button:-moz-locale-dir(rtl),
   #close-button:-moz-locale-dir(rtl) {
     transform: scaleX(-1);
   }
 }
 
 /* ::::: Location Bar ::::: */
 
+%include ../shared/urlbar-searchbar.inc.css
+
+%ifdef MOZ_PHOTON_THEME
+
+#urlbar,
+.searchbar-textbox {
+  font-size: 1.15em;
+}
+
+@media (-moz-windows-default-theme: 0) {
+  #urlbar:not(:-moz-lwtheme):not([focused="true"]),
+  .searchbar-textbox:not(:-moz-lwtheme):not([focused="true"]) {
+    border-color: ThreeDShadow;
+  }
+}
+
+#urlbar[focused="true"],
+.searchbar-textbox[focused="true"] {
+  border-color: Highlight;
+}
+
+%else
+
 #main-window {
   --urlbar-border-color: ThreeDShadow;
   --urlbar-border-color-hover: var(--urlbar-border-color);
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --urlbar-border-color: var(--toolbarbutton-hover-bordercolor);
 }
 
-%include ../shared/urlbar-searchbar.inc.css
-
-%ifdef MOZ_PHOTON_THEME
-
-@media (-moz-windows-default-theme) {
-  #main-window:not(:-moz-lwtheme) {
-    --urlbar-border-color: hsla(240, 5%, 5%, .25);
-    --urlbar-border-color-hover: hsla(240, 5%, 5%, .35);
-  }
-}
-
-#urlbar,
-.searchbar-textbox {
-  font-size: 1.15em;
-}
-
-#urlbar[focused="true"],
-.searchbar-textbox[focused="true"] {
-  border-color: Highlight;
-}
-
-%else
-
 @media (-moz-windows-default-theme) {
   @media (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     #main-window:not(:-moz-lwtheme) {
       --urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
       --urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
     }
   }
--- a/chrome/nsChromeProtocolHandler.cpp
+++ b/chrome/nsChromeProtocolHandler.cpp
@@ -100,16 +100,18 @@ nsChromeProtocolHandler::NewURI(const ns
 NS_IMETHODIMP
 nsChromeProtocolHandler::NewChannel2(nsIURI* aURI,
                                      nsILoadInfo* aLoadInfo,
                                      nsIChannel** aResult)
 {
     nsresult rv;
 
     NS_ENSURE_ARG_POINTER(aURI);
+    NS_ENSURE_ARG_POINTER(aLoadInfo);
+
     NS_PRECONDITION(aResult, "Null out param");
 
 #ifdef DEBUG
     // Check that the uri we got is already canonified
     nsresult debug_rv;
     nsCOMPtr<nsIURI> debugClone;
     debug_rv = aURI->Clone(getter_AddRefs(debugClone));
     if (NS_SUCCEEDED(debug_rv)) {
@@ -140,16 +142,22 @@ nsChromeProtocolHandler::NewChannel2(nsI
     if (NS_FAILED(rv)) {
 #ifdef DEBUG
         printf("Couldn't convert chrome URL: %s\n",
                aURI->GetSpecOrDefault().get());
 #endif
         return rv;
     }
 
+    // We don't want to allow the inner protocol handler modify the result principal URI
+    // since we want either |aURI| or anything pre-set by upper layers to prevail.
+    nsCOMPtr<nsIURI> savedResultPrincipalURI;
+    rv = aLoadInfo->GetResultPrincipalURI(getter_AddRefs(savedResultPrincipalURI));
+    NS_ENSURE_SUCCESS(rv, rv);
+
     rv = NS_NewChannelInternal(getter_AddRefs(result),
                                resolvedURI,
                                aLoadInfo);
     NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef DEBUG
     nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(result));
     if (fileChan) {
@@ -163,19 +171,18 @@ nsChromeProtocolHandler::NewChannel2(nsI
             file->GetNativePath(path);
             printf("Chrome file doesn't exist: %s\n", path.get());
         }
     }
 #endif
 
     // Make sure that the channel remembers where it was
     // originally loaded from.
-    nsLoadFlags loadFlags = 0;
-    result->GetLoadFlags(&loadFlags);
-    result->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
+    rv = aLoadInfo->SetResultPrincipalURI(savedResultPrincipalURI);
+    NS_ENSURE_SUCCESS(rv, rv);
     rv = result->SetOriginalURI(aURI);
     if (NS_FAILED(rv)) return rv;
 
     // Get a system principal for content files and set the owner
     // property of the result
     nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
     nsAutoCString path;
     rv = url->GetPath(path);
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const { createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
-const { debugAddon } = require("../../modules/addon");
+const { debugAddon, uninstallAddon } = require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 
@@ -83,33 +83,36 @@ module.exports = createClass({
     }).isRequired
   },
 
   debug() {
     let { target } = this.props;
     debugAddon(target.addonID);
   },
 
+  uninstall() {
+    let { target } = this.props;
+    uninstallAddon(target.addonID);
+  },
+
   reload() {
     let { client, target } = this.props;
     // This function sometimes returns a partial promise that only
     // implements then().
     client.request({
       to: target.addonActor,
       type: "reload"
     }).then(() => {}, error => {
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
-    // Only temporarily installed add-ons can be reloaded.
-    const canBeReloaded = target.temporarilyInstalled;
 
     return dom.li(
       { className: "addon-target-container", "data-addon-id": target.addonID },
       dom.div({ className: "target" },
         dom.img({
           className: "target-icon",
           role: "presentation",
           src: target.icon
@@ -122,19 +125,24 @@ module.exports = createClass({
         ...internalIDForTarget(target),
       ),
       dom.div({className: "addon-target-actions"},
         dom.button({
           className: "debug-button addon-target-button",
           onClick: this.debug,
           disabled: debugDisabled,
         }, Strings.GetStringFromName("debug")),
-        dom.button({
-          className: "reload-button addon-target-button",
-          onClick: this.reload,
-          disabled: !canBeReloaded,
-          title: !canBeReloaded ?
-            Strings.GetStringFromName("reloadDisabledTooltip") : ""
-        }, Strings.GetStringFromName("reload"))
+        target.temporarilyInstalled
+          ? dom.button({
+            className: "reload-button addon-target-button",
+            onClick: this.reload,
+          }, Strings.GetStringFromName("reload"))
+          : null,
+        target.temporarilyInstalled
+          ? dom.button({
+            className: "uninstall-button addon-target-button",
+            onClick: this.uninstall,
+          }, Strings.GetStringFromName("remove"))
+          : null,
       ),
     );
   }
 });
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -1,23 +1,29 @@
 /* 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/. */
 
 "use strict";
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
+loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 let toolbox = null;
 
 exports.debugAddon = function (addonID) {
   if (toolbox) {
     toolbox.close();
   }
 
   toolbox = BrowserToolboxProcess.init({
     addonID,
     onClose: () => {
       toolbox = null;
     }
   });
 };
+
+exports.uninstallAddon = async function (addonID) {
+  let addon = await AddonManager.getAddonByID(addonID);
+  return addon && addon.uninstall();
+};
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -27,16 +27,17 @@ tags = webextensions
 tags = webextensions
 [browser_addons_debug_webextension_nobg.js]
 tags = webextensions
 [browser_addons_debug_webextension_popup.js]
 tags = webextensions
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
 [browser_addons_reload.js]
+[browser_addons_remove.js]
 [browser_addons_toggle_debug.js]
 [browser_page_not_found.js]
 [browser_service_workers.js]
 [browser_service_workers_fetch_flag.js]
 [browser_service_workers_multi_content_process.js]
 skip-if = !e10s # This test is only valid in e10s
 [browser_service_workers_not_compatible.js]
 [browser_service_workers_push.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -1,74 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
-/**
- * Returns a promise that resolves when the given add-on event is fired. The
- * resolved value is an array of arguments passed for the event.
- */
-function promiseAddonEvent(event) {
-  return new Promise(resolve => {
-    let listener = {
-      [event]: function (...args) {
-        AddonManager.removeAddonListener(listener);
-        resolve(args);
-      }
-    };
-
-    AddonManager.addAddonListener(listener);
-  });
-}
-
-function* tearDownAddon(addon) {
-  const onUninstalled = promiseAddonEvent("onUninstalled");
-  addon.uninstall();
-  const [uninstalledAddon] = yield onUninstalled;
-  is(uninstalledAddon.id, addon.id,
-     `Add-on was uninstalled: ${uninstalledAddon.id}`);
-}
-
 function getReloadButton(document, addonName) {
   const names = getInstalledAddonNames(document);
   const name = names.filter(element => element.textContent === addonName)[0];
   ok(name, `Found ${addonName} add-on in the list`);
   const targetElement = name.parentNode.parentNode;
   const reloadButton = targetElement.querySelector(".reload-button");
   info(`Found reload button for ${addonName}`);
   return reloadButton;
 }
 
-function installAddonWithManager(filePath) {
-  return new Promise((resolve, reject) => {
-    AddonManager.getInstallForFile(filePath, install => {
-      if (!install) {
-        throw new Error(`An install was not created for ${filePath}`);
-      }
-      install.addListener({
-        onDownloadFailed: reject,
-        onDownloadCancelled: reject,
-        onInstallFailed: reject,
-        onInstallCancelled: reject,
-        onInstallEnded: resolve
-      });
-      install.install();
-    });
-  });
-}
-
-function getAddonByID(addonId) {
-  return new Promise(resolve => {
-    AddonManager.getAddonByID(addonId, addon => resolve(addon));
-  });
-}
-
 /**
  * Creates a web extension from scratch in a temporary location.
  * The object must be removed when you're finished working with it.
  */
 class TempWebExt {
   constructor(addonId) {
     this.addonId = addonId;
     this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
@@ -111,17 +62,16 @@ add_task(function* reloadButtonReloadsAd
   yield waitForInitialAddonList(document);
   yield installAddon({
     document,
     path: "addons/unpacked/install.rdf",
     name: ADDON_NAME,
   });
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
-  is(reloadButton.disabled, false, "Reload button should not be disabled");
   is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, ADDON_NAME);
       info("Add-on was re-installed: " + ADDON_NAME);
       done();
@@ -193,15 +143,13 @@ add_task(function* onlyTempInstalledAddo
   yield waitForInitialAddonList(document);
   const onAddonListUpdated = waitForMutation(getAddonList(document),
                                              { childList: true });
   yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
   yield onAddonListUpdated;
   const addon = yield getAddonByID("bug1273184@tests");
 
   const reloadButton = getReloadButton(document, addon.name);
-  ok(reloadButton, "Reload button exists");
-  is(reloadButton.disabled, true, "Reload button should be disabled");
-  ok(reloadButton.title, "Disabled reload button should have a tooltip");
+  ok(!reloadButton, "There should not be a reload button");
 
   yield tearDownAddon(addon);
   yield closeAboutDebugging(tab);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_remove.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function getTargetEl(document, id) {
+  return document.querySelector(`[data-addon-id="${id}"]`);
+}
+
+function getRemoveButton(document, id) {
+  return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
+}
+
+add_task(function* removeLegacyExtension() {
+  const addonID = "test-devtools@mozilla.org";
+  const addonName = "test-devtools";
+
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  // Install this add-on, and verify that it appears in the about:debugging UI
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: addonName,
+  });
+
+  ok(getTargetEl(document, addonID), "add-on is shown");
+
+  // Click the remove button and wait for the DOM to change.
+  const addonListMutation = waitForMutation(
+    getTemporaryAddonList(document),
+    { childList: true });
+  getRemoveButton(document, addonID).click();
+  yield addonListMutation;
+
+  ok(!getTargetEl(document, addonID), "add-on is not shown");
+
+  yield closeAboutDebugging(tab);
+});
+
+add_task(function* removeWebextension() {
+  const addonID = "test-devtools-webextension@mozilla.org";
+  const addonName = "test-devtools-webextension";
+
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  // Install this add-on, and verify that it appears in the about:debugging UI
+  yield installAddon({
+    document,
+    path: "addons/test-devtools-webextension/manifest.json",
+    name: addonName,
+    isWebExtension: true,
+  });
+
+  ok(getTargetEl(document, addonID), "add-on is shown");
+
+  // Click the remove button and wait for the DOM to change.
+  const addonListMutation = waitForMutation(
+    getTemporaryAddonList(document),
+    { childList: true });
+  getRemoveButton(document, addonID).click();
+  yield addonListMutation;
+
+  ok(!getTargetEl(document, addonID), "add-on is not shown");
+
+  yield closeAboutDebugging(tab);
+});
+
+add_task(function* onlyTempInstalledAddonsCanBeRemoved() {
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+  const onAddonListUpdated = waitForMutation(getAddonList(document),
+                                             { childList: true });
+  yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
+  yield onAddonListUpdated;
+  const addon = yield getAddonByID("bug1273184@tests");
+
+  const removeButton = getRemoveButton(document, addon.id);
+  ok(!removeButton, "remove button is not shown");
+
+  yield tearDownAddon(addon);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -3,17 +3,18 @@
 
 /* eslint-env browser */
 /* exported openAboutDebugging, changeAboutDebuggingHash, closeAboutDebugging,
    installAddon, uninstallAddon, waitForMutation, waitForContentMutation, assertHasTarget,
    getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
    waitForServiceWorkerRegistered, unregisterServiceWorker,
    waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension,
    waitForServiceWorkerActivation, enableServiceWorkerDebugging,
-   getServiceWorkerContainer */
+   getServiceWorkerContainer, promiseAddonEvent, installAddonWithManager, getAddonByID,
+   tearDownAddon */
 /* import-globals-from ../../framework/test/shared-head.js */
 
 "use strict";
 
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -431,8 +432,63 @@ function enableServiceWorkerDebugging() 
       ["dom.serviceWorkers.testing.enabled", true],
       // Force single content process.
       ["dom.ipc.processCount", 1],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
     Services.ppmm.releaseCachedProcesses();
   });
 }
+
+/**
+ * Returns a promise that resolves when the given add-on event is fired. The
+ * resolved value is an array of arguments passed for the event.
+ */
+function promiseAddonEvent(event) {
+  return new Promise(resolve => {
+    let listener = {
+      [event]: function (...args) {
+        AddonManager.removeAddonListener(listener);
+        resolve(args);
+      }
+    };
+
+    AddonManager.addAddonListener(listener);
+  });
+}
+
+/**
+ * Install an add-on using the AddonManager so it does not show up as temporary.
+ */
+function installAddonWithManager(filePath) {
+  return new Promise((resolve, reject) => {
+    AddonManager.getInstallForFile(filePath, install => {
+      if (!install) {
+        throw new Error(`An install was not created for ${filePath}`);
+      }
+      install.addListener({
+        onDownloadFailed: reject,
+        onDownloadCancelled: reject,
+        onInstallFailed: reject,
+        onInstallCancelled: reject,
+        onInstallEnded: resolve
+      });
+      install.install();
+    });
+  });
+}
+
+function getAddonByID(addonId) {
+  return new Promise(resolve => {
+    AddonManager.getAddonByID(addonId, addon => resolve(addon));
+  });
+}
+
+/**
+ * Uninstall an add-on.
+ */
+function* tearDownAddon(addon) {
+  const onUninstalled = promiseAddonEvent("onUninstalled");
+  addon.uninstall();
+  const [uninstalledAddon] = yield onUninstalled;
+  is(uninstalledAddon.id, addon.id,
+     `Add-on was uninstalled: ${uninstalledAddon.id}`);
+}
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -73,17 +73,16 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
-  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-jump.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
@@ -177,18 +176,16 @@ skip-if = e10s || true # bug 1113935
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
 [browser_dbg_break-on-dom-07.js]
 [browser_dbg_break-on-dom-08.js]
 [browser_dbg_break-on-dom-event-01.js]
 skip-if = e10s || os == "mac" || e10s # Bug 895426
 [browser_dbg_break-on-dom-event-02.js]
 skip-if = e10s # TODO
-[browser_dbg_break-on-dom-event-03.js]
-skip-if = e10s # TODO
 [browser_dbg_break-unselected.js]
 [browser_dbg_breakpoints-actual-location.js]
 [browser_dbg_breakpoints-actual-location2.js]
 [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_breakpoints-button-01.js]
 [browser_dbg_breakpoints-button-02.js]
 [browser_dbg_breakpoints-condition-thrown-message.js]
@@ -264,18 +261,16 @@ skip-if = e10s && debug
 [browser_dbg_editor-mode.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-01.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-02.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-03.js]
 skip-if = e10s && debug
-[browser_dbg_event-listeners-04.js]
-skip-if = debug || e10s # debug bug 1142597, e10s bug 1146603.
 [browser_dbg_file-reload.js]
 skip-if = e10s && debug
 [browser_dbg_function-display-name.js]
 skip-if = e10s && debug
 [browser_dbg_global-method-override.js]
 skip-if = e10s && debug
 [browser_dbg_globalactor.js]
 skip-if = e10s # TODO
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -73,17 +73,16 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
-  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-jump.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests that the break-on-dom-events request works for load event listeners.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
-
-var gClient, gThreadClient;
-
-function test() {
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
-  }
-
-  let transport = DebuggerServer.connectPipe();
-  gClient = new DebuggerClient(transport);
-  gClient.connect().then(([aType, aTraits]) => {
-    is(aType, "browser",
-      "Root actor should identify itself as a browser.");
-
-    addTab(TAB_URL)
-      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
-      .then(aThreadClient => gThreadClient = aThreadClient)
-      .then(pauseDebuggee)
-      .then(testBreakOnLoad)
-      .then(() => gClient.close())
-      .then(finish)
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-  });
-}
-
-function pauseDebuggee() {
-  let deferred = promise.defer();
-
-  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
-    is(aPacket.type, "paused",
-      "We should now be paused.");
-    is(aPacket.why.type, "debuggerStatement",
-      "The debugger statement was hit.");
-
-    gThreadClient.resume(deferred.resolve);
-  });
-
-  // Spin the event loop before causing the debuggee to pause, to allow
-  // this function to return first.
-  executeSoon(() => triggerButtonClick());
-
-  return deferred.promise;
-}
-
-// Test pause on a load event.
-function testBreakOnLoad() {
-  let deferred = promise.defer();
-
-  // Test calling pauseOnDOMEvents from a running state.
-  gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
-    is(aPacket.error, undefined,
-      "The pause-on-load request completed successfully.");
-    let handlers = ["loadHandler"];
-
-    gClient.addListener("paused", function tester(aEvent, aPacket) {
-      is(aPacket.why.type, "pauseOnDOMEvents",
-        "A hidden breakpoint was hit.");
-
-      is(aPacket.frame.where.line, 15, "Found the load event listener.");
-      gClient.removeListener("paused", tester);
-      deferred.resolve();
-
-      gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
-    });
-
-    getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
-      gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
-        aTabClient.reload();
-      });
-    });
-  });
-
-  return deferred.promise;
-}
-
-function triggerButtonClick() {
-  let button = content.document.querySelector("button");
-  EventUtils.sendMouseEvent({ type: "click" }, button);
-}
-
-registerCleanupFunction(function () {
-  gClient = null;
-  gThreadClient = null;
-});
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that event listeners are properly fetched even if one of the listeners
- * don't have a Debugger.Source object (bug 942899).
- *
- * This test is skipped on debug and e10s builds for following reasons:
- *  - debug: requiring sdk/tabs causes memory leaks when new windows are opened
- *    in tests executed after this one. Bug 1142597.
- *  - e10s: tab.attach is not e10s safe and only works when add-on compatibility
- *    shims are in place. Bug 1146603.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
-
-function test() {
-  Task.spawn(function* () {
-    let tab = yield addTab(TAB_URL);
-
-    // Create a sandboxed content script the Add-on SDK way. Inspired by bug
-    // 1145996.
-    let tabs = require("sdk/tabs");
-    let sdkTab = [...tabs].find(tab => tab.url === TAB_URL);
-    ok(sdkTab, "Add-on SDK found the loaded tab.");
-
-    info("Attaching an event handler via add-on sdk content scripts.");
-    let worker = sdkTab.attach({
-      contentScript: "document.body.addEventListener('click', e => alert(e))",
-      onError: ok.bind(this, false)
-    });
-
-    let options = {
-      source: TAB_URL,
-      line: 1
-    };
-    let [,, panel, win] = yield initDebugger(tab, options);
-    let dbg = panel.panelWin;
-    let controller = dbg.DebuggerController;
-    let constants = dbg.require("./content/constants");
-    let actions = dbg.require("./content/actions/event-listeners");
-    let fetched = waitForDispatch(panel, constants.FETCH_EVENT_LISTENERS);
-
-    info("Scheduling event listener fetch.");
-    controller.dispatch(actions.fetchEventListeners());
-
-    info("Waiting for updated event listeners to arrive.");
-    yield fetched;
-
-    ok(true, "The listener update did not hang.");
-    closeDebuggerAndFinish(panel);
-  });
-}
deleted file mode 100644
--- a/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8"/>
-    <title>Debugger test page</title>
-  </head>
-
-  <body>
-    <button>Click me!</button>
-
-    <script type="text/javascript">
-      window.addEventListener("load", function onload() {
-        var button = document.querySelector("button");
-        button.onclick = function () {
-          debugger;
-        };
-      });
-    </script>
-  </body>
-
-</html>
--- a/devtools/client/framework/about-devtools-toolbox.js
+++ b/devtools/client/framework/about-devtools-toolbox.js
@@ -21,16 +21,17 @@ AboutURL.prototype = {
   classID: components.ID("11342911-3135-45a8-8d71-737a2b0ad469"),
   contractID: "@mozilla.org/network/protocol/about;1?what=devtools-toolbox",
 
   QueryInterface: XPCOMUtils.generateQI([nsIAboutModule]),
 
   newChannel: function (aURI, aLoadInfo) {
     let chan = Services.io.newChannelFromURIWithLoadInfo(this.uri, aLoadInfo);
     chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
+    chan.originalURI = aURI;
     return chan;
   },
 
   getURIFlags: function (aURI) {
     return nsIAboutModule.ALLOW_SCRIPT || nsIAboutModule.ENABLE_INDEXED_DB;
   }
 };
 
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -92,20 +92,19 @@ webExtTip.learnMore = Learn more
 # This string is displayed as the title of the file picker that appears when
 # the user clicks the 'Load Temporary Add-on' button
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
 
 # LOCALIZATION NOTE (reload):
 # This string is displayed as a label of the button that reloads a given addon.
 reload = Reload
 
-# LOCALIZATION NOTE (reloadDisabledTooltip):
-# This string is displayed in a tooltip that appears when hovering over a
-# disabled 'reload' button.
-reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
+# LOCALIZATION NOTE (remove):
+# This string is displayed as a label of the button that will remove a given addon.
+remove = Remove
 
 # LOCALIZATION NOTE (location):
 # This string is displayed as a label for the filesystem location of an extension.
 location = Location
 
 # LOCALIZATION NOTE (workers):
 # This string is displayed as a header of the about:debugging#workers page.
 workers = Workers
--- a/devtools/client/netmonitor/index.html
+++ b/devtools/client/netmonitor/index.html
@@ -40,17 +40,18 @@
           this.mount = document.querySelector("#mount");
           const connection = {
             tabConnection: {
               tabTarget: toolbox.target,
             },
             toolbox,
           };
           const App = createFactory(require("./src/components/app"));
-          render(Provider({ store }, App()), this.mount);
+          const sourceMapService = toolbox.sourceMapURLService;
+          render(Provider({ store }, App({ sourceMapService })), this.mount);
           return onFirefoxConnect(connection, actions, store.getState);
         },
 
         destroy() {
           unmountComponentAtNode(this.mount);
           return onDisconnect();
         }
       };
--- a/devtools/client/netmonitor/src/components/app.js
+++ b/devtools/client/netmonitor/src/components/app.js
@@ -16,25 +16,27 @@ const MonitorPanel = createFactory(requi
 const StatisticsPanel = createFactory(require("./statistics-panel"));
 
 const { div } = DOM;
 
 /*
  * App component
  * The top level component for representing main panel
  */
-function App({ statisticsOpen }) {
+function App({ statisticsOpen, sourceMapService }) {
   return (
     div({ className: "network-monitor" },
-      !statisticsOpen ? MonitorPanel() : StatisticsPanel()
+      !statisticsOpen ? MonitorPanel({sourceMapService}) : StatisticsPanel()
     )
   );
 }
 
 App.displayName = "App";
 
 App.propTypes = {
   statisticsOpen: PropTypes.bool.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
 )(App);
--- a/devtools/client/netmonitor/src/components/monitor-panel.js
+++ b/devtools/client/netmonitor/src/components/monitor-panel.js
@@ -33,16 +33,18 @@ const MediaQueryList = window.matchMedia
 const MonitorPanel = createClass({
   displayName: "MonitorPanel",
 
   propTypes: {
     isEmpty: PropTypes.bool.isRequired,
     networkDetailsOpen: PropTypes.bool.isRequired,
     openNetworkDetails: PropTypes.func.isRequired,
     request: PropTypes.object,
+    // Service to enable the source map feature.
+    sourceMapService: PropTypes.object,
     updateRequest: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     return {
       isVerticalSpliter: MediaQueryList.matches,
     };
   },
@@ -97,33 +99,36 @@ const MonitorPanel = createClass({
 
   onLayoutChange() {
     this.setState({
       isVerticalSpliter: MediaQueryList.matches,
     });
   },
 
   render() {
-    let { isEmpty, networkDetailsOpen } = this.props;
+    let { isEmpty, networkDetailsOpen, sourceMapService } = this.props;
     let initialWidth = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-height");
     return (
       div({ className: "monitor-panel" },
         Toolbar(),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${initialWidth}px`,
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty }),
-          endPanel: networkDetailsOpen && NetworkDetailsPanel({ ref: "endPanel" }),
+          endPanel: networkDetailsOpen && NetworkDetailsPanel({
+            ref: "endPanel",
+            sourceMapService,
+          }),
           endPanelCollapsed: !networkDetailsOpen,
           endPanelControl: true,
           vert: this.state.isVerticalSpliter,
         }),
       )
     );
   }
 });
--- a/devtools/client/netmonitor/src/components/network-details-panel.js
+++ b/devtools/client/netmonitor/src/components/network-details-panel.js
@@ -22,28 +22,30 @@ const { div } = DOM;
 /*
  * Network details panel component
  */
 function NetworkDetailsPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
+  sourceMapService,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     div({ className: "network-details-panel" },
       !request.isCustom ?
         TabboxPanel({
           activeTabId,
           request,
           selectTab,
+          sourceMapService,
         }) :
         CustomRequestPanel({
           cloneSelectedRequest,
           request,
         })
     )
   );
 }
@@ -51,16 +53,18 @@ function NetworkDetailsPanel({
 NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
 
 NetworkDetailsPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   open: PropTypes.bool,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/src/components/stack-trace-panel.js
+++ b/devtools/client/netmonitor/src/components/stack-trace-panel.js
@@ -11,28 +11,31 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const { viewSourceInDebugger } = require("../connector/index");
 
 const { div } = DOM;
 
 // Components
 const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
 
-function StackTracePanel({ request }) {
+function StackTracePanel({ request, sourceMapService }) {
   let { stacktrace } = request.cause;
 
   return (
     div({ className: "panel-container" },
       StackTrace({
         stacktrace,
         onViewSourceInDebugger: ({ url, line }) => viewSourceInDebugger(url, line),
+        sourceMapService,
       }),
     )
   );
 }
 
 StackTracePanel.displayName = "StackTracePanel";
 
 StackTracePanel.propTypes = {
   request: PropTypes.object.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = StackTracePanel;
--- a/devtools/client/netmonitor/src/components/tabbox-panel.js
+++ b/devtools/client/netmonitor/src/components/tabbox-panel.js
@@ -36,16 +36,17 @@ const TIMINGS_TITLE = L10N.getStr("netmo
  * Tabbox panel component
  * Display the network request details
  */
 function TabboxPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
+  sourceMapService,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
       activeTabId,
@@ -83,17 +84,17 @@ function TabboxPanel({
       },
         TimingsPanel({ request }),
       ),
       request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
       TabPanel({
         id: "stack-trace",
         title: STACK_TRACE_TITLE,
       },
-        StackTracePanel({ request }),
+        StackTracePanel({ request, sourceMapService }),
       ),
       request.securityState && request.securityState !== "insecure" &&
       TabPanel({
         id: "security",
         title: SECURITY_TITLE,
       },
         SecurityPanel({ request }),
       ),
@@ -103,16 +104,18 @@ function TabboxPanel({
 
 TabboxPanel.displayName = "TabboxPanel";
 
 TabboxPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -138,17 +138,17 @@ function getUrlBaseName(url) {
 
 /**
  * Helpers for getting the query portion of a url.
  *
  * @param {string} url - url string
  * @return {string} unicode query of a url
  */
 function getUrlQuery(url) {
-  return decodeUnicodeUrl((new URL(url)).search.replace(/^\?/, ""));
+  return (new URL(url)).search.replace(/^\?/, "");
 }
 
 /**
  * Helpers for getting unicode name and query portions of a url.
  *
  * @param {string} url - url string
  * @return {string} unicode basename and query portions of a url
  */
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   html_infinite-get-page.html
   html_json-b64.html
   html_json-basic.html
   html_json-custom-mime-test-page.html
   html_json-long-test-page.html
   html_json-malformed-test-page.html
   html_json-text-mime-test-page.html
   html_jsonp-test-page.html
+  html_maps-test-page.html
   html_navigate-test-page.html
   html_params-test-page.html
   html_post-data-test-page.html
   html_post-json-test-page.html
   html_post-raw-test-page.html
   html_post-raw-with-headers-test-page.html
   html_simple-test-page.html
   html_single-get-page.html
@@ -45,24 +46,28 @@ support-files =
   sjs_simple-test-server.sjs
   sjs_sorting-test-server.sjs
   sjs_status-codes-test-server.sjs
   sjs_truncate-test-server.sjs
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   !/devtools/client/framework/test/shared-head.js
+  xhr_bundle.js
+  xhr_bundle.js.map
+  xhr_original.js
 
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
 [browser_net_api-calls.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
 [browser_net_cause.js]
 [browser_net_cause_redirect.js]
+[browser_net_cause_source_map.js]
 [browser_net_service-worker-status.js]
 [browser_net_charts-01.js]
 [browser_net_charts-02.js]
 [browser_net_charts-03.js]
 [browser_net_charts-04.js]
 [browser_net_charts-05.js]
 [browser_net_charts-06.js]
 [browser_net_charts-07.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause_source_map.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if request cause is reported correctly when using source maps.
+ */
+
+const CAUSE_FILE_NAME = "html_maps-test-page.html";
+const CAUSE_URL = EXAMPLE_URL + CAUSE_FILE_NAME;
+
+const N_EXPECTED_REQUESTS = 4;
+
+add_task(function* () {
+  // the initNetMonitor function clears the network request list after the
+  // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
+  // and only then load the real thing from CAUSE_URL - we want to catch
+  // all the requests the page is making, not only the XHRs.
+  // We can't use about:blank here, because initNetMonitor checks that the
+  // page has actually made at least one request.
+  let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+
+  let { document, store, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+  let waitPromise = waitForNetworkEvents(monitor, N_EXPECTED_REQUESTS);
+  tab.linkedBrowser.loadURI(CAUSE_URL);
+  yield waitPromise;
+
+  info("Clicking item and waiting for details panel to open");
+  waitPromise = waitForDOM(document, ".network-details-panel");
+  let xhrRequestItem = document.querySelectorAll(".request-list-item")[3];
+  EventUtils.sendMouseEvent({ type: "mousedown" }, xhrRequestItem);
+  yield waitPromise;
+
+  info("Clicking stack tab and waiting for stack panel to open");
+  waitPromise = waitForDOM(document, "#stack-trace-panel");
+  let stackTab = document.querySelector("#stack-trace-tab");
+  EventUtils.sendMouseEvent({ type: "click" }, stackTab);
+  yield waitPromise;
+
+  info("Waiting for source maps to be applied");
+  yield waitUntil(() => {
+    let frames = document.querySelectorAll(".frame-link");
+    return frames && frames.length >= 2 &&
+      frames[0].textContent.includes("xhr_original") &&
+      frames[1].textContent.includes("xhr_original");
+  });
+
+  let frames = document.querySelectorAll(".frame-link");
+  is(frames.length, 3, "should have 3 stack frames");
+  is(frames[0].textContent, `reallydoxhr xhr_original.js:6`);
+  is(frames[1].textContent, `doxhr xhr_original.js:10`);
+
+  yield teardown(monitor);
+});
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -411,17 +411,18 @@ function verifyRequestItemTarget(documen
   if (fuzzyUrl) {
     ok(target.querySelector(".requests-list-file").textContent.startsWith(
       name + (query ? "?" + query : "")), "The displayed file is correct.");
     ok(target.querySelector(".requests-list-file").getAttribute("title")
                                                   .startsWith(unicodeUrl),
       "The tooltip file is correct.");
   } else {
     is(target.querySelector(".requests-list-file").textContent,
-      name + (query ? "?" + query : ""), "The displayed file is correct.");
+      decodeURIComponent(name + (query ? "?" + query : "")),
+      "The displayed file is correct.");
     is(target.querySelector(".requests-list-file").getAttribute("title"),
       unicodeUrl, "The tooltip file is correct.");
   }
 
   is(target.querySelector(".requests-list-protocol").textContent,
     httpVersion, "The displayed protocol is correct.");
 
   is(target.querySelector(".requests-list-protocol").getAttribute("title"),
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_maps-test-page.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor source maps test page</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+  </head>
+
+  <body>
+    <script type="text/javascript" src="xhr_bundle.js" charset="utf-8"></script>
+    <script type="text/javascript">
+      "use strict";
+
+      /* globals doxhr */
+      doxhr();
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_bundle.js
@@ -0,0 +1,91 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+function reallydoxhr() {
+  let z = new XMLHttpRequest();
+  z.open("get", "test-image.png", true);
+  z.send();
+}
+
+function doxhr() {
+  reallydoxhr();
+}
+
+window.doxhr = doxhr;
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=xhr_bundle.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_bundle.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 1f90f505700f55e4a0b4","webpack:///./xhr_original.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;AChEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA","file":"xhr_bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 1f90f505700f55e4a0b4","\"use strict\";\n\nfunction reallydoxhr() {\n  let z = new XMLHttpRequest();\n  z.open(\"get\", \"test-image.png\", true);\n  z.send();\n}\n\nfunction doxhr() {\n  reallydoxhr();\n}\n\nwindow.doxhr = doxhr;\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./xhr_original.js\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_original.js
@@ -0,0 +1,13 @@
+"use strict";
+
+function reallydoxhr() {
+  let z = new XMLHttpRequest();
+  z.open("get", "test-image.png", true);
+  z.send();
+}
+
+function doxhr() {
+  reallydoxhr();
+}
+
+window.doxhr = doxhr;
--- a/devtools/client/performance/test/helpers/tab-utils.js
+++ b/devtools/client/performance/test/helpers/tab-utils.js
@@ -1,18 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 /* globals dump */
 
+const { Cu } = require("chrome");
+const { BrowserTestUtils } = Cu.import("resource://testing-common/BrowserTestUtils.jsm", {});
 const Services = require("Services");
-const tabs = require("sdk/tabs");
-const tabUtils = require("sdk/tabs/utils");
-const { viewFor } = require("sdk/view/core");
 const { waitForDelayedStartupFinished } = require("devtools/client/performance/test/helpers/wait-utils");
 const { gDevTools } = require("devtools/client/framework/devtools");
 
 /**
  * Gets a random integer in between an interval. Used to uniquely identify
  * added tabs by augmenting the URL.
  */
 function getRandomInt(min, max) {
@@ -24,54 +23,29 @@ function getRandomInt(min, max) {
  * for it to load.
  */
 exports.addTab = function ({ url, win }, options = {}) {
   let id = getRandomInt(0, Number.MAX_SAFE_INTEGER - 1);
   url += `#${id}`;
 
   dump(`Adding tab with url: ${url}.\n`);
 
-  return new Promise(resolve => {
-    let tab;
-
-    tabs.on("ready", function onOpen(model) {
-      if (tab != viewFor(model)) {
-        return;
-      }
-      dump(`Tab added and finished loading: ${model.url}.\n`);
-      tabs.off("ready", onOpen);
-      resolve(tab);
-    });
-
-    win.focus();
-    tab = tabUtils.openTab(win, url);
-
-    if (options.dontWaitForTabReady) {
-      resolve(tab);
-    }
-  });
+  let { gBrowser } = win || window;
+  return BrowserTestUtils.openNewForegroundTab(gBrowser, url,
+                                               !options.dontWaitForTabReady);
 };
 
 /**
  * Removes a browser tab from the specified window and waits for it to close.
  */
 exports.removeTab = function (tab, options = {}) {
-  dump(`Removing tab: ${tabUtils.getURI(tab)}.\n`);
+  dump(`Removing tab: ${tab.linkedBrowser.currentURI.spec}.\n`);
 
   return new Promise(resolve => {
-    tabs.on("close", function onClose(model) {
-      if (tab != viewFor(model)) {
-        return;
-      }
-      dump(`Tab removed and finished closing: ${model.url}.\n`);
-      tabs.off("close", onClose);
-      resolve(tab);
-    });
-
-    tabUtils.closeTab(tab);
+    BrowserTestUtils.removeTab(tab).then(() => resolve(tab));
 
     if (options.dontWaitForTabClose) {
       resolve(tab);
     }
   });
 };
 
 /**
--- a/devtools/client/responsive.html/test/browser/touch.html
+++ b/devtools/client/responsive.html/test/browser/touch.html
@@ -66,17 +66,20 @@
   div.addEventListener("mouseleave", function (evt) {
     div.style.backgroundColor = "blue";
     updatePreviousEvent(evt);
   }, true);
 
   div.addEventListener("mousedown", function (evt) {
     if (previousEvent === "touchend" && touchendTime !== 0) {
       let now = performance.now();
-      div.dataset.isDelay = ((now - touchendTime) >= 300);
+      // Do to time spent processing events our measurement might
+      // be fractionally short of the actual delay.  Round up any
+      // microsecond changes in case we get something like 299.9.
+      div.dataset.isDelay = ((now - touchendTime) >= 299.5);
     } else {
       div.dataset.isDelay = false;
     }
     updatePreviousEvent(evt);
   }, true);
 
   div.addEventListener("mousemove", updatePreviousEvent, true);
 
--- a/devtools/client/responsivedesign/test/touch.html
+++ b/devtools/client/responsivedesign/test/touch.html
@@ -65,17 +65,20 @@
   div.addEventListener("mouseleave", function(evt) {
     div.style.backgroundColor = "blue";
     updatePreviousEvent(evt);
   }, true);
 
   div.addEventListener("mousedown", function(evt){
     if (previousEvent === "touchend" && touchendTime !== 0) {
       let now = performance.now();
-      div.dataset.isDelay = ((now - touchendTime) >= 300) ? true : false;
+      // Do to time spent processing events our measurement might
+      // be fractionally short of the actual delay.  Round up any
+      // microsecond changes in case we get something like 299.9.
+      div.dataset.isDelay = ((now - touchendTime) >= 299.5) ? true : false;
     } else {
       div.dataset.isDelay = false;
     }
     updatePreviousEvent(evt);
   }, true);
 
   div.addEventListener("mousemove", updatePreviousEvent, true);
 
--- a/devtools/client/shared/test/helper_inplace_editor.js
+++ b/devtools/client/shared/test/helper_inplace_editor.js
@@ -94,17 +94,17 @@ function* testCompletion([key, completio
     onSuggest = editor.once("after-suggest");
   }
 
   info("Synthesizing key " + key);
   EventUtils.synthesizeKey(key, {}, editor.input.defaultView);
 
   yield onSuggest;
   yield onVisibilityChange;
-  yield waitForTick();
+  yield waitForTime(5);
 
   info("Checking the state");
   if (completion !== null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
   if (total === 0) {
     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
   } else {
--- a/devtools/client/sourceeditor/debugger.js
+++ b/devtools/client/sourceeditor/debugger.js
@@ -1,16 +1,15 @@
 /* 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/. */
 
 "use strict";
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const promise = require("promise");
 const dbginfo = new WeakMap();
 
 // These functions implement search within the debugger. Since
 // search in the debugger is different from other components,
 // we can't use search.js CodeMirror addon. This is a slightly
 // modified version of that addon. Depends on searchcursor.js.
 
 function SearchState() {
@@ -125,57 +124,57 @@ function hasBreakpoint(ctx, line) {
 /**
  * Adds a visual breakpoint for a specified line. Third
  * parameter 'cond' can hold any object.
  *
  * After adding a breakpoint, this function makes Editor to
  * emit a breakpointAdded event.
  */
 function addBreakpoint(ctx, line, cond) {
-  function _addBreakpoint() {
-    let { ed, cm } = ctx;
-    let meta = dbginfo.get(ed);
-    let info = cm.lineInfo(line);
-
-    // The line does not exist in the editor. This is harmless, the
-    // architecture calling this assumes the editor will handle this
-    // gracefully, and make sure breakpoints exist when they need to.
-    if (!info) {
-      return;
-    }
-
-    ed.addLineClass(line, "breakpoint");
-    meta.breakpoints[line] = { condition: cond };
-
-    // TODO(jwl): why is `info` null when breaking on page reload?
-    info.handle.on("delete", function onDelete() {
-      info.handle.off("delete", onDelete);
-      meta.breakpoints[info.line] = null;
-    });
-
-    if (cond) {
-      setBreakpointCondition(ctx, line);
-    }
-    ed.emit("breakpointAdded", line);
-    deferred.resolve();
-  }
-
   if (hasBreakpoint(ctx, line)) {
     return null;
   }
 
-  let deferred = promise.defer();
-  // If lineInfo() returns null, wait a tick to give the editor a chance to
-  // initialize properly.
-  if (ctx.cm.lineInfo(line) === null) {
-    DevToolsUtils.executeSoon(() => _addBreakpoint());
-  } else {
-    _addBreakpoint();
-  }
-  return deferred.promise;
+  return new Promise(resolve => {
+    function _addBreakpoint() {
+      let { ed, cm } = ctx;
+      let meta = dbginfo.get(ed);
+      let info = cm.lineInfo(line);
+
+      // The line does not exist in the editor. This is harmless, the
+      // architecture calling this assumes the editor will handle this
+      // gracefully, and make sure breakpoints exist when they need to.
+      if (!info) {
+        return;
+      }
+
+      ed.addLineClass(line, "breakpoint");
+      meta.breakpoints[line] = { condition: cond };
+
+      // TODO(jwl): why is `info` null when breaking on page reload?
+      info.handle.on("delete", function onDelete() {
+        info.handle.off("delete", onDelete);
+        meta.breakpoints[info.line] = null;
+      });
+
+      if (cond) {
+        setBreakpointCondition(ctx, line);
+      }
+      ed.emit("breakpointAdded", line);
+      resolve();
+    }
+
+    // If lineInfo() returns null, wait a tick to give the editor a chance to
+    // initialize properly.
+    if (ctx.cm.lineInfo(line) === null) {
+      DevToolsUtils.executeSoon(() => _addBreakpoint());
+    } else {
+      _addBreakpoint();
+    }
+  });
 }
 
 /**
  * Helps reset the debugger's breakpoint state
  * - removes the breakpoints in the editor
  * - cleares the debugger's breakpoint state
  *
  * Note, does not *actually* remove a source's breakpoints.
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -25,17 +25,16 @@ const VALID_KEYMAPS = new Set(["emacs", 
 const MAX_VERTICAL_OFFSET = 3;
 
 // Match @Scratchpad/N:LINE[:COLUMN] or (LINE[:COLUMN]) anywhere at an end of
 // line in text selection.
 const RE_SCRATCHPAD_ERROR = /(?:@Scratchpad\/\d+:|\()(\d+):?(\d+)?(?:\)|\n)/;
 const RE_JUMP_TO_LINE = /^(\d+):?(\d+)?/;
 
 const Services = require("Services");
-const promise = require("promise");
 const events = require("devtools/shared/event-emitter");
 const { PrefObserver } = require("devtools/client/shared/prefs");
 const { getClientCssProperties } = require("devtools/shared/fronts/css-properties");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/sourceeditor.properties");
 
@@ -252,55 +251,55 @@ Editor.prototype = {
    * Appends the current Editor instance to the element specified by
    * 'el'. You can also provide your won iframe to host the editor as
    * an optional second parameter. This method actually creates and
    * loads CodeMirror and all its dependencies.
    *
    * This method is asynchronous and returns a promise.
    */
   appendTo: function (el, env) {
-    let def = promise.defer();
-    let cm = editors.get(this);
+    return new Promise(resolve => {
+      let cm = editors.get(this);
 
-    if (!env) {
-      env = el.ownerDocument.createElementNS(el.namespaceURI, "iframe");
+      if (!env) {
+        env = el.ownerDocument.createElementNS(el.namespaceURI, "iframe");
 
-      if (el.namespaceURI === XUL_NS) {
-        env.flex = 1;
+        if (el.namespaceURI === XUL_NS) {
+          env.flex = 1;
+        }
       }
-    }
 
-    if (cm) {
-      throw new Error("You can append an editor only once.");
-    }
-
-    let onLoad = () => {
-      let win = env.contentWindow.wrappedJSObject;
-
-      if (!this.config.themeSwitching) {
-        win.document.documentElement.setAttribute("force-theme", "light");
+      if (cm) {
+        throw new Error("You can append an editor only once.");
       }
 
-      Services.scriptloader.loadSubScript(
-        "chrome://devtools/content/shared/theme-switching.js",
-        win, "utf8"
-      );
-      this.container = env;
-      this._setup(win.document.body, el.ownerDocument);
-      env.removeEventListener("load", onLoad, true);
+      let onLoad = () => {
+        let win = env.contentWindow.wrappedJSObject;
+
+        if (!this.config.themeSwitching) {
+          win.document.documentElement.setAttribute("force-theme", "light");
+        }
 
-      def.resolve();
-    };
+        Services.scriptloader.loadSubScript(
+          "chrome://devtools/content/shared/theme-switching.js",
+          win, "utf8"
+        );
+        this.container = env;
+        this._setup(win.document.body, el.ownerDocument);
+        env.removeEventListener("load", onLoad, true);
 
-    env.addEventListener("load", onLoad, true);
-    env.setAttribute("src", CM_IFRAME);
-    el.appendChild(env);
+        resolve();
+      };
 
-    this.once("destroy", () => el.removeChild(env));
-    return def.promise;
+      env.addEventListener("load", onLoad, true);
+      env.setAttribute("src", CM_IFRAME);
+      el.appendChild(env);
+
+      this.once("destroy", () => el.removeChild(env));
+    });
   },
 
   appendToLocalElement: function (el) {
     this._setup(el);
   },
 
   /**
    * Do the actual appending and configuring of the CodeMirror instance. This is
--- a/devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
+++ b/devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js
@@ -24,22 +24,20 @@ function testJS(ed, win) {
 
   ok(ed.getOption("autocomplete"), "Autocompletion is set");
   ok(win.tern, "Tern is defined on the window");
 
   ed.focus();
   ed.setText("document.");
   ed.setCursor({line: 0, ch: 9});
 
-  let waitForSuggestion = promise.defer();
+  return new Promise(resolve => {
+    ed.on("before-suggest", () => {
+      info("before-suggest has been triggered");
+      EventUtils.synthesizeKey("VK_ESCAPE", { }, win);
+      resolve();
+    });
 
-  ed.on("before-suggest", () => {
-    info("before-suggest has been triggered");
-    EventUtils.synthesizeKey("VK_ESCAPE", { }, win);
-    waitForSuggestion.resolve();
+    let autocompleteKey =
+      Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase();
+    EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win);
   });
-
-  let autocompleteKey =
-    Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase();
-  EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win);
-
-  return waitForSuggestion.promise;
 }
--- a/devtools/client/sourceeditor/test/head.js
+++ b/devtools/client/sourceeditor/test/head.js
@@ -23,56 +23,55 @@ SimpleTest.registerCleanupFunction(() =>
 
 function promiseWaitForFocus() {
   return new Promise(resolve =>
     waitForFocus(resolve));
 }
 
 function setup(cb, additionalOpts = {}) {
   cb = cb || function () {};
-  let def = promise.defer();
-  const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
-  const url = "data:application/vnd.mozilla.xul+xml;charset=UTF-8," +
-    "<?xml version='1.0'?>" +
-    "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
-    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper" +
-    "/there.is.only.xul' title='Editor' width='600' height='500'>" +
-    "<box flex='1'/></window>";
+  return new Promise(resolve => {
+    const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+    const url = "data:application/vnd.mozilla.xul+xml;charset=UTF-8," +
+      "<?xml version='1.0'?>" +
+      "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
+      "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper" +
+      "/there.is.only.xul' title='Editor' width='600' height='500'>" +
+      "<box flex='1'/></window>";
 
-  let win = Services.ww.openWindow(null, url, "_blank", opt, null);
-  let opts = {
-    value: "Hello.",
-    lineNumbers: true,
-    foldGutter: true,
-    gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter"],
-    cssProperties: getClientCssProperties()
-  };
+    let win = Services.ww.openWindow(null, url, "_blank", opt, null);
+    let opts = {
+      value: "Hello.",
+      lineNumbers: true,
+      foldGutter: true,
+      gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter"],
+      cssProperties: getClientCssProperties()
+    };
 
-  for (let o in additionalOpts) {
-    opts[o] = additionalOpts[o];
-  }
+    for (let o in additionalOpts) {
+      opts[o] = additionalOpts[o];
+    }
 
-  win.addEventListener("load", function () {
-    waitForFocus(function () {
-      let box = win.document.querySelector("box");
-      let editor = new Editor(opts);
+    win.addEventListener("load", function () {
+      waitForFocus(function () {
+        let box = win.document.querySelector("box");
+        let editor = new Editor(opts);
 
-      editor.appendTo(box)
-        .then(() => {
-          def.resolve({
-            ed: editor,
-            win: win,
-            edWin: editor.container.contentWindow.wrappedJSObject
-          });
-          cb(editor, win);
-        }, err => ok(false, err.message));
-    }, win);
-  }, {once: true});
-
-  return def.promise;
+        editor.appendTo(box)
+          .then(() => {
+            resolve({
+              ed: editor,
+              win: win,
+              edWin: editor.container.contentWindow.wrappedJSObject
+            });
+            cb(editor, win);
+          }, err => ok(false, err.message));
+      }, win);
+    }, {once: true});
+  });
 }
 
 function ch(exp, act, label) {
   is(exp.line, act.line, label + " (line)");
   is(exp.ch, act.ch, label + " (ch)");
 }
 
 function teardown(ed, win) {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -15,16 +15,22 @@ Services.scriptloader.loadSubScript(
   this);
 
 var WCUL10n = require("devtools/client/webconsole/webconsole-l10n");
 
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 
+  // Reset all filter prefs between tests. First flushPrefEnv in case one of the
+  // filter prefs has been pushed for the test
+  yield SpecialPowers.flushPrefEnv();
+  Services.prefs.getChildList("devtools.webconsole.filter").forEach(pref => {
+    Services.prefs.clearUserPref(pref);
+  });
   let browserConsole = HUDService.getBrowserConsole();
   if (browserConsole) {
     if (browserConsole.jsterm) {
       browserConsole.jsterm.clearOutput(true);
     }
     yield HUDService.toggleBrowserConsole();
   }
 });
--- a/devtools/shared/gcli/command-state.js
+++ b/devtools/shared/gcli/command-state.js
@@ -1,19 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
-
-const getTargetId = ({tab}) => getBrowserForTab(tab).outerWindowID;
+const getTargetId = ({tab}) => tab.linkedBrowser.outerWindowID;
 const enabledCommands = new Map();
 
 /**
  * The `CommandState` is a singleton that provides utility methods to keep the commands'
  * state in sync between the toolbox, the toolbar and the content.
  */
 const CommandState = EventEmitter.decorate({
   /**
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -153,54 +153,52 @@ static const RedirEntry kRedirMap[] = {
 static const int kRedirTotal = mozilla::ArrayLength(kRedirMap);
 
 NS_IMETHODIMP
 nsAboutRedirector::NewChannel(nsIURI* aURI,
                               nsILoadInfo* aLoadInfo,
                               nsIChannel** aResult)
 {
   NS_ENSURE_ARG_POINTER(aURI);
+  NS_ENSURE_ARG_POINTER(aLoadInfo);
   NS_ASSERTION(aResult, "must not be null");
 
   nsAutoCString path;
   nsresult rv = NS_GetAboutModuleName(aURI, path);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   for (int i = 0; i < kRedirTotal; i++) {
     if (!strcmp(path.get(), kRedirMap[i].id)) {
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
+                                 tempURI,
+                                 aLoadInfo);
+      NS_ENSURE_SUCCESS(rv, rv);
+
       // If tempURI links to an external URI (i.e. something other than
-      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
-      // channel which forces the channel owner to reflect the displayed
+      // chrome:// or resource://) then set result principal URI on the
+      // load info which forces the channel principal to reflect the displayed
       // URL rather then being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
       bool isAboutBlank = NS_IsAboutBlank(tempURI);
 
-      nsLoadFlags loadFlags = isUIResource || isAboutBlank
-                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
-
-      rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
-                                 tempURI,
-                                 aLoadInfo,
-                                 nullptr, // aLoadGroup
-                                 nullptr, // aCallbacks
-                                 loadFlags);
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (!isUIResource && !isAboutBlank) {
+        aLoadInfo->SetResultPrincipalURI(tempURI);
+      }
 
       tempChannel->SetOriginalURI(aURI);
 
       tempChannel.forget(aResult);
       return rv;
     }
   }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11177,16 +11177,22 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   // Make sure to give the caller a channel if we managed to create one
   // This is important for correct error page/session history interaction
   if (aRequest) {
     NS_ADDREF(*aRequest = channel);
   }
 
   if (aOriginalURI) {
     channel->SetOriginalURI(aOriginalURI);
+    // The LOAD_REPLACE flag and its handling here will be removed as part
+    // of bug 1319110.  For now preserve its restoration here to not break
+    // any code expecting it being set specially on redirected channels.
+    // If the flag has originally been set to change result of
+    // NS_GetFinalChannelURI it won't have any effect and also won't cause
+    // any harm.
     if (aLoadReplace) {
       uint32_t loadFlags;
       channel->GetLoadFlags(&loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
       channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
     }
   } else {
     channel->SetOriginalURI(aURI);
--- a/dom/animation/EffectSet.h
+++ b/dom/animation/EffectSet.h
@@ -255,15 +255,15 @@ private:
 #ifdef DEBUG
   // Track how many iterators are referencing this effect set when we are
   // destroyed, we can assert that nothing is still pointing to us.
   uint64_t mActiveIterators;
 
   bool mCalledPropertyDtor;
 #endif
 
-  uint32_t mMayHaveOpacityAnim;
-  uint32_t mMayHaveTransformAnim;
+  bool mMayHaveOpacityAnim;
+  bool mMayHaveTransformAnim;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_EffectSet_h
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -74,17 +74,17 @@ class EventSourceImpl final : public nsI
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
-  NS_DECL_NSIEVENTTARGET
+  NS_DECL_NSIEVENTTARGET_FULL
   NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 
   explicit EventSourceImpl(EventSource* aEventSource);
 
   enum {
     CONNECTING = 0U,
     OPEN = 1U,
     CLOSED = 2U
--- a/dom/base/IdleRequest.h
+++ b/dom/base/IdleRequest.h
@@ -8,17 +8,16 @@
 #define mozilla_dom_idlerequest_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsICancelableRunnable.h"
-#include "nsIIncrementalRunnable.h"
 #include "nsIRunnable.h"
 #include "nsString.h"
 
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -1574,17 +1574,17 @@ TimeoutManager::OnDocumentLoaded()
 {
   MaybeStartThrottleTrackingTimout();
 }
 
 void
 TimeoutManager::MaybeStartThrottleTrackingTimout()
 {
   if (gTrackingTimeoutThrottlingDelay <= 0 ||
-      mWindow.AsInner()->InnerObjectsFreed()) {
+      mWindow.AsInner()->InnerObjectsFreed() || mWindow.IsSuspended()) {
     return;
   }
 
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTrackingTimeouts);
 
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
            this, gTrackingTimeoutThrottlingDelay));
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -82,18 +82,17 @@ class WebSocketImpl final : public nsIIn
                           , public nsIEventTarget
 {
 public:
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIWEBSOCKETLISTENER
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIREQUEST
   NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIEVENTTARGET
-  using nsIEventTarget::Dispatch;
+  NS_DECL_NSIEVENTTARGET_FULL
 
   explicit WebSocketImpl(WebSocket* aWebSocket)
   : mWebSocket(aWebSocket)
   , mIsServerSide(false)
   , mSecure(false)
   , mOnCloseScheduled(false)
   , mFailed(false)
   , mDisconnectingOrDisconnected(false)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5082,17 +5082,17 @@ nsDocument::MaybeEndOutermostXBLUpdate()
 
 void
 nsDocument::BeginUpdate(nsUpdateType aUpdateType)
 {
   // If the document is going away, then it's probably okay to do things to it
   // in the wrong DocGroup. We're unlikely to run JS or do anything else
   // observable at this point. We reach this point when cycle collecting a
   // <link> element and the unlink code removes a style sheet.
-  if (mDocGroup && !mIsGoingAway && !mIgnoreDocGroupMismatches) {
+  if (mDocGroup && !mIsGoingAway && !mInUnlinkOrDeletion && !mIgnoreDocGroupMismatches) {
     mDocGroup->ValidateAccess();
   }
 
   if (mUpdateNestLevel == 0 && !mInXBLUpdate) {
     mInXBLUpdate = true;
     BindingManager()->BeginOutermostUpdate();
   }
 
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3727,14 +3727,45 @@ nsFocusManager::MarkUncollectableForCCGe
       MarkUncollectableForCCGeneration(aGeneration);
   }
   if (sInstance->mMouseButtonEventHandlingDocument) {
     sInstance->mMouseButtonEventHandlingDocument->
       MarkUncollectableForCCGeneration(aGeneration);
   }
 }
 
+bool
+nsFocusManager::CanSkipFocus(nsIContent* aContent)
+{
+  if (!aContent ||
+      nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
+    return false;
+  }
+
+  if (mFocusedContent == aContent) {
+    return true;
+  }
+
+  nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell();
+  if (!ds) {
+    return true;
+  }
+
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  ds->GetRootTreeItem(getter_AddRefs(root));
+  nsCOMPtr<nsPIDOMWindowOuter> newRootWindow =
+    root ? root->GetWindow() : nullptr;
+  if (mActiveWindow != newRootWindow) {
+    nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
+    if (outerWindow && outerWindow->GetFocusedNode() == aContent) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 nsresult
 NS_NewFocusManager(nsIFocusManager** aResult)
 {
   NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
   return NS_OK;
 }
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -96,20 +96,17 @@ public:
 
   void NeedsFlushBeforeEventHandling(nsIContent* aContent)
   {
     if (mFocusedContent == aContent) {
       mEventHandlingNeedsFlush = true;
     }
   }
 
-  bool CanSkipFocus(nsIContent* aContent)
-  {
-    return mFocusedContent == aContent;
-  }
+  bool CanSkipFocus(nsIContent* aContent);
 
   void FlushBeforeEventHandlingIfNeeded(nsIContent* aContent)
   {
     if (mEventHandlingNeedsFlush) {
       nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
       if (doc) {
         mEventHandlingNeedsFlush = false;
         doc->FlushPendingNotifications(mozilla::FlushType::Layout);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -558,17 +558,17 @@ NS_IMPL_RELEASE_INHERITED(IdleRequestExe
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
 NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler)
 
 
 class IdleRequestExecutor final : public nsIRunnable
                                 , public nsICancelableRunnable
                                 , public nsINamed
-                                , public nsIIncrementalRunnable
+                                , public nsIIdleRunnable
 {
 public:
   explicit IdleRequestExecutor(nsGlobalWindow* aWindow)
     : mDispatched(false)
     , mDeadline(TimeStamp::Now())
     , mWindow(aWindow)
   {
     MOZ_DIAGNOSTIC_ASSERT(mWindow);
@@ -645,17 +645,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDelayedExecutorDispatcher)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
   NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
   NS_INTERFACE_MAP_ENTRY(nsINamed)
-  NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
 IdleRequestExecutor::GetName(nsACString& aName)
 {
     aName.AssignASCII("IdleRequestExecutor");
     return NS_OK;
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -21,16 +21,17 @@
 #include "nsIURI.h"
 #include "nsILoadGroup.h"
 #include "imgIContainer.h"
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
 #include "nsImageFrame.h"
+#include "nsSVGImageFrame.h"
 
 #include "nsIPresShell.h"
 
 #include "nsIChannel.h"
 #include "nsIStreamListener.h"
 
 #include "nsIFrame.h"
 #include "nsIDOMNode.h"
@@ -1175,28 +1176,34 @@ nsImageLoadingContent::CancelPendingEven
     mPendingEvent->Cancel();
     mPendingEvent = nullptr;
   }
 }
 
 RefPtr<imgRequestProxy>&
 nsImageLoadingContent::PrepareNextRequest(ImageLoadType aImageLoadType)
 {
-  nsImageFrame* frame = do_QueryFrame(GetOurPrimaryFrame());
-  if (frame) {
+  nsImageFrame* imageFrame = do_QueryFrame(GetOurPrimaryFrame());
+  nsSVGImageFrame* svgImageFrame = do_QueryFrame(GetOurPrimaryFrame());
+  if (imageFrame || svgImageFrame) {
     // Detect JavaScript-based animations created by changing the |src|
     // attribute on a timer.
     TimeStamp now = TimeStamp::Now();
     TimeDuration threshold =
       TimeDuration::FromMilliseconds(
         gfxPrefs::ImageInferSrcAnimationThresholdMS());
 
     // If the length of time between request changes is less than the threshold,
     // then force sync decoding to eliminate flicker from the animation.
-    frame->SetForceSyncDecoding(now - mMostRecentRequestChange < threshold);
+    bool forceSync = (now - mMostRecentRequestChange < threshold);
+    if (imageFrame) {
+      imageFrame->SetForceSyncDecoding(forceSync);
+    } else {
+      svgImageFrame->SetForceSyncDecoding(forceSync);
+    }
 
     mMostRecentRequestChange = now;
   }
 
   // If we don't have a usable current request, get rid of any half-baked
   // request that might be sitting there and make this one current.
   if (!HaveSize(mCurrentRequest))
     return PrepareCurrentRequest(aImageLoadType);
--- a/dom/events/AsyncEventDispatcher.cpp
+++ b/dom/events/AsyncEventDispatcher.cpp
@@ -34,16 +34,23 @@ AsyncEventDispatcher::AsyncEventDispatch
 }
 
 NS_IMETHODIMP
 AsyncEventDispatcher::Run()
 {
   if (mCanceled) {
     return NS_OK;
   }
+  if (mCheckStillInDoc) {
+    nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
+    MOZ_ASSERT(node);
+    if (!node->IsInComposedDoc()) {
+      return NS_OK;
+    }
+  }
   mTarget->AsyncEventRunning(this);
   RefPtr<Event> event = mEvent ? mEvent->InternalDOMEvent() : nullptr;
   if (!event) {
     event = NS_NewDOMEvent(mTarget, nullptr, nullptr);
     event->InitEvent(mEventType, mBubbles, false);
     event->SetTrusted(true);
   }
   if (mOnlyChromeDispatch) {
@@ -83,16 +90,27 @@ AsyncEventDispatcher::PostDOMEvent()
 
 void
 AsyncEventDispatcher::RunDOMEventWhenSafe()
 {
   RefPtr<AsyncEventDispatcher> ensureDeletionWhenFailing = this;
   nsContentUtils::AddScriptRunner(this);
 }
 
+void
+AsyncEventDispatcher::RequireNodeInDocument()
+{
+#ifdef DEBUG
+  nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
+  MOZ_ASSERT(node);
+#endif
+
+  mCheckStillInDoc = true;
+}
+
 /******************************************************************************
  * mozilla::LoadBlockingAsyncEventDispatcher
  ******************************************************************************/
 
 LoadBlockingAsyncEventDispatcher::~LoadBlockingAsyncEventDispatcher()
 {
   if (mBlockedDoc) {
     mBlockedDoc->UnblockOnload(true);
--- a/dom/events/AsyncEventDispatcher.h
+++ b/dom/events/AsyncEventDispatcher.h
@@ -59,22 +59,28 @@ public:
 
   AsyncEventDispatcher(dom::EventTarget* aTarget, WidgetEvent& aEvent);
 
   NS_IMETHOD Run() override;
   nsresult Cancel() override;
   nsresult PostDOMEvent();
   void RunDOMEventWhenSafe();
 
+  // Calling this causes the Run() method to check that
+  // mTarget->IsInComposedDoc(). mTarget must be an nsINode or else we'll
+  // assert.
+  void RequireNodeInDocument();
+
   nsCOMPtr<dom::EventTarget> mTarget;
   nsCOMPtr<nsIDOMEvent> mEvent;
   nsString              mEventType;
   bool                  mBubbles = false;
   bool                  mOnlyChromeDispatch = false;
   bool                  mCanceled = false;
+  bool                  mCheckStillInDoc = false;
 };
 
 class LoadBlockingAsyncEventDispatcher final : public AsyncEventDispatcher
 {
 public:
   LoadBlockingAsyncEventDispatcher(nsINode* aEventNode,
                                    const nsAString& aEventType,
                                    bool aBubbles, bool aDispatchChromeOnly)
--- a/dom/flyweb/FlyWebService.cpp
+++ b/dom/flyweb/FlyWebService.cpp
@@ -785,24 +785,28 @@ FlyWebMDNSService::PairWithService(const
   DiscoveredInfo* discInfo = mServiceMap.Get(aServiceId);
 
   nsAutoString url;
   if (discInfo->mService.mCert.IsEmpty()) {
     url.AssignLiteral("http://");
   } else {
     url.AssignLiteral("https://");
   }
-  url.Append(aInfo->mService.mHostname + NS_LITERAL_STRING("/"));
+  url.Append(aInfo->mService.mHostname);
+  if (!discInfo->mService.mPath.IsEmpty()) {
+    if (discInfo->mService.mPath.Find("/") != 0) {
+      url.Append(NS_LITERAL_STRING("/"));
+    }
+    url.Append(discInfo->mService.mPath);
+  } else {
+    url.Append(NS_LITERAL_STRING("/"));
+  }
   nsCOMPtr<nsIURI> uiURL;
   NS_NewURI(getter_AddRefs(uiURL), url);
   MOZ_ASSERT(uiURL);
-  if (!discInfo->mService.mPath.IsEmpty()) {
-    nsCOMPtr<nsIURI> tmp = uiURL.forget();
-    NS_NewURI(getter_AddRefs(uiURL), discInfo->mService.mPath, nullptr, tmp);
-  }
   if (uiURL) {
     nsAutoCString spec;
     uiURL->GetSpec(spec);
     CopyUTF8toUTF16(spec, aInfo->mService.mUiUrl);
   }
 
   aInfo->mService.mDiscoveredService = discInfo->mService;
   aInfo->mDNSServiceInfo = discInfo->mDNSServiceInfo;
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1783,16 +1783,20 @@ HTMLInputElement::GetNonFileValueInterna
       }
       return;
   }
 }
 
 bool
 HTMLInputElement::IsValueEmpty() const
 {
+  if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
+    return !mInputData.mState->HasNonEmptyValue();
+  }
+
   nsAutoString value;
   GetNonFileValueInternal(value);
 
   return value.IsEmpty();
 }
 
 void
 HTMLInputElement::ClearFiles(bool aSetValueChanged)
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -271,28 +271,28 @@ TextTrackManager::UpdateCueDisplay()
   }
 
   nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
   nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls();
   if (!overlay) {
     return;
   }
 
-  nsTArray<RefPtr<TextTrackCue> > activeCues;
-  mTextTracks->GetShowingCues(activeCues);
+  nsTArray<RefPtr<TextTrackCue> > showingCues;
+  mTextTracks->GetShowingCues(showingCues);
 
-  if (activeCues.Length() > 0) {
+  if (showingCues.Length() > 0) {
     WEBVTT_LOG("UpdateCueDisplay ProcessCues");
-    WEBVTT_LOGV("UpdateCueDisplay activeCues.Length() %" PRIuSIZE, activeCues.Length());
+    WEBVTT_LOGV("UpdateCueDisplay showingCues.Length() %" PRIuSIZE, showingCues.Length());
     RefPtr<nsVariantCC> jsCues = new nsVariantCC();
 
     jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE,
                        &NS_GET_IID(nsIDOMEventTarget),
-                       activeCues.Length(),
-                       static_cast<void*>(activeCues.Elements()));
+                       showingCues.Length(),
+                       static_cast<void*>(showingCues.Elements()));
     nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow();
     if (window) {
       sParserWrapper->ProcessCues(window, jsCues, overlay, controls);
     }
   } else if (overlay->Length() > 0) {
     WEBVTT_LOG("UpdateCueDisplay EmptyString");
     nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
   }
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -820,33 +820,34 @@ ContentChild::ProvideWindowCommon(TabChi
   nsString name(aName);
   nsTArray<FrameScriptInfo> frameScripts;
   nsCString urlToLoad;
 
   PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor();
   TextureFactoryIdentifier textureFactoryIdentifier;
   uint64_t layersId = 0;
   CompositorOptions compositorOptions;
+  uint32_t maxTouchPoints = 0;
 
   if (aIframeMoz) {
     MOZ_ASSERT(aTabOpener);
     nsAutoCString url;
     if (aURI) {
       aURI->GetSpec(url);
     } else {
       // We can't actually send a nullptr up as the URI, since IPDL doesn't let us
       // send nullptr's for primitives. We indicate that the nsString for the URI
       // should be converted to a nullptr by voiding the string.
       url.SetIsVoid(true);
     }
 
     newChild->SendBrowserFrameOpenWindow(aTabOpener, renderFrame, NS_ConvertUTF8toUTF16(url),
                                          name, NS_ConvertUTF8toUTF16(features),
                                          aWindowIsNew, &textureFactoryIdentifier,
-                                         &layersId, &compositorOptions);
+                                         &layersId, &compositorOptions, &maxTouchPoints);
   } else {
     nsAutoCString baseURIString;
     float fullZoom;
     OriginAttributes originAttributes;
     rv = GetWindowParamsFromParent(aParent, baseURIString, &fullZoom,
                                    originAttributes);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -860,17 +861,18 @@ ContentChild::ProvideWindowCommon(TabChi
                           originAttributes,
                           fullZoom,
                           &rv,
                           aWindowIsNew,
                           &frameScripts,
                           &urlToLoad,
                           &textureFactoryIdentifier,
                           &layersId,
-                          &compositorOptions)) {
+                          &compositorOptions,
+                          &maxTouchPoints)) {
       PRenderFrameChild::Send__delete__(renderFrame);
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (NS_FAILED(rv)) {
       PRenderFrameChild::Send__delete__(renderFrame);
       return rv;
     }
@@ -891,16 +893,18 @@ ContentChild::ProvideWindowCommon(TabChi
   if (opener && (openerShell = opener->GetDocShell())) {
     nsCOMPtr<nsILoadContext> context = do_QueryInterface(openerShell);
     showInfo = ShowInfo(EmptyString(), false,
                         context->UsePrivateBrowsing(), true, false,
                         aTabOpener->mDPI, aTabOpener->mRounding,
                         aTabOpener->mDefaultScale);
   }
 
+  newChild->SetMaxTouchPoints(maxTouchPoints);
+
   // Set the opener window for this window before we start loading the document
   // inside of it. We have to do this before loading the remote scripts, because
   // they can poke at the document and cause the nsDocument to be created before
   // the openerwindow
   nsCOMPtr<mozIDOMWindowProxy> windowProxy = do_GetInterface(newChild->WebNavigation());
   if (!aForceNoOpener && windowProxy && aParent) {
     nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::From(windowProxy);
     nsPIDOMWindowOuter* parent = nsPIDOMWindowOuter::From(aParent);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4258,33 +4258,16 @@ ContentParent::RecvUnregisterRemoteFrame
 mozilla::ipc::IPCResult
 ContentParent::RecvNotifyTabDestroying(const TabId& aTabId,
                                        const ContentParentId& aCpId)
 {
   NotifyTabDestroying(aTabId, aCpId);
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvTabChildNotReady(const TabId& aTabId)
-{
-  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
-  RefPtr<TabParent> tp =
-    cpm->GetTopLevelTabParentByProcessAndTabId(this->ChildID(), aTabId);
-
-  if (!tp) {
-    NS_WARNING("Couldn't find TabParent for TabChildNotReady message.");
-    return IPC_OK();
-  }
-
-  tp->DispatchTabChildNotReadyEvent();
-
-  return IPC_OK();
-}
-
 nsTArray<TabContext>
 ContentParent::GetManagedTabContext()
 {
   return Move(ContentProcessManager::GetSingleton()->
           GetTabContextByContentProcess(this->ChildID()));
 }
 
 mozilla::docshell::POfflineCacheUpdateParent*
@@ -4612,17 +4595,18 @@ ContentParent::RecvCreateWindow(PBrowser
                                 const OriginAttributes& aOpenerOriginAttributes,
                                 const float& aFullZoom,
                                 nsresult* aResult,
                                 bool* aWindowIsNew,
                                 InfallibleTArray<FrameScriptInfo>* aFrameScripts,
                                 nsCString* aURLToLoad,
                                 TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                 uint64_t* aLayersId,
-                                CompositorOptions* aCompositorOptions)
+                                CompositorOptions* aCompositorOptions,
+                                uint32_t* aMaxTouchPoints)
 {
   // We always expect to open a new window here. If we don't, it's an error.
   *aWindowIsNew = true;
   *aResult = NS_OK;
 
   TabParent* newTab = TabParent::GetFrom(aNewTab);
   MOZ_ASSERT(newTab);
 
@@ -4666,16 +4650,19 @@ ContentParent::RecvCreateWindow(PBrowser
 
   RenderFrameParent* rfp = static_cast<RenderFrameParent*>(aRenderFrame);
   if (!newTab->SetRenderFrame(rfp) ||
       !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) {
     *aResult = NS_ERROR_FAILURE;
   }
   *aCompositorOptions = rfp->GetCompositorOptions();
 
+  nsCOMPtr<nsIWidget> widget = newTab->GetWidget();
+  *aMaxTouchPoints = widget ? widget->GetMaxTouchPoints() : 0;
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvCreateWindowInDifferentProcess(
   PBrowserParent* aThisTab,
   const uint32_t& aChromeFlags,
   const bool& aCalledFromJS,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -488,18 +488,16 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvUnregisterRemoteFrame(const TabId& aTabId,
                                                             const ContentParentId& aCpId,
                                                             const bool& aMarkedDestroying) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyTabDestroying(const TabId& aTabId,
                                                           const ContentParentId& aCpId) override;
 
-  virtual mozilla::ipc::IPCResult RecvTabChildNotReady(const TabId& aTabId) override;
-
   nsTArray<TabContext> GetManagedTabContext();
 
   virtual POfflineCacheUpdateParent*
   AllocPOfflineCacheUpdateParent(const URIParams& aManifestURI,
                                  const URIParams& aDocumentURI,
                                  const PrincipalInfo& aLoadingPrincipalInfo,
                                  const bool& aStickDocument) override;
 
@@ -544,17 +542,18 @@ public:
                    const OriginAttributes& aOpenerOriginAttributes,
                    const float& aFullZoom,
                    nsresult* aResult,
                    bool* aWindowIsNew,
                    InfallibleTArray<FrameScriptInfo>* aFrameScripts,
                    nsCString* aURLToLoad,
                    layers::TextureFactoryIdentifier* aTextureFactoryIdentifier,
                    uint64_t* aLayersId,
-                   mozilla::layers::CompositorOptions* aCompositorOptions) override;
+                   mozilla::layers::CompositorOptions* aCompositorOptions,
+                   uint32_t* aMaxTouchPoints) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateWindowInDifferentProcess(
     PBrowserParent* aThisTab,
     const uint32_t& aChromeFlags,
     const bool& aCalledFromJS,
     const bool& aPositionSpecified,
     const bool& aSizeSpecified,
     const URIParams& aURIToLoad,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -373,21 +373,16 @@ parent:
     sync GetDefaultScale() returns (double value);
 
     /**
      * Gets the rounding of coordinates in the widget.
      */
     sync GetWidgetRounding() returns (int32_t value);
 
     /**
-     * Gets maximum of touch points at current device.
-     */
-    sync GetMaxTouchPoints() returns (uint32_t value);
-
-    /**
      * Set the native cursor.
      * @param value
      *   The widget cursor to set.
      * @param force
      *   Invalidate any locally cached cursor settings and force an
      *   update.
      */
     async SetCursor(uint32_t value, bool force);
@@ -461,17 +456,18 @@ parent:
      *
      * @param opener the PBrowser whose content called window.open.
      */
     sync BrowserFrameOpenWindow(PBrowser opener, PRenderFrame renderFrame,
                                 nsString aURL, nsString aName, nsString aFeatures)
       returns (bool windowOpened,
                TextureFactoryIdentifier textureFactoryIdentifier,
                uint64_t layersId,
-               CompositorOptions compositorOptions);
+               CompositorOptions compositorOptions,
+               uint32_t maxTouchPoints);
 
     /**
      * Tells the containing widget whether the given input block results in a
      * swipe. Should be called in response to a WidgetWheelEvent that has
      * mFlags.mCanTriggerSwipe set on it.
      */
     async RespondStartSwipeEvent(uint64_t aInputBlockId, bool aStartSwipe);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -925,19 +925,16 @@ parent:
                                 ContentParentId cpId,
                                 bool aMarkedDestroying);
 
     /**
      * Tell the chrome process there is a destruction of PBrowser(Tab)
      */
     async NotifyTabDestroying(TabId tabId,
                               ContentParentId cpId);
-
-    async TabChildNotReady(TabId tabId);
-
     /**
      * Starts an offline application cache update.
      * @param manifestURI
      *   URI of the manifest to fetch, the application cache group ID
      * @param documentURI
      *   URI of the document that referred the manifest
      * @param loadingPrincipal
      *   Principal of the document that referred the manifest
@@ -1015,17 +1012,18 @@ parent:
                       OriginAttributes aOpenerOriginAttributes,
                       float aFullZoom)
       returns (nsresult rv,
                bool windowOpened,
                FrameScriptInfo[] frameScripts,
                nsCString urlToLoad,
                TextureFactoryIdentifier textureFactoryIdentifier,
                uint64_t layersId,
-               CompositorOptions compositorOptions);
+               CompositorOptions compositorOptions,
+               uint32_t maxTouchPoints);
 
     async CreateWindowInDifferentProcess(
       PBrowser aThisTab,
       uint32_t aChromeFlags,
       bool aCalledFromJS,
       bool aPositionSpecified,
       bool aSizeSpecified,
       URIParams aURIToLoad,
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -28,17 +28,16 @@ union HangData
   PluginHangData;
 };
 
 protocol PProcessHangMonitor
 {
 parent:
   async HangEvidence(HangData data);
   async ClearHang();
-  async Ready();
 
 child:
   async TerminateScript();
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
   async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -7,17 +7,16 @@
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
 #include "jsapi.h"
 #include "js/GCAPI.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/BackgroundHangMonitor.h"
-#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
@@ -188,18 +187,16 @@ public:
     mDumpId = aDumpId;
   }
 
   void ClearHang() {
     mHangData = HangData();
     mDumpId.Truncate();
   }
 
-  void DispatchTabChildNotReady(TabId aTabId);
-
 private:
   ~HangMonitoredProcess() = default;
 
   // Everything here is main thread-only.
   HangMonitorParent* mActor;
   ContentParent* mContentParent;
   HangData mHangData;
   nsAutoString mDumpId;
@@ -209,17 +206,16 @@ class HangMonitorParent
   : public PProcessHangMonitorParent
 {
 public:
   explicit HangMonitorParent(ProcessHangMonitor* aMonitor);
   ~HangMonitorParent() override;
 
   void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint);
 
-  mozilla::ipc::IPCResult RecvReady() override;
   mozilla::ipc::IPCResult RecvHangEvidence(const HangData& aHangData) override;
   mozilla::ipc::IPCResult RecvClearHang() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
@@ -244,33 +240,25 @@ private:
   bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
 
   void SendHangNotification(const HangData& aHangData,
                             const nsString& aBrowserDumpId,
                             bool aTakeMinidump);
 
   void ClearHangNotification();
 
-  void DispatchTabChildNotReady(TabId aTabId);
-
   void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
-  // This field is only accessed on the hang thread. Inits to
-  // false, and will flip to true once the HangMonitorChild is
-  // constructed in the child process, and sends a message saying
-  // so.
-  bool mReady;
-
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 
   Monitor mMonitor;
 
   // Must be accessed with mMonitor held.
   RefPtr<HangMonitoredProcess> mProcess;
   bool mShutdownDone;
@@ -334,21 +322,16 @@ HangMonitorChild::InterruptCallback()
     mForcePaint = false;
   }
 
   if (forcePaint) {
     RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
     if (tabChild) {
       js::AutoAssertNoContentJS nojs(mContext);
       tabChild->ForcePaint(forcePaintEpoch);
-    } else {
-      auto cc = ContentChild::GetSingleton();
-      if (cc) {
-        cc->SendTabChildNotReady(forcePaintTab);
-      }
     }
   }
 }
 
 void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -444,18 +427,16 @@ HangMonitorChild::Bind(Endpoint<PProcess
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 
   DebugOnly<bool> ok = aEndpoint.Bind(this);
   MOZ_ASSERT(ok);
-
-  Unused << SendReady();
 }
 
 void
 HangMonitorChild::NotifySlowScriptAsync(TabId aTabId,
                                         const nsCString& aFileName)
 {
   if (mIPCOpen) {
     Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName));
@@ -566,17 +547,16 @@ HangMonitorChild::ClearHangAsync()
     Unused << SendClearHang();
   }
 }
 
 /* HangMonitorParent implementation */
 
 HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
-   mReady(false),
    mIPCOpen(true),
    mMonitor("HangMonitorParent lock"),
    mShutdownDone(false),
    mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock"),
    mMainThreadTaskFactory(this)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
@@ -647,47 +627,22 @@ HangMonitorParent::ForcePaint(dom::TabPa
   if (sShouldForcePaint) {
     TabId id = aTab->GetTabId();
     MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
                               this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
   }
 }
 
 void
-HangMonitorParent::DispatchTabChildNotReady(TabId aTabId)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (!mProcess) {
-    return;
-  }
-
-  mProcess->DispatchTabChildNotReady(aTabId);
-}
-
-void
 HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (mIPCOpen) {
-    if (mReady) {
-      Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
-    } else {
-      // We've never heard from the HangMonitorChild before, so
-      // it's either not finished setting up, or has only recently
-      // finished setting up. In either case, we're dealing with
-      // a new content process that probably hasn't had time to
-      // get the ContentChild, let alone the TabChild for aTabId,
-      // set up, and so attempting to force paint on the non-existant
-      // TabChild is not going to work. Instead, we tell the main
-      // thread that we're waiting on a TabChild to be created.
-      NS_DispatchToMainThread(
-        mMainThreadTaskFactory.NewRunnableMethod(
-          &HangMonitorParent::DispatchTabChildNotReady, aTabId));
-    }
+    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
   }
 }
 
 void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
   mIPCOpen = false;
@@ -766,24 +721,16 @@ HangMonitorParent::TakeBrowserMinidump(c
     }
   }
 #endif // MOZ_CRASHREPORTER
 
   return false;
 }
 
 mozilla::ipc::IPCResult
-HangMonitorParent::RecvReady()
-{
-  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
-  mReady = true;
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
 {
   // chrome process, background thread
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (!mReportHangs) {
     return IPC_OK();
   }
@@ -1078,27 +1025,16 @@ HangMonitoredProcess::UserCanceled()
 
   if (mActor) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
   return NS_OK;
 }
 
-void
-HangMonitoredProcess::DispatchTabChildNotReady(TabId aTabId)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (!mContentParent) {
-    return;
-  }
-
-  Unused << mContentParent->RecvTabChildNotReady(aTabId);
-}
-
 static bool
 InterruptCallback(JSContext* cx)
 {
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->InterruptCallback();
   }
 
   return true;
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -371,16 +371,17 @@ TabChild::TabChild(nsIContentChild* aMan
                    dom::TabGroup* aTabGroup,
                    const TabContext& aContext,
                    uint32_t aChromeFlags)
   : TabContext(aContext)
   , mTabGroup(aTabGroup)
   , mRemoteFrame(nullptr)
   , mManager(aManager)
   , mChromeFlags(aChromeFlags)
+  , mMaxTouchPoints(0)
   , mActiveSuppressDisplayport(0)
   , mLayersId(0)
   , mBeforeUnloadListeners(0)
   , mLayersConnected(true)
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
@@ -2701,23 +2702,16 @@ TabChild::GetWidgetRounding(int32_t* aRo
     return;
   }
 
   // Fallback to a sync call if needed.
   SendGetWidgetRounding(aRounding);
 }
 
 void
-TabChild::GetMaxTouchPoints(uint32_t* aTouchPoints)
-{
-  // Fallback to a sync call.
-  SendGetMaxTouchPoints(aTouchPoints);
-}
-
-void
 TabChild::NotifyPainted()
 {
     if (!mNotified) {
         mRemoteFrame->SendNotifyCompositorTransaction();
         mNotified = true;
     }
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -498,17 +498,25 @@ public:
   void GetDPI(float* aDPI);
 
   void GetDefaultScale(double *aScale);
 
   void GetWidgetRounding(int32_t* aRounding);
 
   bool IsTransparent() const { return mIsTransparent; }
 
-  void GetMaxTouchPoints(uint32_t* aTouchPoints);
+  void GetMaxTouchPoints(uint32_t* aTouchPoints)
+  {
+    *aTouchPoints = mMaxTouchPoints;
+  }
+
+  void SetMaxTouchPoints(uint32_t aMaxTouchPoints)
+  {
+    mMaxTouchPoints = aMaxTouchPoints;
+  }
 
   ScreenOrientationInternal GetOrientation() const { return mOrientation; }
 
   void SetBackgroundColor(const nscolor& aColor);
 
   void NotifyPainted();
 
   void RequestEditCommands(nsIWidget::NativeKeyBindingsType aType,
@@ -782,16 +790,17 @@ private:
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<mozilla::dom::TabGroup> mTabGroup;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
   RenderFrameChild* mRemoteFrame;
   RefPtr<nsIContentChild> mManager;
   RefPtr<TabChildSHistoryListener> mHistoryListener;
   uint32_t mChromeFlags;
+  uint32_t mMaxTouchPoints;
   int32_t mActiveSuppressDisplayport;
   uint64_t mLayersId;
   int64_t mBeforeUnloadListeners;
   CSSRect mUnscaledOuterRect;
   nscolor mLastBackgroundColor;
   bool mLayersConnected;
   bool mDidFakeShow;
   bool mNotified;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2291,28 +2291,16 @@ TabParent::RecvGetWidgetRounding(int32_t
   TryCacheDPIAndScale();
 
   MOZ_ASSERT(mRounding > 0 || mFrameElement,
              "Must not ask for rounding before OwnerElement is received!");
   *aValue = mRounding;
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-TabParent::RecvGetMaxTouchPoints(uint32_t* aTouchPoints)
-{
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  if (widget) {
-    *aTouchPoints = widget->GetMaxTouchPoints();
-  } else {
-    *aTouchPoints = 0;
-  }
-  return IPC_OK();
-}
-
 already_AddRefed<nsIWidget>
 TabParent::GetTopLevelWidget()
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
   if (content) {
     nsIPresShell* shell = content->OwnerDoc()->GetShell();
     if (shell) {
       nsViewManager* vm = shell->GetViewManager();
@@ -2584,24 +2572,27 @@ mozilla::ipc::IPCResult
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       PRenderFrameParent* aRenderFrame,
                                       const nsString& aURL,
                                       const nsString& aName,
                                       const nsString& aFeatures,
                                       bool* aOutWindowOpened,
                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                       uint64_t* aLayersId,
-                                      CompositorOptions* aCompositorOptions)
+                                      CompositorOptions* aCompositorOptions,
+                                      uint32_t* aMaxTouchPoints)
 {
   BrowserElementParent::OpenWindowResult opened =
     BrowserElementParent::OpenWindowOOP(TabParent::GetFrom(aOpener),
                                         this, aRenderFrame, aURL, aName, aFeatures,
                                         aTextureFactoryIdentifier, aLayersId);
   *aCompositorOptions = static_cast<RenderFrameParent*>(aRenderFrame)->GetCompositorOptions();
   *aOutWindowOpened = (opened == BrowserElementParent::OPEN_WINDOW_ADDED);
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  *aMaxTouchPoints = widget ? widget->GetMaxTouchPoints() : 0;
   if (!*aOutWindowOpened) {
     Destroy();
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
@@ -3262,62 +3253,16 @@ TabParent::LiveResizeStarted()
 }
 
 void
 TabParent::LiveResizeStopped()
 {
   SuppressDisplayport(false);
 }
 
-void
-TabParent::DispatchTabChildNotReadyEvent()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
-  if (!target) {
-    NS_WARNING("Could not locate target for tab child not ready event.");
-    return;
-  }
-
-  if (mHasPresented) {
-    // We shouldn't dispatch this event because clearly the
-    // TabChild _became_ ready by the time we were told to
-    // dispatch.
-    return;
-  }
-
-  if (!mDocShellIsActive) {
-    return;
-  }
-
-  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
-  if (!frameLoader) {
-    return;
-  }
-
-  nsCOMPtr<Element> frameElement(mFrameElement);
-  nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(frameElement);
-  if (!owner) {
-    return;
-  }
-
-  RefPtr<nsFrameLoader> currentFrameLoader = owner->GetFrameLoader();
-  if (currentFrameLoader != frameLoader) {
-    return;
-  }
-
-  RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
-  event->InitEvent(NS_LITERAL_STRING("MozTabChildNotReady"), true, false);
-  event->SetTrusted(true);
-  event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
-  bool dummy;
-  mFrameElement->DispatchEvent(event, &dummy);
-}
-
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
     static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(mCallbackId,
                                              holder->User(),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -176,17 +176,18 @@ public:
   virtual mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                                              PRenderFrameParent* aRenderFrame,
                                                              const nsString& aURL,
                                                              const nsString& aName,
                                                              const nsString& aFeatures,
                                                              bool* aOutWindowOpened,
                                                              TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                              uint64_t* aLayersId,
-                                                             CompositorOptions* aCompositorOptions) override;
+                                                             CompositorOptions* aCompositorOptions,
+                                                             uint32_t* aMaxTouchPoints) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSyncMessage(const nsString& aMessage,
                   const ClonedMessageData& aData,
                   InfallibleTArray<CpowEntry>&& aCpows,
                   const IPC::Principal& aPrincipal,
                   nsTArray<ipc::StructuredCloneData>* aRetVal) override;
 
@@ -310,18 +311,16 @@ public:
   virtual mozilla::ipc::IPCResult RecvHideTooltip() override;
 
   virtual mozilla::ipc::IPCResult RecvGetDPI(float* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvGetDefaultScale(double* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvGetWidgetRounding(int32_t* aValue) override;
 
-  virtual mozilla::ipc::IPCResult RecvGetMaxTouchPoints(uint32_t* aTouchPoints) override;
-
   virtual mozilla::ipc::IPCResult RecvGetWidgetNativeData(WindowsHandle* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvSetNativeChildOfShareableWindow(const uintptr_t& childWindow) override;
 
   virtual mozilla::ipc::IPCResult RecvDispatchFocusToTopLevelWindow() override;
 
   virtual mozilla::ipc::IPCResult RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId,
                                                              const bool& aStartSwipe) override;
@@ -582,18 +581,16 @@ public:
                           uint64_t* aLayersId);
 
   mozilla::ipc::IPCResult RecvEnsureLayersConnected(CompositorOptions* aCompositorOptions) override;
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
-  void DispatchTabChildNotReadyEvent();
-
 protected:
   bool ReceiveMessage(const nsString& aMessage,
                       bool aSync,
                       ipc::StructuredCloneData* aData,
                       mozilla::jsipc::CpowHolder* aCpows,
                       nsIPrincipal* aPrincipal,
                       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "FileBlockCache.h"
 #include "nsIObserverService.h"
 #include "nsISeekableStream.h"
 #include "nsIPrincipal.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
 #include <algorithm>
 
 namespace mozilla {
 
 #undef LOG
 #undef LOGI
 LazyLogModule gMediaCacheLog("MediaCache");
 #define LOG(...) MOZ_LOG(gMediaCacheLog, LogLevel::Debug, (__VA_ARGS__))
@@ -121,16 +122,27 @@ public:
   ~MediaCache() {
     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     Truncate();
     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     if (mFileCache) {
       mFileCache->Close();
       mFileCache = nullptr;
     }
+    LOG("MediaCache::~MediaCache(this=%p) MEDIACACHE_WATERMARK_KB=%u",
+        this, unsigned(mIndexWatermark * MediaCache::BLOCK_SIZE / 1024));
+    Telemetry::Accumulate(
+      Telemetry::HistogramID::MEDIACACHE_WATERMARK_KB,
+      uint32_t(mIndexWatermark * MediaCache::BLOCK_SIZE / 1024));
+    LOG("MediaCache::~MediaCache(this=%p) MEDIACACHE_BLOCKOWNERS_WATERMARK=%u",
+        this, unsigned(mBlockOwnersWatermark));
+    Telemetry::Accumulate(
+      Telemetry::HistogramID::MEDIACACHE_BLOCKOWNERS_WATERMARK,
+      mBlockOwnersWatermark);
+
     MOZ_COUNT_DTOR(MediaCache);
   }
 
   // Main thread only. Creates the backing cache file. If this fails,
   // then the cache is still in a semi-valid state; mFD will be null,
   // so all I/O on the cache file will fail.
   nsresult Init();
   // Shut down the global cache if it's no longer needed. We shut down
@@ -343,16 +355,20 @@ protected:
   // readers that need to block will Wait() on this monitor. When new
   // data becomes available in the cache, we NotifyAll() on this monitor.
   ReentrantMonitor         mReentrantMonitor;
   // This is only written while on the main thread and the monitor is held.
   // Thus, it can be safely read from the main thread or while holding the monitor.
   nsTArray<MediaCacheStream*> mStreams;
   // The Blocks describing the cache entries.
   nsTArray<Block> mIndex;
+  // Keep track for highest number of blocks used, for telemetry purposes.
+  int32_t mIndexWatermark = 0;
+  // Keep track for highest number of blocks owners, for telemetry purposes.
+  uint32_t mBlockOwnersWatermark = 0;
   // Writer which performs IO, asynchronously writing cache blocks.
   RefPtr<FileBlockCache> mFileCache;
   // The list of free blocks; they are not ordered.
   BlockList       mFreeBlocks;
   // True if an event to run Update() has been queued but not processed
   bool            mUpdateQueued;
 #ifdef DEBUG
   bool            mInUpdate;
@@ -715,16 +731,17 @@ MediaCache::FindBlockForIncomingData(Tim
     // b) the data we're going to store in the free block is not higher
     // priority than the data already stored in the free block.
     // The latter can lead us to go over the cache limit a bit.
     if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
          PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
       blockIndex = mIndex.Length();
       if (!mIndex.AppendElement())
         return -1;
+      mIndexWatermark = std::max(mIndexWatermark, blockIndex + 1);
       mFreeBlocks.AddFirstBlock(blockIndex);
       return blockIndex;
     }
   }
 
   return blockIndex;
 }
 
@@ -929,16 +946,18 @@ MediaCache::AddBlockOwnerAsReadahead(int
                                        MediaCacheStream* aStream,
                                        int32_t aStreamBlockIndex)
 {
   Block* block = &mIndex[aBlockIndex];
   if (block->mOwners.IsEmpty()) {
     mFreeBlocks.RemoveBlock(aBlockIndex);
   }
   BlockOwner* bo = block->mOwners.AppendElement();
+  mBlockOwnersWatermark =
+    std::max(mBlockOwnersWatermark, uint32_t(block->mOwners.Length()));
   bo->mStream = aStream;
   bo->mStreamBlock = aStreamBlockIndex;
   aStream->mBlocks[aStreamBlockIndex] = aBlockIndex;
   bo->mClass = READAHEAD_BLOCK;
   InsertReadaheadBlock(bo, aBlockIndex);
 }
 
 void
@@ -1526,16 +1545,18 @@ MediaCache::AllocateAndWriteBlock(
     ResourceStreamIterator iter(aStream->mResourceID);
     while (MediaCacheStream* stream = iter.Next()) {
       BlockOwner* bo = block->mOwners.AppendElement();
       if (!bo) {
         // Roll back mOwners if any allocation fails.
         block->mOwners.Clear();
         return;
       }
+      mBlockOwnersWatermark =
+        std::max(mBlockOwnersWatermark, uint32_t(block->mOwners.Length()));
       bo->mStream = stream;
     }
 
     if (block->mOwners.IsEmpty()) {
       // This happens when all streams with the resource id are closed. We can
       // just return here now and discard the data.
       return;
     }
--- a/dom/media/TextTrackList.cpp
+++ b/dom/media/TextTrackList.cpp
@@ -38,19 +38,22 @@ TextTrackList::TextTrackList(nsPIDOMWind
 
 TextTrackList::~TextTrackList()
 {
 }
 
 void
 TextTrackList::GetShowingCues(nsTArray<RefPtr<TextTrackCue> >& aCues)
 {
+  // Only Subtitles and Captions can show on the screen.
   nsTArray< RefPtr<TextTrackCue> > cues;
   for (uint32_t i = 0; i < Length(); i++) {
-    if (mTextTracks[i]->Mode() == TextTrackMode::Showing) {
+    if (mTextTracks[i]->Mode() == TextTrackMode::Showing &&
+        (mTextTracks[i]->Kind() == TextTrackKind::Subtitles ||
+         mTextTracks[i]->Kind() == TextTrackKind::Captions)) {
       mTextTracks[i]->GetActiveCueArray(cues);
       aCues.AppendElements(cues);
     }
   }
 }
 
 JSObject*
 TextTrackList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -604,17 +604,17 @@ OmxDataDecoder::FillCodecConfigDataToOmx
   MOZ_ASSERT(mOmxState == OMX_StateIdle || mOmxState == OMX_StateExecuting);
 
 
   RefPtr<BufferData> inbuf = FindAvailableBuffer(OMX_DirInput);
   RefPtr<MediaByteBuffer> csc;
   if (mTrackInfo->IsAudio()) {
     csc = mTrackInfo->GetAsAudioInfo()->mCodecSpecificConfig;
   } else if (mTrackInfo->IsVideo()) {
-    csc = mTrackInfo->GetAsVideoInfo()->mCodecSpecificConfig;
+    csc = mTrackInfo->GetAsVideoInfo()->mExtraData;
   }
 
   MOZ_RELEASE_ASSERT(csc);
 
   // Some codecs like h264, its codec specific data is at the first packet, not in container.
   if (csc->Length()) {
     memcpy(inbuf->mBuffer->pBuffer,
            csc->Elements(),
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -3,28 +3,25 @@
 /* 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 "BufferDecoder.h"
 
 #include "nsISupports.h"
 #include "MediaResource.h"
-#include "GMPCrashHelper.h"
 
 namespace mozilla {
 
 NS_IMPL_ISUPPORTS0(BufferDecoder)
 
 BufferDecoder::BufferDecoder(MediaResource* aResource,
-                             AbstractThread* aMainThread,
-                             GMPCrashHelper* aCrashHelper)
+                             AbstractThread* aMainThread)
   : mResource(aResource)
   , mAbstractMainThread(aMainThread)
-  , mCrashHelper(aCrashHelper)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 BufferDecoder::~BufferDecoder()
 {
   // The dtor may run on any thread, we cannot be sure.
 }
@@ -64,21 +61,15 @@ BufferDecoder::GetImageContainer()
 
 MediaDecoderOwner*
 BufferDecoder::GetOwner() const
 {
   // unknown
   return nullptr;
 }
 
-already_AddRefed<GMPCrashHelper>
-BufferDecoder::GetCrashHelper()
-{
-  return do_AddRef(mCrashHelper);
-}
-
 AbstractThread*
 BufferDecoder::AbstractMainThread() const
 {
   return mAbstractMainThread;
 }
 
 } // namespace mozilla
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -20,40 +20,36 @@ namespace mozilla {
  * a memory buffer.
  */
 class BufferDecoder final : public AbstractMediaDecoder
 {
 public:
   // This class holds a weak pointer to MediaResource.  It's the responsibility
   // of the caller to manage the memory of the MediaResource object.
   explicit BufferDecoder(MediaResource* aResource,
-                         AbstractThread* aMainThread,
-                         GMPCrashHelper* aCrashHelper);
+                         AbstractThread* aMainThread);
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // This has to be called before decoding begins
   void BeginDecoding(TaskQueue* aTaskQueueIdentity);
 
   MediaResource* GetResource() const final override;
 
   void NotifyDecodedFrames(const FrameStatisticsData& aStats) final override;
 
   VideoFrameContainer* GetVideoFrameContainer() final override;
   layers::ImageContainer* GetImageContainer() final override;
 
   MediaDecoderOwner* GetOwner() const final override;
 
-  already_AddRefed<GMPCrashHelper> GetCrashHelper() override;
-
   AbstractThread* AbstractMainThread() const final override;
 
 private:
   virtual ~BufferDecoder();
   RefPtr<TaskQueue> mTaskQueueIdentity;
   RefPtr<MediaResource> mResource;
   const RefPtr<AbstractThread> mAbstractMainThread;
-  RefPtr<GMPCrashHelper> mCrashHelper;
 };
 
 } // namespace mozilla
 
 #endif /* BUFFER_DECODER_H_ */
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -24,17 +24,16 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsMimeTypes.h"
 #include "VideoUtils.h"
 #include "WebAudioUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/Telemetry.h"
 #include "nsPrintfCString.h"
-#include "GMPCrashHelper.h"
 
 namespace mozilla {
 
 extern LazyLogModule gMediaDecoderLog;
 
 using namespace dom;
 
 class ReportResultTask final : public Runnable
@@ -161,34 +160,16 @@ MediaDecodeTask::Run()
     break;
   case PhaseEnum::Done:
     break;
   }
 
   return NS_OK;
 }
 
-class BufferDecoderGMPCrashHelper : public GMPCrashHelper
-{
-public:
-  explicit BufferDecoderGMPCrashHelper(nsPIDOMWindowInner* aParent)
-    : mParent(do_GetWeakReference(aParent))
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-  already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mParent);
-    return window.forget();
-  }
-private:
-  nsWeakPtr mParent;
-};
-
 bool
 MediaDecodeTask::CreateReader()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsPIDOMWindowInner* parent = mDecodeJob.mContext->GetParentObject();
   MOZ_ASSERT(parent);
 
@@ -200,18 +181,17 @@ MediaDecodeTask::CreateReader()
 
   RefPtr<BufferMediaResource> resource =
     new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
                             mLength, principal, mContainerType);
 
   MOZ_ASSERT(!mBufferDecoder);
   mMainThread =
     mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
-  mBufferDecoder = new BufferDecoder(resource, mMainThread,
-                                     new BufferDecoderGMPCrashHelper(parent));
+  mBufferDecoder = new BufferDecoder(resource, mMainThread);
 
   // If you change this list to add support for new decoders, please consider
   // updating HTMLMediaElement::CreateDecoder as well.
 
   mDecoderReader = DecoderTraits::CreateReader(mContainerType, mBufferDecoder);
 
   if (!mDecoderReader) {
     return false;
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -377,19 +377,19 @@ var interfaceNamesInGlobalScope =
     "GamepadAxisMoveEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadButtonEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadButton",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "GamepadEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "GamepadHapticActuator", release: false},
+    "GamepadHapticActuator",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "GamepadPose", release: false},
+    "GamepadPose",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HashChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Headers",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "History",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAllCollection",
@@ -1172,31 +1172,31 @@ var interfaceNamesInGlobalScope =
     "UserProximityEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ValidityState",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VideoPlaybackQuality",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VideoStreamTrack",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplay", release: false},
+    {name: "VRDisplay", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplayCapabilities", release: false},
+    {name: "VRDisplayCapabilities", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRDisplayEvent", release: false},
+    {name: "VRDisplayEvent", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VREyeParameters", release: false},
+    {name: "VREyeParameters", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRFieldOfView", release: false},
+    {name: "VRFieldOfView", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRFrameData", release: false},
+    {name: "VRFrameData", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRPose", release: false},
+    {name: "VRPose", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "VRStageParameters", release: false},
+    {name: "VRStageParameters", releaseNonWindows: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VTTCue",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VTTRegion", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WaveShaperNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "WebAuthnAssertion", disabled: true},
@@ -1317,16 +1317,17 @@ function createInterfaceMap(isXBLScope) 
       } else {
         ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
         if ((entry.nightly === !isNightly) ||
             (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
             (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && isAndroid) ||
             (entry.xbl === !isXBLScope) ||
             (entry.desktop === !isDesktop) ||
             (entry.windows === !isWindows) ||
+            (entry.releaseNonWindows === !isRelease && !isWindows) ||
             (entry.mac === !isMac) ||
             (entry.linux === !isLinux) ||
             (entry.android === !isAndroid && !entry.nonReleaseAndroid && !entry.nightlyAndroid) ||
             (entry.release === !isRelease) ||
             (entry.isSecureContext === !isSecureContext) ||
             entry.disabled) {
           interfaceMap[entry.name] = false;
         } else {
--- a/dom/tests/mochitest/whatwg/test_postMessage_special.xhtml
+++ b/dom/tests/mochitest/whatwg/test_postMessage_special.xhtml
@@ -15,16 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none"></div>
 
 <pre id="test">
 <script class="testbody" type="application/javascript"><![CDATA[
 /** Test for Bug 387706 **/
 
 SimpleTest.waitForExplicitFinish();
 
+var isDataUnique = SpecialPowers.Services.prefs.getBoolPref("security.data_uri.unique_opaque_origin");
 var B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
 /**
  * Encodes an array of bytes into a string using the base 64 encoding scheme.
  *
  * @param bytes
  *   An array of bytes to encode.
  */
@@ -135,19 +136,17 @@ function messageReceiver(evt)
       is(evt.origin, "http://mochi.test:8888",
          "wrong origin for event from about:blank #2");
       is(evt.source, aboutBlank2Window, "wrong source");
 
       setupData();
     }
     else if (evt.data === "data-response")
     {
-      // HTML5 defines the origin of a data: URI as the origin of the window or
-      // script that opened the data: URI.
-      is(evt.origin, "http://mochi.test:8888",
+      is(evt.origin, isDataUnique ? "null" : "http://mochi.test:8888",
          "wrong origin for event from data URL (should be the origin of the " +
          "window/script that opened the URL, in this case the origin of this " +
          "file)");
       is(evt.source, dataWindow, "wrong source");
 
       finish();
     }
     else
@@ -294,17 +293,17 @@ function testBlank2()
   script.textContent =
     "window.parent.postMessage('about:blank2-response', " +
     "                          'http://mochi.test:8888');";
   doc.body.appendChild(script);
 }
 
 function testData()
 {
-  dataWindow.postMessage("from-opener", "http://mochi.test:8888");
+  dataWindow.postMessage("from-opener", isDataUnique ? "*" : "http://mochi.test:8888");
 }
 
 window.addEventListener("message", messageReceiver);
 
 addLoadEvent(setupBlank);
 ]]></script>
 </pre>
 </body>
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1684,17 +1684,17 @@ public:
   NS_IMETHOD
   DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override
   {
     nsCOMPtr<nsIRunnable> runnable(aRunnable);
     return Dispatch(runnable.forget(), aFlags);
   }
 
   NS_IMETHOD
-  Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override
+  Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL) override
   {
     MutexAutoLock lock(mMutex);
 
     if (!mWorkerPrivate) {
       return NS_ERROR_FAILURE;
     }
 
     RefPtr<WorkerControlRunnable> r = new WrappedControlRunnable(mWorkerPrivate,
@@ -2101,17 +2101,17 @@ public:
   nsIEventTarget*
   GetWeakNestedEventTarget() const
   {
     MOZ_ASSERT(mWeakNestedEventTarget);
     return mWeakNestedEventTarget;
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIEVENTTARGET
+  NS_DECL_NSIEVENTTARGET_FULL
 
 private:
   ~EventTarget()
   { }
 };
 
 WorkerLoadInfo::
 InterfaceRequestor::InterfaceRequestor(nsIPrincipal* aPrincipal,
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1366176.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+	<script>
+
+	function go()
+	{
+		try { o114 = document.createElement('canvas'); } catch(e) {;}
+		try { o103 = (new DOMParser).parseFromString('/', 'text/html'); } catch(e) {;}
+		try { o217 = o103.all[1]; } catch(e) {;}
+		try { o253 = window.getSelection(); } catch(e) {;}
+
+		try { o270 = document.body.appendChild(document.createElement('a')); } catch(e) {;}
+
+		try { o301 = window.getSelection(); } catch(e) {;}
+		try { o314 = window.getSelection(); } catch(e) {;}
+		try { document.body.appendChild(o114); } catch(e) {;}
+		try { document.documentElement.appendChild(o217); } catch(e) {;}
+		try { document.body.appendChild(o270); } catch(e) {;}
+		try { o217.contentEditable = true } catch(e) {;}
+
+		try { o253.modify('move','forward','lineboundary'); } catch(e) {;}
+		try { o314.modify('extend','left','line'); } catch(e) {;}
+		try { o301.modify('extend','right','line'); } catch(e) {;}
+	}
+	</script>
+</head>
+<body onload="go();">
+	<plaintext></plaintext>
+</bdoy>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -69,8 +69,9 @@ load 1158651.html
 load 1244894.xhtml
 load 1264921.html
 load 1272490.html
 load 1317704.html
 load 1317718.html
 load 1324505.html
 load 1348851.html
 load 1350772.html
+load 1366176.html
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -411,16 +411,25 @@ public:
 
   const ScrollThumbData& GetScrollThumbData() const
   {
     MOZ_ASSERT(IsValid());
 
     return mLayer->GetScrollThumbData();
   }
 
+  uint64_t GetScrollbarAnimationId() const
+  {
+    MOZ_ASSERT(IsValid());
+    // This function is only really needed for template-compatibility with
+    // WebRenderScrollDataWrapper. Although it will be called, the return
+    // value is not used.
+    return 0;
+  }
+
   FrameMetrics::ViewID GetScrollbarTargetContainerId() const
   {
     MOZ_ASSERT(IsValid());
 
     return mLayer->GetScrollbarTargetContainerId();
   }
 
   bool IsScrollbarContainer() const
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -200,24 +200,30 @@ Layer::Layer(LayerManager* aManager, voi
   mAnimationGeneration(0)
 {
 }
 
 Layer::~Layer()
 {
 }
 
+void
+Layer::EnsureAnimationsId()
+{
+  if (!mCompositorAnimationsId) {
+    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
+  }
+}
+
 Animation*
 Layer::AddAnimation()
 {
   // Here generates a new id when the first animation is added and
   // this id is used to represent the animations in this layer.
-  if (!mCompositorAnimationsId) {
-    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
-  }
+  EnsureAnimationsId();
 
   MOZ_LAYERS_LOG_IF_SHADOWABLE(
     this, ("Layer::Mutated(%p) AddAnimation with id=%" PRIu64, this, mCompositorAnimationsId));
 
   MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
 
   Animation* anim = mAnimations.AppendElement();
 
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1218,16 +1218,19 @@ public:
   void SetTransformIsPerspective(bool aTransformIsPerspective)
   {
     if (mSimpleAttrs.SetTransformIsPerspective(aTransformIsPerspective)) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) TransformIsPerspective", this));
       MutatedSimple();
     }
   }
 
+  // Ensure that this layer has a valid (non-zero) animations id. This value is
+  // unique across layers.
+  void EnsureAnimationsId();
   // Call AddAnimation to add a new animation to this layer from layout code.
   // Caller must fill in all the properties of the returned animation.
   // A later animation overrides an earlier one.
   Animation* AddAnimation();
   // ClearAnimations clears animations on this layer.
   virtual void ClearAnimations();
   // This is only called when the layer tree is updated. Do not call this from
   // layout code.  To add an animation to this layer, use AddAnimation.
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -362,23 +362,31 @@ APZCTreeManager::UpdateHitTestingTree(ui
 {
   WebRenderScrollDataWrapper wrapper(&aScrollData);
   UpdateHitTestingTreeImpl(aRootLayerTreeId, wrapper, aIsFirstPaint,
                            aOriginatingLayersId, aPaintSequenceNumber);
 }
 
 bool
 APZCTreeManager::PushStateToWR(wr::WebRenderAPI* aWrApi,
-                               const TimeStamp& aSampleTime)
+                               const TimeStamp& aSampleTime,
+                               nsTArray<WrTransformProperty>& aTransformArray)
 {
   APZThreadUtils::AssertOnCompositorThread();
   MOZ_ASSERT(aWrApi);
 
   MutexAutoLock lock(mTreeLock);
 
+  // During the first pass through the tree, we build a cache of guid->HTTN so
+  // that we can find the relevant APZC instances quickly in subsequent passes,
+  // such as the one below to generate scrollbar transforms. Without this, perf
+  // could end up being O(n^2) instead of O(n log n) because we'd have to search
+  // the tree to find the corresponding APZC every time we hit a thumb node.
+  std::map<ScrollableLayerGuid, HitTestingTreeNode*> httnMap;
+
   bool activeAnimations = false;
   uint64_t lastLayersId = -1;
   WrPipelineId lastPipelineId;
 
   // We iterate backwards here because the HitTestingTreeNode is optimized
   // for backwards iteration. The equivalent code in AsyncCompositionManager
   // iterates forwards, but the direction shouldn't really matter in practice
   // so we do what's faster. In the future, if we need to start doing the
@@ -391,36 +399,85 @@ APZCTreeManager::PushStateToWR(wr::WebRe
           return;
         }
         AsyncPanZoomController* apzc = aNode->GetApzc();
         MOZ_ASSERT(apzc);
 
         if (aNode->GetLayersId() != lastLayersId) {
           // If we walked into or out of a subtree, we need to get the new
           // pipeline id.
+          const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aNode->GetLayersId());
+          if (!(state && state->mWrBridge)) {
+            // During shutdown we might have layer tree information for stuff
+            // that has already been torn down. In that case just skip over
+            // those layers.
+            return;
+          }
+          lastPipelineId = state->mWrBridge->PipelineId();
           lastLayersId = aNode->GetLayersId();
-          const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(lastLayersId);
-          MOZ_ASSERT(state && state->mWrBridge);
-          lastPipelineId = state->mWrBridge->PipelineId();
         }
 
+        // Use a 0 presShellId because when we do a lookup in this map for the
+        // scrollbar below we don't have (or care about) the presShellId.
+        ScrollableLayerGuid guid(lastLayersId, 0, apzc->GetGuid().mScrollId);
+        httnMap.emplace(guid, aNode);
+
         ParentLayerPoint layerTranslation = apzc->GetCurrentAsyncTransform(
             AsyncPanZoomController::RESPECT_FORCE_DISABLE).mTranslation;
         // The positive translation means the painted content is supposed to
         // move down (or to the right), and that corresponds to a reduction in
         // the scroll offset. Since we are effectively giving WR the async
         // scroll delta here, we want to negate the translation.
         ParentLayerPoint asyncScrollDelta = -layerTranslation;
         aWrApi->UpdateScrollPosition(lastPipelineId, apzc->GetGuid().mScrollId,
             wr::ToWrPoint(asyncScrollDelta));
 
         apzc->ReportCheckerboard(aSampleTime);
         activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
       });
 
+  // Now we iterate over the nodes again, and generate the transforms needed
+  // for scrollbar thumbs. Although we *could* do this as part of the previous
+  // iteration, it's cleaner and more efficient to do it as a separate pass
+  // because now we have a populated httnMap which allows O(log n) lookup here,
+  // resulting in O(n log n) runtime.
+  ForEachNode<ReverseIterator>(mRootNode.get(),
+      [&](HitTestingTreeNode* aNode)
+      {
+        if (!aNode->IsScrollThumbNode()) {
+          return;
+        }
+        ScrollableLayerGuid guid(aNode->GetLayersId(), 0, aNode->GetScrollTargetId());
+        auto it = httnMap.find(guid);
+        if (it == httnMap.end()) {
+          // A scrollbar for content which didn't have an APZC. Possibly the
+          // content isn't layerized. Regardless, we can't async-scroll it so
+          // we can skip the async transform on the scrollbar.
+          return;
+        }
+
+        HitTestingTreeNode* scrollTargetNode = it->second;
+        AsyncPanZoomController* scrollTargetApzc = scrollTargetNode->GetApzc();
+        MOZ_ASSERT(scrollTargetApzc);
+        LayerToParentLayerMatrix4x4 transform = scrollTargetApzc->CallWithLastContentPaintMetrics(
+            [&](const FrameMetrics& aMetrics) {
+                return AsyncCompositionManager::ComputeTransformForScrollThumb(
+                    aNode->GetTransform() * AsyncTransformMatrix(),
+                    scrollTargetNode->GetTransform().ToUnknownMatrix(),
+                    scrollTargetApzc,
+                    aMetrics,
+                    aNode->GetScrollThumbData(),
+                    scrollTargetNode->IsAncestorOf(aNode),
+                    nullptr);
+            });
+        aTransformArray.AppendElement(wr::ToWrTransformProperty(
+            aNode->GetScrollbarAnimationId(),
+            transform));
+      });
+
   return activeAnimations;
 }
 
 // Compute the clip region to be used for a layer with an APZC. This function
 // is only called for layers which actually have scrollable metrics and an APZC.
 template<class ScrollNode> static ParentLayerIntRegion
 ComputeClipRegion(GeckoContentController* aController,
                   const ScrollNode& aLayer)
@@ -568,16 +625,17 @@ APZCTreeManager::PrepareNodeForLayer(con
     node = RecycleOrCreateNode(aState, nullptr, aLayersId);
     AttachNodeToTree(node, aParent, aNextSibling);
     node->SetHitTestData(
         GetEventRegions(aLayer),
         aLayer.GetTransformTyped(),
         aLayer.GetClipRect() ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) : Nothing(),
         GetEventRegionsOverride(aParent, aLayer));
     node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
+                           aLayer.GetScrollbarAnimationId(),
                            aLayer.GetScrollThumbData(),
                            aLayer.IsScrollbarContainer());
     node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
     return node;
   }
 
   AsyncPanZoomController* apzc = nullptr;
   // If we get here, aLayer is a scrollable layer and somebody
@@ -757,16 +815,17 @@ APZCTreeManager::PrepareNodeForLayer(con
         Some(clipRegion),
         GetEventRegionsOverride(aParent, aLayer));
   }
 
   // Note: if layer properties must be propagated to nodes, RecvUpdate in
   // LayerTransactionParent.cpp must ensure that APZ will be notified
   // when those properties change.
   node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
+                         aLayer.GetScrollbarAnimationId(),
                          aLayer.GetScrollThumbData(),
                          aLayer.IsScrollbarContainer());
   node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
   return node;
 }
 
 template<typename PanGestureOrScrollWheelInput>
 static bool
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -18,16 +18,17 @@
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "nsCOMPtr.h"                   // for already_AddRefed
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
+struct WrTransformProperty;
 
 namespace mozilla {
 class MultiTouchInput;
 
 namespace wr {
 class WebRenderAPI;
 }
 
@@ -148,22 +149,25 @@ public:
                             uint64_t aOriginatingLayersId,
                             uint32_t aPaintSequenceNumber);
 
   /**
    * Called when webrender is enabled, from the compositor thread. This function
    * walks through the tree of APZC instances and tells webrender about the
    * async scroll position. It also advances APZ animations to the specified
    * sample time. In effect it is the webrender equivalent of (part of) the
-   * code in AsyncCompositionManager.
+   * code in AsyncCompositionManager. If scrollbar transforms need updating
+   * to reflect the async scroll position, the updated transforms are appended
+   * to the provided aTransformArray.
    * Returns true if any APZ animations are in progress and we need to keep
    * compositing.
    */
   bool PushStateToWR(wr::WebRenderAPI* aWrApi,
-                     const TimeStamp& aSampleTime);
+                     const TimeStamp& aSampleTime,
+                     nsTArray<WrTransformProperty>& aTransformArray);
 
   /**
    * Walk the tree of APZCs and flushes the repaint requests for all the APZCS
    * corresponding to the given layers id. Finally, sends a flush complete
    * notification to the GeckoContentController for the layers id.
    */
   void FlushApzRepaints(uint64_t aLayersId);
 
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -32,16 +32,22 @@ static const uint32_t MAX_TAP_TIME = 300
 /**
  * Amount of span or focus change needed to take us from the GESTURE_WAITING_PINCH
  * state to the GESTURE_PINCH state. This is measured as either a change in distance
  * between the fingers used to compute the span ratio, or the a change in
  * position of the focus point between the two fingers.
  */
 static const float PINCH_START_THRESHOLD = 35.0f;
 
+/**
+ * Determines how fast a one touch pinch zooms in and out. The greater the
+ * value, the faster it zooms.
+ */
+static const float ONE_TOUCH_PINCH_SPEED = 0.005f;
+
 static bool sLongTapEnabled = true;
 
 ParentLayerPoint GetCurrentFocus(const MultiTouchInput& aEvent)
 {
   const ParentLayerPoint& firstTouch = aEvent.mTouches[0].mLocalScreenPoint;
   const ParentLayerPoint& secondTouch = aEvent.mTouches[1].mLocalScreenPoint;
   return (firstTouch + secondTouch) / 2;
 }
@@ -49,16 +55,23 @@ ParentLayerPoint GetCurrentFocus(const M
 ParentLayerCoord GetCurrentSpan(const MultiTouchInput& aEvent)
 {
   const ParentLayerPoint& firstTouch = aEvent.mTouches[0].mLocalScreenPoint;
   const ParentLayerPoint& secondTouch = aEvent.mTouches[1].mLocalScreenPoint;
   ParentLayerPoint delta = secondTouch - firstTouch;
   return delta.Length();
 }
 
+ParentLayerCoord GestureEventListener::GetYSpanFromStartPoint()
+{
+  const ParentLayerPoint start = mTouchStartPosition;
+  const ParentLayerPoint& current = mTouches[0].mLocalScreenPoint;
+  return current.y - start.y;
+}
+
 TapGestureInput CreateTapEvent(const MultiTouchInput& aTouch, TapGestureInput::TapGestureType aType)
 {
   return TapGestureInput(aType,
                          aTouch.mTime,
                          aTouch.mTimeStamp,
                          aTouch.mTouches[0].mScreenPoint,
                          aTouch.modifiers);
 }
@@ -88,38 +101,41 @@ nsEventStatus GestureEventListener::Hand
 
   // Cache the current event since it may become the single or long tap that we
   // send.
   mLastTouchInput = aEvent;
 
   switch (aEvent.mType) {
   case MultiTouchInput::MULTITOUCH_START:
     mTouches.Clear();
+    // Cache every touch.
     for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
       mTouches.AppendElement(aEvent.mTouches[i]);
     }