Merge draft head draft
authorGregory Szorc <gregory.szorc@gmail.com>
Thu, 25 Jan 2018 00:57:54 -0800
changeset 745349 3b1673261a0bba385fab88cb8f8251c06d4d3de4
parent 745348 8ddd46976800499dd2e0f42a19225d1256f34f12 (diff)
parent 717800 45f03264527acc9fe70a49931c08a76e97ee778a (current diff)
child 745350 968c2482d17bc471f3a6f4b75a893114334e761b
push id96834
push usergszorc@mozilla.com
push dateThu, 25 Jan 2018 17:14:24 +0000
milestone60.0a1
Merge draft head
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -2,16 +2,17 @@
 build/clang-plugin/.*
 # The two templates cannot be formatted
 config/gcc-stl-wrapper.template.h
 config/msvc-stl-wrapper.template.h
 dom/base/test/.*
 dom/bindings/test/.*
 dom/media/gtest/.*
 gfx/testsd/.*
+.*/gtest/ExampleStylesheet.h
 image/test/.*
 ipc/ipdl/test/.*
 ipc/testshell/.*
 js/src/jsapi-tests/.*
 # See bug 1395584
 js/src/vm/Opcodes.h
 # Ignored because of bug 1342657
 layout/style/nsCSSPropAliasList.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -72,16 +72,20 @@ browser/branding/**/firefox-branding.js
 # Gzipped test file.
 browser/base/content/test/general/gZipOfflineChild.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 # New tab is likely to be replaced soon.
 browser/base/content/newtab/**
 # 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
+# This file is split into two in order to keep it as a valid json file
+# for documentation purposes (policies.json) but to be accessed by the
+# code as a .jsm (schema.jsm)
+browser/components/enterprisepolicies/schemas/schema.jsm
 # generated & special files in cld2
 browser/components/translation/cld2/**
 # Screenshots and Follow-on search are imported as a system add-on and have
 # their own lint rules currently.
 browser/extensions/followonsearch/**
 browser/extensions/screenshots/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -27,19 +27,17 @@ module.exports = {
   }, {
     // XXX Bug 1421969. These files/directories are still being fixed,
     // so turn off mozilla/use-services for them for now.
     "files": [
       // Browser: Bug 1421379
       "browser/extensions/shield-recipe-client/test/browser/head.js",
       "browser/modules/offlineAppCache.jsm",
       "devtools/**",
-      "dom/indexedDB/**",
       "extensions/pref/**",
       "mobile/android/**",
       "testing/**",
-      "tools/profiler/**",
     ],
     "rules": {
       "mozilla/use-services": "off",
     }
   }]
 };
--- a/.gitignore
+++ b/.gitignore
@@ -99,16 +99,20 @@ testing/web-platform/products/
 # Android Gradle artifacts.
 mobile/android/gradle/.gradle
 
 # XCode project cruft
 /*.xcodeproj/
 embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/xcuserdata
 embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/xcuserdata
 
+# Rust port of mozbase are libraries
+testing/mozbase/rust/*/target
+testing/mozbase/rust/*/Cargo.lock
+
 # Ignore mozharness execution files
 testing/mozharness/.tox/
 testing/mozharness/build/
 testing/mozharness/logs/
 testing/mozharness/.coverage
 testing/mozharness/nosetests.xml
 
 # Ignore ESLint node_modules
--- a/.hgignore
+++ b/.hgignore
@@ -106,16 +106,20 @@ GPATH
 # Android Gradle artifacts.
 ^mobile/android/gradle/.gradle
 
 # XCode project cruft
 ^[^/]*\.xcodeproj/
 ^embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/xcuserdata
 ^embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/xcuserdata
 
+# Rust port of mozbase are Rust libraries
+^testing/mozbase/rust/.*/target
+^testing/mozbase/rust/.*/Cargo.lock
+
 # Ignore mozharness execution files
 ^testing/mozharness/.tox/
 ^testing/mozharness/build/
 ^testing/mozharness/logs/
 ^testing/mozharness/.coverage
 ^testing/mozharness/nosetests.xml
 
 # Ignore tox generated dir
@@ -140,20 +144,20 @@ GPATH
 ^testing/talos/talos/tests/tp5n
 ^testing/talos/talos/tests/devtools/damp.manifest.develop
 ^talos-venv
 ^py3venv
 ^testing/talos/talos/mitmproxy/mitmdump
 ^testing/talos/talos/mitmproxy/mitmproxy
 ^testing/talos/talos/mitmproxy/mitmweb
 
-# Ignore talos speedometer files; source is copied from in-tree /third_party
-# into testing/talos/talos/tests/webkit/PerformanceTests/Speedometer when
-# talos speedometer test is run locally
-^testing/talos/talos/tests/webkit/PerformanceTests/Speedometer
+# Ignore talos webkit benchmark files; source is copied from in-tree /third_party
+# into testing/talos/talos/tests/webkit/PerformanceTests/ when run locally
+# i.e. speedometer, motionmark, stylebench
+^testing/talos/talos/tests/webkit/PerformanceTests
 
 # Ignore toolchains.json created by tooltool.
 ^toolchains\.json
 
 # Ignore files created when running a reftest.
 ^lextab.py$
 
 # tup database
--- a/.hgtags
+++ b/.hgtags
@@ -133,8 +133,9 @@ 1196bf3032e1bce1fb07a01fd9082a767426c5fb
 f80dc9fc34680105b714a49b4704bb843f5f7004 FIREFOX_AURORA_53_BASE
 6583496f169cd8a13c531ed16e98e8bf313eda8e FIREFOX_AURORA_54_BASE
 f9605772a0c9098ed1bcaa98089b2c944ed69e9b FIREFOX_BETA_55_BASE
 320642944e42a889db13c6c55b404e32319d4de6 FIREFOX_BETA_56_BASE
 8e818b5e9b6bef0fc1a5c527ecf30b0d56a02f14 FIREFOX_BETA_57_BASE
 f7e9777221a34f9f23c2e4933307eb38b621b679 FIREFOX_NIGHTLY_57_END
 40a14ca1cf04499f398e4cb8ba359b39eae4e216 FIREFOX_BETA_58_BASE
 1f91961bb79ad06fd4caef9e5dfd546afd5bf42c FIREFOX_NIGHTLY_58_END
+5faab9e619901b1513fd4ca137747231be550def FIREFOX_NIGHTLY_59_END
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -93,17 +93,17 @@ tasks:
               GECKO_HEAD_REPOSITORY: '${repoUrl}'
               GECKO_HEAD_REF: '${push.revision}'
               GECKO_HEAD_REV: '${push.revision}'
               GECKO_COMMIT_MSG: {$if: 'tasks_for != "action"', then: '${push.comment}'}
               HG_STORE_PATH: /builds/worker/checkouts/hg-store
               TASKCLUSTER_CACHES: /builds/worker/checkouts
             - $if: 'tasks_for == "action"'
               then:
-                ACTION_TASK_GROUP_ID: '${action.taskGroupId}'
+                ACTION_TASK_GROUP_ID: '${ownTaskId}'
                 ACTION_TASK_ID: {$json: {$eval: 'taskId'}}
                 ACTION_TASK: {$json: {$eval: 'task'}}
                 ACTION_INPUT: {$json: {$eval: 'input'}}
                 ACTION_CALLBACK: '${action.cb_name}'
                 ACTION_PARAMETERS: {$json: {$eval: 'parameters'}}
 
         cache:
           level-${repository.level}-checkouts-sparse-v1: /builds/worker/checkouts
@@ -180,9 +180,12 @@ tasks:
               parent: '${action.taskGroupId}'
               action:
                 name: '${action.name}'
                 context:
                   taskGroupId: '${action.taskGroupId}'
                   taskId: {$eval: 'taskId'}
                   input: {$eval: 'input'}
                   parameters: {$eval: 'parameters'}
+          - $if: 'tasks_for == "cron"'
+            then:
+              cron: {$json: {$eval: 'cron'}}
           - tasks_for: '${tasks_for}'
--- 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.
 
-Bug 1416465 - Old track files may cause problems if they have wildcards.
+Merge day clobber
--- a/accessible/atk/Platform.cpp
+++ b/accessible/atk/Platform.cpp
@@ -14,17 +14,17 @@
 #include "prenv.h"
 #include "prlink.h"
 
 #ifdef MOZ_ENABLE_DBUS
 #include <dbus/dbus.h>
 #endif
 #include <gtk/gtk.h>
 
-#if (MOZ_WIDGET_GTK == 3)
+#ifdef MOZ_WIDGET_GTK
 extern "C" __attribute__((weak,visibility("default"))) int atk_bridge_adaptor_init(int*, char **[]);
 #endif
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 int atkMajorVersion = 1, atkMinorVersion = 12, atkMicroVersion = 0;
 
@@ -64,24 +64,16 @@ static GnomeAccessibilityModule sAtkBrid
     "libatk-bridge.a(libatk-bridge.so.0)", nullptr,
 #else
     "libatk-bridge.so", nullptr,
 #endif
     "gnome_accessibility_module_init", nullptr,
     "gnome_accessibility_module_shutdown", nullptr
 };
 
-#if (MOZ_WIDGET_GTK == 2)
-static GnomeAccessibilityModule sGail = {
-    "libgail.so", nullptr,
-    "gnome_accessibility_module_init", nullptr,
-    "gnome_accessibility_module_shutdown", nullptr
-};
-#endif
-
 static nsresult
 LoadGtkModule(GnomeAccessibilityModule& aModule)
 {
     NS_ENSURE_ARG(aModule.libName);
 
     if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) {
         //try to load the module with "gtk-2.0/modules" appended
         char *curLibPath = PR_GetLibraryPath();
@@ -97,21 +89,17 @@ LoadGtkModule(GnomeAccessibilityModule& 
         int16_t subLen = 0;
         while (loc2 >= 0) {
             loc2 = libPath.FindChar(':', loc1);
             if (loc2 < 0)
                 subLen = libPath.Length() - loc1;
             else
                 subLen = loc2 - loc1;
             nsAutoCString sub(Substring(libPath, loc1, subLen));
-#if (MOZ_WIDGET_GTK == 2)
-            sub.AppendLiteral("/gtk-2.0/modules/");
-#else
             sub.AppendLiteral("/gtk-3.0/modules/");
-#endif
             sub.Append(aModule.libName);
             aModule.lib = PR_LoadLibrary(sub.get());
             if (aModule.lib)
                 break;
 
             loc1 = loc2+1;
         }
         if (!aModule.lib)
@@ -171,29 +159,22 @@ a11y::PlatformInit()
       if (atkMajorVersion != 0L) {
         atkMinorVersion = strtol(endPtr + 1, &endPtr, 10);
         if (atkMinorVersion != 0L)
           atkMicroVersion = strtol(endPtr + 1, &endPtr, 10);
       }
     }
   }
 
-#if (MOZ_WIDGET_GTK == 2)
-  // Load and initialize gail library.
-  nsresult rv = LoadGtkModule(sGail);
-  if (NS_SUCCEEDED(rv))
-    (*sGail.init)();
-#endif
-
   // Initialize the MAI Utility class, it will overwrite gail_util.
   g_type_class_unref(g_type_class_ref(mai_util_get_type()));
 
   // Init atk-bridge now
   PR_SetEnv("NO_AT_BRIDGE=0");
-#if (MOZ_WIDGET_GTK == 3)
+#ifdef MOZ_WIDGET_GTK
   if (atk_bridge_adaptor_init) {
     atk_bridge_adaptor_init(nullptr, nullptr);
   } else
 #endif
   {
     nsresult rv = LoadGtkModule(sAtkBridge);
     if (NS_SUCCEEDED(rv)) {
       (*sAtkBridge.init)();
@@ -231,29 +212,16 @@ a11y::PlatformShutdown()
         // an exit function registered will take care of it
         // if (sAtkBridge.shutdown)
         //     (*sAtkBridge.shutdown)();
         // PR_UnloadLibrary(sAtkBridge.lib);
         sAtkBridge.lib = nullptr;
         sAtkBridge.init = nullptr;
         sAtkBridge.shutdown = nullptr;
     }
-#if (MOZ_WIDGET_GTK == 2)
-    if (sGail.lib) {
-        // Do not shutdown gail because
-        // 1) Maybe it's not init-ed by us. e.g. GtkEmbed
-        // 2) We need it to avoid assert in spi_atk_tidy_windows
-        // if (sGail.shutdown)
-        //   (*sGail.shutdown)();
-        // PR_UnloadLibrary(sGail.lib);
-        sGail.lib = nullptr;
-        sGail.init = nullptr;
-        sGail.shutdown = nullptr;
-    }
-#endif
     // if (sATKLib) {
     //     PR_UnloadLibrary(sATKLib);
     //     sATKLib = nullptr;
     // }
 }
 
   static const char sAccEnv [] = "GNOME_ACCESSIBILITY";
 #ifdef MOZ_ENABLE_DBUS
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -612,16 +612,47 @@ static const nsRoleMapEntry sWAIRoleMaps
     roles::FORM,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     eLandmark,
     kNoReqStates
   },
+  { // graphics-document
+    &nsGkAtoms::graphicsDocument,
+    roles::DOCUMENT,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    kGenericAccType,
+    kNoReqStates,
+    eReadonlyUntilEditable
+  },
+  { // graphics-object
+    &nsGkAtoms::graphicsObject,
+    roles::GROUPING,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    kGenericAccType,
+    kNoReqStates
+  },
+  { // graphics-symbol
+    &nsGkAtoms::graphicsSymbol,
+    roles::GRAPHIC,
+    kUseMapRole,
+    eNoValue,
+    eNoAction,
+    eNoLiveAttr,
+    kGenericAccType,
+    kNoReqStates
+  },
   { // grid
     &nsGkAtoms::grid,
     roles::TABLE,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     eSelect | eTable,
--- a/accessible/base/Logging.cpp
+++ b/accessible/base/Logging.cpp
@@ -829,17 +829,17 @@ logging::Node(const char* aDescr, nsINod
   }
 
   if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
     printf("%s: %p, document\n", aDescr, static_cast<void*>(aNode));
     return;
   }
 
   nsINode* parentNode = aNode->GetParentNode();
-  int32_t idxInParent = parentNode ? parentNode->IndexOf(aNode) : - 1;
+  int32_t idxInParent = parentNode ? parentNode->ComputeIndexOf(aNode) : - 1;
 
   if (aNode->IsNodeOfType(nsINode::eTEXT)) {
     printf("%s: %p, text node, idx in parent: %d\n",
            aDescr, static_cast<void*>(aNode), idxInParent);
     return;
   }
 
   if (!aNode->IsElement()) {
--- a/accessible/base/nsAccUtils.cpp
+++ b/accessible/base/nsAccUtils.cpp
@@ -363,17 +363,17 @@ nsAccUtils::GetScreenCoordsForParent(Acc
   if (!parent)
     return nsIntPoint(0, 0);
 
   nsIFrame *parentFrame = parent->GetFrame();
   if (!parentFrame)
     return nsIntPoint(0, 0);
 
   nsRect rect = parentFrame->GetScreenRectInAppUnits();
-  return nsPoint(rect.x, rect.y).
+  return nsPoint(rect.X(), rect.Y()).
     ToNearestPixels(parentFrame->PresContext()->AppUnitsPerDevPixel());
 }
 
 bool
 nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue)
 {
   switch (aRule) {
     case eOffLiveAttr:
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -477,17 +477,17 @@ class PluginTimerCallBack final : public
 {
   ~PluginTimerCallBack() {}
 
 public:
   explicit PluginTimerCallBack(nsIContent* aContent) : mContent(aContent) {}
 
   NS_DECL_ISUPPORTS
 
-  NS_IMETHOD Notify(nsITimer* aTimer) final
+  NS_IMETHOD Notify(nsITimer* aTimer) final override
   {
     if (!mContent->IsInUncomposedDoc())
       return NS_OK;
 
     nsIPresShell* ps = mContent->OwnerDoc()->GetShell();
     if (ps) {
       DocAccessible* doc = ps->GetDocAccessible();
       if (doc) {
@@ -501,17 +501,17 @@ public:
 
     // We couldn't get a doc accessible so presumably the document went away.
     // In this case don't leak our ref to the content or timer.
     sPendingPlugins->RemoveElement(mContent);
     sPluginTimers->RemoveElement(aTimer);
     return NS_OK;
   }
 
-  NS_IMETHOD GetName(nsACString& aName) final
+  NS_IMETHOD GetName(nsACString& aName) final override
   {
     aName.AssignLiteral("PluginTimerCallBack");
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIContent> mContent;
 };
@@ -1337,19 +1337,16 @@ nsAccessibilityService::Init()
 
 #if defined(XP_WIN)
   // This information needs to be initialized before the observer fires.
   if (XRE_IsParentProcess()) {
     Compatibility::Init();
   }
 #endif // defined(XP_WIN)
 
-  static const char16_t kInitIndicator[] = { '1', 0 };
-  observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
-
   // Subscribe to EventListenerService.
   nsCOMPtr<nsIEventListenerService> eventListenerService =
     do_GetService("@mozilla.org/eventlistenerservice;1");
   if (!eventListenerService)
     return false;
 
   eventListenerService->AddListenerChangeListener(this);
 
@@ -1401,16 +1398,19 @@ nsAccessibilityService::Init()
 #endif
 
   // Now its safe to start platform accessibility.
   if (XRE_IsParentProcess())
     PlatformInit();
 
   statistics::A11yInitialized();
 
+  static const char16_t kInitIndicator[] = { '1', 0 };
+  observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
+
   return true;
 }
 
 void
 nsAccessibilityService::Shutdown()
 {
   // Application is going to be closed, shutdown accessibility and mark
   // accessibility service as shutdown to prevent calls of its methods.
@@ -1420,19 +1420,16 @@ nsAccessibilityService::Shutdown()
   MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
   UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
 
   // Remove observers.
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
-
-    static const char16_t kShutdownIndicator[] = { '0', 0 };
-    observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
   }
 
   // Stop accessible document loader.
   DocManager::Shutdown();
 
   SelectionManager::Shutdown();
 
 #ifdef XP_WIN
@@ -1452,16 +1449,21 @@ nsAccessibilityService::Shutdown()
   NS_RELEASE(gApplicationAccessible);
   gApplicationAccessible = nullptr;
 
   NS_IF_RELEASE(gXPCApplicationAccessible);
   gXPCApplicationAccessible = nullptr;
 
   NS_RELEASE(gAccessibilityService);
   gAccessibilityService = nullptr;
+
+  if (observerService) {
+    static const char16_t kShutdownIndicator[] = { '0', 0 };
+    observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
+  }
 }
 
 already_AddRefed<Accessible>
 nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
                                                DocAccessible* aDoc)
 {
   nsAutoString role;
   nsCoreUtils::XBLBindingRole(aContent, role);
--- a/accessible/base/nsAccessiblePivot.cpp
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -581,18 +581,17 @@ nsAccessiblePivot::MoveToPoint(nsIAccess
     if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE)
       match = nullptr;
 
     // Match if no node below this is a match
     if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) {
       nsIntRect childRect = child->Bounds();
       // Double-check child's bounds since the deepest child may have been out
       // of bounds. This assures we don't return a false positive.
-      if (aX >= childRect.x && aX < childRect.x + childRect.width &&
-          aY >= childRect.y && aY < childRect.y + childRect.height)
+      if (childRect.Contains(aX, aY))
         match = child;
     }
 
     child = child->Parent();
   }
 
   if (match || !aIgnoreNoMatch)
     *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT,
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -297,17 +297,17 @@ nsCoreUtils::ScrollFrameToPoint(nsIFrame
 {
   nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollableFrame);
   if (!scrollableFrame)
     return;
 
   nsPoint point =
     ToAppUnits(aPoint, aFrame->PresContext()->AppUnitsPerDevPixel());
   nsRect frameRect = aFrame->GetScreenRectInAppUnits();
-  nsPoint deltaPoint(point.x - frameRect.x, point.y - frameRect.y);
+  nsPoint deltaPoint = point - frameRect.TopLeft();
 
   nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
   scrollPoint -= deltaPoint;
 
   scrollableFrame->ScrollTo(scrollPoint, nsIScrollableFrame::INSTANT);
 }
 
 void
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -509,18 +509,17 @@ Accessible::FocusedChild()
 Accessible*
 Accessible::ChildAtPoint(int32_t aX, int32_t aY,
                          EWhichChildAtPoint aWhichChild)
 {
   // If we can't find the point in a child, we will return the fallback answer:
   // we return |this| if the point is within it, otherwise nullptr.
   Accessible* fallbackAnswer = nullptr;
   nsIntRect rect = Bounds();
-  if (aX >= rect.x && aX < rect.x + rect.width &&
-      aY >= rect.y && aY < rect.y + rect.height)
+  if (rect.Contains(aX, aY))
     fallbackAnswer = this;
 
   if (nsAccUtils::MustPrune(this))  // Do not dig any further
     return fallbackAnswer;
 
   // Search an accessible at the given point starting from accessible document
   // because containing block (see CSS2) for out of flow element (for example,
   // absolutely positioned element) may be different from its DOM parent and
@@ -538,17 +537,17 @@ Accessible::ChildAtPoint(int32_t aX, int
   // Check whether the point is at popup content.
   nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
   NS_ENSURE_TRUE(rootWidget, nullptr);
 
   LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
 
   WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
                               WidgetMouseEvent::eSynthesized);
-  dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y);
+  dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
 
   nsIFrame* popupFrame = nsLayoutUtils::
     GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(),
                                      &dummyEvent);
   if (popupFrame) {
     // If 'this' accessible is not inside the popup then ignore the popup when
     // searching an accessible at point.
     DocAccessible* popupDoc =
@@ -560,18 +559,18 @@ Accessible::ChildAtPoint(int32_t aX, int
       popupChild = popupChild->Parent();
 
     if (popupChild == popupAcc)
       startFrame = popupFrame;
   }
 
   nsPresContext* presContext = startFrame->PresContext();
   nsRect screenRect = startFrame->GetScreenRectInAppUnits();
-    nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x,
-                   presContext->DevPixelsToAppUnits(aY) - screenRect.y);
+  nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
+                 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
   nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset);
 
   nsIContent* content = nullptr;
   if (!foundFrame || !(content = foundFrame->GetContent()))
     return fallbackAnswer;
 
   // Get accessible for the node with the point or the first accessible in
   // the DOM parent chain.
@@ -612,18 +611,17 @@ Accessible::ChildAtPoint(int32_t aX, int
   // where layout won't walk into things for us, such as image map areas and
   // sub documents (XXX: subdocuments should be handled by methods of
   // OuterDocAccessibles).
   uint32_t childCount = accessible->ChildCount();
   for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
     Accessible* child = accessible->GetChildAt(childIdx);
 
     nsIntRect childRect = child->Bounds();
-    if (aX >= childRect.x && aX < childRect.x + childRect.width &&
-        aY >= childRect.y && aY < childRect.y + childRect.height &&
+    if (childRect.Contains(aX, aY) &&
         (child->State() & states::INVISIBLE) == 0) {
 
       if (aWhichChild == eDeepestChild)
         return child->ChildAtPoint(aX, aY, eDeepestChild);
 
       return child;
     }
   }
@@ -676,27 +674,26 @@ Accessible::Bounds() const
 {
   nsIFrame* boundingFrame = nullptr;
   nsRect unionRectTwips = RelativeBounds(&boundingFrame);
   if (!boundingFrame)
     return nsIntRect();
 
   nsIntRect screenRect;
   nsPresContext* presContext = mDoc->PresContext();
-  screenRect.x = presContext->AppUnitsToDevPixels(unionRectTwips.x);
-  screenRect.y = presContext->AppUnitsToDevPixels(unionRectTwips.y);
-  screenRect.width = presContext->AppUnitsToDevPixels(unionRectTwips.width);
-  screenRect.height = presContext->AppUnitsToDevPixels(unionRectTwips.height);
+  screenRect.SetRect(presContext->AppUnitsToDevPixels(unionRectTwips.X()),
+                     presContext->AppUnitsToDevPixels(unionRectTwips.Y()),
+                     presContext->AppUnitsToDevPixels(unionRectTwips.Width()),
+                     presContext->AppUnitsToDevPixels(unionRectTwips.Height()));
 
   // We have the union of the rectangle, now we need to put it in absolute
   // screen coords.
   nsIntRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits().
     ToNearestPixels(presContext->AppUnitsPerDevPixel());
-  screenRect.x += orgRectPixels.x;
-  screenRect.y += orgRectPixels.y;
+  screenRect.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
 
   return screenRect;
 }
 
 void
 Accessible::SetSelected(bool aSelect)
 {
   if (!HasOwnContent())
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -18,17 +18,16 @@
 #include "RootAccessible.h"
 #include "TreeWalker.h"
 #include "xpcAccessibleDocument.h"
 
 #include "nsIMutableArray.h"
 #include "nsICommandManager.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
-#include "nsIDOMAttr.h"
 #include "nsIDOMCharacterData.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMXULDocument.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsPIDOMWindow.h"
 #include "nsIEditingSession.h"
 #include "nsIFrame.h"
 #include "nsIInterfaceRequestorUtils.h"
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -136,18 +136,18 @@ HyperTextAccessible::GetBoundsInFrame(ns
     rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
     NS_ENSURE_SUCCESS(rv, nsIntRect());
 
     // Use the point for the end offset to calculate the width
     nsPoint frameTextEndPoint;
     rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
     NS_ENSURE_SUCCESS(rv, nsIntRect());
 
-    frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
-    frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
+    frameScreenRect.SetRectX(frameScreenRect.X() + std::min(frameTextStartPoint.x, frameTextEndPoint.x),
+                             mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x));
 
     screenRect.UnionRect(frameScreenRect, screenRect);
 
     // Get ready to loop back for next frame continuation
     startContentOffset += frameSubStringLength;
     startContentOffsetInFrame = 0;
     frame = frame->GetNextContinuation();
   }
@@ -423,17 +423,17 @@ HyperTextAccessible::OffsetToDOMPoint(in
     innerOffset = 1;
   }
 
   // Case of embedded object. The point is either before or after the element.
   NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
   nsINode* node = child->GetNode();
   nsINode* parentNode = node->GetParentNode();
   return parentNode ?
-    DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
+    DOMPoint(parentNode, parentNode->ComputeIndexOf(node) + innerOffset) :
     DOMPoint();
 }
 
 DOMPoint
 HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
                                                  nsIContent* aElementContent)
 {
   MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
@@ -1172,18 +1172,18 @@ HyperTextAccessible::OffsetAtPoint(int32
   nsPresContext* presContext = mDoc->PresContext();
   nsPoint coordsInAppUnits =
     ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
 
   nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
   if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
     return -1; // Not found
 
-  nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
-                           coordsInAppUnits.y - frameScreenRect.y);
+  nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.X(),
+                           coordsInAppUnits.y - frameScreenRect.Y());
 
   // Go through the frames to check if each one has the point.
   // When one does, add up the character offsets until we have a match
 
   // We have an point in an accessible child of this, now we need to add up the
   // offsets before it to what we already have
   int32_t offset = 0;
   uint32_t childCount = ChildCount();
@@ -1262,17 +1262,20 @@ HyperTextAccessible::TextBounds(int32_t 
 
     bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
                                               nextOffset - prevOffset));
 
     prevOffset = nextOffset;
     offset1 = 0;
   }
 
-  nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
+  auto boundsX = bounds.X();
+  auto boundsY = bounds.Y();
+  nsAccUtils::ConvertScreenCoordsTo(&boundsX, &boundsY, aCoordType, this);
+  bounds.MoveTo(boundsX, boundsY);
   return bounds;
 }
 
 already_AddRefed<TextEditor>
 HyperTextAccessible::GetEditor() const
 {
   if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
     // If we're inside an editable container, then return that container's editor
@@ -1517,18 +1520,17 @@ HyperTextAccessible::GetCaretRect(nsIWid
 
   // Correct for character size, so that caret always matches the size of
   // the character. This is important for font size transitions, and is
   // necessary because the Gecko caret uses the previous character's size as
   // the user moves forward in the text by character.
   nsIntRect charRect = CharBounds(CaretOffset(),
                                   nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
   if (!charRect.IsEmpty()) {
-    caretRect.height -= charRect.y - caretRect.y;
-    caretRect.y = charRect.y;
+    caretRect.SetTopEdge(charRect.Y());
   }
   return caretRect;
 }
 
 void
 HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
                                            nsTArray<nsRange*>* aRanges)
 {
@@ -1709,18 +1711,18 @@ HyperTextAccessible::ScrollSubstringToPo
   nsIFrame *parentFrame = frame;
   while ((parentFrame = parentFrame->GetParent())) {
     nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
     if (scrollableFrame) {
       if (!initialScrolled) {
         // Scroll substring to the given point. Turn the point into percents
         // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
         nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
-        nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
-        nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
+        nscoord offsetPointX = coordsInAppUnits.x - frameRect.X();
+        nscoord offsetPointY = coordsInAppUnits.y - frameRect.Y();
 
         nsSize size(parentFrame->GetSize());
 
         // avoid divide by zero
         size.width = size.width ? size.width : 1;
         size.height = size.height ? size.height : 1;
 
         int16_t hPercent = offsetPointX * 100 / size.width;
@@ -2074,17 +2076,17 @@ HyperTextAccessible::GetDOMPointByFrameO
     NS_ASSERTION(!aAccessible->IsDoc(),
                  "Shouldn't be called on document accessible!");
 
     nsIContent* content = aAccessible->GetContent();
     NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
 
     nsIContent* parent = content->GetParent();
 
-    aPoint->idx = parent->IndexOf(content) + 1;
+    aPoint->idx = parent->ComputeIndexOf(content) + 1;
     aPoint->node = parent;
 
   } else if (aFrame->IsTextFrame()) {
     nsIContent* content = aFrame->GetContent();
     NS_ENSURE_STATE(content);
 
     nsIFrame *primaryFrame = content->GetPrimaryFrame();
     nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
@@ -2094,17 +2096,17 @@ HyperTextAccessible::GetDOMPointByFrameO
 
   } else {
     nsIContent* content = aFrame->GetContent();
     NS_ENSURE_STATE(content);
 
     nsIContent* parent = content->GetParent();
     NS_ENSURE_STATE(parent);
 
-    aPoint->idx = parent->IndexOf(content);
+    aPoint->idx = parent->ComputeIndexOf(content);
     aPoint->node = parent;
   }
 
   return NS_OK;
 }
 
 // HyperTextAccessible
 void
--- a/accessible/generic/ImageAccessible.cpp
+++ b/accessible/generic/ImageAccessible.cpp
@@ -143,19 +143,19 @@ ImageAccessible::DoAction(uint8_t aIndex
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // ImageAccessible
 
 nsIntPoint
 ImageAccessible::Position(uint32_t aCoordType)
 {
-  nsIntRect rect = Bounds();
-  nsAccUtils::ConvertScreenCoordsTo(&rect.x, &rect.y, aCoordType, this);
-  return rect.TopLeft();
+  nsIntPoint point = Bounds().TopLeft();
+  nsAccUtils::ConvertScreenCoordsTo(&point.x, &point.y, aCoordType, this);
+  return point;
 }
 
 nsIntSize
 ImageAccessible::Size()
 {
   return Bounds().Size();
 }
 
--- a/accessible/generic/OuterDocAccessible.cpp
+++ b/accessible/generic/OuterDocAccessible.cpp
@@ -65,18 +65,17 @@ OuterDocAccessible::NativeRole()
   return roles::INTERNAL_FRAME;
 }
 
 Accessible*
 OuterDocAccessible::ChildAtPoint(int32_t aX, int32_t aY,
                                  EWhichChildAtPoint aWhichChild)
 {
   nsIntRect docRect = Bounds();
-  if (aX < docRect.x || aX >= docRect.x + docRect.width ||
-      aY < docRect.y || aY >= docRect.y + docRect.height)
+  if (!docRect.Contains(aX, aY))
     return nullptr;
 
   // Always return the inner doc as direct child accessible unless bounds
   // outside of it.
   Accessible* child = GetChildAt(0);
   NS_ENSURE_TRUE(child, nullptr);
 
   if (aWhichChild == eDeepestChild)
--- a/accessible/html/HTMLImageMapAccessible.cpp
+++ b/accessible/html/HTMLImageMapAccessible.cpp
@@ -217,12 +217,11 @@ HTMLAreaAccessible::RelativeBounds(nsIFr
   nsRect bounds;
   nsresult rv = map->GetBoundsForAreaContent(mContent, bounds);
   if (NS_FAILED(rv))
     return nsRect();
 
   // XXX Areas are screwy; they return their rects as a pair of points, one pair
   // stored into the width and height.
   *aBoundingFrame = frame;
-  bounds.width -= bounds.x;
-  bounds.height -= bounds.y;
+  bounds.SizeTo(bounds.Width() - bounds.X(), bounds.Height() - bounds.Y());
   return bounds;
 }
--- a/accessible/html/HTMLListAccessible.cpp
+++ b/accessible/html/HTMLListAccessible.cpp
@@ -82,18 +82,18 @@ nsIntRect
 HTMLLIAccessible::Bounds() const
 {
   nsIntRect rect = AccessibleWrap::Bounds();
   if (rect.IsEmpty() || !mBullet || mBullet->IsInside())
     return rect;
 
   nsIntRect bulletRect = mBullet->Bounds();
 
-  rect.width += rect.x - bulletRect.x;
-  rect.x = bulletRect.x; // Move x coordinate of list item over to cover bullet as well
+  // Move x coordinate of list item over to cover bullet as well
+  rect.SetLeftEdge(bulletRect.X());
   return rect;
 }
 
 bool
 HTMLLIAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
 {
   // Adjust index if there's a bullet.
   if (mBullet && aIndex == 0 && aChild != mBullet) {
--- a/accessible/html/HTMLSelectAccessible.cpp
+++ b/accessible/html/HTMLSelectAccessible.cpp
@@ -208,18 +208,18 @@ HTMLSelectOptionAccessible::NativeState(
     // XXX list frames are weird, don't rely on Accessible's general
     // visibility implementation unless they get reimplemented in layout
     state &= ~states::OFFSCREEN;
     // <select> is not collapsed: compare bounds to calculate OFFSCREEN
     Accessible* listAcc = Parent();
     if (listAcc) {
       nsIntRect optionRect = Bounds();
       nsIntRect listRect = listAcc->Bounds();
-      if (optionRect.y < listRect.y ||
-          optionRect.y + optionRect.height > listRect.y + listRect.height) {
+      if (optionRect.Y() < listRect.Y() ||
+          optionRect.YMost() > listRect.YMost()) {
         state |= states::OFFSCREEN;
       }
     }
   }
 
   return state;
 }
 
--- a/accessible/interfaces/nsIAccessible.idl
+++ b/accessible/interfaces/nsIAccessible.idl
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIArray.idl"
 
 interface nsIPersistentProperties;
-interface nsIDOMCSSPrimitiveValue;
 interface nsIDOMNode;
 interface nsIAccessibleDocument;
 interface nsIAccessibleRelation;
 
 %{C++
 namespace mozilla {
 namespace a11y {
 class Accessible;
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -618,18 +618,18 @@ DocAccessibleParent::MaybeInitWindowEmul
   RootAccessible* rootDocument = outerDoc->RootAccessible();
   MOZ_ASSERT(rootDocument);
 
   bool isActive = true;
   nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
   if (Compatibility::IsDolphin()) {
     rect = Bounds();
     nsIntRect rootRect = rootDocument->Bounds();
-    rect.x = rootRect.x - rect.x;
-    rect.y -= rootRect.y;
+    rect.MoveToX(rootRect.X() - rect.X());
+    rect.MoveToY(rect.Y() - rootRect.Y());
 
     auto tab = static_cast<dom::TabParent*>(Manager());
     tab->GetDocShellIsActive(&isActive);
   }
 
   nsWinUtils::NativeWindowCreateProc onCreate([this](HWND aHwnd) -> void {
     IDispatchHolder hWndAccHolder;
 
@@ -647,18 +647,18 @@ DocAccessibleParent::MaybeInitWindowEmul
 
     Unused << SendEmulatedWindow(reinterpret_cast<uintptr_t>(mEmulatedWindowHandle),
                                  hWndAccHolder);
   });
 
   HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
   DebugOnly<HWND> hWnd = nsWinUtils::CreateNativeWindow(kClassNameTabContent,
                                                         parentWnd,
-                                                        rect.x, rect.y,
-                                                        rect.width, rect.height,
+                                                        rect.X(), rect.Y(),
+                                                        rect.Width(), rect.Height(),
                                                         isActive, &onCreate);
   MOZ_ASSERT(hWnd);
 }
 
 /**
  * @param aCOMProxy COM Proxy to the document in the content process.
  */
 void
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -226,20 +226,17 @@ ProxyAccessible::Bounds()
   long left;
   long top;
   long width;
   long height;
   HRESULT hr = acc->accLocation(&left, &top, &width, &height, kChildIdSelf);
   if (FAILED(hr)) {
     return rect;
   }
-  rect.x = left;
-  rect.y = top;
-  rect.width = width;
-  rect.height = height;
+  rect.SetRect(left, top, width, height);
   return rect;
 }
 
 void
 ProxyAccessible::Language(nsString& aLocale)
 {
   aLocale.Truncate();
 
--- a/accessible/ipc/win/handler/AccessibleHandler.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandler.cpp
@@ -1216,18 +1216,22 @@ static const GUID kUnsupportedServices[]
   // Unknown, queried by Windows
   {0x33f139ee, 0xe509, 0x47f7, {0xbf, 0x39, 0x83, 0x76, 0x44, 0xf7, 0x45, 0x76}},
   // Unknown, queried by Windows
   {0xFDA075CF, 0x7C8B, 0x498C, { 0xB5, 0x14, 0xA9, 0xCB, 0x52, 0x1B, 0xBF, 0xB4 }},
   // Unknown, queried by Windows
   {0x8EDAA462, 0x21F4, 0x4C87, { 0xA0, 0x12, 0xB3, 0xCD, 0xA3, 0xAB, 0x01, 0xFC }},
   // Unknown, queried by Windows
   {0xacd46652, 0x829d, 0x41cb, { 0xa5, 0xfc, 0x17, 0xac, 0xf4, 0x36, 0x61, 0xac }},
-  // Unknown, queried by Windows
-  {0xb96fdb85, 0x7204, 0x4724, { 0x84, 0x2b, 0xc7, 0x05, 0x9d, 0xed, 0xb9, 0xd0 }}
+  // SID_IsUIAutomationObject (undocumented), queried by Windows
+  {0xb96fdb85, 0x7204, 0x4724, { 0x84, 0x2b, 0xc7, 0x05, 0x9d, 0xed, 0xb9, 0xd0 }},
+  // IIS_IsOleaccProxy (undocumented), queried by Windows
+  {0x902697FA, 0x80E4, 0x4560, {0x80, 0x2A, 0xA1, 0x3F, 0x22, 0xA6, 0x47, 0x09}},
+  // IID_IHTMLElement, queried by JAWS
+  {0x3050F1FF, 0x98B5, 0x11CF, {0xBB, 0x82, 0x00, 0xAA, 0x00, 0xBD, 0xCE, 0x0B}}
 };
 
 /*** IServiceProvider ***/
 
 HRESULT
 AccessibleHandler::QueryService(REFGUID aServiceId, REFIID aIid,
                                 void** aOutInterface)
 {
@@ -1237,16 +1241,23 @@ AccessibleHandler::QueryService(REFGUID 
      of our own object to implement this just like a QI. */
   if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 ||
       aIid == IID_IAccessible2) {
     RefPtr<NEWEST_IA2_INTERFACE> ia2(this);
     ia2.forget(aOutInterface);
     return S_OK;
   }
 
+  // JAWS uses QueryService for these, but QI will work just fine and we can
+  // thus avoid a cross-process call. More importantly, if QS is used, the
+  // handler won't get used for that object, so our caching won't be used.
+  if (aIid == IID_IAccessibleAction || aIid == IID_IAccessibleText) {
+    return InternalQueryInterface(aIid, aOutInterface);
+  }
+
   for (uint32_t i = 0; i < ArrayLength(kUnsupportedServices); ++i) {
     if (aServiceId == kUnsupportedServices[i]) {
       return E_NOINTERFACE;
     }
   }
 
   if (!mServProvPassThru) {
     RefPtr<IUnknown> proxy(GetProxy());
--- a/accessible/tests/mochitest/attributes/a11y.ini
+++ b/accessible/tests/mochitest/attributes/a11y.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 support-files =
   !/accessible/tests/mochitest/*.js
 
 [test_dpub_aria_xml-roles.html]
+[test_graphics_aria_xml-roles.html]
 [test_obj.html]
 [test_obj_css.html]
 [test_obj_css.xul]
 [test_obj_group.html]
 [test_obj_group.xul]
 [test_obj_group_tree.xul]
 [test_tag.html]
 [test_xml-roles.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>XML roles tests</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../attributes.js"></script>
+
+  <script type="application/javascript">
+
+    function doTest() {
+      // Graphics ARIA roles should be exposed via the xml-roles object attribute.
+      let graphics_attrs = [
+        "graphics-document",
+        "graphics-object",
+        "graphics-symbol"
+      ];
+      for (let attr of graphics_attrs) {
+        testAttrs(attr, {"xml-roles": attr}, true);
+      }
+      SimpleTest.finish();
+    }
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513"
+     title="implement ARIA Graphics roles">
+    Bug 1432513
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+  <div id="graphics-document" role="graphics-document">document</div>
+  <div id="graphics-object" role="graphics-object">object</div>
+  <div id="graphics-symbol" role="graphics-symbol">symbol</div>
+</body>
+</html>
--- a/accessible/tests/mochitest/elm/test_shadowroot.html
+++ b/accessible/tests/mochitest/elm/test_shadowroot.html
@@ -20,17 +20,17 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <script>
     SimpleTest.waitForExplicitFinish();
     SpecialPowers.pushPrefEnv({
       set: [
-        ["dom.webcomponents.enabled", true]
+        ["dom.webcomponents.shadowdom.enabled", true]
       ]
     }, function() {
       // This test loads in an iframe, to ensure that the element instance is
       // loaded with the correct value of the preference.
       var iframe = document.createElement("iframe");
       iframe.src = "test_shadowroot_subframe.html";
       document.body.appendChild(iframe);
     });
--- a/accessible/tests/mochitest/hittest/test_shadowroot.html
+++ b/accessible/tests/mochitest/hittest/test_shadowroot.html
@@ -20,17 +20,17 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <script>
     SimpleTest.waitForExplicitFinish();
     SpecialPowers.pushPrefEnv({
       set: [
-        ["dom.webcomponents.enabled", true]
+        ["dom.webcomponents.shadowdom.enabled", true]
       ]
     }, function() {
       // This test loads in an iframe, to ensure that the element instance is
       // loaded with the correct value of the preference.
       var iframe = document.createElement("iframe");
       iframe.src = "test_shadowroot_subframe.html";
       document.body.appendChild(iframe);
     });
--- a/accessible/tests/mochitest/role/a11y.ini
+++ b/accessible/tests/mochitest/role/a11y.ini
@@ -3,9 +3,10 @@ support-files =
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/moz.png
 
 [test_aria.html]
 [test_aria.xul]
 [test_dpub_aria.html]
 [test_general.html]
 [test_general.xul]
+[test_graphics_aria.html]
 [test_svg.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_graphics_aria.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test Graphics ARIA roles</title>
+
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+
+  <script type="application/javascript">
+
+    function doTest() {
+      // Graphics ARIA role map.
+      testRole("graphics-document", ROLE_DOCUMENT);
+      testRole("graphics-object", ROLE_GROUPING);
+      testRole("graphics-symbol", ROLE_GRAPHIC);
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513"
+     title="implement ARIA Graphics roles">
+    Bug 1432513
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+  <div id="graphics-document" role="graphics-document">document</div>
+  <div id="graphics-object" role="graphics-object">object</div>
+  <div id="graphics-symbol" role="graphics-symbol">symbol</div>
+</body>
+</html>
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -110,37 +110,27 @@
             {
               // xul:tab ("about:license")
               role: ROLE_PAGETAB,
               children: [
                 {
                   // xul:text, i.e. the tab label text
                   role: ROLE_TEXT_LEAF,
                   children: []
-                },
-                {
-                  // xul:toolbarbutton ("Close Tab")
-                  role: ROLE_PUSHBUTTON,
-                  children: []
                 }
               ]
             },
             {
               // tab ("about:mozilla")
               role: ROLE_PAGETAB,
               children: [
                 {
                   // xul:text, i.e. the tab label text
                   role: ROLE_TEXT_LEAF,
                   children: []
-                },
-                {
-                  // xul:toolbarbutton ("Close Tab")
-                  role: ROLE_PUSHBUTTON,
-                  children: []
                 }
               ]
             },
             {
               // xul:toolbarbutton ("Open a new tab")
               role: ROLE_PUSHBUTTON,
               children: newTabChildren
             }
--- a/accessible/tests/mochitest/treeupdate/test_bug1276857.html
+++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
@@ -107,17 +107,17 @@
       gQueue.push(new runTest());
       gQueue.push(new runShadowTest());
       gQueue.invoke(); // will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     SpecialPowers.pushPrefEnv({
       set: [
-        ["dom.webcomponents.enabled", true]
+        ["dom.webcomponents.shadowdom.enabled", true]
       ]
     }, function() {
       // This test loads in an iframe, to ensure that the element instance is
       // loaded with the correct value of the preference.
       let iframe = document.createElement("iframe");
       iframe.id = "iframe";
       iframe.src = "test_bug1276857_subframe.html";
       addA11yLoadEvent(doTest, iframe.contentWindow);
--- a/accessible/tests/mochitest/treeview.js
+++ b/accessible/tests/mochitest/treeview.js
@@ -102,17 +102,16 @@ nsTreeView.prototype =
     return info.parentIndex;
   },
   hasNextSibling: function hasNextSibling(aRowIndex, aAfterIndex) { },
   getLevel: function getLevel(aIndex) {
     var info = this.getInfoByIndex(aIndex);
     return info.level;
   },
   getImageSrc: function getImageSrc(aRow, aCol) {},
-  getProgressMode: function getProgressMode(aRow, aCol) {},
   isContainer: function isContainer(aIndex) {
     var data = this.getDataForIndex(aIndex);
     return data.open != undefined;
   },
   isContainerOpen: function isContainerOpen(aIndex) {
     var data = this.getDataForIndex(aIndex);
     return data.open;
   },
--- a/accessible/windows/ia2/ia2AccessibleComponent.cpp
+++ b/accessible/windows/ia2/ia2AccessibleComponent.cpp
@@ -57,26 +57,26 @@ ia2AccessibleComponent::get_locationInPa
     return S_OK;
 
   nsIntRect rect = acc->Bounds();
 
   // The coordinates of the returned position are relative to this object's
   // parent or relative to the screen on which this object is rendered if it
   // has no parent.
   if (!acc->Parent()) {
-    *aX = rect.x;
-    *aY = rect.y;
+    *aX = rect.X();
+    *aY = rect.Y();
     return S_OK;
   }
 
   // The coordinates of the bounding box are given relative to the parent's
   // coordinate system.
   nsIntRect parentRect = acc->Parent()->Bounds();
-  *aX = rect.x - parentRect.x;
-  *aY = rect.y - parentRect.y;
+  *aX = rect.X() - parentRect.X();
+  *aY = rect.Y() - parentRect.Y();
   return S_OK;
 }
 
 STDMETHODIMP
 ia2AccessibleComponent::get_foreground(IA2Color* aForeground)
 {
   if (!aForeground)
     return E_INVALIDARG;
--- a/accessible/windows/ia2/ia2AccessibleText.cpp
+++ b/accessible/windows/ia2/ia2AccessibleText.cpp
@@ -105,20 +105,21 @@ ia2AccessibleText::get_characterExtents(
   nsIntRect rect;
   MOZ_ASSERT(!HyperTextProxyFor(this));
   HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
   if (textAcc->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   rect = textAcc->CharBounds(aOffset, geckoCoordType);
 
-  *aX = rect.x;
-  *aY = rect.y;
-  *aWidth = rect.width;
-  *aHeight = rect.height;
+  // Can't use GetRect() because of long vs. int32_t mismatch
+  *aX = rect.X();
+  *aY = rect.Y();
+  *aWidth = rect.Width();
+  *aHeight = rect.Height();
   return S_OK;
 }
 
 STDMETHODIMP
 ia2AccessibleText::get_nSelections(long* aNSelections)
 {
   if (!aNSelections)
     return E_INVALIDARG;
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -897,20 +897,20 @@ AccessibleWrap::accLocation(
 
   if (accessible) {
     return accessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight,
                                    kVarChildIdSelf);
   }
 
   nsIntRect rect = Bounds();
 
-  *pxLeft = rect.x;
-  *pyTop = rect.y;
-  *pcxWidth = rect.width;
-  *pcyHeight = rect.height;
+  *pxLeft = rect.X();
+  *pyTop = rect.Y();
+  *pcxWidth = rect.Width();
+  *pcyHeight = rect.Height();
   return S_OK;
 }
 
 STDMETHODIMP
 AccessibleWrap::accNavigate(
       /* [in] */ long navDir,
       /* [optional][in] */ VARIANT varStart,
       /* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt)
@@ -1656,22 +1656,22 @@ AccessibleWrap::UpdateSystemCaretFor(HWN
                                      const LayoutDeviceIntRect& aCaretRect)
 {
   if (!aCaretWnd || aCaretRect.IsEmpty()) {
     return;
   }
 
   // Create invisible bitmap for caret, otherwise its appearance interferes
   // with Gecko caret
-  nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.height, 1, 1, nullptr));
-  if (::CreateCaret(aCaretWnd, caretBitMap, 1, aCaretRect.height)) {  // Also destroys the last caret
+  nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.Height(), 1, 1, nullptr));
+  if (::CreateCaret(aCaretWnd, caretBitMap, 1, aCaretRect.Height())) {  // Also destroys the last caret
     ::ShowCaret(aCaretWnd);
     RECT windowRect;
     ::GetWindowRect(aCaretWnd, &windowRect);
-    ::SetCaretPos(aCaretRect.x - windowRect.left, aCaretRect.y - windowRect.top);
+    ::SetCaretPos(aCaretRect.X() - windowRect.left, aCaretRect.Y() - windowRect.top);
   }
 }
 
 ITypeInfo*
 AccessibleWrap::GetTI(LCID lcid)
 {
   if (gTypeInfo)
     return gTypeInfo;
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -149,33 +149,33 @@ DocAccessibleWrap::DoInitialUpdate()
     // Create window for tab document.
     if (mDocFlags & eTabDocument) {
       a11y::RootAccessible* rootDocument = RootAccessible();
       bool isActive = true;
       nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
       if (Compatibility::IsDolphin()) {
         rect = Bounds();
         nsIntRect rootRect = rootDocument->Bounds();
-        rect.x = rootRect.x - rect.x;
-        rect.y -= rootRect.y;
+        rect.MoveToX(rootRect.X() - rect.X());
+        rect.MoveByY(-rootRect.Y());
 
         nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
         nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
         docShell->GetIsActive(&isActive);
       }
 
       RefPtr<DocAccessibleWrap> self(this);
       nsWinUtils::NativeWindowCreateProc onCreate([self](HWND aHwnd) -> void {
         ::SetPropW(aHwnd, kPropNameDocAcc, reinterpret_cast<HANDLE>(self.get()));
       });
 
       HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
       mHWND = nsWinUtils::CreateNativeWindow(kClassNameTabContent, parentWnd,
-                                             rect.x, rect.y,
-                                             rect.width, rect.height, isActive,
+                                             rect.X(), rect.Y(),
+                                             rect.Width(), rect.Height(), isActive,
                                              &onCreate);
     } else {
       DocAccessible* parentDocument = ParentDocument();
       if (parentDocument)
         mHWND = parentDocument->GetNativeWindow();
     }
   }
 }
--- a/accessible/windows/msaa/nsWinUtils.cpp
+++ b/accessible/windows/msaa/nsWinUtils.cpp
@@ -34,35 +34,34 @@ const wchar_t* kPropNameTabContent = L"A
 /**
  * WindowProc to process WM_GETOBJECT messages, used in windows emulation mode.
  */
 static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg,
                                    WPARAM wParam, LPARAM lParam);
 
 bool nsWinUtils::sWindowEmulationStarted = false;
 
-already_AddRefed<nsIDOMCSSStyleDeclaration>
+already_AddRefed<nsICSSDeclaration>
 nsWinUtils::GetComputedStyleDeclaration(nsIContent* aContent)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aContent);
   if (!elm)
     return nullptr;
 
   // Returns number of items in style declaration
   nsCOMPtr<nsPIDOMWindowInner> window = elm->OwnerDoc()->GetInnerWindow();
   if (!window)
     return nullptr;
 
   ErrorResult dummy;
-  nsCOMPtr<nsICSSDeclaration> cssDecl;
   nsCOMPtr<Element> domElement(do_QueryInterface(elm));
-  cssDecl = window->GetComputedStyle(*domElement, EmptyString(), dummy);
-  nsCOMPtr<nsIDOMCSSStyleDeclaration> domDecl = do_QueryInterface(cssDecl);
+  nsCOMPtr<nsICSSDeclaration> cssDecl =
+     window->GetComputedStyle(*domElement, EmptyString(), dummy);
   dummy.SuppressException();
-  return domDecl.forget();
+  return cssDecl.forget();
 }
 
 bool
 nsWinUtils::MaybeStartWindowEmulation()
 {
   // Register window class that'll be used for document accessibles associated
   // with tabs.
   if (IPCAccessibilityActive())
--- a/accessible/windows/msaa/nsWinUtils.h
+++ b/accessible/windows/msaa/nsWinUtils.h
@@ -6,17 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsWinUtils_h_
 #define nsWinUtils_h_
 
 #include <functional>
 #include <windows.h>
 
-#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsICSSDeclaration.h"
 #include "nsCOMPtr.h"
 
 class nsIContent;
 
 namespace mozilla {
 namespace a11y {
 
 class DocAccessible;
@@ -30,17 +30,17 @@ class nsWinUtils
 {
 public:
   /**
    * Return computed styles declaration for the given node.
    *
    * @note Please use it carefully since it can shutdown the accessible tree
    *       you operate on.
    */
-  static already_AddRefed<nsIDOMCSSStyleDeclaration>
+  static already_AddRefed<nsICSSDeclaration>
     GetComputedStyleDeclaration(nsIContent* aContent);
 
   /**
    * Start window emulation if presence of specific AT is detected.
    */
   static bool MaybeStartWindowEmulation();
 
   /**
--- a/accessible/windows/sdn/sdnAccessible.cpp
+++ b/accessible/windows/sdn/sdnAccessible.cpp
@@ -8,17 +8,17 @@
 #include "ISimpleDOM_i.c"
 
 #include "DocAccessibleWrap.h"
 
 #include "nsAttrName.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleTypes.h"
 #include "nsIDOMHTMLElement.h"
-#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsICSSDeclaration.h"
 #include "nsNameSpaceManager.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWinUtils.h"
 #include "nsRange.h"
 
 #include "mozilla/dom/BorrowedAttrInfo.h"
 #include "mozilla/dom/Element.h"
 
@@ -226,30 +226,31 @@ sdnAccessible::get_computedStyle(unsigne
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   *aNumStyleProperties = 0;
 
   if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
     return S_FALSE;
 
-  nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
+  nsCOMPtr<nsICSSDeclaration> cssDecl =
     nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
   NS_ENSURE_TRUE(cssDecl, E_FAIL);
 
   uint32_t length = 0;
   cssDecl->GetLength(&length);
 
   uint32_t index = 0, realIndex = 0;
   for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties;
        index ++) {
     nsAutoString property, value;
 
     // Ignore -moz-* properties.
-    if (NS_SUCCEEDED(cssDecl->Item(index, property)) && property.CharAt(0) != '-')
+    cssDecl->Item(index, property);
+    if (property.CharAt(0) != '-')
       cssDecl->GetPropertyValue(property, value);  // Get property value
 
     if (!value.IsEmpty()) {
       aStyleProperties[realIndex] = ::SysAllocString(property.get());
       aStyleValues[realIndex] = ::SysAllocString(value.get());
       ++realIndex;
     }
   }
@@ -269,17 +270,17 @@ sdnAccessible::get_computedStyleForPrope
     return E_INVALIDARG;
 
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
     return S_FALSE;
 
-  nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
+  nsCOMPtr<nsICSSDeclaration> cssDecl =
     nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
   NS_ENSURE_TRUE(cssDecl, E_FAIL);
 
   uint32_t index = 0;
   for (index = 0; index < aNumStyleProperties; index++) {
     nsAutoString value;
     if (aStyleProperties[index])
       cssDecl->GetPropertyValue(nsDependentString(aStyleProperties[index]), value);  // Get property value
--- a/accessible/windows/sdn/sdnTextAccessible.cpp
+++ b/accessible/windows/sdn/sdnTextAccessible.cpp
@@ -70,20 +70,20 @@ sdnTextAccessible::get_clippedSubstringB
                "There must always be a doc accessible, but there isn't. Crash!");
 
   nsIntRect docRect = document->Bounds();
   nsIntRect unclippedRect(x, y, width, height);
 
   nsIntRect clippedRect;
   clippedRect.IntersectRect(unclippedRect, docRect);
 
-  *aX = clippedRect.x;
-  *aY = clippedRect.y;
-  *aWidth = clippedRect.width;
-  *aHeight = clippedRect.height;
+  *aX = clippedRect.X();
+  *aY = clippedRect.Y();
+  *aWidth = clippedRect.Width();
+  *aHeight = clippedRect.Height();
   return S_OK;
 }
 
 STDMETHODIMP
 sdnTextAccessible::get_unclippedSubstringBounds(unsigned int aStartIndex,
                                                 unsigned int aEndIndex,
                                                 int __RPC_FAR* aX,
                                                 int __RPC_FAR* aY,
@@ -107,27 +107,27 @@ sdnTextAccessible::get_unclippedSubstrin
     return E_FAIL;
 
   nsRect sum;
   nsIFrame* iter = startFrame;
   nsIFrame* stopLoopFrame = endFrame->GetNextContinuation();
   for (; iter != stopLoopFrame; iter = iter->GetNextContinuation()) {
     nsRect rect = iter->GetScreenRectInAppUnits();
     nscoord start = (iter == startFrame) ? startPoint.x : 0;
-    nscoord end = (iter == endFrame) ? endPoint.x : rect.width;
-    rect.x += start;
-    rect.width = end - start;
+    nscoord end = (iter == endFrame) ? endPoint.x : rect.Width();
+    rect.MoveByX(start);
+    rect.SetWidth(end - start);
     sum.UnionRect(sum, rect);
   }
 
   nsPresContext* presContext = mAccessible->Document()->PresContext();
-  *aX = presContext->AppUnitsToDevPixels(sum.x);
-  *aY = presContext->AppUnitsToDevPixels(sum.y);
-  *aWidth = presContext->AppUnitsToDevPixels(sum.width);
-  *aHeight = presContext->AppUnitsToDevPixels(sum.height);
+  *aX = presContext->AppUnitsToDevPixels(sum.X());
+  *aY = presContext->AppUnitsToDevPixels(sum.Y());
+  *aWidth = presContext->AppUnitsToDevPixels(sum.Width());
+  *aHeight = presContext->AppUnitsToDevPixels(sum.Height());
 
   return S_OK;
 }
 
 STDMETHODIMP
 sdnTextAccessible::scrollToSubstring(unsigned int aStartIndex,
                                      unsigned int aEndIndex)
 {
--- a/accessible/xpcom/xpcAccessible.cpp
+++ b/accessible/xpcom/xpcAccessible.cpp
@@ -447,21 +447,17 @@ xpcAccessible::GetBounds(int32_t* aX, in
 
   nsIntRect rect;
   if (Accessible* acc = IntlGeneric().AsAccessible()) {
     rect = acc->Bounds();
   } else {
     rect = IntlGeneric().AsProxy()->Bounds();
   }
 
-  *aX = rect.x;
-  *aY = rect.y;
-  *aWidth = rect.width;
-  *aHeight = rect.height;
-
+  rect.GetRect(aX, aY, aWidth, aHeight);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessible::GroupPosition(int32_t* aGroupLevel,
                              int32_t* aSimilarItemsInGroup,
                              int32_t* aPositionInGroup)
 {
--- a/accessible/xpcom/xpcAccessibleHyperText.cpp
+++ b/accessible/xpcom/xpcAccessibleHyperText.cpp
@@ -265,18 +265,17 @@ xpcAccessibleHyperText::GetCharacterExte
     rect = Intl()->CharBounds(aOffset, aCoordType);
   } else {
 #if defined(XP_WIN)
     return NS_ERROR_NOT_IMPLEMENTED;
 #else
     rect = mIntl.AsProxy()->CharBounds(aOffset, aCoordType);
 #endif
   }
-  *aX = rect.x; *aY = rect.y;
-  *aWidth = rect.width; *aHeight = rect.height;
+  rect.GetRect(aX, aY, aWidth, aHeight);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibleHyperText::GetRangeExtents(int32_t aStartOffset, int32_t aEndOffset,
                                         int32_t* aX, int32_t* aY,
                                         int32_t* aWidth, int32_t* aHeight,
                                         uint32_t aCoordType)
@@ -295,18 +294,17 @@ xpcAccessibleHyperText::GetRangeExtents(
     rect = Intl()->TextBounds(aStartOffset, aEndOffset, aCoordType);
   } else {
 #if defined(XP_WIN)
     return NS_ERROR_NOT_IMPLEMENTED;
 #else
     rect = mIntl.AsProxy()->TextBounds(aStartOffset, aEndOffset, aCoordType);
 #endif
   }
-  *aX = rect.x; *aY = rect.y;
-  *aWidth = rect.width; *aHeight = rect.height;
+  rect.GetRect(aX, aY, aWidth, aHeight);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibleHyperText::GetOffsetAtPoint(int32_t aX, int32_t aY,
                                          uint32_t aCoordType, int32_t* aOffset)
 {
   NS_ENSURE_ARG_POINTER(aOffset);
--- a/accessible/xul/XULTreeAccessible.cpp
+++ b/accessible/xul/XULTreeAccessible.cpp
@@ -188,18 +188,18 @@ XULTreeAccessible::ChildAtPoint(int32_t 
   nsPresContext *presContext = frame->PresContext();
   nsIPresShell* presShell = presContext->PresShell();
 
   nsIFrame *rootFrame = presShell->GetRootFrame();
   NS_ENSURE_TRUE(rootFrame, nullptr);
 
   CSSIntRect rootRect = rootFrame->GetScreenRect();
 
-  int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.x;
-  int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.y;
+  int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.X();
+  int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.Y();
 
   int32_t row = -1;
   nsCOMPtr<nsITreeColumn> column;
   nsAutoString childEltUnused;
   mTree->GetCellAt(clientX, clientY, &row, getter_AddRefs(column),
                    childEltUnused);
 
   // If we failed to find tree cell for the given point then it might be
--- a/accessible/xul/XULTreeGridAccessible.cpp
+++ b/accessible/xul/XULTreeGridAccessible.cpp
@@ -322,18 +322,18 @@ XULTreeGridRowAccessible::ChildAtPoint(i
   nsPresContext *presContext = frame->PresContext();
   nsIPresShell* presShell = presContext->PresShell();
 
   nsIFrame *rootFrame = presShell->GetRootFrame();
   NS_ENSURE_TRUE(rootFrame, nullptr);
 
   CSSIntRect rootRect = rootFrame->GetScreenRect();
 
-  int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.x;
-  int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.y;
+  int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.X();
+  int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.Y();
 
   int32_t row = -1;
   nsCOMPtr<nsITreeColumn> column;
   nsAutoString childEltUnused;
   mTree->GetCellAt(clientX, clientY, &row, getter_AddRefs(column),
                    childEltUnused);
 
   // Return if we failed to find tree cell in the row for the given point.
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -48,16 +48,23 @@ ifdef COMPILE_ENVIRONMENT
 libs::
 	cp -p $(MOZ_APP_NAME)$(BIN_SUFFIX) $(DIST)/bin/$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
 endif
 
 GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, firefox.js)
 
 endif
 
+# channel-prefs.js is handled separate from other prefs due to bug 756325
+# DO NOT change the content of channel-prefs.js without taking the appropriate
+# steps. See bug 1431342.
+libs:: $(srcdir)/profile/channel-prefs.js
+	$(NSINSTALL) -D $(DIST)/bin/defaults/pref
+	$(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
+
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 
 MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)
 
 ifdef MOZ_DEBUG
 MAC_APP_NAME := $(MAC_APP_NAME)Debug
 endif
 
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<blocklist lastupdate="1511530749075" xmlns="http://www.mozilla.org/2006/addons-blocklist">
+<blocklist lastupdate="1500496563565" xmlns="http://www.mozilla.org/2006/addons-blocklist">
   <emItems>
     <emItem blockID="i988" id="{b12785f5-d8d0-4530-a3ea-5c4263b85bef}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i398" id="{377e5d4d-77e5-476a-8716-7e70a9272da0}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
@@ -257,16 +257,20 @@
     </emItem>
     <emItem blockID="i862" id="{CA8C84C6-3918-41b1-BE77-049B2BDD887C}">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
+    <emItem blockID="5bf72f70-a611-4845-af3f-d4dabe8862b6" id="/^(\{1490068c-d8b7-4bd2-9621-a648942b312c\})|(\{d47ebc8a-c1ea-4a42-9ca3-f723fff034bd\})|(\{83d6f65c-7fc0-47d0-9864-a488bfcaa376\})|(\{e804fa4c-08e0-4dae-a237-8680074eba07\})|(\{ea618d26-780e-4f0f-91fd-2a6911064204\})|(\{ce93dcc7-f911-4098-8238-7f023dcdfd0d\})|(\{7eaf96aa-d4e7-41b0-9f12-775c2ac7f7c0\})|(\{b019c485-2a48-4f5b-be13-a7af94bc1a3e\})|(\{9b8a3057-8bf4-4a9e-b94b-867e4e71a50c\})|(\{eb3ebb14-6ced-4f60-9800-85c3de3680a4\})|(\{01f409a5-d617-47be-a574-d54325fe05d1\})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i882" id="69ffxtbr@PackageTracer_69.com">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i706" id="thefoxonlybetter@quicksaver">
@@ -964,52 +968,28 @@
     <emItem blockID="i226" id="{462be121-2b54-4218-bf00-b9bf8135b23f}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i44" id="sigma@labs.mozilla">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
-    <emItem blockID="i258" id="helperbar@helperbar.com">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="1.0" severity="1"/>
-    </emItem>
     <emItem blockID="i96" id="youtubeee@youtuber3.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i564" id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="1"/>
-    </emItem>
-    <emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="3"/>
-    </emItem>
     <emItem blockID="i97" id="support3_en@adobe122.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i439" id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="1"/>
-    </emItem>
     <emItem blockID="i576" id="newmoz@facebook.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
-      <prefs/>
-      <versionRange minVersion="0.1" maxVersion="*" severity="1">
-        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
-          <versionRange maxVersion="9.0" minVersion="9.0a1"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
     <emItem blockID="i776" id="g@uzcERQ6ko.net">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i494" id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
@@ -1061,16 +1041,20 @@
         </targetApplication>
       </versionRange>
       <versionRange minVersion="1.5.7.5" maxVersion="1.5.7.5" severity="1"/>
     </emItem>
     <emItem blockID="i515" id="/^({bf9194c2-b86d-4ebc-9b53-1c08b6ff779e}|{61a83e16-7198-49c6-8874-3e4e8faeb4f3}|{f0af464e-5167-45cf-9cf0-66b396d1918c}|{5d9968c3-101c-4944-ba71-72d77393322d}|{01e86e69-a2f8-48a0-b068-83869bdba3d0})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
+    <emItem blockID="4ca8206f-bc2a-4428-9439-7f3142dc08db" id="/^(\{ac06c6b2-3fd6-45ee-9237-6235aa347215\})|(\{d461cc1b-8a36-4ff0-b330-1824c148f326\})|(\{d1ab5ebd-9505-481d-a6cd-6b9db8d65977\})|(\{07953f60-447e-4f53-a5ef-ed060487f616\})|(\{2d3c5a5a-8e6f-4762-8aff-b24953fe1cc9\})|(\{f82b3ad5-e590-4286-891f-05adf5028d2f\})|(\{f96245ad-3bb0-46c5-8ca9-2917d69aa6ca\})|(\{2f53e091-4b16-4b60-9cae-69d0c55b2e78\})|(\{18868c3a-a209-41a6-855d-f99f782d1606\})|(\{47352fbf-80d9-4b70-9398-fb7bffa3da53\})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i596" id="{b99c8534-7800-48fa-bd71-519a46cdc7e1}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i461" id="{8E9E3331-D360-4f87-8803-52DE43566502}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -1257,16 +1241,20 @@
           <versionRange maxVersion="*" minVersion="56.0a1"/>
         </targetApplication>
       </versionRange>
     </emItem>
     <emItem blockID="i1034" id="a88a77ahjjfjakckmmabsy278djasi@jetpack">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="8088b39a-3e6d-4a17-a22f-3f95c0464bd6" id="{5b0f6d3c-10fd-414c-a135-dffd26d7de0f}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="9abc7502-bd6f-40d7-b035-abe721345360" id="{368eb817-31b4-4be9-a761-b67598faf9fa}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i562" id="iobitapps@mybrowserbar.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -1459,16 +1447,20 @@
     <emItem blockID="i68" id="flashupdate@adobe.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i492" id="{af95cc15-3b9b-45ae-8d9b-98d08eda3111}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="674b6e19-f087-4706-a91d-1e723ed6f79e" id="{1490068c-d8b7-4bd2-9621-a648942b312c}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i488" id="jid1-4P0kohSJxU1qGg@jetpack">
       <prefs/>
       <versionRange minVersion="1.2.50" maxVersion="1.2.50" severity="1"/>
     </emItem>
     <emItem blockID="i314" id="crossriderapp8812@crossrider.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -1507,16 +1499,20 @@
     <emItem blockID="i852" id="6lIy@T.edu">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i856" id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="9dfeee42-e6a8-49e0-8979-0648f7368239" id="/^({fce89242-66d3-4946-9ed0-e66078f172fc})|({0c4df994-4f4a-4646-ae5d-8936be8a4188})|({6cee30bc-a27c-43ea-ac72-302862db62b2})|({e08ebf0b-431d-4ed1-88bb-02e5db8b9443})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i520" id="/^({7316e43a-3ebd-4bb4-95c1-9caf6756c97f}|{0cc09160-108c-4759-bab1-5c12c216e005}|{ef03e721-f564-4333-a331-d4062cee6f2b}|{465fcfbb-47a4-4866-a5d5-d12f9a77da00}|{7557724b-30a9-42a4-98eb-77fcb0fd1be3}|{b7c7d4b0-7a84-4b73-a7ef-48ef59a52c3b})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="f7569261-f575-4719-8202-552b20d013b0" id="{7e907a15-0a4c-4ff4-b64f-5eeb8f841349}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
@@ -2146,16 +2142,40 @@
     <emItem blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i117" id="{ce7e73df-6a44-4028-8079-5927a588c948}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="1.0.8" severity="1"/>
     </emItem>
+    <emItem blockID="i258" id="helperbar@helperbar.com">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="1.0" severity="1"/>
+    </emItem>
+    <emItem blockID="i564" id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="1"/>
+    </emItem>
+    <emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="i439" id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="1"/>
+    </emItem>
+    <emItem blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
+      <prefs/>
+      <versionRange minVersion="0.1" maxVersion="*" severity="1">
+        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+          <versionRange maxVersion="9.0" minVersion="9.0a1"/>
+        </targetApplication>
+      </versionRange>
+    </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>
@@ -3425,46 +3445,37 @@
       <serialNumber>F5Bg6C237Q==</serialNumber>
     </certItem>
     <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
       <serialNumber>Ai7cBJYqBE0I9NdyoZfRrw==</serialNumber>
     </certItem>
     <certItem issuerName="MIGLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRUwEwYDVQQLEwxNaWNyb3NvZnQgSVQxHjAcBgNVBAMTFU1pY3Jvc29mdCBJVCBTU0wgU0hBMg==">
       <serialNumber>WgAFElcDxFjoswSzjAABAAUSVw==</serialNumber>
     </certItem>
-    <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
-      <serialNumber>AImQERVYPoeb</serialNumber>
-    </certItem>
-    <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy">
-      <serialNumber>GpO48aJ8GngtwECqZhm/xA==</serialNumber>
-    </certItem>
-    <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
-      <serialNumber>CdYL9vSQCEKzBwjO10ud2w==</serialNumber>
-    </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>UKM/CNF2OvC4giYnAUG/Ag==</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
       <serialNumber>LzVYePklc3vH3jkk0BZr9g==</serialNumber>
     </certItem>
     <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
       <serialNumber>BYOGvG32ukb1Yxj2oKoFyw==</serialNumber>
     </certItem>
-    <certItem issuerName="MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=">
-      <serialNumber>a12RvBNhznU=</serialNumber>
-    </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
       <serialNumber>P4sUnc++hlU/bXj0zSTlcQ==</serialNumber>
     </certItem>
     <certItem issuerName="MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
       <serialNumber>AKrMYlJmUUin8FOM/0TJrmk=</serialNumber>
     </certItem>
     <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
       <serialNumber>A7uy+rmTav6tDH4dRrsnvXGH</serialNumber>
     </certItem>
+    <certItem issuerName="MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=">
+      <serialNumber>bqapwACCtKhVagTl7cEP7KFbM0E=</serialNumber>
+    </certItem>
     <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
       <serialNumber>RUT1Gehd1KKYPfqOlgspoQ==</serialNumber>
     </certItem>
     <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
       <serialNumber>KuzHPJLdK5hNgJRo3R47Ag==</serialNumber>
     </certItem>
     <certItem issuerName="MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==">
       <serialNumber>WJ2qHzWUqTk=</serialNumber>
@@ -3482,16 +3493,19 @@
       <serialNumber>OOkLFZaa4CXGyJlLTIEjUQ==</serialNumber>
     </certItem>
     <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
       <serialNumber>Byc85g==</serialNumber>
     </certItem>
     <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
       <serialNumber>BAAAAAABRE7wRk4=</serialNumber>
     </certItem>
+    <certItem issuerName="MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==">
+      <serialNumber>BFA=</serialNumber>
+    </certItem>
     <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
       <serialNumber>ByeQ9g==</serialNumber>
     </certItem>
     <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>OYBKgxEHpW/8XGAGAlvJyMA=</serialNumber>
     </certItem>
     <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
       <serialNumber>bzTw0uq05TUYEGS98bh0Ww==</serialNumber>
@@ -3821,16 +3835,19 @@
       <serialNumber>GN2Hrh9LtnE=</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>UT6GtTGbEC6SXJteWAKy2g==</serialNumber>
     </certItem>
     <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
       <serialNumber>P6G7IYSL2RZxtzTh8I6qPA==</serialNumber>
     </certItem>
+    <certItem issuerName="MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=">
+      <serialNumber>TasC8Zd8BT8kXEE67cFQmA==</serialNumber>
+    </certItem>
     <certItem issuerName="MGcxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpGcmF1bmhvZmVyMSEwHwYDVQQLExhGcmF1bmhvZmVyIENvcnBvcmF0ZSBQS0kxIDAeBgNVBAMTF0ZyYXVuaG9mZXIgUm9vdCBDQSAyMDA3">
       <serialNumber>YR3YYQAAAAAABA==</serialNumber>
     </certItem>
     <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
       <serialNumber>GN2Hrh9LtnQ=</serialNumber>
     </certItem>
     <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
       <serialNumber>ByfNeA==</serialNumber>
@@ -3896,16 +3913,19 @@
       <serialNumber>CuUEKEJM4xhxlFXraPcSpQ==</serialNumber>
     </certItem>
     <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
       <serialNumber>CgFBQQAAATjkOB1sAAAAAg==</serialNumber>
     </certItem>
     <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
       <serialNumber>QOu0a5Z9rCkw6Nk7Rg1/AQ==</serialNumber>
     </certItem>
+    <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+      <serialNumber>ByfNbw==</serialNumber>
+    </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
       <serialNumber>ElBUYv/f+6+gnbAJ23qnAA==</serialNumber>
     </certItem>
     <certItem issuerName="MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=">
       <serialNumber>a9rf7/BmG9JkKvRuy7J5QA==</serialNumber>
     </certItem>
     <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>QM1zZ4GZ4gfwpQtUYye3Ne0=</serialNumber>
@@ -4421,16 +4441,19 @@
       <serialNumber>JD1wxDd8IgmiqX7MyPPg1g==</serialNumber>
     </certItem>
     <certItem issuerName="MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==">
       <serialNumber>BAAAAAABQaHhNLo=</serialNumber>
     </certItem>
     <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
       <serialNumber>AwBGo0Zmp6KRryAguuMvXATI</serialNumber>
     </certItem>
+    <certItem issuerName="MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+      <serialNumber>AJk3QFH13eHUHHVnsvwS0Vo=</serialNumber>
+    </certItem>
     <certItem issuerName="MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=">
       <serialNumber>U3t2Vk8pfxTcaUPpIq0seQ==</serialNumber>
     </certItem>
     <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
       <serialNumber>bx/XHJqcwxDOptxJ2lh5vw==</serialNumber>
     </certItem>
     <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
       <serialNumber>LU4d0t7PAsZNgJGZcb+o/w==</serialNumber>
@@ -4592,16 +4615,19 @@
       <serialNumber>Fw==</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>Gz4uHrL2usrTZrPCHeuF5g==</serialNumber>
     </certItem>
     <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
       <serialNumber>UWMOvf4tj/x5cQN2PXVSww==</serialNumber>
     </certItem>
+    <certItem issuerName="MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+      <serialNumber>NlLRZJFLco/An3cLAGjGgQ==</serialNumber>
+    </certItem>
     <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>e/fIfg2Dj2tkYIWVu2r82Cc=</serialNumber>
     </certItem>
     <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
       <serialNumber>UMUwXwT1Z4juyQ/CNTf4mw==</serialNumber>
     </certItem>
     <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
       <serialNumber>d8AtKymQwkOPDBj+hjPzFg==</serialNumber>
@@ -4748,16 +4774,19 @@
       <serialNumber>ESDu2nhlLPzfx+LYgjlYFP/k</serialNumber>
     </certItem>
     <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
       <serialNumber>buROL/l2GuXISv+/JVLkdA==</serialNumber>
     </certItem>
     <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
       <serialNumber>CqZgEvHAsnzkT//QV9KjXw==</serialNumber>
     </certItem>
+    <certItem issuerName="MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExGjAYBgNVBAoTEUludGVsIENvcnBvcmF0aW9uMSUwIwYDVQQDExxJbnRlbCBFeHRlcm5hbCBJc3N1aW5nIENBIDZC">
+      <serialNumber>HwAABsvzDP+DIzUG6QAAAAAGyw==</serialNumber>
+    </certItem>
     <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
       <serialNumber>HZyLf+K70FKc+jomm8DiDw==</serialNumber>
     </certItem>
     <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
       <serialNumber>Ew==</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>IARKrBjlKQLyVGA4X52L7w==</serialNumber>
@@ -4845,16 +4874,19 @@
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>CGo/+42e75JBJ2JcOEaMFw==</serialNumber>
     </certItem>
     <certItem issuerName="MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=">
       <serialNumber>Eg==</serialNumber>
     </certItem>
     <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>UDE/uwr4z5V8eZI4+1gkAw==</serialNumber>
+    </certItem>
+    <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
       <serialNumber>ezdAeCxKH7BFs7vn3byYaw==</serialNumber>
     </certItem>
     <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
       <serialNumber>AZ0=</serialNumber>
     </certItem>
     <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
       <serialNumber>CcHC1w==</serialNumber>
     </certItem>
@@ -4988,16 +5020,19 @@
       <serialNumber>TA6BjA==</serialNumber>
     </certItem>
     <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
       <serialNumber>BAAAAAABMYnGRuw=</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>a2GKnRbYMZ0oZkRzJE8NIw==</serialNumber>
     </certItem>
+    <certItem issuerName="MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290">
+      <serialNumber>Eg==</serialNumber>
+    </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
       <serialNumber>G8sz+bm+vQjTpQNBh5CfMg==</serialNumber>
     </certItem>
     <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=">
       <serialNumber>EM8bDLBnnoYe4LnWpLIhS4esr3I=</serialNumber>
     </certItem>
     <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
       <serialNumber>Cfk9oA==</serialNumber>
@@ -5045,10 +5080,22 @@
       <serialNumber>ESByYNtAIfizf2L3NMzCH8zZ</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>QspbHxzWb41SX9TUhF1N1A==</serialNumber>
     </certItem>
     <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
       <serialNumber>KNhgX8XuJduYciIyatpOQg==</serialNumber>
     </certItem>
+    <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+      <serialNumber>AImQERVYPoeb</serialNumber>
+    </certItem>
+    <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy">
+      <serialNumber>GpO48aJ8GngtwECqZhm/xA==</serialNumber>
+    </certItem>
+    <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
+      <serialNumber>CdYL9vSQCEKzBwjO10ud2w==</serialNumber>
+    </certItem>
+    <certItem issuerName="MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=">
+      <serialNumber>a12RvBNhznU=</serialNumber>
+    </certItem>
   </certItems>
 </blocklist>
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -38,19 +38,16 @@ GeckoProgram(CONFIG['MOZ_APP_NAME'])
 
 SOURCES += [
     'nsBrowserApp.cpp',
 ]
 
 # Neither channel-prefs.js nor firefox.exe want to end up in dist/bin/browser.
 DIST_SUBDIR = ""
 
-# channel-prefs.js is handled separate from other prefs due to bug 756325
-JS_PREFERENCE_PP_FILES += ['profile/channel-prefs.js']
-
 LOCAL_INCLUDES += [
     '!/build',
     '/toolkit/xre',
     '/xpcom/base',
     '/xpcom/build',
 ]
 
 if CONFIG['LIBFUZZER']:
--- a/browser/app/profile/channel-prefs.js
+++ b/browser/app/profile/channel-prefs.js
@@ -1,6 +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/. */
 
-#filter substitution
 pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -126,17 +126,21 @@ pref("app.update.altwindowtype", "Browse
 pref("app.update.log", false);
 
 // The number of general background check failures to allow before notifying the
 // user of the failure. User initiated update checks always notify the user of
 // the failure.
 pref("app.update.backgroundMaxErrors", 10);
 
 // Whether or not app updates are enabled
+#ifdef MOZ_UPDATER
 pref("app.update.enabled", true);
+#else
+pref("app.update.enabled", false);
+#endif
 
 // Whether or not to use the doorhanger application update UI.
 pref("app.update.doorhanger", true);
 
 // Ids of the links to the "What's new" update documentation
 pref("app.update.link.updateAvailableWhatsNew", "update-available-whats-new");
 pref("app.update.link.updateManualWhatsNew", "update-manual-whats-new");
 
@@ -906,19 +910,16 @@ pref("browser.sessionstore.cleanup.forge
 // Maximum number of bytes of DOMSessionStorage data we collect per origin.
 pref("browser.sessionstore.dom_storage_limit", 2048);
 // Amount of failed SessionFile writes until we restart the worker.
 pref("browser.sessionstore.max_write_failures", 5);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
-// Whether useAsyncTransactions is enabled or not.
-pref("browser.places.useAsyncTransactions", true);
-
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
 
 // the (maximum) number of the recent visits to sample
 // when calculating frecency
 pref("places.frecency.numVisits", 10);
 
 // buckets (in days) for frecency calculation
@@ -1087,28 +1088,29 @@ pref("security.sandbox.content.level", 3
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is introduced as part of bug 742434, the naming is inspired from
 // its Windows/Mac counterpart, but on Linux it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "content sandbox using seccomp-bpf when available"
 // 2 -> "seccomp-bpf + write file broker"
 // 3 -> "seccomp-bpf + read/write file brokering"
+// 4 -> all of the above + network/socket restrictions
 // Content sandboxing on Linux is currently in the stage of
 // 'just getting it enabled', which includes a very permissive whitelist. We
 // enable seccomp-bpf on nightly to see if everything is running, or if we need
 // to whitelist more system calls.
 //
 // So the purpose of this setting is to allow nightly users to disable the
 // sandbox while we fix their problems. This way, they won't have to wait for
 // another nightly release which disables seccomp-bpf again.
 //
 // This setting may not be required anymore once we decide to permanently
 // enable the content sandbox.
-pref("security.sandbox.content.level", 3);
+pref("security.sandbox.content.level", 4);
 pref("security.sandbox.content.write_path_whitelist", "");
 pref("security.sandbox.content.read_path_whitelist", "");
 pref("security.sandbox.content.syscall_whitelist", "");
 #endif
 
 #if defined(XP_MACOSX) || defined(XP_WIN)
 #if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // ID (a UUID when set by gecko) that is used to form the name of a
@@ -1177,16 +1179,17 @@ pref("services.sync.prefs.sync.browser.s
 pref("services.sync.prefs.sync.browser.search.update", true);
 pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
 pref("services.sync.prefs.sync.browser.startup.homepage", true);
 pref("services.sync.prefs.sync.browser.startup.page", true);
 pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true);
 pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true);
 pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true);
 pref("services.sync.prefs.sync.browser.urlbar.autocomplete.enabled", true);
+pref("services.sync.prefs.sync.browser.urlbar.matchBuckets", true);
 pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
 pref("services.sync.prefs.sync.browser.urlbar.suggest.bookmark", true);
 pref("services.sync.prefs.sync.browser.urlbar.suggest.history", true);
 pref("services.sync.prefs.sync.browser.urlbar.suggest.history.onlyTyped", true);
 pref("services.sync.prefs.sync.browser.urlbar.suggest.openpage", true);
 pref("services.sync.prefs.sync.browser.urlbar.suggest.searches", true);
 pref("services.sync.prefs.sync.dom.disable_open_during_load", true);
 pref("services.sync.prefs.sync.dom.disable_window_flip", true);
@@ -1215,16 +1218,18 @@ pref("services.sync.prefs.sync.privacy.c
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.offlineApps", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.sessions", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.siteSettings", true);
 pref("services.sync.prefs.sync.privacy.donottrackheader.enabled", true);
 pref("services.sync.prefs.sync.privacy.sanitize.sanitizeOnShutdown", true);
 pref("services.sync.prefs.sync.privacy.trackingprotection.enabled", true);
 pref("services.sync.prefs.sync.privacy.trackingprotection.pbmode.enabled", true);
 pref("services.sync.prefs.sync.privacy.resistFingerprinting", true);
+pref("services.sync.prefs.sync.privacy.reduceTimerPrecision", true);
+pref("services.sync.prefs.sync.privacy.resistFingerprinting.reduceTimerPrecision.microseconds", true);
 pref("services.sync.prefs.sync.security.OCSP.enabled", true);
 pref("services.sync.prefs.sync.security.OCSP.require", true);
 pref("services.sync.prefs.sync.security.default_personal_cert", true);
 pref("services.sync.prefs.sync.security.tls.version.min", true);
 pref("services.sync.prefs.sync.security.tls.version.max", true);
 pref("services.sync.prefs.sync.services.sync.syncedTabs.showRemoteIcons", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
@@ -1330,16 +1335,20 @@ pref("security.insecure_password.ui.enab
 
 // Show in-content login form warning UI for insecure login fields
 pref("security.insecure_field_warning.contextual.enabled", true);
 
 // Show degraded UI for http pages; disabled for now
 pref("security.insecure_connection_icon.enabled", false);
 pref("security.insecure_connection_icon.pbmode.enabled", false);
 
+// Show "Not Secure" text for http pages; disabled for now
+pref("security.insecure_connection_text.enabled", false);
+pref("security.insecure_connection_text.pbmode.enabled", false);
+
 // 1 = allow MITM for certificate pinning checks.
 pref("security.cert_pinning.enforcement_level", 1);
 
 
 // Override the Gecko-default value of false for Firefox.
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
@@ -1698,17 +1707,17 @@ pref("browser.crashReports.unsubmittedCh
 // Preferences for the form autofill system extension
 // The truthy values of "extensions.formautofill.available" are "on" and "detect",
 // any other value means autofill isn't available.
 // "detect" means it's enabled if conditions defined in the extension are met.
 #ifdef NIGHTLY_BUILD
 pref("extensions.formautofill.available", "on");
 pref("extensions.formautofill.creditCards.available", true);
 #elif MOZ_UPDATE_CHANNEL == release
-pref("extensions.formautofill.available", "staged-rollout");
+pref("extensions.formautofill.available", "detect");
 pref("extensions.formautofill.creditCards.available", false);
 #else
 pref("extensions.formautofill.available", "detect");
 pref("extensions.formautofill.creditCards.available", true);
 #endif
 pref("extensions.formautofill.addresses.enabled", true);
 pref("extensions.formautofill.creditCards.enabled", true);
 // Pref for shield/heartbeat to recognize users who have used Credit Card
@@ -1719,17 +1728,23 @@ pref("extensions.formautofill.creditCard
 // 2: saw the doorhanger
 // 3: submitted an autofill'ed credit card form
 pref("extensions.formautofill.creditCards.used", 0);
 pref("extensions.formautofill.firstTimeUse", true);
 pref("extensions.formautofill.heuristics.enabled", true);
 pref("extensions.formautofill.section.enabled", true);
 pref("extensions.formautofill.loglevel", "Warn");
 // Comma separated list of countries Form Autofill supports
+#ifdef MOZ_UPDATE_CHANNEL == release
+pref("extensions.formautofill.supportedCountries", "US");
+pref("extensions.formautofill.supportRTL", false);
+#else
 pref("extensions.formautofill.supportedCountries", "US,CA,DE");
+pref("extensions.formautofill.supportRTL", true);
+#endif
 
 // Whether or not to restore a session with lazy-browser tabs.
 pref("browser.sessionstore.restore_tabs_lazily", true);
 
 pref("browser.suppress_first_window_animation", true);
 
 // Preferences for Photon onboarding system extension
 pref("browser.onboarding.enabled", true);
--- a/browser/base/content/browser-ctrlTab.js
+++ b/browser/base/content/browser-ctrlTab.js
@@ -283,16 +283,20 @@ var ctrlTab = {
 
     if (this._selectedIndex == -1) {
       // Focus is already in the panel.
       this.previews[selectedIndex].focus();
     } else {
       this._selectedIndex = selectedIndex;
     }
 
+    if (this.previews[selectedIndex]._tab) {
+      gBrowser.warmupTab(this.previews[selectedIndex]._tab);
+    }
+
     if (this._timer) {
       clearTimeout(this._timer);
       this._timer = null;
       this._openPanel();
     }
   },
 
   _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
@@ -343,16 +347,17 @@ var ctrlTab = {
   open: function ctrlTab_open() {
     if (this.isOpen)
       return;
 
     document.addEventListener("keyup", this, true);
 
     this.updatePreviews();
     this._selectedIndex = 1;
+    gBrowser.warmupTab(this.selected._tab);
 
     // Add a slight delay before showing the UI, so that a quick
     // "ctrl-tab" keypress just flips back to the MRU tab.
     this._timer = setTimeout(function(self) {
       self._timer = null;
       self._openPanel();
     }, 200, this);
   },
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -4,18 +4,16 @@
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
 %browserDTD;
 <!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
 %baseMenuDTD;
 <!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
 %charsetDTD;
 <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
 %textcontextDTD;
-<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
-  %customizeToolbarDTD;
 <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
 %placesDTD;
 <!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
 %safebrowsingDTD;
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 %aboutHomeDTD;
 <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
 %syncBrandDTD;
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -29,21 +29,29 @@ var gEMEHandler = {
       return false;
     }
     if (keySystem == "com.widevine.alpha" &&
         Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
       return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
     }
     return true;
   },
-  getLearnMoreLink(msgId) {
-    let text = gNavigatorBundle.getString("emeNotifications." + msgId + ".learnMoreLabel");
+  getEMEDisabledFragment(msgId) {
+    let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message");
+    let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel");
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
-    return "<label class='text-link' href='" + baseURL + "drm-content'>" +
-           text + "</label>";
+    let link = document.createElement("label");
+    link.className = "text-link";
+    link.setAttribute("href", baseURL + "drm-content");
+    link.textContent = text;
+    return BrowserUtils.getLocalizedFragment(document, mainMessage, link);
+  },
+  getMessageWithBrandName(notificationId) {
+    let msgId = "emeNotifications." + notificationId + ".message";
+    return gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
   },
   receiveMessage({target: browser, data: data}) {
     let parsedData;
     try {
       parsedData = JSON.parse(data);
     } catch (ex) {
       Cu.reportError("Malformed EME video message with data: " + data);
       return;
@@ -51,91 +59,75 @@ var gEMEHandler = {
     let {status: status, keySystem: keySystem} = parsedData;
     // Don't need to show if disabled or keysystem not visible.
     if (!this.uiEnabled || !this.isKeySystemVisible(keySystem)) {
       return;
     }
 
     let notificationId;
     let buttonCallback;
-    let params = [];
+    // Notification message can be either a string or a DOM fragment.
+    let notificationMessage;
     switch (status) {
       case "available":
       case "cdm-created":
         // Only show the chain icon for proprietary CDMs. Clearkey is not one.
         if (keySystem != "org.w3.clearkey") {
           this.showPopupNotificationForSuccess(browser, keySystem);
         }
         // ... and bail!
         return;
 
       case "api-disabled":
       case "cdm-disabled":
         notificationId = "drmContentDisabled";
         buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem);
-        params = [this.getLearnMoreLink(notificationId)];
+        notificationMessage = this.getEMEDisabledFragment();
         break;
 
       case "cdm-insufficient-version":
         notificationId = "drmContentCDMInsufficientVersion";
-        params = [this._brandShortName];
+        notificationMessage = this.getMessageWithBrandName(notificationId);
         break;
 
       case "cdm-not-installed":
         notificationId = "drmContentCDMInstalling";
-        params = [this._brandShortName];
+        notificationMessage = this.getMessageWithBrandName(notificationId);
         break;
 
       case "cdm-not-supported":
         // Not to pop up user-level notification because they cannot do anything
         // about it.
         return;
       default:
         Cu.reportError(new Error("Unknown message ('" + status + "') dealing with EME key request: " + data));
         return;
     }
 
-    this.showNotificationBar(browser, notificationId, keySystem, params, buttonCallback);
-  },
-  showNotificationBar(browser, notificationId, keySystem, labelParams, callback) {
+    // Now actually create the notification
+
     let box = gBrowser.getNotificationBox(browser);
     if (box.getNotificationWithValue(notificationId)) {
       return;
     }
 
-    let msgPrefix = "emeNotifications." + notificationId + ".";
-    let msgId = msgPrefix + "message";
-
-    let message = labelParams.length ?
-                  gNavigatorBundle.getFormattedString(msgId, labelParams) :
-                  gNavigatorBundle.getString(msgId);
-
     let buttons = [];
-    if (callback) {
+    if (buttonCallback) {
+      let msgPrefix = "emeNotifications." + notificationId + ".";
       let btnLabelId = msgPrefix + "button.label";
       let btnAccessKeyId = msgPrefix + "button.accesskey";
       buttons.push({
         label: gNavigatorBundle.getString(btnLabelId),
         accessKey: gNavigatorBundle.getString(btnAccessKeyId),
-        callback
+        callback: buttonCallback,
       });
     }
 
     let iconURL = "chrome://browser/skin/drm-icon.svg";
-
-    // Do a little dance to get rich content into the notification:
-    let fragment = document.createDocumentFragment();
-    let descriptionContainer = document.createElement("description");
-    // eslint-disable-next-line no-unsanitized/property
-    descriptionContainer.innerHTML = message;
-    while (descriptionContainer.childNodes.length) {
-      fragment.appendChild(descriptionContainer.childNodes[0]);
-    }
-
-    box.appendNotification(fragment, notificationId, iconURL, box.PRIORITY_WARNING_MEDIUM,
+    box.appendNotification(notificationMessage, notificationId, iconURL, box.PRIORITY_WARNING_MEDIUM,
                            buttons);
   },
   showPopupNotificationForSuccess(browser, keySystem) {
     // We're playing EME content! Remove any "we can't play because..." messages.
     var box = gBrowser.getNotificationBox(browser);
     ["drmContentDisabled",
      "drmContentCDMInstalling"
      ].forEach(function(value) {
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -6,18 +6,16 @@
 /* eslint-env mozilla/browser-window */
 
 XPCOMUtils.defineLazyScriptGetter(this, ["PlacesToolbar", "PlacesMenu",
                                          "PlacesPanelview", "PlacesPanelMenuView"],
                                   "chrome://browser/content/places/browserPlacesViews.js");
 
 var StarUI = {
   _itemGuids: null,
-  // TODO (bug 1131491): _itemIdsMap is only used for the old transactions manager.
-  _itemIdsMap: null,
   _batching: false,
   _isNewBookmark: false,
   _isComposing: false,
   _autoCloseTimer: 0,
   // The autoclose timer is diasbled if the user interacts with the
   // popup, such as making a change through typing or clicking on
   // the popup.
   _autoCloseTimerEnabled: true,
@@ -91,46 +89,30 @@ var StarUI = {
 
           if (this._anchorToolbarButton) {
             this._anchorToolbarButton.removeAttribute("open");
             this._anchorToolbarButton = null;
           }
           this._restoreCommandsState();
           let removeBookmarksOnPopupHidden = this._removeBookmarksOnPopupHidden;
           this._removeBookmarksOnPopupHidden = false;
-          let idsForRemoval = this._itemIdsMap;
           let guidsForRemoval = this._itemGuids;
           this._itemGuids = null;
-          this._itemIdsMap = null;
 
           if (this._batching) {
             this.endBatch();
           }
 
           if (removeBookmarksOnPopupHidden && guidsForRemoval) {
             if (this._isNewBookmark) {
-              if (!PlacesUIUtils.useAsyncTransactions) {
-                PlacesUtils.transactionManager.undoTransaction();
-                break;
-              }
               PlacesTransactions.undo().catch(Cu.reportError);
               break;
             }
             // Remove all bookmarks for the bookmark's url, this also removes
             // the tags for the url.
-            if (!PlacesUIUtils.useAsyncTransactions) {
-              if (idsForRemoval) {
-                for (let itemId of idsForRemoval.values()) {
-                  let txn = new PlacesRemoveItemTransaction(itemId);
-                  PlacesUtils.transactionManager.doTransaction(txn);
-                }
-              }
-              break;
-            }
-
             PlacesTransactions.Remove(guidsForRemoval)
                               .transact().catch(Cu.reportError);
           } else if (this._isNewBookmark) {
             LibraryUI.triggerLibraryAnimation("bookmark");
           }
         }
         break;
       }
@@ -225,17 +207,16 @@ var StarUI = {
     // Slow double-clicks (not true double-clicks) shouldn't
     // cause the panel to flicker.
     if (this.panel.state == "showing" ||
         this.panel.state == "open") {
       return;
     }
 
     this._isNewBookmark = aIsNewBookmark;
-    this._itemIdsMap = null;
     this._itemGuids = null;
     // TODO (bug 1131491): Deprecate this once async transactions are enabled
     // and the legacy transactions code is gone.
     if (typeof(aNode) == "number") {
       let itemId = aNode;
       let guid = await PlacesUtils.promiseItemGuid(itemId);
       aNode = await PlacesUIUtils.fetchNodeLike(guid);
     }
@@ -286,19 +267,16 @@ var StarUI = {
 
     // The label of the remove button differs if the URI is bookmarked
     // multiple times.
     this._itemGuids = [];
 
     await PlacesUtils.bookmarks.fetch({url: aUrl},
       bookmark => this._itemGuids.push(bookmark.guid));
 
-    if (!PlacesUIUtils.useAsyncTransactions) {
-      this._itemIdsMap = await PlacesUtils.promiseManyItemIds(this._itemGuids);
-    }
     let forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
     let bookmarksCount = this._itemGuids.length;
     let label = PluralForm.get(bookmarksCount, forms)
                           .replace("#1", bookmarksCount);
     this._element("editBookmarkPanelRemoveButton").label = label;
 
     this.beginBatch();
 
@@ -360,37 +338,29 @@ var StarUI = {
   // editBookmarkOverlay so that all of the actions done in the panel
   // are treated by PlacesTransactions as a single batch.  To do so,
   // we start a PlacesTransactions batch when the star UI panel is shown, and
   // we keep the batch ongoing until the panel is hidden.
   _batchBlockingDeferred: null,
   beginBatch() {
     if (this._batching)
       return;
-    if (PlacesUIUtils.useAsyncTransactions) {
-      this._batchBlockingDeferred = PromiseUtils.defer();
-      PlacesTransactions.batch(async () => {
-        await this._batchBlockingDeferred.promise;
-      });
-    } else {
-      PlacesUtils.transactionManager.beginBatch(null);
-    }
+    this._batchBlockingDeferred = PromiseUtils.defer();
+    PlacesTransactions.batch(async () => {
+      await this._batchBlockingDeferred.promise;
+    });
     this._batching = true;
   },
 
   endBatch() {
     if (!this._batching)
       return;
 
-    if (PlacesUIUtils.useAsyncTransactions) {
-      this._batchBlockingDeferred.resolve();
-      this._batchBlockingDeferred = null;
-    } else {
-      PlacesUtils.transactionManager.endBatch(false);
-    }
+    this._batchBlockingDeferred.resolve();
+    this._batchBlockingDeferred = null;
     this._batching = false;
   }
 };
 
 // Checks if an element is visible without flushing layout changes.
 function isVisible(element) {
   let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
@@ -408,85 +378,16 @@ var PlacesCommandHook = {
    *        whether or not to show the edit-bookmark UI for the bookmark item
    * @param [optional] aUrl
    *        Option to provide a URL to bookmark rather than the current page
    * @param [optional] aTitle
    *        Option to provide a title for a bookmark to use rather than the
    *        getting the current page's title
    */
   async bookmarkPage(aBrowser, aShowEditUI, aUrl = null, aTitle = null) {
-    if (PlacesUIUtils.useAsyncTransactions) {
-      await this._bookmarkPagePT(aBrowser, aShowEditUI, aUrl, aTitle);
-      return;
-    }
-
-    // If aUrl is provided, we want to bookmark that url rather than the
-    // the current page
-    var uri = aUrl ? Services.io.newURI(aUrl) : aBrowser.currentURI;
-    var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
-    let isNewBookmark = itemId == -1;
-    if (isNewBookmark) {
-      // Bug 1148838 - Make this code work for full page plugins.
-      var title;
-      var description;
-      var charset;
-
-      let docInfo = aUrl ? {} : await this._getPageDetails(aBrowser);
-
-      try {
-        title = aTitle ||
-                (docInfo.isErrorPage ? PlacesUtils.history.getPageTitle(uri)
-                                     : aBrowser.contentTitle) ||
-                uri.displaySpec;
-        description = docInfo.description;
-        charset = aUrl ? null : aBrowser.characterSet;
-      } catch (e) { }
-
-      if (aShowEditUI) {
-        // If we bookmark the page here but open right into a cancelable
-        // state (i.e. new bookmark in Library), start batching here so
-        // all of the actions can be undone in a single undo step.
-        StarUI.beginBatch();
-      }
-
-      var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
-      var txn = new PlacesCreateBookmarkTransaction(uri,
-                                                    PlacesUtils.unfiledBookmarksFolderId,
-                                                    PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                                    title, null, [descAnno]);
-      PlacesUtils.transactionManager.doTransaction(txn);
-      itemId = txn.item.id;
-      // Set the character-set.
-      if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
-        PlacesUtils.setCharsetForURI(uri, charset);
-    }
-
-    // Revert the contents of the location bar
-    gURLBar.handleRevert();
-
-    // If it was not requested to open directly in "edit" mode, we are done.
-    if (!aShowEditUI)
-      return;
-
-    let anchor = BookmarkingUI.anchor;
-    if (anchor) {
-      await StarUI.showEditBookmarkPopup(itemId, anchor,
-                                         "bottomcenter topright", isNewBookmark,
-                                         uri);
-      return;
-    }
-
-    // Fall back to showing the panel over the content area.
-    await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap",
-                                       isNewBookmark, uri);
-  },
-
-  // TODO: Replace bookmarkPage code with this function once legacy
-  // transactions are removed.
-  async _bookmarkPagePT(aBrowser, aShowEditUI, aUrl, aTitle) {
     // If aUrl is provided, we want to bookmark that url rather than the
     // the current page
     let url = aUrl ? new URL(aUrl) : new URL(aBrowser.currentURI.spec);
     let info = await PlacesUtils.bookmarks.fetch({ url });
     let isNewBookmark = !info;
     if (!info) {
       let parentGuid = PlacesUtils.bookmarks.unfiledGuid;
       info = { url, parentGuid };
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -160,17 +160,17 @@
 
     <!-- Sync broadcasters -->
     <!-- A broadcaster of a number of attributes suitable for "sync now" UI -
         A 'syncstatus' attribute is set while actively syncing, and the label
         attribute which changes from "sync now" to "syncing" etc. -->
     <broadcaster id="sync-status"/>
     <!-- broadcasters of the "hidden" attribute to reflect setup state for
          menus -->
-    <broadcaster id="sync-setup-state"/>
+    <broadcaster id="sync-setup-state" hidden="true"/>
     <broadcaster id="sync-unverified-state" hidden="true"/>
     <broadcaster id="sync-syncnow-state" hidden="true"/>
     <broadcaster id="sync-reauth-state" hidden="true"/>
     <broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
                  type="checkbox" group="sidebar"
                  sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
                  oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
     <broadcaster id="workOfflineMenuitemState"/>
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -117,18 +117,22 @@ var gSync = {
     for (let topic of this._obs) {
       Services.obs.addObserver(this, topic, true);
     }
 
     this._generateNodeGetters();
     this._definePrefGetters();
 
     // initial label for the sync buttons.
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+    let statusBroadcaster = document.getElementById("sync-status");
+    statusBroadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+    // We start with every broadcasters hidden, so that we don't need to init
+    // the sync UI on windows like pageInfo.xul (see bug 1384856).
+    let setupBroadcaster = document.getElementById("sync-setup-state");
+    setupBroadcaster.hidden = false;
 
     this._maybeUpdateUIState();
 
     EnsureFxAccountsWebChannel();
 
     this._initialized = true;
   },
 
@@ -171,16 +175,21 @@ var gSync = {
   updateAllUI(state) {
     this.updatePanelPopup(state);
     this.updateStateBroadcasters(state);
     this.updateSyncButtonsTooltip(state);
     this.updateSyncStatus(state);
   },
 
   updatePanelPopup(state) {
+    // Some windows (e.g. places.xul) won't contain the panel UI, so we can
+    // abort immediately for those (bug 1384856).
+    if (!this.appMenuContainer) {
+      return;
+    }
     let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
     // The localization string is for the signed in text, but it's the default text as well
     let defaultTooltiptext = this.appMenuStatus.getAttribute("signedinTooltiptext");
 
     const status = state.status;
     // Reset the status bar to its original state.
     this.appMenuLabel.setAttribute("label", defaultLabel);
     this.appMenuStatus.setAttribute("tooltiptext", defaultTooltiptext);
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -263,20 +263,16 @@ var TabsInTitlebar = {
       // Reset the margins and padding that might have been modified:
       titlebarContent.style.marginTop = "";
       titlebarContent.style.marginBottom = "";
       titlebar.style.marginBottom = "";
       menubar.style.paddingBottom = "";
     }
 
     ToolbarIconColor.inferFromText("tabsintitlebar", TabsInTitlebar.enabled);
-
-    if (document.documentElement.hasAttribute("customizing")) {
-      gCustomizeMode.updateLWTStyling();
-    }
   },
 
   _sizePlaceholder(type, width) {
     Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"),
                   function(node) { node.width = width; });
   },
 
   uninit() {
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -357,17 +357,17 @@ toolbarpaletteitem {
   }
 
   .webextension-browser-action[cui-areatype="menu-panel"]:-moz-lwtheme-darktext,
   toolbarpaletteitem[place="palette"] > .webextension-browser-action:-moz-lwtheme-darktext {
     list-style-image: var(--webextension-menupanel-image-dark, inherit);
   }
 
   .webextension-menuitem {
-    list-style-image: var(--webextension-menuitem-image, inherit);
+    list-style-image: var(--webextension-menuitem-image, inherit) !important;
   }
 }
 
 @media (min-resolution: 1.1dppx) {
   .webextension-browser-action {
     list-style-image: var(--webextension-toolbar-image-2x, inherit);
   }
 
@@ -389,17 +389,17 @@ toolbarpaletteitem {
   }
 
   .webextension-browser-action[cui-areatype="menu-panel"]:-moz-lwtheme-darktext,
   toolbarpaletteitem[place="palette"] > .webextension-browser-action:-moz-lwtheme-darktext {
     list-style-image: var(--webextension-menupanel-image-2x-dark, inherit);
   }
 
   .webextension-menuitem {
-    list-style-image: var(--webextension-menuitem-image-2x, inherit);
+    list-style-image: var(--webextension-menuitem-image-2x, inherit) !important;
   }
 }
 
 toolbarbutton.webextension-menuitem > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2877,18 +2877,16 @@ function UpdatePopupNotificationsVisibil
 function PageProxyClickHandler(aEvent) {
   if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
 // Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED   = 2;
 const TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED = 3;
-const TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND    = 4;
-const TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND      = 5;
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
 /**
  * Handle command events bubbling up from error page content
  * or from about:newtab or from remote error pages that invoke
  * us via async messaging.
  */
@@ -3144,34 +3142,17 @@ var BrowserOnClick = {
       // There is no button for reporting errors since Google doesn't currently
       // provide a URL endpoint for these reports.
     } else if (reason === "harmful") {
       title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite");
       // There is no button for reporting errors since Google doesn't currently
       // provide a URL endpoint for these reports.
     }
 
-    let notificationBox = gBrowser.getNotificationBox();
-    let value = "blocked-badware-page";
-
-    let previousNotification = notificationBox.getNotificationWithValue(value);
-    if (previousNotification) {
-      notificationBox.removeNotification(previousNotification);
-    }
-
-    let notification = notificationBox.appendNotification(
-      title,
-      value,
-      "chrome://global/skin/icons/blacklist_favicon.png",
-      notificationBox.PRIORITY_CRITICAL_HIGH,
-      buttons
-    );
-    // Persist the notification until the user removes so it
-    // doesn't get removed on redirects.
-    notification.persistence = -1;
+    SafeBrowsingNotificationBox.show(title, buttons);
   },
 };
 
 /**
  * Re-direct the browser to a known-safe page.  This function is
  * used when, for example, the user browses to a known malware page
  * and is presented with about:blocked.  The "Get me out of here!"
  * button should take the user to the default start page so that even
@@ -4583,16 +4564,18 @@ var XULBrowserWindow = {
       URLBarSetURI(aLocationURI);
 
       BookmarkingUI.onLocationChange();
 
       gIdentityHandler.onLocationChange();
 
       BrowserPageActions.onLocationChange();
 
+      SafeBrowsingNotificationBox.onLocationChange(aLocationURI);
+
       gTabletModePageCounter.inc();
 
       // Utility functions for disabling find
       var shouldDisableFind = function(aDocument) {
         let docElt = aDocument.documentElement;
         return docElt && docElt.getAttribute("disablefastfind") == "true";
       };
 
@@ -5314,40 +5297,37 @@ nsBrowserAccess.prototype = {
     return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
   },
 
   canClose() {
     return CanCloseWindow();
   },
 };
 
-function getTogglableToolbars() {
-  let toolbarNodes = Array.slice(gNavToolbox.childNodes);
-  toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
-  toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
-  return toolbarNodes;
-}
-
 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
   var popup = aEvent.target;
   if (popup != aEvent.currentTarget)
     return;
 
   // Empty the menu
   for (var i = popup.childNodes.length - 1; i >= 0; --i) {
     var deadItem = popup.childNodes[i];
     if (deadItem.hasAttribute("toolbarId"))
       popup.removeChild(deadItem);
   }
 
   var firstMenuItem = aInsertPoint || popup.firstChild;
 
-  let toolbarNodes = getTogglableToolbars();
+  let toolbarNodes = gNavToolbox.childNodes;
 
   for (let toolbar of toolbarNodes) {
+    if (!toolbar.hasAttribute("toolbarname")) {
+      continue;
+    }
+
     let menuItem = document.createElement("menuitem");
     let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
                           "autohide" : "collapsed";
     menuItem.setAttribute("id", "toggle_" + toolbar.id);
     menuItem.setAttribute("toolbarId", toolbar.id);
     menuItem.setAttribute("type", "checkbox");
     menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
     menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
@@ -6606,17 +6586,23 @@ var CanvasPermissionPromptHelper = {
     }
 
     let uri = Services.io.newURI(aData);
     if (gBrowser.selectedBrowser !== browser) {
       // Must belong to some other window.
       return;
     }
 
-    let message = gNavigatorBundle.getFormattedString("canvas.siteprompt", [ uri.asciiHost ]);
+    let message = {};
+    let header = gNavigatorBundle.getFormattedString("canvas.siteprompt", ["<>"], 1);
+
+    header = header.split("<>");
+    message.start = header[0];
+    message.host = uri.asciiHost;
+    message.end = header[1];
 
     function setCanvasPermission(aURI, aPerm, aPersistent) {
       Services.perms.add(aURI, "canvas", aPerm,
                           aPersistent ? Ci.nsIPermissionManager.EXPIRE_NEVER
                                       : Ci.nsIPermissionManager.EXPIRE_SESSION);
     }
 
     let mainAction = {
@@ -7479,21 +7465,21 @@ var gIdentityHandler = {
         tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
                                                       [iData.caOrg]);
         icon_label = iData.subjectOrg;
         if (iData.country)
           icon_country_label = "(" + iData.country + ")";
 
         // If the organization name starts with an RTL character, then
         // swap the positions of the organization and country code labels.
-        // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
+        // The Unicode ranges reflect the definition of the UTF16_CODE_UNIT_IS_BIDI
         // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
         // fixed, this test should be replaced by one adhering to the
         // Unicode Bidirectional Algorithm proper (at the paragraph level).
-        icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
+        icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc\ud802\ud803\ud83a\ud83b]/.test(icon_label) ?
                           "rtl" : "ltr";
       }
     } else if (this._pageExtensionPolicy) {
       this._identityBox.className = "extensionPage";
       let extensionName = this._pageExtensionPolicy.name;
       icon_label = gNavigatorBundle.getFormattedString(
         "identity.extension.label", [extensionName]);
     } else if (this._uriHasHost && this._isSecure) {
@@ -7526,18 +7512,25 @@ var gIdentityHandler = {
         } else {
           this._identityBox.classList.add("weakCipher");
         }
       } else {
         let warnOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_icon.enabled") ||
                              (Services.prefs.getBoolPref("security.insecure_connection_icon.pbmode.enabled") &&
                              PrivateBrowsingUtils.isWindowPrivate(window));
         let className = warnOnInsecure ? "notSecure" : "unknownIdentity";
-
         this._identityBox.className = className;
+
+        let warnTextOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_text.enabled") ||
+                                 (Services.prefs.getBoolPref("security.insecure_connection_text.pbmode.enabled") &&
+                                 PrivateBrowsingUtils.isWindowPrivate(window));
+        if (warnTextOnInsecure) {
+          icon_label = gNavigatorBundle.getString("identity.notSecure.label");
+          this._identityBox.classList.add("notSecureText");
+        }
       }
       if (this._hasInsecureLoginForms) {
         // Insecure login forms can only be present on "unknown identity"
         // pages, either already insecure or with mixed active content loaded.
         this._identityBox.classList.add("insecureLoginForms");
       }
     }
 
@@ -8906,16 +8899,63 @@ var AboutPrivateBrowsingListener = {
     window.messageManager.addMessageListener(
       "AboutPrivateBrowsing:DontShowIntroPanelAgain",
       msg => {
         TrackingProtection.dontShowIntroPanelAgain();
     });
   }
 };
 
+const SafeBrowsingNotificationBox = {
+  _currentURIBaseDomain: null,
+  show(title, buttons) {
+    let uri = gBrowser.currentURI;
+
+    // start tracking host so that we know when we leave the domain
+    this._currentURIBaseDomain = Services.eTLD.getBaseDomain(uri);
+
+    let notificationBox = gBrowser.getNotificationBox();
+    let value = "blocked-badware-page";
+
+    let previousNotification = notificationBox.getNotificationWithValue(value);
+    if (previousNotification) {
+      notificationBox.removeNotification(previousNotification);
+    }
+
+    let notification = notificationBox.appendNotification(
+      title,
+      value,
+      "chrome://global/skin/icons/blacklist_favicon.png",
+      notificationBox.PRIORITY_CRITICAL_HIGH,
+      buttons
+    );
+    // Persist the notification until the user removes so it
+    // doesn't get removed on redirects.
+    notification.persistence = -1;
+  },
+  onLocationChange(aLocationURI) {
+    // take this to represent that you haven't visited a bad place
+    if (!this._currentURIBaseDomain) {
+      return;
+    }
+
+    let newURIBaseDomain = Services.eTLD.getBaseDomain(aLocationURI);
+
+    if (newURIBaseDomain !== this._currentURIBaseDomain) {
+      let notificationBox = gBrowser.getNotificationBox();
+      let notification = notificationBox.getNotificationWithValue("blocked-badware-page");
+      if (notification) {
+        notificationBox.removeNotification(notification, false);
+      }
+
+      this._currentURIBaseDomain = null;
+    }
+  }
+};
+
 function TabModalPromptBox(browser) {
   this._weakBrowserRef = Cu.getWeakReference(browser);
 }
 
 TabModalPromptBox.prototype = {
   _promptCloseCallback(onCloseCallback, principalToAllowFocusFor, allowFocusCheckbox, ...args) {
     if (principalToAllowFocusFor && allowFocusCheckbox &&
         allowFocusCheckbox.checked) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -291,18 +291,18 @@
       <toolbarbutton id="sidebar-switcher-tabs"
                      label="&syncedTabs.sidebar.label;"
                      class="subviewbutton subviewbutton-iconic"
                      observes="viewTabsSidebar"
                      oncommand="SidebarUI.show('viewTabsSidebar');">
         <observes element="viewTabsSidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarseparator/>
-      <vbox id="sidebar-extensions"></vbox>
-      <toolbarseparator/>
+      <!-- Extension toolbarbuttons go here. -->
+      <toolbarseparator id="sidebar-extensions-separator"/>
       <toolbarbutton id="sidebar-reverse-position"
                      class="subviewbutton"
                      oncommand="SidebarUI.reversePosition()"/>
       <toolbarseparator/>
       <toolbarbutton label="&sidebarMenuClose.label;"
                      class="subviewbutton"
                      oncommand="SidebarUI.hide()"/>
     </panel>
@@ -591,23 +591,20 @@
       <hbox class="private-browsing-indicator"/>
       <hbox id="titlebar-fullscreen-button"/>
     </hbox>
 #endif
   </hbox>
 </vbox>
 #endif
 
-<deck flex="1" id="tab-view-deck">
-<vbox flex="1" id="browser-panel">
-
-  <toolbox id="navigator-toolbox" mode="icons">
+  <toolbox id="navigator-toolbox">
     <!-- Menu -->
     <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
-             mode="icons" iconsize="small"
+             mode="icons"
 #ifdef MENUBAR_CAN_AUTOHIDE
              toolbarname="&menubarCmd.label;"
              accesskey="&menubarCmd.accesskey;"
              autohide="true"
 #endif
              context="toolbar-context-menu">
       <toolbaritem id="menubar-items" align="center">
 # The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
@@ -623,17 +620,16 @@
 #endif
 #endif
     </toolbar>
 
     <toolbar id="TabsToolbar"
              fullscreentoolbar="true"
              customizable="true"
              mode="icons"
-             iconsize="small"
              aria-label="&tabsToolbar.label;"
              context="toolbar-context-menu"
              collapsed="true">
 
 #ifdef CAN_DRAW_IN_TITLEBAR
       <hbox class="titlebar-placeholder" type="pre-tabs"
             skipintoolbarset="true"/>
 #endif
@@ -706,17 +702,16 @@
             skipintoolbarset="true"/>
 #endif
 #endif
     </toolbar>
 
     <toolbar id="nav-bar"
              aria-label="&navbarCmd.label;"
              fullscreentoolbar="true" mode="icons" customizable="true"
-             iconsize="small"
              customizationtarget="nav-bar-customization-target"
              overflowable="true"
              overflowbutton="nav-bar-overflow-button"
              overflowtarget="widget-overflow-list"
              overflowpanel="widget-overflow"
              context="toolbar-context-menu">
 
       <hbox id="nav-bar-customization-target" flex="1">
@@ -973,20 +968,18 @@
                        oncommand="BrowserFullScreen();"/>
 
         <toolbarbutton id="close-button"
                        tooltiptext="&fullScreenClose.tooltip;"
                        oncommand="BrowserTryToCloseWindow();"/>
       </hbox>
     </toolbar>
 
-    <toolbarset id="customToolbars" context="toolbar-context-menu"/>
-
     <toolbar id="PersonalToolbar"
-             mode="icons" iconsize="small"
+             mode="icons"
              class="chromeclass-directories"
              context="toolbar-context-menu"
              toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
              collapsed="true"
              customizable="true">
       <toolbaritem id="personal-bookmarks"
                    title="&bookmarksToolbarItem.label;"
                    cui-areatype="toolbar"
@@ -1237,15 +1230,9 @@
       &pointerlockWarning.generic.label;
     </html:div>
   </html:div>
 
   <vbox id="browser-bottombox" layer="true">
     <notificationbox id="global-notificationbox" notificationside="bottom"/>
   </vbox>
 
-</vbox>
-# <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
-#     Introducing the iframe dynamically, as needed, was found to be better than
-#     starting with an empty iframe here in browser.xul from a Ts standpoint.
-</deck>
-
 </window>
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -62,22 +62,16 @@ addEventListener("pageshow", function(ev
 });
 addEventListener("DOMAutoComplete", function(event) {
   LoginManagerContent.onUsernameInput(event);
 });
 addEventListener("blur", function(event) {
   LoginManagerContent.onUsernameInput(event);
 });
 
-// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
-const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
-const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
-const TLS_ERROR_REPORT_TELEMETRY_SUCCESS  = 6;
-const TLS_ERROR_REPORT_TELEMETRY_FAILURE  = 7;
-
 const SEC_ERROR_BASE          = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
 
 const SEC_ERROR_EXPIRED_CERTIFICATE                = SEC_ERROR_BASE + 11;
 const SEC_ERROR_UNKNOWN_ISSUER                     = SEC_ERROR_BASE + 13;
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE         = SEC_ERROR_BASE + 30;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
@@ -404,16 +398,19 @@ var AboutNetAndCertErrorListener = {
         return true;
       }
     }
 
     return false;
   },
 
   onPageLoad(evt) {
+    // Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
+    const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
+
     if (this.isAboutCertError) {
       let originalTarget = evt.originalTarget;
       let ownerDoc = originalTarget.ownerDocument;
       ClickEventHandler.onCertError(originalTarget, ownerDoc);
     }
 
     let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
     content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions", {
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -242,21 +242,22 @@ Site.prototype = {
     } catch (e) {}
   },
 
   /**
    * Handles site click events.
    */
   onClick: function Site_onClick(aEvent) {
     let pinned = this.isPinned();
-    let tileIndex = this.cell.index;
     let {button, target} = aEvent;
+    const isLinkClick = target.classList.contains("newtab-link") ||
+      target.parentElement.classList.contains("newtab-link");
 
-    // Only handle primary clicks for the remaining targets
-    if (button == 0) {
+    // Handle primary click for pin and block
+    if (button == 0 && !isLinkClick) {
       aEvent.preventDefault();
       if (target.classList.contains("newtab-control-block")) {
         this.block();
       }
       else if (pinned && target.classList.contains("newtab-control-pin")) {
         this.unpin();
       }
       else if (!pinned && target.classList.contains("newtab-control-pin")) {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -859,34 +859,39 @@ nsContextMenu.prototype = {
 
   reload(event) {
     BrowserReloadOrDuplicate(event);
   },
 
   // View Partial Source
   viewPartialSource(aContext) {
     let inWindow = !Services.prefs.getBoolPref("view_source.tab");
+    let {browser} = this;
     let openSelectionFn = inWindow ? null : function() {
       let tabBrowser = gBrowser;
       // In the case of popups, we need to find a non-popup browser window.
-      if (!tabBrowser || !window.toolbar.visible) {
+      // We might also not have a tabBrowser reference (if this isn't in a
+      // a tabbrowser scope) or might have a fake/stub tabbrowser reference
+      // (in the sidebar). Deal with those cases:
+      if (!tabBrowser || !tabBrowser.loadOneTab || !window.toolbar.visible) {
         // This returns only non-popup browser windows by default.
         let browserWindow = RecentWindow.getMostRecentBrowserWindow();
         tabBrowser = browserWindow.gBrowser;
       }
+      let relatedToCurrent = gBrowser && gBrowser.selectedBrowser == browser;
       let tab = tabBrowser.loadOneTab("about:blank", {
-        relatedToCurrent: true,
+        relatedToCurrent,
         inBackground: false,
         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
       });
       return tabBrowser.getBrowserForTab(tab);
     };
 
     let target = aContext == "mathml" ? this.target : null;
-    top.gViewSourceUtils.viewPartialSourceInBrowser(gBrowser.selectedBrowser, target, openSelectionFn);
+    top.gViewSourceUtils.viewPartialSourceInBrowser(browser, target, openSelectionFn);
   },
 
   // Open new "view source" window with the frame's URL.
   viewFrameSource() {
     BrowserViewSourceOfDocument({
       browser: this.browser,
       URL: gContextMenuContentData.docLocation,
       outerWindowID: this.frameOuterWindowID,
@@ -1151,17 +1156,17 @@ nsContextMenu.prototype = {
                     loadingPrincipal: this.principal,
                     contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
                     securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
                   });
 
     if (linkDownload)
       channel.contentDispositionFilename = linkDownload;
     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
-      let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
+      let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
       channel.setPrivate(docIsPrivate);
     }
     channel.notificationCallbacks = new callbacks();
 
     let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
 
     if (bypassCache)
       flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -123,17 +123,16 @@ pageInfoTreeView.prototype = {
   isSeparator(index) { return false; },
   isSorted() { return this.sortcol > -1; },
   canDrop(index, orientation) { return false; },
   drop(row, orientation) { return false; },
   getParentIndex(index) { return 0; },
   hasNextSibling(index, after) { return false; },
   getLevel(index) { return 0; },
   getImageSrc(row, column) { },
-  getProgressMode(row, column) { },
   getCellValue(row, column) { },
   toggleOpenState(index) { },
   cycleHeader(col) { },
   selectionChanged() { },
   cycleCell(row, column) { },
   isEditable(row, column) { return false; },
   isSelectable(row, column) { return false; },
   performAction(action) { },
@@ -257,19 +256,16 @@ function getClipboardHelper() {
         return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
     } catch (e) {
         // do nothing, later code will handle the error
         return null;
     }
 }
 const gClipboardHelper = getClipboardHelper();
 
-// Interface for image loading content
-const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
-
 // namespaces, don't need all of these yet...
 const XLinkNS  = "http://www.w3.org/1999/xlink";
 const XULNS    = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const XMLNS    = "http://www.w3.org/XML/1998/namespace";
 const XHTMLNS  = "http://www.w3.org/1999/xhtml";
 const XHTML2NS = "http://www.w3.org/2002/06/xhtml2";
 
 const XHTMLNSre  = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 
 /* import-globals-from pageInfo.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+                                  "resource://gre/modules/PluralForm.jsm");
 
 var security = {
   init(uri, windowInfo) {
     this.uri = uri;
     this.windowInfo = windowInfo;
   },
 
   // Display the server certificate (static)
@@ -224,25 +226,22 @@ function securityOnLoad(uri, windowInfo)
   var noStr = pageInfoBundle.getString("no");
 
   setText("security-privacy-cookies-value",
           hostHasCookies(uri) ? yesStr : noStr);
   setText("security-privacy-passwords-value",
           realmHasPasswords(uri) ? yesStr : noStr);
 
   var visitCount = previousVisitCount(info.hostName);
-  if (visitCount > 1) {
-    setText("security-privacy-history-value",
-            pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
-  } else if (visitCount == 1) {
-    setText("security-privacy-history-value",
-            pageInfoBundle.getString("securityOneVisit"));
-  } else {
-    setText("security-privacy-history-value", noStr);
-  }
+
+  let visitCountStr = visitCount > 0
+    ? PluralForm.get(visitCount, pageInfoBundle.getString("securityVisitsNumber"))
+        .replace("#1", visitCount.toLocaleString())
+    : pageInfoBundle.getString("securityNoVisits");
+  setText("security-privacy-history-value", visitCountStr);
 
   /* Set the Technical Detail section messages */
   const pkiBundle = document.getElementById("pkiBundle");
   var hdr;
   var msg1;
   var msg2;
 
   if (info.isBroken) {
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -597,108 +597,16 @@ addEventListener("unload", () => {
 }, false);
 
 addMessageListener("Browser:AppTab", function(message) {
   if (docShell) {
     docShell.isAppTab = message.data.isAppTab;
   }
 });
 
-let PrerenderContentHandler = {
-  init() {
-    this._pending = [];
-    this._idMonotonic = 0;
-    this._initialized = true;
-    addMessageListener("Prerender:Canceled", this);
-    addMessageListener("Prerender:Swapped", this);
-  },
-
-  get initialized() {
-    return !!this._initialized;
-  },
-
-  receiveMessage(aMessage) {
-    switch (aMessage.name) {
-      case "Prerender:Canceled": {
-        for (let i = 0; i < this._pending.length; ++i) {
-          if (this._pending[i].id === aMessage.data.id) {
-            if (this._pending[i].failure) {
-              this._pending[i].failure.run();
-            }
-            // Remove the item from the array
-            this._pending.splice(i, 1);
-            break;
-          }
-        }
-        break;
-      }
-      case "Prerender:Swapped": {
-        for (let i = 0; i < this._pending.length; ++i) {
-          if (this._pending[i].id === aMessage.data.id) {
-            if (this._pending[i].success) {
-              this._pending[i].success.run();
-            }
-            // Remove the item from the array
-            this._pending.splice(i, 1);
-            break;
-          }
-        }
-        break;
-      }
-    }
-  },
-
-  startPrerenderingDocument(aHref, aReferrer, aTriggeringPrincipal) {
-    // XXX: Make this constant a pref
-    if (this._pending.length >= 2) {
-      return;
-    }
-
-    let id = ++this._idMonotonic;
-    sendAsyncMessage("Prerender:Request", {
-      href: aHref.spec,
-      referrer: aReferrer ? aReferrer.spec : null,
-      id,
-      triggeringPrincipal: Utils.serializePrincipal(aTriggeringPrincipal),
-    });
-
-    this._pending.push({
-      href: aHref,
-      referrer: aReferrer,
-      id,
-      success: null,
-      failure: null,
-    });
-  },
-
-  shouldSwitchToPrerenderedDocument(aHref, aReferrer, aSuccess, aFailure) {
-    // Check if we think there is a prerendering document pending for the given
-    // href and referrer. If we think there is one, we will send a message to
-    // the parent process asking it to do a swap, and hook up the success and
-    // failure listeners.
-    for (let i = 0; i < this._pending.length; ++i) {
-      let p = this._pending[i];
-      if (p.href.equals(aHref) && p.referrer.equals(aReferrer)) {
-        p.success = aSuccess;
-        p.failure = aFailure;
-        sendAsyncMessage("Prerender:Swap", {id: p.id});
-        return true;
-      }
-    }
-
-    return false;
-  }
-};
-
-if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
-  // We only want to initialize the PrerenderContentHandler in the content
-  // process. Outside of the content process, this should be unused.
-  PrerenderContentHandler.init();
-}
-
 var WebBrowserChrome = {
   onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
     return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
   },
 
   // Check whether this URI should load in the current process
   shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
     if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
@@ -712,30 +620,16 @@ var WebBrowserChrome = {
   shouldLoadURIInThisProcess(aURI) {
     return E10SUtils.shouldLoadURIInThisProcess(aURI);
   },
 
   // Try to reload the currently active or currently loading page in a new process.
   reloadInFreshProcess(aDocShell, aURI, aReferrer, aTriggeringPrincipal, aLoadFlags) {
     E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, true, aLoadFlags);
     return true;
-  },
-
-  startPrerenderingDocument(aHref, aReferrer, aTriggeringPrincipal) {
-    if (PrerenderContentHandler.initialized) {
-      PrerenderContentHandler.startPrerenderingDocument(aHref, aReferrer, aTriggeringPrincipal);
-    }
-  },
-
-  shouldSwitchToPrerenderedDocument(aHref, aReferrer, aSuccess, aFailure) {
-    if (PrerenderContentHandler.initialized) {
-      return PrerenderContentHandler.shouldSwitchToPrerenderedDocument(
-        aHref, aReferrer, aSuccess, aFailure);
-    }
-    return false;
   }
 };
 
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
   let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsITabChild);
   tabchild.webBrowserChrome = WebBrowserChrome;
 }
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -5,20 +5,16 @@
 .tabbrowser-tabpanels {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabpanels");
 }
 
 .tabbrowser-arrowscrollbox {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-arrowscrollbox");
 }
 
-.tab-close-button {
-  -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-close-tab-button");
-}
-
 .tab-close-button[pinned],
 #tabbrowser-tabs[closebuttons="activetab"] > .tabbrowser-tab > .tab-stack > .tab-content > .tab-close-button:not([selected="true"]),
 .tab-icon-image:not([src]):not([pinned]):not([crashed])[selected],
 .tab-icon-image:not([src]):not([pinned]):not([crashed]):not([sharing]),
 .tab-icon-image[busy],
 .tab-throbber:not([busy]),
 .tab-throbber-fallback:not([busy]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1569,16 +1569,27 @@
           }
           this._tabAttrModified(tab, ["sharing"]);
 
           if (aBrowser == this.mCurrentBrowser)
             gIdentityHandler.updateSharingIndicator();
         ]]></body>
       </method>
 
+      <method name="getTabSharingState">
+        <parameter name="aTab"/>
+        <body><![CDATA[
+          // Normalize the state object for consumers (ie.extensions).
+          let state = Object.assign({}, aTab._sharingState);
+          // ensure bool if undefined
+          state.camera = !!state.camera;
+          state.microphone = !!state.microphone;
+          return state;
+        ]]></body>
+      </method>
 
       <!-- TODO: remove after 57, once we know add-ons can no longer use it. -->
       <method name="setTabTitleLoading">
         <parameter name="aTab"/>
         <body/>
       </method>
 
       <method name="setInitialTabTitle">
@@ -1727,17 +1738,16 @@
             var aForceNotRemote;
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
             var aOpenerBrowser;
-            var aIsPrerendered;
             var aCreateLazyBrowser;
             var aNextTabParentId;
             var aFocusUrlBar;
             var aName;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
@@ -1755,17 +1765,16 @@
               aForceNotRemote           = params.forceNotRemote;
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
               aOpenerBrowser            = params.openerBrowser;
-              aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aNextTabParentId          = params.nextTabParentId;
               aFocusUrlBar              = params.focusUrlBar;
               aName                     = params.name;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
@@ -1787,17 +1796,16 @@
                                   createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
                                   openerBrowser: aOpenerBrowser,
-                                  isPrerendered: aIsPrerendered,
                                   nextTabParentId: aNextTabParentId,
                                   focusUrlBar: aFocusUrlBar,
                                   name: aName });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
@@ -2196,32 +2204,28 @@
       </method>
 
       <method name="_createBrowser">
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             // Supported parameters:
             // userContextId, remote, remoteType, isPreloadBrowser,
-            // uriIsAboutBlank, sameProcessAsFrameLoader, isPrerendered
+            // uriIsAboutBlank, sameProcessAsFrameLoader
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
             let b = document.createElementNS(NS_XUL, "browser");
             b.permanentKey = {};
             b.setAttribute("type", "content");
             b.setAttribute("message", "true");
             b.setAttribute("messagemanagergroup", "browsers");
             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
 
-            if (aParams.isPrerendered) {
-              b.setAttribute("prerendered", "true");
-            }
-
             if (aParams.userContextId) {
               b.setAttribute("usercontextid", aParams.userContextId);
             }
 
             // remote parameter used by some addons, use default in this case.
             if (aParams.remote && !aParams.remoteType) {
               aParams.remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
             }
@@ -2600,17 +2604,16 @@
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
             var aOpenerBrowser;
-            var aIsPrerendered;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
             var aNextTabParentId;
             var aNoInitialLabel;
             var aFocusUrlBar;
             var aName;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
@@ -2632,17 +2635,16 @@
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aEventDetail              = params.eventDetail;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aOpenerBrowser            = params.openerBrowser;
-              aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
               aNextTabParentId          = params.nextTabParentId;
               aNoInitialLabel           = params.noInitialLabel;
               aFocusUrlBar              = params.focusUrlBar;
               aName                     = params.name;
             }
 
@@ -2690,20 +2692,16 @@
               if (isBlankPageURL(aURI)) {
                 t.setAttribute("label", gTabBrowserBundle.GetStringFromName("tabs.emptyTabTitle"));
               } else {
                 // Set URL as label so that the tab isn't empty initially.
                 this.setInitialTabTitle(t, aURI, { beforeTabOpen: true });
               }
             }
 
-            if (aIsPrerendered) {
-              t.setAttribute("hidden", "true");
-            }
-
             // Related tab inherits current tab's user context unless a different
             // usercontextid is specified
             if (aUserContextId == null && openerTab) {
               aUserContextId = openerTab.getAttribute("usercontextid") || 0;
             }
 
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
@@ -2790,17 +2788,16 @@
 
               if (!b) {
                 // No preloaded browser found, create one.
                 b = this._createBrowser({ remoteType,
                                           uriIsAboutBlank,
                                           userContextId: aUserContextId,
                                           sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                           openerWindow: aOpener,
-                                          isPrerendered: aIsPrerendered,
                                           nextTabParentId: aNextTabParentId,
                                           name: aName });
               }
 
               t.linkedBrowser = b;
 
               if (aFocusUrlBar) {
                 b._urlbarFocused = true;
@@ -3446,55 +3443,64 @@
                                       true /* aCanceledOkay */);
 
             if (aCloseWindow)
               this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
           ]]>
         </body>
       </method>
 
-      <method name="_blurTab">
+      <method name="_findTabToBlurTo">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
-            if (!aTab.selected)
-              return;
+            if (!aTab.selected) {
+              return null;
+            }
 
             if (aTab.owner &&
                 !aTab.owner.hidden &&
                 !aTab.owner.closing &&
                 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
-              this.selectedTab = aTab.owner;
-              return;
+              return aTab.owner;
             }
 
             // Switch to a visible tab unless there aren't any others remaining
             let remainingTabs = this.visibleTabs;
             let numTabs = remainingTabs.length;
             if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
               remainingTabs = Array.filter(this.tabs, function(tab) {
                 return !tab.closing;
               }, this);
             }
 
             // Try to find a remaining tab that comes after the given tab
-            var tab = aTab;
+            let tab = aTab;
             do {
               tab = tab.nextSibling;
             } while (tab && remainingTabs.indexOf(tab) == -1);
 
             if (!tab) {
               tab = aTab;
 
               do {
                 tab = tab.previousSibling;
               } while (tab && remainingTabs.indexOf(tab) == -1);
             }
 
-            this.selectedTab = tab;
+            return tab;
+          ]]>
+        </body>
+      </method>
+
+      <method name="_blurTab">
+        <parameter name="aTab"/>
+        <body>
+          <![CDATA[
+            this.selectedTab = this._findTabToBlurTo(aTab);
           ]]>
         </body>
       </method>
 
       <method name="swapBrowsersAndCloseOther">
         <parameter name="aOurTab"/>
         <parameter name="aOtherTab"/>
         <body>
@@ -3883,17 +3889,17 @@
         </body>
       </method>
 
       <method name="hideTab">
         <parameter name="aTab"/>
         <body>
         <![CDATA[
           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
-              !aTab.closing) {
+              !aTab.closing && !aTab._sharingState) {
             aTab.setAttribute("hidden", "true");
             this._visibleTabs = null; // invalidate cache
 
             this.tabContainer._updateCloseButtons();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
@@ -5065,16 +5071,20 @@
               }
             },
 
             canWarmTab(tab) {
               if (!this.tabbrowser.tabWarmingEnabled) {
                 return false;
               }
 
+              if (!tab) {
+                return false;
+              }
+
               // If the tab is not yet inserted, closing, not remote,
               // crashed, already visible, or already requested, warming
               // up the tab makes no sense.
               if (this.minimizedOrFullyOccluded ||
                   !tab.linkedPanel ||
                   tab.closing ||
                   !tab.linkedBrowser.isRemoteBrowser ||
                   !tab.linkedBrowser.frameLoader.tabParent) {
@@ -5819,86 +5829,16 @@
 
                 notificationBox.appendNotification(message, "refresh-blocked",
                                                    "chrome://browser/skin/notification-icons/popup.svg",
                                                    notificationBox.PRIORITY_INFO_MEDIUM,
                                                    buttons);
               }
               break;
             }
-
-            case "Prerender:Request": {
-              let sendCancelPrerendering = () => {
-                browser.frameloader.messageManager.
-                  sendAsyncMessage("Prerender:Canceled", { id: data.id });
-              };
-
-              let tab = this.getTabForBrowser(browser);
-              if (!tab) {
-                // No tab?
-                sendCancelPrerendering();
-                break;
-              }
-
-              if (tab.hidden) {
-                // Skip prerender on hidden tab.
-                sendCancelPrerendering();
-                break;
-              }
-
-              if (browser.canGoForward) {
-                // Skip prerender on history navigation as we don't support it
-                // yet. Remove this check once bug 1323650 is implemented.
-                sendCancelPrerendering();
-                break;
-              }
-
-              if (!data.href) {
-                // If we don't have data.href, loadOneTab will load about:blank
-                // which is meaningless for prerendering.
-                sendCancelPrerendering();
-                break;
-              }
-
-              let groupedSHistory = browser.frameLoader.ensureGroupedSHistory();
-
-              let newTab = this.loadOneTab(data.href, {
-                referrerURI: (data.referrer ? makeURI(data.referrer) : null),
-                referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
-                postData: null,
-                allowThirdPartyFixup: true,
-                relatedToCurrent: true,
-                isPrerendered: true,
-                triggeringPrincipal: Utils.deserializePrincipal(data.triggeringPrincipal),
-              });
-              let partialSHistory = newTab.linkedBrowser.frameLoader.partialSHistory;
-              groupedSHistory.addPrerenderingPartialSHistory(partialSHistory, data.id);
-              break;
-            }
-
-            case "Prerender:Cancel": {
-              let groupedSHistory = browser.frameLoader.groupedSHistory;
-              if (groupedSHistory) {
-                groupedSHistory.cancelPrerendering(data.id);
-              }
-              break;
-            }
-
-            case "Prerender:Swap": {
-              let frameloader = browser.frameLoader;
-              let groupedSHistory = browser.frameLoader.groupedSHistory;
-              if (groupedSHistory) {
-                groupedSHistory.activatePrerendering(data.id).then(
-                  () => frameloader.messageManager.sendAsyncMessage("Prerender:Swapped", data),
-                  () => frameloader.messageManager.sendAsyncMessage("Prerender:Canceled", data),
-                );
-              }
-              break;
-            }
-
           }
           return undefined;
         ]]></body>
       </method>
 
       <method name="observe">
         <parameter name="aSubject"/>
         <parameter name="aTopic"/>
@@ -6055,21 +5995,16 @@
           messageManager.addMessageListener("Browser:WindowCreated", this);
 
           // To correctly handle keypresses for potential FindAsYouType, while
           // the tab's find bar is not yet initialized.
           this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
           Services.prefs.addObserver("accessibility.typeaheadfind", this);
           messageManager.addMessageListener("Findbar:Keypress", this);
 
-          // Add listeners for prerender messages
-          messageManager.addMessageListener("Prerender:Request", this);
-          messageManager.addMessageListener("Prerender:Cancel", this);
-          messageManager.addMessageListener("Prerender:Swap", this);
-
           XPCOMUtils.defineLazyPreferenceGetter(this, "animationsEnabled",
                                                 "toolkit.cosmeticAnimations.enabled", true);
           XPCOMUtils.defineLazyPreferenceGetter(this, "schedulePressureDefaultCount",
                                                 "browser.schedulePressure.defaultCount", 3);
           XPCOMUtils.defineLazyPreferenceGetter(this, "tabWarmingEnabled",
                                                 "browser.tabs.remote.warmup.enabled", false);
           XPCOMUtils.defineLazyPreferenceGetter(this, "tabWarmingMax",
                                                 "browser.tabs.remote.warmup.maxTabs", 3);
@@ -7294,17 +7229,16 @@
         // as double-clicking the titlebar
         if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
           return;
 
         if (event.button != 0 ||
             event.originalTarget.localName != "box")
           return;
 
-        // See hack note in the tabbrowser-close-tab-button binding
         if (!this._blockDblClick)
           BrowserOpenTab();
 
         event.preventDefault();
       ]]></handler>
 
       <handler event="click" button="0" phase="capturing"><![CDATA[
         /* Catches extra clicks meant for the in-tab close button.
@@ -7343,18 +7277,17 @@
           }
         }
 
         /* Protects from close-tab-button errant doubleclick:
          * Since we're removing the event target, if the user
          * double-clicks the button, the dblclick event will be dispatched
          * with the tabbar as its event target (and explicit/originalTarget),
          * which treats that as a mouse gesture for opening a new tab.
-         * In this context, we're manually blocking the dblclick event
-         * (see tabbrowser-close-tab-button dblclick handler).
+         * In this context, we're manually blocking the dblclick event.
          */
         if (this._blockDblClick) {
           if (!("_clickedTabBarOnce" in this)) {
             this._clickedTabBarOnce = true;
             return;
           }
           delete this._clickedTabBarOnce;
           this._blockDblClick = false;
@@ -7825,45 +7758,16 @@
           return;
 
         this._tabDropIndicator.collapsed = true;
         event.stopPropagation();
       ]]></handler>
     </handlers>
   </binding>
 
-  <!-- close-tab-button binding
-       This binding relies on the structure of the tabbrowser binding.
-       Therefore it should only be used as a child of the tab or the tabs
-       element (in both cases, when they are anonymous nodes of <tabbrowser>).
-  -->
-  <binding id="tabbrowser-close-tab-button"
-           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
-    <handlers>
-      <handler event="click" button="0"><![CDATA[
-        var bindingParent = document.getBindingParent(this);
-        var tabContainer = bindingParent.parentNode;
-        tabContainer.tabbrowser.removeTab(bindingParent, {animate: true,
-                byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
-        // This enables double-click protection for the tab container
-        // (see tabbrowser-tabs 'click' handler).
-        tabContainer._blockDblClick = true;
-      ]]></handler>
-
-      <handler event="dblclick" button="0" phase="capturing">
-        // for the one-close-button case
-        event.stopPropagation();
-      </handler>
-
-      <handler event="dragstart">
-        event.stopPropagation();
-      </handler>
-    </handlers>
-  </binding>
-
   <binding id="tabbrowser-tab" display="xul:hbox"
            extends="chrome://global/content/bindings/tabbox.xml#tab">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
     </resources>
 
     <content context="tabContextMenu">
       <xul:stack class="tab-stack" flex="1">
@@ -7908,19 +7812,20 @@
             <xul:label class="tab-text tab-label"
                        xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
                        role="presentation"/>
           </xul:hbox>
           <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked"
                      anonid="soundplaying-icon"
                      class="tab-icon-sound"
                      role="presentation"/>
-          <xul:toolbarbutton anonid="close-button"
-                             xbl:inherits="fadein,pinned,selected=visuallyselected"
-                             class="tab-close-button close-icon"/>
+          <xul:image anonid="close-button"
+                     xbl:inherits="fadein,pinned,selected=visuallyselected"
+                     class="tab-close-button close-icon"
+                     role="presentation"/>
         </xul:hbox>
       </xul:stack>
     </content>
 
     <implementation>
       <constructor><![CDATA[
         if (!("_lastAccessed" in this)) {
           this.updateLastAccessed();
@@ -7931,18 +7836,16 @@
         <setter>
           <![CDATA[
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
             this.removeAttribute("visuallyselected");
           this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
 
-          this._setPositionAttributes(val);
-
           return val;
           ]]>
         </setter>
       </property>
 
       <property name="_selected">
         <setter>
           <![CDATA[
@@ -8086,17 +7989,22 @@
           tabContainer._hoveredTab = this;
           if (this.linkedPanel && !this.selected) {
             this.linkedBrowser.unselectedTabHover(true);
             this.startUnselectedTabHoverTimer();
           }
 
           // Prepare connection to host beforehand.
           SessionStore.speculativeConnectOnTabHover(this);
-          tabContainer.tabbrowser.warmupTab(this);
+
+          let tabToWarm = this;
+          if (this.mOverCloseButton) {
+            tabToWarm = tabContainer.tabbrowser._findTabToBlurTo(this);
+          }
+          tabContainer.tabbrowser.warmupTab(tabToWarm);
         ]]></body>
       </method>
 
       <method name="_mouseleave">
         <body><![CDATA[
           let tabContainer = this.parentNode;
           if (tabContainer._beforeHoveredTab) {
             tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
@@ -8232,61 +8140,81 @@
           ContextualIdentityService.setTabStyle(this);
         ]]>
         </body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="mouseover"><![CDATA[
-        let anonid = event.originalTarget.getAttribute("anonid");
-        if (anonid == "close-button")
+        if (event.originalTarget.getAttribute("anonid") == "close-button") {
           this.mOverCloseButton = true;
+        }
 
         this._mouseenter();
       ]]></handler>
       <handler event="mouseout"><![CDATA[
-        let anonid = event.originalTarget.getAttribute("anonid");
-        if (anonid == "close-button")
+        if (event.originalTarget.getAttribute("anonid") == "close-button") {
           this.mOverCloseButton = false;
+        }
 
         this._mouseleave();
       ]]></handler>
+
       <handler event="dragstart" phase="capturing">
         this.style.MozUserFocus = "";
       </handler>
+
+      <handler event="dragstart"><![CDATA[
+        if (this.mOverCloseButton) {
+          event.stopPropagation();
+        }
+      ]]></handler>
+
       <handler event="mousedown" phase="capturing">
       <![CDATA[
         if (this.selected) {
           this.style.MozUserFocus = "ignore";
         } else if (this.mOverCloseButton ||
                    this._overPlayingIcon) {
           // Prevent tabbox.xml from selecting the tab.
           event.stopPropagation();
         }
       ]]>
       </handler>
       <handler event="mouseup">
         this.style.MozUserFocus = "";
       </handler>
-      <handler event="click">
-      <![CDATA[
-        if (event.button != 0) {
-          return;
-        }
-
+
+      <handler event="click" button="0"><![CDATA[
         if (this._overPlayingIcon) {
           this.toggleMuteAudio();
+          return;
         }
-      ]]>
-      </handler>
+
+        if (event.originalTarget.getAttribute("anonid") == "close-button") {
+          let tabContainer = this.parentNode;
+          tabContainer.tabbrowser.removeTab(this, {animate: true,
+                  byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
+          // This enables double-click protection for the tab container
+          // (see tabbrowser-tabs 'click' handler).
+          tabContainer._blockDblClick = true;
+        }
+      ]]></handler>
+
+      <handler event="dblclick" button="0" phase="capturing"><![CDATA[
+        // for the one-close-button case
+        if (event.originalTarget.getAttribute("anonid") == "close-button") {
+          event.stopPropagation();
+        }
+      ]]></handler>
+
       <handler event="animationend">
       <![CDATA[
-        let anonid = event.originalTarget.getAttribute("anonid");
-        if (anonid == "tab-loading-burst") {
+        if (event.originalTarget.getAttribute("anonid") == "tab-loading-burst") {
           this.removeAttribute("bursting");
         }
       ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-alltabs-popup"
--- a/browser/base/content/test/contextMenu/browser.ini
+++ b/browser/base/content/test/contextMenu/browser.ini
@@ -1,7 +1,9 @@
 [DEFAULT]
 support-files =
   !/browser/base/content/test/general/contextmenu_common.js
   subtst_contextmenu_webext.html
+  test_contextmenu_links.html
 
 [browser_contextmenu_touch.js]
 skip-if = !(os == 'win' && os_version == '10.0')
+[browser_contextmenu_linkopen.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/browser_contextmenu_linkopen.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_LINK = "https://example.com/";
+const RESOURCE_LINK = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "test_contextmenu_links.html";
+
+async function activateContextAndWaitFor(selector, where) {
+  let contextMenuItem = "openlink";
+  let openPromise;
+  let closeMethod;
+  switch (where) {
+    case "tab":
+      contextMenuItem += "intab";
+      openPromise = BrowserTestUtils.waitForNewTab(gBrowser, TEST_LINK, false);
+      closeMethod = async (tab) => BrowserTestUtils.removeTab(tab);
+      break;
+    case "privatewindow":
+      contextMenuItem += "private";
+      openPromise = BrowserTestUtils.waitForNewWindow(TEST_LINK).then(win => {
+        ok(PrivateBrowsingUtils.isWindowPrivate(win), "Should have opened a private window.");
+        return win;
+      });
+      closeMethod = async (win) => BrowserTestUtils.closeWindow(win);
+      break;
+    case "window":
+      // No contextMenuItem suffix for normal new windows;
+      openPromise = BrowserTestUtils.waitForNewWindow(TEST_LINK).then(win => {
+        ok(!PrivateBrowsingUtils.isWindowPrivate(win), "Should have opened a normal window.");
+        return win;
+      });
+      closeMethod = async (win) => BrowserTestUtils.closeWindow(win);
+      break;
+  }
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  is(contextMenu.state, "closed", "checking if popup is closed");
+  let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  await BrowserTestUtils.synthesizeMouse(selector, 0, 0, {
+      type: "contextmenu",
+      button: 2,
+      centered: true,
+    },
+    gBrowser.selectedBrowser);
+  await awaitPopupShown;
+  info("Popup Shown");
+  let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+  let domItem = contextMenu.querySelector("#context-" + contextMenuItem);
+  info("Going to click item " + domItem.id);
+  let bounds = domItem.getBoundingClientRect();
+  ok(bounds.height && bounds.width, "DOM context menu item " + where + " should be visible");
+  ok(!domItem.disabled, "DOM context menu item " + where + " shouldn't be disabled");
+  domItem.click();
+  contextMenu.hidePopup();
+  await awaitPopupHidden;
+
+  let openedThing = await openPromise;
+  await closeMethod(openedThing);
+}
+
+add_task(async function test_select_text_link() {
+  let testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, RESOURCE_LINK);
+  for (let elementID of ["test-link", "test-image-link", "svg-with-link", "svg-with-relative-link"]) {
+    for (let where of ["tab", "window", "privatewindow"]) {
+      await activateContextAndWaitFor("#" + elementID, where);
+    }
+  }
+  await BrowserTestUtils.removeTab(testTab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/contextMenu/test_contextmenu_links.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Subtest for browser context menu links</title>
+</head>
+<body>
+Browser context menu link subtest.
+
+<a id="test-link" href="https://example.com">Click the monkey!</a>
+<a id="test-image-link" href="/"><img src="ctxmenu-image.png"></a>
+<svg id="svg-with-link" width=10 height=10><a xlink:href="https://example.com/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+<svg id="svg-with-relative-link" width=10 height=10><a xlink:href="/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+</body>
+</html>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -294,20 +294,20 @@ skip-if = true # Disabled due to the cli
 [browser_contentAreaClick.js]
 skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s.
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contentAltClick.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contextmenu.js]
 subsuite = clipboard
 tags = fullscreen
-skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+skip-if = toolkit == "gtk3" # disabled on Linux due to bug 513558
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contextmenu_input.js]
-skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+skip-if = toolkit == "gtk3" # disabled on Linux due to bug 513558
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_ctrlTab.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_datachoices_notification.js]
 skip-if = !datareporting
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_decoderDoctor.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
@@ -583,11 +583,8 @@ tags = fullscreen
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_newWindowDrop.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_newwindow_focus.js]
 skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason.
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug1299667.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_close_dependent_tabs.js]
-skip-if = !e10s # GroupedSHistory is e10s-only
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
deleted file mode 100644
--- a/browser/base/content/test/general/browser_close_dependent_tabs.js
+++ /dev/null
@@ -1,78 +0,0 @@
-add_task(async function() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.groupedhistory.enabled", true],
-          ["dom.linkPrerender.enabled", true]]
-  });
-
-  // Wait for a process change and then fulfil the promise.
-  function awaitProcessChange(browser) {
-    return new Promise(resolve => {
-      browser.addEventListener("BrowserChangedProcess", function(e) {
-        ok(true, "The browser changed process!");
-        resolve();
-      }, {once: true});
-    });
-  }
-
-  // Wait for given number tabs being closed.
-  function awaitTabClose(number) {
-    return new Promise(resolve => {
-      let seen = 0;
-      gBrowser.tabContainer.addEventListener("TabClose", function f() {
-        if (++seen == number) {
-          gBrowser.tabContainer.removeEventListener("TabClose", f);
-          resolve();
-        }
-      });
-    });
-  }
-
-  // Test 1: Create prerendered browser, and don't switch to it, then close the tab
-  let closed1 = awaitTabClose(2);
-  await BrowserTestUtils.withNewTab({ gBrowser, url: "data:text/html,a" }, async function(browser1) {
-    // Set up the grouped SHEntry setup
-
-    let requestMade = new Promise(resolve => {
-      browser1.messageManager.addMessageListener("Prerender:Request", function f() {
-        browser1.messageManager.removeMessageListener("Prerender:Request", f);
-        ok(true, "Successfully received the prerender request");
-        resolve();
-      });
-    });
-
-    is(gBrowser.tabs.length, 2);
-    await ContentTask.spawn(browser1, null, function() {
-      let link = content.document.createElement("link");
-      link.setAttribute("rel", "prerender");
-      link.setAttribute("href", "data:text/html,b");
-      content.document.body.appendChild(link);
-    });
-    await requestMade;
-
-    is(gBrowser.tabs.length, 3);
-  });
-  await closed1;
-
-  // At this point prerendered tab should be closed
-  is(gBrowser.tabs.length, 1, "The new tab and the prerendered 'tab' should be closed");
-
-  // Test 2: Create prerendered browser, switch to it, then close the tab
-  let closed2 = awaitTabClose(2);
-  await BrowserTestUtils.withNewTab({ gBrowser, url: "data:text/html,a" }, async function(browser1) {
-    // Set up the grouped SHEntry setup
-    let tab2 = gBrowser.loadOneTab("data:text/html,b", {
-      referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
-      allowThirdPartyFixup: true,
-      relatedToCurrent: true,
-      isPrerendered: true,
-      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-    });
-    await BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
-    browser1.frameLoader.appendPartialSHistoryAndSwap(tab2.linkedBrowser.frameLoader);
-    await awaitProcessChange(browser1);
-  });
-  await closed2;
-
-  // At this point prerendered tab should be closed
-  is(gBrowser.tabs.length, 1, "The new tab and the prerendered 'tab' should be closed");
-});
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -979,16 +979,75 @@ add_task(async function test_svg_link() 
      "context-copylink",      true,
      "context-searchselect",  true,
      "---",                   null,
      "context-sendlinktodevice", true, [], null,
     ]
   );
 });
 
+add_task(async function test_svg_relative_link() {
+  await test_contextmenu("#svg-with-relative-link > a",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true,
+     "---",                   null,
+     "context-sendlinktodevice", true, [], null,
+    ]
+  );
+
+  await test_contextmenu("#svg-with-relative-link2 > a",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true,
+     "---",                   null,
+     "context-sendlinktodevice", true, [], null,
+    ]
+  );
+
+  await test_contextmenu("#svg-with-relative-link3 > a",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true,
+     "---",                   null,
+     "context-sendlinktodevice", true, [], null,
+    ]
+  );
+});
+
 add_task(async function test_cleanup_html() {
   gBrowser.removeCurrentTab();
 });
 
 /**
  * Selects the text of the element that matches the provided `selector`
  *
  * @param {String} selector
--- a/browser/base/content/test/general/file_trackingUI_6.html
+++ b/browser/base/content/test/general/file_trackingUI_6.html
@@ -1,16 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="UTF-8">
   <title>Testing the shield from fetch and XHR</title>
 </head>
 <body>
   <p>Hello there!</p>
-  <script type="application/javascript; version=1.8">
+  <script type="application/javascript">
     function test_fetch() {
       let url = "http://trackertest.org/browser/browser/base/content/test/general/file_trackingUI_6.js";
       return fetch(url);
     }
   </script>
 </body>
 </html>
--- a/browser/base/content/test/general/subtst_contextmenu.html
+++ b/browser/base/content/test/general/subtst_contextmenu.html
@@ -67,10 +67,13 @@ Browser context menu subtest.
 <input id="test-select-input-text" type="text" value="input">
 <input id="test-select-input-text-type-password" type="password" value="password">
 <embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed>
 <img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed>
 <iframe id="test-srcdoc" width="98"  height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe>
 <svg id="svg-with-link" width=10 height=10><a xlink:href="http://example.com/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
 <svg id="svg-with-link2" width=10 height=10><a xlink:href="http://example.com/" xlink:type="simple"><circle cx="50%" cy="50%" r="50%" fill="green"/></a></svg>
 <svg id="svg-with-link3" width=10 height=10><a href="http://example.com/"><circle cx="50%" cy="50%" r="50%" fill="red"/></a></svg>
+<svg id="svg-with-relative-link" width=10 height=10><a xlink:href="/"><circle cx="50%" cy="50%" r="50%" fill="blue"/></a></svg>
+<svg id="svg-with-relative-link2" width=10 height=10><a xlink:href="/" xlink:type="simple"><circle cx="50%" cy="50%" r="50%" fill="green"/></a></svg>
+<svg id="svg-with-relative-link3" width=10 height=10><a href="/"><circle cx="50%" cy="50%" r="50%" fill="red"/></a></svg>
 </body>
 </html>
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -49,17 +49,17 @@ tags = blocklist
 [browser_bug797677.js]
 [browser_bug812562.js]
 tags = blocklist
 [browser_bug818118.js]
 [browser_bug820497.js]
 [browser_clearplugindata.js]
 tags = blocklist
 [browser_CTP_context_menu.js]
-skip-if = toolkit == "gtk2" || toolkit == "gtk3"   # fails intermittently on Linux (bug 909342)
+skip-if = toolkit == "gtk3"   # fails intermittently on Linux (bug 909342)
 tags = blocklist
 [browser_CTP_crashreporting.js]
 skip-if = !crashreporter
 tags = blocklist
 [browser_CTP_drag_drop.js]
 tags = blocklist
 [browser_CTP_favorfallback.js]
 [browser_CTP_hide_overlay.js]
--- a/browser/base/content/test/popupNotifications/browser.ini
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -9,16 +9,18 @@ skip-if = (os == "linux" && (debug || as
 [browser_popupNotification_2.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_3.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_4.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_5.js]
 skip-if = true # bug 1332646
+[browser_popupNotification_accesskey.js]
+skip-if = (os == "linux" && (debug || asan)) || os == "mac"
 [browser_popupNotification_checkbox.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_selection_required.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_keyboard.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_no_anchors.js]
 skip-if = (os == "linux" && (debug || asan))
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_accesskey.js
@@ -0,0 +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/. */
+
+function test() {
+  waitForExplicitFinish();
+
+  ok(PopupNotifications, "PopupNotifications object exists");
+  ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+  setup();
+}
+
+let buttonPressed = false;
+
+function commandTriggered() {
+  buttonPressed = true;
+}
+
+var tests = [
+  // This test ensures that the accesskey closes the popup.
+  { id: "Test#1",
+    run() {
+      this.notifyObj = new BasicNotification(this.id);
+      showNotification(this.notifyObj);
+    },
+    onShown(popup) {
+      window.addEventListener("command", commandTriggered, true);
+      checkPopup(popup, this.notifyObj);
+      EventUtils.synthesizeKey("VK_ALT", { type: "keydown" });
+      EventUtils.synthesizeKey("M", { altKey: true });
+      EventUtils.synthesizeKey("VK_ALT", { type: "keyup" });
+
+      // If bug xxx was present, then the popup would be in the
+      // process of being hidden right now.
+      isnot(popup.state, "hiding", "popup is not hiding");
+    },
+    onHidden(popup) {
+      window.removeEventListener("command", commandTriggered, true);
+      ok(buttonPressed, "button pressed");
+    }
+  }
+ ];
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -198,17 +198,25 @@ function checkPopup(popup, notifyObj) {
     return;
   let icon = document.getAnonymousElementByAttribute(notification, "class",
                                                      "popup-notification-icon");
   if (notifyObj.id == "geolocation") {
     isnot(icon.boxObject.width, 0, "icon for geo displayed");
     ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
        "notification anchored to icon");
   }
-  is(notification.getAttribute("label"), notifyObj.message, "message matches");
+
+  if (typeof notifyObj.message == "string") {
+    is(notification.getAttribute("label"), notifyObj.message, "message matches");
+  } else {
+    is(notification.getAttribute("label"), notifyObj.message.start, "message matches");
+    is(notification.getAttribute("hostname"), notifyObj.message.host, "message matches");
+    is(notification.getAttribute("endlabel"), notifyObj.message.end, "message matches");
+  }
+
   is(notification.id, notifyObj.id + "-notification", "id matches");
   if (notifyObj.mainAction) {
     is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
        "main action label matches");
     is(notification.getAttribute("buttonaccesskey"),
        notifyObj.mainAction.accessKey, "main action accesskey matches");
     is(notification.getAttribute("buttonhighlight"),
        (!notifyObj.mainAction.disableHighlight).toString(),
--- a/browser/base/content/test/siteIdentity/browser_check_identity_state.js
+++ b/browser/base/content/test/siteIdentity/browser_check_identity_state.js
@@ -1,16 +1,17 @@
 /*
  * Test the identity mode UI for a variety of page types
  */
 
 "use strict";
 
 const DUMMY = "browser/browser/base/content/test/siteIdentity/dummy_page.html";
 const INSECURE_ICON_PREF = "security.insecure_connection_icon.enabled";
+const INSECURE_TEXT_PREF = "security.insecure_connection_text.enabled";
 const INSECURE_PBMODE_ICON_PREF = "security.insecure_connection_icon.pbmode.enabled";
 
 function loadNewTab(url) {
   return BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
 }
 
 function getIdentityMode(aWindow = window) {
   return aWindow.document.getElementById("identity-box").className;
@@ -51,16 +52,79 @@ async function webpageTest(secureCheck) 
   await SpecialPowers.popPrefEnv();
 }
 
 add_task(async function test_webpage() {
   await webpageTest(false);
   await webpageTest(true);
 });
 
+async function webpageTestTextWarning(secureCheck) {
+  await SpecialPowers.pushPrefEnv({set: [[INSECURE_TEXT_PREF, secureCheck]]});
+  let oldTab = gBrowser.selectedTab;
+
+  let newTab = await loadNewTab("http://example.com/" + DUMMY);
+  if (secureCheck) {
+    is(getIdentityMode(), "unknownIdentity notSecureText", "Identity should have not secure text");
+  } else {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+  }
+
+  gBrowser.selectedTab = oldTab;
+  is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+  gBrowser.selectedTab = newTab;
+  if (secureCheck) {
+    is(getIdentityMode(), "unknownIdentity notSecureText", "Identity should have not secure text");
+  } else {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+  }
+
+  gBrowser.removeTab(newTab);
+  await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_webpage_text_warning() {
+  await webpageTestTextWarning(false);
+  await webpageTestTextWarning(true);
+});
+
+async function webpageTestTextWarningCombined(secureCheck) {
+  await SpecialPowers.pushPrefEnv({set: [
+    [INSECURE_TEXT_PREF, secureCheck],
+    [INSECURE_ICON_PREF, secureCheck]
+  ]});
+  let oldTab = gBrowser.selectedTab;
+
+  let newTab = await loadNewTab("http://example.com/" + DUMMY);
+  if (secureCheck) {
+    is(getIdentityMode(), "notSecure notSecureText", "Identity should be not secure");
+  } else {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+  }
+
+  gBrowser.selectedTab = oldTab;
+  is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+  gBrowser.selectedTab = newTab;
+  if (secureCheck) {
+    is(getIdentityMode(), "notSecure notSecureText", "Identity should be not secure");
+  } else {
+    is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+  }
+
+  gBrowser.removeTab(newTab);
+  await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_webpage_text_warning_combined() {
+  await webpageTestTextWarning(false);
+  await webpageTestTextWarning(true);
+});
+
 async function blankPageTest(secureCheck) {
   let oldTab = gBrowser.selectedTab;
   await SpecialPowers.pushPrefEnv({set: [[INSECURE_ICON_PREF, secureCheck]]});
 
   let newTab = await loadNewTab("about:blank");
   is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
 
   gBrowser.selectedTab = oldTab;
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -131,18 +131,16 @@ var whitelist = [
   // These are used in content processes. They are actually referenced.
   {file: "resource://shield-recipe-client-content/shield-content-frame.js"},
   {file: "resource://shield-recipe-client-content/shield-content-process.js"},
 
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
-  // Bug 1316187
-  {file: "chrome://global/content/customizeToolbar.xul"},
   // Bug 1356031 (only used by devtools)
   {file: "chrome://global/skin/icons/error-16.png"},
   // Bug 1348362
   {file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux"]},
   // Bug 1348525
   {file: "chrome://global/skin/splitter/grip-bottom.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-left.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-right.gif", platforms: ["linux"]},
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -140,20 +140,16 @@ let gWhitelist = [{
     file: "pocket.properties",
     key: "tos",
     type: "double-quote"
   }, {
     file: "aboutNetworking.dtd",
     key: "aboutNetworking.logTutorial",
     type: "single-quote"
   }, {
-    file: "preferences.properties",
-    key: "searchResults.needHelp2",
-    type: "double-quote"
-  }, {
     file: "aboutdevtools.dtd",
     key: "aboutDevtools.newsletter.privacy.label",
     type: "single-quote"
   }
 ];
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
--- a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
+++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
@@ -70,10 +70,16 @@ add_task(async function() {
 
   // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway.
   // Note that the switchTab promise doesn't actually guarantee anything about *which*
   // tab ends up as selected when its event fires, so using that here wouldn't work.
   await openedTabSelectedPromise;
   // should be switched back
   ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!");
 
+  // In e10s, with the conformant promise scheduling, we have to wait for next tick
+  // to ensure that the prompt is open before removing the opened tab, because the
+  // promise callback of 'openedTabSelectedPromise' could be done at the middle of
+  // RemotePrompt.openTabPrompt() while 'DOMModalDialogClosed' event is fired.
+  await TestUtils.waitForTick();
+
   await BrowserTestUtils.removeTab(openedTab);
 });
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -223,16 +223,35 @@ add_task(async function oneOffReturn() {
     BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
                                    "http://mochi.test:8888/?terms=foo.bar");
   EventUtils.synthesizeKey("VK_RETURN", {});
   await resultsPromise;
 
   gBrowser.removeTab(gBrowser.selectedTab);
 });
 
+add_task(async function collapsedOneOffs() {
+  // Disable all the engines but the current one, check the oneoffs are
+  // collapsed and that moving up selects the last match.
+  let engines = Services.search.getVisibleEngines()
+                               .filter(e => e.name != Services.search.currentEngine.name);
+  await SpecialPowers.pushPrefEnv({"set": [
+    [ "browser.search.hiddenOneOffs", engines.map(e => e.name).join(",") ]
+  ]});
+
+  let typedValue = "foo";
+  await promiseAutocompleteResultPopup(typedValue, window, true);
+  await waitForAutocompleteResultAt(0);
+  assertState(0, -1);
+  Assert.ok(gURLBar.popup.oneOffSearchButtons.buttons.collapsed,
+    "The one-off buttons should be collapsed");
+  EventUtils.synthesizeKey("VK_UP", {});
+  assertState(1, -1);
+  await hidePopup();
+});
 
 function assertState(result, oneOff, textValue = undefined) {
   Assert.equal(gURLBar.popup.selectedIndex, result,
                "Expected result should be selected");
   Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff,
                "Expected one-off should be selected");
   if (textValue !== undefined) {
     Assert.equal(gURLBar.textValue, textValue, "Expected textValue");
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -70,16 +70,31 @@ add_task(async function plainEnterOnSugg
 
 add_task(async function ctrlEnterOnSuggestion() {
   await testPressEnterOnSuggestion("http://www.foofoo.com/",
                                    AppConstants.platform === "macosx" ?
                                      { metaKey: true } :
                                      { ctrlKey: true });
 });
 
+add_task(async function copySuggestionText() {
+  gURLBar.focus();
+  await promiseAutocompleteResultPopup("foo");
+  let [idx, suggestion] = await promiseFirstSuggestion();
+  for (let i = 0; i < idx; ++i) {
+    EventUtils.synthesizeKey("VK_DOWN", {});
+  }
+  gURLBar.select();
+  await new Promise((resolve, reject) => waitForClipboard(suggestion, function() {
+    goDoCommand("cmd_copy");
+  }, resolve, reject));
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  await promisePopupHidden(gURLBar.popup);
+});
+
 function getFirstSuggestion() {
   let controller = gURLBar.popup.input.controller;
   let matchCount = controller.matchCount;
   for (let i = 0; i < matchCount; i++) {
     let url = controller.getValueAt(i);
     let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
     if (mozActionMatch) {
       let [, type, paramStr] = mozActionMatch;
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -974,41 +974,48 @@ file, You can obtain one at http://mozil
             }
             return aURI;
           ]]>
         </body>
       </method>
 
       <method name="_getSelectedValueForClipboard">
         <body><![CDATA[
-          // Grab the actual input field's value, not our value, which could include moz-action:
+          // Grab the actual input field's value, not our value, which could
+          // include "moz-action:".
           var inputVal = this.inputField.value;
           let selection = this.editor.selection;
           const flags = Ci.nsIDocumentEncoder.OutputPreformatted |
                         Ci.nsIDocumentEncoder.OutputRaw;
           let selectedVal = selection.QueryInterface(Ci.nsISelectionPrivate)
                                      .toStringWithFormat("text/plain", flags, 0);
 
           // Handle multiple-range selection as a string for simplicity.
           if (selection.rangeCount > 1) {
              return selectedVal;
           }
 
-          // If the selection doesn't start at the beginning or doesn't span the full domain or
-          // the URL bar is modified or there is no text at all, nothing else to do here.
+          // If the selection doesn't start at the beginning or doesn't span the
+          // full domain or the URL bar is modified or there is no text at all,
+          // nothing else to do here.
           if (this.selectionStart > 0 || this.valueIsTyped || selectedVal == "")
             return selectedVal;
           // The selection doesn't span the full domain if it doesn't contain a slash and is
           // followed by some character other than a slash.
           if (!selectedVal.includes("/")) {
             let remainder = inputVal.replace(selectedVal, "");
             if (remainder != "" && remainder[0] != "/")
               return selectedVal;
           }
 
+          // If the value was filled by a search suggestion, just return it.
+          let action = this._parseActionUrl(this.value);
+          if (action && action.type == "searchengine")
+            return selectedVal;
+
           let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
 
           let uri;
           if (this.getAttribute("pageproxystate") == "valid") {
             uri = gBrowser.currentURI;
           } else {
             // We're dealing with an autocompleted value, create a new URI from that.
             try {
@@ -1641,17 +1648,17 @@ file, You can obtain one at http://mozil
                      role="link"
 #ifdef XP_WIN
                      value="&urlbar.searchSuggestionsNotification.changeSettingsWin;"
                      accesskey="&urlbar.searchSuggestionsNotification.changeSettingsWin.accesskey;"
 #else
                      value="&urlbar.searchSuggestionsNotification.changeSettingsUnix;"
                      accesskey="&urlbar.searchSuggestionsNotification.changeSettingsUnix.accesskey;"
 #endif
-                     onclick="openPreferences('paneSearch');"
+                     onclick="openPreferences('paneSearch', {origin: 'searchChangeSettings'});"
                      control="search-suggestions-change-settings"/>
         </xul:hbox>
       </xul:deck>
       <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
                        flex="1"/>
       <xul:hbox anonid="footer">
         <children/>
         <xul:vbox anonid="one-off-search-buttons"
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -4,16 +4,19 @@
 # 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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 SPHINX_TREES['sslerrorreport'] = 'content/docs/sslerrorreport'
 
+with Files('content/docs/sslerrorreport/**'):
+    SCHEDULES.exclusive = ['docs']
+
 MOCHITEST_MANIFESTS += [
     'content/test/general/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'content/test/chrome/chrome.ini',
 ]
 
@@ -49,18 +52,18 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/webrtc/browser.ini',
 ]
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk3', 'cocoa'):
     DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa', 'gtk3'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk3'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
 
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -53,17 +53,17 @@ const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding"
 ];
 
 /**
  * The current version. We can use this to auto-add new default widgets as necessary.
  * (would be const but isn't because of testing purposes)
  */
-var kVersion = 13;
+var kVersion = 14;
 
 /**
  * Buttons removed from built-ins by version they were removed. kVersion must be
  * bumped any time a new id is added to this. Use the button id as key, and
  * version the button is removed in as the value.  e.g. "pocket-button": 5
  */
 var ObsoleteBuiltinButtons = {
 };
@@ -154,22 +154,29 @@ var gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
   extraDragSpace: null,
   currentTheme: null,
   uiDensity: null,
   autoTouchMode: null,
 };
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gDebuggingEnabled", kPrefCustomizationDebug, false,
+  (pref, oldVal, newVal) => {
+    if (typeof log != "undefined") {
+      log.maxLogLevel = newVal ? "all" : "log";
+    }
+  }
+);
+
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
-  let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
-    maxLogLevel: debug ? "all" : "log",
+    maxLogLevel: gDebuggingEnabled ? "all" : "log",
     prefix: "CustomizableUI",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var CustomizableUIInternal = {
   initialize() {
     log.debug("Initializing");
@@ -239,16 +246,23 @@ var CustomizableUIInternal = {
         "personal-bookmarks",
       ],
       defaultCollapsed: true,
     }, true);
 
     SearchWidgetTracker.init();
   },
 
+  get _builtinAreas() {
+    return new Set([
+      ...this._builtinToolbars,
+      CustomizableUI.AREA_FIXED_OVERFLOW_PANEL,
+    ]);
+  },
+
   get _builtinToolbars() {
     let toolbars = new Set([
       CustomizableUI.AREA_NAVBAR,
       CustomizableUI.AREA_BOOKMARKS,
       CustomizableUI.AREA_TABSTRIP,
     ]);
     if (AppConstants.platform != "macosx") {
       toolbars.add(CustomizableUI.AREA_MENUBAR);
@@ -454,16 +468,25 @@ var CustomizableUIInternal = {
     if (currentVersion < 13 && gSavedState.placements) {
       for (let placements of Object.values(gSavedState.placements)) {
         let buttonIndex = placements.indexOf("e10s-button");
         if (buttonIndex != -1) {
           placements.splice(buttonIndex, 1);
         }
       }
     }
+
+    // Remove unsupported custom toolbar saved placements
+    if (currentVersion < 14 && gSavedState.placements) {
+      for (let area in gSavedState.placements) {
+        if (!this._builtinAreas.has(area)) {
+          delete gSavedState.placements[area];
+        }
+      }
+    }
   },
 
   /**
    * _markObsoleteBuiltinButtonsSeen
    * when upgrading, ensure obsoleted buttons are in seen state.
    */
   _markObsoleteBuiltinButtonsSeen() {
     if (!gSavedState)
@@ -681,20 +704,19 @@ var CustomizableUIInternal = {
     let area = aToolbar.id;
     if (gBuildAreas.has(area) && gBuildAreas.get(area).has(aToolbar)) {
       return;
     }
     let areaProperties = gAreas.get(area);
 
     // If this area is not registered, try to do it automatically:
     if (!areaProperties) {
-      // If there's no defaultset attribute and this isn't a legacy extra toolbar,
-      // we assume that we should wait for registerArea to be called:
-      if (!aToolbar.hasAttribute("defaultset") &&
-          !aToolbar.hasAttribute("customindex")) {
+      // If there's no default set attribute at all, we assume that we should
+      // wait for registerArea to be called:
+      if (!aToolbar.hasAttribute("defaultset")) {
         if (!gPendingBuildAreas.has(area)) {
           gPendingBuildAreas.set(area, new Map());
         }
         let pendingNodes = gPendingBuildAreas.get(area);
         pendingNodes.set(aToolbar, aExistingChildren);
         return;
       }
       let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
@@ -2627,18 +2649,16 @@ var CustomizableUIInternal = {
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
       gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity);
       gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode);
       gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
       gUIStateBeforeReset.autoHideDownloadsButton = Services.prefs.getBoolPref(kPrefAutoHideDownloadsButton);
       gUIStateBeforeReset.newElementCount = gNewElementCount;
     } catch (e) { }
 
-    this._resetExtraToolbars();
-
     Services.prefs.clearUserPref(kPrefCustomizationState);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
     Services.prefs.clearUserPref(kPrefExtraDragSpace);
     Services.prefs.clearUserPref(kPrefUIDensity);
     Services.prefs.clearUserPref(kPrefAutoTouchMode);
     Services.prefs.clearUserPref(kPrefAutoHideDownloadsButton);
     LightweightThemeManager.currentTheme = null;
     gNewElementCount = 0;
@@ -2651,38 +2671,16 @@ var CustomizableUIInternal = {
     // Clear the saved state to ensure that defaults will be used.
     gSavedState = null;
     // Restore the state for each area to its defaults
     for (let [areaId, ] of gAreas) {
       this.restoreStateForArea(areaId);
     }
   },
 
-  _resetExtraToolbars(aFilter = null) {
-    let firstWindow = true; // Only need to unregister and persist once
-    for (let [win, ] of gBuildWindows) {
-      let toolbox = win.gNavToolbox;
-      for (let child of toolbox.children) {
-        let matchesFilter = !aFilter || aFilter == child.id;
-        if (child.hasAttribute("customindex") && matchesFilter) {
-          let toolbarId = "toolbar" + child.getAttribute("customindex");
-          toolbox.toolbarset.removeAttribute(toolbarId);
-          if (firstWindow) {
-            win.document.persist(toolbox.toolbarset.id, toolbarId);
-            // We have to unregister it properly to ensure we don't kill
-            // XUL widgets which might be in here
-            this.unregisterArea(child.id, true);
-          }
-          child.remove();
-        }
-      }
-      firstWindow = false;
-    }
-  },
-
   _rebuildRegisteredAreas() {
     for (let [areaId, areaNodes] of gBuildAreas) {
       let placements = gPlacements.get(areaId);
       let isFirstChangedToolbar = true;
       for (let areaNode of areaNodes) {
         this.buildArea(areaId, placements, areaNode);
 
         let area = gAreas.get(areaId);
@@ -2740,20 +2738,16 @@ var CustomizableUIInternal = {
   },
 
   _clearPreviousUIState() {
     Object.getOwnPropertyNames(gUIStateBeforeReset).forEach((prop) => {
       gUIStateBeforeReset[prop] = null;
     });
   },
 
-  removeExtraToolbar(aToolbarId) {
-    this._resetExtraToolbars(aToolbarId);
-  },
-
   /**
    * @param {String|Node} aWidget - widget ID or a widget node (preferred for performance).
    * @return {Boolean} whether the widget is removable
    */
   isWidgetRemovable(aWidget) {
     let widgetId;
     let widgetNode;
     if (typeof aWidget == "string") {
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -7,17 +7,16 @@
 this.EXPORTED_SYMBOLS = ["CustomizeMode"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 const kPaletteId = "customization-palette";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
-const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
 const kExtraDragSpacePref = "browser.tabs.extraDragSpace";
 const kMaxTransitionDurationMs = 2000;
 const kKeepBroadcastAttributes = "keepbroadcastattributeswhencustomizing";
 
 const kPanelItemContextMenu = "customizationPanelItemContextMenu";
 const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
 
@@ -274,26 +273,16 @@ CustomizeMode.prototype = {
               resolve();
             }
           };
 
           Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished");
         });
       }
 
-      let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn);
-      let togglableToolbars = window.getTogglableToolbars();
-      if (togglableToolbars.length == 0) {
-        toolbarVisibilityBtn.setAttribute("hidden", "true");
-      } else {
-        toolbarVisibilityBtn.removeAttribute("hidden");
-      }
-
-      this.updateLWTStyling();
-
       CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window);
       CustomizableUI.notifyStartCustomizing(this.window);
 
       // Add a keypress listener to the document so that we can quickly exit
       // customization mode when pressing ESC.
       document.addEventListener("keypress", this);
 
       // Same goes for the menu button - if we're customizing, a click on the
@@ -319,18 +308,16 @@ CustomizeMode.prototype = {
       this._wrapToolbarItemSync(CustomizableUI.AREA_TABSTRIP);
 
       let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true]):not([collapsed=true])");
       for (let toolbar of customizableToolbars)
         toolbar.setAttribute("customizing", true);
 
       await this._doTransition(true);
 
-      Services.obs.addObserver(this, "lightweight-theme-window-updated");
-
       // Let everybody in this window know that we're about to customize.
       CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
 
       await this._wrapToolbarItems();
       this.populatePalette();
 
       this._setupPaletteDragging();
 
@@ -392,18 +379,16 @@ CustomizeMode.prototype = {
     if (this.resetting) {
       log.debug("Attempted to exit while we're resetting. " +
                 "We'll exit after resetting has finished.");
       return;
     }
 
     this._handler.isExitingCustomizeMode = true;
 
-    this._removeExtraToolbarsIfEmpty();
-
     this._teardownDownloadAutoHideToggle();
 
     CustomizableUI.removeListener(this);
 
     this.document.removeEventListener("keypress", this);
 
     let window = this.window;
     let document = this.document;
@@ -416,19 +401,16 @@ CustomizeMode.prototype = {
     undoResetButton.hidden = resetButton.disabled = true;
 
     this._transitioning = true;
 
     (async () => {
       await this.depopulatePalette();
 
       await this._doTransition(false);
-      this.updateLWTStyling({});
-
-      Services.obs.removeObserver(this, "lightweight-theme-window-updated");
 
       if (this.browser.selectedTab == gTab) {
         if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
           closeGlobalTab();
         } else {
           unregisterGlobalTab();
         }
       }
@@ -521,36 +503,16 @@ CustomizeMode.prototype = {
       docEl.setAttribute("customize-entered", true);
     } else {
       docEl.removeAttribute("customizing");
       docEl.removeAttribute("customize-entered");
     }
     return Promise.resolve();
   },
 
-  updateLWTStyling(aData) {
-    let docElement = this.document.documentElement;
-    if (!aData) {
-      let lwt = docElement._lightweightTheme;
-      aData = lwt.getData();
-    }
-    let headerURL = aData && aData.headerURL;
-    if (!headerURL) {
-      docElement.removeAttribute("customization-lwtheme");
-      return;
-    }
-    docElement.setAttribute("customization-lwtheme", "true");
-
-    let deck = this.document.getElementById("tab-view-deck");
-    let toolboxRect = this.window.gNavToolbox.getBoundingClientRect();
-    let height = toolboxRect.bottom;
-    deck.style.setProperty("--toolbox-rect-height", `${height}`);
-    deck.style.setProperty("--toolbox-rect-height-with-unit", `${height}px`);
-  },
-
   _getCustomizableChildForNode(aNode) {
     // NB: adjusted from _getCustomizableParent to keep that method fast
     // (it's used during drags), and avoid multiple DOM loops
     let areas = CustomizableUI.areas;
     // Caching this length is important because otherwise we'll also iterate
     // over items we add to the end from within the loop.
     let numberOfAreas = areas.length;
     for (let i = 0; i < numberOfAreas; i++) {
@@ -1104,28 +1066,16 @@ CustomizeMode.prototype = {
           }
         }
         this._removeDragHandlers(target);
       }
       this.areas.clear();
     })().catch(log.error);
   },
 
-  _removeExtraToolbarsIfEmpty() {
-    let toolbox = this.window.gNavToolbox;
-    for (let child of toolbox.children) {
-      if (child.hasAttribute("customindex")) {
-        let placements = CustomizableUI.getWidgetIdsInArea(child.id);
-        if (!placements.length) {
-          CustomizableUI.removeExtraToolbar(child.id);
-        }
-      }
-    }
-  },
-
   persistCurrentSets(aSetBeforePersisting) {
     let document = this.document;
     let toolbars = document.querySelectorAll("toolbar[customizable='true'][currentset]");
     for (let toolbar of toolbars) {
       if (aSetBeforePersisting) {
         let set = toolbar.currentSet;
         toolbar.setAttribute("currentset", set);
       }
@@ -1191,17 +1141,16 @@ CustomizeMode.prototype = {
   _onToolbarVisibilityChange(aEvent) {
     let toolbar = aEvent.target;
     if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
       toolbar.setAttribute("customizing", "true");
     } else {
       toolbar.removeAttribute("customizing");
     }
     this._onUIChange();
-    this.updateLWTStyling();
   },
 
   onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition) {
     this._onUIChange();
   },
 
   onWidgetAdded(aWidgetId, aArea, aPosition) {
     this._onUIChange();
@@ -1639,22 +1588,16 @@ CustomizeMode.prototype = {
       case "nsPref:changed":
         this._updateResetButton();
         this._updateUndoResetButton();
         if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
           this._updateTitlebarCheckbox();
           this._updateDragSpaceCheckbox();
         }
         break;
-      case "lightweight-theme-window-updated":
-        if (aSubject == this.window) {
-          aData = JSON.parse(aData);
-          this.updateLWTStyling(aData);
-        }
-        break;
     }
   },
 
   _updateTitlebarCheckbox() {
     if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
       return;
     }
     let drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref, true);
@@ -2335,20 +2278,16 @@ CustomizeMode.prototype = {
     return aElement.closest(areas.map(a => "#" + CSS.escape(a)).join(","));
   },
 
   _getDragOverNode(aEvent, aAreaElement, aAreaType, aDraggedItemId) {
     let expectedParent = aAreaElement.customizationTarget || aAreaElement;
     if (!expectedParent.contains(aEvent.target)) {
       return expectedParent;
     }
-    // Our tests are stupid. Cope:
-    if (!aEvent.clientX && !aEvent.clientY) {
-      return aEvent.target;
-    }
     // Offset the drag event's position with the offset to the center of
     // the thing we're dragging
     let dragX = aEvent.clientX - this._dragOffset.x;
     let dragY = aEvent.clientY - this._dragOffset.y;
 
     // Ensure this is within the container
     let boundsContainer = expectedParent;
     let bounds = this._dwu.getBoundsWithoutFlushing(boundsContainer);
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -21,35 +21,16 @@
           let CustomizableUI = scope.CustomizableUI;
           // Add an early overflow event listener that will mark if the
           // toolbar overflowed during construction.
           if (CustomizableUI.isAreaOverflowable(this.id)) {
             this.addEventListener("overflow", this);
             this.addEventListener("underflow", this);
           }
 
-          // Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
-          // attributes, just in case they accidentally get restored from
-          // persistence from a user that's been upgrading and downgrading.
-          if (CustomizableUI.isBuiltinToolbar(this.id)) {
-            const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
-            for (let [attribute, value] of kAttributes) {
-              if (this.getAttribute(attribute) != value) {
-                this.setAttribute(attribute, value);
-                document.persist(this.id, attribute);
-              }
-              if (this.toolbox) {
-                if (this.toolbox.getAttribute(attribute) != value) {
-                  this.toolbox.setAttribute(attribute, value);
-                  document.persist(this.toolbox.id, attribute);
-                }
-              }
-            }
-          }
-
           // Searching for the toolbox palette in the toolbar binding because
           // toolbars are constructed first.
           let toolbox = this.toolbox;
           if (toolbox && !toolbox.palette) {
             for (let node of toolbox.children) {
               if (node.localName == "toolbarpalette") {
                 // Hold on to the palette but remove it from the document.
                 toolbox.palette = node;
@@ -136,19 +117,16 @@
         <getter><![CDATA[
           if (this._toolbox)
             return this._toolbox;
 
           let toolboxId = this.getAttribute("toolboxid");
           if (toolboxId) {
             let toolbox = document.getElementById(toolboxId);
             if (toolbox) {
-              if (toolbox.externalToolbars.indexOf(this) == -1)
-                toolbox.externalToolbars.push(this);
-
               this._toolbox = toolbox;
             }
           }
 
           if (!this._toolbox && this.parentNode &&
               this.parentNode.localName == "toolbox") {
             this._toolbox = this.parentNode;
           }
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -92,17 +92,16 @@ skip-if = os == "linux" # Intermittent f
 [browser_967000_button_charEncoding.js]
 [browser_967000_button_feeds.js]
 [browser_968565_insert_before_hidden_items.js]
 [browser_969427_recreate_destroyed_widget_after_reset.js]
 [browser_969661_character_encoding_navbar_disabled.js]
 [browser_970511_undo_restore_default.js]
 [browser_972267_customizationchange_events.js]
 [browser_973641_button_addon.js]
-[browser_975719_customtoolbars_behaviour.js]
 [browser_976792_insertNodeInWindow.js]
 skip-if = os == "linux"
 [browser_978084_dragEnd_after_move.js]
 [browser_980155_add_overflow_toolbar.js]
 [browser_981305_separator_insertion.js]
 [browser_981418-widget-onbeforecreated-handler.js]
 [browser_982656_restore_defaults_builtin_widgets.js]
 [browser_984455_bookmarks_items_reparenting.js]
--- a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
+++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
@@ -9,53 +9,60 @@ const kAPIWidgetId = "feed-button";
 const kPanel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
 const kToolbar = CustomizableUI.AREA_NAVBAR;
 const kVisiblePalette = "customization-palette";
 
 function checkWrapper(id) {
   is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
 }
 
-async function ensureVisibleIfInPalette(node) {
-    if (node.parentNode.parentNode == gNavToolbox.palette) {
-      node.scrollIntoView();
-      window.QueryInterface(Ci.nsIInterfaceRequestor);
-      let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
-      await BrowserTestUtils.waitForCondition(() => {
-        let nodeBounds = dwu.getBoundsWithoutFlushing(node);
-        let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette);
-        return nodeBounds.top >= paletteBounds.top && nodeBounds.bottom <= paletteBounds.bottom;
-      });
+async function ensureVisible(node) {
+  let isInPalette = node.parentNode.parentNode == gNavToolbox.palette;
+  if (isInPalette) {
+    node.scrollIntoView();
+  }
+  window.QueryInterface(Ci.nsIInterfaceRequestor);
+  let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
+  await BrowserTestUtils.waitForCondition(() => {
+    let nodeBounds = dwu.getBoundsWithoutFlushing(node);
+    if (isInPalette) {
+      let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette);
+      if (!(nodeBounds.top >= paletteBounds.top && nodeBounds.bottom <= paletteBounds.bottom)) {
+        return false;
+      }
     }
+    return nodeBounds.height && nodeBounds.width;
+  });
 }
 
 var move = {
   "drag": async function(id, target) {
     let targetNode = document.getElementById(target);
     if (targetNode.customizationTarget) {
       targetNode = targetNode.customizationTarget;
     }
     let nodeToMove = document.getElementById(id);
-    await ensureVisibleIfInPalette(nodeToMove);
-    simulateItemDrag(nodeToMove, targetNode);
+    await ensureVisible(nodeToMove);
+
+    simulateItemDrag(nodeToMove, targetNode, "end");
   },
   "dragToItem": async function(id, target) {
     let targetNode = document.getElementById(target);
     if (targetNode.customizationTarget) {
       targetNode = targetNode.customizationTarget;
     }
     let items = targetNode.querySelectorAll("toolbarpaletteitem");
     if (target == kPanel) {
       targetNode = items[items.length - 1];
     } else {
       targetNode = items[0];
     }
     let nodeToMove = document.getElementById(id);
-    await ensureVisibleIfInPalette(nodeToMove);
-    simulateItemDrag(nodeToMove, targetNode);
+    await ensureVisible(nodeToMove);
+    simulateItemDrag(nodeToMove, targetNode, "start");
   },
   "API": function(id, target) {
     if (target == kVisiblePalette) {
       return CustomizableUI.removeWidgetFromArea(id);
     }
     return CustomizableUI.addWidgetToArea(id, target, null);
   }
 };
@@ -145,16 +152,21 @@ async function checkPanel(id, method) {
 }
 
 async function checkPalette(id, method) {
   // Move back to palette:
   await move[method](id, kVisiblePalette);
   ok(CustomizableUI.inDefaultState, "Should end in default state");
   let visibleChildren = gCustomizeMode.visiblePalette.children;
   let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
+  // Items dragged to the end of the palette should be the final item. That they're the penultimate
+  // item when dragged is tracked in bug 1395950. Once that's fixed, this hack can be removed.
+  if (method == "drag") {
+    expectedChild = expectedChild.previousElementSibling;
+  }
   is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
   if (id == kXULWidgetId) {
     ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
   }
   checkWrapper(id);
 }
 
 // This test needs a XUL button that's in the palette by default. No such
--- a/browser/components/customizableui/test/browser_878452_drag_to_panel.js
+++ b/browser/components/customizableui/test/browser_878452_drag_to_panel.js
@@ -10,17 +10,17 @@ add_task(async function() {
   await startCustomizing();
   let btn = document.getElementById("feed-button");
   let placements = getAreaWidgetIds(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
 
   let lastButtonIndex = placements.length - 1;
   let lastButton = placements[lastButtonIndex];
   let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
   let lastButtonNode = document.getElementById(lastButton);
-  simulateItemDrag(btn, lastButtonNode);
+  simulateItemDrag(btn, lastButtonNode, "start");
   assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterInsert);
   ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
   let palette = document.getElementById("customization-palette");
   simulateItemDrag(btn, palette);
   CustomizableUI.removeWidgetFromArea("cui-panel-item-to-drag-to");
   ok(CustomizableUI.inDefaultState, "Should be in default state again.");
 });
 
@@ -58,12 +58,13 @@ add_task(async function() {
   simulateItemDrag(btn, panel);
   assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterAppend);
   ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
   let palette = document.getElementById("customization-palette");
   simulateItemDrag(btn, palette);
   assertAreaPlacements(panel.id, []);
 });
 
-add_task(async function asyncCleanup() {
+registerCleanupFunction(async function asyncCleanup() {
+  CustomizableUI.destroyWidget("cui-panel-item-to-drag-to");
   await endCustomizing();
   await resetCustomization();
 });
--- a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
+++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
@@ -13,23 +13,24 @@ add_task(async function() {
   skippedItem = document.createElement("toolbarbutton");
   skippedItem.id = "test-skipintoolbarset-item";
   skippedItem.setAttribute("label", "Test");
   skippedItem.setAttribute("skipintoolbarset", "true");
   skippedItem.setAttribute("removable", "true");
   navbar.customizationTarget.appendChild(skippedItem);
   let libraryButton = document.getElementById("library-button");
   await startCustomizing();
+  await waitForElementShown(skippedItem);
   ok(CustomizableUI.inDefaultState, "Should still be in default state");
-  simulateItemDrag(skippedItem, libraryButton);
+  simulateItemDrag(skippedItem, libraryButton, "start");
   ok(CustomizableUI.inDefaultState, "Should still be in default state");
   let skippedItemWrapper = skippedItem.parentNode;
   is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
      libraryButton.parentNode.id, "Should be next to library button");
-  simulateItemDrag(libraryButton, skippedItem);
+  simulateItemDrag(libraryButton, skippedItem, "start");
   let libraryWrapper = libraryButton.parentNode;
   is(libraryWrapper.nextSibling && libraryWrapper.nextSibling.id,
      skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
   ok(CustomizableUI.inDefaultState, "Should still be in default state");
 });
 
 add_task(async function asyncCleanup() {
   await endCustomizing();
--- a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
+++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
@@ -40,17 +40,17 @@ add_task(async function() {
   // Make sure we have some hidden items at the end of the nav-bar.
   navbar.insertItem(hidden1.id);
   navbar.insertItem(hidden2.id);
 
   // Drag an item and drop it onto the nav-bar customization target, but
   // not over a particular item.
   await startCustomizing();
   let homeButton = document.getElementById("home-button");
-  simulateItemDrag(homeButton, navbar.customizationTarget);
+  simulateItemDrag(homeButton, navbar.customizationTarget, "end");
 
   await endCustomizing();
 
   is(homeButton.previousSibling.id, lastVisible.id,
      "The downloads button should be placed after the last visible item.");
 
   await resetCustomization();
 });
deleted file mode 100644
--- a/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js
+++ /dev/null
@@ -1,145 +0,0 @@
-/* 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";
-
-requestLongerTimeout(2);
-
-const kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
-
-add_task(function setup() {
-  // create a XUL button and add it to the palette.
-  createDummyXULButton(kXULWidgetId, "test-button");
-});
-
-add_task(async function customizeToolbarAndKeepIt() {
-  ok(gNavToolbox.toolbarset, "There should be a toolbarset");
-  let toolbarID = "testAustralisCustomToolbar";
-  gNavToolbox.appendCustomToolbar(toolbarID, "");
-  let toolbarDOMID = getToolboxCustomToolbarId(toolbarID);
-  let toolbarElement = document.getElementById(toolbarDOMID);
-  ok(toolbarElement, "There should be a toolbar");
-  if (!toolbarElement) {
-    ok(false, "No toolbar created, bailing out of the test.");
-    return;
-  }
-  is(toolbarElement.nextSibling, gNavToolbox.toolbarset,
-     "Toolbar should have been inserted in toolbox, before toolbarset element");
-  let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
-  is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
-     "CustomizableUI should know the area and think it's a toolbar");
-  if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) {
-    ok(false, "Toolbar not registered successfully, bailing out of the test.");
-    toolbarElement.remove();
-    return;
-  }
-  ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet.");
-  CustomizableUI.addWidgetToArea("open-file-button", toolbarDOMID, 0);
-  ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
-  assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
-
-  gNavToolbox.toolbarset.setAttribute("toolbar1", toolbarID + ":open-file-button");
-  document.persist(gNavToolbox.toolbarset.id, "toolbar1");
-
-  await startCustomizing();
-  // First, exit customize mode without doing anything, and verify the toolbar doesn't get removed.
-  await endCustomizing();
-  ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state, the toolbar should still be there.");
-  cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
-  is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
-     "CustomizableUI should still know the area and think it's a toolbar");
-  ok(toolbarElement.parentNode, "Toolbar should still be in the DOM.");
-  ok(toolbarElement.hasChildNodes(), "Toolbar should still have items in it.");
-  assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
-
-  let newWindow = await openAndLoadWindow({}, true);
-  is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar1"),
-     gNavToolbox.toolbarset.getAttribute("toolbar1"),
-     "Attribute should be the same in new window");
-  await promiseWindowClosed(newWindow);
-
-  // Then customize again, and this time empty out the toolbar and verify it *does* get removed.
-  await startCustomizing();
-  let openFileButton = document.getElementById("open-file-button");
-  let palette = document.getElementById("customization-palette");
-  simulateItemDrag(openFileButton, palette);
-  ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's still a non-collapsed toolbar.");
-  ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes.");
-
-  toolbarElement.collapsed = true;
-  ok(CustomizableUI.inDefaultState, "Should be in default state because there's now just a collapsed toolbar.");
-  toolbarElement.collapsed = false;
-  ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's a non-collapsed toolbar again.");
-  await endCustomizing();
-  ok(CustomizableUI.inDefaultState, "Should be in default state because the toolbar should have been removed.");
-
-  newWindow = await openAndLoadWindow({}, true);
-  ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar1"),
-     "Attribute should be gone in new window");
-  await promiseWindowClosed(newWindow);
-
-  ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM.");
-  cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
-  is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
-});
-
-add_task(async function resetShouldDealWithCustomToolbars() {
-  ok(gNavToolbox.toolbarset, "There should be a toolbarset");
-  let toolbarID = "testAustralisCustomToolbar";
-  gNavToolbox.appendCustomToolbar(toolbarID, "");
-  let toolbarDOMID = getToolboxCustomToolbarId(toolbarID);
-  let toolbarElement = document.getElementById(toolbarDOMID);
-  ok(toolbarElement, "There should be a toolbar");
-  if (!toolbarElement) {
-    ok(false, "No toolbar created, bailing out of the test.");
-    return;
-  }
-  is(toolbarElement.nextSibling, gNavToolbox.toolbarset,
-     "Toolbar should have been inserted in toolbox, before toolbarset element");
-  let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
-  is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
-     "CustomizableUI should know the area and think it's a toolbar");
-  if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) {
-    ok(false, "Toolbar not registered successfully, bailing out of the test.");
-    toolbarElement.remove();
-    return;
-  }
-  ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet.");
-  CustomizableUI.addWidgetToArea(kXULWidgetId, toolbarDOMID, 0);
-  ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
-  assertAreaPlacements(toolbarDOMID, [kXULWidgetId]);
-
-  gNavToolbox.toolbarset.setAttribute("toolbar2", `${toolbarID}:${kXULWidgetId}`);
-  document.persist(gNavToolbox.toolbarset.id, "toolbar2");
-
-  let newWindow = await openAndLoadWindow({}, true);
-  is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar2"),
-     gNavToolbox.toolbarset.getAttribute("toolbar2"),
-     "Attribute should be the same in new window");
-  await promiseWindowClosed(newWindow);
-
-  CustomizableUI.reset();
-
-  newWindow = await openAndLoadWindow({}, true);
-  ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar2"),
-     "Attribute should be gone in new window");
-  await promiseWindowClosed(newWindow);
-
-  ok(CustomizableUI.inDefaultState, "Should be in default state after reset.");
-  let xulButton = document.getElementById(kXULWidgetId);
-  ok(!xulButton, "XUL button shouldn't be in the document anymore.");
-  ok(gNavToolbox.palette.querySelector(`#${kXULWidgetId}`), "XUL button should be in the palette");
-  ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes.");
-  ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM.");
-  cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
-  is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
-});
-
-
-add_task(async function() {
-  let newWin = await openAndLoadWindow({}, true);
-  ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar1"), "New window shouldn't have attribute toolbar1");
-  ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar2"), "New window shouldn't have attribute toolbar2");
-  await promiseWindowClosed(newWin);
-});
--- a/browser/components/customizableui/test/browser_newtab_button_customizemode.js
+++ b/browser/components/customizableui/test/browser_newtab_button_customizemode.js
@@ -27,46 +27,49 @@ function assertNewTabButton(which) {
   }
 }
 
 /**
  * Add and remove items *after* the new tab button in customize mode.
  */
 add_task(async function addremove_after_newtab_customizemode() {
   await startCustomizing();
-  simulateItemDrag(document.getElementById("home-button"),
-                   kGlobalNewTabButton.parentNode.nextElementSibling);
+  await waitForElementShown(kGlobalNewTabButton);
+  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "end");
   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should have the adjacent newtab attribute");
   await endCustomizing();
   assertNewTabButton("inner");
 
   await startCustomizing();
-  simulateItemDrag(document.getElementById("home-button"),
-    document.getElementById("stop-reload-button").parentNode.nextElementSibling);
+  let dropTarget = document.getElementById("stop-reload-button");
+  await waitForElementShown(dropTarget);
+  simulateItemDrag(document.getElementById("home-button"), dropTarget, "end");
   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should still have the adjacent newtab attribute");
   await endCustomizing();
   assertNewTabButton("inner");
   ok(CustomizableUI.inDefaultState, "Should be in default state");
 });
 
 /**
  * Add and remove items *before* the new tab button in customize mode.
  */
 add_task(async function addremove_before_newtab_customizemode() {
   await startCustomizing();
-  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
+  await waitForElementShown(kGlobalNewTabButton);
+  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "start");
   ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should no longer have the adjacent newtab attribute");
   await endCustomizing();
   assertNewTabButton("global");
   await startCustomizing();
-  simulateItemDrag(document.getElementById("home-button"),
-    document.getElementById("stop-reload-button").parentNode.nextElementSibling);
+  let dropTarget = document.getElementById("stop-reload-button");
+  await waitForElementShown(dropTarget);
+  simulateItemDrag(document.getElementById("home-button"), dropTarget, "end");
   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should have the adjacent newtab attribute again");
   await endCustomizing();
   assertNewTabButton("inner");
   ok(CustomizableUI.inDefaultState, "Should be in default state");
 });
 
 /**
@@ -105,17 +108,18 @@ add_task(async function addremove_before
   ok(CustomizableUI.inDefaultState, "Should be in default state");
 });
 
 /**
   * Reset to defaults in customize mode to see if that doesn't break things.
   */
 add_task(async function reset_before_newtab_customizemode() {
   await startCustomizing();
-  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
+  await waitForElementShown(kGlobalNewTabButton);
+  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "start");
   ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should no longer have the adjacent newtab attribute");
   await endCustomizing();
   assertNewTabButton("global");
   await startCustomizing();
   await gCustomizeMode.reset();
   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
     "tabs should have the adjacent newtab attribute again");
--- a/browser/components/customizableui/test/browser_panelUINotifications.js
+++ b/browser/components/customizableui/test/browser_panelUINotifications.js
@@ -187,25 +187,16 @@ add_task(async function testMultipleBadg
     is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
     AppMenuNotifications.showBadgeOnlyNotification("update-succeeded");
     is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
 
     AppMenuNotifications.showBadgeOnlyNotification("update-failed");
     is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
-    AppMenuNotifications.showBadgeOnlyNotification("download-severe");
-    is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
-
-    AppMenuNotifications.showBadgeOnlyNotification("download-warning");
-    is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
-
-    AppMenuNotifications.removeNotification(/^download-/);
-    is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
-
     AppMenuNotifications.removeNotification(/^update-/);
     is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
     AppMenuNotifications.removeNotification(/^fxa-/);
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
 
     await PanelUI.show();
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
--- a/browser/components/customizableui/test/browser_remove_customized_specials.js
+++ b/browser/components/customizableui/test/browser_remove_customized_specials.js
@@ -10,19 +10,20 @@ add_task(async function() {
   await startCustomizing();
   CustomizableUI.addWidgetToArea("spring", "nav-bar", 5);
   await gCustomizeMode.reset();
   let springs = document.querySelectorAll("#nav-bar toolbarspring");
   let lastSpring = springs[springs.length - 1];
   let expectedPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
   info("Placements before drag: " + expectedPlacements.join(","));
   let lastItem = document.getElementById(expectedPlacements[expectedPlacements.length - 1]);
-  simulateItemDrag(lastSpring, lastItem);
+  await waitForElementShown(lastItem);
+  simulateItemDrag(lastSpring, lastItem, "end");
   expectedPlacements.splice(expectedPlacements.indexOf(lastSpring.id), 1);
-  expectedPlacements.splice(expectedPlacements.length - 1, 0, lastSpring.id);
+  expectedPlacements.push(lastSpring.id);
   let actualPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
   // Log these separately because Assert.deepEqual truncates the stringified versions...
   info("Actual placements: " + actualPlacements.join(","));
   info("Expected placements: " + expectedPlacements.join(","));
   Assert.deepEqual(expectedPlacements, actualPlacements, "Should be able to move spring");
   await gCustomizeMode.reset();
   await endCustomizing();
 });
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -98,20 +98,16 @@ function removeCustomToolbars() {
       if (panel)
         panel.remove();
     }
     tb.remove();
   }
   gAddedToolbars.clear();
 }
 
-function getToolboxCustomToolbarId(toolbarName) {
-  return "__customToolbar_" + toolbarName.replace(" ", "_");
-}
-
 function resetCustomization() {
   return CustomizableUI.reset();
 }
 
 function isInDevEdition() {
   return AppConstants.MOZ_DEV_EDITION;
 }
 
@@ -168,18 +164,32 @@ function todoAssertAreaPlacements(areaId
   todo(isPassing, "The area placements for " + areaId +
                   " should equal the expected placements.");
 }
 
 function getAreaWidgetIds(areaId) {
   return CustomizableUI.getWidgetIdsInArea(areaId);
 }
 
-function simulateItemDrag(aToDrag, aTarget) {
-  synthesizeDrop(aToDrag.parentNode, aTarget);
+function simulateItemDrag(aToDrag, aTarget, aEvent = {}) {
+  let ev = aEvent;
+  if (ev == "end" || ev == "start") {
+    let win = aTarget.ownerGlobal;
+    win.QueryInterface(Ci.nsIInterfaceRequestor);
+    const dwu = win.getInterface(Ci.nsIDOMWindowUtils);
+    let bounds = dwu.getBoundsWithoutFlushing(aTarget);
+    if (ev == "end") {
+      ev = {clientX: bounds.right - 2, clientY: bounds.bottom - 2};
+    } else {
+      ev = {clientX: bounds.left + 2, clientY: bounds.top + 2};
+    }
+  }
+  ev._domDispatchOnly = true;
+  synthesizeDrop(aToDrag.parentNode, aTarget, null, null,
+                 aToDrag.ownerGlobal, aTarget.ownerGlobal, ev);
 }
 
 function endCustomizing(aWindow = window) {
   if (aWindow.document.documentElement.getAttribute("customizing") != "true") {
     return true;
   }
   return new Promise(resolve => {
     function onCustomizationEnds() {
@@ -496,17 +506,21 @@ function checkContextMenu(aContextMenu, 
       is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
     } catch (e) {
       ok(false, "Exception when checking context menu: " + e);
     }
   }
 }
 
 function waitForOverflowButtonShown(win = window) {
+  let ov = win.document.getElementById("nav-bar-overflow-button");
+  let icon = win.document.getAnonymousElementByAttribute(ov, "class", "toolbarbutton-icon");
+  return waitForElementShown(icon);
+}
+function waitForElementShown(element) {
+  let win = element.ownerGlobal;
   let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
   return BrowserTestUtils.waitForCondition(() => {
     info("Waiting for overflow button to have non-0 size");
-    let ov = win.document.getElementById("nav-bar-overflow-button");
-    let icon = win.document.getAnonymousElementByAttribute(ov, "class", "toolbarbutton-icon");
-    let bounds = dwu.getBoundsWithoutFlushing(icon);
+    let bounds = dwu.getBoundsWithoutFlushing(element);
     return bounds.width > 0 && bounds.height > 0;
   });
 }
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -36,18 +36,16 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
-  AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
-  CustomizableUI: "resource:///modules/CustomizableUI.jsm",
   DownloadHistory: "resource://gre/modules/DownloadHistory.jsm",
   Downloads: "resource://gre/modules/Downloads.jsm",
   DownloadUIHelper: "resource://gre/modules/DownloadUIHelper.jsm",
   DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   RecentWindow: "resource:///modules/RecentWindow.jsm",
@@ -1054,16 +1052,29 @@ const DownloadsViewPrototype = {
   /**
    * Private function used to refresh an individual view.
    *
    * @note Subclasses should override this.
    */
   _updateView() {
     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   },
+
+  /**
+   * Computes aggregate values and propagates the changes to our views.
+   */
+  _updateViews() {
+    // Do not update the status indicators during batch loads of download items.
+    if (this._loading) {
+      return;
+    }
+
+    this._refreshProperties();
+    this._views.forEach(this._updateView, this);
+  },
 };
 
 // DownloadsIndicatorData
 
 /**
  * This object registers itself with DownloadsData as a view, and transforms the
  * notifications it receives into overall status data, that is then broadcast to
  * the registered download status indicators.
@@ -1168,41 +1179,16 @@ DownloadsIndicatorDataCtor.prototype = {
     this._attentionSuppressed = aValue;
     this._attention = DownloadsCommon.ATTENTION_NONE;
     this._updateViews();
     return aValue;
   },
   _attentionSuppressed: false,
 
   /**
-   * Computes aggregate values and propagates the changes to our views.
-   */
-  _updateViews() {
-    // Do not update the status indicators during batch loads of download items.
-    if (this._loading) {
-      return;
-    }
-
-    this._refreshProperties();
-
-    let widgetGroup = CustomizableUI.getWidget("downloads-button");
-    let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
-    if (inMenu) {
-      if (this._attention == DownloadsCommon.ATTENTION_NONE) {
-        AppMenuNotifications.removeNotification(/^download-/);
-      } else {
-        let badgeClass = "download-" + this._attention;
-        AppMenuNotifications.showBadgeOnlyNotification(badgeClass);
-      }
-    }
-
-    this._views.forEach(this._updateView, this);
-  },
-
-  /**
    * Updates the specified view with the current aggregate values.
    *
    * @param aView
    *        DownloadsIndicatorView object to be updated.
    */
   _updateView(aView) {
     aView.hasDownloads = this._hasDownloads;
     aView.percentComplete = this._percentComplete;
@@ -1352,29 +1338,16 @@ DownloadsSummaryData.prototype = {
     let itemIndex = this._downloads.indexOf(download);
     this._downloads.splice(itemIndex, 1);
     this._updateViews();
   },
 
   // Propagation of properties to our views
 
   /**
-   * Computes aggregate values and propagates the changes to our views.
-   */
-  _updateViews() {
-    // Do not update the status indicators during batch loads of download items.
-    if (this._loading) {
-      return;
-    }
-
-    this._refreshProperties();
-    this._views.forEach(this._updateView, this);
-  },
-
-  /**
    * Updates the specified view with the current aggregate values.
    *
    * @param aView
    *        DownloadsIndicatorView object to be updated.
    */
   _updateView(aView) {
     aView.showingProgress = this._showingProgress;
     aView.percentComplete = this._percentComplete;
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -0,0 +1,358 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  NetUtil: "resource://gre/modules/NetUtil.jsm",
+  Policies: "resource:///modules/policies/Policies.jsm",
+  PoliciesValidator: "resource:///modules/policies/PoliciesValidator.jsm",
+});
+
+// This is the file that will be searched for in the
+// ${InstallDir}/distribution folder.
+const POLICIES_FILENAME = "policies.json";
+
+// For easy testing, modify the helpers/sample.json file,
+// and set PREF_ALTERNATE_PATH in firefox.js as:
+// /your/repo/browser/components/enterprisepolicies/helpers/sample.json
+const PREF_ALTERNATE_PATH     = "browser.policies.alternatePath";
+
+// This pref is meant to be temporary: it will only be used while we're
+// testing this feature without rolling it out officially. When the
+// policy engine is released, this pref should be removed.
+const PREF_ENABLED            = "browser.policies.enabled";
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Enterprise Policies",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+// ==== Start XPCOM Boilerplate ==== \\
+
+// Factory object
+const EnterprisePoliciesFactory = {
+  _instance: null,
+  createInstance: function BGSF_createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this._instance == null ?
+      this._instance = new EnterprisePoliciesManager() : this._instance;
+  }
+};
+
+// ==== End XPCOM Boilerplate ==== //
+
+// Constructor
+function EnterprisePoliciesManager() {
+  Services.obs.addObserver(this, "profile-after-change", true);
+  Services.obs.addObserver(this, "final-ui-startup", true);
+  Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+  Services.obs.addObserver(this, "EnterprisePolicies:Restart", true);
+}
+
+EnterprisePoliciesManager.prototype = {
+  // for XPCOM
+  classID:          Components.ID("{ea4e1414-779b-458b-9d1f-d18e8efbc145}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIEnterprisePolicies]),
+
+  // redefine the default factory for XPCOMUtils
+  _xpcom_factory: EnterprisePoliciesFactory,
+
+  _initialize() {
+    if (!Services.prefs.getBoolPref(PREF_ENABLED, false)) {
+      this.status = Ci.nsIEnterprisePolicies.INACTIVE;
+      return;
+    }
+
+    this._file = new JSONFileReader(getConfigurationFile());
+    this._file.readData();
+
+    if (!this._file.exists) {
+      this.status = Ci.nsIEnterprisePolicies.INACTIVE;
+      return;
+    }
+
+    if (this._file.failed) {
+      this.status = Ci.nsIEnterprisePolicies.FAILED;
+      return;
+    }
+
+    this.status = Ci.nsIEnterprisePolicies.ACTIVE;
+    this._activatePolicies();
+  },
+
+  _activatePolicies() {
+    let { schema } = Cu.import("resource:///modules/policies/schema.jsm", {});
+    let json = this._file.json;
+
+    for (let policyName of Object.keys(json.policies)) {
+      let policySchema = schema.properties[policyName];
+      let policyParameters = json.policies[policyName];
+
+      if (!policySchema) {
+        log.error(`Unknown policy: ${policyName}`);
+        continue;
+      }
+
+      let [parametersAreValid, parsedParameters] =
+        PoliciesValidator.validateAndParseParameters(policyParameters,
+                                                     policySchema);
+
+      if (!parametersAreValid) {
+        log.error(`Invalid parameters specified for ${policyName}.`);
+        continue;
+      }
+
+      let policyImpl = Policies[policyName];
+
+      for (let timing of Object.keys(this._callbacks)) {
+        let policyCallback = policyImpl["on" + timing];
+        if (policyCallback) {
+          this._schedulePolicyCallback(
+            timing,
+            policyCallback.bind(null,
+                                this, /* the EnterprisePoliciesManager */
+                                parsedParameters));
+        }
+      }
+    }
+  },
+
+  _callbacks: {
+    BeforeAddons: [],
+    ProfileAfterChange: [],
+    BeforeUIStartup: [],
+    AllWindowsRestored: [],
+  },
+
+  _schedulePolicyCallback(timing, callback) {
+    this._callbacks[timing].push(callback);
+  },
+
+  _runPoliciesCallbacks(timing) {
+    let callbacks = this._callbacks[timing];
+    while (callbacks.length > 0) {
+      let callback = callbacks.shift();
+      try {
+        callback();
+      } catch (ex) {
+        log.error("Error running ", callback, `for ${timing}:`, ex);
+      }
+    }
+  },
+
+  async _restart() {
+    if (!Cu.isInAutomation) {
+      return;
+    }
+
+    DisallowedFeatures = {};
+
+    this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED;
+    for (let timing of Object.keys(this._callbacks)) {
+      this._callbacks[timing] = [];
+    }
+    delete Services.ppmm.initialProcessData.policies;
+    Services.ppmm.broadcastAsyncMessage("EnterprisePolicies:Restart", null);
+
+    let { PromiseUtils } = Cu.import("resource://gre/modules/PromiseUtils.jsm",
+                                     {});
+
+    // Simulate the startup process. This step-by-step is a bit ugly but it
+    // tries to emulate the same behavior as of a normal startup.
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "policies-startup", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "profile-after-change", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "final-ui-startup", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "sessionstore-windows-restored", null);
+    });
+  },
+
+  // nsIObserver implementation
+  observe: function BG_observe(subject, topic, data) {
+    switch (topic) {
+      case "policies-startup":
+        this._initialize();
+        this._runPoliciesCallbacks("BeforeAddons");
+        break;
+
+      case "profile-after-change":
+        // Before the first set of policy callbacks runs, we must
+        // initialize the service.
+        this._runPoliciesCallbacks("ProfileAfterChange");
+        break;
+
+      case "final-ui-startup":
+        this._runPoliciesCallbacks("BeforeUIStartup");
+        break;
+
+      case "sessionstore-windows-restored":
+        this._runPoliciesCallbacks("AllWindowsRestored");
+
+        // After the last set of policy callbacks ran, notify the test observer.
+        Services.obs.notifyObservers(null,
+                                     "EnterprisePolicies:AllPoliciesApplied");
+        break;
+
+      case "EnterprisePolicies:Restart":
+        this._restart().then(null, Cu.reportError);
+        break;
+    }
+  },
+
+  disallowFeature(feature, neededOnContentProcess = false) {
+    DisallowedFeatures[feature] = true;
+
+    // NOTE: For optimization purposes, only features marked as needed
+    // on content process will be passed onto the child processes.
+    if (neededOnContentProcess) {
+      Services.ppmm.initialProcessData.policies
+                                      .disallowedFeatures.push(feature);
+
+      if (Services.ppmm.childCount > 1) {
+        // If there has been a content process already initialized, let's
+        // broadcast the newly disallowed feature.
+        Services.ppmm.broadcastAsyncMessage(
+          "EnterprisePolicies:DisallowFeature", {feature}
+        );
+      }
+    }
+  },
+
+  // ------------------------------
+  // public nsIEnterprisePolicies members
+  // ------------------------------
+
+  _status: Ci.nsIEnterprisePolicies.UNINITIALIZED,
+
+  set status(val) {
+    this._status = val;
+    if (val != Ci.nsIEnterprisePolicies.INACTIVE) {
+      Services.ppmm.initialProcessData.policies = {
+        status: val,
+        disallowedFeatures: [],
+      };
+    }
+    return val;
+  },
+
+  get status() {
+    return this._status;
+  },
+
+  isAllowed: function BG_sanitize(feature) {
+    return !(feature in DisallowedFeatures);
+  },
+};
+
+let DisallowedFeatures = {};
+
+function JSONFileReader(file) {
+  this._file = file;
+  this._data = {
+    exists: null,
+    failed: false,
+    json: null,
+  };
+}
+
+JSONFileReader.prototype = {
+  get exists() {
+    if (this._data.exists === null) {
+      this.readData();
+    }
+
+    return this._data.exists;
+  },
+
+  get failed() {
+    return this._data.failed;
+  },
+
+  get json() {
+    if (this._data.failed) {
+      return null;
+    }
+
+    if (this._data.json === null) {
+      this.readData();
+    }
+
+    return this._data.json;
+  },
+
+  readData() {
+    try {
+      let data = Cu.readUTF8File(this._file);
+      if (data) {
+        this._data.exists = true;
+        this._data.json = JSON.parse(data);
+      } else {
+        this._data.exists = false;
+      }
+    } catch (ex) {
+      if (ex instanceof Components.Exception &&
+          ex.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
+        this._data.exists = false;
+      } else if (ex instanceof SyntaxError) {
+        log.error("Error parsing JSON file");
+        this._data.failed = true;
+      } else {
+        log.error("Error reading file");
+        this._data.failed = true;
+      }
+    }
+  }
+};
+
+function getConfigurationFile() {
+  let configFile = Services.dirsvc.get("XREAppDist", Ci.nsIFile);
+  configFile.append(POLICIES_FILENAME);
+
+  let prefType = Services.prefs.getPrefType(PREF_ALTERNATE_PATH);
+
+  if ((prefType == Services.prefs.PREF_STRING) && !configFile.exists()) {
+    // We only want to use the alternate file path if the file on the install
+    // folder doesn't exist. Otherwise it'd be possible for a user to override
+    // the admin-provided policies by changing the user-controlled prefs.
+    // This pref is only meant for tests, so it's fine to use this extra
+    // synchronous configFile.exists() above.
+    configFile = Cc["@mozilla.org/file/local;1"]
+                   .createInstance(Ci.nsIFile);
+    let alternatePath = Services.prefs.getStringPref(PREF_ALTERNATE_PATH);
+    configFile.initWithPath(alternatePath);
+  }
+
+  return configFile;
+}
+
+var components = [EnterprisePoliciesManager];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.manifest
@@ -0,0 +1,5 @@
+component {ea4e1414-779b-458b-9d1f-d18e8efbc145} EnterprisePolicies.js process=main
+contract @mozilla.org/browser/enterprisepolicies;1 {ea4e1414-779b-458b-9d1f-d18e8efbc145} process=main
+
+component {dc6358f8-d167-4566-bf5b-4350b5e6a7a2} EnterprisePoliciesContent.js process=content
+contract @mozilla.org/browser/enterprisepolicies;1 {dc6358f8-d167-4566-bf5b-4350b5e6a7a2} process=content
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePoliciesContent.js
@@ -0,0 +1,91 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Enterprise Policies Child",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+
+// ==== Start XPCOM Boilerplate ==== \\
+
+// Factory object
+const EnterprisePoliciesFactory = {
+  _instance: null,
+  createInstance: function BGSF_createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this._instance == null ?
+      this._instance = new EnterprisePoliciesManagerContent() : this._instance;
+  }
+};
+
+// ==== End XPCOM Boilerplate ==== //
+
+
+function EnterprisePoliciesManagerContent() {
+  let policies = Services.cpmm.initialProcessData.policies;
+  if (policies) {
+    this._status = policies.status;
+    // make a copy of the array so that we can keep adding to it
+    // in a way that is not confusing.
+    this._disallowedFeatures = policies.disallowedFeatures.slice();
+  }
+
+  Services.cpmm.addMessageListener("EnterprisePolicies:DisallowFeature", this);
+  Services.cpmm.addMessageListener("EnterprisePolicies:Restart", this);
+}
+
+EnterprisePoliciesManagerContent.prototype = {
+  // for XPCOM
+  classID:          Components.ID("{dc6358f8-d167-4566-bf5b-4350b5e6a7a2}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+                                         Ci.nsIEnterprisePolicies]),
+
+  // redefine the default factory for XPCOMUtils
+  _xpcom_factory: EnterprisePoliciesFactory,
+
+  _status: Ci.nsIEnterprisePolicies.INACTIVE,
+
+  _disallowedFeatures: [],
+
+  receiveMessage({name, data}) {
+    switch (name) {
+      case "EnterprisePolicies:DisallowFeature":
+        this._disallowedFeatures.push(data.feature);
+        break;
+
+      case "EnterprisePolicies:Restart":
+        this._disallowedFeatures = [];
+        break;
+    }
+  },
+
+  get status() {
+    return this._status;
+  },
+
+  isAllowed(feature) {
+    return !this._disallowedFeatures.includes(feature);
+  }
+};
+
+var components = [EnterprisePoliciesManagerContent];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -0,0 +1,123 @@
+/* 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 Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Policies.jsm",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+this.EXPORTED_SYMBOLS = ["Policies"];
+
+this.Policies = {
+  "block_about_config": {
+    onBeforeUIStartup(manager, param) {
+      if (param == true) {
+        manager.disallowFeature("about:config", true);
+      }
+    }
+  },
+
+  "dont_check_default_browser": {
+    onBeforeUIStartup(manager, param) {
+      setAndLockPref("browser.shell.checkDefaultBrowser", false);
+    }
+  },
+
+  "flash_plugin": {
+    onBeforeUIStartup(manager, param) {
+      addAllowDenyPermissions("plugin:flash", param.allow, param.block);
+    }
+  },
+
+  "popups": {
+    onBeforeUIStartup(manager, param) {
+      addAllowDenyPermissions("popup", param.allow, param.block);
+    }
+  },
+
+  "install_addons": {
+    onBeforeUIStartup(manager, param) {
+      addAllowDenyPermissions("install", param.allow, param.block);
+    }
+  },
+
+  "cookies": {
+    onBeforeUIStartup(manager, param) {
+      addAllowDenyPermissions("cookie", param.allow, param.block);
+    }
+  },
+};
+
+/*
+ * ====================
+ * = HELPER FUNCTIONS =
+ * ====================
+ *
+ * The functions below are helpers to be used by several policies.
+ */
+
+function setAndLockPref(prefName, prefValue) {
+  if (Services.prefs.prefIsLocked(prefName)) {
+    Services.prefs.unlockPref(prefName);
+  }
+
+  let defaults = Services.prefs.getDefaultBranch("");
+
+  switch (typeof(prefValue)) {
+    case "boolean":
+      defaults.setBoolPref(prefName, prefValue);
+      break;
+
+    case "number":
+      if (!Number.isInteger(prefValue)) {
+        throw new Error(`Non-integer value for ${prefName}`);
+      }
+
+      defaults.setIntPref(prefName, prefValue);
+      break;
+
+    case "string":
+      defaults.setStringPref(prefName, prefValue);
+      break;
+  }
+
+  Services.prefs.lockPref(prefName);
+}
+
+function addAllowDenyPermissions(permissionName, allowList, blockList) {
+  allowList = allowList || [];
+  blockList = blockList || [];
+
+  for (let origin of allowList) {
+    Services.perms.add(origin,
+                       permissionName,
+                       Ci.nsIPermissionManager.ALLOW_ACTION,
+                       Ci.nsIPermissionManager.EXPIRE_POLICY);
+  }
+
+  for (let origin of blockList) {
+    Services.perms.add(origin,
+                       permissionName,
+                       Ci.nsIPermissionManager.DENY_ACTION,
+                       Ci.nsIPermissionManager.EXPIRE_POLICY);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/PoliciesValidator.jsm
@@ -0,0 +1,148 @@
+/* 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 Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "PoliciesValidator.jsm",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+this.EXPORTED_SYMBOLS = ["PoliciesValidator"];
+
+this.PoliciesValidator = {
+  validateAndParseParameters(param, properties) {
+    return validateAndParseParamRecursive(param, properties);
+  }
+};
+
+function validateAndParseParamRecursive(param, properties) {
+  if (properties.enum) {
+    if (properties.enum.includes(param)) {
+      return [true, param];
+    }
+    return [false, null];
+  }
+
+  log.debug(`checking @${param}@ for type ${properties.type}`);
+  switch (properties.type) {
+    case "boolean":
+    case "number":
+    case "integer":
+    case "string":
+    case "URL":
+    case "origin":
+      return validateAndParseSimpleParam(param, properties.type);
+
+    case "array":
+      if (!Array.isArray(param)) {
+        log.error("Array expected but not received");
+        return [false, null];
+      }
+
+      let parsedArray = [];
+      for (let item of param) {
+        log.debug(`in array, checking @${item}@ for type ${properties.items.type}`);
+        let [valid, parsedValue] = validateAndParseParamRecursive(item, properties.items);
+        if (!valid) {
+          return [false, null];
+        }
+
+        parsedArray.push(parsedValue);
+      }
+
+      return [true, parsedArray];
+
+    case "object": {
+      if (typeof(param) != "object") {
+        log.error("Object expected but not received");
+        return [false, null];
+      }
+
+      let parsedObj = {};
+      for (let property of Object.keys(properties.properties)) {
+        log.debug(`in object, for property ${property} checking @${param[property]}@ for type ${properties.properties[property].type}`);
+        let [valid, parsedValue] = validateAndParseParamRecursive(param[property], properties.properties[property]);
+        if (!valid) {
+          return [false, null];
+        }
+
+        parsedObj[property] = parsedValue;
+      }
+
+      return [true, parsedObj];
+    }
+  }
+
+  return [false, null];
+}
+
+function validateAndParseSimpleParam(param, type) {
+  let valid = false;
+  let parsedParam = param;
+
+  switch (type) {
+    case "boolean":
+    case "number":
+    case "string":
+      valid = (typeof(param) == type);
+      break;
+
+    // integer is an alias to "number" that some JSON schema tools use
+    case "integer":
+      valid = (typeof(param) == "number");
+      break;
+
+    case "origin":
+      if (typeof(param) != "string") {
+        break;
+      }
+
+      try {
+        parsedParam = Services.io.newURI(param);
+
+        let pathQueryRef = parsedParam.pathQueryRef;
+        // Make sure that "origin" types won't accept full URLs.
+        if (pathQueryRef != "/" && pathQueryRef != "") {
+          valid = false;
+        } else {
+          valid = true;
+        }
+      } catch (ex) {
+        valid = false;
+      }
+      break;
+
+    case "URL":
+      if (typeof(param) != "string") {
+        break;
+      }
+
+      try {
+        parsedParam = Services.io.newURI(param);
+        valid = true;
+      } catch (ex) {
+        valid = false;
+      }
+      break;
+  }
+
+  return [valid, parsedParam];
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/sample.json
@@ -0,0 +1,18 @@
+{
+  "policies": {
+    "block_about_config": true,
+    "dont_check_default_browser": true,
+
+    "flash_plugin": {
+      "allow": [
+        "https://www.example.com"
+      ],
+
+      "block": [
+        "https://www.example.org"
+      ]
+    },
+
+    "block_about_profiles": true
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
+
+DIRS += [
+    'helpers',
+    'schemas',
+]
+
+TEST_DIRS += [
+	'tests'
+]
+
+EXTRA_COMPONENTS += [
+    'EnterprisePolicies.js',
+    'EnterprisePolicies.manifest',
+    'EnterprisePoliciesContent.js',
+]
+
+EXTRA_JS_MODULES.policies += [
+    'Policies.jsm',
+    'PoliciesValidator.jsm',
+]
+
+FINAL_LIBRARY = 'browsercomps'
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/configuration.json
@@ -0,0 +1,10 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "properties": {
+    "policies": {
+      "$ref": "policies.json"
+    }
+  },
+  "required": ["policies"]
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
+
+EXTRA_PP_JS_MODULES.policies += [
+    'schema.jsm',
+]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -0,0 +1,109 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "properties": {
+    "block_about_config": {
+      "description": "Blocks access to the about:config page.",
+      "first_available": "60.0",
+
+      "type": "boolean",
+      "enum": [true]
+    },
+
+    "dont_check_default_browser": {
+      "description": "Don't check for the default browser on startup.",
+      "first_available": "60.0",
+
+      "type": "boolean",
+      "enum": [true]
+    },
+
+    "flash_plugin": {
+      "description": "Allow or deny flash plugin usage.",
+      "first_available": "60.0",
+
+      "type": "object",
+      "properties": {
+        "allow": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        },
+
+        "block": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        }
+      }
+    },
+
+    "popups": {
+      "description": "Allow or deny popup usage.",
+      "first_available": "60.0",
+
+      "type": "object",
+      "properties": {
+        "allow": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        },
+
+        "block": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        }
+      }
+    },
+
+    "install_addons": {
+      "description": "Allow or deny popup websites to install webextensions.",
+      "first_available": "60.0",
+
+      "type": "object",
+      "properties": {
+        "allow": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        },
+
+        "block": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        }
+      }
+    },
+
+    "cookies": {
+      "description": "Allow or deny websites to set cookies.",
+      "first_available": "60.0",
+
+      "type": "object",
+      "properties": {
+        "allow": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        },
+
+        "block": {
+          "type": "array",
+          "items": {
+            "type": "origin"
+          }
+        }
+      }
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/schema.jsm
@@ -0,0 +1,10 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["schema"];
+
+this.schema =
+#include policies-schema.json
copy from browser/components/newtab/tests/browser/.eslintrc.js
copy to browser/components/enterprisepolicies/tests/browser/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+prefs =
+  browser.policies.enabled=true
+support-files =
+  head.js
+  config_dont_check_default_browser.json
+  config_popups_cookies_addons_flash.json
+  config_setAndLockPref.json
+  config_simple_policies.json
+  config_broken_json.json
+
+[browser_policies_broken_json.js]
+[browser_policies_popups_cookies_addons_flash.js]
+[browser_policies_setAndLockPref_API.js]
+[browser_policies_simple_policies.js]
+[browser_policies_validate_and_parse_API.js]
+[browser_policy_default_browser_check.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_clean_slate() {
+  await startWithCleanSlate();
+});
+
+add_task(async function test_broken_json() {
+  await setupPolicyEngineWithJson("config_broken_json.json");
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.FAILED, "Engine was correctly set to the error state");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_popups_cookies_addons_flash.js
@@ -0,0 +1,121 @@
+/* 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";
+
+function URI(str) {
+  return Services.io.newURI(str);
+}
+
+add_task(async function test_start_with_disabled_engine() {
+  await startWithCleanSlate();
+});
+
+add_task(async function test_setup_preexisting_permissions() {
+  // Pre-existing ALLOW permissions that should be overriden
+  // with DENY.
+  Services.perms.add(URI("https://www.pre-existing-allow.com"),
+                     "popup",
+                     Ci.nsIPermissionManager.ALLOW_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-allow.com"),
+                     "install",
+                     Ci.nsIPermissionManager.ALLOW_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-allow.com"),
+                     "cookie",
+                     Ci.nsIPermissionManager.ALLOW_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-allow.com"),
+                     "plugin:flash",
+                     Ci.nsIPermissionManager.ALLOW_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  // Pre-existing DENY permissions that should be overriden
+  // with ALLOW.
+  Services.perms.add(URI("https://www.pre-existing-deny.com"),
+                     "popup",
+                     Ci.nsIPermissionManager.DENY_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-deny.com"),
+                     "install",
+                     Ci.nsIPermissionManager.DENY_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-deny.com"),
+                     "cookie",
+                     Ci.nsIPermissionManager.DENY_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  Services.perms.add(URI("https://www.pre-existing-deny.com"),
+                     "plugin:flash",
+                     Ci.nsIPermissionManager.DENY_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+});
+
+add_task(async function test_setup_activate_policies() {
+  await setupPolicyEngineWithJson("config_popups_cookies_addons_flash.json");
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.ACTIVE, "Engine is active");
+});
+
+function checkPermission(url, expected, permissionName) {
+  let expectedValue = Ci.nsIPermissionManager[`${expected}_ACTION`];
+  let uri = Services.io.newURI(`https://www.${url}`);
+
+  is(Services.perms.testPermission(uri, permissionName),
+    expectedValue,
+    `Correct (${permissionName}=${expected}) for URL ${url}`);
+
+  if (expected != "UNKNOWN") {
+    let permission = Services.perms.getPermissionObjectForURI(
+      uri, permissionName, true);
+    ok(permission, "Permission object exists");
+    is(permission.expireType, Ci.nsIPermissionManager.EXPIRE_POLICY,
+       "Permission expireType is correct");
+  }
+}
+
+function checkAllPermissionsForType(type) {
+  checkPermission("allow.com", "ALLOW", type);
+  checkPermission("deny.com", "DENY", type);
+  checkPermission("unknown.com", "UNKNOWN", type);
+  checkPermission("pre-existing-allow.com", "DENY", type);
+  checkPermission("pre-existing-deny.com", "ALLOW", type);
+}
+
+add_task(async function test_popups_policy() {
+  checkAllPermissionsForType("popup");
+});
+
+add_task(async function test_webextensions_policy() {
+  checkAllPermissionsForType("install");
+});
+
+add_task(async function test_cookies_policy() {
+  checkAllPermissionsForType("cookie");
+});
+
+add_task(async function test_flash_policy() {
+  checkAllPermissionsForType("plugin:flash");
+});
+
+add_task(async function test_change_permission() {
+  // Checks that changing a permission will still retain the
+  // value set through the engine.
+  Services.perms.add(URI("https://www.allow.com"), "popup",
+                     Ci.nsIPermissionManager.DENY_ACTION,
+                     Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+  checkPermission("allow.com", "ALLOW", "popup");
+
+  // Also change one un-managed permission to make sure it doesn't
+  // cause any problems to the policy engine or the permission manager.
+  Services.perms.add(URI("https://www.unmanaged.com"), "popup",
+                   Ci.nsIPermissionManager.DENY_ACTION,
+                   Ci.nsIPermissionManager.EXPIRE_SESSION);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_clean_slate() {
+  await startWithCleanSlate();
+});
+
+let { Policies, setAndLockPref } = Cu.import("resource:///modules/policies/Policies.jsm", {});
+
+function checkPref(prefName, expectedValue) {
+  let prefType, prefValue;
+  switch (typeof(expectedValue)) {
+    case "boolean":
+      prefType = Services.prefs.PREF_BOOL;
+      prefValue = Services.prefs.getBoolPref(prefName);
+      break;
+
+    case "number":
+      prefType = Services.prefs.PREF_INT;
+      prefValue = Services.prefs.getIntPref(prefName);
+      break;
+
+    case "string":
+      prefType = Services.prefs.PREF_STRING;
+      prefValue = Services.prefs.getStringPref(prefName);
+      break;
+  }
+
+  ok(Services.prefs.prefIsLocked(prefName), `Pref ${prefName} is correctly locked`);
+  is(Services.prefs.getPrefType(prefName), prefType, `Pref ${prefName} has the correct type`);
+  is(prefValue, expectedValue, `Pref ${prefName} has the correct value`);
+}
+
+add_task(async function test_API_directly() {
+  setAndLockPref("policies.test.boolPref", true);
+  checkPref("policies.test.boolPref", true);
+
+  // Check that a previously-locked pref can be changed
+  // (it will be unlocked first).
+  setAndLockPref("policies.test.boolPref", false);
+  checkPref("policies.test.boolPref", false);
+
+  setAndLockPref("policies.test.intPref", 0);
+  checkPref("policies.test.intPref", 0);
+
+  setAndLockPref("policies.test.stringPref", "policies test");
+  checkPref("policies.test.stringPref", "policies test");
+
+  // Test that user values do not override the prefs, and the get*Pref call
+  // still return the value set through setAndLockPref
+  Services.prefs.setBoolPref("policies.test.boolPref", true);
+  checkPref("policies.test.boolPref", false);
+
+  Services.prefs.setIntPref("policies.test.intPref", 10);
+  checkPref("policies.test.intPref", 0);
+
+  Services.prefs.setStringPref("policies.test.stringPref", "policies test");
+  checkPref("policies.test.stringPref", "policies test");
+
+  try {
+    // Test that a non-integer value is correctly rejected, even though
+    // typeof(val) == "number"
+    setAndLockPref("policies.test.intPref", 1.5);
+    ok(false, "Integer value should be rejected");
+  } catch (ex) {
+    ok(true, "Integer value was rejected");
+  }
+});
+
+add_task(async function test_API_through_policies() {
+  // Ensure that the values received by the policies have the correct
+  // type to make sure things are properly working.
+
+  // Implement functions to handle the three simple policies
+  // that will be added to the schema.
+  Policies.bool_policy = {
+    onBeforeUIStartup(manager, param) {
+      setAndLockPref("policies.test2.boolPref", param);
+    }
+  };
+
+  Policies.int_policy = {
+    onBeforeUIStartup(manager, param) {
+      setAndLockPref("policies.test2.intPref", param);
+    }
+  };
+
+  Policies.string_policy = {
+    onBeforeUIStartup(manager, param) {
+      setAndLockPref("policies.test2.stringPref", param);
+    }
+  };
+
+  await setupPolicyEngineWithJson(
+    "config_setAndLockPref.json",
+    /* custom schema */
+    {
+      properties: {
+        "bool_policy": {
+          "type": "boolean"
+        },
+
+        "int_policy": {
+          "type": "integer"
+        },
+
+        "string_policy": {
+          "type": "string"
+        }
+      }
+    }
+  );
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.ACTIVE, "Engine is active");
+
+  // The expected values come from config_setAndLockPref.json
+  checkPref("policies.test2.boolPref", true);
+  checkPref("policies.test2.intPref", 42);
+  checkPref("policies.test2.stringPref", "policies test 2");
+
+  delete Policies.bool_policy;
+  delete Policies.int_policy;
+  delete Policies.string_policy;
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js
@@ -0,0 +1,101 @@
+/* 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";
+
+add_task(async function test_clean_slate() {
+  await startWithCleanSlate();
+});
+
+add_task(async function test_simple_policies() {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    // Initialize the service in the content process, in case it hasn't
+    // already started.
+    Services.policies;
+  });
+
+  let { Policies } = Cu.import("resource:///modules/policies/Policies.jsm", {});
+
+  let policy0Ran = false, policy1Ran = false, policy2Ran = false, policy3Ran = false;
+
+  // Implement functions to handle the four simple policies that will be added
+  // to the schema.
+  Policies.simple_policy0 = {
+    onProfileAfterChange(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      policy0Ran = true;
+    }
+  };
+
+  Policies.simple_policy1 = {
+    onProfileAfterChange(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      manager.disallowFeature("feature1", /* needed in content process */ true);
+      policy1Ran = true;
+    }
+  };
+
+  Policies.simple_policy2 = {
+    onBeforeUIStartup(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      manager.disallowFeature("feature2", /* needed in content process */ false);
+      policy2Ran = true;
+    }
+  };
+
+  Policies.simple_policy3 = {
+    onAllWindowsRestored(manager, param) {
+      is(param, false, "Param matches what was passed in config file");
+      policy3Ran = true;
+    }
+  };
+
+  await setupPolicyEngineWithJson(
+    "config_simple_policies.json",
+    /* custom schema */
+    {
+      properties: {
+        "simple_policy0": {
+          "type": "boolean"
+        },
+
+        "simple_policy1": {
+          "type": "boolean"
+        },
+
+        "simple_policy2": {
+          "type": "boolean"
+        },
+
+        "simple_policy3": {
+          "type": "boolean"
+        }
+
+      }
+    }
+  );
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.ACTIVE, "Engine is active");
+  is(Services.policies.isAllowed("feature1"), false, "Dummy feature was disallowed");
+  is(Services.policies.isAllowed("feature2"), false, "Dummy feature was disallowed");
+
+  ok(policy0Ran, "Policy 0 ran correctly through BeforeAddons");
+  ok(policy1Ran, "Policy 1 ran correctly through onProfileAfterChange");
+  ok(policy2Ran, "Policy 2 ran correctly through onBeforeUIStartup");
+  ok(policy3Ran, "Policy 3 ran correctly through onAllWindowsRestored");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+      is(Services.policies.isAllowed("feature1"), false, "Correctly disallowed in the content process");
+      // Feature 2 wasn't explictly marked as needed in the content process, so it is not marked
+      // as disallowed there.
+      is(Services.policies.isAllowed("feature2"), true, "Correctly missing in the content process");
+    }
+  });
+
+  delete Policies.simple_policy0;
+  delete Policies.simple_policy1;
+  delete Policies.simple_policy2;
+  delete Policies.simple_policy3;
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
@@ -0,0 +1,231 @@
+/* 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";
+
+/* This file will test the parameters parsing and validation directly through
+   the PoliciesValidator API.
+ */
+
+const { PoliciesValidator } = Cu.import("resource:///modules/policies/PoliciesValidator.jsm", {});
+
+add_task(async function test_boolean_values() {
+  let schema = {
+    type: "boolean"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(true, schema);
+  ok(valid && parsed === true, "Parsed boolean value correctly");
+
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(false, schema);
+  ok(valid && parsed === false, "Parsed boolean value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_number_values() {
+  let schema = {
+    type: "number"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed === 1, "Parsed number value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_integer_values() {
+  // Integer is an alias for number
+  let schema = {
+    type: "integer"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed == 1, "Parsed integer value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_string_values() {
+  let schema = {
+    type: "string"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("foobar", schema);
+  ok(valid && parsed == "foobar", "Parsed string value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_URL_values() {
+  let schema = {
+    type: "URL"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  ok(valid, "URL is valid");
+  ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
+  is(parsed.prePath, "https://www.example.com", "prePath is correct");
+  is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+});
+
+add_task(async function test_origin_values() {
+  // Origin is a URL that doesn't contain a path/query string (i.e., it's only scheme + host + port)
+  let schema = {
+    type: "origin"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com", schema);
+  ok(valid, "Origin is valid");
+  ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
+  is(parsed.prePath, "https://www.example.com", "prePath is correct");
+  is(parsed.pathQueryRef, "/", "pathQueryRef is corect");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
+  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+});
+
+add_task(async function test_array_values() {
+  // The types inside an array object must all be the same
+  let schema = {
+    type: "array",
+    items: {
+      type: "number"
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters([1, 2, 3], schema);
+  ok(valid, "Array is valid");
+  ok(Array.isArray(parsed), "parsed is an array");
+  is(parsed.length, 3, "array is correct");
+
+  // An empty array is also valid
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters([], schema);
+  ok(valid, "Array is valid");
+  ok(Array.isArray(parsed), "parsed is an array");
+  is(parsed.length, 0, "array is correct");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
+  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
+});
+
+add_task(async function test_object_values() {
+  let schema = {
+    type: "object",
+    properties: {
+      url: {
+        type: "URL"
+      },
+      title: {
+        type: "string"
+      }
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+    {
+      url: "https://www.example.com/foo#bar",
+      title: "Foo",
+      alias: "Bar"
+    },
+    schema);
+
+  ok(valid, "Object is valid");
+  ok(typeof(parsed) == "object", "parsed in an object");
+  ok(parsed.url instanceof Ci.nsIURI, "types inside the object are also parsed");
+  is(parsed.url.spec, "https://www.example.com/foo#bar", "URL was correctly parsed");
+  is(parsed.title, "Foo", "title was correctly parsed");
+  is(parsed.alias, undefined, "property not described in the schema is not present in the parsed object");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters(
+    {
+      url: "https://www.example.com/foo#bar",
+      title: 3,
+    },
+    schema)[0], "Mismatched type for title");
+
+  ok(!PoliciesValidator.validateAndParseParameters(
+    {
+      url: "www.example.com",
+      title: 3,
+    },
+    schema)[0], "Invalid URL inside the object");
+});
+
+add_task(async function test_array_of_objects() {
+  // This schema is used, for example, for bookmarks
+  let schema = {
+    type: "array",
+    items: {
+      type: "object",
+      properties: {
+        url: {
+          type: "URL",
+        },
+        title: {
+          type: "string"
+        }
+      }
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+    [{
+      url: "https://www.example.com/bookmark1",
+      title: "Foo",
+    },
+    {
+      url: "https://www.example.com/bookmark2",
+      title: "Bar",
+    }],
+    schema);
+
+  ok(valid, "Array is valid");
+  is(parsed.length, 2, "Correct number of items");
+
+  ok(typeof(parsed[0]) == "object" && typeof(parsed[1]) == "object", "Correct objects inside array");
+
+  is(parsed[0].url.spec, "https://www.example.com/bookmark1", "Correct URL for bookmark 1");
+  is(parsed[1].url.spec, "https://www.example.com/bookmark2", "Correct URL for bookmark 2");
+
+  is(parsed[0].title, "Foo", "Correct title for bookmark 1");
+  is(parsed[1].title, "Bar", "Correct title for bookmark 2");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js
@@ -0,0 +1,28 @@
+/* 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 { ShellService } = Cu.import("resource:///modules/ShellService.jsm", {});
+
+add_task(async function test_default_browser_check() {
+  ShellService._checkedThisSession = false;
+  // On a normal profile, the default is true. However, this gets set to false on the
+  // testing profile. Let's start with true for a sanity check.
+
+  ShellService.shouldCheckDefaultBrowser = true;
+  is(ShellService.shouldCheckDefaultBrowser, true, "Sanity check");
+
+  await setupPolicyEngineWithJson("config_dont_check_default_browser.json");
+
+  is(ShellService.shouldCheckDefaultBrowser, false, "Policy changed it to not check");
+
+  // Try to change it to true and check that it doesn't take effect
+  ShellService.shouldCheckDefaultBrowser = true;
+
+  is(ShellService.shouldCheckDefaultBrowser, false, "Policy is enforced");
+
+  // Unlock the pref because if it stays locked, and this test runs twice in a row,
+  // the first sanity check will fail.
+  Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_broken_json.json
@@ -0,0 +1,3 @@
+{
+  "policies
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_dont_check_default_browser.json
@@ -0,0 +1,5 @@
+{
+  "policies": {
+    "dont_check_default_browser": true
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_popups_cookies_addons_flash.json
@@ -0,0 +1,51 @@
+{
+  "policies": {
+    "popups": {
+      "allow": [
+        "https://www.allow.com",
+        "https://www.pre-existing-deny.com"
+      ],
+
+      "block": [
+        "https://www.deny.com",
+        "https://www.pre-existing-allow.com"
+      ]
+    },
+
+    "cookies": {
+      "allow": [
+        "https://www.allow.com",
+        "https://www.pre-existing-deny.com"
+      ],
+
+      "block": [
+        "https://www.deny.com",
+        "https://www.pre-existing-allow.com"
+      ]
+    },
+
+    "install_addons": {
+      "allow": [
+        "https://www.allow.com",
+        "https://www.pre-existing-deny.com"
+      ],
+
+      "block": [
+        "https://www.deny.com",
+        "https://www.pre-existing-allow.com"
+      ]
+    },
+
+    "flash_plugin": {
+      "allow": [
+        "https://www.allow.com",
+        "https://www.pre-existing-deny.com"
+      ],
+
+      "block": [
+        "https://www.deny.com",
+        "https://www.pre-existing-allow.com"
+      ]
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_setAndLockPref.json
@@ -0,0 +1,7 @@
+{
+  "policies": {
+    "bool_policy": true,
+    "int_policy": 42,
+    "string_policy": "policies test 2"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_simple_policies.json
@@ -0,0 +1,8 @@
+{
+  "policies": {
+    "simple_policy0": true,
+    "simple_policy1": true,
+    "simple_policy2": true,
+    "simple_policy3": false
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/head.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+async function setupPolicyEngineWithJson(jsonName, customSchema) {
+  let filePath = getTestFilePath(jsonName ? jsonName : "non-existing-file.json");
+  Services.prefs.setStringPref("browser.policies.alternatePath", filePath);
+
+  let resolve = null;
+  let promise = new Promise((r) => resolve = r);
+
+  Services.obs.addObserver(function observer() {
+    Services.obs.removeObserver(observer, "EnterprisePolicies:AllPoliciesApplied");
+    resolve();
+  }, "EnterprisePolicies:AllPoliciesApplied");
+
+  // Clear any previously used custom schema
+  Cu.unload("resource:///modules/policies/schema.jsm");
+
+  if (customSchema) {
+    let schemaModule = Cu.import("resource:///modules/policies/schema.jsm", {});
+    schemaModule.schema = customSchema;
+  }
+
+  Services.obs.notifyObservers(null, "EnterprisePolicies:Restart");
+  return promise;
+}
+
+async function startWithCleanSlate() {
+  await setupPolicyEngineWithJson("");
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.INACTIVE, "Engine is inactive");
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "General")
+
+BROWSER_CHROME_MANIFESTS += [
+    'browser/browser.ini'
+]
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -611,16 +611,24 @@ class Tab extends TabBase {
   }
 
   get frameLoader() {
     // If we don't have a frameLoader yet, just return a dummy with no width and
     // height.
     return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
   }
 
+  get hidden() {
+    return this.nativeTab.hidden;
+  }
+
+  get sharingState() {
+    return this.window.gBrowser.getTabSharingState(this.nativeTab);
+  }
+
   get cookieStoreId() {
     return getCookieStoreIdForTab(this, this.nativeTab);
   }
 
   get openerTabId() {
     let opener = this.nativeTab.openerTab;
     if (opener && opener.parentNode && opener.ownerDocument == this.nativeTab.ownerDocument) {
       return tabTracker.getId(opener);
@@ -712,16 +720,17 @@ class Tab extends TabBase {
   static convertFromSessionStoreClosedData(extension, tabData, window = null) {
     let result = {
       sessionId: String(tabData.closedId),
       index: tabData.pos ? tabData.pos : 0,
       windowId: window && windowTracker.getId(window),
       highlighted: false,
       active: false,
       pinned: false,
+      hidden: tabData.state ? tabData.state.hidden : tabData.hidden,
       incognito: Boolean(tabData.state && tabData.state.isPrivate),
       lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
     };
 
     if (extension.tabManager.hasTabPermission(tabData)) {
       let entries = tabData.state ? tabData.state.entries : tabData.entries;
       let lastTabIndex = tabData.state ? tabData.state.index : tabData.index;
       // We need to take lastTabIndex - 1 because the index in the tab data is
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -14,31 +14,33 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
-                                   "@mozilla.org/inspector/dom-utils;1",
-                                   "inIDOMUtils");
-
 var {
   DefaultWeakMap,
 } = ExtensionUtils;
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 var {
   IconDetails,
   StartupCache,
 } = ExtensionParent;
 
+var {
+  ExtensionError,
+} = ExtensionUtils;
+
+Cu.importGlobalProperties(["InspectorUtils"]);
+
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 const POPUP_OPEN_MS_HISTOGRAM = "WEBEXT_BROWSERACTION_POPUP_OPEN_MS";
 const POPUP_RESULT_HISTOGRAM = "WEBEXT_BROWSERACTION_POPUP_PRELOAD_RESULT_COUNT";
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const isAncestorOrSelf = (target, node) => {
   for (; node; node = node.parentNode) {
@@ -604,16 +606,21 @@ this.browserAction = class extends Exten
           browserAction.setProperty(tab, "enabled", true);
         },
 
         disable: function(tabId) {
           let tab = getTab(tabId);
           browserAction.setProperty(tab, "enabled", false);
         },
 
+        isEnabled: function(details) {
+          let tab = getTab(details.tabId);
+          return browserAction.getProperty(tab, "enabled");
+        },
+
         setTitle: function(details) {
           let tab = getTab(details.tabId);
 
           browserAction.setProperty(tab, "title", details.title);
         },
 
         getTitle: function(details) {
           let tab = getTab(details.tabId);
@@ -667,18 +674,21 @@ this.browserAction = class extends Exten
 
           let popup = browserAction.getProperty(tab, "popup");
           return Promise.resolve(popup);
         },
 
         setBadgeBackgroundColor: function(details) {
           let tab = getTab(details.tabId);
           let color = details.color;
-          if (!Array.isArray(color)) {
-            let col = DOMUtils.colorToRGBA(color);
+          if (typeof color == "string") {
+            let col = InspectorUtils.colorToRGBA(color);
+            if (!col) {
+              throw new ExtensionError(`Invalid badge background color: "${color}"`);
+            }
             color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
           }
           browserAction.setProperty(tab, "badgeBackgroundColor", color);
         },
 
         getBadgeBackgroundColor: function(details, callback) {
           let tab = getTab(details.tabId);
 
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -20,13 +20,17 @@ this.devtools_network = class extends Ex
               target.on("navigate", listener);
             });
             return () => {
               targetPromise.then(target => {
                 target.off("navigate", listener);
               });
             };
           }).api(),
+
+          getHAR: function() {
+            return context.devToolsToolbox.getHARFromNetMonitor();
+          },
         },
       },
     };
   }
 };
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -92,26 +92,28 @@ const convertNavHistoryContainerResultNo
 var _observer;
 
 const getHistoryObserver = () => {
   if (!_observer) {
     _observer = new class extends EventEmitter {
       onDeleteURI(uri, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
       }
-      onVisit(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed, lastKnownTitle) {
-        let data = {
-          id: guid,
-          url: uri.spec,
-          title: lastKnownTitle || "",
-          lastVisitTime: time / 1000,  // time from Places is microseconds,
-          visitCount,
-          typedCount: typed,
-        };
-        this.emit("visited", data);
+      onVisits(visits) {
+        for (let visit of visits) {
+          let data = {
+            id: visit.guid,
+            url: visit.uri.spec,
+            title: visit.lastKnownTitle || "",
+            lastVisitTime: visit.time / 1000,  // time from Places is microseconds,
+            visitCount: visit.visitCount,
+            typedCount: visit.typed,
+          };
+          this.emit("visited", data);
+        }
       }
       onBeginUpdateBatch() {}
       onEndUpdateBatch() {}
       onTitleChanged(uri, title) {
         this.emit("titleChanged", {url: uri.spec, title: title});
       }
       onClearHistory() {
         this.emit("visitRemoved", {allHistory: true, urls: []});
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -36,17 +36,17 @@ this.pageAction = class extends Extensio
     let options = extension.manifest.page_action;
 
     let widgetId = makeWidgetId(extension.id);
     this.id = widgetId + "-page-action";
 
     this.tabManager = extension.tabManager;
 
     // If <all_urls> is present, the default is to show the page action.
-    let show = options.show_matches && options.show_matches.includes("<all_urls>");
+    let show = !!options.show_matches && options.show_matches.includes("<all_urls>");
     let showMatches = new MatchPatternSet(options.show_matches || []);
     let hideMatches = new MatchPatternSet(options.hide_matches || []);
 
     this.defaults = {
       show,
       showMatches,
       hideMatches,
       title: options.default_title || extension.name,
@@ -267,34 +267,40 @@ this.pageAction = class extends Extensio
           pageAction.setProperty(tab, "show", true);
         },
 
         hide(tabId) {
           let tab = tabTracker.getTab(tabId);
           pageAction.setProperty(tab, "show", false);
         },
 
+        isShown(details) {
+          let tab = tabTracker.getTab(details.tabId);
+          return pageAction.getProperty(tab, "show");
+        },
+
         setTitle(details) {
           let tab = tabTracker.getTab(details.tabId);
-
-          // Clear the tab-specific title when given a null string.
-          pageAction.setProperty(tab, "title", details.title || null);
+          pageAction.setProperty(tab, "title", details.title);
         },
 
         getTitle(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           let title = pageAction.getProperty(tab, "title");
           return Promise.resolve(title);
         },
 
         setIcon(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           let icon = IconDetails.normalize(details, extension, context);
+          if (!Object.keys(icon).length) {
+            icon = null;
+          }
           pageAction.setProperty(tab, "icon", icon);
         },
 
         setPopup(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           // Note: Chrome resolves arguments to setIcon relative to the calling
           // context, but resolves arguments to setPopup relative to the extension
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -1,22 +1,19 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-browser.js */
+/* globals WINDOW_ID_CURRENT */
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 var {
-  ExtensionError,
-} = ExtensionUtils;
-
-var {
   IconDetails,
 } = ExtensionParent;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> SidebarAction]
 let sidebarActionMap = new WeakMap();
 
@@ -50,23 +47,24 @@ this.sidebarAction = class extends Exten
     this.browserStyle = options.browser_style || options.browser_style === null;
 
     this.defaults = {
       enabled: true,
       title: options.default_title || extension.name,
       icon: IconDetails.normalize({path: options.default_icon}, extension),
       panel: options.default_panel || "",
     };
+    this.globals = Object.create(this.defaults);
 
-    this.tabContext = new TabContext(tab => Object.create(this.defaults),
+    this.tabContext = new TabContext(tab => Object.create(this.globals),
                                      extension);
 
     // We need to ensure our elements are available before session restore.
     this.windowOpenListener = (window) => {
-      this.createMenuItem(window, this.defaults);
+      this.createMenuItem(window, this.globals);
     };
     windowTracker.addOpenListener(this.windowOpenListener);
 
     this.updateHeader = (event) => {
       let window = event.target.ownerGlobal;
       let details = this.tabContext.get(window.gBrowser.selectedTab);
       let header = window.document.getElementById("sidebar-switcher-target");
       if (window.SidebarUI.currentID === this.id) {
@@ -183,17 +181,18 @@ this.sidebarAction = class extends Exten
     let toolbarbutton = document.createElementNS(XUL_NS, "toolbarbutton");
     toolbarbutton.setAttribute("id", this.buttonId);
     toolbarbutton.setAttribute("observes", this.id);
     toolbarbutton.setAttribute("class", "subviewbutton subviewbutton-iconic webextension-menuitem");
     this.setMenuIcon(toolbarbutton, details);
 
     document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
     document.getElementById("viewSidebarMenu").appendChild(menuitem);
-    document.getElementById("sidebar-extensions").appendChild(toolbarbutton);
+    let separator = document.getElementById("sidebar-extensions-separator");
+    separator.parentNode.insertBefore(toolbarbutton, separator);
 
     return menuitem;
   }
 
   setMenuIcon(menuitem, details) {
     let getIcon = size => IconDetails.escapeUrl(
       IconDetails.getPreferredIcon(details.icon, this.extension, size).icon);
 
@@ -284,40 +283,44 @@ this.sidebarAction = class extends Exten
    * @param {XULElement|null} nativeTab
    *        Webextension tab object, may be null.
    * @param {string} prop
    *        String property to retrieve ["icon", "title", or "panel"].
    * @param {string} value
    *        Value for property.
    */
   setProperty(nativeTab, prop, value) {
+    let values;
     if (nativeTab === null) {
-      this.defaults[prop] = value;
-    } else if (value !== null) {
-      this.tabContext.get(nativeTab)[prop] = value;
+      values = this.globals;
     } else {
-      delete this.tabContext.get(nativeTab)[prop];
+      values = this.tabContext.get(nativeTab);
+    }
+    if (value === null) {
+      delete values[prop];
+    } else {
+      values[prop] = value;
     }
 
     this.updateOnChange(nativeTab);
   }
 
   /**
-   * Retrieve a property from the tab or defaults if tab is null.
+   * Retrieve a property from the tab or globals if tab is null.
    *
    * @param {XULElement|null} nativeTab
    *        Browser tab object, may be null.
    * @param {string} prop
    *        String property to retrieve ["icon", "title", or "panel"]
    * @returns {string} value
    *          Value for prop.
    */
   getProperty(nativeTab, prop) {
     if (nativeTab === null) {
-      return this.defaults[prop];
+      return this.globals[prop];
     }
     return this.tabContext.get(nativeTab)[prop];
   }
 
   /**
    * Triggers this sidebar action for the given window, with the same effects as
    * if it were toggled via menu or toolbarbutton by a user.
    *
@@ -343,20 +346,30 @@ this.sidebarAction = class extends Exten
   }
 
   /**
    * Closes this sidebar action for the given window if this sidebar action is open.
    *
    * @param {ChromeWindow} window
    */
   close(window) {
+    if (this.isOpen(window)) {
+      window.SidebarUI.hide();
+    }
+  }
+
+  /**
+   * Checks whether this sidebar action is open in the given window.
+   *
+   * @param {ChromeWindow} window
+   * @returns {boolean}
+   */
+  isOpen(window) {
     let {SidebarUI} = window;
-    if (SidebarUI.isOpen && this.id == SidebarUI.currentID) {
-      SidebarUI.hide();
-    }
+    return SidebarUI.isOpen && this.id == SidebarUI.currentID;
   }
 
   getAPI(context) {
     let {extension} = context;
     const sidebarAction = this;
 
     function getTab(tabId) {
       if (tabId !== null) {
@@ -364,53 +377,48 @@ this.sidebarAction = class extends Exten
       }
       return null;
     }
 
     return {
       sidebarAction: {
         async setTitle(details) {
           let nativeTab = getTab(details.tabId);
-
-          let title = details.title;
-          // Clear the tab-specific title when given a null string.
-          if (nativeTab && title === "") {
-            title = null;
-          }
-          sidebarAction.setProperty(nativeTab, "title", title);
+          sidebarAction.setProperty(nativeTab, "title", details.title);
         },
 
         getTitle(details) {
           let nativeTab = getTab(details.tabId);
 
           let title = sidebarAction.getProperty(nativeTab, "title");
           return Promise.resolve(title);
         },
 
         async setIcon(details) {
           let nativeTab = getTab(details.tabId);
 
           let icon = IconDetails.normalize(details, extension, context);
+          if (!Object.keys(icon).length) {
+            icon = null;
+          }
           sidebarAction.setProperty(nativeTab, "icon", icon);
         },
 
         async setPanel(details) {
           let nativeTab = getTab(details.tabId);
 
           let url;
-          // Clear the tab-specific url when given a null string.
-          if (nativeTab && details.panel === "") {
+          // Clear the url when given null or empty string.
+          if (!details.panel) {
             url = null;
-          } else if (details.panel !== "") {
+          } else {
             url = context.uri.resolve(details.panel);
             if (!context.checkLoadURL(url)) {
               return Promise.reject({message: `Access denied for URL ${url}`});
             }
-          } else {
-            throw new ExtensionError("Invalid url for sidebar panel.");
           }
 
           sidebarAction.setProperty(nativeTab, "panel", url);
         },
 
         getPanel(details) {
           let nativeTab = getTab(details.tabId);
 
@@ -422,14 +430,23 @@ this.sidebarAction = class extends Exten
           let window = windowTracker.topWindow;
           sidebarAction.open(window);
         },
 
         close() {
           let window = windowTracker.topWindow;
           sidebarAction.close(window);
         },
+
+        isOpen(details) {
+          let {windowId} = details;
+          if (windowId == null) {
+            windowId = WINDOW_ID_CURRENT;
+          }
+          let window = windowTracker.getWindow(windowId, context);
+          return sidebarAction.isOpen(window);
+        },
       },
     };
   }
 };
 
 global.sidebarActionFor = this.sidebarAction.for;
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -15,16 +15,21 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
   return Services.strings.createBundle("chrome://global/locale/extensions.properties");
 });
 
 var {
   ExtensionError,
 } = ExtensionUtils;
 
+const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
+
+// WeakMap[Tab -> ExtensionID]
+let hiddenTabs = new WeakMap();
+
 let tabListener = {
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
       windowTracker.addListener("progress", this);
@@ -72,16 +77,37 @@ let tabListener = {
         this.tabReadyPromises.set(nativeTab, deferred);
       }
     }
     return deferred.promise;
   },
 };
 
 this.tabs = class extends ExtensionAPI {
+  onShutdown(reason) {
+    if (!this.extension.hasPermission("tabHide")) {
+      return;
+    }
+    if (reason == "ADDON_DISABLE" ||
+        reason == "ADDON_UNINSTALL") {
+      // Show all hidden tabs if a tab managing extension is uninstalled or
+      // disabled.  If a user has more than one, the extensions will need to
+      // self-manage re-hiding tabs.
+      for (let tab of this.extension.tabManager.query()) {
+        let nativeTab = tabTracker.getTab(tab.id);
+        if (hiddenTabs.get(nativeTab) === this.extension.id) {
+          hiddenTabs.delete(nativeTab);
+          if (nativeTab.ownerGlobal) {
+            nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
+          }
+        }
+      }
+    }
+  }
+
   getAPI(context) {
     let {extension} = context;
 
     let {tabManager} = extension;
 
     function getTabOrActive(tabId) {
       if (tabId !== null) {
         return tabTracker.getTab(tabId);
@@ -256,25 +282,34 @@ this.tabs = class extends ExtensionAPI {
                 needed.push("mutedInfo");
               }
               if (changed.includes("soundplaying")) {
                 needed.push("audible");
               }
               if (changed.includes("label")) {
                 needed.push("title");
               }
+              if (changed.includes("sharing")) {
+                needed.push("sharingState");
+              }
             } else if (event.type == "TabPinned") {
               needed.push("pinned");
             } else if (event.type == "TabUnpinned") {
               needed.push("pinned");
             } else if (event.type == "TabBrowserInserted" &&
                        !event.detail.insertedOnTabCreation) {
               needed.push("discarded");
             } else if (event.type == "TabBrowserDiscarded") {
               needed.push("discarded");
+            } else if (event.type == "TabShow") {
+              needed.push("hidden");
+              // Always remove the tab from the hiddenTabs map.
+              hiddenTabs.delete(event.originalTarget);
+            } else if (event.type == "TabHide") {
+              needed.push("hidden");
             }
 
             let tab = tabManager.getWrapper(event.originalTarget);
             let changeInfo = {};
             for (let prop of needed) {
               changeInfo[prop] = tab[prop];
             }
 
@@ -305,26 +340,30 @@ this.tabs = class extends ExtensionAPI {
           };
 
           windowTracker.addListener("status", statusListener);
           windowTracker.addListener("TabAttrModified", listener);
           windowTracker.addListener("TabPinned", listener);
           windowTracker.addListener("TabUnpinned", listener);
           windowTracker.addListener("TabBrowserInserted", listener);
           windowTracker.addListener("TabBrowserDiscarded", listener);
+          windowTracker.addListener("TabShow", listener);
+          windowTracker.addListener("TabHide", listener);
 
           tabTracker.on("tab-isarticle", isArticleChangeListener);
 
           return () => {
             windowTracker.removeListener("status", statusListener);
             windowTracker.removeListener("TabAttrModified", listener);
             windowTracker.removeListener("TabPinned", listener);
             windowTracker.removeListener("TabUnpinned", listener);
             windowTracker.removeListener("TabBrowserInserted", listener);
             windowTracker.removeListener("TabBrowserDiscarded", listener);
+            windowTracker.removeListener("TabShow", listener);
+            windowTracker.removeListener("TabHide", listener);
             tabTracker.off("tab-isarticle", isArticleChangeListener);
           };
         }).api(),
 
         create(createProperties) {
           return new Promise((resolve, reject) => {
             let window = createProperties.windowId !== null ?
               windowTracker.getWindow(createProperties.windowId, context) :
@@ -546,16 +585,24 @@ this.tabs = class extends ExtensionAPI {
           if (queryInfo.title !== null) {
             queryInfo.title = new MatchGlob(queryInfo.title);
           }
 
           return Array.from(tabManager.query(queryInfo, context),
                             tab => tab.convert());
         },
 
+        async captureTab(tabId, options) {
+          let nativeTab = getTabOrActive(tabId);
+          await tabListener.awaitTabReady(nativeTab);
+
+          let tab = tabManager.wrapTab(nativeTab);
+          return tab.capture(context, options);
+        },
+
         async captureVisibleTab(windowId, options) {
           let window = windowId == null ?
             windowTracker.topWindow :
             windowTracker.getWindow(windowId, context);
 
           let tab = tabManager.wrapTab(window.gBrowser.selectedTab);
           await tabListener.awaitTabReady(tab.nativeTab);
 
@@ -869,60 +916,91 @@ this.tabs = class extends ExtensionAPI {
           picker.appendFilter("PDF", "*.pdf");
           picker.defaultExtension = "pdf";
           picker.defaultString = activeTab.linkedBrowser.contentTitle + ".pdf";
 
           return new Promise(resolve => {
             picker.open(function(retval) {
               if (retval == 0 || retval == 2) {
                 // OK clicked (retval == 0) or replace confirmed (retval == 2)
+
+                // Workaround: When trying to replace an existing file that is open in another application (i.e. a locked file),
+                // the print progress listener is never called. This workaround ensures that a correct status is always returned.
                 try {
                   let fstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
-                  fstream.init(picker.file, 0x2A, 0x1B6, 0); // write|create|truncate, file permissions rw-rw-rw- = 0666 = 0x1B6
-                  fstream.close(); // unlock file
+                  fstream.init(picker.file, 0x2A, 0o666, 0); // ioflags = write|create|truncate, file permissions = rw-rw-rw-
+                  fstream.close();
                 } catch (e) {
                   resolve(retval == 0 ? "not_saved" : "not_replaced");
                   return;
                 }
 
                 let psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService);
                 let printSettings = psService.newPrintSettings;
 
+                printSettings.printerName = "";
+                printSettings.isInitializedFromPrinter = true;
+                printSettings.isInitializedFromPrefs = true;
+
                 printSettings.printToFile = true;
                 printSettings.toFileName = picker.file.path;
 
                 printSettings.printSilent = true;
                 printSettings.showPrintProgress = false;
 
                 printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
                 printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
 
+                if (pageSettings.paperSizeUnit !== null) {
+                  printSettings.paperSizeUnit = pageSettings.paperSizeUnit;
+                }
+                if (pageSettings.paperWidth !== null) {
+                  printSettings.paperWidth = pageSettings.paperWidth;
+                }
+                if (pageSettings.paperHeight !== null) {
+                  printSettings.paperHeight = pageSettings.paperHeight;
+                }
                 if (pageSettings.orientation !== null) {
                   printSettings.orientation = pageSettings.orientation;
                 }
                 if (pageSettings.scaling !== null) {
                   printSettings.scaling = pageSettings.scaling;
                 }
                 if (pageSettings.shrinkToFit !== null) {
                   printSettings.shrinkToFit = pageSettings.shrinkToFit;
                 }
                 if (pageSettings.showBackgroundColors !== null) {
                   printSettings.printBGColors = pageSettings.showBackgroundColors;
                 }
                 if (pageSettings.showBackgroundImages !== null) {
                   printSettings.printBGImages = pageSettings.showBackgroundImages;
                 }
-                if (pageSettings.paperSizeUnit !== null) {
-                  printSettings.paperSizeUnit = pageSettings.paperSizeUnit;
+                if (pageSettings.edgeLeft !== null) {
+                  printSettings.edgeLeft = pageSettings.edgeLeft;
+                }
+                if (pageSettings.edgeRight !== null) {
+                  printSettings.edgeRight = pageSettings.edgeRight;
+                }
+                if (pageSettings.edgeTop !== null) {
+                  printSettings.edgeTop = pageSettings.edgeTop;
+                }
+                if (pageSettings.edgeBottom !== null) {
+                  printSettings.edgeBottom = pageSettings.edgeBottom;
                 }
-                if (pageSettings.paperWidth !== null) {
-                  printSettings.paperWidth = pageSettings.paperWidth;
+                if (pageSettings.marginLeft !== null) {
+                  printSettings.marginLeft = pageSettings.marginLeft;
+                }
+                if (pageSettings.marginRight !== null) {
+                  printSettings.marginRight = pageSettings.marginRight;
                 }
-                if (pageSettings.paperHeight !== null) {
-                  printSettings.paperHeight = pageSettings.paperHeight;
+                if (pageSettings.marginTop !== null) {
+                  printSettings.marginTop = pageSettings.marginTop;
+                }
+                if (pageSettings.marginBottom !== null) {
+                  printSettings.marginBottom = pageSettings.marginBottom;
                 }
                 if (pageSettings.headerLeft !== null) {
                   printSettings.headerStrLeft = pageSettings.headerLeft;
                 }
                 if (pageSettings.headerCenter !== null) {
                   printSettings.headerStrCenter = pageSettings.headerCenter;
                 }
                 if (pageSettings.headerRight !== null) {
@@ -932,32 +1010,35 @@ this.tabs = class extends ExtensionAPI {
                   printSettings.footerStrLeft = pageSettings.footerLeft;
                 }
                 if (pageSettings.footerCenter !== null) {
                   printSettings.footerStrCenter = pageSettings.footerCenter;
                 }
                 if (pageSettings.footerRight !== null) {
                   printSettings.footerStrRight = pageSettings.footerRight;
                 }
-                if (pageSettings.marginLeft !== null) {
-                  printSettings.marginLeft = pageSettings.marginLeft;
-                }
-                if (pageSettings.marginRight !== null) {
-                  printSettings.marginRight = pageSettings.marginRight;
-                }
-                if (pageSettings.marginTop !== null) {
-                  printSettings.marginTop = pageSettings.marginTop;
-                }
-                if (pageSettings.marginBottom !== null) {
-                  printSettings.marginBottom = pageSettings.marginBottom;
-                }
 
-                activeTab.linkedBrowser.print(activeTab.linkedBrowser.outerWindowID, printSettings, null);
+                let printProgressListener = {
+                  onLocationChange(webProgress, request, location, flags) { },
+                  onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { },
+                  onSecurityChange(webProgress, request, state) { },
+                  onStateChange(webProgress, request, flags, status) {
+                    if ((flags & Ci.nsIWebProgressListener.STATE_STOP) && (flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT)) {
+                      resolve(retval == 0 ? "saved" : "replaced");
+                    }
+                  },
+                  onStatusChange: function(webProgress, request, status, message) {
+                    if (status != 0) {
+                      resolve(retval == 0 ? "not_saved" : "not_replaced");
+                    }
+                  },
+                  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener]),
+                };
 
-                resolve(retval == 0 ? "saved" : "replaced");
+                activeTab.linkedBrowser.print(activeTab.linkedBrowser.outerWindowID, printSettings, printProgressListener);
               } else {
                 // Cancel clicked (retval == 1)
                 resolve("canceled");
               }
             });
           });
         },
 
@@ -965,13 +1046,54 @@ this.tabs = class extends ExtensionAPI {
           let tab = await promiseTabWhenReady(tabId);
           if (!tab.isInReaderMode && !tab.isArticle) {
             throw new ExtensionError("The specified tab cannot be placed into reader mode.");
           }
           tab = getTabOrActive(tabId);
 
           tab.linkedBrowser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
         },
+
+        show(tabIds) {
+          if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
+            throw new ExtensionError(`tabs.show is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
+          }
+
+          if (!Array.isArray(tabIds)) {
+            tabIds = [tabIds];
+          }
+
+          for (let tabId of tabIds) {
+            let tab = tabTracker.getTab(tabId);
+            if (tab.ownerGlobal) {
+              hiddenTabs.delete(tab);
+              tab.ownerGlobal.gBrowser.showTab(tab);
+            }
+          }
+        },
+
+        hide(tabIds) {
+          if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
+            throw new ExtensionError(`tabs.hide is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
+          }
+
+          if (!Array.isArray(tabIds)) {
+            tabIds = [tabIds];
+          }
+
+          let hidden = [];
+          let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
+          for (let tab of tabs) {
+            if (tab.ownerGlobal && !tab.hidden) {
+              tab.ownerGlobal.gBrowser.hideTab(tab);
+              if (tab.hidden) {
+                hiddenTabs.set(tab, extension.id);
+                hidden.push(tabTracker.getId(tab));
+              }
+            }
+          }
+          return hidden;
+        },
       },
     };
     return self;
   }
 };
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -413,16 +413,35 @@
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
+        "name": "isEnabled",
+        "type": "function",
+        "description": "Checks whether the browser action is enabled.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Specify the tab to get the enabledness from. If no tab is specified, the non-tab-specific enabledness is returned."
+              }
+            }
+          }
+        ]
+      },
+      {
         "name": "openPopup",
         "type": "function",
         "requireUserInput": true,
         "description": "Opens the extension popup window in the active window.",
         "async": true,
         "parameters": []
       }
     ],
--- a/browser/components/extensions/schemas/devtools_network.json
+++ b/browser/components/extensions/schemas/devtools_network.json
@@ -40,17 +40,16 @@
             ]
           }
         ]
       }
     ],
     "functions": [
       {
         "name": "getHAR",
-        "unsupported": true,
         "type": "function",
         "description": "Returns HAR log that contains all known network requests.",
         "async": "callback",
         "parameters": [
           {
             "name": "callback",
             "type": "function",
             "description": "A function that receives the HAR log when the request completes.",
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -92,26 +92,50 @@
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
+        "name": "isShown",
+        "type": "function",
+        "description": "Checks whether the page action is shown.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "Specify the tab to get the shownness from."
+              }
+            }
+          }
+        ]
+      },
+      {
         "name": "setTitle",
         "type": "function",
         "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
-              "title": {"type": "string", "description": "The tooltip string."}
+              "title": {
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
+                "description": "The tooltip string."
+              }
             }
           }
         ]
       },
       {
         "name": "getTitle",
         "type": "function",
         "description": "Gets the title of the page action.",
@@ -193,17 +217,20 @@
         "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
               "popup": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
               }
             }
           }
         ]
       },
       {
         "name": "getPopup",
--- a/browser/components/extensions/schemas/sidebar_action.json
+++ b/browser/components/extensions/schemas/sidebar_action.json
@@ -59,17 +59,20 @@
         "description": "Sets the title of the sidebar action. This shows up in the tooltip.",
         "async": true,
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "title": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The string the sidebar action should display when moused over."
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Sets the sidebar title for the tab specified by tabId. Automatically resets when the tab is closed."
               }
             }
@@ -151,17 +154,20 @@
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "minimum": 0,
                 "description": "Sets the sidebar url for the tab specified by tabId. Automatically resets when the tab is closed."
               },
               "panel": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The url to the html file to show in a sidebar.  If set to the empty string (''), no sidebar is shown."
               }
             }
           }
         ]
       },
       {
         "name": "getPanel",
@@ -192,12 +198,32 @@
       },
       {
         "name": "close",
         "type": "function",
         "requireUserInput": true,
         "description": "Closes the extension sidebar in the active window if the sidebar belongs to the extension.",
         "async": true,
         "parameters": []
+      },
+      {
+        "name": "isOpen",
+        "type": "function",
+        "description": "Checks whether the sidebar action is open.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": -2,
+                "optional": true,
+                "description": "Specify the window to get the openness from."
+              }
+            }
+          }
+        ]
       }
     ]
   }
 ]
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -7,17 +7,18 @@
     "namespace": "manifest",
     "types": [
       {
         "$extend": "OptionalPermission",
         "choices": [{
           "type": "string",
           "enum": [
             "activeTab",
-            "tabs"
+            "tabs",
+            "tabHide"
           ]
         }]
       }
     ]
   },
   {
     "namespace": "tabs",
     "description": "Use the <code>browser.tabs</code> API to interact with the browser's tab system. You can use this API to create, modify, and rearrange tabs in the browser.",
@@ -48,16 +49,36 @@
           "extensionId": {
             "type": "string",
             "optional": true,
             "description": "The ID of the extension that changed the muted state. Not set if an extension was not the reason the muted state last changed."
           }
         }
       },
       {
+        "id": "SharingState",
+        "type": "object",
+        "description": "Tab sharing state for screen, microphone and camera.",
+        "properties": {
+          "screen": {
+            "type": "string",
+            "optional": true,
+            "description": "If the tab is sharing the screen the value will be one of \"Screen\", \"Window\", or \"Application\", or undefined if not screen sharing."
+          },
+          "camera": {
+            "type": "boolean",
+            "description": "True if the tab is using the camera."
+          },
+          "microphone": {
+            "type": "boolean",
+            "description": "True if the tab is using the microphone."
+          }
+        }
+      },
+      {
         "id": "Tab",
         "type": "object",
         "properties": {
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "optional": true, "minimum": 0, "description": "The ID of the window the tab is contained within."},
           "openerTabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
@@ -70,20 +91,22 @@
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "discarded": {"type": "boolean", "optional": true, "description": "True while the tab is not loaded with content."},
           "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
           "height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
+          "hidden": {"type": "boolean", "optional": true, "description": "True if the tab is hidden."},
           "sessionId": {"type": "string", "optional": true, "description": "The session ID used to uniquely identify a Tab obtained from the $(ref:sessions) API."},
           "cookieStoreId": {"type": "string", "optional": true, "description": "The CookieStoreId used for the tab."},
           "isArticle": {"type": "boolean", "optional": true, "description": "Whether the document in the tab can be rendered in reader mode."},
-          "isInReaderMode": {"type": "boolean", "optional": true, "description": "Whether the document in the tab is being rendered in reader mode."}
+          "isInReaderMode": {"type": "boolean", "optional": true, "description": "Whether the document in the tab is being rendered in reader mode."},
+          "sharingState": {"$ref": "SharingState", "optional": true, "description": "Current tab sharing state for screen, microphone and camera."}
         }
       },
       {
         "id": "ZoomSettingsMode",
         "type": "string",
         "description": "Defines how zoom changes are handled, i.e. which entity is responsible for the actual scaling of the page; defaults to <code>automatic</code>.",
         "enum": [
           {
@@ -137,16 +160,31 @@
           }
         }
       },
       {
         "id": "PageSettings",
         "type": "object",
         "description": "The page settings including: orientation, scale, background, margins, headers, footers.",
         "properties": {
+          "paperSizeUnit": {
+            "type": "integer",
+            "optional": true,
+            "description": "The page size unit: 0 = inches, 1 = millimeters. Default: 0."
+          },
+          "paperWidth": {
+            "type": "number",
+            "optional": true,
+            "description": "The paper width in paper size units. Default: 8.5."
+          },
+          "paperHeight": {
+            "type": "number",
+            "optional": true,
+            "description": "The paper height in paper size units. Default: 11.0."
+          },
           "orientation": {
             "type": "integer",
             "optional": true,
             "description": "The page content orientation: 0 = portrait, 1 = landscape. Default: 0."
           },
           "scaling": {
             "type": "number",
             "optional": true,
@@ -162,30 +200,55 @@
             "optional": true,
             "description": "Whether the page background colors should be shown. Default: false."
           },
           "showBackgroundImages": {
             "type": "boolean",
             "optional": true,
             "description": "Whether the page background images should be shown. Default: false."
           },
-          "paperSizeUnit": {
-            "type": "integer",
+          "edgeLeft": {
+            "type": "number",
+            "optional": true,
+            "description": "The spacing between the left header/footer and the left edge of the paper (inches). Default: 0."
+          },
+          "edgeRight": {
+            "type": "number",
             "optional": true,
-            "description": "The page size unit: 0 = inches, 1 = millimeters. Default: 0."
+            "description": "The spacing between the right header/footer and the right edge of the paper (inches). Default: 0."
           },
-          "paperWidth": {
+          "edgeTop": {
+            "type": "number",
+            "optional": true,
+            "description": "The spacing between the top of the headers and the top edge of the paper (inches). Default: 0"
+          },
+          "edgeBottom": {
             "type": "number",
             "optional": true,
-            "description": "The paper width in paper size units. Default: 8.5."
+            "description": "The spacing between the bottom of the footers and the bottom edge of the paper (inches). Default: 0."
           },
-          "paperHeight": {
+          "marginLeft": {
+            "type": "number",
+            "optional": true,
+            "description": "The margin between the page content and the left edge of the paper (inches). Default: 0.5."
+          },
+          "marginRight": {
             "type": "number",
             "optional": true,
-            "description": "The paper height in paper size units. Default: 11.0."
+            "description": "The margin between the page content and the right edge of the paper (inches). Default: 0.5."
+          },
+          "marginTop": {
+            "type": "number",
+            "optional": true,
+            "description": "The margin between the page content and the top edge of the paper (inches). Default: 0.5."
+          },
+          "marginBottom": {
+            "type": "number",
+            "optional": true,
+            "description": "The margin between the page content and the bottom edge of the paper (inches). Default: 0.5."
           },
           "headerLeft": {
             "type": "string",
             "optional": true,
             "description": "The text for the page's left header. Default: '&T'."
           },
           "headerCenter": {
             "type": "string",
@@ -206,36 +269,16 @@
             "type": "string",
             "optional": true,
             "description": "The text for the page's center footer. Default: ''."
           },
           "footerRight": {
             "type": "string",
             "optional": true,
             "description": "The text for the page's right footer. Default: '&D'."
-          },
-          "marginLeft": {
-            "type": "number",
-            "optional": true,
-            "description": "The margin between the page content and the left edge of the paper (inches). Default: 0.5."
-          },
-          "marginRight": {
-            "type": "number",
-            "optional": true,
-            "description": "The margin between the page content and the right edge of the paper (inches). Default: 0.5."
-          },
-          "marginTop": {
-            "type": "number",
-            "optional": true,
-            "description": "The margin between the page content and the top edge of the paper (inches). Default: 0.5."
-          },
-          "marginBottom": {
-            "type": "number",
-            "optional": true,
-            "description": "The margin between the page content and the bottom edge of the paper (inches). Default: 0.5."
           }
         }
       },
       {
         "id": "TabStatus",
         "type": "string",
         "enum": ["loading", "complete"],
         "description": "Whether the tabs have completed loading."
@@ -595,16 +638,21 @@
                 "optional": true,
                 "description": "Whether the tabs have completed loading."
               },
               "discarded": {
                 "type": "boolean",
                 "optional": true,
                 "description": "True while the tabs are not loaded with content."
               },
+              "hidden": {
+                "type": "boolean",
+                "optional": true,
+                "description": "True while the tabs are hidden."
+              },
               "title": {
                 "type": "string",
                 "optional": true,
                 "description": "Match page titles against a pattern."
               },
               "url": {
                 "choices": [
                   {"type": "string"},
@@ -635,16 +683,34 @@
                 "optional": true,
                 "description": "The CookieStoreId used for the tab."
               },
               "openerTabId": {
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
+              },
+              "screen": {
+                "choices": [
+                  {"type": "string", "enum": ["Screen", "Window", "Application"]},
+                  {"type": "boolean"}
+                ],
+                "optional": true,
+                "description": "True for any screen sharing, or a string to specify type of screen sharing."
+              },
+              "camera": {
+                "type": "boolean",
+                "optional": true,
+                "description": "True if the tab is using the camera."
+              },
+              "microphone": {
+                "type": "boolean",
+                "optional": true,
+                "description": "True if the tab is using the microphone."
               }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
@@ -932,16 +998,37 @@
             "name": "tabId",
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the active tab of the $(topic:current-window)[current window]."
           }
         ]
       },
       {
+        "name": "captureTab",
+        "type": "function",
+        "description": "Captures the visible area of a specified tab. You must have $(topic:declare_permissions)[&lt;all_urls&gt;] permission to use this method.",
+        "permissions": ["<all_urls>"],
+        "async": true,
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "The tab to capture. Defaults to the active tab of the current window."
+          },
+          {
+            "$ref": "extensionTypes.ImageDetails",
+            "name": "options",
+            "optional": true
+          }
+        ]
+      },
+      {
         "name": "captureVisibleTab",
         "type": "function",
         "description": "Captures the visible area of the currently active tab in the specified window. You must have $(topic:declare_permissions)[&lt;all_urls&gt;] permission to use this method.",
         "permissions": ["<all_urls>"],
         "async": "callback",
         "parameters": [
           {
             "type": "integer",
@@ -1206,16 +1293,50 @@
               {
                 "type": "string",
                 "name": "status",
                 "description": "Save status: saved, replaced, canceled, not_saved, not_replaced."
               }
             ]
           }
         ]
+      },
+      {
+        "name": "show",
+        "type": "function",
+        "description": "Shows one or more tabs.",
+        "permissions": ["tabHide"],
+        "async": true,
+        "parameters": [
+          {
+            "name": "tabIds",
+            "description": "The TAB ID or list of TAB IDs to show.",
+            "choices": [
+              {"type": "integer", "minimum": 0},
+              {"type": "array", "items": {"type": "integer", "minimum": 0}}
+            ]
+          }
+        ]
+      },
+      {
+        "name": "hide",
+        "type": "function",
+        "description": "Hides one or more tabs. The <code>\"tabHide\"</code> permission is required to hide tabs.  Not all tabs are hidable.  Returns an array of hidden tabs.",
+        "permissions": ["tabHide"],
+        "async": true,
+        "parameters": [
+          {
+            "name": "tabIds",
+            "description": "The TAB ID or list of TAB IDs to hide.",
+            "choices": [
+              {"type": "integer", "minimum": 0},
+              {"type": "array", "items": {"type": "integer", "minimum": 0}}
+            ]
+          }
+        ]
       }
     ],
     "events": [
       {
         "name": "onCreated",
         "type": "function",
         "description": "Fired when a tab is created. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
         "parameters": [
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -130,16 +130,17 @@ skip-if = (os == 'win' && ccov) # Bug 14
 [browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
 [browser_ext_slow_script.js]
 skip-if = !e10s || debug || asan
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
+[browser_ext_tabs_captureTab.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 skip-if = os == "linux" && debug && bits == 32 # Bug 1350189
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_detectLanguage.js]
 [browser_ext_tabs_discard.js]
 skip-if = !e10s
 [browser_ext_tabs_discarded.js]
@@ -147,33 +148,37 @@ skip-if = !e10s
 [browser_ext_tabs_events.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_executeScript_good.js]
 [browser_ext_tabs_executeScript_bad.js]
 [browser_ext_tabs_executeScript_multiple.js]
 [browser_ext_tabs_executeScript_no_create.js]
 [browser_ext_tabs_executeScript_runAt.js]
 [browser_ext_tabs_getCurrent.js]
+[browser_ext_tabs_hide.js]
 [browser_ext_tabs_insertCSS.js]
 [browser_ext_tabs_lastAccessed.js]
 [browser_ext_tabs_lazy.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_opener.js]
 [browser_ext_tabs_printPreview.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_readerMode.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
+[browser_ext_tabs_saveAsPDF.js]
+skip-if = os == 'mac' # Save as PDF not supported on Mac OS X
 [browser_ext_tabs_sendMessage.js]
+[browser_ext_tabs_sharingState.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_themes_icons.js]
 [browser_ext_themes_validation.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_user_events.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -16,16 +16,20 @@ async function runTests(options) {
       let badge = await browser.browserAction.getBadgeText({tabId});
       browser.test.assertEq(expecting.badge, badge,
                             "expected value from getBadge");
 
       let badgeBackgroundColor = await browser.browserAction.getBadgeBackgroundColor({tabId});
       browser.test.assertEq(String(expecting.badgeBackgroundColor),
                             String(badgeBackgroundColor),
                             "expected value from getBadgeBackgroundColor");
+
+      let enabled = await browser.browserAction.isEnabled({tabId});
+      browser.test.assertEq(!expecting.disabled, enabled,
+                            "expected value from isEnabled");
     }
 
     let expectDefaults = expecting => {
       return checkDetails(expecting);
     };
 
     let tabs = [];
     let tests = getTests(tabs, expectDefaults);
@@ -280,17 +284,17 @@ add_task(async function testTabSwitchCon
 
           await expectDefaults(details[4]);
           expect(details[4]);
         },
         async expect => {
           browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step.");
           await browser.tabs.update(tabs[1], {active: true});
 
-          await expectDefaults(details[3]);
+          await expectDefaults(details[4]);
           expect(details[2]);
         },
         expect => {
           browser.test.log("Navigate to a new page. Expect defaults.");
 
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == tabs[1] && changed.url) {
               browser.tabs.onUpdated.removeListener(listener);
@@ -464,39 +468,34 @@ add_task(async function testPropertyRemo
     "files": {
       "default.png": imageBuffer,
       "i1.png": imageBuffer,
       "i2.png": imageBuffer,
       "i3.png": imageBuffer,
     },
 
     getTests: function(tabs, expectGlobals) {
-      let contextUri = browser.runtime.getURL("_generated_background_page.html");
+      let defaultIcon = "chrome://browser/content/extension.svg";
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default Title",
          "badge": "",
          "badgeBackgroundColor": [0xd9, 0x00, 0x00, 0xFF]},
         {"icon": browser.runtime.getURL("i1.png"),
          "popup": browser.runtime.getURL("p1.html"),
          "title": "t1",
          "badge": "b1",
          "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
         {"icon": browser.runtime.getURL("i2.png"),
          "popup": browser.runtime.getURL("p2.html"),
          "title": "t2",
          "badge": "b2",
          "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
-        {"icon": contextUri,
-         "popup": "",
-         "title": "",
-         "badge": "",
-         "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
-        {"icon": contextUri,
+        {"icon": defaultIcon,
          "popup": "",
          "title": "",
          "badge": "",
          "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
         {"icon": browser.runtime.getURL("i3.png"),
          "popup": browser.runtime.getURL("p3.html"),
          "title": "t3",
          "badge": "b3",
@@ -532,28 +531,25 @@ add_task(async function testPropertyRemo
         },
         async expect => {
           browser.test.log("Set empty tab values, expect empty values except for bgcolor.");
           let tabId = tabs[0];
           browser.browserAction.setIcon({tabId, path: ""});
           browser.browserAction.setPopup({tabId, popup: ""});
           browser.browserAction.setTitle({tabId, title: ""});
           browser.browserAction.setBadgeText({tabId, text: ""});
-          browser.browserAction.setBadgeBackgroundColor({tabId, color: ""});
+          await browser.test.assertRejects(
+            browser.browserAction.setBadgeBackgroundColor({tabId, color: ""}),
+            /^Invalid badge background color: ""$/,
+            "Expected invalid badge background color error"
+          );
           await expectGlobals(details[1]);
           expect(details[3]);
         },
         async expect => {
-          browser.test.log("The invalid color removed tab bgcolor, restore previous tab bgcolor.");
-          let tabId = tabs[0];
-          browser.browserAction.setBadgeBackgroundColor({tabId, color: "#222"});
-          await expectGlobals(details[1]);
-          expect(details[4]);
-        },
-        async expect => {
           browser.test.log("Remove tab values, expect global values.");
           let tabId = tabs[0];
           browser.browserAction.setIcon({tabId, path: null});
           browser.browserAction.setPopup({tabId, popup: null});
           browser.browserAction.setTitle({tabId, title: null});
           browser.browserAction.setBadgeText({tabId, text: null});
           browser.browserAction.setBadgeBackgroundColor({tabId, color: null});
           await expectGlobals(details[1]);
@@ -561,18 +557,18 @@ add_task(async function testPropertyRemo
         },
         async expect => {
           browser.test.log("Change global values, expect the new values.");
           browser.browserAction.setIcon({path: "i3.png"});
           browser.browserAction.setPopup({popup: "p3.html"});
           browser.browserAction.setTitle({title: "t3"});
           browser.browserAction.setBadgeText({text: "b3"});
           browser.browserAction.setBadgeBackgroundColor({color: "#333"});
-          await expectGlobals(details[5]);
-          expect(details[5]);
+          await expectGlobals(details[4]);
+          expect(details[4]);
         },
         async expect => {
           browser.test.log("Remove global values, expect defaults.");
           browser.browserAction.setIcon({path: null});
           browser.browserAction.setPopup({popup: null});
           browser.browserAction.setBadgeText({text: null});
           browser.browserAction.setTitle({title: null});
           browser.browserAction.setBadgeBackgroundColor({color: null});
--- a/browser/components/extensions/test/browser/browser_ext_devtools_network.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_network.js
@@ -1,69 +1,102 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {gDevTools} = require("devtools/client/framework/devtools");
 
+function background() {
+  browser.test.onMessage.addListener(msg => {
+    let code;
+    if (msg === "navigate") {
+      code = "window.wrappedJSObject.location.href = 'http://example.com/';";
+      browser.tabs.executeScript({code});
+    } else if (msg === "reload") {
+      code = "window.wrappedJSObject.location.reload(true);";
+      browser.tabs.executeScript({code});
+    }
+  });
+  browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+    if (changeInfo.status === "complete" && tab.url === "http://example.com/") {
+      browser.test.sendMessage("tabUpdated");
+    }
+  });
+  browser.test.sendMessage("ready");
+}
+
+function devtools_page() {
+  let eventCount = 0;
+  let listener = url => {
+    eventCount++;
+    browser.test.assertEq("http://example.com/", url, "onNavigated received the expected url.");
+    browser.test.sendMessage("onNavigatedFired", eventCount);
+
+    if (eventCount === 2) {
+      eventCount = 0;
+      browser.devtools.network.onNavigated.removeListener(listener);
+    }
+  };
+  browser.devtools.network.onNavigated.addListener(listener);
+
+  let harLogCount = 0;
+  let harListener = async msg => {
+    if (msg !== "getHAR") {
+      return;
+    }
+
+    harLogCount++;
+
+    const harLog = await browser.devtools.network.getHAR();
+    browser.test.sendMessage("getHAR-result", harLog);
+
+    if (harLogCount === 2) {
+      harLogCount = 0;
+      browser.test.onMessage.removeListener(harListener);
+    }
+  };
+  browser.test.onMessage.addListener(harListener);
+}
+
+function waitForRequestAdded(toolbox) {
+  return new Promise(resolve => {
+    let netPanel = toolbox.getPanel("netmonitor");
+    netPanel.panelWin.once("NetMonitor:RequestAdded", () => {
+      resolve();
+    });
+  });
+}
+
+let extData = {
+  background,
+  manifest: {
+    permissions: ["tabs", "http://mochi.test/", "http://example.com/"],
+    devtools_page: "devtools_page.html",
+  },
+  files: {
+    "devtools_page.html": `<!DOCTYPE html>
+      <html>
+        <head>
+          <meta charset="utf-8">
+          <script src="devtools_page.js"></script>
+        </head>
+        <body>
+        </body>
+      </html>`,
+    "devtools_page.js": devtools_page,
+  },
+};
+
+/**
+ * Test for `chrome.devtools.network.onNavigate()` API
+ */
 add_task(async function test_devtools_network_on_navigated() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
-
-  function background() {
-    browser.test.onMessage.addListener(msg => {
-      let code;
-      if (msg === "navigate") {
-        code = "window.wrappedJSObject.location.href = 'http://example.com/';";
-        browser.tabs.executeScript({code});
-      } else if (msg === "reload") {
-        code = "window.wrappedJSObject.location.reload(true);";
-        browser.tabs.executeScript({code});
-      }
-    });
-    browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
-      if (changeInfo.status === "complete" && tab.url === "http://example.com/") {
-        browser.test.sendMessage("tabUpdated");
-      }
-    });
-    browser.test.sendMessage("ready");
-  }
-
-  function devtools_page() {
-    let eventCount = 0;
-    let listener = url => {
-      eventCount++;
-      browser.test.assertEq("http://example.com/", url, "onNavigated received the expected url.");
-      if (eventCount === 2) {
-        browser.devtools.network.onNavigated.removeListener(listener);
-      }
-      browser.test.sendMessage("onNavigatedFired", eventCount);
-    };
-    browser.devtools.network.onNavigated.addListener(listener);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["tabs", "http://mochi.test/", "http://example.com/"],
-      devtools_page: "devtools_page.html",
-    },
-    files: {
-      "devtools_page.html": `<!DOCTYPE html>
-        <html>
-          <head>
-            <meta charset="utf-8">
-            <script src="devtools_page.js"></script>
-          </head>
-          <body>
-          </body>
-        </html>`,
-      "devtools_page.js": devtools_page,
-    },
-  });
+  let extension = ExtensionTestUtils.loadExtension(extData);
 
   await extension.startup();
   await extension.awaitMessage("ready");
 
   let target = gDevTools.getTargetForTab(tab);
 
   await gDevTools.showToolbox(target, "webconsole");
   info("Developer toolbox opened.");
@@ -85,8 +118,60 @@ add_task(async function test_devtools_ne
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   await extension.unload();
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+/**
+ * Test for `chrome.devtools.network.getHAR()` API
+ */
+add_task(async function test_devtools_network_get_har() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+  let extension = ExtensionTestUtils.loadExtension(extData);
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  let target = gDevTools.getTargetForTab(tab);
+
+  // Open the Toolbox
+  let toolbox = await gDevTools.showToolbox(target, "webconsole");
+  info("Developer toolbox opened.");
+
+  // Get HAR, it should be empty since the Net panel wasn't selected.
+  const getHAREmptyPromise = extension.awaitMessage("getHAR-result");
+  extension.sendMessage("getHAR");
+  const getHAREmptyResult = await getHAREmptyPromise;
+  is(getHAREmptyResult.log.entries.length, 0, "HAR log should be empty");
+
+  // Select the Net panel.
+  await toolbox.selectTool("netmonitor");
+
+  // Reload the page to collect some HTTP requests.
+  extension.sendMessage("navigate");
+
+  // Wait till the navigation is complete and request
+  // added into the net panel.
+  await Promise.all([
+    extension.awaitMessage("tabUpdated"),
+    extension.awaitMessage("onNavigatedFired"),
+    waitForRequestAdded(toolbox),
+  ]);
+
+  // Get HAR, it should not be empty now.
+  const getHARPromise = extension.awaitMessage("getHAR-result");
+  extension.sendMessage("getHAR");
+  const getHARResult = await getHARPromise;
+  is(getHARResult.log.entries.length, 1, "HAR log should not be empty");
+
+  // Shutdown
+  await gDevTools.closeToolbox(target);
+
+  await target.destroy();
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -35,16 +35,45 @@ async function testThemeSwitching(extens
          `The onThemeChanged event listener fired for the ${location}.`);
       is(await extension.awaitMessage(`current_theme_${location}`),
          newTheme,
          `The current theme is reported as expected for the ${location}.`);
     }
   }
 }
 
+add_task(async function setup_blank_panel() {
+  // Create a blank custom tool so that we don't need to wait the webconsole
+  // to be fully loaded/unloaded to prevent intermittent failures (related
+  // to a webconsole that is still loading when the test has been completed).
+  const testBlankPanel = {
+    id: "testBlankPanel",
+    url: "about:blank",
+    label: "Blank Tool",
+    isTargetSupported() {
+      return true;
+    },
+    build(iframeWindow, toolbox) {
+      return Promise.resolve({
+        target: toolbox.target,
+        toolbox: toolbox,
+        isReady: true,
+        panelDoc: iframeWindow.document,