Merge review head draft
authorGregory Szorc <gps@mozilla.com>
Fri, 24 Mar 2017 15:31:46 -0700
changeset 547114 d192b72ced03f19eebeb2a8ffa3d58934a924d16
parent 547113 b125eb05f16ed1d7df7ddf504ab6b19fa8e7e807 (diff)
parent 489906 3b72333857874b3b6d03d261d4d6248ece209383 (current diff)
child 547115 dae36946edca28c0b61e4f286f09c0ee56a1ac60
push id50970
push usergszorc@mozilla.com
push dateFri, 24 Mar 2017 22:35:20 +0000
milestone55.0a1
Merge review head
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -1,12 +1,18 @@
 ^build/clang-plugin/tests/.*
 ^config/gcc-stl-wrapper.template.h
 ^config/msvc-stl-wrapper.template.h
 ^js/src/jsapi-tests/.*
+^widget/android/GeneratedJNINatives.h
+^widget/android/GeneratedJNIWrappers.cpp
+^widget/android/GeneratedJNIWrappers.h
+^widget/android/fennec/FennecJNINatives.h
+^widget/android/fennec/FennecJNIWrappers.cpp
+^widget/android/fennec/FennecJNIWrappers.h
 
 # Generated from ./tools/rewriting/ThirdPartyPaths.txt
 # awk '{print "^"$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
 ^browser/components/translation/cld2/.*
 ^build/stlport/.*
 ^db/sqlite3/src/.*
 ^dom/media/platforms/ffmpeg/libav.*
 ^extensions/spellcheck/hunspell/src/.*
--- a/.eslintignore
+++ b/.eslintignore
@@ -50,28 +50,28 @@ xulrunner/**
 # b2g exclusions (pref files).
 b2g/app/b2g.js
 b2g/graphene/graphene.js
 b2g/locales/en-US/b2g-l10n.js
 
 # browser/ exclusions
 browser/app/**
 browser/branding/**/firefox-branding.js
-browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/general/file_csp_block_all_mixedcontent.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 browser/base/content/newtab/**
 browser/components/downloads/**
-browser/components/privatebrowsing/**
-browser/components/sessionstore/**
+# Test files that are really json not js, and don't need to be linted.
+browser/components/sessionstore/test/unit/data/sessionstore_valid.js
+browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
 browser/components/tabview/**
-# generated files in cld2
-browser/components/translation/cld2/cld-worker.js
+# generated & special files in cld2
+browser/components/translation/cld2/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # imported from chromium
 browser/extensions/mortar/**
@@ -82,22 +82,21 @@ devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/selection.js
 !devtools/client/framework/target*
 !devtools/client/framework/toolbox*
 devtools/client/inspector/markup/test/doc_markup_events_*.html
 devtools/client/inspector/rules/test/doc_media_queries.html
 devtools/client/memory/test/chrome/*.html
-devtools/client/netmonitor/test/**
-devtools/client/netmonitor/har/test/**
 devtools/client/performance/components/test/test_jit_optimizations_01.html
 devtools/client/projecteditor/**
 devtools/client/responsive.html/test/browser/touch.html
 devtools/client/responsivedesign/**
+!devtools/client/responsivedesign/responsivedesign.jsm
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/*.jsm
 devtools/client/shared/components/reps/reps.js
 devtools/client/shared/components/reps/test/mochitest/*.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
@@ -127,19 +126,17 @@ devtools/client/webide/**
 devtools/server/actors/webconsole.js
 devtools/server/actors/object.js
 devtools/server/actors/script.js
 devtools/server/actors/styleeditor.js
 devtools/server/actors/stylesheets.js
 devtools/server/tests/browser/storage-*.html
 !devtools/server/tests/browser/storage-unsecured-iframe.html
 devtools/server/tests/browser/stylesheets-nested-iframes.html
-devtools/server/tests/mochitest/**
-devtools/server/tests/unit/**
-devtools/shared/heapsnapshot/**
+devtools/server/tests/unit/xpcshell_debugging_script.js
 devtools/shared/platform/content/test/test_clipboard.html
 devtools/shared/qrcode/tests/mochitest/test_decode.html
 devtools/shared/tests/mochitest/*.html
 devtools/shared/webconsole/test/test_*.html
 
 # Ignore devtools pre-processed files
 devtools/client/framework/toolbox-process-window.js
 devtools/client/performance/system.js
@@ -152,16 +149,17 @@ devtools/shared/acorn/*
 devtools/shared/gcli/source/*
 devtools/shared/node-properties/*
 devtools/shared/pretty-fast/*
 devtools/shared/sourcemap/*
 devtools/shared/sprintfjs/*
 devtools/shared/qrcode/decoder/*
 devtools/shared/qrcode/encoder/*
 devtools/client/shared/demangle.js
+devtools/client/shared/source-map/*
 devtools/client/shared/vendor/*
 devtools/client/sourceeditor/codemirror/*.js
 devtools/client/sourceeditor/codemirror/**/*.js
 devtools/client/sourceeditor/tern/*
 devtools/client/sourceeditor/test/cm_mode_ruby.js
 devtools/client/sourceeditor/test/codemirror/*
 devtools/client/inspector/markup/test/lib_*
 devtools/client/jsonview/lib/require.js
@@ -171,16 +169,17 @@ devtools/server/actors/utils/automation-
 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/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/**
 dom/audiochannel/**
 dom/base/**
 dom/battery/**
@@ -286,16 +285,17 @@ security/manager/ssl/security-prefs.js
 
 #NSS
 security/nss/**
 
 # services/ exclusions
 
 # Uses `#filter substitution`
 services/sync/modules/constants.js
+services/sync/services-sync.js
 
 # Third party services
 services/common/kinto-http-client.js
 services/common/kinto-offline-client.js
 services/sync/tps/extensions/mozmill
 
 # toolkit/ exclusions
 
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,20 +2,22 @@
 
 module.exports = {
   // When adding items to this file please check for effects on sub-directories.
   "plugins": [
     "mozilla"
   ],
   "rules": {
     "mozilla/avoid-removeChild": "error",
+    "mozilla/avoid-nsISupportsString-preferences": "error",
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
     "mozilla/no-useless-parameters": "error",
     "mozilla/no-useless-removeEventListener": "error",
+    "mozilla/use-default-preference-values": "error",
     "mozilla/use-ownerGlobal": "error",
 
     // No (!foo in bar) or (!object instanceof Class)
     "no-unsafe-negation": "error",
   },
   "env": {
     "es6": true
   },
--- a/.hgtags
+++ b/.hgtags
@@ -126,8 +126,9 @@ 99137d6d4061f408ae0869122649d8bdf489cc30
 67c66c2878aed17ae3096d7db483ddbb2293c503 FIREFOX_AURORA_46_BASE
 68d3781deda0d4d58ec9877862830db89669b3a5 FIREFOX_AURORA_47_BASE
 1c6385ae1fe7e37d8f23f958ce14582f07af729e FIREFOX_AURORA_48_BASE
 d98f20c25feeac4dd7ebbd1c022957df1ef58af4 FIREFOX_AURORA_49_BASE
 465d150bc8be5bbf9f02a8607d4552b6a5e1697c FIREFOX_AURORA_50_BASE
 fc69febcbf6c0dcc4b3dfc7a346d8d348798a65f FIREFOX_AURORA_51_BASE
 1196bf3032e1bce1fb07a01fd9082a767426c5fb FIREFOX_AURORA_52_BASE
 f80dc9fc34680105b714a49b4704bb843f5f7004 FIREFOX_AURORA_53_BASE
+6583496f169cd8a13c531ed16e98e8bf313eda8e FIREFOX_AURORA_54_BASE
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -80,17 +80,17 @@ tasks:
           level-{{level}}-checkouts: /home/worker/checkouts
 
         features:
           taskclusterProxy: true
           chainOfTrust: true
 
         # Note: This task is built server side without the context or tooling that
         # exist in tree so we must hard code the hash
-        image: 'taskcluster/decision@sha256:0f59f922d86c471e208b7ea08ab077fc68c3920ed5e6895d69a23e8f3457dc24'
+        image: 'taskcluster/decision:0.1.8@sha256:195d8439c8e90d59311d877bd2a8964849b2e43bfc6c234092618518d8b2891b'
 
         maxRunTime: 1800
 
         # TODO use mozilla-unified for the base repository once the tc-vcs
         # tar.gz archives are created or tc-vcs isn't being used.
         command:
           - /home/worker/bin/run-task
           - '--vcs-checkout=/home/worker/checkouts/gecko'
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Touching clobber for Telemetry IPC refactor in bug 1339749.
+Bug 1343682 - backing out a previous version didn't stop the failures from it, so it appears to need a clobber both out and in
--- a/README.txt
+++ b/README.txt
@@ -1,27 +1,27 @@
 An explanation of the Mozilla Source Code Directory Structure and links to
 project pages with documentation can be found at:
 
     https://developer.mozilla.org/en/Mozilla_Source_Code_Directory_Structure
 
 For information on how to build Mozilla from the source code, see:
 
-    http://developer.mozilla.org/en/docs/Build_Documentation
+    https://developer.mozilla.org/en/docs/Build_Documentation
 
 To have your bug fix / feature added to Mozilla, you should create a patch and
 submit it to Bugzilla (https://bugzilla.mozilla.org). Instructions are at:
 
-    http://developer.mozilla.org/en/docs/Creating_a_patch
-    http://developer.mozilla.org/en/docs/Getting_your_patch_in_the_tree
+    https://developer.mozilla.org/en/docs/Creating_a_patch
+    https://developer.mozilla.org/en/docs/Getting_your_patch_in_the_tree
 
 If you have a question about developing Mozilla, and can't find the solution
-on http://developer.mozilla.org, you can try asking your question in a
+on https://developer.mozilla.org, you can try asking your question in a
 mozilla.* Usenet group, or on IRC at irc.mozilla.org. [The Mozilla news groups
 are accessible on Google Groups, or news.mozilla.org with a NNTP reader.]
 
 You can download nightly development builds from the Mozilla FTP server.
 Keep in mind that nightly builds, which are used by Mozilla developers for
 testing, may be buggy. Firefox nightlies, for example, can be found at:
 
     https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/
             - or -
-    http://nightly.mozilla.org/
+    https://nightly.mozilla.org/
--- a/accessible/aom/AccessibleNode.cpp
+++ b/accessible/aom/AccessibleNode.cpp
@@ -149,25 +149,27 @@ AccessibleNode::Has(const Sequence<nsStr
 
 void
 AccessibleNode::Get(JSContext* aCX, const nsAString& aAttribute,
                     JS::MutableHandle<JS::Value> aValue,
                     ErrorResult& aRv)
 {
   if (!mIntl) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
   }
 
   nsCOMPtr<nsIPersistentProperties> attrs = mIntl->Attributes();
   nsAutoString value;
   attrs->GetStringProperty(NS_ConvertUTF16toUTF8(aAttribute), value);
 
   JS::Rooted<JS::Value> jsval(aCX);
   if (!ToJSValue(aCX, value, &jsval)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
+    return;
   }
 
   aValue.set(jsval);
 }
 
 nsINode*
 AccessibleNode::GetDOMNode()
 {
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -24,17 +24,16 @@
 #include "mozilla/a11y/Platform.h"
 #include "Relation.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "nsISimpleEnumerator.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Sprintf.h"
-#include "nsXPCOMStrings.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIPersistentProperties2.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 MaiAtkObject::EAvailableAtkSignals MaiAtkObject::gAvailableAtkSignals =
   eUnknown;
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -324,30 +324,37 @@ NotificationController::CoalesceMutation
           AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
 
           // We want to make sure that a reorder event comes after any show or
           // hide events targeted at the children of its target.  We keep the
           // invariant that event generation goes up as you are farther in the
           // queue, so we want to use the spot of the event with the higher
           // generation number, and keep that generation number.
           if (reorder && reorder->EventGeneration() < event->EventGeneration()) {
-            // There really should be a show or hide event before the first
-            // reorder event.
-            if (reorder->PrevEvent()) {
-              reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
-            } else {
-              mFirstMutationEvent = reorder->NextEvent();
+            reorder->SetEventGeneration(event->EventGeneration());
+
+            // It may be true that reorder was before event, and we coalesced
+            // away all the show / hide events between them.  In that case
+            // event is already immediately after reorder in the queue and we
+            // do not need to rearrange the list of events.
+            if (event != reorder->NextEvent()) {
+              // There really should be a show or hide event before the first
+              // reorder event.
+              if (reorder->PrevEvent()) {
+                reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
+              } else {
+                mFirstMutationEvent = reorder->NextEvent();
+              }
+
+              reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
+              event->PrevEvent()->SetNextEvent(reorder);
+              reorder->SetPrevEvent(event->PrevEvent());
+              event->SetPrevEvent(reorder);
+              reorder->SetNextEvent(event);
             }
-
-            reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
-            event->PrevEvent()->SetNextEvent(reorder);
-            reorder->SetPrevEvent(event->PrevEvent());
-            event->SetPrevEvent(reorder);
-            reorder->SetNextEvent(event);
-            reorder->SetEventGeneration(event->EventGeneration());
           }
           DropMutationEvent(event);
           break;
         }
 
         acc = parent;
       }
     } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
@@ -743,18 +750,21 @@ NotificationController::WillRefresh(mozi
 
         outerDocAcc->RemoveChild(childDoc);
       }
 
       // Failed to bind the child document, destroy it.
       childDoc->Shutdown();
     }
   }
-
   mHangingChildDocuments.Clear();
+  MOZ_ASSERT(mDocument, "Illicit document shutdown");
+  if (!mDocument) {
+    return;
+  }
 
   // If the document is ready and all its subdocuments are completely loaded
   // then process the document load.
   if (mDocument->HasLoadState(DocAccessible::eReady) &&
       !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
       hangingDocCnt == 0) {
     uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
     for (; childDocIdx < childDocCnt; childDocIdx++) {
--- a/accessible/base/SelectionManager.h
+++ b/accessible/base/SelectionManager.h
@@ -116,17 +116,17 @@ protected:
 
   /**
    * Process DOM selection change. Fire selection and caret move events.
    */
   void ProcessSelectionChanged(SelData* aSelData);
 
 private:
   // Currently focused control.
-  nsWeakFrame mCurrCtrlFrame;
+  WeakFrame mCurrCtrlFrame;
   int32_t mCaretOffset;
   HyperTextAccessible* mAccWithCaret;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/base/TextAttrs.cpp
+++ b/accessible/base/TextAttrs.cpp
@@ -13,20 +13,16 @@
 #include "gfxFont.h"
 #include "nsFontMetrics.h"
 #include "nsLayoutUtils.h"
 #include "nsContainerFrame.h"
 #include "HyperTextAccessible.h"
 #include "mozilla/AppUnits.h"
 #include "mozilla/gfx/2D.h"
 
-#if defined(MOZ_WIDGET_GTK)
-#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
-#endif
-
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // TextAttrsMgr
 ////////////////////////////////////////////////////////////////////////////////
 
 void
@@ -647,38 +643,24 @@ TextAttrsMgr::FontWeightTextAttr::
   // When there doesn't exist a bold font in the family and so the rendering of
   // a non-bold font face is changed so that the user sees what looks like a
   // bold font, i.e. synthetic bolding is used. IsSyntheticBold method is only
   // needed on Mac, but it is "safe" to use on all platforms.  (For non-Mac
   // platforms it always return false.)
   if (font->IsSyntheticBold())
     return 700;
 
-  bool useFontEntryWeight = true;
-
-  // Under Linux, when gfxPangoFontGroup code is used,
-  // font->GetStyle()->weight will give the absolute weight requested of the
-  // font face. The gfxPangoFontGroup code uses the gfxFontEntry constructor
-  // which doesn't initialize the weight field.
-#if defined(MOZ_WIDGET_GTK)
-  useFontEntryWeight = gfxPlatformGtk::UseFcFontList();
-#endif
-
-  if (useFontEntryWeight) {
-    // On Windows, font->GetStyle()->weight will give the same weight as
-    // fontEntry->Weight(), the weight of the first font in the font group,
-    // which may not be the weight of the font face used to render the
-    // characters. On Mac, font->GetStyle()->weight will just give the same
-    // number as getComputedStyle(). fontEntry->Weight() will give the weight
-    // of the font face used.
-    gfxFontEntry *fontEntry = font->GetFontEntry();
-    return fontEntry->Weight();
-  } else {
-    return font->GetStyle()->weight;
-  }
+  // On Windows, font->GetStyle()->weight will give the same weight as
+  // fontEntry->Weight(), the weight of the first font in the font group,
+  // which may not be the weight of the font face used to render the
+  // characters. On Mac, font->GetStyle()->weight will just give the same
+  // number as getComputedStyle(). fontEntry->Weight() will give the weight
+  // of the font face used.
+  gfxFontEntry *fontEntry = font->GetFontEntry();
+  return fontEntry->Weight();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // AutoGeneratedTextAttr
 ////////////////////////////////////////////////////////////////////////////////
 TextAttrsMgr::AutoGeneratedTextAttr::
   AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
                         Accessible* aAccessible) :
--- a/accessible/base/nsAccCache.h
+++ b/accessible/base/nsAccCache.h
@@ -20,26 +20,9 @@ UnbindCacheEntriesFromDocument(
   for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
     T* accessible = iter.Data();
     MOZ_ASSERT(accessible && !accessible->IsDefunct());
     accessible->Document()->UnbindFromDocument(accessible);
     iter.Remove();
   }
 }
 
-/**
- * Clear the cache and shutdown the accessibles.
- */
-template <class T>
-static void
-ClearCache(nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache)
-{
-  for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
-    T* accessible = iter.Data();
-    MOZ_ASSERT(accessible);
-    if (accessible && !accessible->IsDefunct()) {
-      accessible->Shutdown();
-    }
-    iter.Remove();
-  }
-}
-
 #endif
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -312,26 +312,23 @@ nsAccessibilityService::ListenersChanged
           listenerName != nsGkAtoms::onmousedown &&
           listenerName != nsGkAtoms::onmouseup) {
         continue;
       }
 
       nsIDocument* ownerDoc = node->OwnerDoc();
       DocAccessible* document = GetExistingDocAccessible(ownerDoc);
 
-      // Always recreate for onclick changes.
-      if (document) {
-        if (nsCoreUtils::HasClickListener(node)) {
-          if (!document->GetAccessible(node)) {
-            document->RecreateAccessible(node);
-          }
-        } else {
-          if (document->GetAccessible(node)) {
-            document->RecreateAccessible(node);
-          }
+      // Create an accessible for a inaccessible element having click event
+      // handler.
+      if (document && !document->HasAccessible(node) &&
+          nsCoreUtils::HasClickListener(node)) {
+        nsIContent* parentEl = node->GetFlattenedTreeParent();
+        if (parentEl) {
+          document->ContentInserted(parentEl, node, node->GetNextSibling());
         }
         break;
       }
     }
   }
   return NS_OK;
 }
 
@@ -593,16 +590,17 @@ nsAccessibilityService::ContentRemoved(n
     if (!child) {
       Accessible* container = document->GetContainerAccessible(aChildNode);
       a11y::TreeWalker walker(container ? container : document, aChildNode,
                               a11y::TreeWalker::eWalkCache);
       child = walker.Next();
     }
 
     if (child) {
+      MOZ_DIAGNOSTIC_ASSERT(child->Parent(), "Unattached accessible from tree");
       document->ContentRemoved(child->Parent(), aChildNode);
 #ifdef A11Y_LOG
       if (logging::IsEnabled(logging::eTree))
         logging::AccessibleNNode("real container", child->Parent());
 #endif
     }
   }
 
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -106,17 +106,17 @@ nsCoreUtils::DispatchClickEvent(nsITreeB
 
   int32_t tcX = 0;
   tcBoxObj->GetX(&tcX);
 
   int32_t tcY = 0;
   tcBoxObj->GetY(&tcY);
 
   // Dispatch mouse events.
-  nsWeakFrame tcFrame = tcContent->GetPrimaryFrame();
+  AutoWeakFrame tcFrame = tcContent->GetPrimaryFrame();
   nsIFrame* rootFrame = presShell->GetRootFrame();
 
   nsPoint offset;
   nsIWidget *rootWidget =
     rootFrame->GetView()->GetNearestWidget(&offset);
 
   RefPtr<nsPresContext> presContext = presShell->GetPresContext();
 
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -1825,17 +1825,17 @@ Accessible::DispatchClickEvent(nsIConten
   nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell();
 
   // Scroll into view.
   presShell->ScrollContentIntoView(aContent,
                                    nsIPresShell::ScrollAxis(),
                                    nsIPresShell::ScrollAxis(),
                                    nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
 
-  nsWeakFrame frame = aContent->GetPrimaryFrame();
+  AutoWeakFrame frame = aContent->GetPrimaryFrame();
   if (!frame)
     return;
 
   // Compute x and y coordinates.
   nsPoint point;
   nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
   if (!widget)
     return;
@@ -2126,24 +2126,22 @@ Accessible::InsertChildAt(uint32_t aInde
 
   aChild->BindToParent(this, aIndex);
   return true;
 }
 
 bool
 Accessible::RemoveChild(Accessible* aChild)
 {
-  if (!aChild)
-    return false;
-
-  if (aChild->mParent != this || aChild->mIndexInParent == -1)
-    return false;
-
-  MOZ_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc(),
-             "Illicit children change");
+  MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
+  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent");
+  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent");
+  MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
+  MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc(),
+                        "Illicit children change");
 
   int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
   if (mChildren.SafeElementAt(index) != aChild) {
     MOZ_ASSERT_UNREACHABLE("A wrong child index");
     index = mChildren.IndexOf(aChild);
     if (index == -1) {
       MOZ_ASSERT_UNREACHABLE("No child was found");
       return false;
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -478,17 +478,27 @@ DocAccessible::Shutdown()
     mVirtualCursor = nullptr;
   }
 
   mPresShell->SetDocAccessible(nullptr);
   mPresShell = nullptr;  // Avoid reentrancy
 
   mDependentIDsHash.Clear();
   mNodeToAccessibleMap.Clear();
-  ClearCache(mAccessibleCache);
+
+  for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
+    Accessible* accessible = iter.Data();
+    MOZ_ASSERT(accessible);
+    if (accessible && !accessible->IsDefunct()) {
+      // Unlink parent to avoid its cleaning overhead in shutdown.
+      accessible->mParent = nullptr;
+      accessible->Shutdown();
+    }
+    iter.Remove();
+  }
 
   HyperTextAccessibleWrap::Shutdown();
 
   GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
 }
 
 nsIFrame*
 DocAccessible::GetFrame() const
--- a/accessible/interfaces/msaa/AccessibleMarshal.rc
+++ b/accessible/interfaces/msaa/AccessibleMarshal.rc
@@ -1,5 +1,5 @@
 /* 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/. */
 
-1 typelib ISimpleDOMNode.tlb
+1 typelib ISimpleDOM.tlb
new file mode 100644
--- /dev/null
+++ b/accessible/interfaces/msaa/ISimpleDOM.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+// We use #include instead of import here so that MIDL treats these files as
+// part of the current file, thus forcing MIDL to generate proxy info for them.
+#include "ISimpleDOMNode.idl"
+#include "ISimpleDOMDocument.idl"
+#include "ISimpleDOMText.idl"
+
+[
+  uuid(a6245497-9c0b-4449-85a5-bd6ad07df8ea),
+  helpstring("ISimpleDOM Type Library")
+]
+library ISimpleDOM
+{
+  interface ISimpleDOMNode;
+  interface ISimpleDOMText;
+  interface ISimpleDOMDocument;
+};
+
--- a/accessible/interfaces/msaa/ISimpleDOMNode.idl
+++ b/accessible/interfaces/msaa/ISimpleDOMNode.idl
@@ -94,19 +94,16 @@ cpp_quote("//")
 cpp_quote("//")
 cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
 cpp_quote("")
 cpp_quote("")
 
 import "objidl.idl";
 import "oaidl.idl";
 
-import "ISimpleDOMText.idl";
-import "ISimpleDOMDocument.idl";
-
 [object, uuid(1814ceeb-49e2-407f-af99-fa755a7d2607)]
 interface ISimpleDOMNode : IUnknown
 {
   const unsigned short NODETYPE_ELEMENT = 1;
   const unsigned short NODETYPE_ATTRIBUTE = 2;
   const unsigned short NODETYPE_TEXT = 3;
   const unsigned short NODETYPE_CDATA_SECTION = 4;
   const unsigned short NODETYPE_ENTITY_REFERENCE = 5;
@@ -169,20 +166,8 @@ interface ISimpleDOMNode : IUnknown
 
   [propget] HRESULT innerHTML([out, retval] BSTR *innerHTML);
 
   [propget, local] HRESULT localInterface([out][retval] void **localInterface);
 
   [propget] HRESULT language([out, retval] BSTR *language);
 }
 
-
-[
-    uuid(a6245497-9c0b-4449-85a5-bd6ad07df8ea), 
-    helpstring("ISimpleDOM Type Library")
-] 
-library ISimpleDOM 
-{
-  interface ISimpleDOMNode;
-  interface ISimpleDOMText;
-  interface ISimpleDOMDocument;
-};
-
--- a/accessible/interfaces/msaa/Makefile.in
+++ b/accessible/interfaces/msaa/Makefile.in
@@ -1,54 +1,43 @@
 # 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/.
 
 GARBAGE += $(MIDL_GENERATED_FILES) done_gen
 
 MIDL_GENERATED_FILES = \
   dlldata.c \
-  ISimpleDOMNode.h \
-  ISimpleDOMNode_p.c \
-  ISimpleDOMNode_i.c \
-  ISimpleDOMNode.tlb \
-  ISimpleDOMDocument.h \
-  ISimpleDOMDocument_p.c \
-  ISimpleDOMDocument_i.c \
-  ISimpleDOMText.h \
-  ISimpleDOMText_p.c \
-  ISimpleDOMText_i.c \
+  ISimpleDOM.h \
+  ISimpleDOM_i.c \
+  ISimpleDOM_p.c \
+  ISimpleDOM.tlb \
   $(NULL)
 
 $(MIDL_GENERATED_FILES): done_gen
 
-done_gen: ISimpleDOMNode.idl \
+done_gen: ISimpleDOM.idl \
+          ISimpleDOMNode.idl \
           ISimpleDOMDocument.idl \
           ISimpleDOMText.idl
 
-	$(MIDL) $(MIDL_FLAGS) -I $(srcdir) -Oicf $(srcdir)/ISimpleDOMNode.idl
-	$(MIDL) $(MIDL_FLAGS) -Oicf $(srcdir)/ISimpleDOMDocument.idl
-	$(MIDL) $(MIDL_FLAGS) -Oicf $(srcdir)/ISimpleDOMText.idl
+	$(MIDL) $(MIDL_FLAGS) -I $(srcdir) -robust -Oicf $(srcdir)/ISimpleDOM.idl
 	touch $@
 
 export:: done_gen
 
 # This marshall dll is also registered in the installer
 register::
 	regsvr32 -s $(DIST)/bin/$(SHARED_LIBRARY)
 
 EMBED_MANIFEST_AT = 2
 
 midl_exports := \
-    ISimpleDOMDocument.h \
-    ISimpleDOMDocument_i.c \
-    ISimpleDOMNode.h \
-    ISimpleDOMNode_i.c \
-    ISimpleDOMText.h \
-    ISimpleDOMText_i.c \
+    ISimpleDOM.h \
+    ISimpleDOM_i.c \
     $(NULL)
 
 INSTALL_TARGETS += midl_exports
 midl_exports_FILES := $(midl_exports)
 midl_exports_DEST = $(DIST)/include
 midl_exports_TARGET := midl
 
 export:: midl
--- a/accessible/interfaces/msaa/moz.build
+++ b/accessible/interfaces/msaa/moz.build
@@ -3,46 +3,39 @@
 # 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/.
 
 GeckoSharedLibrary('AccessibleMarshal', linkage=None)
 
 SOURCES += [
     '!dlldata.c',
-    '!ISimpleDOMDocument_i.c',
-    '!ISimpleDOMDocument_p.c',
-    '!ISimpleDOMNode_i.c',
-    '!ISimpleDOMNode_p.c',
-    '!ISimpleDOMText_i.c',
-    '!ISimpleDOMText_p.c',
+    '!ISimpleDOM_i.c',
+    '!ISimpleDOM_p.c',
 ]
 
 DEFINES['REGISTER_PROXY_DLL'] = True
+# The following line is required to preserve compatibility with older versions
+# of AccessibleMarshal.dll.
+DEFINES['PROXY_CLSID'] = 'IID_ISimpleDOMNode'
 
 DEFFILE = SRCDIR + '/AccessibleMarshal.def'
 
 OS_LIBS += [
     'kernel32',
     'rpcrt4',
     'oleaut32',
 ]
 
 GENERATED_FILES += [
     'dlldata.c',
-    'ISimpleDOMDocument.h',
-    'ISimpleDOMDocument_i.c',
-    'ISimpleDOMDocument_p.c',
-    'ISimpleDOMNode.h',
-    'ISimpleDOMNode.tlb',
-    'ISimpleDOMNode_i.c',
-    'ISimpleDOMNode_p.c',
-    'ISimpleDOMText.h',
-    'ISimpleDOMText_i.c',
-    'ISimpleDOMText_p.c',
+    'ISimpleDOM.h',
+    'ISimpleDOM.tlb',
+    'ISimpleDOM_i.c',
+    'ISimpleDOM_p.c',
 ]
 
 RCINCLUDE = 'AccessibleMarshal.rc'
 
 # The Windows MIDL code generator creates things like:
 #
 #   #endif !_MIDL_USE_GUIDDEF_
 #
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -16,16 +16,17 @@
 #include "AccessibleWrap.h"
 #include "Compatibility.h"
 #include "nsWinUtils.h"
 #include "RootAccessible.h"
 #endif
 
 namespace mozilla {
 namespace a11y {
+uint64_t DocAccessibleParent::sMaxDocID = 0;
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
                                    const bool& aFromUser)
 {
   if (mShutdown)
     return IPC_OK();
 
@@ -386,16 +387,20 @@ mozilla::ipc::IPCResult
 DocAccessibleParent::RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID)
 {
   // One document should never directly be the child of another.
   // We should always have at least an outer doc accessible in between.
   MOZ_ASSERT(aID);
   if (!aID)
     return IPC_FAIL(this, "ID is 0!");
 
+  if (mShutdown) {
+    return IPC_OK();
+  }
+
   MOZ_ASSERT(CheckDocTree());
 
   auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc);
   childDoc->Unbind();
   ipc::IPCResult result = AddChildDoc(childDoc, aID, false);
   MOZ_ASSERT(result);
   MOZ_ASSERT(CheckDocTree());
 #ifdef DEBUG
@@ -428,30 +433,29 @@ DocAccessibleParent::AddChildDoc(DocAcce
   // here.
   if (outerDoc->ChildrenCount() > 1 ||
       (outerDoc->ChildrenCount() == 1 && !outerDoc->ChildAt(0)->IsDoc())) {
     return IPC_FAIL(this, "binding to proxy that can't be a outerDoc!");
   }
 
   aChildDoc->SetParent(outerDoc);
   outerDoc->SetChildDoc(aChildDoc);
-  mChildDocs.AppendElement(aChildDoc->IProtocol::Id());
-  aChildDoc->mParentDoc = IProtocol::Id();
+  mChildDocs.AppendElement(aChildDoc->mActorID);
+  aChildDoc->mParentDoc = mActorID;
 
   if (aCreating) {
     ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvShutdown()
 {
-  MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(IProtocol::Id()));
   Destroy();
 
   auto mgr = static_cast<dom::TabParent*>(Manager());
   if (!mgr->IsDestroyed()) {
     if (!PDocAccessibleParent::Send__delete__(this)) {
       return IPC_FAIL_NO_REASON(mgr);
     }
   }
@@ -465,41 +469,70 @@ DocAccessibleParent::Destroy()
   // If we are already shutdown that is because our containing tab parent is
   // shutting down in which case we don't need to do anything.
   if (mShutdown) {
     return;
   }
 
   mShutdown = true;
 
+  MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID));
   uint32_t childDocCount = mChildDocs.Length();
   for (uint32_t i = 0; i < childDocCount; i++) {
     for (uint32_t j = i + 1; j < childDocCount; j++) {
       MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]);
     }
   }
 
-  for (uint32_t i = childDocCount - 1; i < childDocCount; i--)
-    ChildDocAt(i)->Destroy();
+  // XXX This indirection through the hash map of live documents shouldn't be
+  // needed, but be paranoid for now.
+  int32_t actorID = mActorID;
+  for (uint32_t i = childDocCount - 1; i < childDocCount; i--) {
+    DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
+    MOZ_ASSERT(thisDoc);
+    if (!thisDoc) {
+      return;
+    }
+
+    thisDoc->ChildDocAt(i)->Destroy();
+  }
 
   for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
     MOZ_ASSERT(iter.Get()->mProxy != this);
     ProxyDestroyed(iter.Get()->mProxy);
     iter.Remove();
   }
 
+  DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
+  MOZ_ASSERT(thisDoc);
+  if (!thisDoc) {
+    return;
+  }
+
   // The code above should have already completely cleared these, but to be
   // extra safe make sure they are cleared here.
-  mAccessibles.Clear();
-  mChildDocs.Clear();
+  thisDoc->mAccessibles.Clear();
+  thisDoc->mChildDocs.Clear();
+
+  DocManager::NotifyOfRemoteDocShutdown(thisDoc);
+  thisDoc = LiveDocs().Get(actorID);
+  MOZ_ASSERT(thisDoc);
+  if (!thisDoc) {
+    return;
+  }
 
-  DocManager::NotifyOfRemoteDocShutdown(this);
-  ProxyDestroyed(this);
-  if (DocAccessibleParent* parentDoc = ParentDoc())
-    parentDoc->RemoveChildDoc(this);
+  ProxyDestroyed(thisDoc);
+  thisDoc = LiveDocs().Get(actorID);
+  MOZ_ASSERT(thisDoc);
+  if (!thisDoc) {
+    return;
+  }
+
+  if (DocAccessibleParent* parentDoc = thisDoc->ParentDoc())
+    parentDoc->RemoveChildDoc(thisDoc);
   else if (IsTopLevel())
     GetAccService()->RemoteDocShutdown(this);
 }
 
 DocAccessibleParent*
 DocAccessibleParent::ParentDoc() const
 {
   if (mParentDoc == kNoParentDoc) {
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -28,20 +28,26 @@ class DocAccessibleParent : public Proxy
 {
 public:
   DocAccessibleParent() :
     ProxyAccessible(this), mParentDoc(kNoParentDoc),
     mTopLevel(false), mShutdown(false)
 #if defined(XP_WIN)
                                       , mEmulatedWindowHandle(nullptr)
 #endif // defined(XP_WIN)
-  { MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible); }
+  {
+    MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible);
+    sMaxDocID++;
+    mActorID = sMaxDocID;
+    MOZ_ASSERT(!LiveDocs().Get(mActorID));
+    LiveDocs().Put(mActorID, this);
+  }
   ~DocAccessibleParent()
   {
-    LiveDocs().Remove(IProtocol::Id());
+    LiveDocs().Remove(mActorID);
     MOZ_COUNT_DTOR_INHERITED(DocAccessibleParent, ProxyAccessible);
     MOZ_ASSERT(mChildDocs.Length() == 0);
     MOZ_ASSERT(!ParentDoc());
   }
 
   void SetTopLevel() { mTopLevel = true; }
   bool IsTopLevel() const { return mTopLevel; }
 
@@ -54,21 +60,16 @@ public:
    */
   void MarkAsShutdown()
   {
     MOZ_ASSERT(mChildDocs.IsEmpty());
     MOZ_ASSERT(mAccessibles.Count() == 0);
     mShutdown = true;
   }
 
-  /**
-   * Add this document to the set of tracked documents.
-   */
-  void AddToMap() { LiveDocs().Put(IProtocol::Id(), this); }
-
   /*
    * Called when a message from a document in a child process notifies the main
    * process it is firing an event.
    */
   virtual mozilla::ipc::IPCResult RecvEvent(const uint64_t& aID, const uint32_t& aType)
     override;
 
   virtual mozilla::ipc::IPCResult RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
@@ -113,17 +114,17 @@ public:
       Destroy();
   }
 
   /*
    * Return the main processes representation of the parent document (if any)
    * of the document this object represents.
    */
   DocAccessibleParent* ParentDoc() const;
-  static const int32_t kNoParentDoc = INT32_MIN;
+  static const uint64_t kNoParentDoc = UINT64_MAX;
 
   /*
    * Called when a document in a content process notifies the main process of a
    * new child document.
    */
   ipc::IPCResult AddChildDoc(DocAccessibleParent* aChildDoc,
                              uint64_t aParentID, bool aCreating = true);
 
@@ -133,17 +134,17 @@ public:
    */
   void RemoveChildDoc(DocAccessibleParent* aChildDoc)
   {
     ProxyAccessible* parent = aChildDoc->Parent();
     MOZ_ASSERT(parent);
     if (parent) {
       aChildDoc->Parent()->ClearChildDoc(aChildDoc);
     }
-    DebugOnly<bool> result = mChildDocs.RemoveElement(aChildDoc->IProtocol::Id());
+    DebugOnly<bool> result = mChildDocs.RemoveElement(aChildDoc->mActorID);
     aChildDoc->mParentDoc = kNoParentDoc;
     MOZ_ASSERT(result);
     MOZ_ASSERT(aChildDoc->mChildDocs.Length() == 0);
   }
 
   void RemoveAccessible(ProxyAccessible* aAccessible)
   {
     MOZ_DIAGNOSTIC_ASSERT(mAccessibles.GetEntry(aAccessible->ID()));
@@ -212,32 +213,34 @@ private:
   };
 
   uint32_t AddSubtree(ProxyAccessible* aParent,
                       const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
                       uint32_t aIdxInParent);
   MOZ_MUST_USE bool CheckDocTree() const;
   xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
-  nsTArray<int32_t> mChildDocs;
-  int32_t mParentDoc;
+  nsTArray<uint64_t> mChildDocs;
+  uint64_t mParentDoc;
 
 #if defined(XP_WIN)
   // The handle associated with the emulated window that contains this document
   HWND mEmulatedWindowHandle;
 #endif
 
   /*
    * Conceptually this is a map from IDs to proxies, but we store the ID in the
    * proxy object so we can't use a real map.
    */
   nsTHashtable<ProxyEntry> mAccessibles;
+  uint64_t mActorID;
   bool mTopLevel;
   bool mShutdown;
 
+  static uint64_t sMaxDocID;
   static nsDataHashtable<nsUint64HashKey, DocAccessibleParent*>&
     LiveDocs()
     {
       static nsDataHashtable<nsUint64HashKey, DocAccessibleParent*> sLiveDocs;
       return sLiveDocs;
     }
 };
 
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -10,16 +10,20 @@
 
 const {utils: Cu, interfaces: Ci} = Components;
 
 this.EXPORTED_SYMBOLS = ['AccessFu']; // jshint ignore:line
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 
+if (Utils.MozBuildApp === 'mobile/android') {
+  Cu.import('resource://gre/modules/Messaging.jsm');
+}
+
 const ACCESSFU_DISABLE = 0; // jshint ignore:line
 const ACCESSFU_ENABLE = 1;
 const ACCESSFU_AUTO = 2;
 
 const SCREENREADER_SETTING = 'accessibility.screenreader';
 const QUICKNAV_MODES_PREF = 'accessibility.accessfu.quicknav_modes';
 const QUICKNAV_INDEX_PREF = 'accessibility.accessfu.quicknav_index';
 
@@ -27,21 +31,19 @@ this.AccessFu = { // jshint ignore:line
   /**
    * Initialize chrome-layer accessibility functionality.
    * If accessibility is enabled on the platform, then a special accessibility
    * mode is started.
    */
   attach: function attach(aWindow) {
     Utils.init(aWindow);
 
-    try {
-      Services.androidBridge.dispatch('Accessibility:Ready');
-      Services.obs.addObserver(this, 'Accessibility:Settings', false);
-    } catch (x) {
-      // Not on Android
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.dispatch('Accessibility:Ready');
+      EventDispatcher.instance.registerListener(this, 'Accessibility:Settings');
     }
 
     this._activatePref = new PrefCache(
       'accessibility.accessfu.activate', this._enableOrDisable.bind(this));
 
     this._enableOrDisable();
   },
 
@@ -49,17 +51,17 @@ this.AccessFu = { // jshint ignore:line
    * Shut down chrome-layer accessibility functionality from the outside.
    */
   detach: function detach() {
     // Avoid disabling twice.
     if (this._enabled) {
       this._disable();
     }
     if (Utils.MozBuildApp === 'mobile/android') {
-      Services.obs.removeObserver(this, 'Accessibility:Settings');
+      EventDispatcher.instance.unregisterListener(this, 'Accessibility:Settings');
     }
     delete this._activatePref;
     Utils.uninit();
   },
 
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
    */
@@ -115,26 +117,31 @@ this.AccessFu = { // jshint ignore:line
     this._notifyOutputPref =
       new PrefCache('accessibility.accessfu.notify_output');
 
 
     this.Input.start();
     Output.start();
     PointerAdapter.start();
 
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.registerListener(this, [
+        'Accessibility:ActivateObject',
+        'Accessibility:Focus',
+        'Accessibility:LongPress',
+        'Accessibility:MoveByGranularity',
+        'Accessibility:NextObject',
+        'Accessibility:PreviousObject',
+        'Accessibility:ScrollBackward',
+        'Accessibility:ScrollForward',
+      ]);
+    }
+
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
-    Services.obs.addObserver(this, 'Accessibility:NextObject', false);
-    Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
-    Services.obs.addObserver(this, 'Accessibility:Focus', false);
-    Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
-    Services.obs.addObserver(this, 'Accessibility:LongPress', false);
-    Services.obs.addObserver(this, 'Accessibility:ScrollForward', false);
-    Services.obs.addObserver(this, 'Accessibility:ScrollBackward', false);
-    Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
     Utils.win.addEventListener('TabOpen', this);
     Utils.win.addEventListener('TabClose', this);
     Utils.win.addEventListener('TabSelect', this);
 
     if (this.readyCallback) {
       this.readyCallback();
       delete this.readyCallback;
     }
@@ -164,24 +171,29 @@ this.AccessFu = { // jshint ignore:line
     PointerAdapter.stop();
 
     Utils.win.removeEventListener('TabOpen', this);
     Utils.win.removeEventListener('TabClose', this);
     Utils.win.removeEventListener('TabSelect', this);
 
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
-    Services.obs.removeObserver(this, 'Accessibility:NextObject');
-    Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
-    Services.obs.removeObserver(this, 'Accessibility:Focus');
-    Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
-    Services.obs.removeObserver(this, 'Accessibility:LongPress');
-    Services.obs.removeObserver(this, 'Accessibility:ScrollForward');
-    Services.obs.removeObserver(this, 'Accessibility:ScrollBackward');
-    Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
+
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.unregisterListener(this, [
+        'Accessibility:ActivateObject',
+        'Accessibility:Focus',
+        'Accessibility:LongPress',
+        'Accessibility:MoveByGranularity',
+        'Accessibility:NextObject',
+        'Accessibility:PreviousObject',
+        'Accessibility:ScrollBackward',
+        'Accessibility:ScrollForward',
+      ]);
+    }
 
     delete this._quicknavModesPref;
     delete this._notifyOutputPref;
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
@@ -283,53 +295,57 @@ this.AccessFu = { // jshint ignore:line
 
   _handleMessageManager: function _handleMessageManager(aMessageManager) {
     if (this._enabled) {
       this._addMessageListeners(aMessageManager);
     }
     this._loadFrameScript(aMessageManager);
   },
 
-  observe: function observe(aSubject, aTopic, aData) {
-    switch (aTopic) {
+  onEvent: function (event, data, callback) {
+    switch (event) {
       case 'Accessibility:Settings':
-        this._systemPref = JSON.parse(aData).enabled;
+        this._systemPref = data.enabled;
         this._enableOrDisable();
         break;
       case 'Accessibility:NextObject':
-      case 'Accessibility:PreviousObject':
-      {
-        let rule = aData ?
-          aData.substr(0, 1).toUpperCase() + aData.substr(1).toLowerCase() :
+      case 'Accessibility:PreviousObject': {
+        let rule = data ?
+          data.rule.substr(0, 1).toUpperCase() + data.rule.substr(1).toLowerCase() :
           'Simple';
-        let method = aTopic.replace(/Accessibility:(\w+)Object/, 'move$1');
+        let method = event.replace(/Accessibility:(\w+)Object/, 'move$1');
         this.Input.moveCursor(method, rule, 'gesture');
         break;
       }
       case 'Accessibility:ActivateObject':
-        this.Input.activateCurrent(JSON.parse(aData));
+        this.Input.activateCurrent(data);
         break;
       case 'Accessibility:LongPress':
         this.Input.sendContextMenuMessage();
         break;
       case 'Accessibility:ScrollForward':
         this.Input.androidScroll('forward');
         break;
       case 'Accessibility:ScrollBackward':
         this.Input.androidScroll('backward');
         break;
       case 'Accessibility:Focus':
-        this._focused = JSON.parse(aData);
+        this._focused = data.gainFocus;
         if (this._focused) {
           this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
         }
         break;
       case 'Accessibility:MoveByGranularity':
-        this.Input.moveByGranularity(JSON.parse(aData));
+        this.Input.moveByGranularity(data);
         break;
+    }
+  },
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
       case 'remote-browser-shown':
       case 'inprocess-browser-shown':
       {
         // Ignore notifications that aren't from a Browser
         let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
         if (!frameLoader.ownerIsMozBrowserFrame) {
           return;
         }
--- a/accessible/tests/browser/.eslintrc.js
+++ b/accessible/tests/browser/.eslintrc.js
@@ -1,13 +1,13 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": [
-    "../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ],
   // All globals made available in the test environment.
   "globals": {
     // Content scripts have global 'content' object
     "content": true,
 
     "add_task": true,
 
--- a/accessible/tests/browser/e10s/browser_caching_name.js
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -387,17 +387,17 @@ function* testElmRule(browser, target, r
  * @param {[type]} expected     expected name value
  */
 function* testSubtreeRule(browser, target, rule, expected) {
   testName(target.acc, expected);
   let onEvent = waitForEvent(EVENT_REORDER, target.id);
   yield ContentTask.spawn(browser, target.id, id => {
     let elm = content.document.getElementById(id);
     while (elm.firstChild) {
-      elm.removeChild(elm.firstChild);
+      elm.firstChild.remove();
     }
   });
   yield updateAccessibleIfNeeded(onEvent, target);
 }
 
 /**
  * Iterate over a list of rules and test accessible names for each one of the
  * rules.
--- a/accessible/tests/browser/e10s/browser_treeupdate_doc.js
+++ b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
@@ -152,17 +152,17 @@ addAccessibleTask(`
   };
   testAccessibleTree(iframe, tree);
 
   /* ================= Remove HTML from iframe document ===================== */
   reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
   yield ContentTask.spawn(browser, {}, () => {
     // Remove HTML element.
     let docNode = content.document.getElementById('iframe').contentDocument;
-    docNode.removeChild(docNode.firstChild);
+    docNode.firstChild.remove();
   });
   let event = yield reorderEventPromise;
 
   ok(event.accessible instanceof nsIAccessibleDocument,
     'Reorder should happen on the document');
   tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
@@ -230,17 +230,17 @@ addAccessibleTask(`
   };
   testAccessibleTree(iframe, tree);
 
   reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
   yield ContentTask.spawn(browser, {}, () => {
     let docEl =
       content.document.getElementById('iframe').contentDocument.documentElement;
     // Remove aftermath of this test before next test starts.
-    docEl.removeChild(docEl.firstChild);
+    docEl.firstChild.remove();
   });
   // Make sure reorder event was fired and that the input was removed.
   yield reorderEventPromise;
   tree = {
     role: ROLE_DOCUMENT,
     children: [ ]
   };
   testAccessibleTree(iframe, tree);
--- a/accessible/tests/browser/e10s/browser_treeupdate_listener.js
+++ b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
@@ -21,23 +21,9 @@ addAccessibleTask('<span id="parent"><sp
       content.window.dummyListener = () => {};
       content.document.getElementById('parent').addEventListener(
         'click', content.window.dummyListener);
     });
     yield onReorder;
 
     let tree = { TEXT: [] };
     testAccessibleTree(findAccessibleChildByID(accDoc, 'parent'), tree);
-
-    onReorder = waitForEvent(EVENT_REORDER, 'body');
-    // Remove an event listener from parent.
-    yield ContentTask.spawn(browser, {}, () => {
-      content.document.getElementById('parent').removeEventListener(
-        'click', content.window.dummyListener);
-      delete content.window.dummyListener;
-    });
-    yield onReorder;
-
-    is(findAccessibleChildByID(accDoc, 'parent'), null,
-      'Check that parent is not accessible.');
-    is(findAccessibleChildByID(accDoc, 'child'), null,
-      'Check that child is not accessible.');
   });
--- a/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
+++ b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
@@ -51,17 +51,17 @@ addAccessibleTask('<select id="select"><
   };
   testAccessibleTree(select, tree);
   ok(!isDefunct(option1Node), 'option shouldn\'t be defunct');
 
   onEvent = waitForEvent(EVENT_REORDER, 'select');
   // Remove grouping from combobox
   yield ContentTask.spawn(browser, {}, () => {
     let contentSelect = content.document.getElementById('select');
-    contentSelect.removeChild(contentSelect.firstChild);
+    contentSelect.firstChild.remove();
   });
   yield onEvent;
 
   tree = {
     COMBOBOX: [ {
       COMBOBOX_LIST: [
         { COMBOBOX_OPTION: [] },
         { COMBOBOX_OPTION: [] }
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -409,18 +409,18 @@
       ];
 
       this.invoke = function hideHideNDestroyDoc_invoke()
       {
         var doc = getAccessible('c6').firstChild;
         var l1 = doc.firstChild;
         this.target = l1.firstChild;
         var l2 = doc.lastChild;
-        l1.DOMNode.removeChild(l1.DOMNode.firstChild);
-        l2.DOMNode.removeChild(l2.DOMNode.firstChild);
+        l1.DOMNode.firstChild.remove();
+        l2.DOMNode.firstChild.remove();
       }
 
       this.check = function hideHideNDestroyDoc_check()
       {
         getNode('c6').remove();
       }
 
       this.getID = function hideHideNDestroyDoc_getID()
--- a/accessible/tests/mochitest/events/test_text.html
+++ b/accessible/tests/mochitest/events/test_text.html
@@ -46,17 +46,17 @@
      */
     function removeChildSpan(aID)
     {
       this.__proto__ = new textRemoveInvoker(aID, 0, 5, "33322");
 
       this.invoke = function removeChildSpan_invoke()
       {
         // remove HTML span, a first child of the node
-        this.DOMNode.removeChild(this.DOMNode.firstChild);
+        this.DOMNode.firstChild.remove();
       }
 
       this.getID = function removeChildSpan_getID()
       {
         return "Remove inaccessible span containing accessible nodes" + prettyName(aID);
       }
     }
 
@@ -157,17 +157,17 @@
 
       this.invoke = function removeChildren_invoke()
       {
         if (aLastToFirst) {
           while (this.DOMNode.firstChild)
             this.DOMNode.removeChild(this.DOMNode.lastChild);
         } else {
           while (this.DOMNode.firstChild)
-            this.DOMNode.removeChild(this.DOMNode.firstChild);
+            this.DOMNode.firstChild.remove();
         }
       }
 
       this.getID = function removeChildren_getID()
       {
         return "remove children of " + prettyName(aID) +
           (aLastToFirst ? " from last to first" : " from first to last");
       }
--- a/accessible/tests/mochitest/jsat/output.js
+++ b/accessible/tests/mochitest/jsat/output.js
@@ -55,16 +55,17 @@ function testContextOutput(expected, aAc
 function testObjectOutput(aAccOrElmOrID, aGenerator) {
   var accessible = getAccessible(aAccOrElmOrID);
   if (!accessible.name || !accessible.name.trim()) {
     return;
   }
   var context = new PivotContext(accessible);
   var output = aGenerator.genForObject(accessible, context);
   var outputOrder;
+  // eslint-disable-next-line mozilla/use-default-preference-values
   try {
     outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
   } catch (ex) {
     // PREF_UTTERANCE_ORDER not set.
     outputOrder = 0;
   }
   var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0;
   var nameIndex = output.indexOf(accessible.name);
--- a/accessible/tests/mochitest/jsat/test_content_text.html
+++ b/accessible/tests/mochitest/jsat/test_content_text.html
@@ -63,26 +63,26 @@
              { android_todo: true /* Bug 980512 */})],
 
           // Editable text tests.
           [ContentMessages.focusSelector('textarea'),
            new ExpectedAnnouncement('editing'),
            new ExpectedEditState({
             editing: true,
             multiline: true,
-            atStart: false,
-            atEnd: true
+            atStart: true,
+            atEnd: false
            }),
            new ExpectedCursorChange(
             ['Please refrain from Mayoneggs during this salmonella scare.',
              {string: 'textarea'}]),
-           new ExpectedTextSelectionChanged(59, 59)
+           new ExpectedTextSelectionChanged(0, 0)
           ],
           [ContentMessages.activateCurrent(10),
-           new ExpectedTextCaretChanged(10, 59),
+           new ExpectedTextCaretChanged(0, 10),
            new ExpectedEditState({ editing: true,
              multiline: true,
              atStart: false,
              atEnd: false }),
            new ExpectedTextSelectionChanged(10, 10)],
           [ContentMessages.activateCurrent(20),
            new ExpectedTextCaretChanged(10, 20),
            new ExpectedTextSelectionChanged(20, 20)
@@ -131,17 +131,16 @@
            new ExpectedClickAction(),
            new ExpectedAnnouncement('editing'),
            new ExpectedEditState({
             editing: true,
             multiline: false,
             atStart: true,
             atEnd: true
            }, { focused: 'input[type=text]' }),
-           new ExpectedTextSelectionChanged(0, 0),
            new ExpectedTextSelectionChanged(0, 0)
            ],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(
             ['So we don\'t get dessert?', {string: 'label'}]),
            new ExpectedAnnouncement('navigating'),
            new ExpectedEditState({
             editing: false,
--- a/accessible/tests/mochitest/name/markup.js
+++ b/accessible/tests/mochitest/name/markup.js
@@ -311,17 +311,17 @@ function testNameForSubtreeRule(aElm, aR
 
   if (gDumpToConsole) {
     dump("\nProcessed from subtree rule. Wait for reorder event on " +
          prettyName(aElm) + "\n");
   }
   waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
 
   while (aElm.firstChild)
-    aElm.removeChild(aElm.firstChild);
+    aElm.firstChild.remove();
 }
 
 /**
  * Return array of 'rule' elements. Used in conjunction with
  * getRuleElmsFromRulesetElm() function.
  */
 function getRuleElmsByRulesetId(aRulesetId)
 {
--- a/accessible/tests/mochitest/states/test_inputs.html
+++ b/accessible/tests/mochitest/states/test_inputs.html
@@ -102,16 +102,17 @@
       testStates(valid[i] + "2", 0, 0, STATE_INVALID);
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // 'invalid' state
     // (per spec, min/maxlength validity is affected by interactive edits)
     var mininp = document.getElementById("minlength");
     mininp.focus();
+    mininp.setSelectionRange(mininp.value.length, mininp.value.length);
     synthesizeKey("VK_BACK_SPACE", {});
     ok(!mininp.validity.valid,
        "input should be invalid after interactive edits");
     testStates(mininp, STATE_INVALID);
     // inputs currently cannot be made longer than maxlength interactively,
     // so we're not testing that case.
 
     ////////////////////////////////////////////////////////////////////////////
--- a/accessible/tests/mochitest/treeupdate/test_bug1175913.html
+++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
@@ -42,28 +42,30 @@
       {
         return "Test that show event is sent when click listener is added";
       }
     }
 
     function testRemoveListener()
     {
       this.eventSeq = [
-        new invokerChecker(EVENT_HIDE, getNode("parent")),
+        new unexpectedInvokerChecker(EVENT_HIDE, getNode("parent")),
       ];
 
       this.invoke = function testRemoveListener_invoke()
       {
         getNode("parent").removeEventListener("click", dummyListener);
       }
 
       this.finalCheck = function testRemoveListener_finalCheck()
       {
-        is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible.");
-        is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible.");
+        ok(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC),
+           "Parent stays accessible after click event listener is removed");
+        ok(!getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC),
+           "Child stays inaccessible");
       }
 
       this.getID = function testRemoveListener_getID()
       {
         return "Test that hide event is sent when click listener is removed";
       }
     }
 
--- a/accessible/tests/mochitest/treeupdate/test_doc.html
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -261,17 +261,17 @@
       this.__proto__ = new rootContentRemoved(aID);
 
       this.invoke = function removeHTMLFromIFrameDoc_invoke()
       {
         this.preinvoke();
 
         // Remove HTML element.
         var docNode = getDocNode(aID);
-        docNode.removeChild(docNode.firstChild);
+        docNode.firstChild.remove();
       }
 
       this.getID = function removeHTMLFromIFrameDoc_getID()
       {
         return "remove HTML element";
       }
     }
 
--- a/accessible/tests/mochitest/treeupdate/test_general.html
+++ b/accessible/tests/mochitest/treeupdate/test_general.html
@@ -59,17 +59,17 @@
     function removeRemove(aContainer)
     {
       this.eventSeq = [
         new invokerChecker(EVENT_REORDER, aContainer)
       ];
 
       this.invoke = function removeRemove_invoke()
       {
-        getNode(aContainer).removeChild(getNode(aContainer).firstChild);
+        getNode(aContainer).firstChild.remove();
       }
 
       this.finalCheck = function removeRemove_finalCheck()
       {
         var accTree =
           { SECTION: [ // container
             { PUSHBUTTON: [ ] }
           ] };
--- a/accessible/tests/mochitest/treeupdate/test_optgroup.html
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -72,17 +72,17 @@
     {
       this.selectNode = getNode(aID);
       this.select = getAccessible(this.selectNode);
       this.selectList = this.select.firstChild;
 
       this.invoke = function removeOptGroup_invoke()
       {
         this.option1Node = this.selectNode.firstChild.firstChild;
-        this.selectNode.removeChild(this.selectNode.firstChild);
+        this.selectNode.firstChild.remove();
       }
 
       this.eventSeq = [
         new invokerChecker(EVENT_REORDER, this.selectList)
       ];
 
       this.finalCheck = function removeOptGroup_finalCheck()
       {
--- a/accessible/tests/mochitest/treeupdate/test_textleaf.html
+++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html
@@ -38,32 +38,26 @@
       }
     }
 
     function setOnClickAttr(aID)
     {
       var node = getNode(aID);
       node.setAttribute("onclick", "alert(3);");
       var textLeaf = getAccessible(node).firstChild;
-      is(textLeaf.actionCount, 1, "Wrong action numbers!");
+      is(textLeaf.actionCount, 1, "setOnClickAttr: wrong action numbers!");
     }
 
     function removeOnClickAttr(aID)
     {
-      this.__proto__ = new textLeafUpdate(aID, false);
-
-      this.invoke = function removeOnClickAttr_invoke()
-      {
-        this.node.removeAttribute("onclick");
-      }
-
-      this.getID = function removeOnClickAttr_getID()
-      {
-        return "unmake " + prettyName(aID) + " linkable";
-      }
+      var node = getNode(aID);
+      node.removeAttribute("onclick");
+      var textLeaf = getAccessible(node).firstChild;
+      is(textLeaf.actionCount, 0,
+         "removeOnClickAttr: wrong action numbers!");
     }
 
     function setOnClickNRoleAttrs(aID)
     {
       this.__proto__ = new textLeafUpdate(aID, true);
 
       this.invoke = function setOnClickAttr_invoke()
       {
@@ -124,20 +118,21 @@
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
 
     function doTest()
     {
       // adds onclick on element, text leaf should inherit its action
       setOnClickAttr("div");
+      // remove onclick attribute, text leaf shouldn't have any action
+      removeOnClickAttr("div");
+
       // Call rest of event tests.
       gQueue = new eventQueue();
-      // remove onclick attribute, text leaf shouldn't have any action
-      gQueue.push(new removeOnClickAttr("div"));
 
       // set onclick attribute making span accessible, it's inserted into tree
       // and adopts text leaf accessible, text leaf should have an action
       gQueue.push(new setOnClickNRoleAttrs("span"));
 
       // text data removal of text node should remove its text accessible
       gQueue.push(new removeTextData("p", ROLE_PARAGRAPH));
       gQueue.push(new removeTextData("pre", ROLE_TEXT_CONTAINER));
--- a/accessible/windows/msaa/ServiceProvider.cpp
+++ b/accessible/windows/msaa/ServiceProvider.cpp
@@ -11,17 +11,17 @@
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "Relation.h"
 #include "uiaRawElmProvider.h"
 
 #include "mozilla/Preferences.h"
 #include "nsIDocShell.h"
 
-#include "ISimpleDOMNode_i.c"
+#include "ISimpleDOM.h"
 
 namespace mozilla {
 namespace a11y {
 
 IMPL_IUNKNOWN_QUERY_HEAD(ServiceProvider)
   IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
 IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible)
 
--- a/accessible/windows/sdn/sdnAccessible.cpp
+++ b/accessible/windows/sdn/sdnAccessible.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "sdnAccessible-inl.h"
-#include "ISimpleDOMNode_i.c"
+#include "ISimpleDOM_i.c"
 
 #include "DocAccessibleWrap.h"
 
 #include "nsAttrName.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleTypes.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMCSSStyleDeclaration.h"
--- a/accessible/windows/sdn/sdnAccessible.h
+++ b/accessible/windows/sdn/sdnAccessible.h
@@ -2,17 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_sdnAccessible_h_
 #define mozilla_a11y_sdnAccessible_h_
 
-#include "ISimpleDOMNode.h"
+#include "ISimpleDOM.h"
 #include "AccessibleWrap.h"
 #include "IUnknownImpl.h"
 
 #include "mozilla/Attributes.h"
 
 namespace mozilla {
 namespace a11y {
 
--- a/accessible/windows/sdn/sdnDocAccessible.cpp
+++ b/accessible/windows/sdn/sdnDocAccessible.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "sdnDocAccessible.h"
 
-#include "ISimpleDOMDocument_i.c"
+#include "ISimpleDOM.h"
 
 #include "nsNameSpaceManager.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // sdnDocAccessible
--- a/accessible/windows/sdn/sdnDocAccessible.h
+++ b/accessible/windows/sdn/sdnDocAccessible.h
@@ -2,17 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_sdnDocAccessible_h_
 #define mozilla_a11y_sdnDocAccessible_h_
 
-#include "ISimpleDOMDocument.h"
+#include "ISimpleDOM.h"
 #include "IUnknownImpl.h"
 
 #include "DocAccessibleWrap.h"
 
 namespace mozilla {
 namespace a11y {
 
 class sdnDocAccessible final : public ISimpleDOMDocument
--- a/accessible/windows/sdn/sdnTextAccessible.cpp
+++ b/accessible/windows/sdn/sdnTextAccessible.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "sdnTextAccessible.h"
 
-#include "ISimpleDOMText_i.c"
+#include "ISimpleDOM.h"
 
 #include "nsCoreUtils.h"
 #include "DocAccessible.h"
 
 #include "nsIFrame.h"
 #include "nsFontMetrics.h"
 #include "nsPresContext.h"
 #include "nsLayoutUtils.h"
--- a/accessible/windows/sdn/sdnTextAccessible.h
+++ b/accessible/windows/sdn/sdnTextAccessible.h
@@ -2,17 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_sdnTextAccessible_h_
 #define mozilla_a11y_sdnTextAccessible_h_
 
-#include "ISimpleDOMText.h"
+#include "ISimpleDOM.h"
 #include "IUnknownImpl.h"
 
 #include "AccessibleWrap.h"
 
 class nsIFrame;
 struct nsPoint;
 
 namespace mozilla {
--- a/accessible/xul/XULTreeAccessible.cpp
+++ b/accessible/xul/XULTreeAccessible.cpp
@@ -860,16 +860,17 @@ XULTreeItemAccessibleBase::DoAction(uint
 // XULTreeItemAccessibleBase: Accessible implementation
 
 void
 XULTreeItemAccessibleBase::Shutdown()
 {
   mTree = nullptr;
   mTreeView = nullptr;
   mRow = -1;
+  mParent = nullptr; // null-out to prevent base class's shutdown ops
 
   AccessibleWrap::Shutdown();
 }
 
 GroupPos
 XULTreeItemAccessibleBase::GroupPosition()
 {
   GroupPos groupPos;
--- a/accessible/xul/XULTreeGridAccessible.cpp
+++ b/accessible/xul/XULTreeGridAccessible.cpp
@@ -450,16 +450,28 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTr
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible)
 NS_INTERFACE_MAP_END_INHERITING(LeafAccessible)
 NS_IMPL_ADDREF_INHERITED(XULTreeGridCellAccessible, LeafAccessible)
 NS_IMPL_RELEASE_INHERITED(XULTreeGridCellAccessible, LeafAccessible)
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeGridCellAccessible: Accessible
 
+void
+XULTreeGridCellAccessible::Shutdown()
+{
+  mTree = nullptr;
+  mTreeView = nullptr;
+  mRow = -1;
+  mColumn = nullptr;
+  mParent = nullptr; // null-out to prevent base class's shutdown ops
+
+  LeafAccessible::Shutdown();
+}
+
 Accessible*
 XULTreeGridCellAccessible::FocusedChild()
 {
   return nullptr;
 }
 
 ENameValueFlag
 XULTreeGridCellAccessible::Name(nsString& aName)
--- a/accessible/xul/XULTreeGridAccessible.h
+++ b/accessible/xul/XULTreeGridAccessible.h
@@ -119,16 +119,17 @@ public:
                             int32_t aRow, nsITreeColumn* aColumn);
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeGridCellAccessible,
                                            LeafAccessible)
 
   // Accessible
+  virtual void Shutdown() override;
   virtual TableCellAccessible* AsTableCell() override { return this; }
   virtual nsIntRect Bounds() const override;
   virtual ENameValueFlag Name(nsString& aName) override;
   virtual Accessible* FocusedChild() override;
   virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
   virtual int32_t IndexInParent() const override;
   virtual Relation RelationByType(RelationType aType) override;
   virtual a11y::role NativeRole() override;
--- a/addon-sdk/source/lib/sdk/places/events.js
+++ b/addon-sdk/source/lib/sdk/places/events.js
@@ -110,16 +110,30 @@ function formatValue (type, data) {
   if (type === 'type')
     return mapBookmarkItemType(data);
   if (type === 'url' && data)
     return data.spec;
   return data;
 }
 
 var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS);
+// Hack alert: we sometimes need to send extra title-changed notifications
+// ourselves for backwards compat. Replace the original onVisit callback to
+// add on that logic:
+historyObserver.realOnVisit = historyObserver.onVisit;
+historyObserver.onVisit = function(url, visitId, time, sessionId,
+                                   referringId, transitionType, guid, hidden,
+                                   visitCount, typed, lastKnownTitle) {
+  // If this is the first visit we're adding, fire title-changed
+  // in case anyone cares.
+  if (visitCount == 1) {
+    historyObserver.onTitleChanged(url, lastKnownTitle);
+  }
+  this.realOnVisit(url, visitId, time, sessionId, referringId, transitionType);
+};
 historyService.addObserver(historyObserver, false);
 
 var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS);
 bookmarkService.addObserver(bookmarkObserver, false);
 
 when(() => {
   historyService.removeObserver(historyObserver);
   bookmarkService.removeObserver(bookmarkObserver);
--- a/addon-sdk/source/test/addons/places/lib/places-helper.js
+++ b/addon-sdk/source/test/addons/places/lib/places-helper.js
@@ -1,24 +1,14 @@
 /* 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 { Cc, Ci, Cu } = require('chrome');
-const bmsrv = Cc['@mozilla.org/browser/nav-bookmarks-service;1'].
-                    getService(Ci.nsINavBookmarksService);
-const hsrv = Cc['@mozilla.org/browser/nav-history-service;1'].
-              getService(Ci.nsINavHistoryService);
-const brsrv = Cc["@mozilla.org/browser/nav-history-service;1"]
-                     .getService(Ci.nsIBrowserHistory);
-const tagsrv = Cc['@mozilla.org/browser/tagging-service;1'].
-              getService(Ci.nsITaggingService);
-const asyncHistory = Cc['@mozilla.org/browser/history;1'].
-              getService(Ci.mozIAsyncHistory);
 const { send } = require('sdk/addon/events');
 const { setTimeout } = require('sdk/timers');
 const { newURI } = require('sdk/url/utils');
 const { defer, all } = require('sdk/core/promise');
 const { once } = require('sdk/system/events');
 const { set } = require('sdk/preferences/service');
 const {
   Bookmark, Group, Separator,
@@ -36,17 +26,17 @@ function invalidResolve (assert) {
     assert.fail('Resolve state should not be called: ' + e);
   };
 }
 exports.invalidResolve = invalidResolve;
 
 // Removes all children of group
 function clearBookmarks (group) {
   group
-   ? bmsrv.removeFolderChildren(group.id)
+   ? PlacesUtils.bookmarks.removeFolderChildren(group.id)
    : clearAllBookmarks();
 }
 
 function clearAllBookmarks () {
   [MENU, TOOLBAR, UNSORTED].forEach(clearBookmarks);
 }
 
 function clearHistory (done) {
@@ -60,72 +50,75 @@ function resetPlaces (done) {
   set('places.database.lastMaintenance', Math.floor(Date.now() / 1000));
   clearAllBookmarks();
   clearHistory(done);
 }
 exports.resetPlaces = resetPlaces;
 
 function compareWithHost (assert, item) {
   let id = item.id;
-  let type = item.type === 'group' ? bmsrv.TYPE_FOLDER : bmsrv['TYPE_' + item.type.toUpperCase()];
+  let type = item.type === 'group' ?
+    PlacesUtils.bookmarks.TYPE_FOLDER :
+    PlacesUtils.bookmarks['TYPE_' + item.type.toUpperCase()];
   let url = item.url && !item.url.endsWith('/') ? item.url + '/' : item.url;
 
-  if (type === bmsrv.TYPE_BOOKMARK) {
-    assert.equal(url, bmsrv.getBookmarkURI(id).spec.toString(), 'Matches host url');
-    let tags = tagsrv.getTagsForURI(newURI(item.url));
+  if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+    assert.equal(url, PlacesUtils.bookmarks.getBookmarkURI(id).spec.toString(),
+                 'Matches host url');
+    let tags = PlacesUtils.tagging.getTagsForURI(newURI(item.url));
     for (let tag of tags) {
       // Handle both array for raw data and set for instances
       if (Array.isArray(item.tags))
         assert.ok(~item.tags.indexOf(tag), 'has correct tag');
       else
         assert.ok(item.tags.has(tag), 'has correct tag');
     }
     assert.equal(tags.length,
       Array.isArray(item.tags) ? item.tags.length : item.tags.size,
       'matches tag count');
   }
-  if (type !== bmsrv.TYPE_SEPARATOR) {
-    assert.equal(item.title, bmsrv.getItemTitle(id), 'Matches host title');
+  if (type !== PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+    assert.equal(item.title, PlacesUtils.bookmarks.getItemTitle(id),
+                 'Matches host title');
   }
-  assert.equal(item.index, bmsrv.getItemIndex(id), 'Matches host index');
-  assert.equal(item.group.id || item.group, bmsrv.getFolderIdForItem(id), 'Matches host group id');
-  assert.equal(type, bmsrv.getItemType(id), 'Matches host type');
+  assert.equal(item.index, PlacesUtils.bookmarks.getItemIndex(id),
+               'Matches host index');
+  assert.equal(item.group.id || item.group,
+               PlacesUtils.bookmarks.getFolderIdForItem(id),
+               'Matches host group id');
+  assert.equal(type, PlacesUtils.bookmarks.getItemType(id),
+               'Matches host type');
 }
 exports.compareWithHost = compareWithHost;
 
+/**
+ * Adds visits to places.
+ *
+ * @param {Array|String} urls Either an array of urls to add, or a single url
+ *                            as a string.
+ */
 function addVisits (urls) {
-  var deferred = defer();
-  asyncHistory.updatePlaces([].concat(urls).map(createVisit), {
-    handleResult: function () {},
-    handleError: deferred.reject,
-    handleCompletion: deferred.resolve
-  });
-
-  return deferred.promise;
+  return PlacesUtils.history.insertMany([].concat(urls).map(createVisit));
 }
 exports.addVisits = addVisits;
 
 function removeVisits (urls) {
-  [].concat(urls).map(url => {
-    hsrv.removePage(newURI(url));
-  });
+  PlacesUtils.history.remove(urls);
 }
 exports.removeVisits = removeVisits;
 
 // Creates a mozIVisitInfo object
 function createVisit (url) {
-  let place = {}
-  place.uri = newURI(url);
-  place.title = "Test visit for " + place.uri.spec;
-  place.visits = [{
-    transitionType: hsrv.TRANSITION_LINK,
-    visitDate: +(new Date()) * 1000,
-    referredURI: undefined
-  }];
-  return place;
+  return {
+    url,
+    title: "Test visit for " + url,
+    visits: [{
+      transition: PlacesUtils.history.TRANSITION_LINK
+    }]
+  };
 }
 
 function createBookmark (data) {
   data = data || {};
   let item = {
     title: data.title || 'Moz',
     url: data.url || (!data.type || data.type === 'bookmark' ?
       'http://moz.com/' :
@@ -136,17 +129,17 @@ function createBookmark (data) {
     type: data.type || 'bookmark',
     group: data.group
   };
   return send('sdk-places-bookmarks-create', item);
 }
 exports.createBookmark = createBookmark;
 
 function historyBatch () {
-  hsrv.runInBatchMode(() => {}, null);
+  PlacesUtils.history.runInBatchMode(() => {}, null);
 }
 exports.historyBatch = historyBatch;
 
 function createBookmarkItem (data) {
   let deferred = defer();
   data = data || {};
   save({
     title: data.title || 'Moz',
--- a/addon-sdk/source/test/addons/places/lib/test-places-history.js
+++ b/addon-sdk/source/test/addons/places/lib/test-places-history.js
@@ -31,19 +31,19 @@ exports.testEmptyQuery = function*(asser
   ]);
 
   let results = yield searchP();
   assert.equal(results.length, 2, 'Correct number of entries returned');
   assert.equal(results[0].url, 'http://simplequery-1.com/',
     'matches url');
   assert.equal(results[1].url, 'http://simplequery-2.com/',
     'matches url');
-  assert.equal(results[0].title, 'Test visit for ' + results[0].url,
+  assert.equal(results[0].title, 'Test visit for ' + 'http://simplequery-1.com',
     'title matches');
-  assert.equal(results[1].title, 'Test visit for ' + results[1].url,
+  assert.equal(results[1].title, 'Test visit for ' + 'http://simplequery-2.com',
     'title matches');
   assert.equal(results[0].visitCount, 1, 'matches access');
   assert.equal(results[1].visitCount, 1, 'matches access');
   assert.ok(within(results[0].time), 'accurate access time');
   assert.ok(within(results[1].time), 'accurate access time');
   assert.equal(Object.keys(results[0]).length, 4,
     'no addition exposed properties on history result');
 };
--- a/addon-sdk/source/test/test-clipboard.js
+++ b/addon-sdk/source/test/test-clipboard.js
@@ -2,85 +2,32 @@
  * 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";
 
 require("sdk/clipboard");
 
 const { Cc, Ci } = require("chrome");
 
-const imageTools = Cc["@mozilla.org/image/tools;1"].
-                    getService(Ci.imgITools);
+const imageTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
+const io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].getService(Ci.nsIAppShellService);
 
-const io = Cc["@mozilla.org/network/io-service;1"].
-                    getService(Ci.nsIIOService);
-
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
                   "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
                   "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
                   "bWRR9AAAAABJRU5ErkJggg%3D%3D";
 
 const { base64jpeg } = require("./fixtures");
 
 const { platform } = require("sdk/system");
 // For Windows, Mac and Linux, platform returns the following: winnt, darwin and linux.
 var isWindows = platform.toLowerCase().indexOf("win") == 0;
 
-const canvasHTML = "data:text/html," + encodeURIComponent(
-  "<html>\
-    <body>\
-      <canvas width='32' height='32'></canvas>\
-    </body>\
-  </html>"
-);
-
-function comparePixelImages(imageA, imageB, callback) {
-  let tabs = require("sdk/tabs");
-
-  tabs.open({
-    url: canvasHTML,
-
-    onReady: function onReady(tab) {
-      let worker = tab.attach({
-        contentScript: "new " + function() {
-          let canvas = document.querySelector("canvas");
-          let context = canvas.getContext("2d");
-
-          self.port.on("draw-image", function(imageURI) {
-            let img = new Image();
-
-            img.onload = function() {
-              context.drawImage(this, 0, 0);
-
-              let pixels = Array.join(context.getImageData(0, 0, 32, 32).data);
-              self.port.emit("image-pixels", pixels);
-            }
-
-            img.src = imageURI;
-          });
-        }
-      });
-
-      let compared = "";
-
-      worker.port.on("image-pixels", function (pixels) {
-        if (!compared) {
-          compared = pixels;
-          this.emit("draw-image", imageB);
-        } else {
-          tab.close(callback.bind(null, compared === pixels))
-        }
-      });
-
-      worker.port.emit("draw-image", imageA);
-    }
-  });
-}
-
-
 // Test the typical use case, setting & getting with no flavors specified
 exports["test With No Flavor"] = function(assert) {
   var contents = "hello there";
   var flavor = "text";
   var fullFlavor = "text/unicode";
   var clip = require("sdk/clipboard");
 
   // Confirm we set the clipboard
@@ -155,30 +102,49 @@ exports["test Set Image"] = function(ass
   var clip = require("sdk/clipboard");
   var flavor = "image";
   var fullFlavor = "image/png";
 
   assert.ok(clip.set(base64png, flavor), "clipboard set");
   assert.equal(clip.currentFlavors[0], flavor, "flavor is set");
 };
 
-exports["test Get Image"] = function(assert, done) {
+exports["test Get Image"] = function* (assert) {
   var clip = require("sdk/clipboard");
 
   clip.set(base64png, "image");
 
   var contents = clip.get();
+  const hiddenWindow = appShellService.hiddenDOMWindow;
+  const Image = hiddenWindow.Image;
+  const canvas = hiddenWindow.document.createElementNS(XHTML_NS, "canvas");
+  let context = canvas.getContext("2d");
 
-  comparePixelImages(base64png, contents, function (areEquals) {
-    assert.ok(areEquals,
-      "Image gets from clipboard equals to image sets to the clipboard");
+  const imageURLToPixels = (imageURL) => {
+    return new Promise((resolve) => {
+      let img = new Image();
+
+      img.onload = function() {
+        context.drawImage(this, 0, 0);
+
+        let pixels = Array.join(context.getImageData(0, 0, 32, 32).data);
+        resolve(pixels);
+      };
 
-    done();
-  });
-}
+      img.src = imageURL;
+    });
+  };
+
+  let [base64pngPixels, clipboardPixels] = yield Promise.all([
+    imageURLToPixels(base64png), imageURLToPixels(contents),
+  ]);
+
+  assert.ok(base64pngPixels === clipboardPixels,
+            "Image gets from clipboard equals to image sets to the clipboard");
+};
 
 exports["test Set Image Type Not Supported"] = function(assert) {
   var clip = require("sdk/clipboard");
   var flavor = "image";
 
   assert.throws(function () {
     clip.set(base64jpeg, flavor);
   }, "Invalid flavor for image/jpeg");
--- a/addon-sdk/source/test/test-l10n-locale.js
+++ b/addon-sdk/source/test/test-l10n-locale.js
@@ -105,18 +105,17 @@ exports.testPreferedContentLocale = func
   prefs.reset(PREF_ACCEPT_LANGUAGES);
 }
 
 exports.testPreferedOsLocale = function(assert) {
   prefs.set(PREF_MATCH_OS_LOCALE, true);
   prefs.set(PREF_SELECTED_LOCALE, "");
   prefs.set(PREF_ACCEPT_LANGUAGES, "");
 
-  let expectedLocale = Services.locale.getLocaleComponentForUserAgent().
-    toLowerCase();
+  let expectedLocale = Services.locale.getAppLocaleAsLangTag().toLowerCase();
   let expectedLocaleList = [expectedLocale];
 
   // Add default "en-us" fallback if the main language is not already en-us
   if (expectedLocale != "en-us")
     expectedLocaleList.push("en-us");
 
   assertPrefered(assert, expectedLocaleList, "Ensure that we select OS locale when related preference is set");
 
--- a/addon-sdk/source/test/test-ui-action-button.js
+++ b/addon-sdk/source/test/test-ui-action-button.js
@@ -303,17 +303,17 @@ exports['test button global state update
   // Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
   // was removed or it's not in the UX build yet
 
   let { node, id: widgetId } = getWidget(button.id);
 
   // check read-only properties
 
   assert.throws(() => button.id = 'another-id',
-    /^setting a property that has only a getter/,
+    /^setting getter-only property/,
     'id cannot be set at runtime');
 
   assert.equal(button.id, 'my-button-4',
     'id is unchanged');
   assert.equal(node.id, widgetId,
     'node id is unchanged');
 
   // check writable properties
--- a/addon-sdk/source/test/test-ui-sidebar.js
+++ b/addon-sdk/source/test/test-ui-sidebar.js
@@ -1045,17 +1045,17 @@ exports.testSidebarGettersAndSettersAfte
     url: url
   });
 
   sidebar.destroy();
 
   assert.equal(sidebar.id, undefined, 'sidebar after destroy has no id');
 
   assert.throws(() => sidebar.id = 'foo-tang',
-    /^setting a property that has only a getter/,
+    /^setting getter-only property/,
     'id cannot be set at runtime');
 
   assert.equal(sidebar.id, undefined, 'sidebar after destroy has no id');
 
   assert.equal(sidebar.title, undefined, 'sidebar after destroy has no title');
   sidebar.title = 'boo-tang';
   assert.equal(sidebar.title, undefined, 'sidebar after destroy has no title');
 
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -297,17 +297,17 @@ exports['test button global state update
   // Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
   // was removed or it's not in the UX build yet
 
   let { node, id: widgetId } = getWidget(button.id);
 
   // check read-only properties
 
   assert.throws(() => button.id = 'another-id',
-    /^setting a property that has only a getter/,
+    /^setting getter-only property/,
     'id cannot be set at runtime');
 
   assert.equal(button.id, 'my-button-4',
     'id is unchanged');
   assert.equal(node.id, widgetId,
     'node id is unchanged');
 
   // check writable properties
--- a/addon-sdk/source/test/test-weak-set.js
+++ b/addon-sdk/source/test/test-weak-set.js
@@ -76,71 +76,71 @@ exports['test add/remove/iterate/clear i
 };
 
 exports['test adding non object or null item'] = function(assert) {
   let items = {};
 
   assert.throws(() => {
     add(items, 'foo');
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, 0);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, undefined);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, null);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, true);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 };
 
 exports['test adding to non object or null item'] = function(assert) {
   let item = {};
 
   assert.throws(() => {
     add('foo', item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(0, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(undefined, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(null, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(true, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 };
 
 require("sdk/test").run(exports);
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -884,19 +884,16 @@ pref("dom.apps.reviewer_paths", "/review
 
 // New implementation to unify touch-caret and selection-carets.
 pref("layout.accessiblecaret.enabled", true);
 
 // Show the selection bars at the two ends of the selection highlight. Required
 // by the spec in bug 921965.
 pref("layout.accessiblecaret.bar.enabled", true);
 
-// Hide the caret in cursor mode after 3 seconds.
-pref("layout.accessiblecaret.timeout_ms", 3000);
-
 // Hide carets and text selection dialog during scrolling.
 pref("layout.accessiblecaret.always_show_when_scrolling", false);
 
 // Enable sync with Firefox Accounts.
 pref("services.sync.fxaccounts.enabled", true);
 pref("identity.fxaccounts.enabled", true);
 
 pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
--- a/b2g/chrome/content/devtools/adb.js
+++ b/b2g/chrome/content/devtools/adb.js
@@ -41,16 +41,17 @@ var AdbController = {
     this.updateState();
   },
 
   startDisableAdbTimer: function() {
     if (this.disableAdbTimer) {
       this.disableAdbTimer.cancel();
     } else {
       this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      // eslint-disable-next-line mozilla/use-default-preference-values
       try {
         this.disableAdbTimeoutHours =
           Services.prefs.getIntPref("b2g.adb.timeout-hours");
       } catch (e) {
         // This happens if the pref doesn't exist, in which case
         // disableAdbTimeoutHours will still be set to the default.
       }
     }
@@ -83,48 +84,16 @@ var AdbController = {
       debug("ADB timer expired - disabling ADB\n");
       navigator.mozSettings.createLock().set(
         {'debugger.remote-mode': 'disabled'});
     }
   },
 
   updateState: function() {
     this.umsActive = false;
-    this.storages = navigator.getDeviceStorages('sdcard');
-    this.updateStorageState(0);
-  },
-
-  updateStorageState: function(storageIndex) {
-    if (storageIndex >= this.storages.length) {
-      // We've iterated through all of the storage objects, now we can
-      // really do updateStateInternal.
-      this.updateStateInternal();
-      return;
-    }
-    let storage = this.storages[storageIndex];
-    DEBUG && debug("Checking availability of storage: '" + storage.storageName + "'");
-
-    let req = storage.available();
-    req.onsuccess = function(e) {
-      DEBUG && debug("Storage: '" + storage.storageName + "' is '" + e.target.result + "'");
-      if (e.target.result == 'shared') {
-        // We've found a storage area that's being shared with the PC.
-        // We can stop looking now.
-        this.umsActive = true;
-        this.updateStateInternal();
-        return;
-      }
-      this.updateStorageState(storageIndex + 1);
-    }.bind(this);
-    req.onerror = function(e) {
-
-      Cu.reportError("AdbController: error querying storage availability for '" +
-                     this.storages[storageIndex].storageName + "' (ignoring)\n");
-      this.updateStorageState(storageIndex + 1);
-    }.bind(this);
   },
 
   updateStateInternal: function() {
     DEBUG && debug("updateStateInternal: called");
 
     if (this.remoteDebuggerEnabled === undefined ||
         this.lockEnabled === undefined ||
         this.locked === undefined) {
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -422,22 +422,18 @@ setUpdateTrackingId();
   let geckoPrefName = 'toolkit.telemetry.enabled';
   SettingsListener.observe(gaiaSettingName, null, function(value) {
     if (value !== null) {
       // Gaia setting has been set; update Gecko pref to that.
       Services.prefs.setBoolPref(geckoPrefName, value);
       return;
     }
     // Gaia setting has not been set; set the gaia setting to default.
-    let prefValue = AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT;
-    try {
-      prefValue = Services.prefs.getBoolPref(geckoPrefName);
-    } catch (e) {
-      // Pref not set; use default value.
-    }
+    let prefValue = Services.prefs.getBoolPref(geckoPrefName,
+                                               AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT);
     let setting = {};
     setting[gaiaSettingName] = prefValue;
     window.navigator.mozSettings.createLock().set(setting);
   });
 })();
 
 // =================== Low-precision buffer ======================
 (function setupLowPrecisionSettings() {
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -251,25 +251,21 @@ var shell = {
     SafeMode.check(window).then(() => {
       let startManifestURL =
         Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
           .getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
 
       // If --start-manifest hasn't been specified, we re-use the latest specified manifest.
       // If it's the first launch, we will fallback to b2g.default.start_manifest_url
       if (AppConstants.MOZ_GRAPHENE && !startManifestURL) {
-        try {
-          startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
-        } catch(e) {}
+        startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url", "");
       }
 
       if (!startManifestURL) {
-        try {
-          startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url");
-        } catch(e) {}
+        startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url", "");
       }
 
       if (startManifestURL) {
         Cu.import('resource://gre/modules/Bootstraper.jsm');
 
         if (AppConstants.MOZ_GRAPHENE && Bootstraper.isInstallRequired(startManifestURL)) {
           // Installing the app my take some time. We don't want to keep the
           // native window hidden.
--- a/b2g/components/AboutServiceWorkers.jsm
+++ b/b2g/components/AboutServiceWorkers.jsm
@@ -42,20 +42,17 @@ function serializeServiceWorkerInfo(aSer
 }
 
 
 this.AboutServiceWorkers = {
   get enabled() {
     if (this._enabled) {
       return this._enabled;
     }
-    this._enabled = false;
-    try {
-      this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
-    } catch(e) {}
+    this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled", false);
     return this._enabled;
   },
 
   init: function() {
     SystemAppProxy.addEventListener("mozAboutServiceWorkersContentEvent",
                                     AboutServiceWorkers);
   },
 
--- a/b2g/components/Bootstraper.jsm
+++ b/b2g/components/Bootstraper.jsm
@@ -70,16 +70,17 @@ this.Bootstraper = {
     * If a system app is already installed, uninstall it so that we can
     * cleanly replace it by the current one.
     */
   uninstallPreviousSystemApp: function() {
     // TODO: FIXME
     return Promise.resolve();
 
     let oldManifestURL;
+    // eslint-disable-next-line mozilla/use-default-preference-values
     try{
       oldManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
     } catch(e) {
       // No preference set, so nothing to uninstall.
       return Promise.resolve();
     }
 
     let id = DOMApplicationRegistry.getAppLocalIdByManifestURL(oldManifestURL);
--- a/b2g/components/DirectoryProvider.js
+++ b/b2g/components/DirectoryProvider.js
@@ -108,23 +108,17 @@ DirectoryProvider.prototype = {
   },
 
   getFileNotGonk: function(prop, persistent) {
     // In desktop builds, coreAppsDir is the same as the profile
     // directory unless otherwise specified. We just need to get the
     // path from the parent, and it is then used to build
     // jar:remoteopenfile:// uris.
     if (prop == "coreAppsDir") {
-      let coreAppsDirPref;
-      try {
-        coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF);
-      } catch (e) {
-        // coreAppsDirPref may not exist if we're on an older version
-        // of gaia, so just fail silently.
-      }
+      let coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF, "");
       let appsDir;
       // If pref doesn't exist or isn't set, default to old value
       if (!coreAppsDirPref || coreAppsDirPref == "") {
         appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
         appsDir.append("webapps");
       } else {
         appsDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile)
         appsDir.initWithPath(coreAppsDirPref);
--- a/b2g/components/SignInToWebsite.jsm
+++ b/b2g/components/SignInToWebsite.jsm
@@ -89,22 +89,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 // The default persona uri; can be overwritten with toolkit.identity.uri pref.
 // Do this if you want to repoint to a different service for testing.
 // There's no point in setting up an observer to monitor the pref, as b2g prefs
 // can only be overwritten when the profie is recreated.  So just get the value
 // on start-up.
-var kPersonaUri = "https://firefoxos.persona.org";
-try {
-  kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri");
-} catch(noSuchPref) {
-  // stick with the default value
-}
+var kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri",
+                                             "https://firefoxos.persona.org");
 
 // JS shim that contains the callback functions that
 // live within the identity UI provisioning frame.
 const kIdentityShimFile = "chrome://b2g/content/identity.js";
 
 // Type of MozChromeEvents to handle id dialogs.
 const kOpenIdentityDialog = "id-dialog-open";
 const kDoneIdentityDialog = "id-dialog-done";
--- a/b2g/config/aries/releng-aries.manifest
+++ b/b2g/config/aries/releng-aries.manifest
@@ -1,27 +1,27 @@
 [
-{
-"version": "Android NDK r11b for B2G",
-"algorithm": "sha512",
-"visibility": "internal",
-"filename": "android-ndk-b2g.tar.xz",
-"unpack": true,
-"digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
-"size": 292442020
-},
-{
-"algorithm": "sha512",
-"visibility": "internal",
-"filename": "backup-aries_23.0.1.A.5.77.tar.xz",
-"unpack": true,
-"digest": "79c8e390e88cc4765ff7f5f29f3d5337c9037b7eb9414006947d38d34acefdbcf7090c18a366948c682b1c2c9d9ef51012e7be44daa28fdde7b837ade647c257",
-"size": 227555180
-},
-{
-"version": "gcc 4.8.5 + PR64905",
-"size": 80160264,
-"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": "True"
-}
+  {
+    "version": "Android NDK r11b for B2G",
+    "algorithm": "sha512",
+    "visibility": "internal",
+    "filename": "android-ndk-b2g.tar.xz",
+    "unpack": true,
+    "digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
+    "size": 292442020
+  },
+  {
+    "algorithm": "sha512",
+    "visibility": "internal",
+    "filename": "backup-aries_23.0.1.A.5.77.tar.xz",
+    "unpack": true,
+    "digest": "79c8e390e88cc4765ff7f5f29f3d5337c9037b7eb9414006947d38d34acefdbcf7090c18a366948c682b1c2c9d9ef51012e7be44daa28fdde7b837ade647c257",
+    "size": 227555180
+  },
+  {
+    "version": "gcc 4.8.5 + PR64905",
+    "size": 80160264,
+    "digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+    "algorithm": "sha512",
+    "filename": "gcc.tar.xz",
+    "unpack": "True"
+  }
 ]
--- a/b2g/config/nexus-5-l/releng-nexus5.manifest
+++ b/b2g/config/nexus-5-l/releng-nexus5.manifest
@@ -1,19 +1,19 @@
 [
-{
-"version": "Android NDK r11b for B2G",
-"algorithm": "sha512",
-"visibility": "internal",
-"filename": "android-ndk-b2g.tar.xz",
-"unpack": true,
-"digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
-"size": 292442020
-},
-{
-"version": "gcc 4.8.5 + PR64905",
-"size": 80160264,
-"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": "True"
-}
+  {
+    "version": "Android NDK r11b for B2G",
+    "algorithm": "sha512",
+    "visibility": "internal",
+    "filename": "android-ndk-b2g.tar.xz",
+    "unpack": true,
+    "digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
+    "size": 292442020
+  },
+  {
+    "version": "gcc 4.8.5 + PR64905",
+    "size": 80160264,
+    "digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+    "algorithm": "sha512",
+    "filename": "gcc.tar.xz",
+    "unpack": "True"
+  }
 ]
--- a/b2g/config/tooltool-manifests/linux32/releng.manifest
+++ b/b2g/config/tooltool-manifests/linux32/releng.manifest
@@ -1,32 +1,32 @@
 [
-{
-"version": "gcc 4.8.5 + PR64905",
-"size": 80160264,
-"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 11189216,
-"digest": "18bc52b0599b1308b667e282abb45f47597bfc98a5140cfcab8da71dacf89dd76d0dee22a04ce26fe7ad1f04e2d6596991f9e5b01fd2aaaab5542965f596b0e6",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"setup": "setup.sh",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-}
+  {
+    "version": "gcc 4.8.5 + PR64905",
+    "size": 80160264,
+    "digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+    "algorithm": "sha512",
+    "filename": "gcc.tar.xz",
+    "unpack": true
+  },
+  {
+    "size": 11189216,
+    "digest": "18bc52b0599b1308b667e282abb45f47597bfc98a5140cfcab8da71dacf89dd76d0dee22a04ce26fe7ad1f04e2d6596991f9e5b01fd2aaaab5542965f596b0e6",
+    "algorithm": "sha512",
+    "filename": "gtk3.tar.xz",
+    "setup": "setup.sh",
+    "unpack": true
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/config/tooltool-manifests/macosx64/releng.manifest
+++ b/b2g/config/tooltool-manifests/macosx64/releng.manifest
@@ -1,24 +1,24 @@
 [
-{
-"version": "clang 3.8.0",
-"size": 133060926,
-"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
-"algorithm": "sha512",
-"filename": "clang.tar.bz2",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-}
+  {
+    "version": "clang 3.8.0",
+    "size": 133060926,
+    "digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+    "algorithm": "sha512",
+    "filename": "clang.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/config/tooltool-manifests/win32/releng.manifest
+++ b/b2g/config/tooltool-manifests/win32/releng.manifest
@@ -1,22 +1,22 @@
 [
-{
-"size": 266240,
-"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
-"algorithm": "sha512",
-"filename": "mozmake.exe"
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-}
+  {
+    "size": 266240,
+    "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+    "algorithm": "sha512",
+    "filename": "mozmake.exe"
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
@@ -1,48 +1,48 @@
 [
-{
-"size": 102421980,
-"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-"version": "gcc 4.9.3",
-"unpack": true,
-"filename": "gcc.tar.xz",
-"algorithm": "sha512"
-},
-{
-"unpack": true,
-"algorithm": "sha512",
-"filename": "sixgill.tar.xz",
-"hg_id": "8cb9c3fb039a+ tip",
-"digest": "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
-"size": 2631908
-},
-{
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"setup": "setup.sh",
-"unpack": true,
-"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"size": 12072532
-},
-{
-"version": "rustc 1.15.1 (021bd294c 2017-02-08) repack",
-"size": 110077036,
-"digest": "8b99d058cc081f6ca2a3cc88c3ca9c15232961d2539774dacee35e2258955ad8fc4cb0af3b903a3e3f8a264ddecb3baae9256502ffc178a2823779284ace2bd8",
-"algorithm": "sha512",
-"filename": "rustc.tar.xz",
-"unpack": true
-},
-{
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"size": 167175
-},
-{
-"filename": "moz-tt.tar.bz2",
-"algorithm": "sha512",
-"unpack": true,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"size": 31078810
-}
+  {
+    "size": 102421980,
+    "digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+    "version": "gcc 4.9.3",
+    "unpack": true,
+    "filename": "gcc.tar.xz",
+    "algorithm": "sha512"
+  },
+  {
+    "unpack": true,
+    "algorithm": "sha512",
+    "filename": "sixgill.tar.xz",
+    "hg_id": "8cb9c3fb039a+ tip",
+    "digest": "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+    "size": 2631908
+  },
+  {
+    "algorithm": "sha512",
+    "filename": "gtk3.tar.xz",
+    "setup": "setup.sh",
+    "unpack": true,
+    "digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+    "size": 12072532
+  },
+  {
+    "version": "rustc 1.15.1 (021bd294c 2017-02-08) repack",
+    "size": 110077036,
+    "digest": "8b99d058cc081f6ca2a3cc88c3ca9c15232961d2539774dacee35e2258955ad8fc4cb0af3b903a3e3f8a264ddecb3baae9256502ffc178a2823779284ace2bd8",
+    "algorithm": "sha512",
+    "filename": "rustc.tar.xz",
+    "unpack": true
+  },
+  {
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "size": 167175
+  },
+  {
+    "filename": "moz-tt.tar.bz2",
+    "algorithm": "sha512",
+    "unpack": true,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "size": 31078810
+  }
 ]
--- a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
@@ -1,40 +1,40 @@
 [
-{
-"version": "gcc 4.9.3",
-"size": 102421980,
-"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 12072532,
-"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"setup": "setup.sh",
-"unpack": true
-},
-{
-"version": "rustc 1.15.1 (021bd294c 2017-02-08) repack",
-"size": 110077036,
-"digest": "8b99d058cc081f6ca2a3cc88c3ca9c15232961d2539774dacee35e2258955ad8fc4cb0af3b903a3e3f8a264ddecb3baae9256502ffc178a2823779284ace2bd8",
-"algorithm": "sha512",
-"filename": "rustc.tar.xz",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-}
+  {
+    "version": "gcc 4.9.3",
+    "size": 102421980,
+    "digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+    "algorithm": "sha512",
+    "filename": "gcc.tar.xz",
+    "unpack": true
+  },
+  {
+    "size": 12072532,
+    "digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+    "algorithm": "sha512",
+    "filename": "gtk3.tar.xz",
+    "setup": "setup.sh",
+    "unpack": true
+  },
+  {
+    "version": "rustc 1.15.1 (021bd294c 2017-02-08) repack",
+    "size": 110077036,
+    "digest": "8b99d058cc081f6ca2a3cc88c3ca9c15232961d2539774dacee35e2258955ad8fc4cb0af3b903a3e3f8a264ddecb3baae9256502ffc178a2823779284ace2bd8",
+    "algorithm": "sha512",
+    "filename": "rustc.tar.xz",
+    "unpack": true
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
@@ -1,24 +1,24 @@
 [
-{
-"version": "clang 3.8.0",
-"size": 133060926,
-"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
-"algorithm": "sha512",
-"filename": "clang.tar.bz2",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-}
+  {
+    "version": "clang 3.8.0",
+    "size": 133060926,
+    "digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+    "algorithm": "sha512",
+    "filename": "clang.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/dev/config/tooltool-manifests/win32/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/win32/releng.manifest
@@ -1,22 +1,22 @@
 [
-{
-"size": 266240,
-"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
-"algorithm": "sha512",
-"filename": "mozmake.exe"
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
-}
+  {
+    "size": 266240,
+    "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+    "algorithm": "sha512",
+    "filename": "mozmake.exe"
+  },
+  {
+    "size": 167175,
+    "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+    "algorithm": "sha512",
+    "filename": "sccache.tar.bz2",
+    "unpack": true
+  },
+  {
+    "size": 31078810,
+    "digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+    "algorithm": "sha512",
+    "filename": "moz-tt.tar.bz2",
+    "unpack": true
+  }
 ]
--- a/b2g/test/emulator.manifest
+++ b/b2g/test/emulator.manifest
@@ -1,6 +1,8 @@
-[{
-"size": 746441603,
-"digest": "199236aefecc1657cdc1b791ec38c8184557ab9249aff9c63a74abf73edc1dc0ea36b19b558f34ca3b14f8a511b10bcf37408b19701929522b4dc22dbaddcbe9",
-"algorithm": "sha512",
-"filename": "emulator.zip"
-}]
+[
+  {
+    "size": 746441603,
+    "digest": "199236aefecc1657cdc1b791ec38c8184557ab9249aff9c63a74abf73edc1dc0ea36b19b558f34ca3b14f8a511b10bcf37408b19701929522b4dc22dbaddcbe9",
+    "algorithm": "sha512",
+    "filename": "emulator.zip"
+  }
+]
--- a/browser/.eslintrc.js
+++ b/browser/.eslintrc.js
@@ -1,12 +1,15 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../toolkit/.eslintrc.js"
+    "plugin:mozilla/recommended"
   ],
 
   "rules": {
+    // XXX Bug 1326071 - This should be reduced down - probably to 20 or to
+    // be removed & synced with the mozilla/recommended value.
+    "complexity": ["error", {"max": 42}],
+
     "no-shadow": "error",
-    "no-undef": "error"
   }
 };
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -657,16 +657,24 @@
     <emItem blockID="i1128" id="youtubeunblocker@unblocker.yt">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i1038" id="344141-fasf9jas08hasoiesj9ia8ws@jetpack">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="89a61123-79a2-45d1-aec2-97afca0863eb" id="InternetProtection@360safe.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="5.0.0.1002" severity="3">
+        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+          <versionRange maxVersion="*" minVersion="52.0a1"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
     <emItem blockID="i471" id="firefox@luckyleap.net">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i560" id="adsremoval@adsremoval.net">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -860,32 +868,16 @@
     <emItem blockID="i532" id="249911bc-d1bd-4d66-8c17-df533609e6d8@c76f3de9-939e-4922-b73c-5d7a3139375d.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i65" id="activity@facebook.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i656" id="hdv@vovcacik.addons.mozilla.org">
-      <prefs/>
-      <versionRange minVersion="102.0" maxVersion="102.0" severity="3"/>
-    </emItem>
-    <emItem blockID="i722" id="{9802047e-5a84-4da3-b103-c55995d147d1}">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="3"/>
-    </emItem>
-    <emItem blockID="i228" id="crossriderapp5060@crossrider.com">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="1"/>
-    </emItem>
-    <emItem blockID="i470" id="extension@FastFreeConverter.com">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="3"/>
-    </emItem>
     <emItem blockID="i1223" id="tmbepff@trendmicro.com">
       <prefs/>
       <versionRange minVersion="9.2" maxVersion="9.2.0.1023" severity="1"/>
       <versionRange minVersion="0" maxVersion="9.1.0.1035" severity="1"/>
     </emItem>
     <emItem blockID="i478" id="{7e8a1050-cf67-4575-92df-dcc60e7d952d}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
@@ -1530,16 +1522,24 @@
     <emItem blockID="i314" id="crossriderapp8812@crossrider.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i262" id="{167d9323-f7cc-48f5-948a-6f012831a69f}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="08addad8-2f03-4cff-a791-e6f2a1b170ed" id="WebProtection@360safe.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3">
+        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+          <versionRange maxVersion="*" minVersion="52.0a1"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
     <emItem blockID="i838" id="{87b5a11e-3b54-42d2-9102-0a7cb1f79ebf}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i678" id="{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -1715,16 +1715,24 @@
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i720" id="FXqG@xeeR.net">
       <prefs>
         <pref>browser.startup.homepage</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
+    <emItem blockID="d33f6d48-a555-49dd-96ff-8d75473403a8" id="mozilla_cc2@internetdownloadmanager.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="6.26.11" severity="3">
+        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+          <versionRange maxVersion="*" minVersion="53.0a1"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
     <emItem blockID="i493" id="12x3q@3244516.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i1265" id="@video_downloader_pro">
       <prefs/>
       <versionRange minVersion="1.2.1" maxVersion="1.2.5" severity="1"/>
     </emItem>
@@ -2030,16 +2038,32 @@
           <versionRange maxVersion="9.*" minVersion="9.0a1"/>
         </targetApplication>
       </versionRange>
     </emItem>
     <emItem blockID="i1264" id="suchpony@suchpony.de">
       <prefs/>
       <versionRange minVersion="0" maxVersion="1.6.7" severity="3"/>
     </emItem>
+    <emItem blockID="i656" id="hdv@vovcacik.addons.mozilla.org">
+      <prefs/>
+      <versionRange minVersion="102.0" maxVersion="102.0" severity="3"/>
+    </emItem>
+    <emItem blockID="i722" id="{9802047e-5a84-4da3-b103-c55995d147d1}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="i228" id="crossriderapp5060@crossrider.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="1"/>
+    </emItem>
+    <emItem blockID="i470" id="extension@FastFreeConverter.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
   </emItems>
   <pluginItems>
     <pluginItem blockID="p416">
       <match exp="JavaAppletPlugin\.plugin" name="filename"/>
       <versionRange maxVersion="Java 6 Update 45" minVersion="Java 6 Update 42" severity="0" vulnerabilitystatus="1">
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
           <versionRange maxVersion="*" minVersion="17.0"/>
         </targetApplication>
@@ -2151,16 +2175,21 @@
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="24.0.0.186" minVersion="23.0.0.207" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p1420">
       <match exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="23.0.0.205" minVersion="23.0.0.185" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
+    <pluginItem blockID="26c2a4e2-9aff-4ab1-b654-20e478b375f0" os="Linux">
+      <match exp="libflashplayer\.so" name="filename"/>
+      <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+      <versionRange maxVersion="24.0.0.221" minVersion="24.0.0.194" severity="0" vulnerabilitystatus="1"/>
+    </pluginItem>
     <pluginItem blockID="p248">
       <match exp="Scorch\.plugin" name="filename"/>
       <versionRange maxVersion="6.2.0b88" minVersion="0" severity="1"/>
     </pluginItem>
     <pluginItem blockID="p1141">
       <match exp="JavaAppletPlugin\.plugin" name="filename"/>
       <infoURL>https://java.com/</infoURL>
       <versionRange maxVersion="Java 7 Update 97" minVersion="Java 7 Update 91" severity="0" vulnerabilitystatus="1"/>
@@ -2177,16 +2206,21 @@
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
           <versionRange maxVersion="17.0.1" minVersion="0.1"/>
         </targetApplication>
       </versionRange>
     </pluginItem>
     <pluginItem blockID="p31">
       <match exp="NPMySrch.dll" name="filename"/>
     </pluginItem>
+    <pluginItem blockID="2b608fae-1750-4a06-a142-0bc9ba17a7d0">
+      <match exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
+      <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+      <versionRange maxVersion="24.0.0.221" minVersion="24.0.0.194" severity="0" vulnerabilitystatus="1"/>
+    </pluginItem>
     <pluginItem blockID="p1020">
       <match exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="13.*" minVersion="13.0" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p958">
       <match exp="Java\(TM\) Platform SE 7 U(79|80)(\s[^\d\._U]|$)" name="name"/>
       <match exp="npjp2\.dll" name="filename"/>
@@ -2271,22 +2305,22 @@
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
           <versionRange maxVersion="17.0.*" minVersion="17.0.4"/>
         </targetApplication>
       </versionRange>
     </pluginItem>
     <pluginItem blockID="3f136e56-4c93-4619-8c0d-d86258c1065d">
       <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/reader/</infoURL>
-      <versionRange maxVersion="15.006.30244" minVersion="15.006" severity="1" vulnerabilitystatus="1"/>
+      <versionRange maxVersion="15.006.30244" minVersion="15.006" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="43b45ad8-a373-42c1-89c6-64e2746885e5">
       <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/reader/</infoURL>
-      <versionRange maxVersion="15.020.20042" minVersion="15.020" severity="1" vulnerabilitystatus="1"/>
+      <versionRange maxVersion="15.020.20042" minVersion="15.020" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p366">
       <match exp="Scorch\.plugin" name="filename"/>
       <versionRange maxVersion="6.2.0" minVersion="6.2.0" severity="1"/>
     </pluginItem>
     <pluginItem blockID="p936" os="Linux">
       <match exp="libflashplayer\.so" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
@@ -2361,17 +2395,17 @@
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
           <versionRange maxVersion="*" minVersion="17.0"/>
         </targetApplication>
       </versionRange>
     </pluginItem>
     <pluginItem blockID="59c31ade-88d6-4b22-8601-5316f82e3977">
       <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/reader/</infoURL>
-      <versionRange maxVersion="11.0.18" minVersion="11.0" severity="1" vulnerabilitystatus="1"/>
+      <versionRange maxVersion="11.0.18" minVersion="11.0" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p1004">
       <match exp="Unity Web Player\.plugin" name="filename"/>
       <match exp="^($|Unity Web Player version 5.0(\.([0-2]|3f1))?[^0-9.])" name="description"/>
       <versionRange severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p459">
       <match exp="JavaAppletPlugin\.plugin" name="filename"/>
@@ -3011,16 +3045,21 @@
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="18.0.0.254" minVersion="18.0.0.233" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p828">
       <match exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="16.0.0.287" minVersion="15.0.0.243" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
+    <pluginItem blockID="e939e3f9-cb55-494d-b95a-c5ac82bd8d3d" os="Linux">
+      <match exp="libflashplayer\.so" name="filename"/>
+      <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+      <versionRange maxVersion="24.0.0.194" minVersion="24.0.0.186" severity="0" vulnerabilitystatus="1"/>
+    </pluginItem>
     <pluginItem blockID="p113">
       <match exp="npuplaypc\.dll" name="filename"/>
       <versionRange maxVersion="1.0.0.0" minVersion="0" severity="1"/>
     </pluginItem>
     <pluginItem blockID="p1247">
       <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
       <infoURL>https://get.adobe.com/reader</infoURL>
       <versionRange maxVersion="15.006.30174" minVersion="15.006.30174" severity="0" vulnerabilitystatus="1"/>
@@ -3086,16 +3125,21 @@
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="20.0.0.235" minVersion="19.0.0.246" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p1150" os="Linux">
       <match exp="libflashplayer\.so" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="11.2.202.577" minVersion="11.2.202.569" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
+    <pluginItem blockID="f77960ca-28f3-4664-994d-2b713d2a1434">
+      <match exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
+      <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+      <versionRange maxVersion="24.0.0.194" minVersion="24.0.0.186" severity="0" vulnerabilitystatus="1"/>
+    </pluginItem>
     <pluginItem blockID="p1138" os="Linux">
       <match exp="libflashplayer\.so" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange maxVersion="11.2.202.569" minVersion="11.2.202.559" severity="0" vulnerabilitystatus="1"/>
     </pluginItem>
     <pluginItem blockID="p1224" os="Linux">
       <match exp="libflashplayer\.so" name="filename"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -24,20 +24,16 @@ DEFINES['APP_VERSION'] = CONFIG['MOZ_APP
 
 LOCAL_INCLUDES += [
     '!/build',
     '/toolkit/xre',
     '/xpcom/base',
     '/xpcom/build',
 ]
 
-USE_LIBS += [
-    'mozglue',
-]
-
 if CONFIG['LIBFUZZER']:
     USE_LIBS += [ 'fuzzer' ]
     LOCAL_INCLUDES += [
         '/tools/fuzzing/libfuzzer',
     ]
 
 if CONFIG['_MSC_VER']:
     # Always enter a Windows program through wmain, whether or not we're
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -16,17 +16,16 @@
 #endif
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <time.h>
 
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
-#include "nsStringGlue.h"
 
 #ifdef XP_WIN
 #ifdef MOZ_ASAN
 // ASAN requires firefox.exe to be built with -MD, and it's OK if we don't
 // support Windows XP SP2 in ASAN builds.
 #define XRE_DONT_SUPPORT_XPSP2
 #endif
 #define XRE_WANT_ENVIRON
@@ -35,17 +34,17 @@
 #include "mozilla/sandboxing/SandboxInitialization.h"
 #endif
 #endif
 #include "BinaryPath.h"
 
 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
 
 #include "mozilla/Sprintf.h"
-#include "mozilla/Telemetry.h"
+#include "mozilla/StartupTimeline.h"
 #include "mozilla/WindowsDllBlocklist.h"
 
 #ifdef LIBFUZZER
 #include "FuzzerDefs.h"
 #endif
 
 #ifdef MOZ_LINUX_32_SSE2_STARTUP_ERROR
 #include <cpuid.h>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -33,16 +33,17 @@ pref("extensions.strictCompatibility", f
 // Specifies a minimum maxVersion an addon needs to say it's compatible with
 // for it to be compatible by default.
 pref("extensions.minCompatibleAppVersion", "4.0");
 // Temporary preference to forcibly make themes more safe with Australis even if
 // extensions.checkCompatibility=false has been set.
 pref("extensions.checkCompatibility.temporaryThemeOverride_minAppVersion", "29.0a1");
 
 pref("xpinstall.customConfirmationUI", true);
+pref("extensions.webextPermissionPrompts", true);
 
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
 pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
@@ -60,19 +61,16 @@ pref("extensions.hotfix.certs.2.sha1Fing
 
 // Check AUS for system add-on updates.
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
 pref("extensions.autoDisableScopes", 15);
 
-// Whether or not webextension themes are supported.
-pref("extensions.webextensions.themes.enabled", false);
-
 // Add-on content security policies.
 pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
 pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
 
 // Require signed add-ons by default
 pref("xpinstall.signatures.required", true);
 pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
 
@@ -160,41 +158,40 @@ pref("app.update.service.enabled", true)
 //  .. etc ..
 //
 pref("extensions.update.enabled", true);
 pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.interval", 86400);  // Check for updates to Extensions and
                                             // Themes every day
 // Non-symmetric (not shared by extensions) extension-specific [update] preferences
-pref("extensions.dss.enabled", false);          // Dynamic Skin Switching
 pref("extensions.dss.switchPending", false);    // Non-dynamic switch pending after next
                                                 // restart.
 
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties");
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
 
+pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,bookmark_star,bookmark_menu,downloads,home,app_menu,cut,copy,paste,new_window,new_private_window,save_page,print,history,full_screen,find,options,addons,developer,synced_tabs,open_file,sidebars,share_page,subscribe,text_encoding,email_link,forget,pocket");
+
 pref("lightweightThemes.update.enabled", true);
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
 pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.footer.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#f2d9b1\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.footer.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.footer.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"}]");
 
 #if defined(MOZ_WIDEVINE_EME)
 pref("browser.eme.ui.enabled", true);
 #else
 pref("browser.eme.ui.enabled", false);
 #endif
 
 // UI tour experience.
 pref("browser.uitour.enabled", true);
 pref("browser.uitour.loglevel", "Error");
 pref("browser.uitour.requireSecure", true);
 pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
 pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/");
-// This is used as a regexp match against the page's URL.
-pref("browser.uitour.readerViewTrigger", "^https:\\/\\/www\\.mozilla\\.org\\/[^\\/]+\\/firefox\\/reading\\/start");
 // How long to show a Hearbeat survey (two hours, in seconds)
 pref("browser.uitour.surveyDuration", 7200);
 
 pref("browser.customizemode.tip0.shown", false);
 pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize");
 
 pref("keyword.enabled", true);
 pref("browser.fixup.domainwhitelist.localhost", true);
@@ -531,16 +528,17 @@ pref("privacy.sanitize.timeSpan", 1);
 pref("privacy.sanitize.sanitizeOnShutdown", false);
 
 pref("privacy.sanitize.migrateFx3Prefs",    false);
 
 pref("privacy.panicButton.enabled",         true);
 
 pref("privacy.firstparty.isolate",                        false);
 pref("privacy.firstparty.isolate.restrict_opener_access", true);
+pref("privacy.resistFingerprinting", false);
 
 // Time until temporary permissions expire, in ms
 pref("privacy.temporary_permission_expire_time_ms",  3600000);
 
 pref("network.proxy.share_proxy_settings",  false); // use the same proxy settings for all protocols
 
 // simple gestures support
 pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
@@ -1190,39 +1188,32 @@ pref("browser.newtabpage.rows", 3);
 pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
 pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // endpoint to send newtab click and view pings
 pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
 
-// activates the remote-hosted newtab page
-pref("browser.newtabpage.remote", false);
-
-// remote newtab version targeted
-pref("browser.newtabpage.remote.version", "1");
-
-// Toggles endpoints allowed for remote newtab communications
-pref("browser.newtabpage.remote.mode", "production");
-
-// content-signature tests for remote newtab
-pref("browser.newtabpage.remote.content-signing-test", false);
-
-// verification keys for remote-hosted newtab page
-pref("browser.newtabpage.remote.keys", "");
+// activates Activity Stream
+pref("browser.newtabpage.activity-stream.enabled", false);
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("toolkit.startup.max_resumed_crashes", 3);
 
+// Whether we use pdfium to view content with the pdf mime type.
+// Note: if the pref is set to false while Firefox is open, it won't
+// take effect until there are no open pdfium tabs.
+pref("pdfium.enabled", false);
+
 // Completely disable pdf.js as an option to preview pdfs within firefox.
 // Note: if this is not disabled it does not necessarily mean pdf.js is the pdf
 // handler just that it is an option.
 pref("pdfjs.disabled", false);
 // Used by pdf.js to know the first time firefox is run with it installed so it
 // can become the default pdf viewer.
 pref("pdfjs.firstRun", true);
 // The values of preferredAction and alwaysAskBeforeHandling before pdf.js
@@ -1259,50 +1250,56 @@ pref("security.cert_pinning.enforcement_
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // All the Geolocation preferences are here.
 //
-// The request URL of the GeoLocation backend.
-#ifdef RELEASE_OR_BETA
+
+// Geolocation preferences for the RELEASE and "later" Beta channels.
+// Some of these prefs are specified even though they are redundant; they are
+// here for clarity and end-user experiments.
+#ifndef EARLY_BETA_OR_EARLIER
 pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
-#else
-pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
-#endif
 
 #ifdef XP_MACOSX
-#ifdef RELEASE_OR_BETA
 pref("geo.provider.use_corelocation", false);
-#else
-pref("geo.provider.use_corelocation", true);
-#endif
 #endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
 #ifdef MOZ_WIDGET_GTK
-#ifdef MOZ_GPSD
-#ifdef RELEASE_OR_BETA
 pref("geo.provider.use_gpsd", false);
+#endif
+
 #else
+
+// Geolocation preferences for Nightly/Aurora/Beta.
+pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
+
+#ifdef XP_MACOSX
+pref("geo.provider.use_corelocation", true);
+#endif
+
+// The native Windows location provider is only enabled in Nightly and likely to
+// be unstable. Set to false if things are really broken.
+#if defined(XP_WIN) && defined(NIGHTLY_BUILD)
+pref("geo.provider.ms-windows-location", true);
+#endif
+
+#if defined(MOZ_WIDGET_GTK) && defined(MOZ_GPSD)
 pref("geo.provider.use_gpsd", true);
 #endif
-#endif
+
 #endif
 
-// We keep allowing non-HTTPS geo requests on all the release
-// channels, for now.
-// TODO: default to false (or remove altogether) for #1072859.
-pref("geo.security.allowinsecure", true);
-
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // CustomizableUI state of the browser's user interface
@@ -1524,17 +1521,21 @@ pref("toolkit.pageThumbs.minHeight", 190
 
 // Enable speech synthesis
 pref("media.webspeech.synth.enabled", true);
 
 pref("browser.esedbreader.loglevel", "Error");
 
 pref("browser.laterrun.enabled", false);
 
+#ifdef EARLY_BETA_OR_EARLIER
+pref("browser.migrate.automigrate.enabled", true);
+#else
 pref("browser.migrate.automigrate.enabled", false);
+#endif
 // 4 here means the suggestion notification will be automatically
 // hidden the 4th day, so it will actually be shown on 3 different days.
 pref("browser.migrate.automigrate.daysToOfferUndo", 4);
 pref("browser.migrate.automigrate.ui.enabled", true);
 
 // See comments in bug 1340115 on how we got to these numbers.
 pref("browser.migrate.chrome.history.limit", 2000);
 pref("browser.migrate.chrome.history.maxAgeInDays", 180);
@@ -1576,22 +1577,16 @@ pref("browser.crashReports.unsubmittedCh
 
 // chancesUntilSuppress is how many times we'll show the unsubmitted
 // crash report notification across different days and shutdown
 // without a user choice before we suppress the notification for
 // some number of days.
 pref("browser.crashReports.unsubmittedCheck.chancesUntilSuppress", 4);
 pref("browser.crashReports.unsubmittedCheck.autoSubmit", false);
 
-#ifdef NIGHTLY_BUILD
-// Enable the (fairly costly) client/server validation on nightly only. The other prefs
-// controlling validation are located in /services/sync/services-sync.js
-pref("services.sync.validation.enabled", true);
-#endif
-
 // Preferences for the form autofill system extension
 pref("browser.formautofill.experimental", false);
 pref("browser.formautofill.enabled", false);
 pref("browser.formautofill.loglevel", "Warn");
 
 // Enable safebrowsing v4 tables (suffixed by "-proto") update.
 #ifdef NIGHTLY_BUILD
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,goog-malware-proto,goog-unwanted-proto,test-malware-simple,test-unwanted-simple");
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -7,39 +7,30 @@
 // Services = object with smart getters for common XPCOM services
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 function init(aEvent) {
   if (aEvent.target != document)
     return;
 
-  try {
-    var distroId = Services.prefs.getCharPref("distribution.id");
-    if (distroId) {
-      var distroVersion = Services.prefs.getCharPref("distribution.version");
-
-      var distroIdField = document.getElementById("distributionId");
-      distroIdField.value = distroId + " - " + distroVersion;
-      distroIdField.style.display = "block";
+  var distroId = Services.prefs.getCharPref("distribution.id", "");
+  if (distroId) {
+    var distroVersion = Services.prefs.getCharPref("distribution.version");
 
-      try {
-        // This is in its own try catch due to bug 895473 and bug 900925.
-        var distroAbout = Services.prefs.getComplexValue("distribution.about",
-          Components.interfaces.nsISupportsString);
-        var distroField = document.getElementById("distribution");
-        distroField.value = distroAbout;
-        distroField.style.display = "block";
-      } catch (ex) {
-        // Pref is unset
-        Components.utils.reportError(ex);
-      }
+    var distroIdField = document.getElementById("distributionId");
+    distroIdField.value = distroId + " - " + distroVersion;
+    distroIdField.style.display = "block";
+
+    var distroAbout = Services.prefs.getStringPref("distribution.about", "");
+    if (distroAbout) {
+      var distroField = document.getElementById("distribution");
+      distroField.value = distroAbout;
+      distroField.style.display = "block";
     }
-  } catch (e) {
-    // Pref is unset
   }
 
   // Include the build ID and display warning if this is an "a#" (nightly or aurora) build
   let versionField = document.getElementById("version");
   let version = Services.appinfo.version;
   if (/a\d+$/.test(version)) {
     let buildID = Services.appinfo.appBuildID;
     let year = buildID.slice(0, 4);
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -91,17 +91,17 @@
         document.getElementById("certificateErrorReporting").style.display = "block";
       }
 
       function showPrefChangeContainer() {
         const panel = document.getElementById("prefChangeContainer");
         panel.style.display = "block";
         document.getElementById("netErrorButtonContainer").style.display = "none";
         document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
-          const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
+          const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles: true});
           document.dispatchEvent(event);
         });
         addAutofocus("prefResetButton", "beforeend");
       }
 
       function setupAdvancedButton() {
         // Get the hostname and add it to the panel
         var panel = document.getElementById("badCertAdvancedPanel");
@@ -122,17 +122,17 @@
             // information panel is hidden as well, since it's opened by the
             // error code link in the advanced panel.
             var div = document.getElementById("certificateErrorDebugInformation");
             div.style.display = "none";
           }
 
           if (panel.style.display == "block") {
             // send event to trigger telemetry ping
-            var event = new CustomEvent("AboutNetErrorUIExpanded", {bubbles:true});
+            var event = new CustomEvent("AboutNetErrorUIExpanded", {bubbles: true});
             document.dispatchEvent(event);
           }
         });
 
         if (!gIsCertError) {
           return;
         }
 
@@ -250,17 +250,17 @@
 
         window.addEventListener("AboutNetErrorOptions", function(evt) {
         // Pinning errors are of type nssFailure2
           if (getErrorCode() == "nssFailure2") {
             document.getElementById("learnMoreContainer").style.display = "block";
             let learnMoreLink = document.getElementById("learnMoreLink");
             // nssFailure2 also gets us other non-overrideable errors. Choose
             // a "learn more" link based on description:
-            if (getDescription().includes("mozilla_pkix_error_key_pinning_failure")) {
+            if (getDescription().includes("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE")) {
               learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
             }
 
             var options = JSON.parse(evt.detail);
             if (options && options.enabled) {
               var checkbox = document.getElementById("automaticallyReportInFuture");
               showCertificateErrorReporting();
               if (options.automatic) {
@@ -287,17 +287,17 @@
               showPrefChangeContainer();
             }
           }
           if (getErrorCode() == "sslv3Used") {
             setupAdvancedButton();
           }
         }, true, true);
 
-        var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+        var event = new CustomEvent("AboutNetErrorLoad", {bubbles: true});
         document.dispatchEvent(event);
 
         if (err == "inadequateSecurityError") {
           // Remove the "Try again" button for HTTP/2 inadequate security as it
           // is useless.
           document.getElementById("errorTryAgain").style.display = "none";
 
           var container = document.getElementById("errorLongDesc");
@@ -310,17 +310,17 @@
       }
 
       function initPageCaptivePortal() {
         document.body.className = "captiveportal";
         document.title = document.getElementById("captivePortalPageTitle").textContent;
 
         document.getElementById("openPortalLoginPageButton")
                 .addEventListener("click", () => {
-          let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
+          let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles: true});
           document.dispatchEvent(event);
         });
 
         addAutofocus("openPortalLoginPageButton");
         setupAdvancedButton();
 
         addDomainErrorLinks();
 
@@ -357,17 +357,17 @@
             // Display error reporting UI
             document.getElementById("certificateErrorReporting").style.display = "block";
 
             // set the checkbox
             checkbox.checked = !!options.automatic;
           }
         }, true, true);
 
-        let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+        let event = new CustomEvent("AboutNetErrorLoad", {bubbles: true});
         document.getElementById("advancedButton").dispatchEvent(event);
 
         addDomainErrorLinks();
       }
 
       /* Only do autofocus if we're the toplevel frame; otherwise we
          don't want to call attention to ourselves!  The key part is
          that autofocus happens on insertion into the tree, so we
@@ -625,17 +625,17 @@
       <div id="netErrorButtonContainer" class="button-container">
         <button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
       </div>
 
       <!-- UI for option to report certificate errors to Mozilla. Removed on
            init for other error types .-->
       <div id="certificateErrorReporting">
         <p class="toggle-container-with-text">
-          <input type="checkbox" id="automaticallyReportInFuture" />
+          <input type="checkbox" id="automaticallyReportInFuture" role="checkbox" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
         </p>
       </div>
 
       <div id="advancedPanelContainer">
         <div id="badCertAdvancedPanel" class="advanced-panel">
           <p id="badCertTechnicalInfo"/>
           <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
--- a/browser/base/content/aboutTabCrashed.js
+++ b/browser/base/content/aboutTabCrashed.js
@@ -100,17 +100,17 @@ var AboutTabCrashed = {
       let el = document.getElementById(targetID);
       el.addEventListener("click", this);
     });
 
     // For setting "emailMe" checkbox automatically on email value change.
     document.getElementById("email").addEventListener("input", this);
 
     // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
-    let event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
+    let event = new CustomEvent("AboutTabCrashedLoad", {bubbles: true});
     document.dispatchEvent(event);
 
     sendAsyncMessage("Load");
   },
 
   onClick(event) {
     switch (event.target.id) {
       case "closeTab": {
@@ -197,17 +197,17 @@ var AboutTabCrashed = {
     } else {
       this.showCrashReportUI(false);
     }
 
     if (data.requestAutoSubmit) {
       document.getElementById("requestAutoSubmit").hidden = false;
     }
 
-    let event = new CustomEvent("AboutTabCrashedReady", {bubbles:true});
+    let event = new CustomEvent("AboutTabCrashedReady", {bubbles: true});
     document.dispatchEvent(event);
   },
 
   /**
    * Handler for when the parent reports that the crash report associated
    * with this about:tabcrashed page has been sent.
    */
   onCrashReportSent() {
--- a/browser/base/content/aboutTabCrashed.xhtml
+++ b/browser/base/content/aboutTabCrashed.xhtml
@@ -44,43 +44,43 @@
 
       <div id="reportBox">
         <h2>&tabCrashed.requestHelp;</h2>
         <p>&tabCrashed.requestHelpMessage;</p>
 
         <h2>&tabCrashed.requestReport;</h2>
 
         <div class="checkbox-with-label">
-          <input type="checkbox" id="sendReport"/>
+          <input type="checkbox" id="sendReport" role="checkbox"/>
           <label for="sendReport">&tabCrashed.sendReport2;</label>
         </div>
 
         <ul id="options">
           <li>
             <textarea id="comments" placeholder="&tabCrashed.commentPlaceholder2;" rows="4"></textarea>
           </li>
 
           <li class="checkbox-with-label">
-            <input type="checkbox" id="includeURL"/>
+            <input type="checkbox" id="includeURL" role="checkbox"/>
             <label for="includeURL">&tabCrashed.includeURL2;</label>
           </li>
 
           <li id="requestEmail" hidden="true">
             <div class="checkbox-with-label">
-              <input type="checkbox" id="emailMe"/>
+              <input type="checkbox" id="emailMe" role="checkbox"/>
               <label for="emailMe">&tabCrashed.emailMe;</label>
             </div>
             <input type="text" id="email" placeholder="&tabCrashed.emailPlaceholder;"/>
           </li>
         </ul>
 
         <div id="requestAutoSubmit" hidden="true">
           <h2>&tabCrashed.requestAutoSubmit2;</h2>
           <div class="checkbox-with-label">
-            <input type="checkbox" id="autoSubmit"/>
+            <input type="checkbox" id="autoSubmit" role="checkbox"/>
             <label for="autoSubmit">&tabCrashed.autoSubmit2;</label>
           </div>
         </div>
       </div>
 
       <p id="reportSent">&tabCrashed.reportSent;</p>
 
       <div class="button-container">
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -25,27 +25,24 @@ const OBSERVER_TOPICS = [
 ];
 
 function log(msg) {
   // dump("FXA: " + msg + "\n");
 }
 
 function getPreviousAccountNameHash() {
   try {
-    return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
+    return Services.prefs.getStringPref(PREF_LAST_FXA_USER);
   } catch (_) {
     return "";
   }
 }
 
 function setPreviousAccountNameHash(acctName) {
-  let string = Cc["@mozilla.org/supports-string;1"]
-               .createInstance(Ci.nsISupportsString);
-  string.data = sha256(acctName);
-  Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
+  Services.prefs.setStringPref(PREF_LAST_FXA_USER, sha256(acctName));
 }
 
 function needRelinkWarning(acctName) {
   let prevAcctHash = getPreviousAccountNameHash();
   return prevAcctHash && prevAcctHash != sha256(acctName);
 }
 
 // Given a string, returns the SHA265 hash in base64
@@ -427,24 +424,19 @@ function show(id, childId) {
 // Migrate sync data from the default profile to the dev-edition profile.
 // Returns a promise of a true value if migration succeeded, or false if it
 // failed.
 function migrateToDevEdition(urlParams) {
   let defaultProfilePath;
   try {
     defaultProfilePath = window.getDefaultProfilePath();
   } catch (e) {} // no default profile.
-  let migrateSyncCreds = false;
-  if (defaultProfilePath) {
-    try {
-      migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
-    } catch (e) {}
-  }
 
-  if (!migrateSyncCreds) {
+  if (!defaultProfilePath ||
+      !Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition", false)) {
     return Promise.resolve(false);
   }
 
   Cu.import("resource://gre/modules/osfile.jsm");
   let fxAccountsStorage = OS.Path.join(defaultProfilePath, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
   return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
     let accountData = JSON.parse(text).accountData;
     updateDisplayedEmail(accountData);
deleted file mode 100644
index 83af78d6c48476cbf19f517c4967642fcdcc42fd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -48,17 +48,17 @@ window.addEventListener("pageshow", func
   // later and may use asynchronous getters.
   window.gObserver.observe(document.documentElement, { attributes: true });
   window.gObserver.observe(document.getElementById("launcher"), { attributes: true });
   fitToWidth();
   setupSearch();
   window.addEventListener("resize", fitToWidth);
 
   // Ask chrome to update snippets.
-  var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
+  var event = new CustomEvent("AboutHomeLoad", {bubbles: true});
   document.dispatchEvent(event);
 });
 
 window.addEventListener("pagehide", function() {
   window.gObserver.disconnect();
   window.removeEventListener("resize", fitToWidth);
 });
 
@@ -236,30 +236,30 @@ function setupSearch() {
                                     "abouthome", "homepage");
   }
 }
 
 /**
  * Inform the test harness that we're done loading the page.
  */
 function loadCompleted() {
-  var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
+  var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles: true});
   document.dispatchEvent(event);
 }
 
 /**
  * Update the local snippets from the remote storage, then show them through
  * showSnippets.
  */
 function loadSnippets() {
   if (!gSnippetsMap)
     throw new Error("Snippets map has not properly been initialized");
 
   // Allow tests to modify the snippets map before using it.
-  var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true});
+  var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles: true});
   document.dispatchEvent(event);
 
   // Check cached snippets version.
   let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
   let currentVersion = document.documentElement.getAttribute("snippetsVersion");
   if (cachedVersion < currentVersion) {
     // The cached snippets are old and unsupported, restart from scratch.
     gSnippetsMap.clear();
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -130,17 +130,17 @@
         if (!getOverride()) {
           var btn = document.getElementById("ignoreWarningButton");
           if (btn) {
             btn.remove();
           }
         }
 
         // Inform the test harness that we're done loading the page
-        var event = new CustomEvent("AboutBlockedLoaded", {bubbles:true});
+        var event = new CustomEvent("AboutBlockedLoaded", {bubbles: true});
         document.dispatchEvent(event);
       }
     ]]></script>
   </head>
 
   <body dir="&locale.dir;">
     <div id="errorPageContainer" class="container">
 
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -85,16 +85,17 @@ const gXPInstallObserver = {
     }
 
     const anchorID = "addons-notification-icon";
 
     // Make notifications persistent
     var options = {
       displayURI: installInfo.originatingURI,
       persistent: true,
+      hideClose: true,
     };
 
     let acceptInstallation = () => {
       for (let install of installInfo.installs)
         install.install();
       installInfo = null;
 
       Services.telemetry
@@ -784,86 +785,8 @@ var LightWeightThemeWebInstaller = {
     if (!uri.schemeIs("https")) {
       return false;
     }
 
     let pm = Services.perms;
     return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
   }
 };
-
-/*
- * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
- */
-var LightweightThemeListener = {
-  _modifiedStyles: [],
-
-  init() {
-    XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
-      for (let i = document.styleSheets.length - 1; i >= 0; i--) {
-        let sheet = document.styleSheets[i];
-        if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
-          return sheet;
-      }
-      return undefined;
-    });
-
-    Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
-    Services.obs.addObserver(this, "lightweight-theme-optimized", false);
-    if (document.documentElement.hasAttribute("lwtheme"))
-      this.updateStyleSheet(document.documentElement.style.backgroundImage);
-  },
-
-  uninit() {
-    Services.obs.removeObserver(this, "lightweight-theme-styling-update");
-    Services.obs.removeObserver(this, "lightweight-theme-optimized");
-  },
-
-  /**
-   * Append the headerImage to the background-image property of all rulesets in
-   * browser-lightweightTheme.css.
-   *
-   * @param headerImage - a string containing a CSS image for the lightweight theme header.
-   */
-  updateStyleSheet(headerImage) {
-    if (!this.styleSheet)
-      return;
-    this.substituteRules(this.styleSheet.cssRules, headerImage);
-  },
-
-  substituteRules(ruleList, headerImage, existingStyleRulesModified = 0) {
-    let styleRulesModified = 0;
-    for (let i = 0; i < ruleList.length; i++) {
-      let rule = ruleList[i];
-      if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
-        // Add the number of modified sub-rules to the modified count
-        styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
-      } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
-        if (!rule.style.backgroundImage)
-          continue;
-        let modifiedIndex = existingStyleRulesModified + styleRulesModified;
-        if (!this._modifiedStyles[modifiedIndex])
-          this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
-
-        rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
-        styleRulesModified++;
-      } else {
-        Cu.reportError("Unsupported rule encountered");
-      }
-    }
-    return styleRulesModified;
-  },
-
-  // nsIObserver
-  observe(aSubject, aTopic, aData) {
-    if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
-          !this.styleSheet)
-      return;
-
-    if (aTopic == "lightweight-theme-optimized" && aSubject != window)
-      return;
-
-    let themeData = JSON.parse(aData);
-    if (!themeData)
-      return;
-    this.updateStyleSheet("url(" + themeData.headerURL + ")");
-  },
-};
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -16,23 +16,23 @@
         <menuitem id="context-forward"
                   class="menuitem-iconic"
                   tooltiptext="&forwardButton.tooltip;"
                   aria-label="&forwardCmd.label;"
                   command="Browser:ForwardOrForwardDuplicate"
                   onclick="checkForMiddleClick(this, event);"/>
         <menuitem id="context-reload"
                   class="menuitem-iconic"
-                  tooltiptext="&reloadButton.tooltip;"
+                  tooltip="dynamic-shortcut-tooltip"
                   aria-label="&reloadCmd.label;"
                   oncommand="gContextMenu.reload(event);"
                   onclick="checkForMiddleClick(this, event);"/>
         <menuitem id="context-stop"
                   class="menuitem-iconic"
-                  tooltiptext="&stopButton.tooltip;"
+                  tooltip="dynamic-shortcut-tooltip"
                   aria-label="&stopCmd.label;"
                   command="Browser:Stop"/>
         <menuitem id="context-bookmarkpage"
                   class="menuitem-iconic"
                   observes="bookmarkThisPageBroadcaster"
                   aria-label="&bookmarkPageCmd2.label;"
                   oncommand="gContextMenu.bookmarkThisPage();"/>
       </menugroup>
--- a/browser/base/content/browser-ctrlTab.js
+++ b/browser/base/content/browser-ctrlTab.js
@@ -1,8 +1,9 @@
+
 /* 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/. */
 
 /**
  * Tab previews utility, produces thumbnails
  */
 var tabPreviews = {
@@ -198,26 +199,27 @@ var ctrlTab = {
       tabPreviews.init();
 
       this._initRecentlyUsedTabs();
       this._init(true);
     }
   },
 
   uninit: function ctrlTab_uninit() {
-    this._recentlyUsedTabs = null;
-    this._init(false);
+    if (this._recentlyUsedTabs) {
+      this._recentlyUsedTabs = null;
+      this._init(false);
+    }
   },
 
   prefName: "browser.ctrlTab.previews",
   readPref: function ctrlTab_readPref() {
     var enable =
       gPrefService.getBoolPref(this.prefName) &&
-      (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
-       !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
+      !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders", false);
 
     if (enable)
       this.init();
     else
       this.uninit();
   },
   observe(aSubject, aTopic, aPrefName) {
     this.readPref();
@@ -235,17 +237,17 @@ var ctrlTab = {
 
   updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
     if (aPreview == this.showAllButton)
       return;
 
     aPreview._tab = aTab;
 
     if (aPreview.firstChild)
-      aPreview.removeChild(aPreview.firstChild);
+      aPreview.firstChild.remove();
     if (aTab) {
       let canvasWidth = this.canvasWidth;
       let canvasHeight = this.canvasHeight;
       aPreview.appendChild(tabPreviews.get(aTab));
       aPreview.setAttribute("label", aTab.label);
       aPreview.setAttribute("tooltiptext", aTab.label);
       aPreview.setAttribute("canvaswidth", canvasWidth);
       aPreview.setAttribute("canvasstyle",
@@ -468,21 +470,25 @@ var ctrlTab = {
   },
 
   handleEvent: function ctrlTab_handleEvent(event) {
     switch (event.type) {
       case "SSWindowRestored":
         this._initRecentlyUsedTabs();
         break;
       case "TabAttrModified":
-        // tab attribute modified (e.g. label, busy, image, selected)
-        for (let i = this.previews.length - 1; i >= 0; i--) {
-          if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
-            this.updatePreview(this.previews[i], event.target);
-            break;
+        // tab attribute modified (i.e. label, busy, image)
+        // update preview only if tab attribute modified in the list
+        if (event.detail.changed.some(
+          (elem, ind, arr) => ["label", "busy", "image"].includes(elem))) {
+          for (let i = this.previews.length - 1; i >= 0; i--) {
+            if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
+              this.updatePreview(this.previews[i], event.target);
+              break;
+            }
           }
         }
         break;
       case "TabSelect":
         this.detachTab(event.target);
         this.attachTab(event.target, 0);
         break;
       case "TabOpen":
@@ -525,25 +531,25 @@ var ctrlTab = {
     this._recentlyUsedTabs =
       Array.filter(gBrowser.tabs, tab => !tab.closing)
            .sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
   },
 
   _init: function ctrlTab__init(enable) {
     var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
 
-    window[toggleEventListener]("SSWindowRestored", this, false);
+    window[toggleEventListener]("SSWindowRestored", this);
 
     var tabContainer = gBrowser.tabContainer;
-    tabContainer[toggleEventListener]("TabOpen", this, false);
-    tabContainer[toggleEventListener]("TabAttrModified", this, false);
-    tabContainer[toggleEventListener]("TabSelect", this, false);
-    tabContainer[toggleEventListener]("TabClose", this, false);
+    tabContainer[toggleEventListener]("TabOpen", this);
+    tabContainer[toggleEventListener]("TabAttrModified", this);
+    tabContainer[toggleEventListener]("TabSelect", this);
+    tabContainer[toggleEventListener]("TabClose", this);
 
-    document[toggleEventListener]("keypress", this, false);
+    document[toggleEventListener]("keypress", this);
     gBrowser.mTabBox.handleCtrlTab = !enable;
 
     if (enable)
       PageThumbs.addExpirationFilter(this);
     else
       PageThumbs.removeExpirationFilter(this);
 
     // If we're not running, hide the "Show All Tabs" menu item,
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -1,18 +1,143 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+                                  "resource://gre/modules/DeferredTask.jsm");
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+
+const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+const PREF_UPDATE_DELAY = 2000;
+
+const SETTABLE_PREFS = new Set([
+  PREF_VIDEO_SELECTED_ACTION,
+  PREF_AUDIO_SELECTED_ACTION,
+  PREF_SELECTED_ACTION,
+  PREF_VIDEO_SELECTED_READER,
+  PREF_AUDIO_SELECTED_READER,
+  PREF_SELECTED_READER,
+  PREF_VIDEO_SELECTED_WEB,
+  PREF_AUDIO_SELECTED_WEB,
+  PREF_SELECTED_WEB
+]);
+
+const EXECUTABLE_PREFS = new Set([
+  PREF_SELECTED_APP,
+  PREF_VIDEO_SELECTED_APP,
+  PREF_AUDIO_SELECTED_APP
+]);
+
+const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
+const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
+                                      "feeds.log", false);
+
+function LOG(str) {
+  if (SHOULD_LOG)
+    dump("*** Feeds: " + str + "\n");
+}
+
+function getPrefActionForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_ACTION;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_ACTION;
+
+    default:
+      return PREF_SELECTED_ACTION;
+  }
+}
+
+function getPrefReaderForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_READER;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_READER;
+
+    default:
+      return PREF_SELECTED_READER;
+  }
+}
+
+function getPrefWebForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_WEB;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_WEB;
+
+    default:
+      return PREF_SELECTED_WEB;
+  }
+}
+
+function getPrefAppForType(t) {
+  switch (t) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_APP;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_APP;
+
+    default:
+      return PREF_SELECTED_APP;
+  }
+}
+
+/**
+ * Maps a feed type to a maybe-feed mimetype.
+ */
+function getMimeTypeForFeedType(aFeedType) {
+  switch (aFeedType) {
+    case Ci.nsIFeed.TYPE_VIDEO:
+      return TYPE_MAYBE_VIDEO_FEED;
+
+    case Ci.nsIFeed.TYPE_AUDIO:
+      return TYPE_MAYBE_AUDIO_FEED;
+
+    default:
+      return TYPE_MAYBE_FEED;
+  }
+}
+
 /**
  * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
  * and shows UI when they are discovered.
  */
 var FeedHandler = {
+  _prefChangeCallback: null,
+
   /** Called when the user clicks on the Subscribe to This Page... menu item,
    * or when the user clicks the feed button when the page contains multiple
    * feeds.
    * Builds a menu of unique feeds associated with the page, and if there
    * is only one, shows the feed inline in the browser window.
    * @param   container
    *          The feed list container (menupopup or subview) to be populated.
    * @param   isSubview
@@ -190,17 +315,18 @@ var FeedHandler = {
           } catch (e) {}
         }
         break;
     }
 
     return file.leafName;
   },
 
-  chooseClientApp(aTitle, aPrefName, aBrowser) {
+  _chooseClientApp(aTitle, aTypeName, aBrowser) {
+    const prefName = getPrefAppForType(aTypeName);
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 
     fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
     fp.appendFilters(Ci.nsIFilePicker.filterApps);
 
     fp.open((aResult) => {
       if (aResult == Ci.nsIFilePicker.returnOK) {
         let selectedApp = fp.file;
@@ -217,17 +343,17 @@ var FeedHandler = {
               appName = AppConstants.MOZ_MACBUNDLE_NAME;
               break;
             default:
               appName = AppConstants.MOZ_APP_NAME + "-bin";
               break;
           }
 
           if (fp.file.leafName != appName) {
-            Services.prefs.setComplexValue(aPrefName, Ci.nsILocalFile, selectedApp);
+            Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
             aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
                                                     { name: this._getFileDisplayName(selectedApp),
                                                       type: "SelectedAppMenuItem" });
           }
         }
       }
     });
 
@@ -272,79 +398,246 @@ var FeedHandler = {
       // nsIProcess instance
       let p = Cc["@mozilla.org/process/util;1"]
                 .createInstance(Ci.nsIProcess);
       p.init(clientApp);
       p.run(false, [aSpec], 1);
     }
   },
 
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+
   init() {
     window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
-    window.messageManager.addMessageListener("FeedWriter:RequestClientAppName", this);
-    window.messageManager.addMessageListener("FeedWriter:SetFeedCharPref", this);
-    window.messageManager.addMessageListener("FeedWriter:SetFeedComplexString", this);
+    window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
+    window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
     window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
 
     Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
+
+    const prefs = Services.prefs;
+    prefs.addObserver(PREF_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_SELECTED_WEB, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
+    prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
   },
 
   uninit() {
     Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
+
+    this._prefChangeCallback = null;
+  },
+
+  // nsIObserver
+  observe(subject, topic, data) {
+    if (topic == "nsPref:changed") {
+      LOG(`Pref changed ${data}`)
+      if (this._prefChangeCallback) {
+        this._prefChangeCallback.disarm();
+      }
+      // Multiple prefs are set at the same time, debounce to reduce noise
+      // This can happen in one feed and we want to message all feed pages
+      this._prefChangeCallback = new DeferredTask(() => {
+        this._prefChanged(data);
+      }, PREF_UPDATE_DELAY);
+      this._prefChangeCallback.arm();
+    }
+  },
+
+  _prefChanged(prefName) {
+    // Don't observe for PREF_*SELECTED_APP as user likely just picked one
+    // That is also handled by SetApplicationLauncherMenuItem call
+    // Rather than the others which happen on subscription
+    switch (prefName) {
+      case PREF_SELECTED_READER:
+      case PREF_SELECTED_WEB:
+      case PREF_VIDEO_SELECTED_READER:
+      case PREF_VIDEO_SELECTED_WEB:
+      case PREF_AUDIO_SELECTED_READER:
+      case PREF_AUDIO_SELECTED_WEB:
+      case PREF_SELECTED_ACTION:
+      case PREF_VIDEO_SELECTED_ACTION:
+      case PREF_AUDIO_SELECTED_ACTION:
+        const response = {
+         default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
+         [Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
+         [Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
+        };
+        Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
+                                          response);
+        break;
+    }
+  },
+
+  _initSubscriptionUIResponse(feedType) {
+    const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+               getService(Ci.nsIWebContentConverterService);
+    const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
+    const handlers = [];
+    for (let handler of handlersRaw) {
+      LOG(`Handler found: ${handler}`);
+      handlers.push({
+        name: handler.name,
+        uri: handler.uri
+      });
+    }
+    let showFirstRunUI = true;
+    // eslint-disable-next-line mozilla/use-default-preference-values
+    try {
+      showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
+    } catch (ex) { }
+    const response = { handlers, showFirstRunUI };
+    let selectedClientApp;
+    const feedTypePref = getPrefAppForType(feedType);
+    try {
+      selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
+    } catch (ex) {
+      // Just do nothing, then we won't bother populating
+    }
+
+    let defaultClientApp = null;
+    try {
+      // This can sometimes not exist
+      defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
+                           .getService(Ci.nsIShellService)
+                           .defaultFeedReader;
+    } catch (ex) {
+      // Just do nothing, then we don't bother populating
+    }
+
+    if (selectedClientApp && selectedClientApp.exists()) {
+      if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
+        // Only set the default menu item if it differs from the selected one
+        response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
+      }
+      response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
+    }
+    response.reader = this._getReaderForType(feedType);
+    return response;
+  },
+
+  _setPref(aPrefName, aPrefValue, aIsComplex = false) {
+    LOG(`FeedWriter._setPref ${aPrefName}`);
+    // Ensure we have a pref that is settable
+    if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
+      if (aIsComplex) {
+        Services.prefs.setStringPref(aPrefName, aPrefValue);
+      } else {
+        Services.prefs.setCharPref(aPrefName, aPrefValue);
+      }
+    } else {
+      LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
+    }
+  },
+
+  _getReaderForType(feedType) {
+    let prefs = Services.prefs;
+    let handler = "bookmarks";
+    let url;
+    // eslint-disable-next-line mozilla/use-default-preference-values
+    try {
+      handler = prefs.getCharPref(getPrefReaderForType(feedType));
+    } catch (ex) { }
+
+    if (handler === "web") {
+      try {
+        url = prefs.getStringPref(getPrefWebForType(feedType));
+      } catch (ex) {
+        LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
+        url = null;
+      }
+    }
+    const alwaysUse = this._getAlwaysUseState(feedType);
+    const action = prefs.getCharPref(getPrefActionForType(feedType));
+    return { handler, url, alwaysUse, action };
+  },
+
+  _getAlwaysUseState(feedType) {
+    try {
+      return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
+    } catch (ex) { }
+    return false;
   },
 
   receiveMessage(msg) {
+    let handler;
     switch (msg.name) {
-      case "FeedWriter:ChooseClientApp":
-        this.chooseClientApp(msg.data.title, msg.data.prefName, msg.target);
+      case "FeedWriter:GetSubscriptionUI":
+        const response = this._initSubscriptionUIResponse(msg.data.feedType);
+        msg.target.messageManager
+           .sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
+                            response);
         break;
-      case "FeedWriter:RequestClientAppName":
-        let selectedClientApp;
-        try {
-          selectedClientApp = Services.prefs.getComplexValue(msg.data.feedTypePref, Ci.nsILocalFile);
-        } catch (ex) {
-          // Just do nothing, then we won't bother populating
-        }
-
-        let defaultClientApp = null;
-        try {
-          // This can sometimes not exist
-          defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
-                               .getService(Ci.nsIShellService)
-                               .defaultFeedReader;
-        } catch (ex) {
-          // Just do nothing, then we don't bother populating
-        }
-
-        if (selectedClientApp && selectedClientApp.exists()) {
-          if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
-            // Only set the default menu item if it differs from the selected one
-            msg.target.messageManager
-               .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
-                                { name: this._getFileDisplayName(defaultClientApp),
-                                  type: "DefaultAppMenuItem" });
-          }
-          msg.target.messageManager
-             .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
-                              { name: this._getFileDisplayName(selectedClientApp),
-                                type: "SelectedAppMenuItem" });
-        }
+      case "FeedWriter:ChooseClientApp":
+        this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
         break;
       case "FeedWriter:ShownFirstRun":
-        Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false);
-        break;
-      case "FeedWriter:SetFeedCharPref":
-        Services.prefs.setCharPref(msg.data.pref, msg.data.value);
+        Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
         break;
-      case "FeedWriter:SetFeedComplexString": {
-        let supportsString = Cc["@mozilla.org/supports-string;1"].
-                             createInstance(Ci.nsISupportsString);
-        supportsString.data = msg.data.value;
-        Services.prefs.setComplexValue(msg.data.pref, Ci.nsISupportsString, supportsString);
-        break;
-      }
+      case "FeedWriter:SetFeedPrefsAndSubscribe":
+        const settings = msg.data;
+        if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
+          LOG(`Invalid action ${settings.action}`);
+          return;
+        }
+        if (!settings.reader || !VALID_READERS.has(settings.reader)) {
+          LOG(`Invalid reader ${settings.reader}`);
+          return;
+        }
+        const actionPref = getPrefActionForType(settings.feedType);
+        this._setPref(actionPref, settings.action);
+        const readerPref = getPrefReaderForType(settings.feedType);
+        this._setPref(readerPref, settings.reader);
+        handler = null;
+
+        switch (settings.reader) {
+          case "web":
+            // This is a web set URI by content using window.registerContentHandler()
+            // Lets make sure we know about it before setting it
+            const webPref = getPrefWebForType(settings.feedType);
+            let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+                       getService(Ci.nsIWebContentConverterService);
+            // If the user provided an invalid web URL this function won't give us a reference
+            handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
+            if (handler) {
+              this._setPref(webPref, settings.uri, true);
+              if (settings.useAsDefault) {
+                wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
+              }
+              msg.target.messageManager
+                 .sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
+                                  { redirect: handler.getHandlerURI(settings.feedLocation) });
+            } else {
+              LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
+            }
+            break;
+          default:
+            const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+                                getService(Ci.nsIFeedResultService);
+
+            feedService.addToClientReader(settings.feedLocation,
+                                          settings.feedTitle,
+                                          settings.feedSubtitle,
+                                          settings.feedType,
+                                          settings.reader);
+         }
+         break;
       case "FeedConverter:ExecuteClientApp":
-        this.executeClientApp(msg.data.spec, msg.data.title,
-                              msg.data.subtitle, msg.data.feedHandler);
+        // Always check feedHandler is from a set array of executable prefs
+        if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
+          this.executeClientApp(msg.data.spec, msg.data.title,
+                                msg.data.subtitle, msg.data.feedHandler);
+        } else {
+          LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
+        }
         break;
     }
   },
 };
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -152,20 +152,17 @@ var gFxAccounts = {
 
   handleEvent(event) {
     this._inCustomizationMode = event.type == "customizationstarting";
     this.updateUI();
   },
 
   // Note that updateUI() returns a Promise that's only used by tests.
   updateUI() {
-    let profileInfoEnabled = false;
-    try {
-      profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
-    } catch (e) { }
+    let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
 
     this.panelUIFooter.hidden = false;
 
     // Make sure the button is disabled in customization mode.
     if (this._inCustomizationMode) {
       this.panelUIStatus.setAttribute("disabled", "true");
       this.panelUILabel.setAttribute("disabled", "true");
       this.panelUIAvatar.setAttribute("disabled", "true");
@@ -321,24 +318,31 @@ var gFxAccounts = {
       replaceQueryString: true
     });
   },
 
   openSignInAgainPage(entryPoint) {
     this.openAccountsPage("reauth", { entrypoint: entryPoint });
   },
 
+  async openDevicesManagementPage(entryPoint) {
+    let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
+    switchToTabHavingURI(url, true, {
+      replaceQueryString: true
+    });
+  },
+
   sendTabToDevice(url, clientId, title) {
     Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
   },
 
   populateSendTabToDevicesMenu(devicesPopup, url, title) {
     // remove existing menu items
     while (devicesPopup.hasChildNodes()) {
-      devicesPopup.removeChild(devicesPopup.firstChild);
+      devicesPopup.firstChild.remove();
     }
 
     const fragment = document.createDocumentFragment();
 
     const onTargetDeviceCommand = (event) => {
       let clients = event.target.getAttribute("clientId") ?
         [event.target.getAttribute("clientId")] :
         this.remoteClients.map(client => client.id);
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -244,16 +244,20 @@ let gDecoderDoctorHandler = {
     //   to store at-issue formats.
     // - 'formats' contains a comma-separated list of formats (or key systems)
     //   that suffer the issue. These are kept in a pref, which the backend
     //   uses to later find when an issue is resolved.
     // - 'isSolved' is true when the notification actually indicates the
     //   resolution of that issue, to be reported as telemetry.
     let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
     type = type.toLowerCase();
+    // Error out early on invalid ReportId
+    if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
+      return
+    }
     let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
     if (!title) {
       return;
     }
 
     // We keep the list of formats in prefs for the sake of the decoder itself,
     // which reads it to determine when issues get solved for these formats.
     // (Writing prefs from e10s content is now allowed.)
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -292,16 +292,17 @@
                           accesskey="&fullScreenCmd.accesskey;"
                           label="&fullScreenCmd.label;"
                           key="key_fullScreen"
                           type="checkbox"
                           observes="View:FullScreen"/>
 #endif
                 <menuitem id="menu_readerModeItem"
                           observes="View:ReaderView"
+                          key="key_toggleReaderMode"
                           hidden="true"/>
                 <menuitem id="menu_showAllTabs"
                           hidden="true"
                           accesskey="&showAllTabsCmd.accesskey;"
                           label="&showAllTabsCmd.label;"
                           command="Browser:ShowAllTabs"
                           key="key_showAllTabs"/>
                 <menuseparator hidden="true" id="documentDirection-separator"/>
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -741,17 +741,17 @@ HistoryMenu.prototype = {
    * Populate when the history menu is opened
    */
   populateUndoSubmenu: function PHM_populateUndoSubmenu() {
     var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
     var undoPopup = undoMenu.firstChild;
 
     // remove existing menu items
     while (undoPopup.hasChildNodes())
-      undoPopup.removeChild(undoPopup.firstChild);
+      undoPopup.firstChild.remove();
 
     // no restorable tabs, so make sure menu is disabled, and return
     if (this._getClosedTabCount() == 0) {
       undoMenu.setAttribute("disabled", true);
       return;
     }
 
     // enable menu
@@ -777,17 +777,17 @@ HistoryMenu.prototype = {
    * Populate when the history menu is opened
    */
   populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
     let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
     let undoPopup = undoMenu.firstChild;
 
     // remove existing menu items
     while (undoPopup.hasChildNodes())
-      undoPopup.removeChild(undoPopup.firstChild);
+      undoPopup.firstChild.remove();
 
     // no restorable windows, so make sure menu is disabled, and return
     if (SessionStore.getClosedWindowCount() == 0) {
       undoMenu.setAttribute("disabled", true);
       return;
     }
 
     // enable menu
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -279,17 +279,17 @@
     <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
     <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
     <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
 #else
     <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
     <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
     <key keycode="VK_F11" command="View:FullScreen"/>
 #endif
-    <key id="toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
+    <key id="key_toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
     <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
     <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
     <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
 #ifndef XP_WIN
     <key id="key_viewInfo"   key="&pageInfoCmd.commandkey;"   command="View:PageInfo"   modifiers="accel"/>
 #endif
     <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
     <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
@@ -333,17 +333,17 @@
     <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;"   command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key                          key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;"  command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset"   key="&fullZoomResetCmd.commandkey;"    command="cmd_fullZoomReset"   modifiers="accel"/>
     <key                          key="&fullZoomResetCmd.commandkey2;"   command="cmd_fullZoomReset"   modifiers="accel"/>
 
-    <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
+    <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift" disabled="true"/>
 
     <key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
 
     <key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;"
          modifiers="accel,shift" reserved="true"/>
     <key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
 #ifdef XP_MACOSX
     <key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,31 +1,39 @@
 /* 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/. */
 
+/* eslint-env mozilla/browser-window */
+/* eslint no-undef: "error" */
+/* global OpenGraphBuilder:false, DynamicResizeWatcher:false */
+
 // the "exported" symbols
 var SocialUI,
     SocialShare,
     SocialActivationListener;
 
 (function() {
+"use strict";
 
 XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
   let tmp = {};
   Cu.import("resource:///modules/Social.jsm", tmp);
   return tmp.OpenGraphBuilder;
 });
 
 XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
   let tmp = {};
   Cu.import("resource:///modules/Social.jsm", tmp);
   return tmp.DynamicResizeWatcher;
 });
 
+let messageManager = window.messageManager;
+let openUILinkIn = window.openUILinkIn;
+
 SocialUI = {
   _initialized: false,
 
   // Called on delayed startup to initialize the UI
   init: function SocialUI_init() {
     if (this._initialized) {
       return;
     }
@@ -62,26 +70,26 @@ SocialUI = {
   observe: function SocialUI_observe(subject, topic, data) {
     switch (topic) {
       case "social:providers-changed":
         this._providersChanged();
         break;
     }
   },
 
-  _providersChanged: function() {
+  _providersChanged() {
     SocialShare.populateProviderMenu();
   },
 
-  showLearnMore: function() {
+  showLearnMore() {
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
     openUILinkIn(url, "tab");
   },
 
-  closeSocialPanelForLinkTraversal: function (target, linkNode) {
+  closeSocialPanelForLinkTraversal(target, linkNode) {
     // No need to close the panel if this traversal was not retargeted
     if (target == "" || target == "_self")
       return;
 
     // Check to see whether this link traversal was in a social panel
     let win = linkNode.ownerGlobal;
     let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIWebNavigation)
@@ -98,36 +106,36 @@ SocialUI = {
   },
 
   get _chromeless() {
     // Is this a popup window that doesn't want chrome shown?
     let docElem = document.documentElement;
     // extrachrome is not restored during session restore, so we need
     // to check for the toolbar as well.
     let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
-                     docElem.getAttribute('chromehidden').includes("toolbar");
+                     docElem.getAttribute("chromehidden").includes("toolbar");
     // This property is "fixed" for a window, so avoid doing the check above
     // multiple times...
     delete this._chromeless;
     this._chromeless = chromeless;
     return chromeless;
   },
 
   get enabled() {
     // Returns whether social is enabled *for this window*.
     if (this._chromeless)
       return false;
     return Social.providers.length > 0;
   },
 
-  canSharePage: function(aURI) {
-    return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
+  canSharePage(aURI) {
+    return (aURI && (aURI.schemeIs("http") || aURI.schemeIs("https")));
   },
 
-  onCustomizeEnd: function(aWindow) {
+  onCustomizeEnd(aWindow) {
     if (aWindow != window)
       return;
     // customization mode gets buttons out of sync with command updating, fix
     // the disabled state
     let canShare = this.canSharePage(gBrowser.currentURI);
     let shareButton = SocialShare.shareButton;
     if (shareButton) {
       if (canShare) {
@@ -135,30 +143,30 @@ SocialUI = {
       } else {
         shareButton.setAttribute("disabled", "true")
       }
     }
   },
 
   // called on tab/urlbar/location changes and after customization. Update
   // anything that is tab specific.
-  updateState: function() {
+  updateState() {
     goSetCommandEnabled("Social:PageShareable", this.canSharePage(gBrowser.currentURI));
   }
 }
 
 // message manager handlers
 SocialActivationListener = {
-  init: function() {
+  init() {
     messageManager.addMessageListener("Social:Activation", this);
   },
-  uninit: function() {
+  uninit() {
     messageManager.removeMessageListener("Social:Activation", this);
   },
-  receiveMessage: function(aMessage) {
+  receiveMessage(aMessage) {
     let data = aMessage.json;
     let browser = aMessage.target;
     data.window = window;
     // if the source if the message is the share panel, we do a one-click
     // installation. The source of activations is controlled by the
     // social.directories preference
     let options;
     if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
@@ -180,18 +188,18 @@ SocialActivationListener = {
             CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
             // Ensure correct state.
             SocialUI.onCustomizeEnd(window);
           }
 
           // make this new provider the selected provider. If the panel hasn't
           // been opened, we need to make the frame first.
           SocialShare._createFrame();
-          SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
-          SocialShare.iframe.setAttribute('origin', provider.origin);
+          SocialShare.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
+          SocialShare.iframe.setAttribute("origin", provider.origin);
           // get the right button selected
           SocialShare.populateProviderMenu();
           if (SocialShare.panel.state == "open") {
             SocialShare.sharePage(provider.origin);
           }
         }
         if (provider.postActivationURL) {
           // if activated from an open share panel, we load the landing page in
@@ -224,28 +232,28 @@ SocialShare = {
   },
 
   get iframe() {
     // panel.firstChild is our toolbar hbox, panel.lastChild is the iframe
     // container hbox used for an interstitial "loading" graphic
     return this.panel.lastChild.firstChild;
   },
 
-  uninit: function () {
+  uninit() {
     if (this.iframe) {
       let mm = this.messageManager;
       mm.removeMessageListener("PageVisibility:Show", this);
       mm.removeMessageListener("PageVisibility:Hide", this);
       mm.removeMessageListener("Social:DOMWindowClose", this);
       this.iframe.removeEventListener("load", this);
       this.iframe.remove();
     }
   },
 
-  _createFrame: function() {
+  _createFrame() {
     let panel = this.panel;
     if (this.iframe)
       return;
     this.panel.hidden = false;
     // create and initialize the panel for this window
     let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-share-frame");
@@ -268,58 +276,58 @@ SocialShare = {
   },
 
   get messageManager() {
     // The xbl bindings for the iframe may not exist yet, so we can't
     // access iframe.messageManager directly - but can get at it with this dance.
     return this.iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
   },
 
-  receiveMessage: function(aMessage) {
+  receiveMessage(aMessage) {
     let iframe = this.iframe;
-    switch(aMessage.name) {
+    switch (aMessage.name) {
       case "PageVisibility:Show":
         SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
         break;
       case "PageVisibility:Hide":
         SocialShare._dynamicResizer.stop();
         break;
       case "Social:DOMWindowClose":
         this.panel.hidePopup();
         break;
     }
   },
 
-  handleEvent: function(event) {
+  handleEvent(event) {
     switch (event.type) {
       case "load": {
         this.iframe.parentNode.removeAttribute("loading");
         if (this.currentShare)
           SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
       }
     }
   },
 
-  getSelectedProvider: function() {
+  getSelectedProvider() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
     if (lastProviderOrigin) {
       provider = Social._getProviderFromOrigin(lastProviderOrigin);
     }
     return provider;
   },
 
-  createTooltip: function(event) {
+  createTooltip(event) {
     let tt = event.target;
     let provider = Social._getProviderFromOrigin(tt.triggerNode.getAttribute("origin"));
     tt.firstChild.setAttribute("value", provider.name);
     tt.lastChild.setAttribute("value", provider.origin);
   },
 
-  populateProviderMenu: function() {
+  populateProviderMenu() {
     if (!this.iframe)
       return;
     let providers = Social.providers.filter(p => p.shareURL);
     let hbox = document.getElementById("social-share-provider-buttons");
     // remove everything before the add-share-provider button (which should also
     // be lastChild if any share providers were added)
     let addButton = document.getElementById("add-share-provider");
     while (hbox.lastChild != addButton) {
@@ -354,106 +362,106 @@ SocialShare = {
     if (!window.CustomizableUI)
       return null;
     let widget = CustomizableUI.getWidget("social-share-button");
     if (!widget || !widget.areaType)
       return null;
     return widget.forWindow(window).node;
   },
 
-  _onclick: function() {
+  _onclick() {
     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(0);
   },
 
-  onShowing: function() {
+  onShowing() {
     (this._currentAnchor || this.anchor).setAttribute("open", "true");
     this.iframe.addEventListener("click", this._onclick, true);
   },
 
-  onHidden: function() {
+  onHidden() {
     (this._currentAnchor || this.anchor).removeAttribute("open");
     this._currentAnchor = null;
     this.iframe.docShellIsActive = false;
     this.iframe.removeEventListener("click", this._onclick, true);
     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
     // make sure that the frame is unloaded after it is hidden
     this.messageManager.sendAsyncMessage("Social:ClearFrame");
     this.currentShare = null;
     // share panel use is over, purge any history
     this.iframe.purgeSessionHistory();
   },
 
-  sharePage: function(providerOrigin, graphData, target, anchor) {
+  sharePage(providerOrigin, graphData, target, anchor) {
     // if providerOrigin is undefined, we use the last-used provider, or the
     // current/default provider.  The provider selection in the share panel
     // will call sharePage with an origin for us to switch to.
     this._createFrame();
     let iframe = this.iframe;
 
     // graphData is an optional param that either defines the full set of data
     // to be shared, or partial data about the current page. It is set by a call
     // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
     // define at least url. If it is undefined, we're sharing the current url in
     // the browser tab.
-    let pageData = graphData ? graphData : this.currentShare;
-    let sharedURI = pageData ? Services.io.newURI(pageData.url) :
+    let sharedPageData = graphData || this.currentShare;
+    let sharedURI = sharedPageData ? Services.io.newURI(sharedPageData.url) :
                                 gBrowser.currentURI;
     if (!SocialUI.canSharePage(sharedURI))
       return;
 
     let browserMM = gBrowser.selectedBrowser.messageManager;
 
     // the point of this action type is that we can use existing share
     // endpoints (e.g. oexchange) that do not support additional
     // socialapi functionality.  One tweak is that we shoot an event
     // containing the open graph data.
     let _dataFn;
-    if (!pageData || sharedURI == gBrowser.currentURI) {
+    if (!sharedPageData || sharedURI == gBrowser.currentURI) {
       browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
         browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
         let pageData = msg.json;
         if (graphData) {
           // overwrite data retreived from page with data given to us as a param
           for (let p in graphData) {
             pageData[p] = graphData[p];
           }
         }
         this.sharePage(providerOrigin, pageData, target, anchor);
       });
       browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
       return;
     }
     // if this is a share of a selected item, get any microformats
-    if (!pageData.microformats && target) {
+    if (!sharedPageData.microformats && target) {
       browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
         browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
-        pageData.microformats = msg.data;
-        this.sharePage(providerOrigin, pageData, target, anchor);
+        sharedPageData.microformats = msg.data;
+        this.sharePage(providerOrigin, sharedPageData, target, anchor);
       });
       browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
       return;
     }
-    this.currentShare = pageData;
+    this.currentShare = sharedPageData;
 
     let provider;
     if (providerOrigin)
       provider = Social._getProviderFromOrigin(providerOrigin);
     else
       provider = this.getSelectedProvider();
     if (!provider || !provider.shareURL) {
       this.showDirectory(anchor);
       return;
     }
     // check the menu button
     let hbox = document.getElementById("social-share-provider-buttons");
     let btn = hbox.querySelector("[origin='" + provider.origin + "']");
     if (btn)
       btn.checked = true;
 
-    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
+    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, sharedPageData);
 
     this._dynamicResizer.stop();
     let size = provider.getPageSize("share");
     if (size) {
       // let the css on the share panel define width, but height
       // calculations dont work on all sites, so we allow that to be
       // defined.
       delete size.width;
@@ -469,35 +477,34 @@ SocialShare = {
     } else {
       iframe.parentNode.setAttribute("loading", "true");
     }
     // if the user switched between share providers we do not want that history
     // available.
     iframe.purgeSessionHistory();
 
     // always ensure that origin belongs to the endpoint
-    let uri = Services.io.newURI(shareEndpoint);
     iframe.setAttribute("origin", provider.origin);
     iframe.setAttribute("src", shareEndpoint);
     this._openPanel(anchor);
   },
 
-  showDirectory: function(anchor) {
+  showDirectory(anchor) {
     this._createFrame();
     let iframe = this.iframe;
     if (iframe.getAttribute("src") == "about:providerdirectory")
       return;
     iframe.removeAttribute("origin");
     iframe.parentNode.setAttribute("loading", "true");
 
     iframe.setAttribute("src", "about:providerdirectory");
     this._openPanel(anchor);
   },
 
-  _openPanel: function(anchor) {
+  _openPanel(anchor) {
     this._currentAnchor = anchor || this.anchor;
     anchor = document.getAnonymousElementByAttribute(this._currentAnchor, "class", "toolbarbutton-icon");
     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
   }
 };
 
-})();
+}).call(this);
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
   XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
                                     "resource://gre/modules/CloudSync.jsm");
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
@@ -35,18 +36,16 @@ var gSyncUI = {
 
   _unloaded: false,
   // The last sync start time. Used to calculate the leftover animation time
   // once syncing completes (bug 1239042).
   _syncStartTime: 0,
   _syncAnimationTimer: 0,
 
   init() {
-    Cu.import("resource://services-common/stringbundle.js");
-
     // Proceed to set up the UI if Sync has already started up.
     // Otherwise we'll do it when Sync is firing up.
     if (this.weaveService.ready) {
       this.initUI();
       return;
     }
 
     // Sync isn't ready yet, but we can still update the UI with an initial
@@ -219,18 +218,19 @@ var gSyncUI = {
     this.updateUI();
   },
 
   onLogout: function SUI_onLogout() {
     this.updateUI();
   },
 
   _getAppName() {
-    let brand = new StringBundle("chrome://branding/locale/brand.properties");
-    return brand.get("brandShortName");
+    let brand = Services.strings.createBundle(
+      "chrome://branding/locale/brand.properties");
+    return brand.GetStringFromName("brandShortName");
   },
 
   // Commands
   // doSync forces a sync - it *does not* return a promise as it is called
   // via the various UI components.
   doSync() {
     this._needsSetup().then(needsSetup => {
       if (!needsSetup) {
@@ -292,20 +292,17 @@ var gSyncUI = {
      button being in "customize purgatory" and if so, move it to the panel.
      This is done primarily for profiles created before SyncedTabs landed,
      where the button defaulted to being in that purgatory.
      We use a preference to ensure we only do it once, so people can still
      customize it away and have it stick.
   */
   maybeMoveSyncedTabsButton() {
     const prefName = "browser.migrated-sync-button";
-    let migrated = false;
-    try {
-      migrated = Services.prefs.getBoolPref(prefName);
-    } catch (_) {}
+    let migrated = Services.prefs.getBoolPref(prefName, false);
     if (migrated) {
       return;
     }
     if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
       CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
     }
     Services.prefs.setBoolPref(prefName, true);
   },
@@ -470,19 +467,18 @@ var gSyncUI = {
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ])
 };
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
   // XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
   //        but for now just make it work
-  return Cc["@mozilla.org/intl/stringbundle;1"].
-         getService(Ci.nsIStringBundleService).
-         createBundle("chrome://weave/locale/services/sync.properties");
+  return Services.strings.createBundle(
+    "chrome://weave/locale/sync.properties");
 });
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
   return Log.repository.getLogger("browserwindow.syncui");
 });
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
   return Components.classes["@mozilla.org/weave/service;1"]
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -6,16 +6,25 @@
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
   --panelui-subview-transition-duration: 150ms;
 }
 
+:root:-moz-lwtheme {
+  color: var(--lwt-text-color) !important;
+}
+
+:root:-moz-lwtheme:not([customization-lwtheme]) {
+  background-color: var(--lwt-accent-color) !important;
+  background-image: var(--lwt-header-image) !important;
+}
+
 #main-window:not([chromehidden~="toolbar"]) {
 %ifdef XP_MACOSX
   min-width: 335px;
 %else
   min-width: 300px;
 %endif
 }
 
@@ -437,19 +446,21 @@ toolbar:not(#TabsToolbar) > #personal-bo
 }
 
 %ifdef XP_MACOSX
 #main-window[inFullscreen="true"] {
   padding-top: 0; /* override drawintitlebar="true" */
 }
 %endif
 
-#browser-bottombox[lwthemefooter="true"] {
+:root[lwthemefooter=true] #browser-bottombox:-moz-lwtheme {
   background-repeat: no-repeat;
   background-position: bottom left;
+  background-color: var(--lwt-accent-color);
+  background-image: var(--lwt-footer-image);
 }
 
 .menuitem-iconic-tooltip {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
 }
 
 /* Hide menu elements intended for keyboard access support */
 #main-menubar[openedwithkey=false] .show-only-for-keyboard {
@@ -556,17 +567,17 @@ toolbar:not(#TabsToolbar) > #personal-bo
 #PopupAutoCompleteRichResult > richlistbox {
   transition: height 100ms;
 }
 
 #PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
   transition: none;
 }
 
-#DateTimePickerPanel {
+#DateTimePickerPanel[active="true"] {
   -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
 }
 
 #urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
 #urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
 #urlbar[pageproxystate="valid"] > #urlbar-go-button,
 #urlbar:not([focused="true"]) > #urlbar-go-button {
   visibility: collapse;
@@ -818,17 +829,16 @@ html|*#fullscreen-exit-button {
 
 #invalid-form-popup > description {
   max-width: 280px;
 }
 
 .popup-anchor {
   /* should occupy space but not be visible */
   opacity: 0;
-  visibility: hidden;
   pointer-events: none;
   -moz-stack-sizing: ignore;
 }
 
 #addon-progress-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
 }
 
@@ -850,20 +860,19 @@ browser[tabmodalPromptShowing] {
 statuspanel {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
   position: fixed;
   margin-top: -3em;
   max-width: calc(100% - 5px);
   pointer-events: none;
 }
 
-statuspanel:-moz-locale-dir(ltr)[mirror],
-statuspanel:-moz-locale-dir(rtl):not([mirror]) {
-  left: auto;
-  right: 0;
+statuspanel[mirror] {
+  offset-inline-start: auto;
+  offset-inline-end: 0;
 }
 
 statuspanel[sizelimit] {
   max-width: 50%;
 }
 
 statuspanel[type=status] {
   min-width: 23em;
@@ -872,16 +881,19 @@ statuspanel[type=status] {
 @media all and (max-width: 800px) {
   statuspanel[type=status] {
     min-width: 33%;
   }
 }
 
 statuspanel[type=overLink] {
   transition: opacity 120ms ease-out;
+}
+
+statuspanel[type=overLink] > .statuspanel-inner {
   direction: ltr;
 }
 
 statuspanel[inactive] {
   transition: none;
   opacity: 0;
 }
 
@@ -971,20 +983,24 @@ notification[value="translation"] {
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
   display: -moz-box;
 }
 
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
   display: none;
 }
 
-#downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
-#downloads-button:not(:-moz-any([progress], [counter], [paused]))
-                                                   #downloads-indicator-progress-area
-{
+#downloads-button.withProgressBar:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
+#downloads-button:not(:-moz-any([progress], [counter], [paused])) #downloads-indicator-progress-area {
+  visibility: hidden;
+}
+
+/* Hide elements for another type of progressmeter if it's not in use. */
+#downloads-button.withProgressBar #downloads-indicator-progress-icon,
+#downloads-button:not(.withProgressBar) #downloads-indicator-progress-area {
   visibility: hidden;
 }
 
 /* Combobox dropdown renderer */
 #ContentSelectDropdown > menupopup {
   /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
    * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
   direction: ltr;
@@ -1245,8 +1261,10 @@ toolbarpaletteitem[place="palette"][hidd
   box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
 }
 
 .dragfeedback-tab {
   -moz-appearance: none;
   opacity: 0.65;
   -moz-window-shadow: none;
 }
+
+%include theme-vars.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -10,50 +10,51 @@ var Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 
 // lazy module getters
 
-/* global AboutHome:false, AddonWatcher:false, AppConstants: false,
+/* global AboutHome:false, AddonWatcher:false,
           BrowserUITelemetry:false, BrowserUsageTelemetry:false, BrowserUtils:false,
           CastingApps:false, CharsetMenu:false, Color:false, ContentSearch:false,
           Deprecated:false, E10SUtils:false, FormValidationHandler:false,
           GMPInstallManager:false, LightweightThemeManager:false, Log:false,
           LoginManagerParent:false, NewTabUtils:false, PageThumbs:false,
           PluralForm:false, Preferences:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, PromiseUtils:false, ReaderMode:false,
           ReaderParent:false, RecentWindow:false, SessionStore:false,
           ShortcutUtils:false, SimpleServiceDiscovery:false, SitePermissions:false,
           Social:false, TabCrashHandler:false, Task:false, TelemetryStopwatch:false,
           Translation:false, UITour:false, UpdateUtils:false, Weave:false,
-          fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false
+          fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false,
+          FullZoomUI: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.
  */
 [
   ["AboutHome", "resource:///modules/AboutHome.jsm"],
   ["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
-  ["AppConstants", "resource://gre/modules/AppConstants.jsm"],
   ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
   ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
   ["BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"],
   ["CastingApps", "resource:///modules/CastingApps.jsm"],
   ["CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"],
   ["Color", "resource://gre/modules/Color.jsm"],
   ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
   ["Deprecated", "resource://gre/modules/Deprecated.jsm"],
   ["E10SUtils", "resource:///modules/E10SUtils.jsm"],
   ["ExtensionsUI", "resource:///modules/ExtensionsUI.jsm"],
   ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+  ["FullZoomUI", "resource:///modules/FullZoomUI.jsm"],
   ["GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm"],
   ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
   ["Log", "resource://gre/modules/Log.jsm"],
   ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
   ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
   ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
   ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
   ["Preferences", "resource://gre/modules/Preferences.jsm"],
@@ -73,17 +74,17 @@ Cu.import("resource://gre/modules/Notifi
   ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
   ["Translation", "resource:///modules/translation/Translation.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
   ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
   ["Weave", "resource://services-sync/main.js"],
   ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
   ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
   ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
-  ["webrtcUI", "resource:///modules/webrtcUI.jsm", ]
+  ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
   "resource://gre/modules/SafeBrowsing.jsm");
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
     "resource:///modules/ContentCrashHandlers.jsm");
@@ -148,19 +149,23 @@ XPCOMUtils.defineLazyGetter(this, "PageM
 });
 
 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
   try {
     // Hide all notifications while the URL is being edited and the address bar
     // has focus, including the virtual focus in the results popup.
+    // We also have to hide notifications explicitly when the window is
+    // minimized because of the effects of the "noautohide" attribute on Linux.
+    // This can be removed once bug 545265 and bug 1320361 are fixed.
     let shouldSuppress = () => {
-      return gURLBar.getAttribute("pageproxystate") != "valid" &&
-             gURLBar.focused;
+      return window.windowState == window.STATE_MINIMIZED ||
+             (gURLBar.getAttribute("pageproxystate") != "valid" &&
+             gURLBar.focused);
     };
     return new tmp.PopupNotifications(gBrowser,
                                       document.getElementById("notification-popup"),
                                       document.getElementById("notification-popup-box"),
                                       { shouldSuppress });
   } catch (ex) {
     Cu.reportError(ex);
     return null;
@@ -788,23 +793,23 @@ var gPopupBlockerObserver = {
           // For host-less URIs like file://, prePath would effectively allow
           // popups everywhere on file://. Use the full spec:
           prefillValue = principalURI.spec;
         }
       }
     } catch (e) { }
 
     var bundlePreferences = document.getElementById("bundle_preferences");
-    var params = { blockVisible   : false,
-                   sessionVisible : false,
-                   allowVisible   : true,
-                   prefilledHost  : prefillValue,
-                   permissionType : "popup",
-                   windowTitle    : bundlePreferences.getString("popuppermissionstitle"),
-                   introText      : bundlePreferences.getString("popuppermissionstext") };
+    var params = { blockVisible: false,
+                   sessionVisible: false,
+                   allowVisible: true,
+                   prefilledHost: prefillValue,
+                   permissionType: "popup",
+                   windowTitle: bundlePreferences.getString("popuppermissionstitle"),
+                   introText: bundlePreferences.getString("popuppermissionstext") };
     var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
     if (existingWindow) {
       existingWindow.initWithParams(params);
       existingWindow.focus();
     } else
       window.openDialog("chrome://browser/content/preferences/permissions.xul",
                         "_blank", "resizable,dialog=no,centerscreen", params);
   },
@@ -822,16 +827,18 @@ function gKeywordURIFixup({ target: brow
   // We get called irrespective of whether we did a keyword search, or
   // whether the original input would be vaguely interpretable as a URL,
   // so figure that out first.
   let alternativeURI = deserializeURI(fixupInfo.fixedURI);
   if (!fixupInfo.keywordProviderName || !alternativeURI || !alternativeURI.host) {
     return;
   }
 
+  let contentPrincipal = browser.contentPrincipal;
+
   // At this point we're still only just about to load this URI.
   // When the async DNS lookup comes back, we may be in any of these states:
   // 1) still on the previous URI, waiting for the preferredURI (keyword
   //    search) to respond;
   // 2) at the keyword search URI (preferredURI)
   // 3) at some other page because the user stopped navigation.
   // We keep track of the currentURI to detect case (1) in the DNS lookup
   // callback.
@@ -924,17 +931,18 @@ function gKeywordURIFixup({ target: brow
     let notification =
       notificationBox.appendNotification(message, "keyword-uri-fixup", null,
                                          notificationBox.PRIORITY_INFO_HIGH,
                                          buttons);
     notification.persistence = 1;
   };
 
   try {
-    gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
+    gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread,
+                             contentPrincipal.originAttributes);
   } catch (ex) {
     // Do nothing if the URL is invalid (we don't want to show a notification in that case).
     if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
       // ... otherwise, report:
       Cu.reportError(ex);
     }
   }
 }
@@ -1127,16 +1135,17 @@ var gBrowserInit = {
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
     CompactTheme.init();
     AboutPrivateBrowsingListener.init();
     TrackingProtection.init();
     RefreshBlocker.init();
     CaptivePortalWatcher.init();
+    FullZoomUI.init(window);
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
     mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
 
     // initialize observers and listeners
@@ -1362,17 +1371,16 @@ var gBrowserInit = {
     if (AppConstants.E10S_TESTING_ONLY)
       gRemoteTabsUI.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
-    LightweightThemeListener.init();
 
     UpdateUrlbarSearchSplitterState();
 
     if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
         !focusAndSelectUrlBar()) {
       if (gBrowser.selectedBrowser.isRemoteBrowser) {
         // If the initial browser is remote, in order to optimize for first paint,
         // we'll defer switching focus to that browser until it has painted.
@@ -1490,16 +1498,25 @@ var gBrowserInit = {
     gBrowserThumbnails.init();
 
     gMenuButtonBadgeManager.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();
+      }
+    });
+
     window.addEventListener("mousemove", MousePosTracker);
     window.addEventListener("dragover", MousePosTracker);
 
     gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
     gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
     gNavToolbox.addEventListener("customizationending", CustomizationHandler);
 
     // End startup crash tracking after a delay to catch crashes while restoring
@@ -1699,17 +1716,16 @@ var gBrowserInit = {
       }
 
       if (this.gmpInstallManager) {
         this.gmpInstallManager.uninit();
       }
 
       BrowserOffline.uninit();
       IndexedDBPromptHelper.uninit();
-      LightweightThemeListener.uninit();
       PanelUI.uninit();
       AutoShowBookmarksToolbar.uninit();
     }
 
     // Final window teardown, do this last.
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
@@ -2407,24 +2423,24 @@ function BrowserViewSourceOfDocument(aAr
     args = aArgsOrDocument;
   }
 
   let viewInternal = () => {
     let inTab = Services.prefs.getBoolPref("view_source.tab");
     if (inTab) {
       let tabBrowser = gBrowser;
       let preferredRemoteType;
-      if (!tabBrowser) {
-        if (!args.browser) {
+      if (args.browser) {
+        preferredRemoteType = args.browser.remoteType;
+      } else {
+        if (!tabBrowser) {
           throw new Error("BrowserViewSourceOfDocument should be passed the " +
                           "subject browser if called from a window without " +
                           "gBrowser defined.");
         }
-        preferredRemoteType = args.browser.remoteType;
-      } else {
         // Some internal URLs (such as specific chrome: and about: URLs that are
         // not yet remote ready) cannot be loaded in a remote browser.  View
         // source in tab expects the new view source browser's remoteness to match
         // that of the original URL, so disable remoteness if necessary for this
         // URL.
         preferredRemoteType =
           E10SUtils.getRemoteTypeForURI(args.URL, gMultiProcessBrowser);
       }
@@ -2534,37 +2550,39 @@ function URLBarSetURI(aURI) {
   if (value == null) {
     let uri = aURI || gBrowser.currentURI;
     // Strip off "wyciwyg://" and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
 
     // Replace initial page URIs with an empty string
-    // 1. only if there's no opener (bug 370555).
-    // 2. if remote newtab is enabled and it's the default remote newtab page
-    let defaultRemoteURL = gAboutNewTabService.remoteEnabled &&
-                           uri.spec === gAboutNewTabService.newTabURL;
-    if ((gInitialPages.includes(uri.spec) || defaultRemoteURL) &&
+    // only if there's no opener (bug 370555).
+    if (gInitialPages.includes(uri.spec) &&
         checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
       value = "";
     } else {
       // We should deal with losslessDecodeURI throwing for exotic URIs
       try {
         value = losslessDecodeURI(uri);
       } catch (ex) {
         value = "about:blank";
       }
     }
 
     valid = !isBlankPageURL(uri.spec);
   }
 
+  let isDifferentValidValue = valid && value != gURLBar.value;
   gURLBar.value = value;
   gURLBar.valueIsTyped = !valid;
+  if (isDifferentValidValue) {
+    gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+  }
+
   SetPageProxyState(valid ? "valid" : "invalid");
 }
 
 function losslessDecodeURI(aURI) {
   let scheme = aURI.scheme;
   if (scheme == "moz-action")
     throw new Error("losslessDecodeURI should never get a moz-action URI");
 
@@ -2787,25 +2805,20 @@ var gMenuButtonBadgeManager = {
 // Setup the hamburger button badges for updates, if enabled.
 var gMenuButtonUpdateBadge = {
   enabled: false,
   badgeWaitTime: 0,
   timer: null,
   cancelObserverRegistered: false,
 
   init() {
-    try {
-      this.enabled = Services.prefs.getBoolPref("app.update.badge");
-    } catch (e) {}
+    this.enabled = Services.prefs.getBoolPref("app.update.badge", false);
     if (this.enabled) {
-      try {
-        this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
-      } catch (e) {
-        this.badgeWaitTime = 345600; // 4 days
-      }
+      this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime",
+                                                     345600); // 4 days
       Services.obs.addObserver(this, "update-staged", false);
       Services.obs.addObserver(this, "update-downloaded", false);
     }
   },
 
   uninit() {
     if (this.timer)
       this.timer.cancel();
@@ -3038,17 +3051,17 @@ var BrowserOnClick = {
       case "exceptionDialogButton":
         if (isTopFrame) {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
         }
 
         securityInfo = getSecurityInfo(securityInfoAsString);
         let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
                                     .SSLStatus;
-        let params = { exceptionAdded : false,
+        let params = { exceptionAdded: false,
                        sslStatus };
 
         try {
           switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
             case 2 : // Pre-fetch & pre-populate
               params.prefetchCert = true;
             case 1 : // Pre-populate
               params.location = location;
@@ -3593,25 +3606,21 @@ function openHomeDialog(aURL) {
   if (aURL.includes("|")) {
     promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
   } else {
     promptMsg = gNavigatorBundle.getString("droponhomemsg");
   }
 
   var pressedVal  = Services.prompt.confirmEx(window, promptTitle, promptMsg,
                           Services.prompt.STD_YES_NO_BUTTONS,
-                          null, null, null, null, {value:0});
+                          null, null, null, null, {value: 0});
 
   if (pressedVal == 0) {
     try {
-      var homepageStr = Components.classes["@mozilla.org/supports-string;1"]
-                        .createInstance(Components.interfaces.nsISupportsString);
-      homepageStr.data = aURL;
-      gPrefService.setComplexValue("browser.startup.homepage",
-                                   Components.interfaces.nsISupportsString, homepageStr);
+      gPrefService.setStringPref("browser.startup.homepage", aURL);
     } catch (ex) {
       dump("Failed to set the home page.\n" + ex + "\n");
     }
   }
 }
 
 var newTabButtonObserver = {
   onDragOver(aEvent) {
@@ -4464,30 +4473,33 @@ var XULBrowserWindow = {
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
     let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
     SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
     return target;
   },
 
   // Check whether this URI should load in the current process
-  shouldLoadURI(aDocShell, aURI, aReferrer, aTriggeringPrincipal) {
+  shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
     if (!gMultiProcessBrowser)
       return true;
 
     let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
                            .sameTypeRootTreeItem
                            .QueryInterface(Ci.nsIDocShell)
                            .chromeEventHandler;
 
     // Ignore loads that aren't in the main tabbrowser
     if (browser.localName != "browser" || !browser.getTabBrowser || browser.getTabBrowser() != gBrowser)
       return true;
 
-    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
+      // XXX: Do we want to complain if we have post data but are still
+      // redirecting the load? Perhaps a telemetry probe? Theoretically we
+      // shouldn't do this, as it throws out data. See bug 1348018.
       E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
       return false;
     }
 
     return true;
   },
 
   onProgressChange(aWebProgress, aRequest,
@@ -4641,18 +4653,16 @@ var XULBrowserWindow = {
       URLBarSetURI(aLocationURI);
 
       BookmarkingUI.onLocationChange();
 
       gIdentityHandler.onLocationChange();
 
       SocialUI.updateState();
 
-      UITour.onLocationChange(location);
-
       gTabletModePageCounter.inc();
 
       // Utility functions for disabling find
       var shouldDisableFind = function(aDocument) {
         let docElt = aDocument.documentElement;
         return docElt && docElt.getAttribute("disablefastfind") == "true";
       }
 
@@ -5075,17 +5085,16 @@ nsBrowserAccess.prototype = {
     let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
                                       triggeringPrincipal: aTriggeringPrincipal,
                                       referrerURI: aReferrer,
                                       referrerPolicy: aReferrerPolicy,
                                       userContextId: aUserContextId,
                                       fromExternal: aIsExternal,
                                       inBackground: loadInBackground,
                                       forceNotRemote: aForceNotRemote,
-                                      forceBrowserInsertion: true,
                                       opener: aOpener,
                                       });
     let browser = win.gBrowser.getBrowserForTab(tab);
 
     if (needToFocusWin || (!loadInBackground && aIsExternal))
       win.focus();
 
     return browser;
@@ -5460,29 +5469,39 @@ var gHomeButton = {
     }
 
     return url;
   },
 };
 
 const nodeToTooltipMap = {
   "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
+  "context-reload": "reloadButton.tooltip",
+  "context-stop": "stopButton.tooltip",
+  "downloads-button": "downloads.tooltip",
+  "fullscreen-button": "fullscreenButton.tooltip",
   "new-window-button": "newWindowButton.tooltip",
   "new-tab-button": "newTabButton.tooltip",
   "tabs-newtab-button": "newTabButton.tooltip",
-  "fullscreen-button": "fullscreenButton.tooltip",
-  "downloads-button": "downloads.tooltip",
+  "urlbar-reload-button": "reloadButton.tooltip",
+  "urlbar-stop-button": "stopButton.tooltip",
+  "urlbar-zoom-button": "urlbar-zoom-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",
-  "fullscreen-button": "key_fullScreen",
-  "downloads-button": "key_openDownloads"
+  "urlbar-reload-button": "key_reload",
+  "urlbar-stop-button": "key_stop",
+  "urlbar-zoom-button": "key_fullZoomReset",
 };
 
 if (AppConstants.platform == "macosx") {
   nodeToTooltipMap["print-button"] = "printButton.tooltip";
   nodeToShortcutMap["print-button"] = "printKb";
 }
 
 const gDynamicTooltipCache = new Map();
@@ -5734,24 +5753,29 @@ function handleLinkClick(event, href, li
       linkNode) {
     let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
                             getAttribute("referrerpolicy"));
     if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
   }
 
+  let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindowUtils)
+                              .outerWindowID;
+
   urlSecurityCheck(href, doc.nodePrincipal);
   let params = {
     charset: doc.characterSet,
     allowMixedContent: persistAllowMixedContentInChildTab,
     referrerURI,
     referrerPolicy,
     noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
     originPrincipal: doc.nodePrincipal,
+    frameOuterWindowID,
   };
 
   // The new tab/window must use the same userContextId
   if (doc.nodePrincipal.originAttributes.userContextId) {
     params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
   }
 
   openLinkIn(href, where, params);
@@ -6213,17 +6237,17 @@ var OfflineApps = {
         }
       }];
       let message = gNavigatorBundle.getFormattedString("offlineApps.available2",
                                                         [host]);
       let anchorID = "indexedDB-notification-icon";
       let options = {
         persistent: true,
         hideClose: true,
-        controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
+        controlledItems: [[Cu.getWeakReference(browser), docId, uri]]
       };
       notification = PopupNotifications.show(browser, notificationID, message,
                                              anchorID, mainAction,
                                              secondaryActions, options);
     }
   },
 
   disallowSite(uri) {
@@ -6641,40 +6665,75 @@ function checkEmptyPageOrigin(browser = 
   // If another page opened this page with e.g. window.open, this page might
   // be controlled by its opener - return false.
   if (browser.hasContentOpener) {
     return false;
   }
   let contentPrincipal = browser.contentPrincipal;
   // Not all principals have URIs...
   if (contentPrincipal.URI) {
-    // There are two specialcases involving about:blank. One is where
+    // There are two special-cases involving about:blank. One is where
     // the user has manually loaded it and it got created with a null
     // principal. The other involves the case where we load
     // some other empty page in a browser and the current page is the
     // initial about:blank page (which has that as its principal, not
     // just URI in which case it could be web-based). Especially in
     // e10s, we need to tackle that case specifically to avoid race
     // conditions when updating the URL bar.
-    if ((uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
+    //
+    // Note that we check the documentURI here, since the currentURI on
+    // the browser might have been set by SessionStore in order to
+    // support switch-to-tab without having actually loaded the content
+    // yet.
+    let uriToCheck = browser.documentURI || uri;
+    if ((uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
         contentPrincipal.URI.spec == "about:blank") {
       return true;
     }
     return contentPrincipal.URI.equals(uri);
   }
   // ... so for those that don't have them, enforce that the page has the
   // system principal (this matches e.g. on about:newtab).
   let ssm = Services.scriptSecurityManager;
   return ssm.isSystemPrincipal(contentPrincipal);
 }
 
 function BrowserOpenSyncTabs() {
   gSyncUI.openSyncedTabsPanel();
 }
 
+function ReportFalseDeceptiveSite() {
+  let docURI = gBrowser.selectedBrowser.documentURI;
+  let isPhishingPage =
+    docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked");
+
+  if (isPhishingPage) {
+    let mm = gBrowser.selectedBrowser.messageManager;
+    let onMessage = (message) => {
+      mm.removeMessageListener("DeceptiveBlockedDetails:Result", onMessage);
+      let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", message.data.blockedInfo);
+      if (reportUrl) {
+        openUILinkIn(reportUrl, "tab");
+      } else {
+        let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+                            getService(Ci.nsIPromptService);
+        let bundle =
+          Services.strings.createBundle("chrome://browser/locale/safebrowsing/safebrowsing.properties");
+        promptService.alert(window,
+                            bundle.GetStringFromName("errorReportFalseDeceptiveTitle"),
+                            bundle.formatStringFromName("errorReportFalseDeceptiveMessage",
+                                                        [message.data.blockedInfo.provider], 1));
+        }
+    }
+    mm.addMessageListener("DeceptiveBlockedDetails:Result", onMessage);
+
+    mm.sendAsyncMessage("DeceptiveBlockedDetails");
+  }
+}
+
 /**
  * Format a URL
  * eg:
  * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
  * > https://addons.mozilla.org/en-US/firefox/3.0a1/
  *
  * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
  */
@@ -6716,16 +6775,22 @@ var gIdentityHandler = {
    */
   _sslStatus: null,
 
   /**
    * Bitmask provided by nsIWebProgressListener.onSecurityChange.
    */
   _state: 0,
 
+  /**
+   * This flag gets set if the identity popup was opened by a keypress,
+   * to be able to focus it on the popupshown event.
+   */
+  _popupTriggeredByKeyboard: false,
+
   get _isBroken() {
     return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
   },
 
   get _isSecure() {
     // If a <browser> is included within a chrome document, then this._state
     // will refer to the security state for the <browser> and not the top level
     // document. In this case, don't upgrade the security state in the UI
@@ -6850,16 +6915,20 @@ var gIdentityHandler = {
   get _permissionEmptyHint() {
     delete this._permissionEmptyHint;
     return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
   },
   get _permissionReloadHint() {
     delete this._permissionReloadHint;
     return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
   },
+  get _popupExpander() {
+    delete this._popupExpander;
+    return this._popupExpander = document.getElementById("identity-popup-security-expander");
+  },
   get _permissionAnchors() {
     delete this._permissionAnchors;
     let permissionAnchors = {};
     for (let anchor of document.getElementById("blocked-permissions-container").children) {
       permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
     }
     return this._permissionAnchors = permissionAnchors;
   },
@@ -6871,20 +6940,28 @@ var gIdentityHandler = {
   handleMoreInfoClick(event) {
     displaySecurityInfo();
     event.stopPropagation();
     this._identityPopup.hidePopup();
   },
 
   toggleSubView(name, anchor) {
     let view = this._identityPopupMultiView;
+    let id = `identity-popup-${name}View`;
+    let subView = document.getElementById(id);
+
+    // Listen for the subview showing or hiding to change
+    // the tooltip on the expander button.
+    subView.addEventListener("ViewShowing", this);
+    subView.addEventListener("ViewHiding", this);
+
     if (view.showingSubView) {
       view.showMainView();
     } else {
-      view.showSubView(`identity-popup-${name}View`, anchor);
+      view.showSubView(id, anchor);
     }
 
     // If an element is focused that's not the anchor, clear the focus.
     // Elements of hidden views have -moz-user-focus:ignore but setting that
     // per CSS selector doesn't blur a focused element in those hidden views.
     if (Services.focus.focusedElement != anchor) {
       Services.focus.clearFocus(window);
     }
@@ -7183,16 +7260,19 @@ var gIdentityHandler = {
   refreshIdentityPopup() {
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     this._identityPopupMixedContentLearnMore
         .setAttribute("href", baseURL + "mixed-content");
     this._identityPopupInsecureLoginFormsLearnMore
         .setAttribute("href", baseURL + "insecure-password");
 
+    // The expander switches its tooltip when toggled, change it to the default.
+    this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
+
     // Determine connection security information.
     let connection = "not-secure";
     if (this._isSecureInternalUI) {
       connection = "chrome";
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
@@ -7356,16 +7436,18 @@ var gIdentityHandler = {
       return; // Left click, space or enter only
     }
 
     // Don't allow left click, space or enter if the location has been modified.
     if (gURLBar.getAttribute("pageproxystate") != "valid") {
       return;
     }
 
+    this._popupTriggeredByKeyboard = event.type == "keypress";
+
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
     // Remove the reload hint that we show after a user has cleared a permission.
     this._permissionReloadHint.setAttribute("hidden", "true");
 
     // Update the popup strings
@@ -7375,33 +7457,45 @@ var gIdentityHandler = {
     this._identityBox.setAttribute("open", "true");
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
   },
 
   onPopupShown(event) {
     if (event.target == this._identityPopup) {
-      // Move focus to the next available element in the identity popup.
-      // This is required by role=alertdialog and fixes an issue where
-      // an already open panel would steal focus from the identity popup.
-      document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+      if (this._popupTriggeredByKeyboard) {
+        // Move focus to the next available element in the identity popup.
+        // This is required by role=alertdialog and fixes an issue where
+        // an already open panel would steal focus from the identity popup.
+        document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+      }
 
       window.addEventListener("focus", this, true);
     }
   },
 
   onPopupHidden(event) {
     if (event.target == this._identityPopup) {
       window.removeEventListener("focus", this, true);
       this._identityBox.removeAttribute("open");
     }
   },
 
   handleEvent(event) {
+    // If the subview is shown or hidden, change the tooltip on the expander button.
+    if (event.type == "ViewShowing") {
+      this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.hideDetails.tooltip");
+      return;
+    }
+    if (event.type == "ViewHiding") {
+      this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
+      return;
+    }
+
     let elem = document.activeElement;
     let position = elem.compareDocumentPosition(this._identityPopup);
 
     if (!(position & (Node.DOCUMENT_POSITION_CONTAINS |
                       Node.DOCUMENT_POSITION_CONTAINED_BY)) &&
         !this._identityPopup.hasAttribute("noautohide")) {
       // Hide the panel when focusing an element that is
       // neither an ancestor nor descendant unless the panel has
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -8,17 +8,16 @@
 
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/browser-lightweightTheme.css" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 # All DTD information is stored in a separate file so that it can be shared by
 # hiddenWindow.xul.
 #include browser-doctype.inc
@@ -48,17 +47,16 @@
         chromemargin="0,2,2,2"
 #else
         chromemargin="0,-1,-1,-1"
 #endif
         tabsintitlebar="true"
 #endif
         titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
         lightweightthemes="true"
-        lightweightthemesfooter="browser-bottombox"
         windowtype="navigator:browser"
         macanimationtype="document"
         screenX="4" screenY="4"
         fullscreenbutton="true"
         sizemode="normal"
         retargetdocumentfocus="urlbar"
         persist="screenX screenY width height sizemode">
 
@@ -475,16 +473,20 @@
                 id="syncedTabsBookmarkSelected"/>
       <menuitem label="&syncedTabs.context.copy.label;"
                 accesskey="&syncedTabs.context.copy.accesskey;"
                 id="syncedTabsCopySelected"/>
       <menuseparator/>
       <menuitem label="&syncedTabs.context.openAllInTabs.label;"
                 accesskey="&syncedTabs.context.openAllInTabs.accesskey;"
                 id="syncedTabsOpenAllInTabs"/>
+      <menuitem label="&syncedTabs.context.managedevices.label;"
+                accesskey="&syncedTabs.context.managedevices.accesskey;"
+                id="syncedTabsManageDevices"
+                oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-sidebar');"/>
       <menuitem label="&syncSyncNowItem.label;"
                 accesskey="&syncSyncNowItem.accesskey;"
                 id="syncedTabsRefresh"/>
     </menupopup>
     <menupopup id="SyncedTabsSidebarTabsFilterContext"
                class="textbox-contextmenu">
       <menuitem label="&undoCmd.label;"
                 accesskey="&undoCmd.accesskey;"
@@ -610,17 +612,16 @@
                      class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
                      type="menu"
                      label="&listAllTabs.label;"
                      tooltiptext="&listAllTabs.label;"
                      removable="false">
         <menupopup id="alltabs-popup"
                    position="after_end">
           <menuitem id="alltabs_undoCloseTab"
-                    class="menuitem-iconic"
                     key="key_undoCloseTab"
                     label="&undoCloseTab.label;"
                     observes="History:UndoCloseTab"/>
           <menuseparator id="alltabs-popup-separator-1"/>
           <menu id="alltabs_containersTab"
                 label="&newUserContext.label;">
             <menupopup id="alltabs_containersMenuTab" />
           </menu>
@@ -770,36 +771,36 @@
                        tooltiptext="&pageReportIcon.tooltip;"
                        onmousedown="gPopupBlockerObserver.onReportButtonMousedown(event);"/>
                 <image id="reader-mode-button"
                        class="urlbar-icon"
                        hidden="true"
                        onclick="ReaderParent.buttonClick(event);"/>
                 <toolbarbutton id="urlbar-zoom-button"
                        onclick="FullZoom.reset();"
-                       tooltiptext="&urlbar.zoomReset.tooltip;"
+                       tooltip="dynamic-shortcut-tooltip"
                        hidden="true"/>
               </hbox>
               <hbox id="userContext-icons" hidden="true">
                 <label id="userContext-label"/>
                 <image id="userContext-indicator"/>
               </hbox>
               <toolbarbutton id="urlbar-go-button"
                              class="chromeclass-toolbar-additional"
                              onclick="gURLBar.handleCommand(event);"
                              tooltiptext="&goEndCap.tooltip;"/>
               <toolbarbutton id="urlbar-reload-button"
                              class="chromeclass-toolbar-additional"
                              command="Browser:ReloadOrDuplicate"
                              onclick="checkForMiddleClick(this, event);"
-                             tooltiptext="&reloadButton.tooltip;"/>
+                             tooltip="dynamic-shortcut-tooltip"/>
               <toolbarbutton id="urlbar-stop-button"
                              class="chromeclass-toolbar-additional"
                              command="Browser:Stop"
-                             tooltiptext="&stopButton.tooltip;"/>
+                             tooltip="dynamic-shortcut-tooltip"/>
             </textbox>
           </hbox>
         </toolbaritem>
 
         <toolbaritem id="search-container" title="&searchItem.title;"
                      align="center" class="chromeclass-toolbar-additional panel-wide-item"
                      cui-areatype="toolbar"
                      flex="100" persist="width" removable="true">
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -257,16 +257,45 @@ function getSerializedSecurityInfo(docSh
     return "";
   }
   securityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
               .QueryInterface(Ci.nsISerializable);
 
   return serhelper.serializeToString(securityInfo);
 }
 
+function getSiteBlockedErrorDetails(docShell) {
+  let blockedInfo = {};
+  if (docShell.failedChannel) {
+    let classifiedChannel = docShell.failedChannel.
+                            QueryInterface(Ci.nsIClassifiedChannel);
+    if (classifiedChannel) {
+      let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel);
+
+      let reportUri = httpChannel.URI.clone();
+
+      // Remove the query to avoid leaking sensitive data
+      if (reportUri instanceof Ci.nsIURL) {
+        reportUri.query = "";
+      }
+
+      blockedInfo = { list: classifiedChannel.matchedList,
+                      provider: classifiedChannel.matchedProvider,
+                      uri: reportUri.asciiSpec };
+    }
+  }
+  return blockedInfo;
+}
+
+addMessageListener("DeceptiveBlockedDetails", (message) => {
+  sendAsyncMessage("DeceptiveBlockedDetails:Result", {
+    blockedInfo: getSiteBlockedErrorDetails(docShell),
+  });
+});
+
 var AboutNetAndCertErrorListener = {
   init(chromeGlobal) {
     addMessageListener("CertErrorDetails", this);
     addMessageListener("Browser:CaptivePortalFreed", this);
     chromeGlobal.addEventListener("AboutNetErrorLoad", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorOpenCaptivePortal", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorSetAutomatic", this, false, true);
     chromeGlobal.addEventListener("AboutNetErrorResetPreferences", this, false, true);
@@ -496,20 +525,24 @@ var ClickEventHandler = {
         node) {
       let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node.
                               getAttribute("referrerpolicy"));
       if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
         referrerPolicy = referrerAttrValue;
       }
     }
 
+    let frameOuterWindowID = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                                     .getInterface(Ci.nsIDOMWindowUtils)
+                                     .outerWindowID;
+
     let json = { button: event.button, shiftKey: event.shiftKey,
                  ctrlKey: event.ctrlKey, metaKey: event.metaKey,
                  altKey: event.altKey, href: null, title: null,
-                 bookmark: false, referrerPolicy,
+                 bookmark: false, frameOuterWindowID, referrerPolicy,
                  triggeringPrincipal: principal,
                  originAttributes: principal ? principal.originAttributes : {},
                  isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
 
     if (href) {
       try {
         BrowserUtils.urlSecurityCheck(href, principal);
       } catch (e) {
@@ -574,42 +607,23 @@ var ClickEventHandler = {
       reason = "malware";
     } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
       reason = "unwanted";
     }
 
     let docShell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                                        .getInterface(Ci.nsIWebNavigation)
                                       .QueryInterface(Ci.nsIDocShell);
-    let blockedInfo = {};
-    if (docShell.failedChannel) {
-      let classifiedChannel = docShell.failedChannel.
-                              QueryInterface(Ci.nsIClassifiedChannel);
-      if (classifiedChannel) {
-        let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel);
-
-        let reportUri = httpChannel.URI.clone();
-
-        // Remove the query to avoid leaking sensitive data
-        if (reportUri instanceof Ci.nsIURL) {
-          reportUri.query = "";
-        }
-
-        blockedInfo = { list: classifiedChannel.matchedList,
-                        provider: classifiedChannel.matchedProvider,
-                        uri: reportUri.asciiSpec };
-      }
-    }
 
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
       reason,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
-      blockedInfo
+      blockedInfo: getSiteBlockedErrorDetails(docShell),
     });
   },
 
   onAboutNetError(event, documentURI) {
     let elmId = event.originalTarget.getAttribute("id");
     if (elmId == "returnButton") {
       sendAsyncMessage("Browser:SSLErrorGoBack", {});
       return;
@@ -785,48 +799,51 @@ addMessageListener("ContextMenu:SaveVide
   let ctxDraw = canvas.getContext("2d");
   ctxDraw.drawImage(video, 0, 0);
   sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
     dataURL: canvas.toDataURL("image/jpeg", ""),
   });
 });
 
 addMessageListener("ContextMenu:MediaCommand", (message) => {
-  let media = message.objects.element;
-
-  switch (message.data.command) {
-    case "play":
-      media.play();
-      break;
-    case "pause":
-      media.pause();
-      break;
-    case "loop":
-      media.loop = !media.loop;
-      break;
-    case "mute":
-      media.muted = true;
-      break;
-    case "unmute":
-      media.muted = false;
-      break;
-    case "playbackRate":
-      media.playbackRate = message.data.data;
-      break;
-    case "hidecontrols":
-      media.removeAttribute("controls");
-      break;
-    case "showcontrols":
-      media.setAttribute("controls", "true");
-      break;
-    case "fullscreen":
-      if (content.document.fullscreenEnabled)
-        media.requestFullscreen();
-      break;
-  }
+  E10SUtils.wrapHandlingUserInput(
+    content, message.data.handlingUserInput,
+    () => {
+      let media = message.objects.element;
+      switch (message.data.command) {
+        case "play":
+          media.play();
+          break;
+        case "pause":
+          media.pause();
+          break;
+        case "loop":
+          media.loop = !media.loop;
+          break;
+        case "mute":
+          media.muted = true;
+          break;
+        case "unmute":
+          media.muted = false;
+          break;
+        case "playbackRate":
+          media.playbackRate = message.data.data;
+          break;
+        case "hidecontrols":
+          media.removeAttribute("controls");
+          break;
+        case "showcontrols":
+          media.setAttribute("controls", "true");
+          break;
+        case "fullscreen":
+          if (content.document.fullscreenEnabled)
+            media.requestFullscreen();
+          break;
+      }
+    });
 });
 
 addMessageListener("ContextMenu:Canvas:ToBlobURL", (message) => {
   message.objects.target.toBlob((blob) => {
     let blobURL = URL.createObjectURL(blob);
     sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
   });
 });
@@ -1236,18 +1253,20 @@ var PageInfoListener = {
     // One swi^H^H^Hif-else to rule them all.
     if (elem instanceof content.HTMLImageElement) {
       addImage(elem.src, strings.mediaImg,
                (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, elem, false);
     } else if (elem instanceof content.SVGImageElement) {
       try {
         // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
         //       or the URI formed from the baseURI and the URL is not a valid URI.
-        let href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
-        addImage(href, strings.mediaImg, "", elem, false);
+        if (elem.href.baseVal) {
+          let href = Services.io.newURI(elem.href.baseVal, null, Services.io.newURI(elem.baseURI)).spec;
+          addImage(href, strings.mediaImg, "", elem, false);
+        }
       } catch (e) { }
     } else if (elem instanceof content.HTMLVideoElement) {
       addImage(elem.currentSrc, strings.mediaVideo, "", elem, false);
     } else if (elem instanceof content.HTMLAudioElement) {
       addImage(elem.currentSrc, strings.mediaAudio, "", elem, false);
     } else if (elem instanceof content.HTMLLinkElement) {
       if (elem.rel && /\bicon\b/i.test(elem.rel)) {
         addImage(elem.href, strings.mediaLink, "", elem, false);
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -567,22 +567,22 @@ body.compact #newtab-search-container {
 
 .newtab-customize-panel-item,
 .newtab-customize-complex-option {
   display: block;
   text-align: start;
   background-color: #F9F9F9;
 }
 
-.newtab-customize-panel-item[selected]:-moz-locale-dir(rtl) {
+.newtab-customize-panel-item[selected]:dir(rtl){
   background-position: right 15px center;
 }
 
-.newtab-customize-complex-option:hover > .selectable:not([selected]):-moz-locale-dir(rtl),
-.selectable:not([selected]):hover:-moz-locale-dir(rtl) {
+.newtab-customize-complex-option:hover > .selectable:not([selected]):dir(rtl),
+.selectable:not([selected]):hover:dir(rtl) {
   background-position: right 15px center;
 }
 
 .newtab-customize-panel-item:not([selected]),
 .newtab-customize-panel-subitem:not([selected]){
   color: #7A7A7A;
 }
 
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -946,21 +946,27 @@ nsContextMenu.prototype = {
            this.target.mediaKeys.keySystem != "org.w3.clearkey";
   },
 
   _openLinkInParameters : function (extra) {
     let params = { charset: gContextMenuContentData.charSet,
                    originPrincipal: this.principal,
                    referrerURI: gContextMenuContentData.documentURIObject,
                    referrerPolicy: gContextMenuContentData.referrerPolicy,
+                   frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
                    noReferrer: this.linkHasNoReferrer };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
+    if (!this.isRemote) {
+      params.frameOuterWindowID = this.target.ownerGlobal
+                                      .QueryInterface(Ci.nsIInterfaceRequestor)
+                                      .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+    }
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if ("userContextId" in params &&
         params.userContextId != gContextMenuContentData.userContextId) {
       params.noReferrer = true;
     }
 
     return params;
@@ -1123,20 +1129,22 @@ nsContextMenu.prototype = {
       };
       mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
     });
   },
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia: function(e) {
     let referrerURI = gContextMenuContentData.documentURIObject;
+    let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
     if (this.onCanvas) {
       this._canvasToBlobURL(this.target).then(function(blobURL) {
         openUILink(blobURL, e, { disallowInheritPrincipal: true,
-                                 referrerURI: referrerURI });
+                                 referrerURI: referrerURI,
+                                 originPrincipal: systemPrincipal});
       }, Cu.reportError);
     }
     else {
       urlSecurityCheck(this.mediaURL,
                        this.browser.contentPrincipal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
       openUILink(this.mediaURL, e, { disallowInheritPrincipal: true,
                                      referrerURI: referrerURI });
@@ -1728,18 +1736,23 @@ nsContextMenu.prototype = {
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
   },
 
   mediaCommand : function CM_mediaCommand(command, data) {
     let mm = this.browser.messageManager;
+    let win = this.browser.ownerGlobal;
+    let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
     mm.sendAsyncMessage("ContextMenu:MediaCommand",
-                        {command: command, data: data},
+                        {command: command,
+                         data: data,
+                         handlingUserInput: windowUtils.isHandlingUserInput},
                         {element: this.target});
   },
 
   copyMediaLocation : function () {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL);
   },
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -413,17 +413,17 @@ function resetPageInfo(args) {
     mediaTab.hidden = true;
   }
   gImageView.clear();
   gImageHash = {};
 
   /* Reset Feeds Tab */
   var feedListbox = document.getElementById("feedListbox");
   while (feedListbox.firstChild)
-    feedListbox.removeChild(feedListbox.firstChild);
+    feedListbox.firstChild.remove();
 
   /* Call registered overlay reset functions */
   onResetRegistry.forEach(function(func) { func(); });
 
   /* Rebuild the data */
   loadTab(args);
 }
 
@@ -997,22 +997,19 @@ function formatNumber(number) {
   return (+number).toLocaleString();  // coerce number to a numeric value before calling toLocaleString()
 }
 
 function formatDate(datestr, unknown) {
   var date = new Date(datestr);
   if (!date.valueOf())
     return unknown;
 
-  const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
-                 .getService(Components.interfaces.nsIXULChromeRegistry)
-                 .getSelectedLocale("global", true);
   const dtOptions = { year: "numeric", month: "long", day: "numeric",
                       hour: "numeric", minute: "numeric", second: "numeric" };
-  return date.toLocaleString(locale, dtOptions);
+  return date.toLocaleString(undefined, dtOptions);
 }
 
 function doCopy() {
   if (!gClipboardHelper)
     return;
 
   var elem = document.commandDispatcher.focusedElement;
 
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -213,27 +213,28 @@ function onIndexedDBClear() {
 }
 
 function onIndexedDBUsageCallback(request) {
   let uri = request.principal.URI;
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
   }
 
-  if (request.usage) {
+  let usage = request.result.usage;
+  if (usage) {
     if (!("DownloadUtils" in window)) {
       Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
     }
 
     var status = document.getElementById("indexedDBStatus");
     var button = document.getElementById("indexedDBClear");
 
     status.value =
       gBundle.getFormattedString("indexedDBUsage",
-                                 DownloadUtils.convertByteUnits(request.usage));
+                                 DownloadUtils.convertByteUnits(usage));
     status.removeAttribute("hidden");
     button.removeAttribute("hidden");
   }
 }
 
 function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
   let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
   permPluginTemplate.setAttribute("permString", aPermissionString);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -50,25 +50,25 @@ var security = {
     if (!isInsecure && status) {
       status.QueryInterface(nsISSLStatus);
       var cert = status.serverCert;
       var issuerName =
         this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
 
       var retval = {
         hostName,
-        cAName : issuerName,
-        encryptionAlgorithm : undefined,
-        encryptionStrength : undefined,
+        cAName: issuerName,
+        encryptionAlgorithm: undefined,
+        encryptionStrength: undefined,
         version: undefined,
         isBroken,
         isMixed,
         isEV,
         cert,
-        certificateTransparency : undefined
+        certificateTransparency: undefined
       };
 
       var version;
       try {
         retval.encryptionAlgorithm = status.cipherName;
         retval.encryptionStrength = status.secretKeyLength;
         version = status.protocolVersion;
       } catch (e) {
@@ -106,25 +106,25 @@ var security = {
           retval.certificateTransparency = "Compliant";
           break;
       }
 
       return retval;
     }
     return {
       hostName,
-      cAName : "",
-      encryptionAlgorithm : "",
-      encryptionStrength : 0,
+      cAName: "",
+      encryptionAlgorithm: "",
+      encryptionStrength: 0,
       version: "",
       isBroken,
       isMixed,
       isEV,
-      cert : null,
-      certificateTransparency : null
+      cert: null,
+      certificateTransparency: null
     };
   },
 
   // Find the secureBrowserUI object (if present)
   _getSecurityUI() {
     if (window.opener.gBrowser)
       return window.opener.gBrowser.securityUI;
     return null;
@@ -160,27 +160,27 @@ var security = {
       eTLD = this.uri.asciiHost;
     }
 
     if (win) {
       win.gCookiesWindow.setFilter(eTLD);
       win.focus();
     } else
       window.openDialog("chrome://browser/content/preferences/cookies.xul",
-                        "Browser:Cookies", "", {filterString : eTLD});
+                        "Browser:Cookies", "", {filterString: eTLD});
   },
 
   /**
    * Open the login manager window
    */
   viewPasswords() {
     LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
   },
 
-  _cert : null
+  _cert: null
 };
 
 function securityOnLoad(uri, windowInfo) {
   security.init(uri, windowInfo);
 
   var info = security._getSecurityInfo();
   if (!info) {
     document.getElementById("securityTab").hidden = true;
@@ -301,17 +301,17 @@ function securityOnLoad(uri, windowInfo)
 function setText(id, value) {
   var element = document.getElementById(id);
   if (!element)
     return;
   if (element.localName == "textbox" || element.localName == "label")
     element.value = value;
   else {
     if (element.hasChildNodes())
-      element.removeChild(element.firstChild);
+      element.firstChild.remove();
     var textNode = document.createTextNode(value);
     element.appendChild(textNode);
   }
 }
 
 function viewCertHelper(parent, cert) {
   if (!cert)
     return;
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -1,17 +1,16 @@
 # to be included inside a popupset element
 
     <panel id="notification-popup"
            type="arrow"
            position="after_start"
            hidden="true"
            orient="vertical"
            noautofocus="true"
-           followanchor="false"
            role="alert"/>
 
     <popupnotification id="webRTC-shareDevices-notification" hidden="true">
       <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
         <label value="&getUserMedia.selectCamera.label;"
                accesskey="&getUserMedia.selectCamera.accesskey;"
                control="webRTC-selectCamera-menulist"/>
         <menulist id="webRTC-selectCamera-menulist">
--- a/browser/base/content/report-phishing-overlay.xul
+++ b/browser/base/content/report-phishing-overlay.xul
@@ -24,12 +24,12 @@
               observes="reportPhishingBroadcaster"
               oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
               onclick="checkForMiddleClick(this, event);"/>
     <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
               label="&safeb.palm.notdeceptive.label;"
               accesskey="&safeb.palm.notdeceptive.accesskey;"
               insertbefore="aboutSeparator"
               observes="reportPhishingErrorBroadcaster"
-              oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');"
+              oncommand="ReportFalseDeceptiveSite();"
               onclick="checkForMiddleClick(this, event);"/>
   </menupopup>
 </overlay>
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -184,18 +184,18 @@ Sanitizer.prototype = {
     }
   }),
 
   // Time span only makes sense in certain cases.  Consumers who want
   // to only clear some private data can opt in by setting this to false,
   // and can optionally specify a specific range.  If timespan is not ignored,
   // and range is not set, sanitize() will use the value of the timespan
   // pref to determine a range
-  ignoreTimespan : true,
-  range : null,
+  ignoreTimespan: true,
+  range: null,
 
   items: {
     cache: {
       clear: Task.async(function* (range) {
         let seenException;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
 
@@ -729,41 +729,28 @@ Sanitizer.clearPluginData = Task.async(f
 
     // Determine age range in seconds. (-1 means clear all.) We don't know
     // that range[1] is actually now, so we compute age range based
     // on the lower bound. If range results in a negative age, do nothing.
     let age = range ? (Date.now() / 1000 - range[0] / 1000000) : -1;
     if (!range || age >= 0) {
       let tags = ph.getPluginTags();
       for (let tag of tags) {
-        let refObj = {};
-        let probe = "";
-        if (/\bFlash\b/.test(tag.name)) {
-          probe = tag.loaded ? "FX_SANITIZE_LOADED_FLASH"
-                             : "FX_SANITIZE_UNLOADED_FLASH";
-          TelemetryStopwatch.start(probe, refObj);
-        }
         try {
           let rv = yield new Promise(resolve =>
             ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, resolve)
           );
           // If the plugin doesn't support clearing by age, clear everything.
           if (rv == Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
             yield new Promise(resolve =>
               ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
             );
           }
-          if (probe) {
-            TelemetryStopwatch.finish(probe, refObj);
-          }
         } catch (ex) {
           // Ignore errors from plug-ins
-          if (probe) {
-            TelemetryStopwatch.cancel(probe, refObj);
-          }
         }
       }
     }
   });
 
   try {
     // We don't want to wait for this operation to complete...
     promiseClearPluginData = promiseClearPluginData(range);
--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -1,21 +1,18 @@
 <?xml version="1.0"?>
 
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# 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/.
+<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -->
+<!-- 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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/"?>
 <?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-#endif
 
 <?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
 
 <!DOCTYPE prefwindow [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
   %brandDTD;
   %sanitizeDTD;
@@ -31,26 +28,16 @@
 
   <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
     <stringbundle id="bundleBrowser"
                   src="chrome://browser/locale/browser.properties"/>
 
     <script type="application/javascript"
             src="chrome://browser/content/sanitize.js"/>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    <script type="application/javascript"
-            src="chrome://global/content/globalOverlay.js"/>
-    <script type="application/javascript"
-            src="chrome://browser/content/places/treeView.js"/>
-    <script type="application/javascript"><![CDATA[
-      Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-      Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
-    ]]></script>
-#endif
 
     <script type="application/javascript"
             src="chrome://browser/content/sanitizeDialog.js"/>
 
     <preferences id="sanitizePreferences">
       <preference id="privacy.cpd.history"               name="privacy.cpd.history"               type="bool"/>
       <preference id="privacy.cpd.formdata"              name="privacy.cpd.formdata"              type="bool"/>
       <preference id="privacy.cpd.downloads"             name="privacy.cpd.downloads"             type="bool" disabled="true"/>
@@ -72,65 +59,43 @@
              accesskey="&clearTimeDuration.accesskey;"
              control="sanitizeDurationChoice"
              id="sanitizeDurationLabel"/>
       <menulist id="sanitizeDurationChoice"
                 preference="privacy.sanitize.timeSpan"
                 onselect="gSanitizePromptDialog.selectByTimespan();"
                 flex="1">
         <menupopup id="sanitizeDurationPopup">
-#ifdef CRH_DIALOG_TREE_VIEW
-          <menuitem label="" value="-1" id="sanitizeDurationCustom"/>
-#endif
           <menuitem label="&clearTimeDuration.lastHour;" value="1"/>
           <menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
           <menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
           <menuitem label="&clearTimeDuration.today;" value="4"/>
           <menuseparator/>
           <menuitem label="&clearTimeDuration.everything;" value="0"/>
         </menupopup>
       </menulist>
       <label id="sanitizeDurationSuffixLabel"
              value="&clearTimeDuration.suffix;"/>
     </hbox>
 
     <separator class="thin"/>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    <deck id="durationDeck">
-      <tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
-            disabled="true" disableKeyNavigation="true">
-        <treecols>
-          <treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
-          <splitter class="tree-splitter"/>
-          <treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
-        </treecols>
-        <treechildren id="placesTreechildren"
-                      ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
-                      ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
-                      onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
-                      onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
-      </tree>
-#endif
 
       <vbox id="sanitizeEverythingWarningBox">
         <spacer flex="1"/>
         <hbox align="center">
           <image id="sanitizeEverythingWarningIcon"/>
           <vbox id="sanitizeEverythingWarningDescBox" flex="1">
             <description id="sanitizeEverythingWarning"/>
             <description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
           </vbox>
         </hbox>
         <spacer flex="1"/>
       </vbox>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    </deck>
-#endif
 
     <separator class="thin"/>
 
     <hbox id="detailsExpanderWrapper" align="center">
       <button type="image"
               id="detailsExpander"
               class="expander-down"
               persist="class"
--- a/browser/base/content/sanitizeDialog.js
+++ b/browser/base/content/sanitizeDialog.js
@@ -251,639 +251,12 @@ var gSanitizePromptDialog = {
   toggleItemList: function ()
   {
     var itemList = document.getElementById("itemList");
 
     if (itemList.collapsed)
       this.showItemList();
     else
       this.hideItemList();
-  },
-
-#ifdef CRH_DIALOG_TREE_VIEW
-  // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
-  // Sanitizer.TIMESPAN_2HOURS, et al.  This should match the value attribute
-  // of the sanitizeDurationCustom menuitem.
-  get TIMESPAN_CUSTOM()
-  {
-    return -1;
-  },
-
-  get placesTree()
-  {
-    if (!this._placesTree)
-      this._placesTree = document.getElementById("placesTree");
-    return this._placesTree;
-  },
-
-  init: function ()
-  {
-    // This is used by selectByTimespan() to determine if the window has loaded.
-    this._inited = true;
-
-    var s = new Sanitizer();
-    s.prefDomain = "privacy.cpd.";
-
-    document.documentElement.getButton("accept").label =
-      this.bundleBrowser.getString("sanitizeButtonOK");
-
-    this.selectByTimespan();
-  },
-
-  /**
-   * Sets up the hashes this.durationValsToRows, which maps duration values
-   * to rows in the tree, this.durationRowsToVals, which maps rows in
-   * the tree to duration values, and this.durationStartTimes, which maps
-   * duration values to their corresponding start times.
-   */
-  initDurationDropdown: function ()
-  {
-    // First, calculate the start times for each duration.
-    this.durationStartTimes = {};
-    var durVals = [];
-    var durPopup = document.getElementById("sanitizeDurationPopup");
-    var durMenuitems = durPopup.childNodes;
-    for (let i = 0; i < durMenuitems.length; i++) {
-      let durMenuitem = durMenuitems[i];
-      let durVal = parseInt(durMenuitem.value);
-      if (durMenuitem.localName === "menuitem" &&
-          durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
-          durVal !== this.TIMESPAN_CUSTOM) {
-        durVals.push(durVal);
-        let durTimes = Sanitizer.getClearRange(durVal);
-        this.durationStartTimes[durVal] = durTimes[0];
-      }
-    }
-
-    // Sort the duration values ascending.  Because one tree index can map to
-    // more than one duration, this ensures that this.durationRowsToVals maps
-    // a row index to the largest duration possible in the code below.
-    durVals.sort();
-
-    // Now calculate the rows in the tree of the durations' start times.  For
-    // each duration, we are looking for the node in the tree whose time is the
-    // smallest time greater than or equal to the duration's start time.
-    this.durationRowsToVals = {};
-    this.durationValsToRows = {};
-    var view = this.placesTree.view;
-    // For all rows in the tree except the grippy row...
-    for (let i = 0; i < view.rowCount - 1; i++) {
-      let unfoundDurVals = [];
-      let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
-                     nodeForTreeIndex(i).time;
-      // For all durations whose rows have not yet been found in the tree, see
-      // if index i is their index.  An index may map to more than one duration,
-      // in which case the final duration (the largest) wins.
-      for (let j = 0; j < durVals.length; j++) {
-        let durVal = durVals[j];
-        let durStartTime = this.durationStartTimes[durVal];
-        if (nodeTime < durStartTime) {
-          this.durationValsToRows[durVal] = i - 1;
-          this.durationRowsToVals[i - 1] = durVal;
-        }
-        else
-          unfoundDurVals.push(durVal);
-      }
-      durVals = unfoundDurVals;
-    }
-
-    // If any durations were not found above, then every node in the tree has a
-    // time greater than or equal to the duration.  In other words, those
-    // durations include the entire tree (except the grippy row).
-    for (let i = 0; i < durVals.length; i++) {
-      let durVal = durVals[i];
-      this.durationValsToRows[durVal] = view.rowCount - 2;
-      this.durationRowsToVals[view.rowCount - 2] = durVal;
-    }
-  },
-
-  /**
-   * If the Places tree is not set up, sets it up.  Otherwise does nothing.
-   */
-  ensurePlacesTreeIsInited: function ()
-  {
-    if (this._placesTreeIsInited)
-      return;
-
-    this._placesTreeIsInited = true;
-
-    // Either "Last Four Hours" or "Today" will have the most history.  If
-    // it's been more than 4 hours since today began, "Today" will. Otherwise
-    // "Last Four Hours" will.
-    var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
-
-    // If it's been less than 4 hours since today began, use the past 4 hours.
-    if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
-      times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
-    }
-
-    var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
-                   getService(Ci.nsINavHistoryService);
-    var query = histServ.getNewQuery();
-    query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
-    query.beginTime = times[0];
-    query.endTimeReference = query.TIME_RELATIVE_EPOCH;
-    query.endTime = times[1];
-    var opts = histServ.getNewQueryOptions();
-    opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
-    opts.queryType = opts.QUERY_TYPE_HISTORY;
-    var result = histServ.executeQuery(query, opts);
-
-    var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
-                                                      new PlacesTreeView());
-    result.addObserver(view, false);
-    this.initDurationDropdown();
-  },
-
-  /**
-   * Called on select of the duration dropdown and when grippyMoved() sets a
-   * duration based on the location of the grippy row.  Selects all the nodes in
-   * the tree that are contained in the selected duration.  If clearing
-   * everything, the warning panel is shown instead.
-   */
-  selectByTimespan: function ()
-  {
-    // This method is the onselect handler for the duration dropdown.  As a
-    // result it's called a couple of times before onload calls init().
-    if (!this._inited)
-      return;
-
-    var durDeck = document.getElementById("durationDeck");
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durVal = parseInt(durList.value);
-    var durCustom = document.getElementById("sanitizeDurationCustom");
-
-    // If grippy row is not at a duration boundary, show the custom menuitem;
-    // otherwise, hide it.  Since the user cannot specify a custom duration by
-    // using the dropdown, this conditional is true only when this method is
-    // called onselect from grippyMoved(), so no selection need be made.
-    if (durVal === this.TIMESPAN_CUSTOM) {
-      durCustom.hidden = false;
-      return;
-    }
-    durCustom.hidden = true;
-
-    // If clearing everything, show the warning and change the dialog's title.
-    if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
-      this.prepareWarning();
-      durDeck.selectedIndex = 1;
-      window.document.title =
-        this.bundleBrowser.getString("sanitizeDialog2.everything.title");
-      document.documentElement.getButton("accept").disabled = false;
-      return;
-    }
-
-    // Otherwise -- if clearing a specific time range -- select that time range
-    // in the tree.
-    this.ensurePlacesTreeIsInited();
-    durDeck.selectedIndex = 0;
-    window.document.title =
-      window.document.documentElement.getAttribute("noneverythingtitle");
-    var durRow = this.durationValsToRows[durVal];
-    gContiguousSelectionTreeHelper.rangedSelect(durRow);
-    gContiguousSelectionTreeHelper.scrollToGrippy();
-
-    // If duration is empty (there are no selected rows), disable the dialog's
-    // OK button.
-    document.documentElement.getButton("accept").disabled = durRow < 0;
-  },
-
-  sanitize: function ()
-  {
-    // Update pref values before handing off to the sanitizer (bug 453440)
-    this.updatePrefs();
-    var s = new Sanitizer();
-    s.prefDomain = "privacy.cpd.";
-
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durValue = parseInt(durList.value);
-    s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
-
-    // Set the sanitizer's time range if we're not clearing everything.
-    if (!s.ignoreTimespan) {
-      // If user selected a custom timespan, use that.
-      if (durValue === this.TIMESPAN_CUSTOM) {
-        var view = this.placesTree.view;
-        var now = Date.now() * 1000;
-        // We disable the dialog's OK button if there's no selection, but we'll
-        // handle that case just in... case.
-        if (view.selection.getRangeCount() === 0)
-          s.range = [now, now];
-        else {
-          var startIndexRef = {};
-          // Tree sorted by visit date DEscending, so start time time comes last.
-          view.selection.getRangeAt(0, {}, startIndexRef);
-          view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
-          var startNode = view.nodeForTreeIndex(startIndexRef.value);
-          s.range = [startNode.time, now];
-        }
-      }
-      // Otherwise use the predetermined range.
-      else
-        s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
-    }
-
-    try {
-      s.sanitize(); // We ignore the resulting Promise
-    } catch (er) {
-      Components.utils.reportError("Exception during sanitize: " + er);
-    }
-    return true;
-  },
-
-  /**
-   * In order to mark the custom Places tree view and its nsINavHistoryResult
-   * for garbage collection, we need to break the reference cycle between the
-   * two.
-   */
-  unload: function ()
-  {
-    let result = this.placesTree.getResult();
-    result.removeObserver(this.placesTree.view);
-    this.placesTree.view = null;
-  },
-
-  /**
-   * Called when the user moves the grippy by dragging it, clicking in the tree,
-   * or on keypress.  Updates the duration dropdown so that it displays the
-   * appropriate specific or custom duration.
-   *
-   * @param aEventName
-   *        The name of the event whose handler called this method, e.g.,
-   *        "ondragstart", "onkeypress", etc.
-   * @param aEvent
-   *        The event captured in the event handler.
-   */
-  grippyMoved: function (aEventName, aEvent)
-  {
-    gContiguousSelectionTreeHelper[aEventName](aEvent);
-    var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durValue = parseInt(durList.value);
-
-    // Multiple durations can map to the same row.  Don't update the dropdown
-    // if the current duration is valid for lastSelRow.
-    if ((durValue !== this.TIMESPAN_CUSTOM ||
-         lastSelRow in this.durationRowsToVals) &&
-        (durValue === this.TIMESPAN_CUSTOM ||
-         this.durationValsToRows[durValue] !== lastSelRow)) {
-      // Setting durList.value causes its onselect handler to fire, which calls
-      // selectByTimespan().
-      if (lastSelRow in this.durationRowsToVals)
-        durList.value = this.durationRowsToVals[lastSelRow];
-      else
-        durList.value = this.TIMESPAN_CUSTOM;
-    }
-
-    // If there are no selected rows, disable the dialog's OK button.
-    document.documentElement.getButton("accept").disabled = lastSelRow < 0;
   }
-#endif
-
-};
 
 
-#ifdef CRH_DIALOG_TREE_VIEW
-/**
- * A helper for handling contiguous selection in the tree.
- */
-var gContiguousSelectionTreeHelper = {
-
-  /**
-   * Gets the tree associated with this helper.
-   */
-  get tree()
-  {
-    return this._tree;
-  },
-
-  /**
-   * Sets the tree that this module handles.  The tree is assigned a new view
-   * that is equipped to handle contiguous selection.  You can pass in an
-   * object that will be used as the prototype of the new view.  Otherwise
-   * the tree's current view is used as the prototype.
-   *
-   * @param  aTreeElement
-   *         The tree element
-   * @param  aProtoTreeView
-   *         If defined, this will be used as the prototype of the tree's new
-   *         view
-   * @return The new view
-   */
-  setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
-  {
-    this._tree = aTreeElement;
-    var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
-    aTreeElement.view = newView;
-    return newView;
-  },
-
-  /**
-   * The index of the row that the grippy occupies.  Note that the index of the
-   * last selected row is getGrippyRow() - 1.  If getGrippyRow() is 0, then
-   * no selection exists.
-   *
-   * @return The row index of the grippy
-   */
-  getGrippyRow: function CSTH_getGrippyRow()
-  {
-    var sel = this.tree.view.selection;
-    var rangeCount = sel.getRangeCount();
-    if (rangeCount === 0)
-      return 0;
-    if (rangeCount !== 1) {
-      throw "contiguous selection tree helper: getGrippyRow called with " +
-            "multiple selection ranges";
-    }
-    var max = {};
-    sel.getRangeAt(0, {}, max);
-    return max.value + 1;
-  },
-
-  /**
-   * Helper function for the dragover event.  Your dragover listener should
-   * call this.  It updates the selection in the tree under the mouse.
-   *
-   * @param aEvent
-   *        The observed dragover event
-   */
-  ondragover: function CSTH_ondragover(aEvent)
-  {
-    // Without this when dragging on Windows the mouse cursor is a "no" sign.
-    // This makes it a drop symbol.
-    var ds = Cc["@mozilla.org/widget/dragservice;1"].
-             getService(Ci.nsIDragService).
-             getCurrentSession();
-    ds.canDrop = true;
-    ds.dragAction = 0;
-
-    var tbo = this.tree.treeBoxObject;
-    aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
-    var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (hoverRow < 0)
-      return;
-
-    this.rangedSelect(hoverRow - 1);
-  },
-
-  /**
-   * Helper function for the dragstart event.  Your dragstart listener should
-   * call this.  It starts a drag session.
-   *
-   * @param aEvent
-   *        The observed dragstart event
-   */
-  ondragstart: function CSTH_ondragstart(aEvent)
-  {
-    var tbo = this.tree.treeBoxObject;
-    var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (clickedRow !== this.getGrippyRow())
-      return;
-
-    // This part is a hack.  What we really want is a grab and slide, not
-    // drag and drop.  Start a move drag session with dummy data and a
-    // dummy region.  Set the region's coordinates to (Infinity, Infinity)
-    // so it's drawn offscreen and its size to (1, 1).
-    var arr = Cc["@mozilla.org/array;1"].
-              createInstance(Ci.nsIMutableArray);
-    var trans = Cc["@mozilla.org/widget/transferable;1"].
-                createInstance(Ci.nsITransferable);
-    trans.init(null);
-    trans.setTransferData('dummy-flavor', null, 0);
-    arr.appendElement(trans, /* weak = */ false);
-    var reg = Cc["@mozilla.org/gfx/region;1"].
-              createInstance(Ci.nsIScriptableRegion);
-    reg.setToRect(Infinity, Infinity, 1, 1);
-    var ds = Cc["@mozilla.org/widget/dragservice;1"].
-             getService(Ci.nsIDragService);
-    ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
-  },
-
-  /**
-   * Helper function for the keypress event.  Your keypress listener should
-   * call this.  Users can use Up, Down, Page Up/Down, Home, and End to move
-   * the bottom of the selection window.
-   *
-   * @param aEvent
-   *        The observed keypress event
-   */
-  onkeypress: function CSTH_onkeypress(aEvent)
-  {
-    var grippyRow = this.getGrippyRow();
-    var tbo = this.tree.treeBoxObject;
-    var rangeEnd;
-    switch (aEvent.keyCode) {
-    case aEvent.DOM_VK_HOME:
-      rangeEnd = 0;
-      break;
-    case aEvent.DOM_VK_PAGE_UP:
-      rangeEnd = grippyRow - tbo.getPageLength();
-      break;
-    case aEvent.DOM_VK_UP:
-      rangeEnd = grippyRow - 2;
-      break;
-    case aEvent.DOM_VK_DOWN:
-      rangeEnd = grippyRow;
-      break;
-    case aEvent.DOM_VK_PAGE_DOWN:
-      rangeEnd = grippyRow + tbo.getPageLength();
-      break;
-    case aEvent.DOM_VK_END:
-      rangeEnd = this.tree.view.rowCount - 2;
-      break;
-    default:
-      return;
-      break;
-    }
-
-    aEvent.stopPropagation();
-
-    // First, clip rangeEnd.  this.rangedSelect() doesn't clip the range if we
-    // select past the ends of the tree.
-    if (rangeEnd < 0)
-      rangeEnd = -1;
-    else if (this.tree.view.rowCount - 2 < rangeEnd)
-      rangeEnd = this.tree.view.rowCount - 2;
-
-    // Next, (de)select.
-    this.rangedSelect(rangeEnd);
-
-    // Finally, scroll the tree.  We always want one row above and below the
-    // grippy row to be visible if possible.
-    if (rangeEnd < grippyRow) // moved up
-      tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
-    else {                    // moved down
-      if (rangeEnd + 2 < this.tree.view.rowCount)
-        tbo.ensureRowIsVisible(rangeEnd + 2);
-      else if (rangeEnd + 1 < this.tree.view.rowCount)
-        tbo.ensureRowIsVisible(rangeEnd + 1);
-    }
-  },
-
-  /**
-   * Helper function for the mousedown event.  Your mousedown listener should
-   * call this.  Users can click on individual rows to make the selection
-   * jump to them immediately.
-   *
-   * @param aEvent
-   *        The observed mousedown event
-   */
-  onmousedown: function CSTH_onmousedown(aEvent)
-  {
-    var tbo = this.tree.treeBoxObject;
-    var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
-      return;
-
-    if (clickedRow < this.getGrippyRow())
-      this.rangedSelect(clickedRow);
-    else if (clickedRow > this.getGrippyRow())
-      this.rangedSelect(clickedRow - 1);
-  },
-
-  /**
-   * Selects range [0, aEndRow] in the tree.  The grippy row will then be at
-   * index aEndRow + 1.  aEndRow may be -1, in which case the selection is
-   * cleared and the grippy row will be at index 0.
-   *
-   * @param aEndRow
-   *        The range [0, aEndRow] will be selected.
-   */
-  rangedSelect: function CSTH_rangedSelect(aEndRow)
-  {
-    var tbo = this.tree.treeBoxObject;
-    if (aEndRow < 0)
-      this.tree.view.selection.clearSelection();
-    else
-      this.tree.view.selection.rangedSelect(0, aEndRow, false);
-    tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
-  },
-
-  /**
-   * Scrolls the tree so that the grippy row is in the center of the view.
-   */
-  scrollToGrippy: function CSTH_scrollToGrippy()
-  {
-    var rowCount = this.tree.view.rowCount;
-    var tbo = this.tree.treeBoxObject;
-    var pageLen = tbo.getPageLength() ||
-                  parseInt(this.tree.getAttribute("rows")) ||
-                  10;
-
-    // All rows fit on a single page.
-    if (rowCount <= pageLen)
-      return;
-
-    var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
-
-    // Grippy row is in first half of first page.
-    if (scrollToRow < 0)
-      scrollToRow = 0;
-
-    // Grippy row is in last half of last page.
-    else if (rowCount < scrollToRow + pageLen)
-      scrollToRow = rowCount - pageLen;
-
-    tbo.scrollToRow(scrollToRow);
-  },
-
-  /**
-   * Creates a new tree view suitable for contiguous selection.  If
-   * aProtoTreeView is specified, it's used as the new view's prototype.
-   * Otherwise the tree's current view is used as the prototype.
-   *
-   * @param aProtoTreeView
-   *        Used as the new view's prototype if specified
-   */
-  _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
-  {
-    var view = aProtoTreeView;
-    var that = this;
-
-    //XXXadw: When Alex gets the grippy icon done, this may or may not change,
-    //        depending on how we style it.
-    view.isSeparator = function CSTH_View_isSeparator(aRow)
-    {
-      return aRow === that.getGrippyRow();
-    };
-
-    // rowCount includes the grippy row.
-    view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
-    view.__defineGetter__("rowCount",
-      function CSTH_View_rowCount()
-      {
-        return this._rowCount + 1;
-      });
-
-    // This has to do with visual feedback in the view itself, e.g., drawing
-    // a small line underneath the dropzone.  Not what we want.
-    view.canDrop = function CSTH_View_canDrop() { return false; };
-
-    // No clicking headers to sort the tree or sort feedback on columns.
-    view.cycleHeader = function CSTH_View_cycleHeader() {};
-    view.sortingChanged = function CSTH_View_sortingChanged() {};
-
-    // Override a bunch of methods to account for the grippy row.
-
-    view._getCellProperties = view.getCellProperties;
-    view.getCellProperties =
-      function CSTH_View_getCellProperties(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "grippyRow";
-        if (aRow < grippyRow)
-          return this._getCellProperties(aRow, aCol);
-
-        return this._getCellProperties(aRow - 1, aCol);
-      };
-
-    view._getRowProperties = view.getRowProperties;
-    view.getRowProperties =
-      function CSTH_View_getRowProperties(aRow)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "grippyRow";
-
-        if (aRow < grippyRow)
-          return this._getRowProperties(aRow);
-
-        return this._getRowProperties(aRow - 1);
-      };
-
-    view._getCellText = view.getCellText;
-    view.getCellText =
-      function CSTH_View_getCellText(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "";
-        aRow = aRow < grippyRow ? aRow : aRow - 1;
-        return this._getCellText(aRow, aCol);
-      };
-
-    view._getImageSrc = view.getImageSrc;
-    view.getImageSrc =
-      function CSTH_View_getImageSrc(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "";
-        aRow = aRow < grippyRow ? aRow : aRow - 1;
-        return this._getImageSrc(aRow, aCol);
-      };
-
-    view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
-    view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
-    view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
-    view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
-    {
-      return aRow < this.rowCount - 1;
-    };
-
-    return view;
-  }
 };
-#endif
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -297,22 +297,17 @@ var RemoteTabViewer = {
 
       el = el.nextSibling;
     }
   },
 
   _refetchTabs(force) {
     if (!force) {
       // Don't bother refetching tabs if we already did so recently
-      let lastFetch = 0;
-      try {
-        lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
-      } catch (e) {
-        /* Just use the default value of 0 */
-      }
+      let lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch", 0);
 
       let now = Math.floor(Date.now() / 1000);
       if (now - lastFetch < 30) {
         return false;
       }
     }
 
     // Ask Sync to just do the tabs engine if it can.
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -255,17 +255,17 @@ var AboutPrivateBrowsingListener = {
   },
 };
 AboutPrivateBrowsingListener.init(this);
 
 var AboutReaderListener = {
 
   _articlePromise: null,
 
-  _isLeavingReaderMode: false,
+  _isLeavingReaderableReaderMode: false,
 
   init() {
     addEventListener("AboutReaderContentLoaded", this, false, true);
     addEventListener("DOMContentLoaded", this, false);
     addEventListener("pageshow", this, false);
     addEventListener("pagehide", this, false);
     addMessageListener("Reader:ToggleReaderMode", this);
     addMessageListener("Reader:PushState", this);
@@ -273,17 +273,17 @@ var AboutReaderListener = {
 
   receiveMessage(message) {
     switch (message.name) {
       case "Reader:ToggleReaderMode":
         if (!this.isAboutReader) {
           this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
           ReaderMode.enterReaderMode(docShell, content);
         } else {
-          this._isLeavingReaderMode = true;
+          this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
           ReaderMode.leaveReaderMode(docShell, content);
         }
         break;
 
       case "Reader:PushState":
         this.updateReaderButton(!!(message.data && message.data.isArticle));
         break;
     }
@@ -291,16 +291,21 @@ var AboutReaderListener = {
 
   get isAboutReader() {
     if (!content) {
       return false;
     }
     return content.document.documentURI.startsWith("about:reader");
   },
 
+  get isReaderableAboutReader() {
+    return this.isAboutReader &&
+      !content.document.documentElement.dataset.isError;
+  },
+
   handleEvent(aEvent) {
     if (aEvent.originalTarget.defaultView != content) {
       return;
     }
 
     switch (aEvent.type) {
       case "AboutReaderContentLoaded":
         if (!this.isAboutReader) {
@@ -312,22 +317,22 @@ var AboutReaderListener = {
           sendAsyncMessage("Reader:UpdateReaderButton");
           new AboutReader(global, content, this._articlePromise);
           this._articlePromise = null;
         }
         break;
 
       case "pagehide":
         this.cancelPotentialPendingReadabilityCheck();
-        // this._isLeavingReaderMode is used here to keep the Reader Mode icon
+        // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
         // visible in the location bar when transitioning from reader-mode page
-        // back to the source page.
-        sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderMode });
-        if (this._isLeavingReaderMode) {
-          this._isLeavingReaderMode = false;
+        // back to the readable source page.
+        sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode });
+        if (this._isLeavingReaderableReaderMode) {
+          this._isLeavingReaderableReaderMode = false;
         }
         break;
 
       case "pageshow":
         // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
         // event, so we need to rely on "pageshow" in this case.
         if (aEvent.persisted) {
           this.updateReaderButton();
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -92,19 +92,24 @@ 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[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
@@ -63,19 +63,16 @@
       <field name="mURIFixup" readonly="true">
         Components.classes["@mozilla.org/docshell/urifixup;1"]
                   .getService(Components.interfaces.nsIURIFixup);
       </field>
       <field name="_unifiedComplete" readonly="true">
          Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
                    .getService(Components.interfaces.mozIPlacesAutoComplete);
       </field>
-      <field name="AppConstants" readonly="true">
-        (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
-      </field>
       <field name="mTabBox" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
       </field>
       <field name="mPanelContainer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
       </field>
       <field name="mStringBundle">
         document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
@@ -103,17 +100,17 @@
       </field>
       <field name="mIsBusy">
         false
       </field>
       <field name="_outerWindowIDBrowserMap">
         new Map();
       </field>
       <field name="arrowKeysShouldWrap" readonly="true">
-        this.AppConstants.platform == "macosx";
+        AppConstants == "macosx";
       </field>
 
       <field name="_autoScrollPopup">
         null
       </field>
 
       <field name="_previewMode">
         false
@@ -1509,17 +1506,17 @@
             var aSkipAnimation;
             var aForceNotRemote;
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
-            var aForceBrowserInsertion;
+            var aCreateLazyBrowser;
             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;
@@ -1533,17 +1530,17 @@
               aForceNotRemote           = params.forceNotRemote;
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
-              aForceBrowserInsertion    = params.forceBrowserInsertion;
+              aCreateLazyBrowser        = params.createLazyBrowser;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
 
             var tab = this.addTab(aURI, {
                                   triggeringPrincipal: aTriggeringPrincipal,
@@ -1553,17 +1550,17 @@
                                   postData: aPostData,
                                   ownerTab: owner,
                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
                                   fromExternal: aFromExternal,
                                   relatedToCurrent: aRelatedToCurrent,
                                   skipAnimation: aSkipAnimation,
                                   allowMixedContent: aAllowMixedContent,
                                   forceNotRemote: aForceNotRemote,
-                                  forceBrowserInsertion: aForceBrowserInsertion,
+                                  createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
                                   isPrerendered: aIsPrerendered });
             if (!bgLoad)
@@ -2055,18 +2052,17 @@
         "canGoBack", "canGoForward", "goBack", "goForward", "permitUnload",
         "reload", "reloadWithFlags", "stop", "loadURI", "loadURIWithFlags",
         "goHome", "homePage", "gotoIndex", "currentURI", "documentURI",
         "preferences", "imageDocument", "isRemoteBrowser", "messageManager",
         "getTabBrowser", "finder", "fastFind", "sessionHistory", "contentTitle",
         "characterSet", "fullZoom", "textZoom", "webProgress",
         "addProgressListener", "removeProgressListener", "audioPlaybackStarted",
         "audioPlaybackStopped", "adjustPriority", "pauseMedia", "stopMedia",
-        "blockMedia", "resumeMedia", "audioMuted", "mute", "unmute",
-        "blockedPopups", "mIconURL", "lastURI", "userTypedValue",
+        "blockMedia", "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
         "purgeSessionHistory", "stopScroll", "startScroll"
       ]</field>
 
       <method name="_createLazyBrowser">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             let browser = aTab.linkedBrowser;
@@ -2192,17 +2188,18 @@
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
-            var aForceBrowserInsertion;
+            var aCreateLazyBrowser;
+            var aSkipBackgroundNotify;
             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;
@@ -2218,17 +2215,18 @@
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aEventDetail              = params.eventDetail;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
-              aForceBrowserInsertion    = params.forceBrowserInsertion;
+              aCreateLazyBrowser        = params.createLazyBrowser;
+              aSkipBackgroundNotify     = params.skipBackgroundNotify;
             }
 
             // 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");
 
@@ -2253,16 +2251,21 @@
             }
 
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
               ContextualIdentityService.setTabStyle(t);
             }
 
             t.setAttribute("onerror", "this.removeAttribute('image');");
+
+            if (aSkipBackgroundNotify) {
+              t.setAttribute("skipbackgroundnotify", true);
+            }
+
             t.className = "tabbrowser-tab";
 
             this.tabContainer._unlockTabSizing();
 
             // 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 &&
@@ -2332,19 +2335,18 @@
 
             t.linkedBrowser = b;
             this._tabForBrowser.set(b, t);
             t.permanentKey = b.permanentKey;
             t._browserParams = { uriIsAboutBlank,
                                  remoteType,
                                  usingPreloadedContent };
 
-            // If we're creating a blank tab, create a lazy browser.
-            // Otherwise insert the browser into the document now.
-            if (uriIsAboutBlank && !aForceBrowserInsertion) {
+            // If the caller opts in, create a lazy browser.
+            if (aCreateLazyBrowser) {
               this._createLazyBrowser(t);
             } else {
               this._insertBrowser(t);
             }
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
@@ -2552,57 +2554,79 @@
         []
       </field>
 
       <method name="removeTab">
         <parameter name="aTab"/>
         <parameter name="aParams"/>
         <body>
           <![CDATA[
+            // We collect Telemetry on how long it takes to close a tab.
+            // Sometimes, tabs animate closed, and sometimes they don't, and
+            // it doesn't really make sense to put timings for both cases in the
+            // same bucket.
+            //
+            // We decide later on whether or not we're actually going to animate
+            // the tab closed. In order to capture as much of the time we care
+            // about with these stopwatches, but also in order to avoid any
+            // unnecessary work (since calculating whether or not we're going
+            // to do the animation might involved a style flush), we start
+            // stopwatches for both the animating and non-animating case. When
+            // we decide that we're animating, we cancel the non-animating case,
+            // and vice-versa. Then, in _endRemoveTab, we "finish" both
+            // stopwatches, which is a no-op for cancelled stopwatches.
+            TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
+            TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
+
             if (aParams) {
               var animate = aParams.animate;
               var byMouse = aParams.byMouse;
               var skipPermitUnload = aParams.skipPermitUnload;
             }
 
-            // Ensure aTab's browser is inserted into the document.
-            this._insertBrowser(aTab);
-
             window.maybeRecordAbandonmentTelemetry(aTab, "tabClosed");
 
             // Handle requests for synchronously removing an already
             // asynchronously closing tab.
             if (!animate &&
                 aTab.closing) {
               this._endRemoveTab(aTab);
               return;
             }
 
             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
 
-            if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload))
+            if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload)) {
+              TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
+              TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
               return;
+            }
 
             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
               this.tabContainer._lockTabSizing(aTab);
             else
               this.tabContainer._unlockTabSizing();
 
             if (!animate /* the caller didn't opt in */ ||
                 isLastTab ||
                 aTab.pinned ||
                 aTab.hidden ||
                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
                 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
+              // We're not animating, so we can cancel the animation stopwatch.
+              TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
               this._endRemoveTab(aTab);
               return;
             }
 
+            // We're animating, so we can cancel the non-animation stopwatch.
+            TelemetryStopwatch.cancel("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
+
             this.tabContainer._handleTabTelemetryStart(aTab);
 
             this._blurTab(aTab);
             aTab.style.maxWidth = ""; // ensure that fade-out transition happens
             aTab.removeAttribute("fadein");
 
             setTimeout(function(tab, tabbrowser) {
               if (tab.parentNode &&
@@ -2630,29 +2654,35 @@
         <body>
           <![CDATA[
             if (aTab.closing ||
                 this._windowIsClosing)
               return false;
 
             var browser = this.getBrowserForTab(aTab);
 
-            if (!aTab._pendingPermitUnload && !aAdoptedByTab && !aSkipPermitUnload) {
+            if (!aTab._pendingPermitUnload &&
+                !aSkipPermitUnload &&
+                aTab.linkedPanel &&
+                !aAdoptedByTab) {
+              TelemetryStopwatch.start("FX_TAB_CLOSE_PERMIT_UNLOAD_TIME_MS", aTab);
+
               // We need to block while calling permitUnload() because it
               // processes the event queue and may lead to another removeTab()
               // call before permitUnload() returns.
               aTab._pendingPermitUnload = true;
               let {permitUnload, timedOut} = browser.permitUnload();
               delete aTab._pendingPermitUnload;
+
+              TelemetryStopwatch.finish("FX_TAB_CLOSE_PERMIT_UNLOAD_TIME_MS", aTab);
+
               // If we were closed during onbeforeunload, we return false now
               // so we don't (try to) close the same tab again. Of course, we
               // also stop if the unload was cancelled by the user:
               if (aTab.closing || (!timedOut && !permitUnload)) {
-                // NB: deliberately keep the _closedDuringPermitUnload set to
-                // true so we keep exiting early in case of multiple calls.
                 return false;
               }
             }
 
 
             var closeWindow = false;
             var newTab = false;
             if (this.tabs.length - this._removingTabs.length == 1) {
@@ -2696,31 +2726,33 @@
 
             // We're committed to closing the tab now.
             // Dispatch a notification.
             // We dispatch it before any teardown so that event listeners can
             // inspect the tab that's about to close.
             var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
             aTab.dispatchEvent(evt);
 
-            if (!aAdoptedByTab && !gMultiProcessBrowser) {
-              // Prevent this tab from showing further dialogs, since we're closing it
-              var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
-                                getInterface(Ci.nsIDOMWindowUtils);
-              windowUtils.disableDialogs();
-            }
-
-            // Remove the tab's filter and progress listener.
-            const filter = this._tabFilters.get(aTab);
-
-            browser.webProgress.removeProgressListener(filter);
-
-            const listener = this._tabListeners.get(aTab);
-            filter.removeProgressListener(listener);
-            listener.destroy();
+            if (aTab.linkedPanel) {
+              if (!aAdoptedByTab && !gMultiProcessBrowser) {
+                // Prevent this tab from showing further dialogs, since we're closing it
+                var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+                                  getInterface(Ci.nsIDOMWindowUtils);
+                windowUtils.disableDialogs();
+              }
+
+              // Remove the tab's filter and progress listener.
+              const filter = this._tabFilters.get(aTab);
+
+              browser.webProgress.removeProgressListener(filter);
+
+              const listener = this._tabListeners.get(aTab);
+              filter.removeProgressListener(listener);
+              listener.destroy();
+            }
 
             if (browser.registeredOpenURI && !aAdoptedByTab) {
               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
                                                        browser.getAttribute("usercontextid") || 0);
               delete browser.registeredOpenURI;
             }
 
             // We are no longer the primary content area.
@@ -2775,27 +2807,30 @@
               this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
             }
 
             // We're going to remove the tab and the browser now.
             this._tabFilters.delete(aTab);
             this._tabListeners.delete(aTab);
 
             var browser = this.getBrowserForTab(aTab);
-            this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
-
-            // Because of the way XBL works (fields just set JS
-            // properties on the element) and the code we have in place
-            // to preserve the JS objects for any elements that have
-            // JS properties set on them, the browser element won't be
-            // destroyed until the document goes away.  So we force a
-            // cleanup ourselves.
-            // This has to happen before we remove the child so that the
-            // XBL implementation of nsIObserver still works.
-            browser.destroy();
+
+            if (aTab.linkedPanel) {
+              this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
+
+              // Because of the way XBL works (fields just set JS
+              // properties on the element) and the code we have in place
+              // to preserve the JS objects for any elements that have
+              // JS properties set on them, the browser element won't be
+              // destroyed until the document goes away.  So we force a
+              // cleanup ourselves.
+              // This has to happen before we remove the child so that the
+              // XBL implementation of nsIObserver still works.
+              browser.destroy();
+            }
 
             var wasPinned = aTab.pinned;
 
             // Remove the tab ...
             this.tabContainer.removeChild(aTab);
 
             // ... and fix up the _tPos properties immediately.
             for (let i = aTab._tPos; i < this.tabs.length; i++)
@@ -2839,22 +2874,25 @@
             // a consistent state (tab removed, tab positions updated, etc.).
             browser.remove();
 
             // Release the browser in case something is erroneously holding a
             // reference to the tab after its removal.
             this._tabForBrowser.delete(aTab.linkedBrowser);
             aTab.linkedBrowser = null;
 
-            // As the browser is removed, the removal of a dependent document can
-            // cause the whole window to close. So at this point, it's possible
-            // that the binding is destructed.
-            if (this.mTabBox) {
-              this.mPanelContainer.removeChild(panel);
-            }
+            panel.remove();
+
+            // closeWindow might wait an arbitrary length of time if we're supposed
+            // to warn about closing the window, so we'll just stop the tab close
+            // stopwatches here instead.
+            TelemetryStopwatch.finish("FX_TAB_CLOSE_TIME_ANIM_MS", aTab,
+                                      true /* aCanceledOkay */);
+            TelemetryStopwatch.finish("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab,
+                                      true /* aCanceledOkay */);
 
             if (aCloseWindow)
               this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
           ]]>
         </body>
       </method>
 
       <method name="_blurTab">
@@ -3385,17 +3423,17 @@
         </body>
       </method>
 
       <!-- Opens a given tab to a non-remote window. -->
       <method name="openNonRemoteWindow">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
-            if (!this.AppConstants.E10S_TESTING_ONLY) {
+            if (!AppConstants.E10S_TESTING_ONLY) {
               throw "This method is intended only for e10s testing!";
             }
             let url = aTab.linkedBrowser.currentURI.spec;
             return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
           ]]>
         </body>
       </method>
 
@@ -3698,16 +3736,17 @@
             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.
 
@@ -3719,25 +3758,32 @@
 
             // We show this tab in case the requestedTab hasn't loaded yet.
             lastVisibleTab: this.selectedTab,
 
             // Auxilliary state variables:
 
             visibleTab: this.selectedTab,   // Tab that's on screen.
             spinnerTab: null,               // Tab showing a spinner.
+            blankTab: null,                 // Tab showing blank.
             originalTab: this.selectedTab,  // Tab that we started on.
 
             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(),
 
@@ -3823,18 +3869,27 @@
 
               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.setTabState(this.requestedTab, this.STATE_LOADED);
+                this.log("Initial tab is loaded?: " + tabIsLoaded);
+                this.setTabState(tab, tabIsLoaded ? this.STATE_LOADED
+                                                  : this.STATE_LOADING);
               }
             },
 
             destroy() {
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
                 this.unloadTimer = null;
               }
@@ -3845,31 +3900,33 @@
 
               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();
             },
 
             finish() {
               this.log("FINISH");
 
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
               this.assert(!this.spinnerTab);
+              this.assert(!this.blankTab);
               this.assert(!this.loadTimer);
               this.assert(!this.loadingTab);
               this.assert(this.lastVisibleTab === this.requestedTab);
               this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
@@ -3890,30 +3947,55 @@
                 cancelable: true
               });
               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;
+
               // Figure out which tab we actually want visible right now.
               let showTab = null;
-              if (this.getTabState(this.requestedTab) != this.STATE_LOADED &&
-                  this.lastVisibleTab && this.loadTimer) {
+              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;
               } else {
-                // Show the requested tab. If it's not available, we'll show the spinner.
+                // 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 = null;
+              } else if (shouldBeBlank && this.blankTab !== showTab) {
+                if (this.blankTab) {
+                  this.blankTab.linkedBrowser.removeAttribute("pendingtabchild");
+                }
+                this.blankTab = showTab;
+                this.tabbrowser.setAttribute("pendingtabchild", "true");
+                this.blankTab.linkedBrowser.setAttribute("pendingtabchild", "true");
+              }
+
               // Show or hide the spinner as needed.
-              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
+              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED &&
+                                !this.minimized &&
+                                !shouldBeBlank;
+
               if (!needSpinner && this.spinnerTab) {
                 this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
@@ -3953,17 +4035,17 @@
               this.lastVisibleTab = this.visibleTab;
             },
 
             assert(cond) {
               if (!cond) {
                 dump("Assertion failure\n" + Error().stack);
 
                 // Don't break a user's browser if an assertion fails.
-                if (this.tabbrowser.AppConstants.DEBUG) {
+                if (AppConstants.DEBUG) {
                   throw new Error("Assertion failure");
                 }
               }
             },
 
             // We've decided to try to load requestedTab.
             loadRequestedTab() {
               this.assert(!this.loadTimer);
@@ -3988,16 +4070,19 @@
                 if (!tab.linkedBrowser) {
                   this.tabState.delete(tab);
                 }
               }
 
               if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) {
                 this.lastVisibleTab = null;
               }
+              if (this.blankTab && !this.blankTab.linkedBrowser) {
+                this.blankTab = null;
+              }
               if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
                 this.spinnerHidden();
                 this.spinnerTab = null;
               }
               if (this.loadingTab && !this.loadingTab.linkedBrowser) {
                 this.loadingTab = null;
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
@@ -4100,18 +4185,19 @@
               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})`);
+              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();
 
               if (this.loadingTab === tab) {
@@ -4126,16 +4212,17 @@
             // 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);
               }
             },
@@ -4146,16 +4233,25 @@
             onRemotenessChange(tab) {
               this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
               if (!tab.linkedBrowser.isRemoteBrowser) {
                 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);
+                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) {
                 // The browser that was being presented to the user is
@@ -4192,29 +4288,41 @@
 
             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);
-                otherState = otherTabbrowser._switcher.getTabState(otherTab);
+                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, otherState);
+              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.
 
               if (this.loadTimer) {
@@ -4223,36 +4331,63 @@
                 // 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 otherState = this.swapMap.get(otherBrowser);
+              let { state: otherState, pendingTabChild } =
+                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();
@@ -4298,16 +4433,18 @@
               } 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
@@ -4324,36 +4461,59 @@
             /**
              * Something has occurred that might mean that we've completed
              * the tab switch (layers are ready, paints are done, spinners
              * are hidden). This checks to make sure all conditions are
              * satisfied, and then records the tab switch as finished.
              */
             maybeFinishTabSwitch() {
               if (this.switchInProgress && this.requestedTab &&
-                  this.getTabState(this.requestedTab) == this.STATE_LOADED) {
+                  (this.getTabState(this.requestedTab) == this.STATE_LOADED ||
+                   this.requestedTab === this.blankTab)) {
                 // After this point the tab has switched from the content thread's point of view.
                 // The changes will be visible after the next refresh driver tick + composite.
                 let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                 if (time != -1) {
                   TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
                   this.log("DEBUG: tab switch time = " + time);
                   this.addMarker("AsyncTabSwitch:Finish");
                 }
                 this.switchInProgress = false;
               }
             },
 
             spinnerDisplayed() {
               this.assert(!this.spinnerTab);
+              let browser = this.requestedTab.linkedBrowser;
+              this.assert(browser.isRemoteBrowser);
               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);
@@ -4375,21 +4535,17 @@
             _useDumpForLogging: false,
             _logInit: false,
 
             logging() {
               if (this._useDumpForLogging)
                 return true;
               if (this._logInit)
                 return this._shouldLog;
-              let result = false;
-              try {
-                result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
-              } catch (ex) {
-              }
+              let result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming", false);
               this._shouldLog = result;
               this._logInit = true;
               return this._shouldLog;
             },
 
             tinfo(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
@@ -4415,16 +4571,17 @@
               for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
                 let tab = this.tabbrowser.tabs[i];
                 let state = this.getTabState(tab);
 
                 accum += i + ":";
                 if (tab === this.lastVisibleTab) accum += "V";
                 if (tab === this.loadingTab) accum += "L";
                 if (tab === this.requestedTab) accum += "R";
+                if (tab === this.blankTab) accum += "B";
                 if (state == this.STATE_LOADED) accum += "(+)";
                 if (state == this.STATE_LOADING) accum += "(+?)";
                 if (state == this.STATE_UNLOADED) accum += "(-)";
                 if (state == this.STATE_UNLOADING) accum += "(-?)";
                 accum += " ";
               }
               if (this._useDumpForLogging) {
                 dump(accum + "\n");
@@ -4654,17 +4811,17 @@
                 return;
               case aEvent.DOM_VK_PAGE_DOWN:
                 this.moveTabForward();
                 aEvent.preventDefault();
                 return;
             }
           }
 
-          if (this.AppConstants.platform != "macosx") {
+          if (AppConstants.platform != "macosx") {
             if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
                 aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
                 !this.mCurrentTab.pinned) {
               this.removeCurrentTab({animate: true});
               aEvent.preventDefault();
             }
           }
         ]]></body>
@@ -4676,17 +4833,17 @@
           if (!aEvent.isTrusted) {
             // Don't let untrusted events mess with tabs.
             return;
           }
 
           if (aEvent.altKey)
             return;
 
-          if (this.AppConstants.platform == "macosx") {
+          if (AppConstants.platform == "macosx") {
             if (!aEvent.metaKey)
               return;
 
             var offset = 1;
             switch (aEvent.charCode) {
               case "}".charCodeAt(0):
                 offset = -1;
               case "{".charCodeAt(0):
@@ -4739,17 +4896,17 @@
                   "tabs.unmuteAudio.background.tooltip" :
                   "tabs.muteAudio.background.tooltip";
               }
 
               label = this.mStringBundle.getString(stringID);
             }
           } else {
             label = tab.getAttribute("label");
-            if (this.AppConstants.E10S_TESTING_ONLY &&
+            if (AppConstants.E10S_TESTING_ONLY &&
                 tab.linkedBrowser &&
                 tab.linkedBrowser.isRemoteBrowser) {
               label += " - e10s";
               if (Services.prefs.getIntPref("dom.ipc.processCount") > 1) {
                 label += " (" + tab.linkedBrowser.frameLoader.tabParent.osPid + ")";
               }
             }
             if (tab.userContextId) {
@@ -5022,17 +5179,17 @@
           Services.obs.addObserver(this, "contextual-identity-updated", false);
 
           this.mCurrentTab = this.tabContainer.firstChild;
           const nsIEventListenerService =
             Components.interfaces.nsIEventListenerService;
           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                               .getService(nsIEventListenerService);
           els.addSystemEventListener(document, "keydown", this, false);
-          if (this.AppConstants.platform == "macosx") {
+          if (AppConstants.platform == "macosx") {
             els.addSystemEventListener(document, "keypress", this, false);
           }
           window.addEventListener("sizemodechange", this);
 
           var uniqueId = this._generateUniquePanelID();
           this.mPanelContainer.childNodes[0].id = uniqueId;
           this.mCurrentTab.linkedPanel = uniqueId;
           this.mCurrentTab.permanentKey = this.mCurrentBrowser.permanentKey;
@@ -5139,17 +5296,17 @@
             this._tabFilters.delete(tab);
             this._tabListeners.delete(tab);
           }
           const nsIEventListenerService =
             Components.interfaces.nsIEventListenerService;
           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                               .getService(nsIEventListenerService);
           els.removeSystemEventListener(document, "keydown", this, false);
-          if (this.AppConstants.platform == "macosx") {
+          if (AppConstants.platform == "macosx") {
             els.removeSystemEventListener(document, "keypress", this, false);
           }
           window.removeEventListener("sizemodechange", this);
 
           if (gMultiProcessBrowser) {
             let messageManager = window.getGroupMessageManager("browsers");
             messageManager.removeMessageListener("DOMTitleChanged", this);
             window.messageManager.removeMessageListener("contextmenu", this);
@@ -5551,21 +5708,17 @@
 
           var tab = this.firstChild;
           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
 
           window.addEventListener("resize", this);
           window.addEventListener("load", this);
 
-          try {
-            this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
-          } catch (ex) {
-            this._tabAnimationLoggingEnabled = false;
-          }
+          this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled", false);
           this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
           Services.prefs.addObserver("privacy.userContext", this, false);
           this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
@@ -6159,20 +6312,20 @@
               return;
             }
 
             this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
                                                  selected.right - scrollRect.right :
                                                  selected.left - scrollRect.left);
           }
 
-          if (!this._animateElement.hasAttribute("notifybgtab")) {
-            this._animateElement.setAttribute("notifybgtab", "true");
+          if (!this._animateElement.hasAttribute("highlight")) {
+            this._animateElement.setAttribute("highlight", "true");
             setTimeout(function(ele) {
-              ele.removeAttribute("notifybgtab");
+              ele.removeAttribute("highlight");
             }, 150, this._animateElement);
           }
         ]]></body>
       </method>
 
       <method name="_getDragTargetTab">
         <parameter name="event"/>
         <parameter name="isLink"/>
@@ -6250,17 +6403,17 @@
             return;
           tab._fullyOpen = true;
 
           this.adjustTabstrip();
 
           if (tab.getAttribute("selected") == "true") {
             this._fillTrailingGap();
             this._handleTabSelect();
-          } else {
+          } else if (!tab.hasAttribute("skipbackgroundnotify")) {
             this._notifyBackgroundTab(tab);
           }
 
           // XXXmano: this is a temporary workaround for bug 345399
           // We need to manually update the scroll buttons disabled state
           // if a tab was inserted to the overflow area or removed from it
           // without any scrolling and when the tabbar has already
           // overflowed.
@@ -6503,17 +6656,17 @@
         event.stopPropagation();
       ]]></handler>
 
       <handler event="keydown" group="system"><![CDATA[
         if (event.altKey || event.shiftKey)
           return;
 
         let wrongModifiers;
-        if (this.tabbrowser.AppConstants.platform == "macosx") {
+        if (AppConstants.platform == "macosx") {
           wrongModifiers = !event.metaKey;
         } else {
           wrongModifiers = !event.ctrlKey || event.metaKey;
         }
 
         if (wrongModifiers)
           return;
 
@@ -6557,16 +6710,20 @@
         // We must not set text/x-moz-url or text/plain data here,
         // otherwise trying to deatch the tab by dropping it on the desktop
         // may result in an "internet shortcut"
         dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
 
         // Set the cursor to an arrow during tab drags.
         dt.mozCursor = "default";
 
+        // Set the tab as the source of the drag, which ensures we have a stable
+        // node to deliver the `dragend` event.  See bug 1345473.
+        dt.addElement(tab);
+
         // Create a canvas to which we capture the current tab.
         // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
         // canvas size (in CSS pixels) to the window's backing resolution in order
         // to get a full-resolution drag image for use on HiDPI displays.
         let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
         let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
         let canvas = this._dndCanvas;
         if (!canvas) {
@@ -6582,17 +6739,17 @@
         let toDrag = canvas;
         let dragImageOffset = -16;
         if (gMultiProcessBrowser) {
           var context = canvas.getContext("2d");
           context.fillStyle = "white";
           context.fillRect(0, 0, canvas.width, canvas.height);
 
           let captureListener;
-          let platform = this.tabbrowser.AppConstants.platform;
+          let platform = AppConstants.platform;
           // On Windows and Mac we can update the drag image during a drag
           // using updateDragImage. On Linux, we can use a panel.
           if (platform == "win" || platform == "macosx") {
             captureListener = function() {
               dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
             }
           } else {
             // Create a panel to use it in setDragImage
@@ -6867,17 +7024,17 @@
           // resize _before_ move to ensure the window fits the new screen.  if
           // the window is too large for its screen, the window manager may do
           // automatic repositioning.
           window.resizeTo(winWidth, winHeight);
           window.moveTo(left, top);
           window.focus();
         } else {
           let props = { screenX: left, screenY: top };
-          if (this.tabbrowser.AppConstants.platform != "win") {
+          if (AppConstants.platform != "win") {
             props.outerWidth = winWidth;
             props.outerHeight = winHeight;
           }
           this.tabbrowser.replaceTabWithWindow(draggedTab, props);
         }
         event.stopPropagation();
       ]]></handler>
 
@@ -6982,16 +7139,20 @@
       </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
@@ -7088,16 +7249,22 @@
         <parameter name="aDate"/>
         <body><![CDATA[
           this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
         ]]></body>
       </method>
 
       <field name="cachePosition">Infinity</field>
 
+      <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/alerts/.eslintrc.js
+++ b/browser/base/content/test/alerts/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/captivePortal/.eslintrc.js
+++ b/browser/base/content/test/captivePortal/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/browser.eslintrc.js"
+    "plugin:mozilla/browser-test"
   ]
 };
--- a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
@@ -1,10 +1,14 @@
 "use strict";
 
+// Bug 1318389 - This test does a lot of window and tab manipulation,
+//               causing it to take a long time on debug.
+requestLongerTimeout(2);
+
 add_task(setupPrefsAndRecentWindowBehavior);
 
 // Each of the test cases below is run twice: once for login-success and once
 // for login-abort (aSuccess set to true and false respectively).
 let testCasesForBothSuccessAndAbort = [
   /**
    * A portal is detected when there's no browser window, then a browser
    * window is opened, then the portal is freed.
--- a/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
+++ b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
@@ -23,17 +23,17 @@ add_task(function* checkCaptivePortalCer
 
   // Open a page with a cert error.
   let browser;
   let certErrorLoaded;
   let errorTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
     let tab = gBrowser.addTab(BAD_CERT_PAGE);
     gBrowser.selectedTab = tab;
     browser = gBrowser.selectedBrowser;
-    certErrorLoaded = waitForCertErrorLoad(browser);
+    certErrorLoaded = BrowserTestUtils.waitForContentEvent(browser, "DOMContentLoaded");
     return tab;
   }, false);
 
   info("Waiting for cert error page to load.")
   yield certErrorLoaded;
 
   let portalTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, CANONICAL_URL);
 
@@ -59,18 +59,18 @@ add_task(function* checkCaptivePortalCer
   yield ContentTask.spawn(browser, null, () => {
     info("Clicking the Open Login Page button.");
     content.document.getElementById("openPortalLoginPageButton").click();
   });
 
   let portalTab2 = yield portalTabPromise;
   is(portalTab2, portalTab, "The existing portal tab should be focused.");
 
-  let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true});
-  let errorTabReloaded = waitForCertErrorLoad(browser);
+  let portalTabRemoved = BrowserTestUtils.tabRemoved(portalTab);
+  let errorTabReloaded = BrowserTestUtils.waitForErrorPage(browser);
 
   Services.obs.notifyObservers(null, "captive-portal-login-success", null);
   yield portalTabRemoved;
 
   info("Waiting for error tab to be reloaded after the captive portal was freed.");
   yield errorTabReloaded;
   yield ContentTask.spawn(browser, null, () => {
     let doc = content.document;
--- a/browser/base/content/test/captivePortal/head.js
+++ b/browser/base/content/test/captivePortal/head.js
@@ -164,18 +164,8 @@ function* closeWindowAndWaitForXulWindow
  * opened window has received focus when the promise resolves, so we
  * have to manually wait every time.
  */
 function* openWindowAndWaitForFocus() {
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   yield SimpleTest.promiseFocus(win);
   return win;
 }
-
-function waitForCertErrorLoad(browser) {
-  return new Promise(resolve => {
-    info("Waiting for DOMContentLoaded event");
-    browser.addEventListener("DOMContentLoaded", function load() {
-      browser.removeEventListener("DOMContentLoaded", load, false, true);
-      resolve();
-    }, false, true);
-  });
-}
--- a/browser/base/content/test/chrome/.eslintrc.js
+++ b/browser/base/content/test/chrome/.eslintrc.js
@@ -1,7 +1,7 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "../../../../../testing/mochitest/chrome.eslintrc.js"
+    "plugin:mozilla/chrome-test"
   ]
 };
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/forms/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+  head.js
+
+[browser_selectpopup.js]
+skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
+[browser_selectpopup_colors.js]
+skip-if = os == "linux" # Bug 1329991 - test fails intermittently on Linux builds
rename from browser/base/content/test/general/browser_selectpopup.js
rename to browser/base/content/test/forms/browser_selectpopup.js
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/forms/browser_selectpopup.js
@@ -74,53 +74,16 @@ const PAGECONTENT_SOMEHIDDEN =
 const PAGECONTENT_TRANSLATED =
   "<html><body>" +
   "<div id='div'>" +
   "<iframe id='frame' width='320' height='295' style='border: none;'" +
   "        src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
   "</iframe>" +
   "</div></body></html>";
 
-const PAGECONTENT_COLORS =
-  "<html><head><style>" +
-  "  .blue { color: #fff; background-color: #00f; }" +
-  "  .green { color: #800080; background-color: green; }" +
-  "  .defaultColor { color: -moz-ComboboxText; }" +
-  "  .defaultBackground { background-color: -moz-Combobox; }" +
-  "</style>" +
-  "<body><select id='one'>" +
-  '  <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
-  '  <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
-  '  <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
-  '  <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
-  '  <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
-  '  <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
-  '  <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
-  "</select></body></html>";
-
-const PAGECONTENT_COLORS_ON_SELECT =
-  "<html><head><style>" +
-  "  #one { background-color: #7E3A3A; color: #fff }" +
-  "</style>" +
-  "<body><select id='one'>" +
-  '  <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
-  '  <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
-  '  <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
-  '  <option value="Four" selected="true">{"end": "true"}</option>' +
-  "</select></body></html>";
-
-const TRANSPARENT_SELECT =
-  "<html><head><style>" +
-  "  #one { background-color: transparent; }" +
-  "</style>" +
-  "<body><select id='one'>" +
-  '  <option value="One">{"unstyled": "true"}</option>' +
-  '  <option value="Two" selected="true">{"end": "true"}</option>' +
-  "</select></body></html>";
-
 function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (mode == "click" || mode == "mousedown") {
     let mousePromise;
     if (mode == "click") {
       mousePromise = BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser);
     } else {
@@ -129,34 +92,16 @@ function openSelectPopup(selectPopup, mo
 
     return Promise.all([popupShownPromise, mousePromise]);
   }
 
   EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }, win);
   return popupShownPromise;
 }
 
-function hideSelectPopup(selectPopup, mode = "enter", win = window) {
-  let browser = win.gBrowser.selectedBrowser;
-  let selectClosedPromise = ContentTask.spawn(browser, null, function*() {
-    Cu.import("resource://gre/modules/SelectContentHelper.jsm");
-    return ContentTaskUtils.waitForCondition(() => !SelectContentHelper.open);
-  });
-
-  if (mode == "escape") {
-    EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, win);
-  } else if (mode == "enter") {
-    EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" }, win);
-  } else if (mode == "click") {
-    EventUtils.synthesizeMouseAtCenter(selectPopup.lastChild, { }, win);
-  }
-
-  return selectClosedPromise;
-}
-
 function getInputEvents() {
   return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     return content.wrappedJSObject.gInputEvents;
   });
 }
 
 function getChangeEvents() {
   return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
@@ -165,54 +110,16 @@ function getChangeEvents() {
 }
 
 function getClickEvents() {
   return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     return content.wrappedJSObject.gClickEvents;
   });
 }
 
-function getSystemColor(color) {
-  // Need to convert system color to RGB color.
-  let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
-  textarea.style.color = color;
-  return getComputedStyle(textarea).color;
-}
-
-function testOptionColors(index, item, menulist) {
-  // The label contains a JSON string of the expected colors for
-  // `color` and `background-color`.
-  let expected = JSON.parse(item.label);
-
-  for (let color of Object.keys(expected)) {
-    if (color.toLowerCase().includes("color") &&
-        !expected[color].startsWith("rgb")) {
-      expected[color] = getSystemColor(expected[color]);
-    }
-  }
-
-  // Press Down to move the selected item to the next item in the
-  // list and check the colors of this item when it's not selected.
-  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
-
-  if (expected.end) {
-    return;
-  }
-
-  if (expected.unstyled) {
-    ok(!item.hasAttribute("customoptionstyling"),
-      `Item ${index} should not have any custom option styling`);
-  } else {
-    is(getComputedStyle(item).color, expected.color,
-       "Item " + (index) + " has correct foreground color");
-    is(getComputedStyle(item).backgroundColor, expected.backgroundColor,
-       "Item " + (index) + " has correct background color");
-  }
-}
-
 function* doSelectTests(contentType, dtd) {
   const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
   let menulist = document.getElementById("ContentSelectDropdown");
   let selectPopup = menulist.menupopup;
 
   yield openSelectPopup(selectPopup);
@@ -580,22 +487,21 @@ function* performLargePopupTests(win) {
 
   scrollPos = selectPopup.scrollBox.scrollTop;
   EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 25, { type: "mouseup" }, win);
   is(selectPopup.scrollBox.scrollTop, scrollPos, "scroll position at mouseup from option should not change");
 
   EventUtils.synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }, win);
   is(selectPopup.scrollBox.scrollTop, scrollPos, "scroll position at mousemove after mouseup should not change");
 
-
   yield hideSelectPopup(selectPopup, "escape", win);
 
   let positions = [
     "margin-top: 300px;",
-    "position: fixed; bottom: 100px;",
+    "position: fixed; bottom: 200px;",
     "width: 100%; height: 9999px;"
   ];
 
   let position;
   while (positions.length) {
     yield openSelectPopup(selectPopup, "key", "select", win);
 
     let rect = selectPopup.getBoundingClientRect();
@@ -630,18 +536,63 @@ function* performLargePopupTests(win) {
     let contentPainted = BrowserTestUtils.contentPainted(browser);
     yield ContentTask.spawn(browser, position, function*(contentPosition) {
       let select = content.document.getElementById("one");
       select.setAttribute("style", contentPosition || "");
       select.getBoundingClientRect();
     });
     yield contentPainted;
   }
+
+  if (navigator.platform.indexOf("Mac") == 0) {
+    yield ContentTask.spawn(browser, null, function*() {
+      let doc = content.document;
+      doc.body.style = "padding-top: 400px;"
+
+      let select = doc.getElementById("one");
+      select.options[41].selected = true;
+      select.focus();
+    });
+
+    yield openSelectPopup(selectPopup, "key", "select", win);
+
+    ok(selectPopup.getBoundingClientRect().top > browser.getBoundingClientRect().top,
+       "select popup appears over selected item");
+
+    yield hideSelectPopup(selectPopup, "escape", win);
+  }
 }
 
+// This test checks select elements with a large number of options to ensure that
+// the popup appears within the browser area.
+add_task(function* test_large_popup() {
+  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  yield* performLargePopupTests(window);
+
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks the same as the previous test but in a new smaller window.
+add_task(function* test_large_popup_in_small_window() {
+  let newwin = yield BrowserTestUtils.openNewBrowserWindow({ width: 400, height: 400 });
+
+  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+  let browserLoadedPromise = BrowserTestUtils.browserLoaded(newwin.gBrowser.selectedBrowser);
+  yield BrowserTestUtils.loadURI(newwin.gBrowser.selectedBrowser, pageUrl);
+  yield browserLoadedPromise;
+
+  newwin.gBrowser.selectedBrowser.focus();
+
+  yield* performLargePopupTests(newwin);
+
+  yield BrowserTestUtils.closeWindow(newwin);
+});
+
 function* performSelectSearchTests(win) {
   let browser = win.gBrowser.selectedBrowser;
   yield ContentTask.spawn(browser, null, function*() {
     let doc = content.document;
     let select = doc.getElementById("one");
 
     for (var i = 0; i < 40; i++) {
       select.add(new content.Option("Test" + i));
@@ -704,43 +655,16 @@ add_task(function* test_select_search() 
 
   yield performSelectSearchTests(window);
 
   yield BrowserTestUtils.removeTab(tab);
 
   yield SpecialPowers.popPrefEnv();
 });
 
-// This test checks select elements with a large number of options to ensure that
-// the popup appears within the browser area.
-add_task(function* test_large_popup() {
-  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
-
-  yield* performLargePopupTests(window);
-
-  yield BrowserTestUtils.removeTab(tab);
-});
-
-// This test checks the same as the previous test but in a new smaller window.
-add_task(function* test_large_popup_in_small_window() {
-  let newwin = yield BrowserTestUtils.openNewBrowserWindow({ width: 400, height: 400 });
-
-  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
-  let browserLoadedPromise = BrowserTestUtils.browserLoaded(newwin.gBrowser.selectedBrowser);
-  yield BrowserTestUtils.loadURI(newwin.gBrowser.selectedBrowser, pageUrl);
-  yield browserLoadedPromise;
-
-  newwin.gBrowser.selectedBrowser.focus();
-
-  yield* performLargePopupTests(newwin);
-
-  yield BrowserTestUtils.closeWindow(newwin);
-});
-
 // This test checks that a mousemove event is fired correctly at the menu and
 // not at the browser, ensuring that any mouse capture has been cleared.
 add_task(function* test_mousemove_correcttarget() {
   const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
   let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
 
@@ -799,111 +723,16 @@ add_task(function* test_somehidden() {
        "Item " + (idx++) + " is visible");
     child = child.nextSibling;
   }
 
   yield hideSelectPopup(selectPopup, "escape");
   yield BrowserTestUtils.removeTab(tab);
 });
 
-// This test checks when a <select> element has styles applied to <option>s within it.
-add_task(function* test_colors_applied_to_popup_items() {
-  const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
-
-  let menulist = document.getElementById("ContentSelectDropdown");
-  let selectPopup = menulist.menupopup;
-
-  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
-  // The label contains a JSON string of the expected colors for
-  // `color` and `background-color`.
-  is(selectPopup.parentNode.itemCount, 7, "Correct number of items");
-  let child = selectPopup.firstChild;
-  let idx = 1;
-
-  ok(!child.selected, "The first child should not be selected");
-  while (child) {
-    testOptionColors(idx, child, menulist);
-    idx++;
-    child = child.nextSibling;
-  }
-
-  yield hideSelectPopup(selectPopup, "escape");
-  yield BrowserTestUtils.removeTab(tab);
-});
-
-// This test checks when a <select> element has styles applied to itself.
-add_task(function* test_colors_applied_to_popup() {
-  const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS_ON_SELECT);
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
-
-  let menulist = document.getElementById("ContentSelectDropdown");
-  let selectPopup = menulist.menupopup;
-
-  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
-  is(selectPopup.parentNode.itemCount, 4, "Correct number of items");
-  let child = selectPopup.firstChild;
-  let idx = 1;
-
-  is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
-    "popup has expected foreground color");
-  is(getComputedStyle(selectPopup).backgroundColor, "rgb(126, 58, 58)",
-    "popup has expected background color");
-
-  ok(!child.selected, "The first child should not be selected");
-  while (child) {
-    testOptionColors(idx, child, menulist);
-    idx++;
-    child = child.nextSibling;
-  }
-
-  yield hideSelectPopup(selectPopup, "escape");
-
-  yield BrowserTestUtils.removeTab(tab);
-});
-
-// This test checks when a <select> element has a transparent background applied to itself.
-add_task(function* test_transparent_applied_to_popup() {
-  const pageUrl = "data:text/html," + escape(TRANSPARENT_SELECT);
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
-
-  let menulist = document.getElementById("ContentSelectDropdown");
-  let selectPopup = menulist.menupopup;
-
-  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
-  is(selectPopup.parentNode.itemCount, 2, "Correct number of items");
-  let child = selectPopup.firstChild;
-  let idx = 1;
-
-  is(getComputedStyle(selectPopup).color, getSystemColor("-moz-ComboboxText"),
-    "popup has expected foreground color");
-  is(getComputedStyle(selectPopup).backgroundColor, getSystemColor("-moz-Combobox"),
-    "popup has expected background color");
-
-  ok(!child.selected, "The first child should not be selected");
-  while (child) {
-    testOptionColors(idx, child, menulist);
-    idx++;
-    child = child.nextSibling;
-  }
-
-  yield hideSelectPopup(selectPopup, "escape");
-
-  yield BrowserTestUtils.removeTab(tab);
-});
-
 // This test checks that the popup is closed when the select element is blurred.
 add_task(function* test_blur_hides_popup() {
   const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
 
   yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
     content.addEventListener("blur", function(event) {
       event.preventDefault();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/forms/browser_selectpopup_colors.js
@@ -0,0 +1,299 @@
+const PAGECONTENT_COLORS =
+  "<html><head><style>" +
+  "  .blue { color: #fff; background-color: #00f; }" +
+  "  .green { color: #800080; background-color: green; }" +
+  "  .defaultColor { color: -moz-ComboboxText; }" +
+  "  .defaultBackground { background-color: -moz-Combobox; }" +
+  "</style>" +
+  "<body><select id='one'>" +
+  '  <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
+  '  <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
+  '  <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
+  '  <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
+  '  <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
+  '  <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
+  '  <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
+  "</select></body></html>";
+
+const PAGECONTENT_COLORS_ON_SELECT =
+  "<html><head><style>" +
+  "  #one { background-color: #7E3A3A; color: #fff }" +
+  "</style>" +
+  "<body><select id='one'>" +
+  '  <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Four" selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
+const TRANSPARENT_SELECT =
+  "<html><head><style>" +
+  "  #one { background-co