merge mozilla-central to mozilla-beta. a=merge l10n=merge DEVEDITION_65_0b2_BUILD1 DEVEDITION_65_0b2_RELEASE
authorSebastian Hengst <archaeopteryx@coole-files.de>
Thu, 06 Dec 2018 18:23:04 +0200
changeset 505946 7d2f3c71997c
parent 505673 1f6ce017121d (current diff)
parent 505945 f7895b06b742 (diff)
child 505947 c3047e612a7f
push id10301
push userarchaeopteryx@coole-files.de
push dateThu, 06 Dec 2018 16:36:14 +0000
treeherdermozilla-beta@7d2f3c71997c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0
merge mozilla-central to mozilla-beta. a=merge l10n=merge
browser/components/attribution/test/.eslintrc.js
browser/extensions/screenshots/test/browser/.eslintrc.yml
editor/reftests/spellcheck-contenteditable-nofocus.html
testing/web-platform/meta/wasm/webapi/abort.any.js.ini
third_party/python/compare-locales/compare_locales/compare.py
third_party/python/compare-locales/compare_locales/paths.py
third_party/python/compare-locales/compare_locales/tests/test_paths.py
third_party/python/fluent/fluent/syntax/ftlstream.py
toolkit/locales/en-US/chrome/global/aboutServiceWorkers.dtd
toolkit/locales/en-US/chrome/global/aboutServiceWorkers.properties
--- a/.eslintignore
+++ b/.eslintignore
@@ -179,17 +179,16 @@ dom/cache/test/mochitest/**
 dom/cache/test/xpcshell/**
 dom/canvas/**
 dom/console/**
 dom/crypto/**
 dom/encoding/**
 dom/events/**
 dom/fetch/**
 dom/file/**
-dom/filesystem/**
 dom/flex/**
 dom/grid/**
 dom/html/**
 dom/ipc/**
 dom/jsurl/**
 dom/localstorage/**
 dom/manifest/**
 dom/media/test/**
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,4 +1,9 @@
 # Bug 1204606 - Reformat of dom/media r=jya
 # https://hg.mozilla.org/mozilla-central/rev/0ceae9db9ec0be18daa1a279511ad305723185d4
 abd6d77c618998827e5ffc3dab12f1a34d6ed03d # cinnabar
 804b8b8883ba2a6795b0fcf65ebcb18306b6416b # gecko-dev
+
+# Bug 1511181 - Reformat everything to the Google coding style r=ehsan
+# https://hg.mozilla.org/mozilla-central/rev/6f3709b3878117466168c40affa7bca0b60cf75b
+0e0308d10a5fd4a8dcf0601978776342a2abf2df # cinnabar
+265e6721798a455604328ed5262f430cfcc37c2f # gecko-dev
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -195,17 +195,17 @@ name = "binast"
 version = "0.1.1"
 dependencies = [
  "binjs_meta 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "webidl 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bincode"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1913,16 +1913,17 @@ dependencies = [
 name = "procedural-masquerade"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "profiler_helper"
 version = "0.1.0"
 dependencies = [
+ "goblin 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "object 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "pulse"
 version = "0.2.0"
@@ -3090,17 +3091,17 @@ version = "0.1.0"
 dependencies = [
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "yaml-rust"
-version = "0.4.0"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "zip"
 version = "0.4.2"
@@ -3396,10 +3397,10 @@ dependencies = [
 "checksum winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum winapi-i686-pc-windows-gnu 0.4.0 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767"
 "checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
 "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5"
-"checksum yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab38ee1a4a266ed033496cf9af1828d8d6e6c1cfa5f643a2809effcae4d628"
+"checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e"
 "checksum zip 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "36b9e08fb518a65cf7e08a1e482573eb87a2f4f8c6619316612a3c1f162fe822"
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -182,16 +182,31 @@ void AccessibleWrap::Shutdown() {
       ReleaseID(mID);
       mID = 0;
     }
   }
 
   Accessible::Shutdown();
 }
 
+bool AccessibleWrap::DoAction(uint8_t aIndex) const {
+  if (ActionCount()) {
+    return Accessible::DoAction(aIndex);
+  }
+
+  if (mContent) {
+    // We still simulate a click on an accessible even if there is no
+    // known actions. For the sake of bad markup.
+    DoCommand();
+    return true;
+  }
+
+  return false;
+}
+
 int32_t AccessibleWrap::AcquireID() { return sIDSet.GetID(); }
 
 void AccessibleWrap::ReleaseID(int32_t aID) { sIDSet.ReleaseID(aID); }
 
 void AccessibleWrap::SetTextContents(const nsAString& aText) {
   if (IsHyperText()) {
     AsHyperText()->ReplaceText(aText);
   }
@@ -209,17 +224,18 @@ bool AccessibleWrap::GetSelectionBounds(
                                         int32_t* aEndOffset) {
   if (IsHyperText()) {
     return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
   }
 
   return false;
 }
 
-uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState) {
+uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState,
+                                  uint8_t aActionCount) {
   uint32_t flags = 0;
   if (aState & states::CHECKABLE) {
     flags |= java::SessionAccessibility::FLAG_CHECKABLE;
   }
 
   if (aState & states::CHECKED) {
     flags |= java::SessionAccessibility::FLAG_CHECKED;
   }
@@ -227,17 +243,17 @@ uint32_t AccessibleWrap::GetFlags(role a
   if (aState & states::INVALID) {
     flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
   }
 
   if (aState & states::EDITABLE) {
     flags |= java::SessionAccessibility::FLAG_EDITABLE;
   }
 
-  if (aState & states::SENSITIVE) {
+  if (aActionCount && aRole != roles::TEXT_LEAF) {
     flags |= java::SessionAccessibility::FLAG_CLICKABLE;
   }
 
   if (aState & states::ENABLED) {
     flags |= java::SessionAccessibility::FLAG_ENABLED;
   }
 
   if (aState & states::FOCUSABLE) {
@@ -266,17 +282,19 @@ uint32_t AccessibleWrap::GetFlags(role a
 
   if (aRole == roles::PASSWORD_TEXT) {
     flags |= java::SessionAccessibility::FLAG_PASSWORD;
   }
 
   return flags;
 }
 
-void AccessibleWrap::GetRoleDescription(role aRole, nsAString& aGeckoRole,
+void AccessibleWrap::GetRoleDescription(role aRole,
+                                        nsIPersistentProperties* aAttributes,
+                                        nsAString& aGeckoRole,
                                         nsAString& aRoleDescription) {
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIStringBundleService> sbs =
       do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get string bundle service");
     return;
@@ -284,16 +302,29 @@ void AccessibleWrap::GetRoleDescription(
 
   nsCOMPtr<nsIStringBundle> bundle;
   rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get string bundle");
     return;
   }
 
+  if (aRole == roles::HEADING) {
+    nsString level;
+    rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("level"), level);
+    if (NS_SUCCEEDED(rv)) {
+      const char16_t* formatString[] = {level.get()};
+      rv = bundle->FormatStringFromName("headingLevel", formatString, 1,
+                                        aRoleDescription);
+      if (NS_SUCCEEDED(rv)) {
+        return;
+      }
+    }
+  }
+
   GetAccService()->GetStringRole(aRole, aGeckoRole);
   rv = bundle->GetStringFromName(NS_ConvertUTF16toUTF8(aGeckoRole).get(),
                                  aRoleDescription);
   if (NS_FAILED(rv)) {
     aRoleDescription.AssignLiteral("");
   }
 }
 
@@ -391,54 +422,55 @@ mozilla::java::GeckoBundle::LocalRef Acc
   double curValue = UnspecifiedNaN<double>();
   double minValue = UnspecifiedNaN<double>();
   double maxValue = UnspecifiedNaN<double>();
   double step = UnspecifiedNaN<double>();
   WrapperRangeInfo(&curValue, &minValue, &maxValue, &step);
 
   nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
 
-  return ToBundle(State(), Bounds(), name, textValue, nodeID, curValue,
-                  minValue, maxValue, step, attributes);
+  return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID,
+                  curValue, minValue, maxValue, step, attributes);
 }
 
 mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(
-    const uint64_t aState, const nsIntRect& aBounds, const nsString& aName,
-    const nsString& aTextValue, const nsString& aDOMNodeID,
-    const double& aCurVal, const double& aMinVal, const double& aMaxVal,
-    const double& aStep, nsIPersistentProperties* aAttributes) {
+    const uint64_t aState, const nsIntRect& aBounds, const uint8_t aActionCount,
+    const nsString& aName, const nsString& aTextValue,
+    const nsString& aDOMNodeID, const double& aCurVal, const double& aMinVal,
+    const double& aMaxVal, const double& aStep,
+    nsIPersistentProperties* aAttributes) {
   if (!IsProxy() && IsDefunct()) {
     return nullptr;
   }
 
   GECKOBUNDLE_START(nodeInfo);
   GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
 
   AccessibleWrap* parent = WrapperParent();
   GECKOBUNDLE_PUT(
       nodeInfo, "parentId",
       java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
 
   role role = WrapperRole();
-  uint32_t flags = GetFlags(role, aState);
+  uint32_t flags = GetFlags(role, aState, aActionCount);
   GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
   GECKOBUNDLE_PUT(nodeInfo, "className",
                   java::sdk::Integer::ValueOf(AndroidClass()));
 
   if (aState & states::EDITABLE) {
     GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(aName));
     GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
   } else {
     GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
   }
 
   nsAutoString geckoRole;
   nsAutoString roleDescription;
   if (VirtualViewID() != kNoID) {
-    GetRoleDescription(role, geckoRole, roleDescription);
+    GetRoleDescription(role, aAttributes, geckoRole, roleDescription);
   }
 
   GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
                   jni::StringParam(roleDescription));
   GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
 
   GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
                   jni::StringParam(roleDescription));
@@ -551,30 +583,31 @@ mozilla::java::GeckoBundle::LocalRef Acc
   GECKOBUNDLE_PUT(nodeInfo, "children",
                   jni::IntArray::New(children.Elements(), children.Length()));
   GECKOBUNDLE_FINISH(nodeInfo);
 
   return nodeInfo;
 }
 
 mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToSmallBundle() {
-  return ToSmallBundle(State(), Bounds());
+  return ToSmallBundle(State(), Bounds(), ActionCount());
 }
 
 mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToSmallBundle(
-    const uint64_t aState, const nsIntRect& aBounds) {
+    const uint64_t aState, const nsIntRect& aBounds,
+    const uint8_t aActionCount) {
   GECKOBUNDLE_START(nodeInfo);
   GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
 
   AccessibleWrap* parent = WrapperParent();
   GECKOBUNDLE_PUT(
       nodeInfo, "parentId",
       java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
 
-  uint32_t flags = GetFlags(WrapperRole(), aState);
+  uint32_t flags = GetFlags(WrapperRole(), aState, aActionCount);
   GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
   GECKOBUNDLE_PUT(nodeInfo, "className",
                   java::sdk::Integer::ValueOf(AndroidClass()));
 
   const int32_t data[4] = {aBounds.x, aBounds.y, aBounds.x + aBounds.width,
                            aBounds.y + aBounds.height};
   GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
 
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -15,36 +15,41 @@ namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap : public Accessible {
  public:
   AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
   virtual ~AccessibleWrap();
 
   virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
   virtual void Shutdown() override;
 
+  virtual bool DoAction(uint8_t aIndex) const override;
+
   int32_t VirtualViewID() const { return mID; }
 
   virtual void SetTextContents(const nsAString& aText);
 
   virtual void GetTextContents(nsAString& aText);
 
   virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
 
   mozilla::java::GeckoBundle::LocalRef ToBundle();
 
   mozilla::java::GeckoBundle::LocalRef ToBundle(
-      const uint64_t aState, const nsIntRect& aBounds, const nsString& aName,
+      const uint64_t aState, const nsIntRect& aBounds,
+      const uint8_t aActionCount, const nsString& aName,
       const nsString& aTextValue, const nsString& aDOMNodeID,
       const double& aCurVal, const double& aMinVal, const double& aMaxVal,
       const double& aStep, nsIPersistentProperties* aAttributes);
 
-  mozilla::java::GeckoBundle::LocalRef ToSmallBundle(const uint64_t aState,
-                                                     const nsIntRect& aBounds);
+  mozilla::java::GeckoBundle::LocalRef ToSmallBundle(
+      const uint64_t aState, const nsIntRect& aBounds,
+      const uint8_t aActionCount);
 
   mozilla::java::GeckoBundle::LocalRef ToSmallBundle();
 
   virtual void WrapperDOMNodeID(nsString& aDOMNodeID);
 
   int32_t AndroidClass() {
     return mID == kNoID ? java::SessionAccessibility::CLASSNAME_WEBVIEW
                         : GetAndroidClass(WrapperRole());
@@ -71,19 +76,22 @@ class AccessibleWrap : public Accessible
     return static_cast<AccessibleWrap*>(Parent());
   }
 
   virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal,
                                 double* aMaxVal, double* aStep);
 
   virtual role WrapperRole() { return Role(); }
 
-  static void GetRoleDescription(role aRole, nsAString& aGeckoRole,
+  static void GetRoleDescription(role aRole,
+                                 nsIPersistentProperties* aAttributes,
+                                 nsAString& aGeckoRole,
                                  nsAString& aRoleDescription);
-  static uint32_t GetFlags(role aRole, uint64_t aState);
+
+  static uint32_t GetFlags(role aRole, uint64_t aState, uint8_t aActionCount);
 };
 
 static inline AccessibleWrap* WrapperFor(const ProxyAccessible* aProxy) {
   return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
 }
 
 }  // namespace a11y
 }  // namespace mozilla
--- a/accessible/android/DocAccessibleWrap.cpp
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "Accessible-inl.h"
 #include "DocAccessibleWrap.h"
 #include "nsIDocShell.h"
 #include "nsLayoutUtils.h"
 #include "DocAccessibleChild.h"
 #include "nsAccessibilityService.h"
 #include "nsAccUtils.h"
 #include "nsIPersistentProperties2.h"
 #include "SessionAccessibility.h"
@@ -129,20 +130,20 @@ void DocAccessibleWrap::CacheViewportCal
     nsTArray<BatchData> cacheData(inViewAccs.Count());
     for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
       Accessible* accessible = iter.Data();
       auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc()
                      ? 0
                      : reinterpret_cast<uint64_t>(accessible->UniqueID());
       cacheData.AppendElement(
           BatchData(accessible->Document()->IPCDoc(), uid, accessible->State(),
-                    accessible->Bounds(), nsString(), nsString(), nsString(),
+                    accessible->Bounds(), accessible->ActionCount(), nsString(),
+                    nsString(), nsString(), UnspecifiedNaN<double>(),
                     UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
-                    UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
-                    nsTArray<Attribute>()));
+                    UnspecifiedNaN<double>(), nsTArray<Attribute>()));
     }
 
     ipcDoc->SendBatch(eBatch_Viewport, cacheData);
   } else if (SessionAccessibility* sessionAcc =
                  SessionAccessibility::GetInstanceFor(docAcc)) {
     nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
     for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
       accessibles.AppendElement(
@@ -194,20 +195,20 @@ void DocAccessibleWrap::CacheFocusPath(A
       acc->Name(name);
       nsAutoString textValue;
       acc->Value(textValue);
       nsAutoString nodeID;
       acc->WrapperDOMNodeID(nodeID);
       nsCOMPtr<nsIPersistentProperties> props = acc->Attributes();
       nsTArray<Attribute> attributes;
       nsAccUtils::PersistentPropertiesToArray(props, &attributes);
-      cacheData.AppendElement(
-          BatchData(acc->Document()->IPCDoc(), uid, acc->State(), acc->Bounds(),
-                    name, textValue, nodeID, acc->CurValue(), acc->MinValue(),
-                    acc->MaxValue(), acc->Step(), attributes));
+      cacheData.AppendElement(BatchData(
+          acc->Document()->IPCDoc(), uid, acc->State(), acc->Bounds(),
+          acc->ActionCount(), name, textValue, nodeID, acc->CurValue(),
+          acc->MinValue(), acc->MaxValue(), acc->Step(), attributes));
       mFocusPath.Put(acc->UniqueID(), acc);
     }
 
     ipcDoc->SendBatch(eBatch_FocusPath, cacheData);
   } else if (SessionAccessibility* sessionAcc =
                  SessionAccessibility::GetInstanceFor(this)) {
     nsTArray<AccessibleWrap*> accessibles;
     for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent();
@@ -224,21 +225,26 @@ void DocAccessibleWrap::UpdateFocusPathB
     return;
   }
 
   if (IPCAccessibilityActive()) {
     DocAccessibleChild* ipcDoc = IPCDoc();
     nsTArray<BatchData> boundsData(mFocusPath.Count());
     for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
       Accessible* accessible = iter.Data();
+      if (!accessible || accessible->IsDefunct()) {
+        MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
+        continue;
+      }
+
       auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc()
                      ? 0
                      : reinterpret_cast<uint64_t>(accessible->UniqueID());
       boundsData.AppendElement(BatchData(
-          accessible->Document()->IPCDoc(), uid, 0, accessible->Bounds(),
+          accessible->Document()->IPCDoc(), uid, 0, accessible->Bounds(), 0,
           nsString(), nsString(), nsString(), UnspecifiedNaN<double>(),
           UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
           UnspecifiedNaN<double>(), nsTArray<Attribute>()));
     }
 
     ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData);
   } else if (SessionAccessibility* sessionAcc =
                  SessionAccessibility::GetInstanceFor(this)) {
--- a/accessible/android/ProxyAccessibleWrap.cpp
+++ b/accessible/android/ProxyAccessibleWrap.cpp
@@ -75,16 +75,28 @@ void ProxyAccessibleWrap::Value(nsString
 uint64_t ProxyAccessibleWrap::State() { return Proxy()->State(); }
 
 nsIntRect ProxyAccessibleWrap::Bounds() const { return Proxy()->Bounds(); }
 
 void ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const {
   Proxy()->ScrollTo(aHow);
 }
 
+uint8_t
+ProxyAccessibleWrap::ActionCount() const
+{
+  return Proxy()->ActionCount();
+}
+
+bool
+ProxyAccessibleWrap::DoAction(uint8_t aIndex) const
+{
+  return Proxy()->DoAction(aIndex);
+}
+
 // Other
 
 void ProxyAccessibleWrap::SetTextContents(const nsAString& aText) {
   Proxy()->ReplaceText(PromiseFlatString(aText));
 }
 
 void ProxyAccessibleWrap::GetTextContents(nsAString& aText) {
   nsAutoString text;
--- a/accessible/android/ProxyAccessibleWrap.h
+++ b/accessible/android/ProxyAccessibleWrap.h
@@ -40,16 +40,20 @@ class ProxyAccessibleWrap : public Acces
   virtual void Value(nsString& aValue) const override;
 
   virtual uint64_t State() override;
 
   virtual nsIntRect Bounds() const override;
 
   virtual void ScrollTo(uint32_t aHow) const override;
 
+  virtual uint8_t ActionCount() const override;
+
+  virtual bool DoAction(uint8_t aIndex) const override;
+
   // AccessibleWrap
 
   virtual void SetTextContents(const nsAString& aText) override;
 
   virtual void GetTextContents(nsAString& aText) override;
 
   virtual bool GetSelectionBounds(int32_t* aStartOffset,
                                   int32_t* aEndOffset) override;
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -101,16 +101,27 @@ void SessionAccessibility::SetText(int32
     if (!acc) {
       return;
     }
 
     acc->SetTextContents(aText->ToString());
   }
 }
 
+void SessionAccessibility::Click(int32_t aID) {
+  if (RootAccessibleWrap* rootAcc = GetRoot()) {
+    AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
+    if (!acc) {
+      return;
+    }
+
+    acc->DoAction(0);
+  }
+}
+
 SessionAccessibility* SessionAccessibility::GetInstanceFor(
     ProxyAccessible* aAccessible) {
   auto tab = static_cast<dom::TabParent*>(aAccessible->Document()->Manager());
   dom::Element* frame = tab->GetOwnerElement();
   MOZ_ASSERT(frame);
   if (!frame) {
     return nullptr;
   }
@@ -316,17 +327,17 @@ void SessionAccessibility::SendSelectedE
 void SessionAccessibility::ReplaceViewportCache(
     const nsTArray<AccessibleWrap*>& aAccessibles,
     const nsTArray<BatchData>& aData) {
   auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
   for (size_t i = 0; i < aAccessibles.Length(); i++) {
     AccessibleWrap* acc = aAccessibles.ElementAt(i);
     if (aData.Length() == aAccessibles.Length()) {
       const BatchData& data = aData.ElementAt(i);
-      auto bundle = acc->ToSmallBundle(data.State(), data.Bounds());
+      auto bundle = acc->ToSmallBundle(data.State(), data.Bounds(), data.ActionCount());
       infos->SetElement(i, bundle);
     } else {
       infos->SetElement(i, acc->ToSmallBundle());
     }
   }
 
   mSessionAccessibility->ReplaceViewportCache(infos);
 }
@@ -336,20 +347,20 @@ void SessionAccessibility::ReplaceFocusP
     const nsTArray<BatchData>& aData) {
   auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
   for (size_t i = 0; i < aAccessibles.Length(); i++) {
     AccessibleWrap* acc = aAccessibles.ElementAt(i);
     if (aData.Length() == aAccessibles.Length()) {
       const BatchData& data = aData.ElementAt(i);
       nsCOMPtr<nsIPersistentProperties> props =
           AccessibleWrap::AttributeArrayToProperties(data.Attributes());
-      auto bundle =
-          acc->ToBundle(data.State(), data.Bounds(), data.Name(),
-                        data.TextValue(), data.DOMNodeID(), data.CurValue(),
-                        data.MinValue(), data.MaxValue(), data.Step(), props);
+      auto bundle = acc->ToBundle(
+          data.State(), data.Bounds(), data.ActionCount(), data.Name(),
+          data.TextValue(), data.DOMNodeID(), data.CurValue(), data.MinValue(),
+          data.MaxValue(), data.Step(), props);
       infos->SetElement(i, bundle);
     } else {
       infos->SetElement(i, acc->ToBundle());
     }
   }
 
   mSessionAccessibility->ReplaceFocusPathCache(infos);
 }
@@ -357,17 +368,17 @@ void SessionAccessibility::ReplaceFocusP
 void SessionAccessibility::UpdateCachedBounds(
     const nsTArray<AccessibleWrap*>& aAccessibles,
     const nsTArray<BatchData>& aData) {
   auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
   for (size_t i = 0; i < aAccessibles.Length(); i++) {
     AccessibleWrap* acc = aAccessibles.ElementAt(i);
     if (aData.Length() == aAccessibles.Length()) {
       const BatchData& data = aData.ElementAt(i);
-      auto bundle = acc->ToSmallBundle(data.State(), data.Bounds());
+      auto bundle = acc->ToSmallBundle(data.State(), data.Bounds(), data.ActionCount());
       infos->SetElement(i, bundle);
     } else {
       infos->SetElement(i, acc->ToSmallBundle());
     }
   }
 
   mSessionAccessibility->UpdateCachedBounds(infos);
 }
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -70,16 +70,17 @@ class SessionAccessibility final
   static SessionAccessibility* GetInstanceFor(ProxyAccessible* aAccessible);
   static SessionAccessibility* GetInstanceFor(Accessible* aAccessible);
 
   // Native implementations
   using Base::AttachNative;
   using Base::DisposeNative;
   jni::Object::LocalRef GetNodeInfo(int32_t aID);
   void SetText(int32_t aID, jni::String::Param aText);
+  void Click(int32_t aID);
   void StartNativeAccessibility();
 
   // Event methods
   void SendFocusEvent(AccessibleWrap* aAccessible);
   void SendScrollingEvent(AccessibleWrap* aAccessible, int32_t aScrollX,
                           int32_t aScrollY, int32_t aMaxScrollX,
                           int32_t aMaxScrollY);
   void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -298,18 +298,17 @@ NS_IMETHODIMP
 DocManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                            nsresult aStatus, const char16_t* aMessage) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
-                             uint32_t aOldState, uint32_t aState,
-                             const nsAString& aContentBlockingLogJSON) {
+                             uint32_t aState) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIDOMEventListener
 
 NS_IMETHODIMP
--- a/accessible/ipc/other/PDocAccessible.ipdl
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -31,16 +31,17 @@ union OriginDocument
 };
 
 struct BatchData
 {
   OriginDocument Document;
   uint64_t ID;
   uint64_t State;
   nsIntRect Bounds;
+  uint8_t ActionCount;
   nsString Name;
   nsString TextValue;
   nsString DOMNodeID;
   double CurValue;
   double MinValue;
   double MaxValue;
   double Step;
   Attribute[] Attributes;
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -21,17 +21,16 @@ const GECKOVIEW_MESSAGE = {
   CLIPBOARD: "GeckoView:AccessibilityClipboard",
   CURSOR_TO_FOCUSED: "GeckoView:AccessibilityCursorToFocused",
   EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
   LONG_PRESS: "GeckoView:AccessibilityLongPress",
   NEXT: "GeckoView:AccessibilityNext",
   PREVIOUS: "GeckoView:AccessibilityPrevious",
   SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
   SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
-  SELECT: "GeckoView:AccessibilitySelect",
   SET_SELECTION: "GeckoView:AccessibilitySetSelection",
   VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
 };
 
 const ACCESSFU_MESSAGE = {
   DOSCROLL: "AccessFu:DoScroll",
 };
 
@@ -187,19 +186,16 @@ var AccessFu = {
         this.Input.moveToPoint("Simple", ...data.coordinates);
         break;
       case GECKOVIEW_MESSAGE.SET_SELECTION:
         this.Input.setSelection(data);
         break;
       case GECKOVIEW_MESSAGE.CLIPBOARD:
         this.Input.clipboard(data);
         break;
-      case GECKOVIEW_MESSAGE.SELECT:
-        this.Input.selectCurrent(data);
-        break;
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "domwindowopened": {
         let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
         win.addEventListener("load", () => {
@@ -296,21 +292,16 @@ var Input = {
     mm.sendAsyncMessage("AccessFu:Clipboard", aDetails);
   },
 
   activateCurrent: function activateCurrent(aData) {
     let mm = Utils.getMessageManager();
     mm.sendAsyncMessage("AccessFu:Activate", { offset: 0 });
   },
 
-  selectCurrent: function selectCurrent(aData) {
-    let mm = Utils.getMessageManager();
-    mm.sendAsyncMessage("AccessFu:Select", aData);
-  },
-
   doScroll: function doScroll(aDetails, aBrowser) {
     let horizontal = aDetails.horizontal;
     let page = aDetails.page;
     let win = aBrowser.ownerGlobal;
     let winUtils = win.windowUtils;
     let p = AccessFu.screenToClientBounds(aDetails.bounds, win).center();
     winUtils.sendWheelEvent(p.x, p.y,
       horizontal ? page : 0, horizontal ? 0 : page, 0,
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -34,17 +34,16 @@ this.ContentControl.prototype = {
   messagesOfInterest: ["AccessFu:Activate",
                        "AccessFu:AndroidScroll",
                        "AccessFu:AutoMove",
                        "AccessFu:ClearCursor",
                        "AccessFu:Clipboard",
                        "AccessFu:MoveByGranularity",
                        "AccessFu:MoveCursor",
                        "AccessFu:MoveToPoint",
-                       "AccessFu:Select",
                        "AccessFu:SetSelection"],
 
   start: function cc_start() {
     let cs = this._contentScope.get();
     for (let message of this.messagesOfInterest) {
       cs.addMessageListener(message, this);
     }
   },
@@ -171,26 +170,16 @@ this.ContentControl.prototype = {
     }
     this.document.activeElement.blur();
   },
 
   handleAutoMove: function cc_handleAutoMove(aMessage) {
     this.autoMove(null, aMessage.json);
   },
 
-  handleSelect: function cc_handleSelect(aMessage) {
-    const vc = this.vc;
-    if (!this.sendToChild(vc, aMessage, null, true)) {
-      const acc = vc.position;
-      if (Utils.getState(acc).contains(States.SELECTABLE)) {
-        this.handleActivate(aMessage);
-      }
-    }
-  },
-
   handleActivate: function cc_handleActivate(aMessage) {
     let activateAccessible = (aAccessible) => {
       Logger.debug(() => {
         return ["activateAccessible", Logger.accessibleToString(aAccessible)];
       });
 
       if (aAccessible.actionCount > 0) {
         aAccessible.doAction(0);
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -21,16 +21,18 @@ XPCOMUtils.defineLazyGlobalGetters(this,
 XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() {
   return Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
   return Services.strings.createBundle("chrome://branding/locale/brand.properties");
 });
 XPCOMUtils.defineLazyPreferenceGetter(this, "newErrorPagesEnabled",
   "browser.security.newcerterrorpage.enabled");
+XPCOMUtils.defineLazyPreferenceGetter(this, "mitmErrorPageEnabled",
+  "browser.security.newcerterrorpage.mitm.enabled");
 XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() {
   return Services.strings.createBundle("chrome://pipnss/locale/nsserrors.properties");
 });
 
 
 const SEC_ERROR_BASE          = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
 
@@ -119,18 +121,28 @@ class NetErrorChild extends ActorChild {
     }
 
     let msg1 = gPipNSSBundle.formatStringFromName("certErrorIntro",
                                                   [hostString], 1);
     msg1 += "\n\n";
 
     if (input.data.certIsUntrusted) {
       switch (input.data.code) {
-        // We only want to measure MitM rates for now. Treat it as unkown issuer.
         case MOZILLA_PKIX_ERROR_MITM_DETECTED:
+          if (newErrorPagesEnabled && mitmErrorPageEnabled) {
+            let brandName = gBrandBundle.GetStringFromName("brandShortName");
+            msg1 = gPipNSSBundle.GetStringFromName("certErrorMitM");
+            msg1 += "\n\n";
+            msg1 += gPipNSSBundle.formatStringFromName("certErrorMitM2", [brandName], 1);
+            msg1 += "\n\n";
+            msg1 += gPipNSSBundle.formatStringFromName("certErrorMitM3", [brandName], 1);
+            msg1 += "\n";
+            break;
+          }
+          // If the condition is false, fall through...
         case SEC_ERROR_UNKNOWN_ISSUER:
           let brandName = gBrandBundle.GetStringFromName("brandShortName");
           if (newErrorPagesEnabled) {
             msg1 = "";
             msg1 += gPipNSSBundle.formatStringFromName("certErrorTrust_UnknownIssuer4", [hostString], 1);
             msg1 += "\n\n";
             msg1 += gPipNSSBundle.formatStringFromName("certErrorTrust_UnknownIssuer6", [brandName, hostString], 2);
             msg1 += "\n\n";
@@ -405,16 +417,49 @@ class NetErrorChild extends ActorChild {
           gPipNSSBundle.GetStringFromName("certErrorSymantecDistrustAdministrator");
         descriptionContainer.append(adminDescription);
 
         learnMoreLink.href = baseURL + "symantec-warning";
 
         updateContainerPosition();
         break;
       case MOZILLA_PKIX_ERROR_MITM_DETECTED:
+        if (newErrorPagesEnabled && mitmErrorPageEnabled) {
+          // We don't actually know what the MitM is called (since we don't
+          // maintain a list), so we'll try and display the common name of the
+          // root issuer to the user. In the worst case they are as clueless as
+          // before, in the best case this gives them an actionable hint.
+          // This may be revised in the future.
+          let {securityInfo} = docShell.failedChannel;
+          securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+          let mitmName = null;
+          for (let cert of securityInfo.failedCertChain.getEnumerator()) {
+            mitmName = cert.issuerCommonName;
+          }
+          for (let span of doc.querySelectorAll(".mitm-name")) {
+            span.textContent = mitmName;
+          }
+
+          learnMoreLink.href = baseURL + "security-error";
+
+          let title = doc.getElementById("et_mitm");
+          let desc = doc.getElementById("ed_mitm");
+          doc.querySelector(".title-text").textContent = title.textContent;
+          // eslint-disable-next-line no-unsanitized/property
+          doc.getElementById("errorShortDescText").innerHTML = desc.innerHTML;
+
+          // eslint-disable-next-line no-unsanitized/property
+          es.innerHTML = errWhatToDo.innerHTML;
+          // eslint-disable-next-line no-unsanitized/property
+          est.innerHTML = errWhatToDoTitle.innerHTML;
+
+          updateContainerPosition();
+          break;
+        }
+        // If the condition is false, fall through...
       case MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
         learnMoreLink.href = baseURL + "security-error";
         break;
 
       // In case the certificate expired we make sure the system clock
       // matches the remote-settings service (blocklist via Kinto) ping time
       // and is not before the build date.
       case SEC_ERROR_EXPIRED_CERTIFICATE:
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -951,18 +951,20 @@ pref("app.feedback.baseURL", "https://in
 pref("app.productInfo.baseURL", "https://www.mozilla.org/firefox/features/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 // Enable the new certificate error pages.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.security.newcerterrorpage.enabled", true);
+pref("browser.security.newcerterrorpage.mitm.enabled", true);
 #else
 pref("browser.security.newcerterrorpage.enabled", false);
+pref("browser.security.newcerterrorpage.mitm.enabled", false);
 #endif
 
 pref("security.certerrors.recordEventTelemetry", true);
 
 // Whether to start the private browsing mode at application startup
 pref("browser.privatebrowsing.autostart", false);
 
 // Whether the bookmark panel should be shown when bookmarking a page.
@@ -1466,19 +1468,19 @@ pref("media.gmp.trial-create.enabled", t
 pref("media.gmp-widevinecdm.visible", true);
 pref("media.gmp-widevinecdm.enabled", true);
 #endif
 
 #ifdef NIGHTLY_BUILD
 // Switch block autoplay logic to v2, and enable UI.
 pref("media.autoplay.enabled.user-gestures-needed", true);
 // Allow asking for permission to autoplay to appear in UI.
-pref("media.autoplay.ask-permission", true);
+pref("media.autoplay.ask-permission", false);
 // Set Firefox to block autoplay, asking for permission by default.
-pref("media.autoplay.default", 2); // 0=Allowed, 1=Blocked, 2=Prompt
+pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked, 2=Prompt
 #else
 pref("media.autoplay.default", 0); // 0=Allowed, 1=Blocked, 2=Prompt
 pref("media.autoplay.enabled.user-gestures-needed", false);
 pref("media.autoplay.ask-permission", false);
 #endif
 
 
 // Play with different values of the decay time and get telemetry,
@@ -1532,16 +1534,18 @@ pref("dom.storage_access.enabled", true)
 
 pref("dom.storage_access.auto_grants", true);
 pref("dom.storage_access.max_concurrent_auto_grants", 5);
 
 // Define a set of default features for the Content Blocking UI.
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 
+pref("browser.contentblocking.control-center.ui.showAllowedLabels", false);
+
 // Enable the Report Breakage UI on Nightly and Beta but not on Release yet.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.contentblocking.reportBreakage.enabled", true);
 #else
 pref("browser.contentblocking.reportBreakage.enabled", false);
 #endif
 // Show report breakage for tracking cookies in all channels.
 pref("browser.contentblocking.rejecttrackers.reportBreakage.enabled", true);
--- a/browser/base/content/aboutNetError-new.xhtml
+++ b/browser/base/content/aboutNetError-new.xhtml
@@ -63,16 +63,17 @@
         <h1 id="et_nssBadCert">&certerror.longpagetitle2;</h1>
         <h1 id="et_nssBadCert_sts">&certerror.sts.longpagetitle;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
         <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
         <h1 id="et_blockedByPolicy">&blockedByPolicy.title;</h1>
+        <h1 id="et_mitm">&certerror.mitm.title;</h1>
         <h1 id="et_clockSkewError">&clockSkewError.title;</h1>
         <h1 id="et_networkProtocolError">&networkProtocolError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_captivePortal">&captivePortal.longDesc2;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc1;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -91,16 +92,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&certerror.introPara2;</div>
         <div id="ed_nssBadCert_sts">&certerror.sts.introPara;</div>
         <div id="ed_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE">&certerror.expiredCert.introPara;</div>
+        <div id="ed_mitm">&certerror.mitm.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
         <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
         <div id="ed_blockedByPolicy"></div>
         <div id="ed_clockSkewError">&clockSkewError.longDesc;</div>
         <div id="ed_networkProtocolError">&networkProtocolError.longDesc;</div>
@@ -114,21 +116,29 @@
       </div>
       <div id="whatCanYouDoAboutItContainer">
         <div id="es_nssBadCert_SEC_ERROR_UNKNOWN_ISSUER">&certerror.unknownIssuer.whatCanYouDoAboutIt;</div>
         <div id="es_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE">&certerror.expiredCert.whatCanYouDoAboutIt2;</div>
         <div id="es_nssBadCert_SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE">&certerror.expiredCert.whatCanYouDoAboutIt2;</div>
         <div id="es_nssBadCert_MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE">&certerror.expiredCert.whatCanYouDoAboutIt2;</div>
         <div id="es_nssBadCert_MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE">&certerror.expiredCert.whatCanYouDoAboutIt2;</div>
         <div id="es_nssBadCert_SSL_ERROR_BAD_CERT_DOMAIN">&certerror.badCertDomain.whatCanYouDoAboutIt;</div>
+        <div id="es_nssBadCert_MOZILLA_PKIX_ERROR_MITM_DETECTED">
+          <ul>
+            <li>&certerror.mitm.whatCanYouDoAboutIt1;</li>
+            <li>&certerror.mitm.whatCanYouDoAboutIt2;</li>
+            <li id="mitmWhatCanYouDoAboutIt3">&certerror.mitm.whatCanYouDoAboutIt3;</li>
+          </ul>
+        </div>
       </div>
       <!-- Stores an alternative text for when we don't want to add "Recommended" to the
            return button. This is one of many l10n atrocities in this file and should be
            removed when we finally switch to Fluent. -->
       <span id="stsReturnButtonText">&returnToPreviousPage.label;</span>
+      <span id="stsMitmWhatCanYouDoAboutIt3">&certerror.mitm.sts.whatCanYouDoAboutIt3;</span>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer" class="container">
       <div id="text-container">
         <!-- Error Title -->
         <div class="title">
           <h1 class="title-text"/>
--- a/browser/base/content/aboutNetError.js
+++ b/browser/base/content/aboutNetError.js
@@ -129,16 +129,21 @@ function disallowCertOverridesIfNeeded()
   }
   if (cssClass == "badStsCert") {
     document.getElementById("badStsCertExplanation").removeAttribute("hidden");
 
     if (gNewErrorPagesEnabled) {
       let stsReturnButtonText = document.getElementById("stsReturnButtonText").textContent;
       document.getElementById("returnButton").textContent = stsReturnButtonText;
       document.getElementById("advancedPanelReturnButton").textContent = stsReturnButtonText;
+
+      let stsMitmWhatCanYouDoAboutIt3 =
+        document.getElementById("stsMitmWhatCanYouDoAboutIt3").innerHTML;
+      // eslint-disable-next-line no-unsanitized/property
+      document.getElementById("mitmWhatCanYouDoAboutIt3").innerHTML = stsMitmWhatCanYouDoAboutIt3;
     }
   }
 }
 
 function initPage() {
   var err = getErrorCode();
   // List of error pages with an illustration.
   let illustratedErrors = [
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -75,19 +75,19 @@ var TrackingProtection = {
     this.updateCategoryLabel();
   },
 
   updateCategoryLabel() {
     let label;
     if (this.enabled) {
       label = "contentBlocking.trackers.blocked.label";
     } else {
-      label = "contentBlocking.trackers.allowed.label";
+      label = ContentBlocking.showAllowedLabels ? "contentBlocking.trackers.allowed.label" : null;
     }
-    this.categoryLabel.textContent = gNavigatorBundle.getString(label);
+    this.categoryLabel.textContent = label ? gNavigatorBundle.getString(label) : "";
   },
 
   isBlocking(state) {
     return (state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) != 0;
   },
 
   isAllowing(state) {
     return (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) != 0;
@@ -261,20 +261,20 @@ var ThirdPartyCookies = {
       break;
     case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
       label = "contentBlocking.cookies.trackersBlocked.label";
       break;
     default:
       Cu.reportError(`Error: Unknown cookieBehavior pref observed: ${this.behaviorPref}`);
       // fall through
     case Ci.nsICookieService.BEHAVIOR_ACCEPT:
-      label = "contentBlocking.cookies.allowed.label";
+      label = ContentBlocking.showAllowedLabels ? "contentBlocking.cookies.allowed.label" : null;
       break;
     }
-    this.categoryLabel.textContent = gNavigatorBundle.getString(label);
+    this.categoryLabel.textContent = label ? gNavigatorBundle.getString(label) : "";
   },
 
   init() {
     XPCOMUtils.defineLazyPreferenceGetter(this, "behaviorPref", this.PREF_ENABLED,
       Ci.nsICookieService.BEHAVIOR_ACCEPT, this.updateCategoryLabel.bind(this));
     XPCOMUtils.defineLazyPreferenceGetter(this, "reportBreakageEnabled",
       this.PREF_REPORT_BREAKAGE_ENABLED, false);
     this.updateCategoryLabel();
@@ -483,16 +483,17 @@ var ThirdPartyCookies = {
 var ContentBlocking = {
   // If the user ignores the doorhanger, we stop showing it after some time.
   MAX_INTROS: 20,
   PREF_ANIMATIONS_ENABLED: "toolkit.cosmeticAnimations.enabled",
   PREF_REPORT_BREAKAGE_ENABLED: "browser.contentblocking.reportBreakage.enabled",
   PREF_REPORT_BREAKAGE_URL: "browser.contentblocking.reportBreakage.url",
   PREF_INTRO_COUNT_CB: "browser.contentblocking.introCount",
   PREF_CB_CATEGORY: "browser.contentblocking.category",
+  PREF_SHOW_ALLOWED_LABELS: "browser.contentblocking.control-center.ui.showAllowedLabels",
   content: null,
   icon: null,
   activeTooltipText: null,
   disabledTooltipText: null,
 
   get prefIntroCount() {
     return this.PREF_INTRO_COUNT_CB;
   },
@@ -570,16 +571,22 @@ var ContentBlocking = {
         blocker.init();
       }
     }
 
     this.updateAnimationsEnabled();
 
     Services.prefs.addObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
 
+    XPCOMUtils.defineLazyPreferenceGetter(this, "showAllowedLabels",
+      this.PREF_SHOW_ALLOWED_LABELS, false, () => {
+        for (let blocker of this.blockers) {
+          blocker.updateCategoryLabel();
+        }
+    });
     XPCOMUtils.defineLazyPreferenceGetter(this, "reportBreakageEnabled",
       this.PREF_REPORT_BREAKAGE_ENABLED, false);
 
     this.appMenuLabel.setAttribute("value", this.strings.appMenuTitle);
     this.appMenuLabel.setAttribute("tooltiptext", this.strings.appMenuTooltip);
 
     this.activeTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
@@ -712,18 +719,17 @@ var ContentBlocking = {
 
   shieldHistogramAdd(value) {
     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
     Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
   },
 
-  onSecurityChange(oldState, state, webProgress, isSimulated,
-                   contentBlockingLogJSON) {
+  onSecurityChange(state, webProgress, isSimulated) {
     let baseURI = this._baseURIForChannelClassifier;
 
     // Don't deal with about:, file: etc.
     if (!baseURI) {
       this.iconBox.removeAttribute("animate");
       this.iconBox.removeAttribute("active");
       this.iconBox.removeAttribute("hasException");
       return;
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -743,17 +743,17 @@ var BookmarksEventHandler = {
       closeMenus(aEvent.target);
     }
 
     if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
       // Don't open the root folder in tabs when the empty area on the toolbar
       // is middle-clicked or when a non-bookmark item (except for Open in Tabs)
       // in a bookmarks menupopup is middle-clicked.
       if (target.localName == "menu" || target.localName == "toolbarbutton")
-        PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
+        PlacesUIUtils.openMultipleLinksInTabs(target._placesNode, aEvent, aView);
     } else if (aEvent.button == 1) {
       // left-clicks with modifier are already served by onCommand
       this.onCommand(aEvent);
     }
   },
 
   /**
    * Handler for command event for an item in the bookmarks toolbar.
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3730,31 +3730,49 @@ const DOMEventHandler = {
   clearPendingIcon(aBrowser) {
     let tab = gBrowser.getTabForBrowser(aBrowser);
     tab.removeAttribute("pendingicon");
   },
 
   setIconFromLink(aBrowser, aPageURL, aOriginalURL, aCanUseForTab, aExpiration, aIconURL) {
     let tab = gBrowser.getTabForBrowser(aBrowser);
     if (!tab) {
-      return false;
+      return;
+    }
+
+    if (aCanUseForTab) {
+      this.clearPendingIcon(aBrowser);
+    }
+
+    let iconURI;
+    try {
+      iconURI = Services.io.newURI(aIconURL);
+    } catch (ex) {
+      Cu.reportError(ex);
+      return;
+    }
+    if (iconURI.scheme != "data") {
+      try {
+        Services.scriptSecurityManager.checkLoadURIWithPrincipal(
+          aBrowser.contentPrincipal, iconURI, Services.scriptSecurityManager.ALLOW_CHROME);
+      } catch (ex) {
+        return;
+      }
     }
     try {
       PlacesUIUtils.loadFavicon(aBrowser, Services.scriptSecurityManager.getSystemPrincipal(),
                                 makeURI(aPageURL), makeURI(aOriginalURL),
-                                aExpiration, makeURI(aIconURL));
+                                aExpiration, iconURI);
     } catch (ex) {
       Cu.reportError(ex);
     }
 
     if (aCanUseForTab) {
-      this.clearPendingIcon(aBrowser);
       gBrowser.setIcon(tab, aIconURL, aOriginalURL);
     }
-    return true;
   },
 
   addSearch(aBrowser, aEngine, aURL) {
     let tab = gBrowser.getTabForBrowser(aBrowser);
     if (!tab)
       return;
 
     BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
@@ -4922,18 +4940,17 @@ var XULBrowserWindow = {
   _lastLocation: null,
 
   // This is called in multiple ways:
   //  1. Due to the nsIWebProgressListener.onSecurityChange notification.
   //  2. Called by tabbrowser.xml when updating the current browser.
   //  3. Called directly during this object's initializations.
   // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
   // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT is observed).
-  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
-                   aContentBlockingLogJSON, aIsSimulated) {
+  onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
     // Don't need to do anything if the data we use to update the UI hasn't
     // changed
     let uri = gBrowser.currentURI;
     let spec = uri.spec;
     if (this._state == aState &&
         this._lastLocation == spec) {
       // Switching to a tab of the same URL doesn't change most security
       // information, but tab specific permissions may be different.
@@ -4950,18 +4967,17 @@ var XULBrowserWindow = {
     // Make sure the "https" part of the URL is striked out or not,
     // depending on the current mixed active content blocking state.
     gURLBar.formatValue();
 
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
     gIdentityHandler.updateIdentity(this._state, uri);
-    ContentBlocking.onSecurityChange(aOldState, this._state, aWebProgress, aIsSimulated,
-                                     aContentBlockingLogJSON);
+    ContentBlocking.onSecurityChange(this._state, aWebProgress, aIsSimulated);
   },
 
   // simulate all change notifications after switching tabs
   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
     if (FullZoom.updateBackgroundTabs)
       FullZoom.onLocationChange(gBrowser.currentURI, true);
 
     CombinedStopReload.onTabSwitch();
@@ -5907,33 +5923,38 @@ const nodeToShortcutMap = {
 };
 
 if (AppConstants.platform == "macosx") {
   nodeToTooltipMap["print-button"] = "printButton.tooltip";
   nodeToShortcutMap["print-button"] = "printKb";
 }
 
 const gDynamicTooltipCache = new Map();
-function UpdateDynamicShortcutTooltipText(aTooltip) {
-  let nodeId = aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
+function GetDynamicShortcutTooltipText(nodeId) {
   if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
     let strId = nodeToTooltipMap[nodeId];
     let args = [];
     if (nodeId in nodeToShortcutMap) {
       let shortcutId = nodeToShortcutMap[nodeId];
       let shortcut = document.getElementById(shortcutId);
       if (shortcut) {
         args.push(ShortcutUtils.prettifyShortcut(shortcut));
       }
     }
     gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args));
   }
-  aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId));
+  return gDynamicTooltipCache.get(nodeId);
 }
 
+function UpdateDynamicShortcutTooltipText(aTooltip) {
+  let nodeId = aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
+  aTooltip.setAttribute("label", GetDynamicShortcutTooltipText(nodeId));
+}
+
+
 /*
  * - [ Dependencies ] ---------------------------------------------------------
  *  utilityOverlay.js:
  *    - gatherTextUnder
  */
 
 /**
  * Extracts linkNode and href for the current click target.
@@ -7405,17 +7426,17 @@ const gAccessibilityServiceIndicator = {
   },
 
   handleEvent({ key, type }) {
     if ((type === "keypress" && [" ", "Enter"].includes(key)) ||
          type === "click") {
       let a11yServicesSupportURL =
         Services.urlFormatter.formatURLPref("accessibility.support.url");
       // This is a known URL coming from trusted UI
-      gBrowser.selectedTab = gBrowser.addTrustedTab(a11yServicesSupportURL);
+      openTrustedLinkIn(a11yServicesSupportURL, "tab");
       Services.telemetry.scalarSet("a11y.indicator_acted_on", true);
     }
   },
 
   uninit() {
     Services.prefs.removeObserver("accessibility.indicator.enabled", this);
     Services.obs.removeObserver(this, "a11y-init-or-shutdown");
   },
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -957,23 +957,18 @@ window._gBrowser = {
     this._callProgressListeners(null, "onLocationChange",
                                 [webProgress, null, newBrowser.currentURI, 0, true],
                                 true, false);
 
     let securityUI = newBrowser.securityUI;
     if (securityUI) {
       // Include the true final argument to indicate that this event is
       // simulated (instead of being observed by the webProgressListener).
-      // Note: check state first to make sure the security UI object updates its
-      // state from the docshell correctly.
-      let state = securityUI.state;
-      let oldState = securityUI.oldState;
       this._callProgressListeners(null, "onSecurityChange",
-                                  [webProgress, null, oldState, state,
-                                   securityUI.contentBlockingLogJSON, true],
+                                  [webProgress, null, securityUI.state, true],
                                   true, false);
     }
 
     let listener = this._tabListeners.get(newTab);
     if (listener && listener.mStateFlags) {
       this._callProgressListeners(null, "onUpdateCurrentBrowser",
                                   [listener.mStateFlags, listener.mStatus,
                                    listener.mMessage, listener.mTotalProgress],
@@ -1708,27 +1703,22 @@ window._gBrowser = {
     this._tabListeners.set(tab, listener);
     filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the progress listener.
     aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the securityUI state.
     let securityUI = aBrowser.securityUI;
-    // Make sure to call the state getter before the oldState getter to give
-    // the securityUI object a chance to sync its state with the docshell
     let state = securityUI ? securityUI.state :
       Ci.nsIWebProgressListener.STATE_IS_INSECURE;
-    let oldState = securityUI ? securityUI.oldState :
-      Ci.nsIWebProgressListener.STATE_IS_INSECURE;
     // Include the true final argument to indicate that this event is
     // simulated (instead of being observed by the webProgressListener).
     this._callProgressListeners(aBrowser, "onSecurityChange",
-                                [aBrowser.webProgress, null, oldState, state,
-                                 securityUI.contentBlockingLogJSON, true],
+                                [aBrowser.webProgress, null, state, true],
                                 true, false);
 
     if (aShouldBeRemote) {
       // Switching the browser to be remote will connect to a new child
       // process so the browser can no longer be considered to be
       // crashed.
       tab.removeAttribute("crashed");
     } else {
@@ -5201,20 +5191,19 @@ class TabProgressListener {
       return;
 
     this._callProgressListeners("onStatusChange",
                                 [aWebProgress, aRequest, aStatus, aMessage]);
 
     this.mMessage = aMessage;
   }
 
-  onSecurityChange(aWebProgress, aRequest, aOldState, aState, aContentBlockingLogJSON) {
+  onSecurityChange(aWebProgress, aRequest, aState) {
     this._callProgressListeners("onSecurityChange",
-                                [aWebProgress, aRequest, aOldState, aState,
-                                 aContentBlockingLogJSON]);
+                                [aWebProgress, aRequest, aState]);
   }
 
   onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
     return this._callProgressListeners("onRefreshAttempted",
                                        [aWebProgress, aURI, aDelay, aSameURI]);
   }
 }
 TabProgressListener.prototype.QueryInterface = ChromeUtils.generateQI(
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -88,17 +88,17 @@
   This is to ensure anything extensions put here will go before the newtab
   button, necessary due to the previous hack.
 -->
         <children/>
         <xul:toolbarbutton class="tabs-newtab-button toolbarbutton-1"
                            anonid="tabs-newtab-button"
                            command="cmd_newNavigatorTab"
                            onclick="checkForMiddleClick(this, event);"
-                           tooltip="dynamic-shortcut-tooltip"/>
+                           />
         <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
                     style="width: 0;"/>
       </xul:arrowscrollbox>
     </content>
 
     <implementation implements="nsIObserver">
       <constructor>
         <![CDATA[
@@ -107,16 +107,20 @@
 
           let strId = PrivateBrowsingUtils.isWindowPrivate(window) ?
               "emptyPrivateTabTitle" : "emptyTabTitle";
           this.emptyTabTitle = gTabBrowserBundle.GetStringFromName("tabs." + strId);
 
           var tab = this.firstElementChild;
           tab.label = this.emptyTabTitle;
 
+          let newTabButton = document.getAnonymousElementByAttribute(
+            this, "anonid", "tabs-newtab-button");
+          newTabButton.setAttribute("tooltiptext", GetDynamicShortcutTooltipText("tabs-newtab-button"));
+
           window.addEventListener("resize", this);
 
           Services.prefs.addObserver("privacy.userContext", this);
           this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
 
           XPCOMUtils.defineLazyPreferenceGetter(this, "_tabMinWidthPref",
             "browser.tabs.tabMinWidth", null,
             (pref, prevValue, newValue) => this._tabMinWidth = newValue,
--- a/browser/base/content/test/favicons/browser_oversized.js
+++ b/browser/base/content/test/favicons/browser_oversized.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const ROOT = "http://mochi.test:8888/browser/browser/base/content/test/favicons/";
 
 add_task(async () => {
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, async (browser) => {
-    let faviconPromise = waitForFaviconMessage();
+    let faviconPromise = waitForFaviconMessage(true, `${ROOT}large.png`);
 
     BrowserTestUtils.loadURI(browser, ROOT + "large_favicon.html");
     await BrowserTestUtils.browserLoaded(browser);
 
     await Assert.rejects(faviconPromise, result => {
       return result.iconURL == `${ROOT}large.png`;
     }, "Should have failed to load the large icon.");
   });
--- a/browser/base/content/test/general/browser_alltabslistener.js
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -31,18 +31,17 @@ var gFrontProgressListener = {
     ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
     is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
     gFrontNotificationsPos++;
   },
 
   onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
   },
 
-  onSecurityChange(aWebProgress, aRequest, aOldState, aState,
-                   aContentBlockingLogJSON) {
+  onSecurityChange(aWebProgress, aRequest, aState) {
     if (aRequest &&
         aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec == "about:blank") {
       // ignore initial about blank
       return;
     }
     var state = "onSecurityChange";
     info("FrontProgress: " + state + " 0x" + aState.toString(16));
     ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
@@ -91,18 +90,17 @@ var gAllProgressListener = {
         aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec == "about:blank") {
       // ignore initial about blank
       return;
     }
     var state = "onStatusChange";
     ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
   },
 
-  onSecurityChange(aBrowser, aWebProgress, aRequest, aOldState, aState,
-                   aContentBlockingLogJSON) {
+  onSecurityChange(aBrowser, aWebProgress, aRequest, aState) {
     if (aRequest &&
         aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec == "about:blank") {
       // ignore initial about blank
       return;
     }
     var state = "onSecurityChange";
     info("AllProgress: " + state + " 0x" + aState.toString(16));
     ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -44,16 +44,28 @@ let gWhitelist = [{
     file: "netError.dtd",
     key: "certerror.wrongSystemTimeWithoutReference",
     type: "single-quote",
   }, {
     file: "netError.dtd",
     key: "clockSkewError.longDesc",
     type: "single-quote",
   }, {
+    file: "netError.dtd",
+    key: "certerror.mitm.longDesc",
+    type: "single-quote",
+  }, {
+    file: "netError.dtd",
+    key: "certerror.mitm.whatCanYouDoAboutIt3",
+    type: "single-quote",
+  }, {
+    file: "netError.dtd",
+    key: "certerror.mitm.sts.whatCanYouDoAboutIt3",
+    type: "single-quote",
+  }, {
     file: "phishing-afterload-warning-message.dtd",
     key: "safeb.palm.advisory.desc2",
     type: "single-quote",
   }, {
     file: "phishing-afterload-warning-message.dtd",
     key: "safeb.blocked.malwarePage.errorDesc.override",
     type: "single-quote",
   }, {
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
@@ -17,33 +17,16 @@ requestLongerTimeout(2);
 registerCleanupFunction(function() {
   UrlClassifierTestUtils.cleanupTestTrackers();
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(NCB_PREF);
   Services.prefs.clearUserPref(ContentBlocking.prefIntroCount);
 });
 
-function waitForSecurityChange(tabbrowser, numChanges = 1) {
-  return new Promise(resolve => {
-    let n = 0;
-    let listener = {
-      onSecurityChange() {
-        n = n + 1;
-        info("Received onSecurityChange event " + n + " of " + numChanges);
-        if (n >= numChanges) {
-          tabbrowser.removeProgressListener(listener);
-          resolve(n);
-        }
-      },
-    };
-    tabbrowser.addProgressListener(listener);
-  });
-}
-
 async function testTrackingProtectionAnimation(tabbrowser) {
   info("Load a test page not containing tracking elements");
   let benignTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
   let ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
 
   ok(!ContentBlocking.iconBox.hasAttribute("active"), "iconBox not active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
@@ -57,101 +40,101 @@ async function testTrackingProtectionAni
   info("Load a test page containing tracking cookies");
   let trackingCookiesTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, COOKIE_PAGE);
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Switch from tracking cookie -> benign tab");
-  let securityChanged = waitForSecurityChange(tabbrowser);
+  let securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = benignTab;
   await securityChanged;
 
   ok(!ContentBlocking.iconBox.hasAttribute("active"), "iconBox not active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Switch from benign -> tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = trackingTab;
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Switch from tracking -> tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = trackingCookiesTab;
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Reload tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser, 3);
+  securityChanged = waitForSecurityChange(3, tabbrowser.ownerGlobal);
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Reload tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser, 4);
+  securityChanged = waitForSecurityChange(4, tabbrowser.ownerGlobal);
   tabbrowser.selectedTab = trackingTab;
   tabbrowser.reload();
   await securityChanged;
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
   await BrowserTestUtils.waitForEvent(ContentBlocking.animatedIcon, "animationend");
 
   info("Inject tracking cookie inside tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   let timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("cookie", "*");
   });
   let result = await Promise.race([securityChanged, timeoutPromise]);
   is(result, undefined, "No securityChange events should be received");
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Inject tracking element inside tracking tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("tracking", "*");
   });
   result = await Promise.race([securityChanged, timeoutPromise]);
   is(result, undefined, "No securityChange events should be received");
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   tabbrowser.selectedTab = trackingCookiesTab;
 
   info("Inject tracking cookie inside tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("cookie", "*");
   });
   result = await Promise.race([securityChanged, timeoutPromise]);
   is(result, undefined, "No securityChange events should be received");
 
   ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Inject tracking element inside tracking cookies tab");
-  securityChanged = waitForSecurityChange(tabbrowser);
+  securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
   timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
   await ContentTask.spawn(tabbrowser.selectedBrowser, {},
                           function() {
     content.postMessage("tracking", "*");
   });
   result = await Promise.race([securityChanged, timeoutPromise]);
   is(result, undefined, "No securityChange events should be received");
 
--- a/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
@@ -65,16 +65,18 @@ add_task(async function testCategoryLabe
     await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
       gNavigatorBundle.getString("contentBlocking.category.custom"));
     is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.custom"),
       "The appMenuCategory label has been changed to custom");
   });
 });
 
 add_task(async function testSubcategoryLabels() {
+  SpecialPowers.pushPrefEnv({set: [["browser.contentblocking.control-center.ui.showAllowedLabels", true]]});
+
   await BrowserTestUtils.withNewTab("http://www.example.com", async function() {
     let categoryLabel =
       document.getElementById("identity-popup-content-blocking-tracking-protection-state-label");
 
     Services.prefs.setBoolPref(TP_PREF, true);
     await TestUtils.waitForCondition(() => categoryLabel.textContent ==
       gNavigatorBundle.getString("contentBlocking.trackers.blocked.label"),
       "The category label has updated correctly");
--- a/browser/base/content/test/trackingUI/head.js
+++ b/browser/base/content/test/trackingUI/head.js
@@ -35,24 +35,27 @@ function promiseTabLoadEvent(tab, url) {
 
 function openIdentityPopup() {
   let mainView = document.getElementById("identity-popup-mainView");
   let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
   gIdentityHandler._identityBox.click();
   return viewShown;
 }
 
-function waitForSecurityChange(numChanges = 1) {
+function waitForSecurityChange(numChanges = 1, win = null) {
+  if (!win) {
+    win = window;
+  }
   return new Promise(resolve => {
     let n = 0;
     let listener = {
       onSecurityChange() {
         n = n + 1;
         info("Received onSecurityChange event " + n + " of " + numChanges);
         if (n >= numChanges) {
-          gBrowser.removeProgressListener(listener);
+          win.gBrowser.removeProgressListener(listener);
           resolve(n);
         }
       },
     };
-    gBrowser.addProgressListener(listener);
+    win.gBrowser.addProgressListener(listener);
   });
 }
--- a/browser/components/aboutconfig/test/browser/browser_search.js
+++ b/browser/components/aboutconfig/test/browser/browser_search.js
@@ -19,45 +19,57 @@ add_task(async function test_search() {
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: PAGE_URL,
   }, async browser => {
     let prefArray = Services.prefs.getChildList("");
 
     // Test page loaded with correct number of prefs.
     await ContentTask.spawn(browser, prefArray, aPrefArray => {
-      Assert.equal(content.document.getElementById("prefs").childElementCount,
-                   aPrefArray.length);
+      // The total number of preferences may change at any time because of
+      // operations running in the background, so we only test approximately.
+      // The change in count would be because of one or two added preferences,
+      // but we tolerate a difference of up to 50 preferences just to be safe.
+      // We want thousands of prefs instead of a few dozen that are filtered.
+      Assert.greater(content.document.getElementById("prefs").childElementCount,
+                     aPrefArray.length - 50);
 
-      // Test page search of "button" returns correct number of preferences.
+      // Filter a subset of preferences. The "browser.download." branch is
+      // chosen because it is very unlikely that its preferences would be
+      // modified by other code during the execution of this test.
       let search = content.document.getElementById("search");
-      search.value = "button   ";
+      search.value = "wser.down   ";
       search.focus();
     });
 
     EventUtils.sendKey("return");
     await ContentTask.spawn(browser, prefArray, aPrefArray => {
       let filteredPrefArray =
-          aPrefArray.filter(pref => pref.includes("button"));
+          aPrefArray.filter(pref => pref.includes("wser.down"));
       // Adding +1 to the list since button does not match an exact
       // preference name then a row is added for the user to add a
       // new button preference if desired
       Assert.equal(content.document.getElementById("prefs").childElementCount,
                    filteredPrefArray.length + 1);
 
       // Test empty search returns all preferences.
       let search = content.document.getElementById("search");
       search.value = "";
       search.focus();
     });
 
     EventUtils.sendKey("return");
     await ContentTask.spawn(browser, prefArray, aPrefArray => {
-      Assert.equal(content.document.getElementById("prefs").childElementCount,
-                   aPrefArray.length);
+      // The total number of preferences may change at any time because of
+      // operations running in the background, so we only test approximately.
+      // The change in count would be because of one or two added preferences,
+      // but we tolerate a difference of up to 50 preferences just to be safe.
+      // We want thousands of prefs instead of a few dozen that are filtered.
+      Assert.greater(content.document.getElementById("prefs").childElementCount,
+                     aPrefArray.length - 50);
 
       // Test invalid search returns no preferences.
       let search = content.document.getElementById("search");
       search.value = "aJunkValueasdf";
       search.focus();
     });
 
     EventUtils.sendKey("return");
--- a/browser/components/extensions/parent/ext-pageAction.js
+++ b/browser/components/extensions/parent/ext-pageAction.js
@@ -50,18 +50,19 @@ this.pageAction = class extends Extensio
     let show, showMatches, hideMatches;
     let show_matches = options.show_matches || [];
     let hide_matches = options.hide_matches || [];
     if (!show_matches.length) {
       // Always hide by default. No need to do any pattern matching.
       show = false;
     } else {
       // Might show or hide depending on the URL. Enable pattern matching.
-      showMatches = new MatchPatternSet(show_matches);
-      hideMatches = new MatchPatternSet(hide_matches);
+      const {restrictSchemes} = extension;
+      showMatches = new MatchPatternSet(show_matches, {restrictSchemes});
+      hideMatches = new MatchPatternSet(hide_matches, {restrictSchemes});
     }
 
     this.defaults = {
       show,
       showMatches,
       hideMatches,
       title: options.default_title || extension.name,
       popup: options.default_popup || "",
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1216,18 +1216,17 @@ this.tabs = class extends ExtensionAPI {
                 }
                 if (pageSettings.footerRight !== null) {
                   printSettings.footerStrRight = pageSettings.footerRight;
                 }
 
                 let printProgressListener = {
                   onLocationChange(webProgress, request, location, flags) { },
                   onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { },
-                  onSecurityChange(webProgress, request, oldState, state,
-                                   contentBlockingLogJSON) { },
+                  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");
--- a/browser/components/extensions/schemas/geckoProfiler.json
+++ b/browser/components/extensions/schemas/geckoProfiler.json
@@ -30,17 +30,18 @@
           "memory",
           "privacy",
           "responsiveness",
           "screenshots",
           "seqstyle",
           "stackwalk",
           "tasktracer",
           "threads",
-          "trackopts"
+          "trackopts",
+          "jstracer"
         ]
       },
       {
         "id": "supports",
         "type": "string",
         "enum": [
           "windowLength"
         ]
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js
@@ -170,8 +170,68 @@ add_task(async function test_pageAction_
   info("Check <all_urls> is not allowed in hide_matches");
   let extension = getExtension({
     "show_matches": ["*://mochi.test/*"],
     "hide_matches": ["<all_urls>"],
   });
   let rejects = await extension.startup().then(() => false, () => true);
   is(rejects, true, "startup failed");
 });
+
+add_task(async function test_pageAction_restrictScheme_false() {
+  info("Check restricted origins are allowed in show_matches for privileged extensions");
+  let extension = ExtensionTestUtils.loadExtension({
+    isPrivileged: true,
+    manifest: {
+      permissions: ["mozillaAddons", "tabs"],
+      page_action: {
+        "show_matches": ["about:reader*"],
+        "hide_matches": ["*://*/*"],
+      },
+    },
+    background: function() {
+      browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
+        if (changeInfo.url && changeInfo.url.startsWith("about:reader")) {
+          browser.test.sendMessage("readerModeEntered");
+        }
+      });
+
+      browser.test.onMessage.addListener(async (msg) => {
+        if (msg !== "enterReaderMode") {
+          browser.test.fail(`Received unexpected test message: ${msg}`);
+          return;
+        }
+
+        browser.tabs.toggleReaderMode();
+      });
+    },
+  });
+
+  async function expectPageAction(extension, tab, isShown) {
+    await promiseAnimationFrame();
+    let widgetId = makeWidgetId(extension.id);
+    let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(widgetId);
+    let iconEl = document.getElementById(pageActionId);
+
+    if (isShown) {
+      ok(iconEl && !iconEl.hasAttribute("disabled"), "pageAction is shown");
+    } else {
+      ok(iconEl == null || iconEl.getAttribute("disabled") == "true", "pageAction is hidden");
+    }
+  }
+
+  const baseUrl = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+  const url = `${baseUrl}/readerModeArticle.html`;
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, true, true);
+
+  await extension.startup();
+
+  await expectPageAction(extension, tab, false);
+
+  extension.sendMessage("enterReaderMode");
+  await extension.awaitMessage("readerModeEntered");
+
+  await expectPageAction(extension, tab, true);
+
+  BrowserTestUtils.removeTab(tab);
+
+  await extension.unload();
+});
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -621,36 +621,46 @@ var PlacesUIUtils = {
     // loadTabs with aReplace set to false.
     browserWindow.gBrowser.loadTabs(urls, {
       inBackground: loadInBackground,
       replace: false,
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     });
   },
 
-  openContainerNodeInTabs:
-  function PUIU_openContainerInTabs(aNode, aEvent, aView) {
-    let window = aView.ownerWindow;
-
-    let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
-    if (OpenInTabsUtils.confirmOpenInTabs(urlsToOpen.length, window)) {
-      this._openTabset(urlsToOpen, aEvent, window);
-    }
-  },
+  /**
+   * Loads a selected node's or nodes' URLs in tabs,
+   * warning the user when lots of URLs are being opened
+   *
+   * @param {object|array} nodeOrNodes
+   *          Contains the node or nodes that we're opening in tabs
+   * @param {event} event
+   *          The DOM mouse/key event with modifier keys set that track the
+   *          user's preferred destination window or tab.
+   * @param {object} view
+   *          The current view that contains the node or nodes selected for
+   *          opening
+   */
+  openMultipleLinksInTabs(nodeOrNodes, event, view) {
+    let window = view.ownerWindow;
+    let urlsToOpen = [];
 
-  openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) {
-    let window = aView.ownerWindow;
-
-    let urlsToOpen = [];
-    for (var i = 0; i < aNodes.length; i++) {
-      // Skip over separators and folders.
-      if (PlacesUtils.nodeIsURI(aNodes[i]))
-        urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
+    if (PlacesUtils.nodeIsContainer(nodeOrNodes)) {
+      urlsToOpen = PlacesUtils.getURLsForContainerNode(nodeOrNodes);
+    } else {
+      for (var i = 0; i < nodeOrNodes.length; i++) {
+        // Skip over separators and folders.
+        if (PlacesUtils.nodeIsURI(nodeOrNodes[i])) {
+          urlsToOpen.push({uri: nodeOrNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(nodeOrNodes[i])});
+        }
+      }
     }
-    this._openTabset(urlsToOpen, aEvent, window);
+    if (OpenInTabsUtils.confirmOpenInTabs(urlsToOpen.length, window)) {
+      this._openTabset(urlsToOpen, event, window);
+    }
   },
 
   /**
    * Loads the node's URL in the appropriate tab or window given the
    * user's preference specified by modifier keys tracked by a
    * DOM mouse/key event.
    * @param   aNode
    *          An uri result node.
@@ -951,17 +961,17 @@ var PlacesUIUtils = {
                      (event.button == 1 || (event.button == 0 && modifKey)) &&
                      PlacesUtils.hasChildURIs(tree.view.nodeForTreeIndex(cell.row));
 
     if (event.button == 0 && isContainer && !openInTabs) {
       tbo.view.toggleOpenState(cell.row);
     } else if (!mouseInGutter && openInTabs &&
                event.originalTarget.localName == "treechildren") {
       tbo.view.selection.select(cell.row);
-      this.openContainerNodeInTabs(tree.selectedNode, event, tree);
+      this.openMultipleLinksInTabs(tree.selectedNode, event, tree);
     } else if (!mouseInGutter && !isContainer &&
                event.originalTarget.localName == "treechildren") {
       // Clear all other selection since we're loading a link now. We must
       // do this *before* attempting to load the link since openURL uses
       // selection as an indication of which link to load.
       tbo.view.selection.select(cell.row);
       this.openNodeWithEvent(tree.selectedNode, event);
     }
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -793,17 +793,17 @@ function PlacesToolbar(aPlace) {
   Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
                     .add(Date.now() - startTime);
 }
 
 PlacesToolbar.prototype = {
   __proto__: PlacesViewBase.prototype,
 
   _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop",
-              "mousemove", "mouseover", "mouseout"],
+              "mousemove", "mouseover", "mouseout", "mousedown"],
 
   QueryInterface: ChromeUtils.generateQI(["nsITimerCallback",
                                           ...PlacesViewBase.interfaces]),
 
   uninit: function PT_uninit() {
     this._removeEventListeners(this._viewElt, this._cbEvents, false);
     this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
                                true);
@@ -1012,16 +1012,19 @@ PlacesToolbar.prototype = {
         this._onMouseOver(aEvent);
         break;
       case "mousemove":
         this._onMouseMove(aEvent);
         break;
       case "mouseout":
         this._onMouseOut(aEvent);
         break;
+      case "mousedown":
+        this._onMouseDown(aEvent);
+        break;
       case "popupshowing":
         this._onPopupShowing(aEvent);
         break;
       case "popuphidden":
         this._onPopupHidden(aEvent);
         break;
       default:
         throw "Trying to handle unexpected event.";
@@ -1464,16 +1467,30 @@ PlacesToolbar.prototype = {
         PlacesUtils.nodeIsURI(button._placesNode))
       window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null);
   },
 
   _onMouseOut: function PT__onMouseOut(aEvent) {
     window.XULBrowserWindow.setOverLink("", null);
   },
 
+  _onMouseDown: function PT__onMouseDown(aEvent) {
+    let target = aEvent.target;
+    if (aEvent.button == 0 &&
+        target.localName == "toolbarbutton" &&
+        target.getAttribute("type") == "menu") {
+      let modifKey = aEvent.shiftKey || aEvent.getModifierState("Accel");
+      if (modifKey) {
+        // Do not open the popup since BEH_onClick is about to
+        // open all child uri nodes in tabs.
+        this._allowPopupShowing = false;
+      }
+    }
+  },
+
   _cleanupDragDetails: function PT__cleanupDragDetails() {
     // Called on dragend and drop.
     PlacesControllerDragHelper.currentDropTarget = null;
     this._draggedElt = null;
     if (this._ibTimer)
       this._ibTimer.cancel();
 
     this._dropIndicator.collapsed = true;
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -641,20 +641,17 @@ PlacesController.prototype = {
    */
   openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
     var node = this._view.selectedNode;
     var nodes = this._view.selectedNodes;
     // In the case of no selection, open the root node:
     if (!node && !nodes.length) {
       node = this._view.result.root;
     }
-    if (node && PlacesUtils.nodeIsContainer(node))
-      PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._view);
-    else
-      PlacesUIUtils.openURINodesInTabs(nodes, aEvent, this._view);
+    PlacesUIUtils.openMultipleLinksInTabs(node ? node : nodes, aEvent, this._view);
   },
 
   /**
    * Shows the Add Bookmark UI for the current insertion point.
    *
    * @param aType
    *        the type of the new item (bookmark/folder)
    */
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -348,17 +348,17 @@ var PlacesOrganizer = {
 
     let node = this._places.selectedNode;
     if (node) {
       let middleClick = aEvent.button == 1 && aEvent.detail == 1;
       if (middleClick && PlacesUtils.nodeIsContainer(node)) {
         // The command execution function will take care of seeing if the
         // selection is a folder or a different container type, and will
         // load its contents in tabs.
-        PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._places);
+        PlacesUIUtils.openMultipleLinksInTabs(node, aEvent, this._places);
       }
     }
   },
 
   /**
    * Handle focus changes on the places list and the current content view.
    */
   updateDetailsPane: function PO_updateDetailsPane() {
@@ -1290,17 +1290,17 @@ var ContentTree = {
       let middleClick = aEvent.button == 1 && aEvent.detail == 1;
       if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) {
         // Open associated uri in the browser.
         this.openSelectedNode(aEvent);
       } else if (middleClick && PlacesUtils.nodeIsContainer(node)) {
         // The command execution function will take care of seeing if the
         // selection is a folder or a different container type, and will
         // load its contents in tabs.
-        PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this.view);
+        PlacesUIUtils.openMultipleLinksInTabs(node, aEvent, this.view);
       }
     }
   },
 
   onKeyPress: function CT_onKeyPress(aEvent) {
     if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
       this.openSelectedNode(aEvent);
   },
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -68,16 +68,17 @@ skip-if = (verify && debug && (os == 'ma
 [browser_library_middleclick.js]
 [browser_library_new_bookmark.js]
 [browser_library_open_leak.js]
 [browser_library_openFlatContainer.js]
 [browser_library_open_bookmark.js]
 [browser_library_panel_leak.js]
 [browser_library_search.js]
 [browser_library_views_liveupdate.js]
+[browser_library_warnOnOpen.js]
 [browser_markPageAsFollowedLink.js]
 [browser_panelview_bookmarks_delete.js]
 [browser_paste_bookmarks.js]
 subsuite = clipboard
 [browser_paste_into_tags.js]
 [browser_paste_resets_cut_highlights.js]
 subsuite = clipboard
 [browser_remove_bookmarks.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_warnOnOpen.js
@@ -0,0 +1,140 @@
+/* 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/. */
+
+/*
+ * Bug 1435562 - Test that browser.tabs.warnOnOpen is respected when
+ * opening multiple items from the Library. */
+
+"use strict";
+
+var gLibrary = null;
+
+add_task(async function setup() {
+  // Temporarily disable history, so we won't record pages navigation.
+  await SpecialPowers.pushPrefEnv({set: [
+    ["places.history.enabled", false],
+  ]});
+
+  // Open Library window.
+  gLibrary = await promiseLibrary();
+
+  registerCleanupFunction(async () => {
+    // We must close "Other Bookmarks" ready for other tests.
+    gLibrary.PlacesOrganizer.selectLeftPaneBuiltIn("UnfiledBookmarks");
+    gLibrary.PlacesOrganizer._places.selectedNode.containerOpen = false;
+
+    await PlacesUtils.bookmarks.eraseEverything();
+
+    // Close Library window.
+    await promiseLibraryClosed(gLibrary);
+  });
+});
+
+add_task(async function test_warnOnOpenFolder() {
+  // Generate a list of links larger than browser.tabs.maxOpenBeforeWarn
+  const MAX_LINKS = 16;
+  let children = [];
+  for (let i = 0; i < MAX_LINKS; i++) {
+    children.push({
+      title: `Folder Target ${i}`,
+      url: `http://example${i}.com`,
+    });
+  }
+
+  // Create a new folder containing our links.
+  await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    children: [{
+      title: "bigFolder",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      children,
+    }],
+  });
+  info("Pushed test folder into the bookmarks tree");
+
+  // Select unsorted bookmarks root in the left pane.
+  gLibrary.PlacesOrganizer.selectLeftPaneBuiltIn("UnfiledBookmarks");
+  info("Got selection in the Library left pane");
+
+  // Get our bookmark in the right pane.
+  gLibrary.ContentTree.view.view.nodeForTreeIndex(0);
+  info("Got bigFolder in the right pane");
+
+  gLibrary.PlacesOrganizer._places.selectedNode.containerOpen = true;
+
+  // Middle-click on folder (opens all links in folder) and then cancel opening in the dialog
+  let promiseLoaded = BrowserTestUtils.promiseAlertDialog("cancel");
+  let bookmarkedNode = gLibrary.PlacesOrganizer._places.selectedNode.getChild(0);
+  mouseEventOnCell(gLibrary.PlacesOrganizer._places,
+    gLibrary.PlacesOrganizer._places.view.treeIndexForNode(bookmarkedNode),
+    0,
+    { button: 1 });
+
+  await promiseLoaded;
+
+  Assert.ok(true, "Expected dialog was shown when attempting to open folder with lots of links");
+
+  await PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(async function test_warnOnOpenLinks() {
+  // Generate a list of links larger than browser.tabs.maxOpenBeforeWarn
+  const MAX_LINKS = 16;
+  let children = [];
+  for (let i = 0; i < MAX_LINKS; i++) {
+    children.push({
+      title: `Highlighted Target ${i}`,
+      url: `http://example${i}.com`,
+    });
+  }
+
+  // Insert the links into the tree
+  await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    children,
+  });
+  info("Pushed test folder into the bookmarks tree");
+
+  gLibrary.PlacesOrganizer.selectLeftPaneBuiltIn("BookmarksToolbar");
+  info("Got selection in the Library left pane");
+
+  // Select all the links
+  gLibrary.ContentTree.view.selectAll();
+
+  let placesContext = gLibrary.document.getElementById("placesContext");
+  let promiseContextMenu = BrowserTestUtils.waitForEvent(placesContext, "popupshown");
+
+  // Open up the context menu and select "Open All In Tabs" (the first item in the list)
+  synthesizeClickOnSelectedTreeCell(gLibrary.ContentTree.view, {
+    button: 2,
+    type: "contextmenu",
+  });
+
+  await promiseContextMenu;
+  info("Context menu opened as expected");
+
+  let openTabs = gLibrary.document.getElementById("placesContext_openLinks:tabs");
+  let promiseLoaded = BrowserTestUtils.promiseAlertDialog("cancel");
+
+  EventUtils.synthesizeMouseAtCenter(openTabs, {}, gLibrary);
+
+  await promiseLoaded;
+
+  Assert.ok(true, "Expected dialog was shown when attempting to open lots of selected links");
+
+  await PlacesUtils.bookmarks.eraseEverything();
+});
+
+function mouseEventOnCell(aTree, aRowIndex, aColumnIndex, aEventDetails) {
+  var selection = aTree.view.selection;
+  selection.select(aRowIndex);
+  aTree.treeBoxObject.ensureRowIsVisible(aRowIndex);
+  var column = aTree.columns[aColumnIndex];
+
+  // get cell coordinates
+  var rect = aTree.treeBoxObject.getCoordsForCellItem(aRowIndex, column, "text");
+
+  EventUtils.synthesizeMouse(aTree.body, rect.x, rect.y,
+    aEventDetails, gLibrary);
+}
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -62,17 +62,17 @@ function promiseLibraryClosed(organizer)
 function promiseClipboard(aPopulateClipboardFn, aFlavor) {
   return new Promise((resolve, reject) => {
     waitForClipboard(data => !!data, aPopulateClipboardFn, resolve, reject, aFlavor);
   });
 }
 
 function synthesizeClickOnSelectedTreeCell(aTree, aOptions) {
   let tbo = aTree.treeBoxObject;
-  if (tbo.view.selection.count != 1)
+  if (tbo.view.selection.count < 1)
      throw new Error("The test node should be successfully selected");
   // Get selection rowID.
   let min = {}, max = {};
   tbo.view.selection.getRangeAt(0, min, max);
   let rowID = min.value;
   tbo.ensureRowIsVisible(rowID);
   // Calculate the click coordinates.
   var rect = tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text");
--- a/browser/components/preferences/in-content/containers.xul
+++ b/browser/components/preferences/in-content/containers.xul
@@ -12,17 +12,17 @@
       data-category="paneContainers">
   <label class="text-link" id="backContainersLink" data-l10n-id="containers-back-link"/>
 </hbox>
 
 <hbox id="header-containers"
       class="header"
       hidden="true"
       data-category="paneContainers">
-  <label class="header-name" flex="1" data-l10n-id="containers-header"/>
+  <html:h1 data-l10n-id="containers-header"/>
 </hbox>
 
 <!-- Containers -->
 <groupbox id="browserContainersGroupPane" data-category="paneContainers" hidden="true"
           data-hidden-from-search="true" data-subpanel="true">
   <vbox id="browserContainersbox">
     <richlistbox id="containersView"/>
   </vbox>
--- a/browser/components/preferences/in-content/home.xul
+++ b/browser/components/preferences/in-content/home.xul
@@ -6,17 +6,17 @@
 
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/home.js"/>
 
 <hbox id="firefoxHomeCategory"
       class="subcategory"
       hidden="true"
       data-category="paneHome">
-  <label class="header-name" data-l10n-id="pane-home-title" flex="1"/>
+  <html:h1 style="-moz-box-flex: 1;" data-l10n-id="pane-home-title"/>
   <button id="restoreDefaultHomePageBtn"
           class="homepage-button check-home-page-controlled"
           data-preference-related="browser.startup.homepage"
           data-l10n-id="home-restore-defaults"
           preference="pref.browser.homepage.disable_button.restore_default"/>
 </hbox>
 
 <groupbox id="homepageGroup"
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -15,30 +15,30 @@
         src="chrome://mozapps/content/preferences/fontbuilder.js"/>
 
 <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences.properties"/>
 
 <hbox id="generalCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-      <label class="header-name" flex="1" data-l10n-id="pane-general-title"/>
+  <html:h1 data-l10n-id="pane-general-title"/>
 </hbox>
 
 <!-- Startup -->
 <groupbox id="startupGroup"
           data-category="paneGeneral"
           hidden="true">
   <label><html:h2 data-l10n-id="startup-header"/></label>
 
 #ifdef MOZ_DEV_EDITION
   <vbox id="separateProfileBox">
     <checkbox id="separateProfileMode"
               data-l10n-id="separate-profile-mode"/>
-    <hbox id="sync-dev-edition-root" lign="center" class="indent" hidden="true">
+    <hbox id="sync-dev-edition-root" align="center" class="indent" hidden="true">
       <label id="useFirefoxSync" data-l10n-id="use-firefox-sync"/>
       <deck id="getStarted">
         <label class="text-link" data-l10n-id="get-started-not-logged-in"/>
         <label class="text-link" data-l10n-id="get-started-configured"/>
       </deck>
     </hbox>
   </vbox>
 #endif
@@ -134,17 +134,17 @@
       </hbox>
     </vbox>
 </groupbox>
 
 <hbox id="languageAndAppearanceCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="language-and-appearance-header"/>
+  <html:h1 data-l10n-id="language-and-appearance-header"/>
 </hbox>
 
 <!-- Fonts and Colors -->
 <groupbox id="fontsGroup" data-category="paneGeneral" hidden="true">
   <label><html:h2 data-l10n-id="fonts-and-colors-header"/></label>
 
   <hbox id="fontSettings">
     <hbox align="center" flex="1">
@@ -349,17 +349,17 @@
           preference="layout.spellcheckDefault"/>
 </groupbox>
 
 <!-- Files and Applications -->
 <hbox id="filesAndApplicationsCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="files-and-applications-title"/>
+  <html:h1 data-l10n-id="files-and-applications-title"/>
 </hbox>
 
 <!--Downloads-->
 <groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true">
   <label><html:h2 data-l10n-id="download-header"/></label>
 
   <radiogroup id="saveWhere"
               preference="browser.download.useDownloadDir"
@@ -431,17 +431,17 @@
   <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
   <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
 #endif
 
 <hbox id="updatesCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="update-application-title"/>
+  <html:h1 data-l10n-id="update-application-title"/>
 </hbox>
 
 <!-- Update -->
 <groupbox id="updateApp" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="update-application-title"/></label>
 
   <label data-l10n-id="update-application-description"/>
   <hbox align="center">
@@ -577,17 +577,17 @@
             data-l10n-id="update-enable-search-update"
             preference="browser.search.update"/>
 </groupbox>
 
 <hbox id="performanceCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="performance-title"/>
+  <html:h1 data-l10n-id="performance-title"/>
 </hbox>
 
 <!-- Performance -->
 <groupbox id="performanceGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="performance-title"/></label>
 
   <hbox align="center">
     <checkbox id="useRecommendedPerformanceSettings"
@@ -623,17 +623,17 @@
     </description>
   </vbox>
 </groupbox>
 
 <hbox id="browsingCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="browsing-title"/>
+  <html:h1 data-l10n-id="browsing-title"/>
 </hbox>
 
 <!-- Browsing -->
 <groupbox id="browsingGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="browsing-title"/></label>
 
   <checkbox id="useAutoScroll"
             data-l10n-id="browsing-use-autoscroll"
@@ -662,17 +662,17 @@
     <label id="cfrLearnMore" class="learnMore text-link" data-l10n-id="browsing-cfr-recommendations-learn-more"/>
   </hbox>
 </groupbox>
 
 <hbox id="networkProxyCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="network-settings-title"/>
+  <html:h1 data-l10n-id="network-settings-title"/>
 </hbox>
 
 <!-- Network Settings-->
 <groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
 
   <hbox align="center">
     <hbox align="center" flex="1">
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -207,16 +207,23 @@ var gPrivacyPane = {
       }
 
       // Notify observers that the TP UI has been updated.
       // This is needed since our tests need to be notified about the
       // trackingProtectionMenu element getting disabled/enabled at the right time.
       Services.obs.notifyObservers(window, "privacy-pane-tp-ui-updated");
     }
 
+    // We watch the network.cookie.cookieBehavior default value, if it is
+    // BEHAVIOR_ACCEPT (0) then show the fallback UI. When we change
+    // this default to BEHAVIOR_REJECT_TRACKER (4) show our default UI.
+    let defaults = Services.prefs.getDefaultBranch("");
+    document.getElementById("contentBlockingCategories").toggleAttribute("fallback-ui",
+      defaults.getIntPref("network.cookie.cookieBehavior") === Ci.nsICookieService.BEHAVIOR_ACCEPT);
+
     if (isLocked) {
       // An extension can't control this setting if either pref is locked.
       hideControllingExtension(TRACKING_PROTECTION_KEY);
       setInputsDisabledState(false);
     } else {
       handleControllingExtension(
         PREF_SETTING_TYPE,
         TRACKING_PROTECTION_KEY)
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -8,17 +8,17 @@
         src="chrome://browser/content/preferences/in-content/privacy.js"/>
 <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
 <stringbundle id="signonBundle" src="chrome://passwordmgr/locale/passwordmgr.properties"/>
 
 <hbox id="browserPrivacyCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="privacy-header"/>
+  <html:h1 data-l10n-id="privacy-header"/>
 </hbox>
 
 <!-- Tracking / Content Blocking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription">
   <label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-header"/></label>
   <vbox data-subcategory="trackingprotection">
     <hbox align="start">
       <image id="trackingProtectionShield"/>
@@ -52,33 +52,36 @@
                     preference="browser.contentblocking.category"
                     aria-labelledby="trackingProtectionMenuDesc">
           <vbox id="contentBlockingOptionStandard" class="content-blocking-category">
             <hbox>
               <radio id="standardRadio"
                      value="standard"
                      data-l10n-id="content-blocking-setting-standard"
                      flex="1"/>
-              <button id="standardArrow" class="arrowhead"/>
+              <button id="standardArrow" class="arrowhead default-content-blocking-ui"/>
             </hbox>
-            <vbox class="indent">
+            <vbox class="indent default-content-blocking-ui">
               <description data-l10n-id="content-blocking-standard-desc"></description>
               <vbox class="content-blocking-extra-information">
                 <vbox class="indent">
                   <hbox class="extra-information-label">
                     <image class="content-blocking-trackers-image"/>
                     <label data-l10n-id="content-blocking-private-trackers"/>
                   </hbox>
                   <hbox class="extra-information-label">
                     <image class="content-blocking-cookies-image"/>
                     <label data-l10n-id="content-blocking-third-party-cookies"/>
                   </hbox>
                 </vbox>
               </vbox>
             </vbox>
+            <vbox class="indent fallback-content-blocking-ui">
+              <description data-l10n-id="content-blocking-standard-description"/>
+            </vbox>
           </vbox>
           <vbox id="contentBlockingOptionStrict" class="content-blocking-category">
             <hbox>
               <radio id="strictRadio"
                      value="strict"
                      data-l10n-id="content-blocking-setting-strict"
                      flex="1"/>
               <button id="strictArrow" class="arrowhead"/>
@@ -432,17 +435,17 @@
   <label class="text-link" id="openSearchEnginePreferences"
          data-l10n-id="addressbar-suggestions-settings"/>
 </groupbox>
 
 <hbox id="permissionsCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="permissions-header"/>
+  <html:h1 data-l10n-id="permissions-header"/>
 </hbox>
 
 <!-- Permissions -->
 <groupbox id="permissionsGroup" data-category="panePrivacy" hidden="true" data-subcategory="permissions">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="permissions-header"/></label>
 
   <grid>
     <columns>
@@ -663,17 +666,17 @@
 </groupbox>
 
 <!-- Firefox Data Collection and Use -->
 #ifdef MOZ_DATA_REPORTING
 <hbox id="dataCollectionCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="collection-header"/>
+  <html:h1 data-l10n-id="collection-header"/>
 </hbox>
 
 <groupbox id="dataCollectionGroup" data-category="panePrivacy" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="collection-header"/></label>
 
   <description>
     <label class="tail-with-learn-more" data-l10n-id="collection-description"/>
     <label id="dataCollectionPrivacyNotice"
@@ -742,17 +745,17 @@
   </vbox>
 </groupbox>
 #endif
 
 <hbox id="securityCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="security-header"/>
+  <html:h1 data-l10n-id="security-header"/>
 </hbox>
 
 <!-- addons, forgery (phishing) UI Security -->
 <groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true">
   <label><html:h2 data-l10n-id="security-browsing-protection"/></label>
   <hbox align = "center">
     <checkbox id="enableSafeBrowsing"
               data-l10n-id="security-enable-safe-browsing"
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -1,16 +1,16 @@
     <script type="application/javascript"
             src="chrome://browser/content/preferences/in-content/search.js"/>
 
     <hbox id="searchCategory"
           class="subcategory"
           hidden="true"
           data-category="paneSearch">
-      <label class="header-name" flex="1" data-l10n-id="pane-search-title" />
+      <html:h1 data-l10n-id="pane-search-title"/>
     </hbox>
 
     <groupbox id="searchbarGroup" data-category="paneSearch">
       <label control="searchBarVisibleGroup"><html:h2 data-l10n-id="search-bar-header"/></label>
       <radiogroup id="searchBarVisibleGroup" preference="browser.search.widget.inNavBar">
         <radio id="searchBarHiddenRadio" value="false" data-l10n-id="search-bar-hidden"/>
         <image class="searchBarImage searchBarHiddenImage" role="presentation"/>
         <radio id="searchBarShownRadio" value="true" data-l10n-id="search-bar-shown"/>
--- a/browser/components/preferences/in-content/searchResults.xul
+++ b/browser/components/preferences/in-content/searchResults.xul
@@ -2,17 +2,17 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this file,
    - You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <hbox id="header-searchResults"
       class="subcategory"
       hidden="true"
       data-hidden-from-search="true"
       data-category="paneSearchResults">
-  <label class="header-name" flex="1" data-l10n-id="search-results-header" />
+  <html:h1 data-l10n-id="search-results-header"/>
 </hbox>
 
 <groupbox id="no-results-message"
           data-hidden-from-search="true"
           data-category="paneSearchResults"
           hidden="true">
   <vbox class="no-results-container">
     <label id="sorry-message" data-l10n-id="search-results-empty-message">
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -6,17 +6,17 @@
 
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/sync.js"/>
 
 <hbox id="firefoxAccountCategory"
       class="subcategory"
       hidden="true"
       data-category="paneSync">
-  <label class="header-name" flex="1" data-l10n-id="pane-sync-title" />
+  <html:h1 data-l10n-id="pane-sync-title"/>
 </hbox>
 
 <deck id="weavePrefsDeck" data-category="paneSync" hidden="true"
       data-hidden-from-search="true">
   <groupbox id="noFxaAccount">
     <hbox>
       <vbox flex="1">
         <label id="noFxaCaption"><html:h2 data-l10n-id="sync-signedout-caption"/></label>
--- a/browser/components/sessionstore/StartupPerformance.jsm
+++ b/browser/components/sessionstore/StartupPerformance.jsm
@@ -71,17 +71,19 @@ var StartupPerformance = {
   get isRestored() {
     return this._isRestored;
   },
 
   // Called when restoration starts.
   // Record the start timestamp, setup the timer and `this._promiseFinished`.
   // Behavior is unspecified if there was already an ongoing measure.
   _onRestorationStarts(isAutoRestore) {
-    Services.profiler.AddMarker("_onRestorationStarts");
+    if (Services.profiler) {
+      Services.profiler.AddMarker("_onRestorationStarts");
+    }
     this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
     this._totalNumberOfEagerTabs = 0;
     this._totalNumberOfTabs = 0;
     this._totalNumberOfWindows = 0;
 
     // While we may restore several sessions in a single run of the browser,
     // that's a very unusual case, and not really worth measuring, so let's
     // stop listening for further restorations.
@@ -194,17 +196,19 @@ var StartupPerformance = {
 
           let observer = (event) => {
             // We don't care about tab restorations that are due to
             // a browser flipping from out-of-main-process to in-main-process
             // or vice-versa. We only care about restorations that are due
             // to the user switching to a lazily restored tab, or for tabs
             // that are restoring eagerly.
             if (!event.detail.isRemotenessUpdate) {
-              Services.profiler.AddMarker("SSTabRestored");
+              if (Services.profiler) {
+                Services.profiler.AddMarker("SSTabRestored");
+              }
               this._latestRestoredTimeStamp = Date.now();
               this._totalNumberOfEagerTabs += 1;
             }
           };
           win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
           this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
 
           // Once we have finished collecting the results, clean up the observers.
--- a/browser/components/shell/nsMacShellService.cpp
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -179,19 +179,17 @@ NS_IMETHODIMP
 nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress,
                                   nsIRequest* aRequest, nsresult aStatus,
                                   const char16_t* aMessage) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
-                                    nsIRequest* aRequest, uint32_t aOldState,
-                                    uint32_t aState,
-                                    const nsAString& aContentBlockingLogJSON) {
+                                    nsIRequest* aRequest, uint32_t aState) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
                                  nsIRequest* aRequest, uint32_t aStateFlags,
                                  nsresult aStatus) {
   if (aStateFlags & STATE_STOP) {
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -167,17 +167,17 @@ class UrlbarInput {
    * @param {Event} event
    *   DOM event from the <textbox>.
    */
   handleEvent(event) {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
     } else {
-      throw "Unrecognized urlbar event: " + event.type;
+      throw new Error("Unrecognized UrlbarInput event: " + event.type);
     }
   }
 
   /**
    * Handles an event which would cause a url or text to be opened.
    * XXX the name is currently handleCommand which is compatible with
    * urlbarBindings. However, it is no longer called automatically by autocomplete,
    * See _on_keyup.
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -26,26 +26,18 @@ class UrlbarView {
     this.document = urlbar.panel.ownerDocument;
     this.window = this.document.defaultView;
 
     this._mainContainer = this.panel.querySelector(".urlbarView-body-inner");
     this._rows = this.panel.querySelector(".urlbarView-results");
 
     // For the horizontal fade-out effect, set the overflow attribute on result
     // rows when they overflow.
-    this._rows.addEventListener("overflow", event => {
-      if (event.target.classList.contains("urlbarView-row-inner")) {
-        event.target.toggleAttribute("overflow", true);
-      }
-    });
-    this._rows.addEventListener("underflow", event => {
-      if (event.target.classList.contains("urlbarView-row-inner")) {
-        event.target.toggleAttribute("overflow", false);
-      }
-    });
+    this._rows.addEventListener("overflow", this);
+    this._rows.addEventListener("underflow", this);
 
     this.controller.addQueryListener(this);
   }
 
   get oneOffSearchButtons() {
     return this._oneOffSearchButtons ||
       (this._oneOffSearchButtons =
          new this.window.SearchOneOffs(this.panel.querySelector(".search-one-offs")));
@@ -199,25 +191,37 @@ class UrlbarView {
    * @param {Event} event
    *   DOM event from the <view>.
    */
   handleEvent(event) {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
     } else {
-      throw "Unrecognized urlbar event: " + event.type;
+      throw new Error("Unrecognized UrlbarView event: " + event.type);
     }
   }
 
   _on_click(event) {
     let row = event.target;
     while (!row.classList.contains("urlbarView-row")) {
       row = row.parentNode;
     }
     let resultIndex = row.getAttribute("resultIndex");
     let result = this._queryContext.results[resultIndex];
     if (result) {
       this.urlbar.resultSelected(event, result);
     }
     this.close();
   }
+
+  _on_overflow(event) {
+    if (event.target.classList.contains("urlbarView-row-inner")) {
+      event.target.toggleAttribute("overflow", true);
+    }
+  }
+
+  _on_underflow(event) {
+    if (event.target.classList.contains("urlbarView-row-inner")) {
+      event.target.toggleAttribute("overflow", false);
+    }
+  }
 }
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
@@ -59,18 +59,19 @@ async function expectNoTooltip() {
 }
 
 add_task(async function() {
   window.windowUtils.disableNonTestMouseEvents(true);
   registerCleanupFunction(() => {
     window.windowUtils.disableNonTestMouseEvents(false);
   });
 
-  // Ensure the URL bar isn't focused.
+  // Ensure the URL bar is neither focused nor hovered before we start.
   gBrowser.selectedBrowser.focus();
+  await synthesizeMouseOut(gURLBar.inputField);
 
   gURLBar.value = "short string";
   await expectNoTooltip();
 
   let longURL = "http://longurl.com/" + "foobar/".repeat(30);
   gURLBar.value = longURL;
   is(gURLBar.inputField.value, longURL.replace(/^http:\/\//, ""), "Urlbar value has http:// stripped");
   await expectTooltip(longURL);
--- a/browser/config/mozconfigs/win32/mingwclang
+++ b/browser/config/mozconfigs/win32/mingwclang
@@ -43,27 +43,22 @@ ac_add_options --disable-webrtc # Bug 13
 ac_add_options --disable-geckodriver # Bug 1489320
 
 # Find our toolchain
 HOST_CC="$TOOLTOOL_DIR/clang/bin/clang"
 HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang++"
 CC="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang"
 CXX="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang++"
 CXXFLAGS="-fms-extensions"
-CPP="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang -E"
 AR=llvm-ar
 RANLIB=llvm-ranlib
 
 # For Stylo
 BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
 
-# Since we use windres from binutils without the rest of tools (like cpp), we need to
-# explicitly specify clang as a preprocessor.
-WINDRES="i686-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
-
 # Bug 1471698 - Work around binutils corrupting mingw clang binaries.
 LDFLAGS="-Wl,-S"
 STRIP=/bin/true
 OBJCOPY=/bin/true
 
 # We want to make sure we use binutils and other binaries in the tooltool
 # package.
 mk_add_options "export PATH=$TOOLTOOL_DIR/clang/bin:$TOOLTOOL_DIR/mingw32/bin:$TOOLTOOL_DIR/wine/bin:$TOOLTOOL_DIR/upx/bin:$TOOLTOOL_DIR/fxc2/bin:$PATH"
--- a/browser/config/mozconfigs/win64/mingwclang
+++ b/browser/config/mozconfigs/win64/mingwclang
@@ -43,27 +43,22 @@ ac_add_options --disable-webrtc # Bug 13
 ac_add_options --disable-geckodriver # Bug 1489320
 
 # Find our toolchain
 HOST_CC="$TOOLTOOL_DIR/clang/bin/clang"
 HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang++"
 CC="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang"
 CXX="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang++"
 CXXFLAGS="-fms-extensions"
-CPP="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang -E"
 AR=llvm-ar
 RANLIB=llvm-ranlib
 
 # For Stylo
 BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include"
 
-# Since we use windres from binutils without the rest of tools (like cpp), we need to
-# explicitly specify clang as a preprocessor.
-WINDRES="x86_64-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
-
 # Bug 1471698 - Work around binutils corrupting mingw clang binaries.
 LDFLAGS="-Wl,-S"
 STRIP=/bin/true
 OBJCOPY=/bin/true
 
 # We want to make sure we use binutils and other binaries in the tooltool
 # package.
 mk_add_options "export PATH=$TOOLTOOL_DIR/clang/bin:$TOOLTOOL_DIR/mingw32/bin:$TOOLTOOL_DIR/wine/bin:$TOOLTOOL_DIR/upx/bin:$TOOLTOOL_DIR/fxc2/bin:$PATH"
--- a/browser/extensions/screenshots/manifest.json
+++ b/browser/extensions/screenshots/manifest.json
@@ -31,17 +31,17 @@
     }
   ],
   "page_action": {
     "browser_style": true,
     "default_icon" : {
       "32": "icons/icon-v2.svg"
     },
     "default_title": "__MSG_contextMenuLabel__",
-    "show_matches": ["<all_urls>"],
+    "show_matches": ["<all_urls>", "about:reader*"],
     "pinned": false
   },
   "icons": {
     "32": "icons/icon-v2.svg"
   },
   "web_accessible_resources": [
     "blank.html",
     "icons/cancel.svg",
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -229,16 +229,23 @@ certificate.">
 
 <!ENTITY inadequateSecurityError.title "Your connection is not secure">
 <!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
      "NS_ERROR_NET_INADEQUATE_SECURITY". -->
 <!ENTITY inadequateSecurityError.longDesc "<p><span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.</p><p>Error code: NS_ERROR_NET_INADEQUATE_SECURITY</p>">
 
 <!ENTITY blockedByPolicy.title "Blocked Page">
 
+<!ENTITY certerror.mitm.title "Software is Preventing &brandShortName; From Safely Connecting to This Site">
+<!ENTITY certerror.mitm.longDesc "<span class='hostname'></span> is most likely a safe site, but a secure connection could not be established. This issue is caused by <span class='mitm-name'/>, which is either software on your computer or your network.">
+<!ENTITY certerror.mitm.whatCanYouDoAboutIt1 "If your antivirus software includes a feature that scans encrypted connections (often called “web scanning” or “https scanning”), you can disable that feature. If that doesn’t work, you can remove and reinstall the antivirus software.">
+<!ENTITY certerror.mitm.whatCanYouDoAboutIt2 "If you are on a corporate network, you can contact your IT department.">
+<!ENTITY certerror.mitm.whatCanYouDoAboutIt3 "If you are not familiar with <span class='mitm-name'/>, then this could be an attack and you should not continue to the site.">
+<!ENTITY certerror.mitm.sts.whatCanYouDoAboutIt3 "If you are not familiar with <span class='mitm-name'/>, then this could be an attack, and there is nothing you can do to access the site.">
+
 <!ENTITY clockSkewError.title "Your Computer Clock is Wrong">
 <!ENTITY clockSkewError.longDesc "Your computer thinks it is <span id='wrongSystemTime_systemDate1'/>, which prevents &brandShortName; from connecting securely. To visit <span class='hostname'></span>, update your computer clock in your system settings to the current date, time, and time zone, and then refresh <span class='hostname'></span>.">
 
 <!ENTITY prefReset.longDesc "It looks like your network security settings might be causing this. Do you want the default settings to be restored?">
 <!ENTITY prefReset.label "Restore default settings">
 
 <!ENTITY networkProtocolError.title "Network Protocol Error">
 <!ENTITY networkProtocolError.longDesc "<p>The page you are trying to view cannot be shown because an error in the network protocol was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
--- a/browser/modules/FaviconLoader.jsm
+++ b/browser/modules/FaviconLoader.jsm
@@ -418,16 +418,22 @@ class IconLoader {
   }
 
   async load(iconInfo) {
     if (this._loader) {
       this._loader.cancel();
     }
 
     if (LOCAL_FAVICON_SCHEMES.includes(iconInfo.iconUri.scheme)) {
+      // We need to do a manual security check because the channel won't do
+      // it for us.
+      try {
+        Services.scriptSecurityManager.checkLoadURIWithPrincipal(
+          iconInfo.node.nodePrincipal, iconInfo.iconUri, Services.scriptSecurityManager.ALLOW_CHROME);
+      } catch (ex) { return; }
       this.mm.sendAsyncMessage("Link:SetIcon", {
         pageURL: iconInfo.pageUri.spec,
         originalURL: iconInfo.iconUri.spec,
         canUseForTab: !iconInfo.isRichIcon,
         expiration: undefined,
         iconURL: iconInfo.iconUri.spec,
       });
       return;
--- a/browser/modules/test/browser/head.js
+++ b/browser/modules/test/browser/head.js
@@ -282,12 +282,12 @@ function getPopupNotificationNode() {
 
 
 /**
  * Disable non-release page actions (that are tested elsewhere).
  *
  * @return void
  */
 async function disableNonReleaseActions() {
-  if (AppConstants.MOZ_DEV_EDITION || AppConstants.NIGHTLY_BUILD) {
+  if (!["release", "esr"].includes(AppConstants.MOZ_UPDATE_CHANNEL)) {
     SpecialPowers.Services.prefs.setBoolPref("extensions.webcompat-reporter.enabled", false);
   }
 }
--- a/browser/themes/shared/aboutNetError-new.css
+++ b/browser/themes/shared/aboutNetError-new.css
@@ -140,17 +140,18 @@ body:not(:-moz-any(.clockSkewError,.badS
   flex-direction: row;
   flex-wrap: wrap;
   justify-content: space-between;
   align-content: space-between;
   align-items: flex-start;
   margin-top: 1em;
 }
 
-span#hostname {
+.mitm-name,
+#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
 }
 
 #errorCode:not([href]) {
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -23,26 +23,29 @@
   width: 100%;
   padding: 0;
 }
 
 groupbox[data-category] {
   margin: 0 0 32px;
 }
 
+html|h1 {
+  margin: 0 0 8px;
+  font-size: 1.46em;
+  font-weight: 300;
+  line-height: 1.3em;
+}
+
 html|h2 {
   margin: 16px 0 4px;
   font-size: 1.14em;
   line-height: normal;
 }
 
-.header-name {
-  margin-bottom: 8px;
-}
-
 description.indent,
 .indent > description {
   color: #737373;
 }
 
 button,
 treecol,
 html|option {
--- a/browser/themes/shared/incontentprefs/privacy.css
+++ b/browser/themes/shared/incontentprefs/privacy.css
@@ -108,16 +108,21 @@
   opacity: 1;
 }
 
 .content-blocking-warning > .indent,
 .content-blocking-category > .indent {
   margin-inline-end: 28px;
 }
 
+#contentBlockingCategories[fallback-ui] .default-content-blocking-ui,
+#contentBlockingCategories:not([fallback-ui]) .fallback-content-blocking-ui {
+  display: none;
+}
+
 .arrowhead {
   -moz-appearance: none;
   border: none;
   border-radius: 2px;
   min-height: 20px;
   min-width: 20px;
   max-height: 20px;
   max-width: 20px;
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -144,17 +144,16 @@
   }
   100% {
     opacity: 0;
     transform: scale(40);
   }
 }
 
 .tab-throbber,
-.tab-throbber-tabslist,
 .tab-icon-pending,
 .tab-icon-image,
 .tab-sharing-icon-overlay,
 .tab-icon-sound,
 .tab-close-button {
   margin-top: 1px;
 }
 
@@ -163,17 +162,16 @@
 .tab-icon-pending,
 .tab-icon-image,
 .tab-sharing-icon-overlay {
   height: 16px;
   width: 16px;
 }
 
 .tab-throbber:not([pinned]),
-.tab-throbber-tabslist:not([pinned]),
 .tab-sharing-icon-overlay:not([pinned]),
 .tab-icon-pending:not([pinned]),
 .tab-icon-image:not([pinned]) {
   margin-inline-end: 6px;
 }
 
 :root[sessionrestored] .tab-throbber[busy] {
   position: relative;
@@ -329,30 +327,30 @@
 .tab-throbber-tabslist[busy] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-connecting.png");
 }
 
 .tab-throbber-tabslist[progress] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading.png");
 }
 
-#TabsToolbar[brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
+:root[lwt-popup-brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading-inverted.png");
 }
 
 @media (min-resolution: 1.1dppx) {
   .tab-throbber-tabslist[busy] {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-connecting@2x.png");
   }
 
   .tab-throbber-tabslist[progress] {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading@2x.png");
   }
 
-  #TabsToolbar[brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
+  :root[lwt-popup-brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading-inverted@2x.png");
   }
 }
 
 .tab-label {
   margin-inline-end: 0;
   margin-inline-start: 0;
   /* Maintain consistent alignment in case of font fallback for non-Latin characters. */
@@ -752,21 +750,16 @@
 }
 
 .all-tabs-item:hover > .all-tabs-button,
 .all-tabs-item:hover > .all-tabs-secondary-button {
   /* Since the background is set on the item, don't set it on the children. */
   background-color: transparent !important;
 }
 
-.tab-throbber-tabslist {
-  display: block;
-  margin-inline-end: 0;
-}
-
 .all-tabs-item[selected] {
   font-weight: bold;
   box-shadow: inset 4px 0 var(--blue-40);
 }
 
 .all-tabs-item[selected]:-moz-locale-dir(rtl) {
   box-shadow: inset -4px 0 var(--blue-40);
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -96,16 +96,29 @@
 @media (-moz-windows-default-theme) {
   @media not all and (-moz-os-version: windows-win7) {
     #toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive {
       color: ThreeDShadow;
     }
   }
 }
 
+@media not (-moz-os-version: windows-win7) {
+  @media not (-moz-os-version: windows-win8) {
+    /* On Windows 10, when temporarily showing the menu bar, make it at least as
+     * tall as the tab bar such that the window controls don't appear to move up. */
+    :root[tabsintitlebar] #toolbar-menubar[autohide="true"] {
+      height: calc(var(--tab-min-height) - @navbarTabsShadowSize@);
+    }
+    :root[tabsintitlebar][sizemode="normal"] #toolbar-menubar[autohide="true"] {
+      height: calc(var(--tab-min-height) + var(--space-above-tabbar) - @navbarTabsShadowSize@);
+    }
+  }
+}
+
 :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .toolbar-items,
 :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .toolbar-items {
   padding-top: var(--space-above-tabbar);
 }
 
 /* Add 4px extra margin on top of the tabs toolbar on Windows 7. */
 @media (-moz-os-version: windows-win7) {
   :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .toolbar-items,
@@ -951,19 +964,18 @@ notification[value="translation"] {
  * paint, so this hack is how we sidestep that performance bottleneck.
  */
 #main-window:-moz-any([customize-entering],[customize-exiting]) label {
   transform: perspective(0.01px);
 }
 
 /* End customization mode */
 
-/* Prevent titlebar items (window caption buttons, private browsing indicator,
- * accessibility indicator, etc) from overlapping the nav bar's shadow on the
- * tab bar. */
+/* Prevent window controls from overlapping the nav bar's shadow on the tab
+ * bar. */
 #TabsToolbar > .titlebar-buttonbox-container {
   margin-bottom: @navbarTabsShadowSize@;
 }
 
 :root:not([privatebrowsingmode=temporary]) .accessibility-indicator,
 .private-browsing-indicator {
   margin-inline-end: 12px;
 }
--- a/build/build-clang/clang-trunk-mingw.json
+++ b/build/build-clang/clang-trunk-mingw.json
@@ -1,10 +1,10 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "348363",
     "stages": "3",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
--- a/build/debian-packages/valgrind-wheezy.diff
+++ b/build/debian-packages/valgrind-wheezy.diff
@@ -1,12 +1,19 @@
 diff -Nru valgrind-3.14.0/debian/changelog valgrind-3.14.0/debian/changelog
 --- valgrind-3.14.0/debian/changelog	2018-11-15 09:21:43.000000000 +0900
-+++ valgrind-3.14.0/debian/changelog	2018-11-15 11:45:25.000000000 +0900
-@@ -1,3 +1,14 @@
++++ valgrind-3.14.0/debian/changelog	2018-11-30 15:20:10.000000000 +0900
+@@ -1,3 +1,21 @@
++valgrind (1:3.14.0-1.deb7moz2) wheezy; urgency=medium
++
++  * debian/patches/vg401112: Cherry-pick upstream revision
++    f2c03ce3babe51eecbf03735f726c4028a162857.
++
++ -- Mike Hommey <glandium@debian.org>  Fri, 30 Nov 2018 15:20:10 +0900
++
 +valgrind (1:3.14.0-1.deb7moz1) wheezy; urgency=medium
 +
 +  * Mozilla backport for wheezy.
 +  * debian/control, debian/compat: Drop debhelper compat back to 7, which
 +    requires adding back an explicit dependency on dh-autoreconf.
 +  * debian/rules: Debhelper only defaulted to --parallel in compat >= 10, so
 +    add --parallel back.
 +  * debian/valgrind-mpi.install: Use non-multiarch path.
@@ -30,16 +37,175 @@ diff -Nru valgrind-3.14.0/debian/control
  Priority: optional
  Maintainer: Alessandro Ghedini <ghedo@debian.org>
 -Build-Depends: debhelper (>= 11),
 +Build-Depends: debhelper (>= 7.0.50~),
 + dh-autoreconf,
   gdb,
   gcc-multilib [amd64],
   libc6-dev-i386 [amd64],
+diff -Nru valgrind-3.14.0/debian/patches/series valgrind-3.14.0/debian/patches/series
+--- valgrind-3.14.0/debian/patches/series	2018-11-15 09:21:43.000000000 +0900
++++ valgrind-3.14.0/debian/patches/series	2018-11-30 15:19:14.000000000 +0900
+@@ -5,3 +5,4 @@
+ 08_fix-spelling-in-manpage.patch
+ 09_fix-armhf-detect.patch
+ 10_mpi-pkg-config.patch
++vg401112
+diff -Nru valgrind-3.14.0/debian/patches/vg401112 valgrind-3.14.0/debian/patches/vg401112
+--- valgrind-3.14.0/debian/patches/vg401112	1970-01-01 09:00:00.000000000 +0900
++++ valgrind-3.14.0/debian/patches/vg401112	2018-11-30 15:19:46.000000000 +0900
+@@ -0,0 +1,147 @@
++From f2c03ce3babe51eecbf03735f726c4028a162857 Mon Sep 17 00:00:00 2001
++From: Julian Seward <jseward@acm.org>
++Date: Wed, 28 Nov 2018 14:15:06 +0100
++Subject: [PATCH] Bug 401112 - LLVM 5.0 generates comparison against partially
++ initialized data.
++
++This generalises the existing spec rules for W of 32 bits:
++
++             W  <u   0---(N-1)---0 1 0---0  or
++
++(that is, B/NB after SUBL, where dep2 has the above form), to also cover
++
++             W  <=u  0---(N-1)---0 0 1---1
++
++(that is, BE/NBE after SUBL, where dept2 has the specified form).
++
++Patch from Nicolas B. Pierron (nicolas.b.pierron@nbp.name).
++---
++ VEX/priv/guest_amd64_helpers.c | 90 ++++++++++++++++++++++++++++++------------
++ 2 files changed, 66 insertions(+), 25 deletions(-)
++
++diff --git a/VEX/priv/guest_amd64_helpers.c b/VEX/priv/guest_amd64_helpers.c
++index a2b0789..30e82db 100644
++--- a/VEX/priv/guest_amd64_helpers.c
+++++ b/VEX/priv/guest_amd64_helpers.c
++@@ -1013,13 +1013,10 @@ static inline Bool isU64 ( IRExpr* e, ULong n )
++           && e->Iex.Const.con->Ico.U64 == n;
++ }
++ 
++-/* Returns N if E is an immediate of the form 1 << N for N in 1 to 31,
+++/* Returns N if W64 is a value of the form 1 << N for N in 1 to 31,
++    and zero in any other case. */
++-static Int isU64_1_shl_N ( IRExpr* e )
+++static Int isU64_1_shl_N_literal ( ULong w64 )
++ {
++-   if (e->tag != Iex_Const || e->Iex.Const.con->tag != Ico_U64)
++-      return 0;
++-   ULong w64 = e->Iex.Const.con->Ico.U64;
++    if (w64 < (1ULL << 1) || w64 > (1ULL << 31))
++       return 0;
++    if ((w64 & (w64 - 1)) != 0)
++@@ -1036,6 +1033,30 @@ static Int isU64_1_shl_N ( IRExpr* e )
++    return 0;
++ }
++ 
+++/* Returns N if E is an immediate of the form 1 << N for N in 1 to 31,
+++   and zero in any other case. */
+++static Int isU64_1_shl_N ( IRExpr* e )
+++{
+++   if (e->tag != Iex_Const || e->Iex.Const.con->tag != Ico_U64)
+++      return 0;
+++   ULong w64 = e->Iex.Const.con->Ico.U64;
+++   return isU64_1_shl_N_literal(w64);
+++}
+++
+++/* Returns N if E is an immediate of the form (1 << N) - 1 for N in 1 to 31,
+++   and zero in any other case. */
+++static Int isU64_1_shl_N_minus_1 ( IRExpr* e )
+++{
+++  if (e->tag != Iex_Const || e->Iex.Const.con->tag != Ico_U64)
+++    return 0;
+++  ULong w64 = e->Iex.Const.con->Ico.U64;
+++  // This isn't actually necessary since isU64_1_shl_N_literal will return
+++  // zero given a zero argument, but still ..
+++  if (w64 == 0xFFFFFFFFFFFFFFFFULL)
+++     return 0;
+++  return isU64_1_shl_N_literal(w64 + 1);
+++}
+++
++ IRExpr* guest_amd64_spechelper ( const HChar* function_name,
++                                  IRExpr** args,
++                                  IRStmt** precedingStmts,
++@@ -1258,32 +1279,51 @@ IRExpr* guest_amd64_spechelper ( const HChar* function_name,
++         /* It appears that LLVM 5.0 and later have a new way to find out
++            whether the top N bits of a word W are all zero, by computing
++ 
++-             W  <u  0---(N-1)---0 1 0---0
+++             W  <u   0---(N-1)---0 1 0---0  or
+++             W  <=u  0---(N-1)---0 0 1---1
++ 
++            In particular, the result will be defined if the top N bits of W
++            are defined, even if the trailing bits -- those corresponding to
++-           the 0---0 section -- are undefined.  Rather than make Memcheck
++-           more complex, we detect this case where we can and shift out the
++-           irrelevant and potentially undefined bits. */
+++           the rightmost 0---0 / 1---1 section -- are undefined.  Rather than
+++           make Memcheck more complex, we detect this case where we can and
+++           shift out the irrelevant and potentially undefined bits. */
++         Int n = 0;
++-        if (isU64(cc_op, AMD64G_CC_OP_SUBL)
++-            && (isU64(cond, AMD64CondB) || isU64(cond, AMD64CondNB))
++-            && (n = isU64_1_shl_N(cc_dep2)) > 0) {
++-           /* long sub/cmp, then B (unsigned less than),
++-              where dep2 is a power of 2:
++-                -> CmpLT32(dep1, 1 << N)
++-                -> CmpEQ32(dep1 >>u N, 0)
++-              and
++-              long sub/cmp, then NB (unsigned greater than or equal),
++-              where dep2 is a power of 2:
++-                -> CmpGE32(dep1, 1 << N)
++-                -> CmpNE32(dep1 >>u N, 0)
++-              This avoids CmpLT32U/CmpGE32U being applied to potentially
++-              uninitialised bits in the area being shifted out. */
+++        Bool is_NB_or_NBE = False;
+++        if (isU64(cc_op, AMD64G_CC_OP_SUBL)) {
+++           if (isU64(cond, AMD64CondB) || isU64(cond, AMD64CondNB)) {
+++              /* long sub/cmp, then B (unsigned less than),
+++                 where dep2 is a power of 2:
+++                   -> CmpLT32U(dep1, 1 << N)
+++                   -> CmpEQ32(dep1 >>u N, 0)
+++                 and
+++                 long sub/cmp, then NB (unsigned greater than or equal),
+++                 where dep2 is a power of 2:
+++                   -> CmpGE32U(dep1, 1 << N)
+++                   -> CmpNE32(dep1 >>u N, 0)
+++                 This avoids CmpLT32U/CmpGE32U being applied to potentially
+++                 uninitialised bits in the area being shifted out. */
+++              n = isU64_1_shl_N(cc_dep2);
+++              is_NB_or_NBE = isU64(cond, AMD64CondNB);
+++           } else if (isU64(cond, AMD64CondBE) || isU64(cond, AMD64CondNBE)) {
+++              /* long sub/cmp, then BE (unsigned less than or equal),
+++                 where dep2 is a power of 2 minus 1:
+++                  -> CmpLE32U(dep1, (1 << N) - 1)
+++                  -> CmpEQ32(dep1 >>u N, 0)
+++                 and
+++                 long sub/cmp, then NBE (unsigned greater than),
+++                 where dep2 is a power of 2 minus 1:
+++                   -> CmpGT32U(dep1, (1 << N) - 1)
+++                   -> CmpNE32(dep1 >>u N, 0)
+++                 This avoids CmpLE32U/CmpGT32U being applied to potentially
+++                 uninitialised bits in the area being shifted out. */
+++              n = isU64_1_shl_N_minus_1(cc_dep2);
+++              is_NB_or_NBE = isU64(cond, AMD64CondNBE);
+++           }
+++        }
+++        if (n > 0) {
++            vassert(n >= 1 && n <= 31);
++-           Bool isNB = isU64(cond, AMD64CondNB);
++            return unop(Iop_1Uto64,
++-                       binop(isNB ? Iop_CmpNE32 : Iop_CmpEQ32,
+++                       binop(is_NB_or_NBE ? Iop_CmpNE32 : Iop_CmpEQ32,
++                              binop(Iop_Shr32, unop(Iop_64to32, cc_dep1),
++                                               mkU8(n)),
++                              mkU32(0)));
++-- 
++2.9.3
++
 diff -Nru valgrind-3.14.0/debian/rules valgrind-3.14.0/debian/rules
 --- valgrind-3.14.0/debian/rules	2018-11-15 09:21:43.000000000 +0900
 +++ valgrind-3.14.0/debian/rules	2018-11-15 11:45:25.000000000 +0900
 @@ -11,7 +11,7 @@
  LDFLAGS  = $(shell dpkg-buildflags --get LDFLAGS)
  
  %:
 -	dh $@ --with=autoreconf
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -20,17 +20,17 @@ const RuntimeInfo = createFactory(requir
 const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
 const TabDetail = createFactory(require("./debugtarget/TabDetail"));
 const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction"));
 const TemporaryExtensionInstaller =
   createFactory(require("./debugtarget/TemporaryExtensionInstaller"));
 const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail"));
 
 const Actions = require("../actions/index");
-const { DEBUG_TARGET_PANE, PAGE_TYPES } = require("../constants");
+const { DEBUG_TARGET_PANE, PAGE_TYPES, RUNTIMES } = require("../constants");
 
 const {
   getCurrentConnectionPromptSetting,
   getCurrentRuntimeInfo,
 } = require("../modules/runtimes-state-helper");
 const { isSupportedDebugTargetPane } = require("../modules/debug-target-support");
 
 class RuntimePage extends PureComponent {
@@ -93,35 +93,41 @@ class RuntimePage extends PureComponent 
     );
   }
 
   render() {
     const {
       dispatch,
       installedExtensions,
       otherWorkers,
+      runtimeId,
       runtimeInfo,
       serviceWorkers,
       sharedWorkers,
       tabs,
       temporaryExtensions,
     } = this.props;
 
     if (!runtimeInfo) {
       // runtimeInfo can be null when the selectPage action navigates from a runtime A
       // to a runtime B (between unwatchRuntime and watchRuntime).
       return null;
     }
 
+    // do not show the connection prompt setting in 'This Firefox'
+    const shallShowPromptSetting = runtimeId !== RUNTIMES.THIS_FIREFOX;
+
     return dom.article(
       {
         className: "page js-runtime-page",
       },
       RuntimeInfo(runtimeInfo),
-      this.renderConnectionPromptSetting(),
+      shallShowPromptSetting
+        ? this.renderConnectionPromptSetting()
+        : null,
       isSupportedDebugTargetPane(runtimeInfo.type, DEBUG_TARGET_PANE.TEMPORARY_EXTENSION)
         ? TemporaryExtensionInstaller({ dispatch })
         : null,
       this.renderDebugTargetPane("Temporary Extensions",
                                  temporaryExtensions,
                                  TemporaryExtensionAction,
                                  ExtensionDetail,
                                  DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -26,16 +26,17 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js]
 [browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
 [browser_aboutdebugging_debug-target-pane_empty.js]
 [browser_aboutdebugging_debug-target-pane_usb_runtime.js]
 [browser_aboutdebugging_devtools.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_persist_connection.js]
 [browser_aboutdebugging_routes.js]
+[browser_aboutdebugging_runtime_connection-prompt.js]
 [browser_aboutdebugging_select_network_runtime.js]
 [browser_aboutdebugging_sidebar_network_runtimes.js]
 [browser_aboutdebugging_sidebar_usb_runtime.js]
 [browser_aboutdebugging_sidebar_usb_runtime_connect.js]
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_system_addons.js]
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_connection_prompt_setting.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_connection_prompt_setting.js
@@ -1,40 +1,57 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(
+  CHROME_URL_ROOT + "head-mocks.js", this);
+
 /**
  * Check whether can toggle enable/disable connection prompt setting.
  */
 add_task(async function() {
+  // enable USB devices mocks
+  const mocks = new Mocks();
+  const runtime = mocks.createUSBRuntime("1337id", {
+    deviceName: "Fancy Phone",
+    name: "Lorem ipsum",
+  });
+
   info("Set initial state for test");
   await pushPref("devtools.debugger.prompt-connection", true);
 
+  // open a remote runtime page
   const { document, tab } = await openAboutDebugging();
 
+  mocks.emitUSBUpdate();
+  await connectToRuntime("Fancy Phone", document);
+  await selectRuntime("Fancy Phone", "Lorem ipsum", document);
+
   info("Check whether connection prompt toggle button exists");
-  const connectionPromptToggleButton =
+  let connectionPromptToggleButton =
     document.querySelector(".js-connection-prompt-toggle-button");
   ok(connectionPromptToggleButton, "Toggle button existed");
-  await waitUntil(() => connectionPromptToggleButton.textContent.includes("Disable"));
+  ok(connectionPromptToggleButton.textContent.includes("Disable"),
+    "Toggle button shows 'Disable'");
 
   info("Click on the toggle button");
+  connectionPromptToggleButton =
+    document.querySelector(".js-connection-prompt-toggle-button");
   connectionPromptToggleButton.click();
   info("Wait until the toggle button text is updated");
   await waitUntil(() => connectionPromptToggleButton.textContent.includes("Enable"));
   info("Check the preference");
-  is(Services.prefs.getBoolPref("devtools.debugger.prompt-connection"),
-     false,
-     "The preference should be updated");
+  const disabledPref = runtime.getPreference("devtools.debugger.prompt-connection");
+  is(disabledPref, false, "The preference should be updated");
 
   info("Click on the toggle button again");
   connectionPromptToggleButton.click();
   info("Wait until the toggle button text is updated");
   await waitUntil(() => connectionPromptToggleButton.textContent.includes("Disable"));
   info("Check the preference");
-  is(Services.prefs.getBoolPref("devtools.debugger.prompt-connection"),
-     true,
-     "The preference should be updated");
+  const enabledPref = runtime.getPreference("devtools.debugger.prompt-connection");
+  is(enabledPref, true, "The preference should be updated");
 
   await removeTab(tab);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_runtime_connection-prompt.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(
+  CHROME_URL_ROOT + "head-mocks.js", this);
+
+/**
+ * Test that remote runtimes show the connection prompt,
+ * but it's hidden in 'This Firefox'
+ */
+add_task(async function() {
+  // enable USB devices mocks
+  const mocks = new Mocks();
+  mocks.createUSBRuntime("1337id", {
+    deviceName: "Fancy Phone",
+    name: "Lorem ipsum",
+  });
+
+  const { document, tab } = await openAboutDebugging();
+
+  info("Checking This Firefox");
+  ok(!document.querySelector(".js-connection-prompt-toggle-button"),
+    "This Firefox does not contain the connection prompt button");
+
+  info("Checking a USB runtime");
+  mocks.emitUSBUpdate();
+  await connectToRuntime("Fancy Phone", document);
+  await selectRuntime("Fancy Phone", "Lorem ipsum", document);
+  ok(!!document.querySelector(".js-connection-prompt-toggle-button"),
+    "Runtime contains the connection prompt button");
+
+  await removeTab(tab);
+});
--- a/devtools/client/aboutdebugging-new/test/browser/mocks/head-client-wrapper-mock.js
+++ b/devtools/client/aboutdebugging-new/test/browser/mocks/head-client-wrapper-mock.js
@@ -21,16 +21,17 @@ function createClientMock() {
   const EventEmitter = require("devtools/shared/event-emitter");
   const eventEmitter = {};
   EventEmitter.decorate(eventEmitter);
 
   return {
     // add a reference to the internal event emitter so that consumers can fire client
     // events.
     _eventEmitter: eventEmitter,
+    _preferences: {},
     addOneTimeListener: (evt, listener) => {
       eventEmitter.once(evt, listener);
     },
     addListener: (evt, listener) => {
       eventEmitter.on(evt, listener);
     },
     removeListener: (evt, listener) => {
       eventEmitter.off(evt, listener);
@@ -50,34 +51,39 @@ function createClientMock() {
 
     // no-op
     close: () => {},
     // no-op
     connect: () => {},
     // no-op
     getDeviceDescription: () => {},
     // Return default preference value or null if no match.
-    getPreference: (prefName) => {
+    getPreference: function(prefName) {
+      if (prefName in this._preferences) {
+        return this._preferences[prefName];
+      }
       if (prefName in DEFAULT_PREFERENCES) {
         return DEFAULT_PREFERENCES[prefName];
       }
       return null;
     },
     // Empty array of addons
     listAddons: () => [],
     // Empty array of tabs
     listTabs: () => ({ tabs: []}),
     // Empty arrays of workers
     listWorkers: () => ({
       otherWorkers: [],
       serviceWorkers: [],
       sharedWorkers: [],
     }),
-    // no-op
-    setPreference: () => {},
+    // stores the preference locally (doesn't update about:config)
+    setPreference: function(prefName, value) {
+      this._preferences[prefName] = value;
+    },
   };
 }
 
 // Create a ClientWrapper mock that can be used to replace the this-firefox runtime.
 function createThisFirefoxClientMock() {
   const mockThisFirefoxDescription = {
     name: "Firefox",
     channel: "nightly",
--- a/devtools/client/debugger/new/src/components/App.js
+++ b/devtools/client/debugger/new/src/components/App.js
@@ -264,16 +264,17 @@ class App extends Component<Props, State
         style={{ width: "100vw" }}
         initialSize={prefs.endPanelSize}
         minSize={30}
         maxSize={maxSize}
         splitterSize={1}
         vert={horizontal}
         onResizeEnd={num => {
           prefs.endPanelSize = num;
+          this.triggerEditorPaneResize();
         }}
         startPanel={
           <SplitBox
             style={{ width: "100vw" }}
             initialSize={prefs.startPanelSize}
             minSize={30}
             maxSize="85%"
             splitterSize={1}
@@ -288,17 +289,16 @@ class App extends Component<Props, State
         endPanelControl={true}
         endPanel={
           <SecondaryPanes
             horizontal={horizontal}
             toggleShortcutsModal={() => this.toggleShortcutsModal()}
           />
         }
         endPanelCollapsed={endPanelCollapsed}
-        onResizeEnd={this.triggerEditorPaneResize}
       />
     );
   };
 
   renderShortcutsModal() {
     const additionalClass = isMacOS ? "mac" : "";
 
     if (!features.shortcuts) {
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -370,16 +370,17 @@ Target.prototype = {
   // than most other fronts because it is closely related to the toolbox.
   // TODO: remove once inspector is separated from the toolbox
   async getInspector(typeName) {
     // the front might have been destroyed and no longer have an actor ID
     if (this._inspector && this._inspector.actorID) {
       return this._inspector;
     }
     this._inspector = await getFront(this.client, "inspector", this.form);
+    this.emit("inspector", this._inspector);
     return this._inspector;
   },
 
   // Run callback on every front of this type that currently exists, and on every
   // instantiation of front type in the future.
   onFront(typeName, callback) {
     const front = this.fronts.get(typeName);
     if (front) {
--- a/devtools/client/inspector/changes/reducers/changes.js
+++ b/devtools/client/inspector/changes/reducers/changes.js
@@ -211,28 +211,40 @@ const reducers = {
       }
     }
 
     if (change.remove && change.remove.length) {
       for (const decl of change.remove) {
         // Find the position of any added declaration which matches the incoming
         // declaration to be removed.
         const addIndex = rule.add.findIndex(addDecl => {
-          return addDecl.index === decl.index;
+          return addDecl.index === decl.index &&
+                 addDecl.property === decl.property &&
+                 addDecl.value === decl.value;
+        });
+
+        // Find the position of any removed declaration which matches the incoming
+        // declaration to be removed. It's possible to get duplicate remove operations
+        // when, for example, disabling a declaration then deleting it.
+        const removeIndex = rule.remove.findIndex(removeDecl => {
+          return removeDecl.index === decl.index &&
+                 removeDecl.property === decl.property &&
+                 removeDecl.value === decl.value;
         });
 
         // Track the remove operation only if the property was not previously introduced
         // by an add operation. This ensures repeated changes of the same property
-        // register as a single remove operation of its original value.
-        if (addIndex < 0) {
+        // register as a single remove operation of its original value. Avoid tracking the
+        // remove declaration if already tracked (happens on disable followed by delete).
+        if (addIndex < 0 && removeIndex < 0) {
           rule.remove.push(decl);
         }
 
         // Delete any previous add operation which would be canceled out by this remove.
-        if (rule.add[addIndex] && rule.add[addIndex].value === decl.value) {
+        if (rule.add[addIndex]) {
           rule.add.splice(addIndex, 1);
         }
 
         // Update the indexes of previously tracked declarations which follow this removed
         // one so future tracking continues to point to the right declarations.
         if (changeType === "declaration-remove") {
           rule.add = rule.add.map((addDecl => {
             if (addDecl.index > decl.index) {
@@ -253,28 +265,29 @@ const reducers = {
       }
     }
 
     if (change.add && change.add.length) {
       for (const decl of change.add) {
         // Find the position of any removed declaration which matches the incoming
         // declaration to be added.
         const removeIndex = rule.remove.findIndex(removeDecl => {
-          return removeDecl.index === decl.index;
+          return removeDecl.index === decl.index &&
+                 removeDecl.value === decl.value &&
+                 removeDecl.property === decl.property;
         });
 
         // Find the position of any added declaration which matches the incoming
         // declaration to be added in case we need to replace it.
         const addIndex = rule.add.findIndex(addDecl => {
-          return addDecl.index === decl.index;
+          return addDecl.index === decl.index &&
+                 addDecl.property === decl.property;
         });
 
-        if (rule.remove[removeIndex] &&
-            rule.remove[removeIndex].value === decl.value &&
-            rule.remove[removeIndex].property === decl.property) {
+        if (rule.remove[removeIndex]) {
           // Delete any previous remove operation which would be canceled out by this add.
           rule.remove.splice(removeIndex, 1);
         } else if (rule.add[addIndex]) {
           // Replace previous add operation for declaration at this index.
           rule.add.splice(addIndex, 1, decl);
         } else {
           // Track new add operation.
           rule.add.push(decl);
--- a/devtools/client/inspector/changes/test/browser.ini
+++ b/devtools/client/inspector/changes/test/browser.ini
@@ -11,11 +11,12 @@ support-files =
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_changes_declaration_disable.js]
 [browser_changes_declaration_duplicate.js]
 [browser_changes_declaration_edit_value.js]
 [browser_changes_declaration_remove_ahead.js]
+[browser_changes_declaration_remove_disabled.js]
 [browser_changes_declaration_remove.js]
 [browser_changes_declaration_rename.js]
 [browser_changes_rule_selector.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser_changes_declaration_remove_disabled.js
@@ -0,0 +1,99 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that disabling a CSS declaration and then removing it from the Rule view
+// is tracked as removed only once. Toggling leftover declarations should not introduce
+// duplicate changes.
+
+const TEST_URI = `
+  <style type='text/css'>
+    div {
+      color: red;
+      background: black;
+    }
+  </style>
+  <div></div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view: ruleView } = await openRuleView();
+  const { document: doc, store } = selectChangesView(inspector);
+
+  await selectNode("div", inspector);
+  const rule = getRuleViewRuleEditor(ruleView, 1).rule;
+
+  info("Using the second declaration");
+  await testRemoveValue(ruleView, store, doc, rule.textProps[1]);
+  info("Using the first declaration");
+  await testToggle(ruleView, store, doc, rule.textProps[0]);
+  info("Using the first declaration");
+  await testRemoveName(ruleView, store, doc, rule.textProps[0]);
+});
+
+async function testRemoveValue(ruleView, store, doc, prop) {
+  info("Test removing disabled declaration by clearing its property value.");
+  let onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the declaration");
+  await togglePropStatus(ruleView, prop);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Remove the disabled declaration by clearing its value");
+  await setProperty(ruleView, prop, null);
+  await onTrackChange;
+
+  const removeDecl = getRemovedDeclarations(doc);
+  is(removeDecl.length, 1, "Only one declaration tracked as removed");
+}
+
+async function testToggle(ruleView, store, doc, prop) {
+  info("Test toggling leftover declaration off and on will not track extra changes.");
+  let onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the declaration");
+  await togglePropStatus(ruleView, prop);
+  await onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Re-enable the declaration");
+  await togglePropStatus(ruleView, prop);
+  await onTrackChange;
+
+  const removeDecl = getRemovedDeclarations(doc);
+  is(removeDecl.length, 1, "Still just one declaration tracked as removed");
+}
+
+async function testRemoveName(ruleView, store, doc, prop) {
+  info("Test removing disabled declaration by clearing its property name.");
+  let onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the declaration");
+  await togglePropStatus(ruleView, prop);
+  await onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Remove the disabled declaration by clearing its name");
+  await removeProperty(ruleView, prop);
+  await onTrackChange;
+
+  info(`Expecting two declarations removed:
+  - one removed by its value in the other test
+  - one removed by its name from this test
+  `);
+
+  const removeDecl = getRemovedDeclarations(doc);
+  is(removeDecl.length, 2, "Two declarations tracked as removed");
+  is(removeDecl[0].property, "background", "First declaration name correct");
+  is(removeDecl[0].value, "black", "First declaration value correct");
+  is(removeDecl[1].property, "color", "Second declaration name correct");
+  is(removeDecl[1].value, "red", "Second declaration value correct");
+}
--- a/devtools/client/inspector/flexbox/test/browser.ini
+++ b/devtools/client/inspector/flexbox/test/browser.ini
@@ -44,10 +44,11 @@ support-files =
 [browser_flexbox_sizing_info_for_different_writing_modes.js]
 [browser_flexbox_sizing_info_for_pseudos.js]
 [browser_flexbox_sizing_info_for_text_nodes.js]
 [browser_flexbox_sizing_info_has_correct_sections.js]
 [browser_flexbox_sizing_info_matches_properties_with_!important.js]
 [browser_flexbox_sizing_info_updates_on_change.js]
 [browser_flexbox_sizing_wanted_to_grow_but_was_clamped.js]
 [browser_flexbox_text_nodes_are_listed.js]
+[browser_flexbox_text_nodes_are_not_inlined.js]
 [browser_flexbox_toggle_flexbox_highlighter_01.js]
 [browser_flexbox_toggle_flexbox_highlighter_02.js]
--- a/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_ESC.js
@@ -1,19 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+const asyncStorage = require("devtools/shared/async-storage");
+
 // Test that the flexbox highlighter color change in the color picker is reverted when
 // ESCAPE is pressed.
 
 const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
 
 add_task(async function() {
+  // Make sure there are no custom highlighter colors stored before starting.
+  await asyncStorage.removeItem("flexboxInspectorHostColors");
+
   await addTab(TEST_URI);
   const { inspector, flexboxInspector, layoutView } = await openLayoutView();
   const { document: doc } = flexboxInspector;
   const { store } = inspector;
   const cPicker = layoutView.swatchColorPickerTooltip;
   const spectrum = cPicker.spectrum;
 
   const onColorSwatchRendered = waitForDOM(doc,
--- a/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_highlighter_color_picker_on_RETURN.js
@@ -1,19 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+const asyncStorage = require("devtools/shared/async-storage");
+
 // Test that the flexbox highlighter color change in the color picker is committed when
 // RETURN is pressed.
 
 const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
 
 add_task(async function() {
+  // Make sure there are no custom highlighter colors stored before starting.
+  await asyncStorage.removeItem("flexboxInspectorHostColors");
+
   await addTab(TEST_URI);
   const { inspector, flexboxInspector, layoutView } = await openLayoutView();
   const { document: doc } = flexboxInspector;
   const { highlighters, store } = inspector;
   const cPicker = layoutView.swatchColorPickerTooltip;
   const spectrum = cPicker.spectrum;
 
   const onColorSwatchRendered = waitForDOM(doc,
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_not_inlined.js
@@ -0,0 +1,40 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that single child text nodes that are also flex items can be selected in the
+// flexbox inspector.
+// This means that they are not inlined like normal single child text nodes, since
+// selecting them in the flexbox inspector also means selecting them in the markup view.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function() {
+  await addTab(TEST_URI);
+  const { inspector, flexboxInspector } = await openLayoutView();
+  const { document: doc } = flexboxInspector;
+
+  // Select the flex container in the inspector.
+  const onItemsListRendered = waitForDOM(doc, ".layout-flexbox-wrapper .flex-item-list");
+  await selectNode(".container.single-child", inspector);
+  const [flexItemList] = await onItemsListRendered;
+
+  const items = [...flexItemList.querySelectorAll("button .objectBox")];
+  is(items.length, 1, "There is 1 item displayed in the list");
+  is(items[0].textContent, "#text", "The item in the list is a text node");
+
+  info("Click on the item to select it");
+  const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+  items[0].closest("button").click();
+  const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+  ok(flexOutlineContainer,
+     "The flex outline is displayed for a single child short text node too");
+
+  ok(inspector.selection.isTextNode(),
+     "The current inspector selection is the text node");
+
+  const markupContainer = inspector.markup.getContainer(inspector.selection.nodeFront);
+  is(markupContainer.elt.textContent, "short text", "This is the right text node");
+});
--- a/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html
@@ -12,8 +12,9 @@
   align-self: stretch;
 }
 </style>
 <div class="container">
   A text node will be wrapped into an anonymous block container
   <div></div>
   Here is yet another text node
 </div>
+<div class="container single-child">short text</div>
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1407,16 +1407,18 @@ Inspector.prototype = {
 
     if (this.walker) {
       this.walker.off("new-root", this.onNewRoot);
       this.pageStyle = null;
     }
 
     this.cancelUpdate();
 
+    this.sidebar.destroy();
+
     this.panelWin.removeEventListener("resize", this.onPanelWindowResize, true);
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("detached-front", this.onDetached);
     this.sidebar.off("select", this.onSidebarSelect);
     this.sidebar.off("show", this.onSidebarShown);
     this.sidebar.off("hide", this.onSidebarHidden);
     this.sidebar.off("destroy", this.onSidebarHidden);
     this.target.off("will-navigate", this._onBeforeNavigate);
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -65,16 +65,17 @@ class HighlightersOverlay {
     // Name of the highlighter shown on mouse hover.
     this.hoveredHighlighterShown = null;
     // Name of the selector highlighter shown.
     this.selectorHighlighterShown = null;
     // NodeFront of the shape that is highlighted
     this.shapesHighlighterShown = null;
 
     this.onClick = this.onClick.bind(this);
+    this.onDisplayChange = this.onDisplayChange.bind(this);
     this.onMarkupMutation = this.onMarkupMutation.bind(this);
     this.onMouseMove = this.onMouseMove.bind(this);
     this.onMouseOut = this.onMouseOut.bind(this);
     this.onWillNavigate = this.onWillNavigate.bind(this);
     this.hideFlexboxHighlighter = this.hideFlexboxHighlighter.bind(this);
     this.hideFlexItemHighlighter = this.hideFlexItemHighlighter.bind(this);
     this.hideGridHighlighter = this.hideGridHighlighter.bind(this);
     this.hideShapesHighlighter = this.hideShapesHighlighter.bind(this);
@@ -83,16 +84,17 @@ class HighlightersOverlay {
     this.showGridHighlighter = this.showGridHighlighter.bind(this);
     this.showShapesHighlighter = this.showShapesHighlighter.bind(this);
     this._handleRejection = this._handleRejection.bind(this);
     this.onShapesHighlighterShown = this.onShapesHighlighterShown.bind(this);
     this.onShapesHighlighterHidden = this.onShapesHighlighterHidden.bind(this);
 
     // Add inspector events, not specific to a given view.
     this.inspector.on("markupmutation", this.onMarkupMutation);
+    this.inspector.walker.on("display-change", this.onDisplayChange);
     this.inspector.target.on("will-navigate", this.onWillNavigate);
 
     EventEmitter.decorate(this);
   }
 
   /**
    * Returns true if the grid highlighter can be toggled on/off for the given node, and
    * false otherwise. A grid container can be toggled on if the max grid highlighters
@@ -1033,16 +1035,45 @@ class HighlightersOverlay {
 
       this.toggleShapesHighlighter(this.inspector.selection.nodeFront, {
         mode: event.target.dataset.mode,
         transformMode: event.metaKey || event.ctrlKey,
       }, nodeInfo.value.textProperty);
     }
   }
 
+  /**
+   * Handler for "display-change" events from the walker. Hides the flexbox or
+   * grid highlighter if their respective node is no longer a flex container or
+   * grid container.
+   *
+   * @param  {Array} nodes
+   *         An array of nodeFronts
+   */
+  async onDisplayChange(nodes) {
+    for (const node of nodes) {
+      const display = node.displayType;
+
+      // Hide the flexbox highlighter if the node is no longer a flexbox
+      // container.
+      if (display !== "flex" && display !== "inline-flex" &&
+          node == this.flexboxHighlighterShown) {
+        await this.hideFlexboxHighlighter(node);
+        return;
+      }
+
+      // Hide the grid highlighter if the node is no longer a grid container.
+      if (display !== "grid" && display !== "inline-grid" &&
+          this.gridHighlighters.has(node)) {
+        await this.hideGridHighlighter(node);
+        return;
+      }
+    }
+  }
+
   onMouseMove(event) {
     // Bail out if the target is the same as for the last mousemove.
     if (event.target === this._lastHovered) {
       return;
     }
 
     // Only one highlighter can be displayed at a time, hide the currently shown.
     this._hideHoveredHighlighter();
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -155,16 +155,17 @@ skip-if = (os == 'linux' && bits == 32 &
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
 [browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_navigate_to_errors.js]
 [browser_inspector_open_on_neterror.js]
+[browser_inspector_pane_state_restore.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_pane-toggle-04.js]
 [browser_inspector_picker-stop-on-tool-change.js]
 [browser_inspector_portrait_mode.js]
 [browser_inspector_pseudoclass-lock.js]
 [browser_inspector_pseudoclass-menu.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane_state_restore.js
@@ -0,0 +1,55 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the previous inspector split box sizes are restored when reopening the
+// inspector.
+
+const EXPECTED_INITIAL_WIDTH = 101;
+const EXPECTED_INITIAL_HEIGHT = 102;
+const EXPECTED_INITIAL_SIDEBAR_WIDTH = 103;
+
+const EXPECTED_NEW_WIDTH = 150;
+const EXPECTED_NEW_HEIGHT = 100;
+const EXPECTED_NEW_SIDEBAR_WIDTH = 250;
+
+add_task(async function() {
+  // Simulate that the user has already stored their preferred split boxes widths.
+  await pushPref("devtools.toolsidebar-width.inspector", EXPECTED_INITIAL_WIDTH);
+  await pushPref("devtools.toolsidebar-height.inspector", EXPECTED_INITIAL_HEIGHT);
+  await pushPref("devtools.toolsidebar-width.inspector.splitsidebar",
+    EXPECTED_INITIAL_SIDEBAR_WIDTH);
+
+  const { inspector } = await openInspectorForURL("about:blank");
+
+  info("Check the initial size of the inspector.");
+  const { width, height, splitSidebarWidth } = inspector.getSidebarSize();
+  is(width, EXPECTED_INITIAL_WIDTH, "Got correct initial width.");
+  is(height, EXPECTED_INITIAL_HEIGHT, "Got correct initial height.");
+  is(splitSidebarWidth, EXPECTED_INITIAL_SIDEBAR_WIDTH,
+    "Got correct initial split sidebar width.");
+
+  info("Simulate updates to the dimensions of the various splitboxes.");
+  inspector.splitBox.setState({
+    width: EXPECTED_NEW_WIDTH,
+    height: EXPECTED_NEW_HEIGHT,
+  });
+  inspector.sidebarSplitBox.setState({
+    width: EXPECTED_NEW_SIDEBAR_WIDTH,
+  });
+
+  await closeToolbox();
+
+  info("Check the stored sizes of the inspector in the preferences when the inspector " +
+    "is closed");
+  const storedWidth = Services.prefs.getIntPref("devtools.toolsidebar-width.inspector");
+  const storedHeight = Services.prefs.getIntPref("devtools.toolsidebar-height.inspector");
+  const storedSplitSidebarWidth =
+    Services.prefs.getIntPref("devtools.toolsidebar-width.inspector.splitsidebar");
+  is(storedWidth, EXPECTED_NEW_WIDTH, "Got correct stored width.");
+  is(storedHeight, EXPECTED_NEW_HEIGHT, "Got correct stored height");
+  is(storedSplitSidebarWidth, EXPECTED_NEW_SIDEBAR_WIDTH,
+    "Got correct stored split sidebar width.");
+});
--- a/devtools/client/memory/components/SnapshotListItem.js
+++ b/devtools/client/memory/components/SnapshotListItem.js
@@ -87,17 +87,20 @@ class SnapshotListItem extends Component
     }
 
     const saveLink = !snapshot.path ? void 0 : dom.a({
       onClick: () => onSave(snapshot),
       className: "save",
     }, L10N.getStr("snapshot.io.save"));
 
     const deleteButton = !snapshot.path ? void 0 : dom.button({
-      onClick: () => onDelete(snapshot),
+      onClick: (event) => {
+        event.stopPropagation();
+        onDelete(snapshot);
+      },
       className: "delete",
       title: L10N.getStr("snapshot.io.delete"),
     });
 
     return (
       dom.li({ className, onClick },
         dom.span({
           className: `snapshot-title ${wantThrobber ? " devtools-throbber" : ""}`,
--- a/devtools/client/memory/test/browser/browser_memory_clear_snapshots.js
+++ b/devtools/client/memory/test/browser/browser_memory_clear_snapshots.js
@@ -16,23 +16,43 @@ this.test = makeMemoryTest(TEST_URL, asy
 
   let snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
   is(getState().snapshots.length, 0, "Starts with no snapshots in store");
   is(snapshotEls.length, 0, "No snapshots visible");
 
   info("Take two snapshots");
   takeSnapshot(panel.panelWin);
   takeSnapshot(panel.panelWin);
+  takeSnapshot(panel.panelWin);
   await waitUntilState(gStore, state =>
-  state.snapshots.length === 2 &&
-  state.snapshots[0].treeMap && state.snapshots[1].treeMap &&
-  state.snapshots[0].treeMap.state === treeMapState.SAVED &&
-  state.snapshots[1].treeMap.state === treeMapState.SAVED);
+    state.snapshots.length === 3 &&
+    state.snapshots[0].treeMap && state.snapshots[1].treeMap &&
+    state.snapshots[2].treeMap &&
+    state.snapshots[0].treeMap.state === treeMapState.SAVED &&
+    state.snapshots[1].treeMap.state === treeMapState.SAVED &&
+    state.snapshots[2].treeMap.state === treeMapState.SAVED);
 
   snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
+  is(snapshotEls.length, 3, "Three snapshots visible");
+  is(document.querySelectorAll(".selected").length, 1, "One selected snapshot visible");
+  ok(snapshotEls[2].classList.contains("selected"), "Third snapshot selected");
+
+  info("Clicking on first snapshot delete button");
+  document.querySelectorAll(".delete")[0].click();
+
+  await waitUntilState(gStore, state =>
+    state.snapshots.length === 2 &&
+    state.snapshots[0].treeMap && state.snapshots[1].treeMap &&
+    state.snapshots[0].treeMap.state === treeMapState.SAVED &&
+    state.snapshots[1].treeMap.state === treeMapState.SAVED);
+
+  snapshotEls = document.querySelectorAll(".snapshot-list-item");
   is(snapshotEls.length, 2, "Two snapshots visible");
+  // Bug 1476289
+  ok(!snapshotEls[0].classList.contains("selected"), "First snapshot not selected");
+  ok(snapshotEls[1].classList.contains("selected"), "Second snapshot selected");
 
   info("Click on Clear Snapshots");
   await clearSnapshots(panel.panelWin);
   is(getState().snapshots.length, 0, "No snapshots in store");
   snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
   is(snapshotEls.length, 0, "No snapshot visible");
 });
--- a/devtools/client/performance-new/components/Settings.js
+++ b/devtools/client/performance-new/components/Settings.js
@@ -143,16 +143,21 @@ const featureCheckboxes = [
     value: "tasktracer",
     title: "Enable TaskTracer (Experimental, requires custom build.)",
   },
   {
     name: "Screenshots",
     value: "screenshots",
     title: "Record screenshots of all browser windows.",
   },
+  {
+    name: "JSTracer",
+    value: "jstracer",
+    title: "Trace JS engine (Experimental, requires custom build.)",
+  },
 ];
 
 /**
  * This component manages the settings for recording a performance profile.
  */
 class Settings extends PureComponent {
   static get propTypes() {
     return {
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -49,23 +49,18 @@ pref("devtools.inspector.showUserAgentSt
 // Show all native anonymous content
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Show user agent shadow roots
 pref("devtools.inspector.showUserAgentShadowRoots", false);
 // Enable the CSS shapes highlighter
 pref("devtools.inspector.shapesHighlighter.enabled", true);
 // Enable the font highlight-on-hover feature
 pref("devtools.inspector.fonthighlighter.enabled", true);
-
 // Enable tracking of style changes and the Changes panel in the Inspector
-#if defined(NIGHTLY_BUILD)
 pref("devtools.inspector.changes.enabled", true);
-#else
-pref("devtools.inspector.changes.enabled", false);
-#endif
 
 // Flexbox preferences
 pref("devtools.inspector.flexboxHighlighter.enabled", true);
 pref("devtools.flexboxinspector.enabled", true);
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
 pref("devtools.gridinspector.gridOutlineMaxRows", 50);
@@ -277,22 +272,18 @@ pref("devtools.webconsole.timestampMessa
 pref("devtools.webconsole.sidebarToggle", true);
 #else
 pref("devtools.webconsole.sidebarToggle", false);
 #endif
 
 // Enable CodeMirror in the JsTerm
 pref("devtools.webconsole.jsterm.codeMirror", true);
 
-// Enable console input reverse-search in Nightly builds
-#if defined(NIGHTLY_BUILD)
+// Enable console input reverse-search everywhere
 pref("devtools.webconsole.jsterm.reverse-search", true);
-#else
-pref("devtools.webconsole.jsterm.reverse-search", false);
-#endif
 
 // Disable the new performance recording panel by default
 pref("devtools.performance.new-panel-enabled", false);
 
 // Enable client-side mapping service for source maps
 pref("devtools.source-map.client-service.enabled", true);
 
 // The number of lines that are displayed in the web console.
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -121,18 +121,34 @@ function swapToInnerBrowser({ tab, conta
       );
       if (newFrameloader) {
         debug(`Tab will force a new frameloader on navigation, load about:blank first`);
         await loadURIWithNewFrameLoader(tab.linkedBrowser, "about:blank", {
           flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
           triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
         });
       }
-      if (mustChangeProcess) {
-        debug(`Tab will force a process flip on navigation, load about:blank first`);
+      // When the separate privileged content process is enabled, about:home and
+      // about:newtab will load in it, and we'll need to switch away if the user
+      // ever browses to a new URL. To avoid that, when the privileged process is
+      // enabled, we do the process flip immediately before entering RDM mode. The
+      // trade-off is that about:newtab can't be inspected in RDM, but it allows
+      // users to start RDM on that page and keep it open.
+      //
+      // The other trade is that sometimes users will be viewing the local file
+      // URI process, and will want to view the page in RDM. We allow this without
+      // blanking out the page, but we trade that for closing RDM if browsing ever
+      // causes them to flip processes.
+      //
+      // Bug 1510806 has been filed to fix this properly, by making RDM resilient
+      // to process flips.
+      if (mustChangeProcess &&
+          tab.linkedBrowser.remoteType == "privileged") {
+        debug(`Tab must flip away from the privileged content process ` +
+              `on navigation`);
         gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true, {
           remoteType: requiredRemoteType,
         });
       }
 
       tab.isResponsiveDesignMode = true;
 
       // Hide the browser content temporarily while things move around to avoid displaying
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -15,27 +15,16 @@ const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
     value: null,
     extra: {
       oldpanel: "layoutview",
-      newpanel: "changesview",
-    },
-  },
-  {
-    timestamp: null,
-    category: "devtools.main",
-    method: "sidepanel_changed",
-    object: "inspector",
-    value: null,
-    extra: {
-      oldpanel: "changesview",
       newpanel: "animationinspector",
     },
   },
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
@@ -70,27 +59,16 @@ const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
     value: null,
     extra: {
       oldpanel: "computedview",
-      newpanel: "changesview",
-    },
-  },
-  {
-    timestamp: null,
-    category: "devtools.main",
-    method: "sidepanel_changed",
-    object: "inspector",
-    value: null,
-    extra: {
-      oldpanel: "changesview",
       newpanel: "animationinspector",
     },
   },
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
@@ -123,19 +101,16 @@ const DATA = [
     },
   },
 ];
 
 add_task(async function() {
   // Let's reset the counts.
   Services.telemetry.clearEvents();
 
-  // Ensure the Changes panel is enabled before running the tests.
-  await pushPref("devtools.inspector.changes.enabled", true);
-
   // Ensure no events have been logged
   const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
   ok(!snapshot.parent, "No events have been logged for the main process");
 
   await addTab(TEST_URI);
   startTelemetry();
 
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
@@ -150,17 +125,17 @@ add_task(async function() {
   gBrowser.removeCurrentTab();
 });
 
 function testSidebar(toolbox) {
   info("Testing sidebar");
 
   const inspector = toolbox.getCurrentPanel();
   let sidebarTools = ["computedview", "layoutview", "fontinspector",
-                      "animationinspector", "changesview"];
+                      "animationinspector"];
 
   // Concatenate the array with itself so that we can open each tool twice.
   sidebarTools = [...sidebarTools, ...sidebarTools];
 
   return new Promise(resolve => {
     // See TOOL_DELAY for why we need setTimeout here
     setTimeout(function selectSidebarTab() {
       const tool = sidebarTools.pop();
@@ -179,21 +154,19 @@ function testSidebar(toolbox) {
 function checkResults() {
   // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
   // here.
   checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", {0: 1, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", {0: 1, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", {0: 3, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
-  checkTelemetry("devtools.changesview.opened_count", "", 2, "scalar");
   checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
   checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
   checkTelemetry("DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
-  checkTelemetry("DEVTOOLS_CHANGESVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
 
 function checkEventTelemetry() {
   const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
   const events = snapshot.parent.filter(event => event[1] === "devtools.main" &&
                                                   event[2] === "sidepanel_changed" &&
                                                   event[3] === "inspector" &&
                                                   event[4] === null
--- a/devtools/client/themes/changes.css
+++ b/devtools/client/themes/changes.css
@@ -21,16 +21,26 @@
 :root.theme-dark {
   --diff-add-background-color: rgba(18, 188, 0, 0.15);
   --diff-add-text-color: #12BC00;
   --diff-remove-background-color: rgba(255, 0, 57, 0.15);
   --diff-remove-text-color: #FF0039;
   --diff-source-background: #222225;
 }
 
+:root[dir="rtl"] {
+  /* Increase minimum offset on right-to-left layout to clear the floating scrollbar. */
+  --diff-level-min-offset: 15px;
+}
+
+:root[dir="rtl"] #sidebar-panel-changes .source {
+  /* Enforce left-to-right code rendering on right-to-left layout. */
+  direction: ltr;
+}
+
 #sidebar-panel-changes {
   margin: 0;
   padding: 0;
   width: 100%;
   height: 100%;
   overflow: auto;
   background: var(--theme-sidebar-background);
 }
@@ -38,19 +48,24 @@
 #sidebar-panel-changes .href {
   display: flex;
   align-items: center;
   color: var(--theme-toolbar-color);
   background: var(--diff-source-background);
   border-top: 1px solid var(--theme-splitter-color);
   border-bottom: 1px solid var(--theme-splitter-color);
   padding: 4px;
+  padding-inline-start: var(--diff-level-min-offset);
   font-size: 12px;
 }
 
+#sidebar-panel-changes .source:first-child .href {
+  border-top: unset;
+}
+
 #sidebar-panel-changes .href span {
   /* Allows trimming of flex item with overflow ellipsis within the flex container */
   min-width: 0;
   white-space: nowrap;
   text-overflow: ellipsis;
   overflow: hidden;
 }
 
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -338,16 +338,17 @@ skip-if = true  # Bug 1438979
 [browser_webconsole_network_requests_from_chrome.js]
 [browser_webconsole_network_reset_filter.js]
 [browser_webconsole_nodes_highlight.js]
 [browser_webconsole_nodes_select.js]
 [browser_webconsole_object_ctrl_click.js]
 [browser_webconsole_object_in_sidebar_keyboard_nav.js]
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_object_inspector_entries.js]
+[browser_webconsole_object_inspector_getters.js]
 [browser_webconsole_object_inspector_key_sorting.js]
 [browser_webconsole_object_inspector_local_session_storage.js]
 [browser_webconsole_object_inspector_selected_text.js]
 [browser_webconsole_object_inspector_scroll.js]
 [browser_webconsole_object_inspector_while_debugging_and_inspecting.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_optimized_out_vars.js]
 [browser_webconsole_output_copy.js]
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_nodes_highlight.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_nodes_highlight.js
@@ -19,29 +19,33 @@ const HTML = `
     </script>
   </html>
 `;
 const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const toolbox = gDevTools.getToolbox(hud.target);
-  await toolbox.initInspector();
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.logNode("h1");
   });
 
   const msg = await waitFor(() => findMessage(hud, "<h1>"));
   const node = msg.querySelector(".objectBox-node");
   ok(node !== null, "Node was logged as expected");
   const view = node.ownerDocument.defaultView;
 
   info("Highlight the node by moving the cursor on it");
-  const onNodeHighlight = toolbox.highlighter.once("node-highlight");
+
+  // the inspector should be initialized first and then the node should
+  // highlight after the hover effect.
+  const onNodeHighlight = toolbox.target.once("inspector")
+    .then(inspector => inspector.highlighter.once("node-highlight"));
+
   EventUtils.synthesizeMouseAtCenter(node, {type: "mousemove"}, view);
 
   const nodeFront = await onNodeHighlight;
   is(nodeFront.displayName, "h1", "The correct node was highlighted");
 
   info("Unhighlight the node by moving away from the node");
   const onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight");
   const btn = toolbox.doc.getElementById("toolbox-meatball-menu-button");
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_getters.js
@@ -0,0 +1,342 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check evaluating and expanding getters in the console.
+const TEST_URI = "data:text/html;charset=utf8,<h1>Object Inspector on Getters</h1>";
+const {ELLIPSIS} = require("devtools/shared/l10n");
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  const LONGSTRING = "ab ".repeat(1e5);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, LONGSTRING, function(longString) {
+    content.wrappedJSObject.console.log(
+      "oi-test",
+      Object.create(null, Object.getOwnPropertyDescriptors({
+        get myStringGetter() {
+          return "hello";
+        },
+        get myNumberGetter() {
+          return 123;
+        },
+        get myUndefinedGetter() {
+          return undefined;
+        },
+        get myNullGetter() {
+          return null;
+        },
+        get myObjectGetter() {
+          return {foo: "bar"};
+        },
+        get myArrayGetter() {
+          return Array.from({length: 1000}, (_, i) => i);
+        },
+        get myMapGetter() {
+          return new Map([["foo", {bar: "baz"}]]);
+        },
+        get myProxyGetter() {
+          const handler = {
+            get: function(target, name) {
+              return name in target ? target[name] : 37;
+            },
+          };
+          return new Proxy({a: 1}, handler);
+        },
+        get myThrowingGetter() {
+          throw new Error("myError");
+        },
+        get myLongStringGetter() {
+          return longString;
+        },
+      }))
+    );
+  });
+
+  const node = await waitFor(() => findMessage(hud, "oi-test"));
+  const oi = node.querySelector(".tree");
+
+  expandNode(oi);
+  await waitFor(() => getOiNodes(oi).length > 1);
+
+  await testStringGetter(oi);
+  await testNumberGetter(oi);
+  await testUndefinedGetter(oi);
+  await testNullGetter(oi);
+  await testObjectGetter(oi);
+  await testArrayGetter(oi);
+  await testMapGetter(oi);
+  await testProxyGetter(oi);
+  await testThrowingGetter(oi);
+  await testLongStringGetter(oi, LONGSTRING);
+});
+
+async function testStringGetter(oi) {
+  let node = getOiNode(oi, "myStringGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myStringGetter")));
+
+  node = getOiNode(oi, "myStringGetter");
+  ok(node.textContent.includes(`myStringGetter: "hello"`),
+    "String getter now has the expected text content");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+}
+
+async function testNumberGetter(oi) {
+  let node = getOiNode(oi, "myNumberGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myNumberGetter")));
+
+  node = getOiNode(oi, "myNumberGetter");
+  ok(node.textContent.includes(`myNumberGetter: 123`),
+    "Number getter now has the expected text content");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+}
+
+async function testUndefinedGetter(oi) {
+  let node = getOiNode(oi, "myUndefinedGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myUndefinedGetter")));
+
+  node = getOiNode(oi, "myUndefinedGetter");
+  ok(node.textContent.includes(`myUndefinedGetter: undefined`),
+    "undefined getter now has the expected text content");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+}
+
+async function testNullGetter(oi) {
+  let node = getOiNode(oi, "myNullGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myNullGetter")));
+
+  node = getOiNode(oi, "myNullGetter");
+  ok(node.textContent.includes(`myNullGetter: null`),
+    "null getter now has the expected text content");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+}
+
+async function testObjectGetter(oi) {
+  let node = getOiNode(oi, "myObjectGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myObjectGetter")));
+
+  node = getOiNode(oi, "myObjectGetter");
+  ok(node.textContent.includes(`myObjectGetter: Object { foo: "bar" }`),
+    "object getter now has the expected text content");
+  is(nodeIsExpandable(node), true, "The node can be expanded");
+
+  expandNode(node);
+  await waitFor(() => getChildrenNodes(node).length > 0);
+  checkChildren(node, [`foo: "bar"`, `<prototype>`]);
+}
+
+async function testArrayGetter(oi) {
+  let node = getOiNode(oi, "myArrayGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myArrayGetter")));
+
+  node = getOiNode(oi, "myArrayGetter");
+  ok(node.textContent.includes(
+    `myArrayGetter: Array(1000) [ 0, 1, 2, ${ELLIPSIS} ]`),
+    "Array getter now has the expected text content - ");
+  is(nodeIsExpandable(node), true, "The node can be expanded");
+
+  expandNode(node);
+  await waitFor(() => getChildrenNodes(node).length > 0);
+  const children = getChildrenNodes(node);
+
+  const firstBucket = children[0];
+  ok(firstBucket.textContent.includes(`[0${ELLIPSIS}99]`), "Array has buckets");
+
+  is(nodeIsExpandable(firstBucket), true, "The bucket can be expanded");
+  expandNode(firstBucket);
+  await waitFor(() => getChildrenNodes(firstBucket).length > 0);
+  checkChildren(firstBucket, Array.from({length: 100}, (_, i) => `${i}: ${i}`));
+}
+
+async function testMapGetter(oi) {
+  let node = getOiNode(oi, "myMapGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myMapGetter")));
+
+  node = getOiNode(oi, "myMapGetter");
+  ok(node.textContent.includes(`myMapGetter: Map`),
+    "map getter now has the expected text content");
+  is(nodeIsExpandable(node), true, "The node can be expanded");
+
+  expandNode(node);
+  await waitFor(() => getChildrenNodes(node).length > 0);
+  checkChildren(node, [`size`, `<entries>`, `<prototype>`]);
+
+  const entriesNode = getOiNode(oi, "<entries>");
+  expandNode(entriesNode);
+  await waitFor(() => getChildrenNodes(entriesNode).length > 0);
+  // "→" character. Need to be instantiated this way for the test to pass.
+  const arrow = String.fromCodePoint(8594);
+  checkChildren(entriesNode, [`foo ${arrow} Object { ${ELLIPSIS} }`]);
+
+  const entryNode = getChildrenNodes(entriesNode)[0];
+  expandNode(entryNode);
+  await waitFor(() => getChildrenNodes(entryNode).length > 0);
+  checkChildren(entryNode, [`<key>: "foo"`, `<value>: Object { ${ELLIPSIS} }`]);
+}
+
+async function testProxyGetter(oi) {
+  let node = getOiNode(oi, "myProxyGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myProxyGetter")));
+
+  node = getOiNode(oi, "myProxyGetter");
+  ok(node.textContent.includes(`myProxyGetter: Proxy`),
+    "proxy getter now has the expected text content");
+  is(nodeIsExpandable(node), true, "The node can be expanded");
+
+  expandNode(node);
+  await waitFor(() => getChildrenNodes(node).length > 0);
+  checkChildren(node, [`<target>`, `<handler>`]);
+
+  const targetNode = getOiNode(oi, "<target>");
+  expandNode(targetNode);
+  await waitFor(() => getChildrenNodes(targetNode).length > 0);
+  checkChildren(targetNode, [`a: 1`, `<prototype>`]);
+
+  const handlerNode = getOiNode(oi, "<handler>");
+  expandNode(handlerNode);
+  await waitFor(() => getChildrenNodes(handlerNode).length > 0);
+  checkChildren(handlerNode, [`get:`, `<prototype>`]);
+}
+
+async function testThrowingGetter(oi) {
+  let node = getOiNode(oi, "myThrowingGetter");
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() => !getInvokeGetterButton(getOiNode(oi, "myThrowingGetter")));
+
+  node = getOiNode(oi, "myThrowingGetter");
+  ok(node.textContent.includes(`myThrowingGetter: Error: "myError"`),
+    "throwing getter does show the error");
+  is(nodeIsExpandable(node), true, "The node can be expanded");
+
+  expandNode(node);
+  await waitFor(() => getChildrenNodes(node).length > 0);
+  checkChildren(node,
+    [`columnNumber`, `fileName`, `lineNumber`, `message`, `stack`, `<prototype>`]);
+}
+
+async function testLongStringGetter(oi, longString) {
+  const getLongStringNode = () => getOiNode(oi, "myLongStringGetter");
+  const node = getLongStringNode();
+  is(nodeIsExpandable(node), false, "The node can't be expanded");
+  const invokeButton = getInvokeGetterButton(node);
+  ok(invokeButton, "There is an invoke button as expected");
+
+  invokeButton.click();
+  await waitFor(() =>
+    getLongStringNode().textContent.includes(`myLongStringGetter: "ab ab`));
+  ok(true, "longstring getter shows the initial text");
+  is(nodeIsExpandable(getLongStringNode()), true, "The node can be expanded");
+
+  expandNode(getLongStringNode());
+  await waitFor(() =>
+    getLongStringNode().textContent.includes(`myLongStringGetter: "${longString}"`));
+  ok(true, "the longstring was expanded");
+}
+
+function checkChildren(node, expectedChildren) {
+  const children = getChildrenNodes(node);
+  is(children.length, expectedChildren.length,
+    "There is the expected number of children");
+  children.forEach((child, index) => {
+    ok(child.textContent.includes(expectedChildren[index]),
+      `Expected "${expectedChildren[index]}" child`);
+  });
+}
+
+function nodeIsExpandable(node) {
+  return !!getNodeArrow(node);
+}
+
+function expandNode(node) {
+  const arrow = getNodeArrow(node);
+  if (!arrow) {
+    ok(false, "Node can't be expanded");
+    return;
+  }
+  arrow.click();
+}
+
+function getNodeArrow(node) {
+  return node.querySelector(".arrow");
+}
+
+function getOiNodes(oi) {
+  return oi.querySelectorAll(".tree-node");
+}
+
+function getChildrenNodes(node) {
+  const getLevel = n => parseInt(n.getAttribute("aria-level"), 10);
+  const level = getLevel(node);
+  const childLevel = level + 1;
+  const children = [];
+
+  let currentNode = node;
+  while (currentNode.nextSibling && getLevel(currentNode.nextSibling) === childLevel) {
+    currentNode = currentNode.nextSibling;
+    children.push(currentNode);
+  }
+
+  return children;
+}
+
+function getInvokeGetterButton(node) {
+  return node.querySelector(".invoke-getter");
+}
+
+function getOiNode(oi, nodeLabel) {
+  return [...oi.querySelectorAll(".tree-node")].find(node => {
+    const label = node.querySelector(".object-label");
+    if (!label) {
+      return false;
+    }
+    return label.textContent === nodeLabel;
+  });
+}
--- a/devtools/client/webconsole/webconsole-output-wrapper.js
+++ b/devtools/client/webconsole/webconsole-output-wrapper.js
@@ -205,29 +205,30 @@ WebConsoleOutputWrapper.prototype = {
           }),
           openNetworkPanel: (requestId) => {
             return this.toolbox.selectTool("netmonitor").then((panel) => {
               return panel.panelWin.Netmonitor.inspectRequest(requestId);
             });
           },
           sourceMapService: this.toolbox ? this.toolbox.sourceMapURLService : null,
           highlightDomElement: async (grip, options = {}) => {
+            await this.toolbox.initInspector();
             if (!this.toolbox.highlighter) {
               return null;
             }
-            await this.toolbox.initInspector();
             const nodeFront = await this.toolbox.walker.gripToNodeFront(grip);
             return this.toolbox.highlighter.highlight(nodeFront, options);
           },
           unHighlightDomElement: (forceHide = false) => {
             return this.toolbox.highlighter
               ? this.toolbox.highlighter.unhighlight(forceHide)
               : null;
           },
           openNodeInInspector: async (grip) => {
+            await this.toolbox.initInspector();
             const onSelectInspector = this.toolbox.selectTool("inspector", "inspect_dom");
             const onGripNodeToFront = this.toolbox.walker.gripToNodeFront(grip);
             const [
               front,
               inspector,
             ] = await Promise.all([onGripNodeToFront, onSelectInspector]);
 
             const onInspectorUpdated = inspector.once("inspector-updated");
--- a/devtools/server/actors/changes.js
+++ b/devtools/server/actors/changes.js
@@ -21,23 +21,27 @@ const ChangesActor = protocol.ActorClass
    * @param {TargetActor} targetActor
    *    The top-level Actor for this tab.
    */
   initialize: function(conn, targetActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.targetActor = targetActor;
 
     this.onTrackChange = this.pushChange.bind(this);
+    this.onWillNavigate = this.clearChanges.bind(this);
+
     TrackChangeEmitter.on("track-change", this.onTrackChange);
+    this.targetActor.on("will-navigate", this.onWillNavigate);
 
     this.changes = [];
   },
 
   destroy: function() {
     this.clearChanges();
+    this.targetActor.off("will-navigate", this.onWillNavigate);
     TrackChangeEmitter.off("track-change", this.onTrackChange);
     protocol.Actor.prototype.destroy.call(this);
   },
 
   start: function() {
     /**
      * This function currently does nothing and returns nothing. It exists only
      * so that the client can trigger the creation of the ChangesActor through
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -752,28 +752,16 @@ class FlexboxHighlighter extends AutoRef
           break;
       }
     }
 
     this.ctx.restore();
   }
 
   _update() {
-    // If this.currentNode is not a flex container we have nothing to highlight.
-    // We can't simply use getAsFlexContainer() here because this fails for
-    // text fields. This will be removed by https://bugzil.la/1509460.
-    if (!this.computedStyle) {
-      this.computedStyle = getComputedStyle(this.currentNode);
-    }
-
-    if (this.computedStyle.display !== "flex" &&
-        this.computedStyle.display !== "inline-flex") {
-      return false;
-    }
-
     setIgnoreLayoutChanges(true);
 
     const root = this.getElement("root");
 
     // Hide the root element and force the reflow in order to get the proper window's
     // dimensions without increasing them.
     root.setAttribute("style", "display: none");
     this.win.document.documentElement.offsetWidth;
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -30,16 +30,17 @@ loader.lazyRequireGetter(this, "nodeDocu
 loader.lazyRequireGetter(this, "standardTreeWalkerFilter", "devtools/server/actors/inspector/utils", true);
 
 loader.lazyRequireGetter(this, "CustomElementWatcher", "devtools/server/actors/inspector/custom-element-watcher", true);
 loader.lazyRequireGetter(this, "DocumentWalker", "devtools/server/actors/inspector/document-walker", true);
 loader.lazyRequireGetter(this, "SKIP_TO_SIBLING", "devtools/server/actors/inspector/document-walker", true);
 loader.lazyRequireGetter(this, "NodeActor", "devtools/server/actors/inspector/node", true);
 loader.lazyRequireGetter(this, "NodeListActor", "devtools/server/actors/inspector/node", true);
 loader.lazyRequireGetter(this, "LayoutActor", "devtools/server/actors/layout", true);
+loader.lazyRequireGetter(this, "findFlexOrGridParentContainerForNode", "devtools/server/actors/layout", true);
 loader.lazyRequireGetter(this, "getLayoutChangesObserver", "devtools/server/actors/reflow", true);
 loader.lazyRequireGetter(this, "releaseLayoutChangesObserver", "devtools/server/actors/reflow", true);
 loader.lazyRequireGetter(this, "WalkerSearch", "devtools/server/actors/utils/walker-search", true);
 
 loader.lazyServiceGetter(this, "eventListenerService",
   "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
 
 loader.lazyRequireGetter(this, "ChromeUtils");
@@ -504,57 +505,63 @@ var WalkerActor = protocol.ActorClassWit
   },
 
   /**
    * If the given NodeActor only has a single text node as a child with a text
    * content small enough to be inlined, return that child's NodeActor.
    *
    * @param NodeActor node
    */
-  inlineTextChild: function(node) {
+  inlineTextChild: function({ rawNode }) {
     // Quick checks to prevent creating a new walker if possible.
-    if (isBeforePseudoElement(node.rawNode) ||
-        isAfterPseudoElement(node.rawNode) ||
-        isShadowHost(node.rawNode) ||
-        node.rawNode.nodeType != Node.ELEMENT_NODE ||
-        node.rawNode.children.length > 0) {
+    if (isBeforePseudoElement(rawNode) ||
+        isAfterPseudoElement(rawNode) ||
+        isShadowHost(rawNode) ||
+        rawNode.nodeType != Node.ELEMENT_NODE ||
+        rawNode.children.length > 0) {
       return undefined;
     }
 
     let walker;
     try {
       // By default always try to use an anonymous walker. Even for DirectShadowHostChild,
       // children should be available through an anonymous walker (unless the child is not
       // slotted, see catch block).
-      walker = this.getDocumentWalker(node.rawNode);
+      walker = this.getDocumentWalker(rawNode);
     } catch (e) {
       // Using an anonymous walker might throw, for instance on unslotted shadow host
       // children.
-      walker = this.getNonAnonymousWalker(node.rawNode);
+      walker = this.getNonAnonymousWalker(rawNode);
     }
 
     const firstChild = walker.firstChild();
 
     // Bail out if:
     // - more than one child
     // - unique child is not a text node
     // - unique child is a text node, but is too long to be inlined
     // - we are a slot -> these are always represented on their own lines with
     //                    a link to the original node.
+    // - we are a flex item -> these are always shown on their own lines so they can be
+    //                         selected by the flexbox inspector.
     const isAssignedSlot =
       !!firstChild &&
-      node.rawNode.nodeName === "SLOT" &&
+      rawNode.nodeName === "SLOT" &&
       isDirectShadowHostChild(firstChild);
 
+    const isFlexItem =
+      !!firstChild &&
+      findFlexOrGridParentContainerForNode(firstChild, "flex", this);
+
     if (!firstChild ||
         walker.nextSibling() ||
         firstChild.nodeType !== Node.TEXT_NODE ||
         firstChild.nodeValue.length > gValueSummaryLength ||
-        isAssignedSlot
-        ) {
+        isAssignedSlot ||
+        isFlexItem) {
       return undefined;
     }
 
     return this._ref(firstChild);
   },
 
   /**
    * Mark a node as 'retained'.
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -84,17 +84,18 @@ const FlexboxActor = ActorClassWithSpec(
 
     return form;
   },
 
   /**
    * Returns an array of FlexItemActor objects for all the flex item elements contained
    * in the flex container element.
    *
-   * @return {Array} An array of FlexItemActor objects.
+   * @return {Array}
+   *         An array of FlexItemActor objects.
    */
   getFlexItems() {
     if (isNodeDead(this.containerEl)) {
       return [];
     }
 
     const flex = this.containerEl.getAsFlexContainer();
     if (!flex) {
@@ -330,128 +331,99 @@ const LayoutActor = ActorClassWithSpec(l
    * selected node. The current node can be a grid/flex container or grid/flex item.
    * If it is a grid/flex item, returns the parent grid/flex container. Otherwise, returns
    * null if the current or parent node is not a grid/flex container.
    *
    * @param  {Node|NodeActor} node
    *         The node to start iterating at.
    * @param  {String} type
    *         Can be "grid" or "flex", the display type we are searching for.
-   * @param  {Node|null} container
-   *         The container of the current node.
-   * @return {GridActor|FlexboxActor|null} The GridActor or FlexboxActor of the
-   * grid/flex container of the give node. Otherwise, returns null.
+   * @return {GridActor|FlexboxActor|null}
+   *         The GridActor or FlexboxActor of the grid/flex container of the given node.
+   *         Otherwise, returns null.
    */
-  getCurrentDisplay(node, type, container) {
+  getCurrentDisplay(node, type) {
     if (isNodeDead(node)) {
       return null;
     }
 
     // Given node can either be a Node or a NodeActor.
     if (node.rawNode) {
       node = node.rawNode;
     }
 
-    const treeWalker = this.walker.getDocumentWalker(node, SHOW_ELEMENT);
-    let currentNode = treeWalker.currentNode;
-    let displayType = this.walker.getNode(currentNode).displayType;
+    const flexType = type === "flex";
+    const gridType = type === "grid";
+    const displayType = this.walker.getNode(node).displayType;
 
     // If the node is an element, check first if it is itself a flex or a grid.
-    if (currentNode.nodeType === currentNode.ELEMENT_NODE) {
+    if (node.nodeType === node.ELEMENT_NODE) {
       if (!displayType) {
         return null;
       }
 
-      if (type == "flex") {
-        // If only the current node is being considered when finding its display, then
-        // return it as only a flex container.
-        if ((displayType == "inline-flex" || displayType == "flex") &&
-              !container) {
-          return new FlexboxActor(this, currentNode);
-
-        // If considering the current node's container, then we are trying to determine
-        // the current node's flex item status.
-        } else if (container) {
-          if (this.isNodeFlexItemInContainer(currentNode, container)) {
-            return new FlexboxActor(this, container);
-          }
-        }
-      } else if (type == "grid" &&
-                 (displayType == "inline-grid" || displayType == "grid")) {
-        return new GridActor(this, currentNode);
+      if (flexType && displayType.includes("flex")) {
+        return new FlexboxActor(this, node);
+      } else if (gridType && displayType.includes("grid")) {
+        return new GridActor(this, node);
       }
     }
 
     // Otherwise, check if this is a flex/grid item or the parent node is a flex/grid
     // container.
     // Note that text nodes that are children of flex/grid containers are wrapped in
     // anonymous containers, so even if their displayType getter returns null we still
     // want to walk up the chain to find their container.
-    while ((currentNode = treeWalker.parentNode())) {
-      if (!currentNode) {
-        break;
-      }
-
-      displayType = this.walker.getNode(currentNode).displayType;
-
-      if (type == "flex" &&
-          (displayType == "inline-flex" || displayType == "flex")) {
-        if (this.isNodeFlexItemInContainer(node, currentNode)) {
-          return new FlexboxActor(this, currentNode);
-        }
-      } else if (type == "grid" &&
-                 (displayType == "inline-grid" || displayType == "grid")) {
-        return new GridActor(this, currentNode);
-      } else if (displayType == "contents") {
-        // Continue walking up the tree since the parent node is a content element.
-        continue;
-      }
-
-      break;
+    const container = findFlexOrGridParentContainerForNode(node, type, this.walker);
+    if (container && flexType) {
+      return new FlexboxActor(this, container);
+    }
+    if (container && gridType) {
+      return new GridActor(this, container);
     }
 
     return null;
   },
 
   /**
-   * Returns the grid container found by iterating on the given selected node. The current
-   * node can be a grid container or grid item. If it is a grid item, returns the parent
-   * grid container. Otherwise, return null if the current or parent node is not a grid
+   * Returns the grid container for a given selected node.
+   * The node itself can be a container, but if not, walk up the DOM to find its
    * container.
+   * Returns null if no container can be found.
    *
    * @param  {Node|NodeActor} node
    *         The node to start iterating at.
-   * @return {GridActor|null} The GridActor of the grid container of the given node.
-   * Otherwise, returns null.
+   * @return {GridActor|null}
+   *         The GridActor of the grid container of the given node. Otherwise, returns
+   *         null.
    */
   getCurrentGrid(node) {
     return this.getCurrentDisplay(node, "grid");
   },
 
   /**
-   * Returns the flex container found by iterating on the given selected node. The current
-   * node can be a flex container or flex item. If it is a flex item, returns the parent
-   * flex container. Otherwise, return null if the current or parent node is not a flex
+   * Returns the flex container for a given selected node.
+   * The node itself can be a container, but if not, walk up the DOM to find its
    * container.
+   * Returns null if no container can be found.
    *
    * @param  {Node|NodeActor} node
    *         The node to start iterating at.
    * @param  {Boolean|null} onlyLookAtParents
-   *         Whether or not to only consider the parent node of the given node.
-   * @return {FlexboxActor|null} The FlexboxActor of the flex container of the given node.
-   * Otherwise, returns null.
+   *         If true, skip the passed node and only start looking at its parent and up.
+   * @return {FlexboxActor|null}
+   *         The FlexboxActor of the flex container of the given node. Otherwise, returns
+   *         null.
    */
   getCurrentFlexbox(node, onlyLookAtParents) {
-    let container = null;
-
     if (onlyLookAtParents) {
-      container = node.rawNode.parentNode;
+      node = node.rawNode.parentNode;
     }
 
-    return this.getCurrentDisplay(node, "flex", container);
+    return this.getCurrentDisplay(node, "flex");
   },
 
   /**
    * Returns an array of GridActor objects for all the grid elements contained in the
    * given root node.
    *
    * @param  {Node|NodeActor} node
    *         The root node for grid elements
@@ -477,47 +449,99 @@ const LayoutActor = ActorClassWithSpec(l
 
     const frames = node.querySelectorAll("iframe, frame");
     for (const frame of frames) {
       gridActors = gridActors.concat(this.getGrids(frame.contentDocument));
     }
 
     return gridActors;
   },
-
-  /**
-   * Returns whether or not the given node is actually considered a flex item by its
-   * container.
-   *
-   * @param  {Node|NodeActor} supposedItem
-   *         The node that might be a flex item of its container.
-   * @param  {Node} container
-   *         The node's container.
-   * @return {Boolean} Whether or not the node we are looking at is a flex item of its
-   * container.
-   */
-  isNodeFlexItemInContainer(supposedItem, container) {
-    const containerDisplayType = this.walker.getNode(container).displayType;
-
-    if (containerDisplayType == "inline-flex" || containerDisplayType == "flex") {
-      const containerFlex = container.getAsFlexContainer();
-
-      for (const line of containerFlex.getLines()) {
-        for (const item of line.getItems()) {
-          if (item.node === supposedItem) {
-            return true;
-          }
-        }
-      }
-    }
-
-    return false;
-  },
 });
 
 function isNodeDead(node) {
   return !node || (node.rawNode && Cu.isDeadWrapper(node.rawNode));
 }
 
+/**
+ * If the provided node is a grid of flex item, then return its parent grid or flex
+ * container.
+ *
+ * @param  {DOMNode} node
+ *         The node that is supposedly a grid or flex item.
+ * @param  {String} type
+ *         The type of container/item to look for: "flex" or "grid".
+ * @param  {WalkerActor} walkerActor
+ *         The current instance of WalkerActor.
+ * @return {DOMNode|null}
+ *         The parent grid or flex container if found, null otherwise.
+ */
+function findFlexOrGridParentContainerForNode(node, type, walker) {
+  const treeWalker = walker.getDocumentWalker(node, SHOW_ELEMENT);
+  let currentNode = treeWalker.currentNode;
+
+  const flexType = type === "flex";
+  const gridType = type === "grid";
+
+  try {
+    while ((currentNode = treeWalker.parentNode())) {
+      if (!currentNode) {
+        break;
+      }
+
+      const displayType = walker.getNode(currentNode).displayType;
+      if (!displayType) {
+        break;
+      }
+
+      if (flexType && displayType.includes("flex")) {
+        if (isNodeAFlexItemInContainer(node, currentNode, walker)) {
+          return currentNode;
+        }
+      } else if (gridType && displayType.includes("grid")) {
+        return currentNode;
+      } else if (displayType === "contents") {
+        // Continue walking up the tree since the parent node is a content element.
+        continue;
+      }
+
+      break;
+    }
+  } catch (e) {
+    // Getting the parentNode can fail when the supplied node is in shadow DOM.
+  }
+
+  return null;
+}
+
+/**
+ * Returns whether or not the given node is actually considered a flex item by its
+ * container.
+ *
+ * @param  {Node|NodeActor} supposedItem
+ *         The node that might be a flex item of its container.
+ * @param  {Node} container
+ *         The node's container.
+ * @return {Boolean}
+ *         Whether or not the node we are looking at is a flex item of its container.
+ */
+function isNodeAFlexItemInContainer(supposedItem, container, walker) {
+  const containerDisplayType = walker.getNode(container).displayType;
+
+  if (containerDisplayType.includes("flex")) {
+    const containerFlex = container.getAsFlexContainer();
+
+    for (const line of containerFlex.getLines()) {
+      for (const item of line.getItems()) {
+        if (item.node === supposedItem) {
+          return true;
+        }
+      }
+    }
+  }
+
+  return false;
+}
+
+exports.findFlexOrGridParentContainerForNode = findFlexOrGridParentContainerForNode;
 exports.FlexboxActor = FlexboxActor;
 exports.FlexItemActor = FlexItemActor;
 exports.GridActor = GridActor;
 exports.LayoutActor = LayoutActor;
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -30,19 +30,17 @@ support-files =
   setBreakpoint-on-line-with-no-offsets.js
   setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
   addons/web-extension/manifest.json
   addons/web-extension-upgrade/manifest.json
   addons/web-extension2/manifest.json
 
 [test_addon_events.js]
 [test_addon_reload.js]
-skip-if = verify # verify mode causes AddonRepository shutdown errors
 [test_addons_actor.js]
-skip-if = (verify && !debug && (os == 'win'))
 [test_animation_name.js]
 [test_animation_type.js]
 [test_actor-registry-actor.js]
 [test_nesting-01.js]
 [test_nesting-02.js]
 [test_nesting-03.js]
 [test_forwardingprefix.js]
 [test_getyoungestframe.js]
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "BrowsingContext.h"
+#include "mozilla/dom/BrowsingContext.h"
 
 #include "mozilla/dom/ChromeBrowsingContext.h"
 #include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Logging.h"
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef BrowsingContext_h
-#define BrowsingContext_h
+#ifndef mozilla_dom_BrowsingContext_h
+#define mozilla_dom_BrowsingContext_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsString.h"
@@ -107,18 +107,18 @@ class BrowsingContext : public nsWrapper
   BrowsingContext* GetOpener() { return mOpener; }
 
   void SetOpener(BrowsingContext* aOpener);
 
   static void GetRootBrowsingContexts(
       nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
 
   nsISupports* GetParentObject() const;
-  virtual JSObject* WrapObject(JSContext* aCx,
-                               JS::Handle<JSObject*> aGivenProto) override;
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
 
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContext)
 
   using Children = AutoCleanLinkedList<RefPtr<BrowsingContext>>;
 
  protected:
@@ -138,9 +138,10 @@ class BrowsingContext : public nsWrapper
   Children mChildren;
   WeakPtr<BrowsingContext> mOpener;
   nsCOMPtr<nsIDocShell> mDocShell;
   nsString mName;
 };
 
 }  // namespace dom
 }  // namespace mozilla
-#endif
+
+#endif  // !defined(mozilla_dom_BrowsingContext_h)
--- a/docshell/base/ChromeBrowsingContext.cpp
+++ b/docshell/base/ChromeBrowsingContext.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "ChromeBrowsingContext.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
 
 namespace mozilla {
 namespace dom {
 
 ChromeBrowsingContext::ChromeBrowsingContext(BrowsingContext* aParent,
                                              BrowsingContext* aOpener,
                                              const nsAString& aName,
                                              uint64_t aBrowsingContextId,
@@ -51,10 +52,58 @@ ChromeBrowsingContext::ChromeBrowsingCon
 }
 
 /* static */ const ChromeBrowsingContext* ChromeBrowsingContext::Cast(
     const BrowsingContext* aContext) {
   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
   return static_cast<const ChromeBrowsingContext*>(aContext);
 }
 
+void ChromeBrowsingContext::GetWindowGlobals(
+    nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
+  aWindows.SetCapacity(mWindowGlobals.Count());
+  for (auto iter = mWindowGlobals.Iter(); !iter.Done(); iter.Next()) {
+    aWindows.AppendElement(iter.Get()->GetKey());
+  }
+}
+
+void ChromeBrowsingContext::RegisterWindowGlobal(WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(!mWindowGlobals.Contains(aGlobal), "Global already registered!");
+  mWindowGlobals.PutEntry(aGlobal);
+}
+
+void ChromeBrowsingContext::UnregisterWindowGlobal(
+    WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(mWindowGlobals.Contains(aGlobal), "Global not registered!");
+  mWindowGlobals.RemoveEntry(aGlobal);
+
+  // Our current window global should be in our mWindowGlobals set. If it's not
+  // anymore, clear that reference.
+  if (aGlobal == mCurrentWindowGlobal) {
+    mCurrentWindowGlobal = nullptr;
+  }
+}
+
+void ChromeBrowsingContext::SetCurrentWindowGlobal(
+    WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(mWindowGlobals.Contains(aGlobal), "Global not registered!");
+
+  // TODO: This should probably assert that the processes match.
+  mCurrentWindowGlobal = aGlobal;
+}
+
+JSObject* ChromeBrowsingContext::WrapObject(JSContext* aCx,
+                                            JS::Handle<JSObject*> aGivenProto) {
+  return ChromeBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ChromeBrowsingContext::Traverse(nsCycleCollectionTraversalCallback& cb) {
+  ChromeBrowsingContext* tmp = this;
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobals);
+}
+
+void ChromeBrowsingContext::Unlink() {
+  ChromeBrowsingContext* tmp = this;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobals);
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/base/ChromeBrowsingContext.h
+++ b/docshell/base/ChromeBrowsingContext.h
@@ -1,53 +1,77 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef ChromeBrowsingContext_h
-#define ChromeBrowsingContext_h
+#ifndef mozilla_dom_ChromeBrowsingContext_h
+#define mozilla_dom_ChromeBrowsingContext_h
 
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/RefPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
 
 class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
+class WindowGlobalParent;
+
 // ChromeBrowsingContext is a BrowsingContext living in the parent
 // process, with whatever extra data that a BrowsingContext in the
 // parent needs.
 class ChromeBrowsingContext final : public BrowsingContext {
  public:
   static void CleanupContexts(uint64_t aProcessId);
   static already_AddRefed<ChromeBrowsingContext> Get(uint64_t aId);
   static ChromeBrowsingContext* Cast(BrowsingContext* aContext);
   static const ChromeBrowsingContext* Cast(const BrowsingContext* aContext);
 
   bool IsOwnedByProcess(uint64_t aProcessId) const {
     return mProcessId == aProcessId;
   }
 
+  void GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& aWindows);
+
+  // Called by WindowGlobalParent to register and unregister window globals.
+  void RegisterWindowGlobal(WindowGlobalParent* aGlobal);
+  void UnregisterWindowGlobal(WindowGlobalParent* aGlobal);
+
+  // The current active WindowGlobal.
+  WindowGlobalParent* GetCurrentWindowGlobal() const {
+    return mCurrentWindowGlobal;
+  }
+  void SetCurrentWindowGlobal(WindowGlobalParent* aGlobal);
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
  protected:
-  void Traverse(nsCycleCollectionTraversalCallback& cb) {}
-  void Unlink() {}
+  void Traverse(nsCycleCollectionTraversalCallback& cb);
+  void Unlink();
 
   using Type = BrowsingContext::Type;
   ChromeBrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
                         const nsAString& aName, uint64_t aBrowsingContextId,
                         uint64_t aProcessId, Type aType = Type::Chrome);
 
  private:
   friend class BrowsingContext;
 
   // XXX(farre): Store a ContentParent pointer here rather than mProcessId?
   // Indicates which process owns the docshell.
   uint64_t mProcessId;
+
+  // All live window globals within this browsing context.
+  nsTHashtable<nsRefPtrHashKey<WindowGlobalParent>> mWindowGlobals;
+  RefPtr<WindowGlobalParent> mCurrentWindowGlobal;
 };
 
 }  // namespace dom
 }  // namespace mozilla
-#endif
+
+#endif  // !defined(mozilla_dom_ChromeBrowsingContext_h)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3188,16 +3188,32 @@ nsDocShell::SetTreeOwner(nsIDocShellTree
     nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
     NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
 
     if (child->ItemType() == mItemType) {
       child->SetTreeOwner(aTreeOwner);
     }
   }
 
+  // If we're in the content process and have had a TreeOwner set on us, extract
+  // our TabChild actor. If we've already had our TabChild set, assert that it
+  // hasn't changed.
+  if (mTreeOwner && XRE_IsContentProcess()) {
+    nsCOMPtr<nsITabChild> newTabChild = do_GetInterface(mTreeOwner);
+    MOZ_ASSERT(newTabChild, "No TabChild actor for tree owner in Content!");
+
+    if (mTabChild) {
+      nsCOMPtr<nsITabChild> oldTabChild = do_QueryReferent(mTabChild);
+      MOZ_RELEASE_ASSERT(oldTabChild == newTabChild,
+                         "Cannot cahnge TabChild during nsDocShell lifetime!");
+    } else {
+      mTabChild = do_GetWeakReference(newTabChild);
+    }
+  }
+
   // Our tree owner has changed. Recompute scriptability.
   //
   // Note that this is near-redundant with the recomputation in
   // SetDocLoaderParent(), but not so for the root DocShell, where the call to
   // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
   // and we never set another parent. Given that this is neither expensive nor
   // performance-critical, let's be safe and unconditionally recompute this
   // state whenever dependent state changes.
@@ -5040,16 +5056,18 @@ nsDocShell::Destroy() {
     mSessionHistory->EvictLocalContentViewers();
     mSessionHistory = nullptr;
   }
 
   mBrowsingContext->Detach();
 
   SetTreeOwner(nullptr);
 
+  mTabChild = nullptr;
+
   mOnePermittedSandboxedNavigator = nullptr;
 
   // required to break ref cycle
   mSecurityUI = nullptr;
 
   // Cancel any timers that were set for this docshell; this is needed
   // to break the cycle between us and the timers.
   CancelRefreshURITimers();
@@ -6625,18 +6643,17 @@ NS_IMETHODIMP
 nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                            nsresult aStatus, const char16_t* aMessage) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
-                             uint32_t aOldState, uint32_t aState,
-                             const nsAString& aContentBlockingLogJSON) {
+                             uint32_t aState) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
                                  nsIChannel* aChannel, nsresult aStatus) {
   if (!aChannel) {
     return NS_ERROR_NULL_POINTER;
@@ -13317,18 +13334,17 @@ nsDocShell::GetEditingSession(nsIEditing
 
 NS_IMETHODIMP
 nsDocShell::GetScriptableTabChild(nsITabChild** aTabChild) {
   *aTabChild = GetTabChild().take();
   return *aTabChild ? NS_OK : NS_ERROR_FAILURE;
 }
 
 already_AddRefed<nsITabChild> nsDocShell::GetTabChild() {
-  nsCOMPtr<nsIDocShellTreeOwner> owner(mTreeOwner);
-  nsCOMPtr<nsITabChild> tc = do_GetInterface(owner);
+  nsCOMPtr<nsITabChild> tc = do_QueryReferent(mTabChild);
   return tc.forget();
 }
 
 nsICommandManager* nsDocShell::GetCommandManager() {
   NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
   return mCommandManager;
 }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -916,16 +916,19 @@ class nsDocShell final : public nsDocLoa
   uint64_t mContentWindowID;
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIWidget> mParentWidget;
   RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
   nsCOMPtr<nsIWebBrowserFind> mFind;
   nsCOMPtr<nsICommandManager> mCommandManager;
   RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
 
+  // Weak reference to our TabChild actor.
+  nsWeakPtr mTabChild;
+
   // Dimensions of the docshell
   nsIntRect mBounds;
 
   /**
    * Content-Type Hint of the most-recently initiated load. Used for
    * session history entries.
    */
   nsCString mContentTypeHint;
--- a/docshell/base/nsDocShellTreeOwner.cpp
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -689,19 +689,18 @@ nsDocShellTreeOwner::OnLocationChange(ns
 NS_IMETHODIMP
 nsDocShellTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress,
                                     nsIRequest* aRequest, nsresult aStatus,
                                     const char16_t* aMessage) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShellTreeOwner::OnSecurityChange(
-    nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aOldState,
-    uint32_t aState, const nsAString& aContentBlockingLogJSON) {
+nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress,
+                                      nsIRequest* aRequest, uint32_t aState) {
   return NS_OK;
 }
 
 //*****************************************************************************
 // nsDocShellTreeOwner: Accessors
 //*****************************************************************************
 
 void nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -230,16 +230,17 @@
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabGroup.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsPluginHost.h"
 #include "nsIBrowser.h"
 #include "mozilla/HangAnnotations.h"
 #include "mozilla/Encoding.h"
 #include "nsXULElement.h"
+#include "mozilla/RecordReplay.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
@@ -10091,16 +10092,22 @@ static const uint64_t kIdBits = 64 - kId
 
   MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
   uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
 
   uint64_t id = aId;
   MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
   uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
 
+  // Set the high bit for middleman processes so it doesn't conflict with the
+  // content process's generated IDs.
+  if (recordreplay::IsMiddleman()) {
+    bits |= uint64_t(1) << (kIdBits - 1);
+  }
+
   return (processBits << kIdBits) | bits;
 }
 
 // Tab ID is composed in a similar manner of Window ID.
 static uint64_t gNextTabId = 0;
 
 /* static */ uint64_t nsContentUtils::GenerateTabId() {
   return GenerateProcessSpecificId(++gNextTabId);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -555,17 +555,17 @@ nsDOMWindowUtils::SetResolution(float aR
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution) {
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
-  presShell->SetResolutionAndScaleTo(aResolution);
+  presShell->SetResolutionAndScaleTo(aResolution, nsGkAtoms::other);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SetRestoreResolution(float aResolution,
                                        uint32_t aDisplayWidth,
                                        uint32_t aDisplayHeight) {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -266,16 +266,18 @@
 #include "nsXULCommandDispatcher.h"
 #include "nsXULPopupManager.h"
 #include "nsIDocShellTreeOwner.h"
 #endif
 #include "nsIPresShellInlines.h"
 
 #include "mozilla/DocLoadingTimelineMarker.h"
 
+#include "mozilla/dom/WindowGlobalChild.h"
+
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
 
 #include "nsIURIClassifier.h"
 #include "nsIURIMutator.h"
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/PendingFullscreenEvent.h"
@@ -2892,16 +2894,23 @@ void nsIDocument::SetDocumentURI(nsIURI*
   // document's original URI.
   if (!mOriginalURI) mOriginalURI = mDocumentURI;
 
   // If changing the document's URI changed the base URI of the document, we
   // need to refresh the hrefs of all the links on the page.
   if (!equalBases) {
     RefreshLinkHrefs();
   }
+
+  // Tell our WindowGlobalParent that the document's URI has been changed.
+  nsPIDOMWindowInner* inner = GetInnerWindow();
+  WindowGlobalChild* wgc = inner ? inner->GetWindowGlobalChild() : nullptr;
+  if (wgc) {
+    Unused << wgc->SendUpdateDocumentURI(mDocumentURI);
+  }
 }
 
 static void GetFormattedTimeString(PRTime aTime,
                                    nsAString& aFormattedTimeString) {
   PRExplodedTime prtime;
   PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
   // "MM/DD/YYYY hh:mm:ss"
   char formatedTime[24];
@@ -10174,17 +10183,17 @@ void nsIDocument::CleanupFullscreenState
     }
   }
   mFullscreenStack.Clear();
   mFullscreenRoot = nullptr;
 
   // Restore the zoom level that was in place prior to entering fullscreen.
   if (nsIPresShell* shell = GetShell()) {
     if (shell->GetMobileViewportManager()) {
-      shell->SetResolutionAndScaleTo(mSavedResolution);
+      shell->SetResolutionAndScaleTo(mSavedResolution, nsGkAtoms::restore);
     }
   }
 
   UpdateViewportScrollbarOverrideForFullscreen(this);
 }
 
 bool nsIDocument::FullscreenStackPush(Element* aElement) {
   NS_ASSERTION(aElement, "Must pass non-null to FullscreenStackPush()");
@@ -10571,17 +10580,18 @@ bool nsIDocument::ApplyFullscreen(Unique
     // fixed elements are sized to the layout viewport).
     // This also ensures that things like video controls aren't zoomed in
     // when in fullscreen mode.
     if (nsIPresShell* shell = child->GetShell()) {
       if (RefPtr<MobileViewportManager> manager =
               shell->GetMobileViewportManager()) {
         // Save the previous resolution so it can be restored.
         child->mSavedResolution = shell->GetResolution();
-        shell->SetResolutionAndScaleTo(manager->ComputeIntrinsicResolution());
+        shell->SetResolutionAndScaleTo(manager->ComputeIntrinsicResolution(),
+                                       nsGkAtoms::other);
       }
     }
 
     NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
                  "Fullscreen root should be set!");
     if (child == fullScreenRootDoc) {
       break;
     }
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -95,16 +95,17 @@
 #include "mozilla/dom/CustomEvent.h"
 
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/WebBrowserPersistLocalDocument.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/ParentSHistory.h"
 #include "mozilla/dom/ChildSHistory.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
 
 #include "mozilla/dom/HTMLBodyElement.h"
 
 #include "mozilla/ContentPrincipal.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PPluginWidgetParent.h"
 #include "../plugins/ipc/PluginWidgetParent.h"
@@ -3017,16 +3018,26 @@ already_AddRefed<nsILoadContext> nsFrame
   if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
     loadContext = mRemoteBrowser->GetLoadContext();
   } else {
     loadContext = do_GetInterface(GetDocShell(IgnoreErrors()));
   }
   return loadContext.forget();
 }
 
+already_AddRefed<BrowsingContext> nsFrameLoader::GetBrowsingContext() {
+  RefPtr<BrowsingContext> browsingContext;
+  if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
+    browsingContext = mRemoteBrowser->GetBrowsingContext();
+  } else if (GetDocShell(IgnoreErrors())) {
+    browsingContext = nsDocShell::Cast(mDocShell)->GetBrowsingContext();
+  }
+  return browsingContext.forget();
+}
+
 void nsFrameLoader::InitializeBrowserAPI() {
   if (!OwnerIsMozBrowserFrame()) {
     return;
   }
   if (!IsRemoteFrame()) {
     nsresult rv = EnsureMessageManager();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -43,16 +43,17 @@ class nsIPrintSettings;
 class nsIWebBrowserPersistDocumentReceiver;
 class nsIWebProgressListener;
 
 namespace mozilla {
 
 class OriginAttributes;
 
 namespace dom {
+class BrowsingContext;
 class ChromeMessageSender;
 class ContentParent;
 class InProcessTabChildMessageManager;
 class MessageSender;
 class PBrowserParent;
 class ProcessMessageManager;
 class Promise;
 class TabParent;
@@ -118,16 +119,18 @@ class nsFrameLoader final : public nsStu
   // WebIDL methods
 
   nsIDocShell* GetDocShell(mozilla::ErrorResult& aRv);
 
   already_AddRefed<nsITabParent> GetTabParent();
 
   already_AddRefed<nsILoadContext> LoadContext();
 
+  already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext();
+
   /**
    * Start loading the frame. This method figures out what to load
    * from the owner content in the frame loader.
    */
   void LoadFrame(bool aOriginalSrc);
 
   /**
    * Loads the specified URI in this frame. Behaves identically to loadFrame,
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -255,16 +255,18 @@
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ClientState.h"
 
+#include "mozilla/dom/WindowGlobalChild.h"
+
 // Apple system headers seem to have a check() macro.  <sigh>
 #ifdef check
 class nsIScriptTimeoutHandler;
 #undef check
 #endif  // check
 #include "AccessCheck.h"
 
 #ifdef ANDROID
@@ -1261,16 +1263,21 @@ void nsGlobalWindowInner::FreeInnerObjec
 
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     if (mChromeFields.mMessageManager) {
       mChromeFields.mMessageManager->Disconnect();
     }
   }
 
+  if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
+    mWindowGlobalChild->Send__delete__(mWindowGlobalChild);
+  }
+  mWindowGlobalChild = nullptr;
+
   mIntlUtils = nullptr;
 }
 
 //*****************************************************************************
 // nsGlobalWindowInner::nsISupports
 //*****************************************************************************
 
 // QueryInterface implementation for nsGlobalWindowInner
@@ -1630,16 +1637,25 @@ void nsGlobalWindowInner::InnerSetNewDoc
   mPerformance = nullptr;
 
   // This must be called after nullifying the internal objects because here we
   // could recreate them, calling the getter methods, and store them into the JS
   // slots. If we nullify them after, the slot values and the objects will be
   // out of sync.
   ClearDocumentDependentSlots(aCx);
 
+  // FIXME: Currently, devtools can crete a fallback webextension window global
+  // in the content process which does not have a corresponding TabChild actor.
+  // This means we have no actor to be our parent. (Bug 1498293)
+  MOZ_DIAGNOSTIC_ASSERT(!mWindowGlobalChild,
+                        "Shouldn't have created WindowGlobalChild yet!");
+  if (XRE_IsParentProcess() || mTabChild) {
+    mWindowGlobalChild = WindowGlobalChild::Create(this);
+  }
+
 #ifdef DEBUG
   mLastOpenedURI = aDocument->GetDocumentURI();
 #endif
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_TEXT_EVENT_LISTENERS,
                         mMayHaveTextEventListenerInDefaultGroup ? 1 : 0);
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -63,16 +63,17 @@
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
 #include "nsVariant.h"
 #include "nsPrintfCString.h"
 #include "mozilla/intl/LocaleService.h"
 #include "WindowDestroyedEvent.h"
 #include "nsDocShellLoadState.h"
+#include "mozilla/dom/WindowGlobalChild.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"
 #include "js/Wrapper.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsReadableUtils.h"
 #include "nsJSEnvironment.h"
@@ -1957,16 +1958,20 @@ nsresult nsGlobalWindowOuter::SetNewDocu
   }
 
   // Handle any document.open() logic after we setup the new inner window
   // so that any bound DETH objects can see the top window, document, etc.
   if (handleDocumentOpen) {
     newInnerWindow->MigrateStateForDocumentOpen(currentInner);
   }
 
+  // Tell the WindowGlobalParent that it should become the current window global
+  // for our BrowsingContext if it isn't already.
+  mInnerWindow->GetWindowGlobalChild()->SendBecomeCurrentWindowGlobal();
+
   // We no longer need the old inner window.  Start its destruction if
   // its not being reused and clear our reference.
   if (doomCurrentInner) {
     currentInner->FreeInnerObjects(handleDocumentOpen);
   }
   currentInner = nullptr;
 
   // Ask the JS engine to assert that it's valid to access our DocGroup whenever
@@ -2221,19 +2226,26 @@ void nsGlobalWindowOuter::SetOpenerWindo
                "SetOpenerWindow!");
   NS_ASSERTION(aOpener || !aOriginalOpener,
                "Shouldn't set mHadOriginalOpener if aOpener is null");
 
   mOpener = opener.forget();
   NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");
 
   if (mDocShell) {
-    MOZ_DIAGNOSTIC_ASSERT(!aOriginalOpener || !aOpener ||
-                          aOpener->GetBrowsingContext() ==
-                              GetBrowsingContext()->GetOpener());
+    MOZ_DIAGNOSTIC_ASSERT(
+        !aOriginalOpener || !aOpener ||
+        // TODO(farre): Allowing to set a closed or closing window as
+        // opener is not ideal, since it won't have a docshell and
+        // therefore no browsing context. This means that we're
+        // effectively setting the browsing context opener to null and
+        // the window opener to a closed window. This needs to be
+        // cleaned up, see Bug 1511353.
+        nsGlobalWindowOuter::Cast(aOpener)->IsClosedOrClosing() ||
+        aOpener->GetBrowsingContext() == GetBrowsingContext()->GetOpener());
     // TODO(farre): Here we really wish to only consider the case
     // where 'aOriginalOpener'. See bug 1509016.
     GetBrowsingContext()->SetOpener(aOpener ? aOpener->GetBrowsingContext()
                                             : nullptr);
   }
 
   // Check that the js visible opener matches! We currently don't depend on this
   // being true outside of nightly, so we disable the assertion in optimized
@@ -5070,18 +5082,17 @@ void nsGlobalWindowOuter::NotifyContentB
     state &= ~aState;
   }
 
   if (state == oldState) {
     // Avoid dispatching repeated notifications when nothing has changed
     return;
   }
 
-  eventSink->OnSecurityChange(aChannel, oldState, state,
-                              doc->GetContentBlockingLog());
+  eventSink->OnSecurityChange(aChannel, state);
 }
 
 // static
 bool nsGlobalWindowOuter::SameLoadingURI(nsIDocument* aDoc,
                                          nsIChannel* aChannel) {
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
   if (!channelLoadInfo || !docURI) {
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -63,16 +63,17 @@ class Performance;
 class Report;
 class ReportBody;
 class ReportingObserver;
 class Selection;
 class ServiceWorker;
 class ServiceWorkerDescriptor;
 class Timeout;
 class TimeoutManager;
+class WindowGlobalChild;
 class CustomElementRegistry;
 enum class CallerType : uint32_t;
 }  // namespace dom
 }  // namespace mozilla
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
 // all situations. Pushing popup state onto the stack never makes the
@@ -373,16 +374,20 @@ class nsPIDOMWindowInner : public mozIDO
 
   nsIDocument* GetDoc() {
     if (!mDoc) {
       MaybeCreateDoc();
     }
     return mDoc;
   }
 
+  mozilla::dom::WindowGlobalChild* GetWindowGlobalChild() {
+    return mWindowGlobalChild;
+  }
+
   virtual PopupControlState GetPopupControlState() const = 0;
 
   // Determine if the window is suspended or frozen.  Outer windows
   // will forward this call to the inner window for convenience.  If
   // there is no inner window then the outer window is considered
   // suspended and frozen by default.
   virtual bool IsSuspended() const = 0;
   virtual bool IsFrozen() const = 0;
@@ -690,16 +695,22 @@ class nsPIDOMWindowInner : public mozIDO
   // List of Report objects for ReportingObservers.
   nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers;
   nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords;
 
   // This is a list of storage access granted for the current window. These are
   // also set as permissions, but it could happen that we need to access them
   // synchronously in this context, and for this, we need a copy here.
   nsTArray<nsCString> mStorageAccessGranted;
+
+  // The WindowGlobalChild actor for this window.
+  //
+  // This will be non-null during the full lifetime of the window, initialized
+  // during SetNewDocument, and cleared during FreeInnerObjects.
+  RefPtr<mozilla::dom::WindowGlobalChild> mWindowGlobalChild;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
  protected:
   explicit nsPIDOMWindowOuter(uint64_t aWindowID);
 
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -6,27 +6,30 @@ support-files =
   file_bug945152.jar
   file_bug945152_worker.js
   file_bug1008126_worker.js
   file_inline_script.html
   file_inline_script.xhtml
   file_external_script.html
   file_external_script.xhtml
   file_script.js
+  referrer_helper.js
+  referrer_testserver.sjs
   mozbrowser_api_utils.js
   !/image/test/mochitest/shaver.png
 
 [test_anonymousContent_xul_window.xul]
 [test_blockParsing.html]
 [test_blocking_image.html]
 [test_bug715041.xul]
 [test_bug715041_removal.xul]
 [test_bug945152.html]
 [test_bug1008126.html]
 [test_bug1016960.html]
+[test_anchor_target_blank_referrer.html]
 [test_copypaste.xul]
 subsuite = clipboard
 [test_domrequesthelper.xul]
 [test_fragment_sanitization.xul]
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
 [test_mozbrowser_apis_allowed.html]
 [test_navigator_resolve_identity_xrays.xul]
--- a/dom/base/test/referrer_testserver.sjs
+++ b/dom/base/test/referrer_testserver.sjs
@@ -13,26 +13,29 @@ const BASE_URL = BASE_ORIGIN + SJS_PATH 
 const SHARED_KEY = SJS;
 const SAME_ORIGIN = "mochi.test:8888" + SJS_PATH + SJS;
 const CROSS_ORIGIN_URL = "test1.example.com" + SJS_PATH + SJS;
 
 const IMG_BYTES = atob(
   "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
   "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
 
-function createTestUrl(aPolicy, aAction, aName, aType, aSchemeFrom, aSchemeTo, crossOrigin) {
+function createTestUrl(aPolicy, aAction, aName, aType, aSchemeFrom, aSchemeTo,
+                       crossOrigin, referrerPolicyHeader) {
   var schemeTo = aSchemeTo || "http";
   var schemeFrom = aSchemeFrom || "http";
+  var rpHeader = referrerPolicyHeader || "";
   var url = schemeTo + "://";
   url += (crossOrigin ? CROSS_ORIGIN_URL : BASE_URL);
   url +=
          "ACTION=" + aAction + "&" +
          "policy=" + aPolicy + "&" +
          "NAME=" + aName + "&" +
          "type=" + aType + "&" +
+         "RP_HEADER=" + rpHeader + "&" +
          "SCHEME_FROM=" + schemeFrom;
   return url;
   }
 
 // test page using iframe referrer attribute
 // if aParams are set this creates a test where the iframe url is a redirect
 function createIframeTestPageUsingRefferer(aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aParams,
                                            aSchemeFrom, aSchemeTo, aChangingMethod) {
@@ -124,16 +127,41 @@ function createAETestPageUsingRefferer(a
                    ${changeString}
                    document.getElementById("link").click();
                  }.bind(window), false);
                </script>
              </body>
            </html>`;
 }
 
+// test page using anchor target=_blank rel=noopener
+function createTargetBlankRefferer(aMetaPolicy, aName, aSchemeFrom,
+                                   aSchemeTo, aRpHeader) {
+  var metaString = "";
+  if (aMetaPolicy) {
+    metaString = `<head><meta name="referrer" content="${aMetaPolicy}"></head>`;
+  }
+  var elementString = `<a href="${createTestUrl(aMetaPolicy, 'test', aName, 'link', aSchemeFrom, aSchemeTo, aRpHeader)}" target=_blank rel="noopener" id="link">link</a>`;
+
+  return `<!DOCTYPE HTML>
+           <html>
+             ${metaString}
+             <body>
+               ${elementString}
+               <script>
+                 window.addEventListener("load", function() {
+                   let link = document.getElementById("link");
+                   SpecialPowers.wrap(window).parent.postMessage("childLoadReady", "*");
+                   link.click();
+                 }.bind(window), false);
+               </script>
+             </body>
+           </html>`;
+}
+
 // creates test page with img that is a redirect
 function createRedirectImgTestCase(aParams, aAttributePolicy) {
   var metaString = "";
   if (aParams.has("META_POLICY")) {
     metaString = `<meta name="referrer" content="${aParams.get('META_POLICY')}">`;
   }
   aParams.delete("ACTION");
   aParams.append("ACTION", "redirectImg");
@@ -238,18 +266,22 @@ function buildLinkString(aPolicy, aName,
 }
 
 function handleRequest(request, response) {
   var params = new URLSearchParams(request.queryString);
   var action = params.get("ACTION");
   var schemeFrom = params.get("SCHEME_FROM") || "http";
   var schemeTo = params.get("SCHEME_TO") || "http";
   var crossOrigin = params.get("CROSS_ORIGIN") || false;
+  var referrerPolicyHeader = params.get("RP_HEADER") || "";
 
   response.setHeader("Access-Control-Allow-Origin", "*", false);
+  if (referrerPolicyHeader) {
+    response.setHeader("Referrer-Policy", referrerPolicyHeader, false);
+  }
 
   if (action === "resetState") {
     setSharedState(SHARED_KEY, "{}");
     response.write("");
     return;
   }
   if (action === "get-test-results") {
     // ?action=get-result
@@ -367,16 +399,20 @@ function handleRequest(request, response
   if (action === "generate-area-changing-policy-test-set-attribute") {
     response.write(_getAreaPage("setAttribute"));
     return;
   }
   if (action === "generate-area-changing-policy-test-property") {
     response.write(_getAreaPage("property"));
     return;
   }
+  if (action === "generate-anchor-target-blank-policy-test") {
+    response.write(createTargetBlankRefferer(metaPolicy, name, schemeFrom, schemeTo, referrerPolicyHeader));
+    return;
+  }
 
   // iframe
   _getPage = createIframeTestPageUsingRefferer.bind(null, metaPolicy, attributePolicy, newAttributePolicy, name, "",
                                                     schemeFrom, schemeTo);
 
   // aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aChangingMethod
   if (action === "generate-iframe-policy-test") {
     response.write(_getPage());
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_anchor_target_blank_referrer.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test anchor target=_blank rel=noopener referrer header for Bug 1502678</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+  <!--
+  Testing that anchor referrer header are honoured correctly
+  * anchor tag with rel=noopener target=_blank
+  https://bugzilla.mozilla.org/show_bug.cgi?id=1502678
+  -->
+
+  <script type="application/javascript">
+  // We are going to open new tabs with target=_blank and rel=noopener
+  // Listen a new tab is opened then close the new tab, otherwise we will lose
+  // focus for the next tests
+  ChromeUtils.import("resource://gre/modules/Services.jsm");
+  ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+  const gBrowser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
+  window.addEventListener("message", function(event) {
+    if (event.data == "childLoadReady") {
+      BrowserTestUtils.waitForNewTab(gBrowser, null,
+        true).then(function(aNewTab) {
+          BrowserTestUtils.removeTab(aNewTab);
+          advance();
+        });
+    }
+  });
+
+  const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+  const PARAMS = ["RP_HEADER", "META_POLICY", "REL", "SCHEME_FROM", "SCHEME_TO"];
+
+  const testCases = [
+    {ACTION: ["generate-anchor-target-blank-policy-test"],
+      TESTS: [
+        // Referrer policy is set in meta
+        {NAME: 'origin-in-meta-rel-noopener',
+         META_POLICY: 'origin',
+         DESC: "origin in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'origin'},
+        {NAME: 'unsafe-url-in-meta-rel-noopener',
+         META_POLICY: 'unsafe-url',
+         DESC: "unsafe-url in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'full'},
+        {NAME: 'no-referrer-in-meta-rel-noopener',
+         META_POLICY: 'no-referrer',
+         DESC: "no-referrer in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'strict-origin-in-meta-rel-noopener',
+         META_POLICY: 'strict-origin',
+         DESC: "strict-origin in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'strict-origin-when-cross-origin-in-meta-rel-noopener',
+         META_POLICY: 'strict-origin-when-cross-origin',
+         DESC: "strict-origin-when-cross-origin in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'same-origin-in-meta-rel-noopener',
+         META_POLICY: 'same-origin',
+         DESC: "same-origin in meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'no-meta-rel-noopener',
+         META_POLICY: '',
+         DESC: "no meta and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+
+        // Referrer policy is set in Referrer-Policy Header
+        {NAME: 'origin-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'origin',
+         DESC: "origin in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'origin'},
+        {NAME: 'unsafe-url-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'unsafe-url',
+         DESC: "unsafe-url in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'full'},
+        {NAME: 'no-referrer-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'no-referrer',
+         DESC: "no-referrer in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'strict-origin-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'strict-origin',
+         DESC: "strict-origin in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'strict-origin-when-cross-origin-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'strict-origin-when-cross-origin',
+         DESC: "strict-origin-when-cross-origin in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'same-origin-in-referrer-policy-header-rel-noopener',
+         RP_HEADER: 'same-origin',
+         DESC: "same-origin in Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'},
+        {NAME: 'no-referrer-policy-header-rel-noopener',
+         RP_HEADER: '',
+         DESC: "no Referrer-Policy Header and rel=noopener",
+         SCHEME_FROM: 'https',
+         SCHEME_TO: 'http',
+         RESULT: 'none'}
+
+      ]}
+  ];
+  </script>
+  <script type="application/javascript" src="referrer_helper.js"></script>
+</head>
+<body onload="tests.next();">
+  <iframe id="testframe"></iframe>
+</body>
+</html>
+
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1176,18 +1176,17 @@ BrowserElementChild.prototype = {
             } catch (e) {}
 
             sendAsyncMsg('error', { type: 'other' });
             return;
         }
       }
     },
 
-    onSecurityChange: function(webProgress, request, oldState, state,
-                               contentBlockingLogJSON) {
+    onSecurityChange: function(webProgress, request, state) {
       if (webProgress != docShell) {
         return;
       }
 
       var securityStateDesc;
       if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
         securityStateDesc = 'secure';
       }
--- a/dom/chrome-webidl/BrowsingContext.webidl
+++ b/dom/chrome-webidl/BrowsingContext.webidl
@@ -12,8 +12,15 @@ interface BrowsingContext {
   sequence<BrowsingContext> getChildren();
 
   readonly attribute nsIDocShell? docShell;
 
   readonly attribute unsigned long long id;
 
   readonly attribute BrowsingContext? opener;
 };
+
+[Exposed=Window, ChromeOnly]
+interface ChromeBrowsingContext : BrowsingContext {
+  sequence<WindowGlobalParent> getWindowGlobals();
+
+  readonly attribute WindowGlobalParent? currentWindowGlobal;
+};
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/WindowGlobalActors.webidl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface Principal;
+interface URI;
+interface nsIDocShell;
+
+[Exposed=Window, ChromeOnly]
+interface WindowGlobalParent {
+  readonly attribute boolean isClosed;
+  readonly attribute boolean isInProcess;
+  readonly attribute ChromeBrowsingContext browsingContext;
+
+  readonly attribute boolean isCurrentGlobal;
+
+  readonly attribute unsigned long long innerWindowId;
+  readonly attribute unsigned long long outerWindowId;
+
+  readonly attribute FrameLoader? rootFrameLoader; // Embedded (browser) only
+
+  readonly attribute WindowGlobalChild? childActor; // in-process only
+
+  // Information about the currently loaded document.
+  readonly attribute Principal documentPrincipal;
+  readonly attribute URI? documentURI;
+
+  static WindowGlobalParent? getByInnerWindowId(unsigned long long innerWindowId);
+};
+
+[Exposed=Window, ChromeOnly]
+interface WindowGlobalChild {
+  readonly attribute boolean isClosed;
+  readonly attribute boolean isInProcess;
+  readonly attribute BrowsingContext browsingContext;
+
+  readonly attribute boolean isCurrentGlobal;
+
+  readonly attribute unsigned long long innerWindowId;
+  readonly attribute unsigned long long outerWindowId;
+
+  readonly attribute WindowGlobalParent? parentActor; // in-process only
+
+  static WindowGlobalChild? getByInnerWindowId(unsigned long long innerWIndowId);
+};
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -49,16 +49,17 @@ WEBIDL_FILES = [
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'TelemetryStopwatch.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
+    'WindowGlobalActors.webidl',
     'XULFrameElement.webidl',
     'XULMenuElement.webidl',
     'XULScrollElement.webidl',
     'XULTextElement.webidl'
 ]
 
 if CONFIG['MOZ_PLACES']:
     WEBIDL_FILES += [
--- a/dom/clients/manager/ClientNavigateOpChild.cpp
+++ b/dom/clients/manager/ClientNavigateOpChild.cpp
@@ -116,18 +116,17 @@ class NavigateLoadListener final : publi
   OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                  nsresult aStatus, const char16_t* aMessage) override {
     MOZ_CRASH("Unexpected notification.");
     return NS_OK;
   }
 
   NS_IMETHOD
   OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
-                   uint32_t aOldState, uint32_t aState,
-                   const nsAString& aContentBlockingLogJSON) override {
+                   uint32_t aState) override {
     MOZ_CRASH("Unexpected notification.");
     return NS_OK;
   }
 
   NS_DECL_ISUPPORTS
 };
 
 NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
--- a/dom/clients/manager/ClientOpenWindowUtils.cpp
+++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
@@ -112,18 +112,17 @@ class WebProgressListener final : public
   OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                  nsresult aStatus, const char16_t* aMessage) override {
     MOZ_ASSERT(false, "Unexpected notification.");
     return NS_OK;
   }
 
   NS_IMETHOD
   OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
-                   uint32_t aOldState, uint32_t aState,
-                   const nsAString& aContentBlockingLogJSON) override {
+                   uint32_t aState) override {
     MOZ_ASSERT(false, "Unexpected notification.");
     return NS_OK;
   }
 
  private:
   ~WebProgressListener() {
     if (mPromise) {
       mPromise->Reject(NS_ERROR_ABORT, __func__);
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/mochitest-test",
+  ]
+};
--- a/dom/filesystem/compat/tests/script_entries.js
+++ b/dom/filesystem/compat/tests/script_entries.js
@@ -1,47 +1,42 @@
+/* eslint-env mozilla/frame-script */
 Cu.importGlobalProperties(["File", "Directory"]);
-
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 var tmpFile, tmpDir;
 
-addMessageListener("entries.open", function (e) {
-  tmpFile = Cc["@mozilla.org/file/directory_service;1"]
-              .getService(Ci.nsIDirectoryService)
-              .QueryInterface(Ci.nsIProperties)
-              .get('TmpD', Ci.nsIFile)
-  tmpFile.append('file.txt');
+addMessageListener("entries.open", function(e) {
+  tmpFile = Services.dirsvc.QueryInterface(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+  tmpFile.append("file.txt");
   tmpFile.createUnique(Ci.nsIFile.FILE_TYPE, 0o600);
 
-  tmpDir = Cc["@mozilla.org/file/directory_service;1"]
-              .getService(Ci.nsIDirectoryService)
-              .QueryInterface(Ci.nsIProperties)
-              .get('TmpD', Ci.nsIFile)
+  tmpDir = Services.dirsvc.QueryInterface(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
 
-  tmpDir.append('dir-test');
+  tmpDir.append("dir-test");
   tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
 
   var file1 = tmpDir.clone();
-  file1.append('foo.txt');
+  file1.append("foo.txt");
   file1.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   var dir1 = tmpDir.clone();
-  dir1.append('subdir');
+  dir1.append("subdir");
   dir1.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
 
   var file2 = dir1.clone();
-  file2.append('bar..txt'); // Note the double ..
+  file2.append("bar..txt"); // Note the double ..
   file2.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   var dir2 = dir1.clone();
-  dir2.append('subsubdir');
+  dir2.append("subsubdir");
   dir2.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
 
   File.createFromNsIFile(tmpFile).then(function(file) {
     sendAsyncMessage("entries.opened", {
-      data: [ new Directory(tmpDir.path), file ]
+      data: [ new Directory(tmpDir.path), file ],
     });
   });
 });
 
 addMessageListener("entries.delete", function(e) {
   tmpFile.remove(true);
   tmpDir.remove(true);
   sendAsyncMessage("entries.deleted");
--- a/dom/filesystem/compat/tests/test_basic.html
+++ b/dom/filesystem/compat/tests/test_basic.html
@@ -4,43 +4,42 @@
   <title>Test for Blink FileSystem API - subset</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
 <input id="entries" type="file"></input>
 <script type="application/javascript">
-
 var fileEntry;
 var directoryEntry;
 var script;
 
 function setup_tests() {
   SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
                                      ["dom.filesystem.pathcheck.disabled", true],
                                      ["dom.webkitBlink.filesystem.enabled", true]]}, next);
 }
 
 function populate_entries() {
   var url = SimpleTest.getTestFileURL("script_entries.js");
   script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var entries = document.getElementById('entries');
+    var entries = document.getElementById("entries");
     SpecialPowers.wrap(entries).mozSetDndFilesAndDirectories(message.data);
     next();
   }
 
   script.addMessageListener("entries.opened", onOpened);
   script.sendAsyncMessage("entries.open");
 }
 
 function test_entries() {
-  var entries = document.getElementById('entries');
+  var entries = document.getElementById("entries");
   ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
   is(entries.webkitEntries.length, 2, "HTMLInputElement.webkitEntries.length == 2");
   is(entries.files.length, 1, "HTMLInputElement.files is still populated");
 
   for (var i = 0; i < entries.webkitEntries.length; ++i) {
     if (entries.webkitEntries[i].isFile) {
       ok(!fileEntry, "We just want 1 fileEntry");
       fileEntry = entries.webkitEntries[i];
@@ -98,19 +97,19 @@ function test_directoryEntry_createReade
     is(a.length, 2, "reader.readyEntries returns 2 elements.");
 
     for (var i = 0; i < 2; ++i) {
       ok(a[i].name == "subdir" || a[i].name == "foo.txt", "Correct names");
       is(a[i].fullPath, directoryEntry.fullPath + "/" + a[i].name, "FullPath is correct");
     }
 
     // Called twice:
-    reader.readEntries(function(a) {
-      ok(Array.isArray(a), "We want an array.");
-      is(a.length, 0, "reader.readyEntries returns 0 elements.");
+    reader.readEntries(function(a1) {
+      ok(Array.isArray(a1), "We want an array.");
+      is(a1.length, 0, "reader.readyEntries returns 0 elements.");
       next();
     }, function() {
       ok(false, "Something when wrong!");
     });
 
   }, function() {
     ok(false, "Something when wrong!");
   });
@@ -250,17 +249,17 @@ function test_filesystem() {
 
   var fs = fileEntry.filesystem;
   ok(fs.name, "FileSystem.name exists.");
   ok(fs.root, "FileSystem has a root.");
 
   is(fs.root.name, "", "FileSystem.root.name must be an empty string.");
   is(fs.root.fullPath, "/", "FileSystem.root.fullPath must be '/'");
 
-  reader = fs.root.createReader();
+  var reader = fs.root.createReader();
   reader.readEntries(function(a) {
     ok(Array.isArray(a), "We want an array.");
     is(a.length, 2, "reader.readyEntries returns 2 elements.");
     next();
   }, function() {
     ok(false, "Something when wrong!");
   });
 }
@@ -394,17 +393,17 @@ function cleanUpTestingFiles() {
 
   script.sendAsyncMessage("entries.delete");
 }
 
 function test_getParent(entry, parentEntry, nested) {
   entry.getParent(function(e) {
     ok(e, "We have a parent Entry.");
     if (!nested) {
-      is (e, parentEntry, "Parent entry matches");
+      is(e, parentEntry, "Parent entry matches");
       next();
     } else {
       test_getParent(e, parentEntry, false);
     }
   }, function(e) {
     ok(false, "This should not happen.");
   });
 }
--- a/dom/filesystem/compat/tests/test_formSubmission.html
+++ b/dom/filesystem/compat/tests/test_formSubmission.html
@@ -10,53 +10,52 @@
 
 <iframe name="target_iframe" id="target_iframe"></iframe>
 
 <form action="../../../html/test/form_submit_server.sjs" target="target_iframe" id="form"
       method="POST" enctype="multipart/form-data">
 </form>
 
 <script class="testbody" type="text/javascript">
-
 var form;
 var iframe;
 var input;
 var script;
 var xhr;
 
 function setup_tests() {
   form = document.getElementById("form");
 
   iframe = document.getElementById("target_iframe");
   iframe.onload = function() {
     info("Frame loaded!");
     next();
-  }
+  };
 
   SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
                                      ["dom.webkitBlink.dirPicker.enabled", true],
                                      ["dom.filesystem.pathcheck.disabled", true],
                                      ["dom.webkitBlink.filesystem.enabled", true]]}, next);
 }
 
 function populate_entries(webkitDirectory) {
   var url = SimpleTest.getTestFileURL("script_entries.js");
   script = SpecialPowers.loadChromeScript(url);
 
   if (input) {
     form.removeChild(input);
   }
 
-  input = document.createElement('input');
-  input.setAttribute('id', 'input');
-  input.setAttribute('type', 'file');
-  input.setAttribute('name', 'input');
+  input = document.createElement("input");
+  input.setAttribute("id", "input");
+  input.setAttribute("type", "file");
+  input.setAttribute("name", "input");
 
   if (webkitDirectory) {
-    input.setAttribute('webkitdirectory', 'true');
+    input.setAttribute("webkitdirectory", "true");
   }
 
   form.appendChild(input);
 
   function onOpened(message) {
     input.addEventListener("change", function() {
       next();
     }, {once: true});
--- a/dom/filesystem/compat/tests/test_no_dnd.html
+++ b/dom/filesystem/compat/tests/test_no_dnd.html
@@ -3,31 +3,30 @@
 <head>
   <title>Test for Blink FileSystem API - no DND == no webkitEntries</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
 <script type="application/javascript">
-
 var fileEntry;
 var directoryEntry;
 var script;
 var entries;
 
 function setup_tests() {
   SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
                                      ["dom.filesystem.pathcheck.disabled", true],
                                      ["dom.webkitBlink.filesystem.enabled", true]]}, next);
 }
 
 function populate_entries() {
-  entries = document.createElement('input');
-  entries.setAttribute('type', 'file');
+  entries = document.createElement("input");
+  entries.setAttribute("type", "file");
   document.body.appendChild(entries);
 
   var url = SimpleTest.getTestFileURL("script_entries.js");
   script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     for (var i = 0 ; i < message.data.length; ++i) {
       if (message.data[i] instanceof File) {
--- a/dom/filesystem/tests/filesystem_commons.js
+++ b/dom/filesystem/tests/filesystem_commons.js
@@ -1,10 +1,10 @@
 function createPath(parentDir, dirOrFile) {
-  return parentDir.path + (parentDir.path == '/' ? '' : '/') + dirOrFile.name;
+  return parentDir.path + (parentDir.path == "/" ? "" : "/") + dirOrFile.name;
 }
 
 function createRelativePath(parentDir, dirOrFile) {
   let path = createPath(parentDir, dirOrFile);
   is(path[0], "/", "The full path should start with '/'");
   return path.substring(1);
 }
 
@@ -13,29 +13,29 @@ function setup_tests(aNext) {
   SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
                                      ["dom.filesystem.pathcheck.disabled", true],
                                      ["dom.webkitBlink.dirPicker.enabled", true]]}, aNext);
 }
 
 function test_basic(aDirectory, aNext) {
   ok(aDirectory, "Directory exists.");
   ok(aDirectory instanceof Directory, "We have a directory.");
-  is(aDirectory.path, '/' + aDirectory.name, "directory.path must be '/'+name");
+  is(aDirectory.path, "/" + aDirectory.name, "directory.path must be '/'+name");
   aNext();
 }
 
 function test_getFilesAndDirectories(aDirectory, aRecursive, aNext) {
   function checkSubDir(dir) {
     return dir.getFilesAndDirectories().then(
       function(data) {
         for (var i = 0; i < data.length; ++i) {
-          ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
+          ok(data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
           if (data[i] instanceof Directory) {
-            isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-            isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
+            isnot(data[i].name, "/", "Subdirectory should be called with the leafname");
+            isnot(data[i].path, "/", "Subdirectory path should be called with the leafname");
             isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
             is(data[i].path, createPath(dir, data[i]), "Subdirectory path should be called parentdir.path + '/' + leafname: " + data[i].path);
           }
 
           if (data[i] instanceof File) {
             is(data[i].webkitRelativePath, createRelativePath(dir, data[i]), "File.webkitRelativePath should be called: parentdir.path + '/' + file.name: " + data[i].webkitRelativePath);
           }
         }
@@ -43,19 +43,19 @@ function test_getFilesAndDirectories(aDi
     );
   }
 
   aDirectory.getFilesAndDirectories().then(
     function(data) {
       ok(data.length, "We should have some data.");
       var promises = [];
       for (var i = 0; i < data.length; ++i) {
-        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories: " + data[i].name);
+        ok(data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories: " + data[i].name);
         if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
+          isnot(data[i].name, "/", "Subdirectory should be called with the leafname");
           is(data[i].path, createPath(aDirectory, data[i]), "Subdirectory path should be called parentdir.path + '/' + leafname: " + data[i].path);
           if (aRecursive) {
             promises.push(checkSubDir(data[i]));
           }
         }
 
         if (data[i] instanceof File) {
           is(data[i].webkitRelativePath, createRelativePath(aDirectory, data[i]), "File.webkitRelativePath should be called file.name: " + data[i].webkitRelativePath);
@@ -70,34 +70,34 @@ function test_getFilesAndDirectories(aDi
   ).then(aNext);
 }
 
 function test_getFiles(aDirectory, aRecursive, aNext) {
   aDirectory.getFiles(aRecursive).then(
     function(data) {
       for (var i = 0; i < data.length; ++i) {
         ok(data[i] instanceof File, "File: " + data[i].name);
-        is(aDirectory.path[0], '/', "Directory path must start with '/'");
+        is(aDirectory.path[0], "/", "Directory path must start with '/'");
         ok(data[i].webkitRelativePath.indexOf(aDirectory.path.substring(1)) == 0 &&
-           data[i].webkitRelativePath.indexOf('/' + data[i].name) + ('/' + data[i].name).length == data[i].webkitRelativePath.length,
+           data[i].webkitRelativePath.indexOf("/" + data[i].name) + ("/" + data[i].name).length == data[i].webkitRelativePath.length,
            "File.webkitRelativePath should be called dir.path + '/' + file.name: " + data[i].webkitRelativePath);
       }
     },
     function() {
       ok(false, "Something when wrong");
     }
   ).then(aNext);
 }
 
 function test_getFiles_recursiveComparison(aDirectory, aNext) {
   aDirectory.getFiles(true).then(function(data) {
     is(data.length, 2, "Only 2 files for this test.");
-    ok(data[0].name == 'foo.txt' || data[0].name == 'bar.txt', "First filename matches");
-    ok(data[1].name == 'foo.txt' || data[1].name == 'bar.txt', "Second filename matches");
+    ok(data[0].name == "foo.txt" || data[0].name == "bar.txt", "First filename matches");
+    ok(data[1].name == "foo.txt" || data[1].name == "bar.txt", "Second filename matches");
   }).then(function() {
     return aDirectory.getFiles(false);
   }).then(function(data) {
     is(data.length, 1, "Only 1 file for this test.");
-    ok(data[0].name == 'foo.txt' || data[0].name == 'bar.txt', "First filename matches");
+    ok(data[0].name == "foo.txt" || data[0].name == "bar.txt", "First filename matches");
   }).catch(function() {
     ok(false, "Something when wrong");
   }).then(aNext);
 }
--- a/dom/filesystem/tests/script_fileList.js
+++ b/dom/filesystem/tests/script_fileList.js
@@ -1,15 +1,13 @@
+/* eslint-env mozilla/frame-script */
 Cu.importGlobalProperties(["File"]);
-
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 function createProfDFile() {
-  return Cc["@mozilla.org/file/directory_service;1"]
-           .getService(Ci.nsIDirectoryService)
-           .QueryInterface(Ci.nsIProperties)
-           .get('ProfD', Ci.nsIFile);
+  return Services.dirsvc.QueryInterface(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
 }
 
 // Creates a parametric arity directory hierarchy as a function of depth.
 // Each directory contains one leaf file, and subdirectories of depth [1, depth).
 // e.g. for depth 3:
 //
 // subdir3
 // - file.txt
@@ -18,33 +16,30 @@ function createProfDFile() {
 //   - subdir1
 //     - file.txt
 // - subdir1
 //   - file.txt
 //
 // Returns the parent directory of the subtree.
 function createTreeFile(depth, parent) {
   if (!parent) {
-    parent = Cc["@mozilla.org/file/directory_service;1"]
-                .getService(Ci.nsIDirectoryService)
-                .QueryInterface(Ci.nsIProperties)
-                .get('TmpD', Ci.nsIFile);
-    parent.append('dir-tree-test');
+    parent = Services.dirsvc.QueryInterface(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+    parent.append("dir-tree-test");
     parent.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
   }
 
   var nextFile = parent.clone();
   if (depth == 0) {
-    nextFile.append('file.txt');
+    nextFile.append("file.txt");
     nextFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
   } else {
-    nextFile.append('subdir' + depth);
+    nextFile.append("subdir" + depth);
     nextFile.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
     // Decrement the maximal depth by one for each level of nesting.
-    for (i = 0; i < depth; i++) {
+    for (var i = 0; i < depth; i++) {
       createTreeFile(i, nextFile);
     }
   }
 
   return parent;
 }
 
 function createRootFile() {
@@ -59,70 +54,64 @@ function createRootFile() {
 
     testFile = parent;
   }
 
   return testFile;
 }
 
 function createTestFile() {
-  var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
-                  .getService(Ci.nsIDirectoryService)
-                  .QueryInterface(Ci.nsIProperties)
-                  .get('TmpD', Ci.nsIFile)
-  tmpFile.append('dir-test');
+  var tmpFile = Services.dirsvc.QueryInterface(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+  tmpFile.append("dir-test");
   tmpFile.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
 
   var file1 = tmpFile.clone();
-  file1.append('foo.txt');
+  file1.append("foo.txt");
   file1.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   var dir = tmpFile.clone();
-  dir.append('subdir');
+  dir.append("subdir");
   dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700);
 
   var file2 = dir.clone();
-  file2.append('bar.txt');
+  file2.append("bar.txt");
   file2.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   return tmpFile;
 }
 
-addMessageListener("dir.open", function (e) {
+addMessageListener("dir.open", function(e) {
   var testFile;
 
   switch (e.path) {
-    case 'ProfD':
+    case "ProfD":
       // Note that files in the profile directory are not guaranteed to persist-
       // see bug 1284742.
       testFile = createProfDFile();
       break;
 
-    case 'root':
+    case "root":
       testFile = createRootFile();
       break;
 
-    case 'test':
+    case "test":
       testFile = createTestFile();
       break;
 
-    case 'tree':
+    case "tree":
       testFile = createTreeFile(3);
       break;
   }
 
   sendAsyncMessage("dir.opened", {
     dir: testFile.path,
-    name: testFile.leafName
+    name: testFile.leafName,
   });
 });
 
-addMessageListener("file.open", function (e) {
-  var testFile = Cc["@mozilla.org/file/directory_service;1"]
-                   .getService(Ci.nsIDirectoryService)
-                   .QueryInterface(Ci.nsIProperties)
-                   .get("ProfD", Ci.nsIFile);
+addMessageListener("file.open", function(e) {
+  var testFile = Services.dirsvc.QueryInterface(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
   testFile.append("prefs.js");
 
   File.createFromNsIFile(testFile).then(function(file) {
     sendAsyncMessage("file.opened", { file });
   });
 });
--- a/dom/filesystem/tests/test_basic.html
+++ b/dom/filesystem/tests/test_basic.html
@@ -9,26 +9,26 @@
 
 <body>
 <script type="application/javascript">
 
 var directory;
 var fileList;
 
 function create_fileList(aPath) {
-  fileList = document.createElement('input');
-  fileList.setAttribute('type', 'file');
+  fileList = document.createElement("input");
+  fileList.setAttribute("type", "file");
   document.body.appendChild(fileList);
 
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
-    fileList.setAttribute('data-name', message.name);
+    fileList.setAttribute("data-name", message.name);
 
     fileList.getFilesAndDirectories().then(function(array) {
       is(array.length, 1, "We want just 1 directory.");
       ok(array[0] instanceof Directory, "We want just 1 directory.");
 
       directory = array[0];
       script.destroy();
       next();
@@ -71,88 +71,88 @@ function test_duplicateGetFilesAndDirect
 
     isnot(p1, p2, "We create 2 different promises");
 
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
-  script.sendAsyncMessage("dir.open", { path: 'test' });
+  script.sendAsyncMessage("dir.open", { path: "test" });
 }
 
 function test_inputGetFiles() {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
-    fileList.setAttribute('data-name', message.name);
+    fileList.setAttribute("data-name", message.name);
 
     fileList.getFilesAndDirectories()
     .then(function(result) {
        is(result.length, 1, "getFilesAndDirectories should return 1 element");
        ok(result[0] instanceof Directory, "getFilesAndDirectories should return 1 directory");
 
       return fileList.getFiles(false);
     })
     .then(function(result) {
       is(result.length, 1, "getFiles should return 1 element");
       ok(result[0] instanceof File, "getFile should return 1 file");
-      is(result[0].name, 'foo.txt', "getFiles()[0].name should be 'foo.txt'");
-      is(result[0].webkitRelativePath, fileList.dataset.name + '/foo.txt', "getFiles()[0].webkitRelativePath should be '/foo.txt'");
+      is(result[0].name, "foo.txt", "getFiles()[0].name should be 'foo.txt'");
+      is(result[0].webkitRelativePath, fileList.dataset.name + "/foo.txt", "getFiles()[0].webkitRelativePath should be '/foo.txt'");
 
       return fileList.getFiles(true);
     })
     .then(function(result) {
       is(result.length, 2, "getFiles should return 2 elements");
 
       function checkFile(file) {
         ok(file instanceof File, "getFile[x] should return a file");
-        if (file.name == 'foo.txt') {
-          is(file.webkitRelativePath, fileList.dataset.name + '/foo.txt', "getFiles()[x].webkitRelativePath should be '/foo.txt'");
+        if (file.name == "foo.txt") {
+          is(file.webkitRelativePath, fileList.dataset.name + "/foo.txt", "getFiles()[x].webkitRelativePath should be '/foo.txt'");
         } else {
-          is(file.name, 'bar.txt', "getFiles()[x].name should be 'bar.txt'");
-          is(file.webkitRelativePath, fileList.dataset.name + '/subdir/bar.txt', "getFiles()[x].webkitRelativePath should be '/subdir/bar.txt'");
+          is(file.name, "bar.txt", "getFiles()[x].name should be 'bar.txt'");
+          is(file.webkitRelativePath, fileList.dataset.name + "/subdir/bar.txt", "getFiles()[x].webkitRelativePath should be '/subdir/bar.txt'");
         }
       }
 
       checkFile(result[0]);
       checkFile(result[1]);
     })
     .then(function() {
       script.destroy();
       next();
     });
   }
 
   script.addMessageListener("dir.opened", onOpened);
-  script.sendAsyncMessage("dir.open", { path: 'test' });
+  script.sendAsyncMessage("dir.open", { path: "test" });
 }
 
 var tests = [
   function() { setup_tests(next); },
 
-  function() { create_fileList('tree') },
+  function() { create_fileList("tree"); },
   function() { test_basic(directory, next); },
   function() { test_getFilesAndDirectories(directory, true, next); },
   function() { test_getFiles(directory, false, next); },
   function() { test_getFiles(directory, true, next); },
 
-  function() { create_fileList('test') },
+  function() { create_fileList("test"); },
   function() { test_getFiles_recursiveComparison(directory, next); },
 
-  function() { create_fileList('root'); },
+  function() { create_fileList("root"); },
   function() { test_basic(directory, next); },
   function() { test_getFilesAndDirectories(directory, false, next); },
   function() { test_getFiles(directory, false, next); },
 
   test_duplicateGetFilesAndDirectories,
   test_inputGetFiles,
-  test_simpleFilePicker
+  test_simpleFilePicker,
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/filesystem/tests/test_bug1319088.html
+++ b/dom/filesystem/tests/test_bug1319088.html
@@ -31,17 +31,17 @@ function populateInputFile() {
   script.addMessageListener("file.opened", onOpened);
   script.sendAsyncMessage("file.open");
 }
 
 function checkBug() {
   var input = document.getElementById("input");
   is(input.files[0].webkitRelativePath, "", "No relative path!");
 
-  let form = document.createElement('form');
+  let form = document.createElement("form");
   form.appendChild(input);
 
   is(input.files[0].webkitRelativePath, "", "No relative path!");
   SimpleTest.finish();
 }
 
 var tests = [
   testSetup,
--- a/dom/filesystem/tests/test_webkitdirectory.html
+++ b/dom/filesystem/tests/test_webkitdirectory.html
@@ -20,28 +20,28 @@ function populateInputFile(aInputFile) {
 
   var MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
 
   function onOpened(message) {
     MockFilePicker.useDirectory(message.dir);
 
     var input = document.getElementById(aInputFile);
-    input.setAttribute('data-name', message.name);
-    input.addEventListener('change', function() {
+    input.setAttribute("data-name", message.name);
+    input.addEventListener("change", function() {
       MockFilePicker.cleanup();
       script.destroy();
       next();
     }, {once: true});
 
     input.click();
   }
 
   script.addMessageListener("dir.opened", onOpened);
-  script.sendAsyncMessage("dir.open", { path: 'test' });
+  script.sendAsyncMessage("dir.open", { path: "test" });
 }
 
 function checkFile(file, fileList, dirName) {
   for (var i = 0; i < fileList.length; ++i) {
     ok(fileList[i] instanceof File, "We want just files.");
     if (fileList[i].name == file.name) {
       is(fileList[i].webkitRelativePath, dirName + file.path, "Path matches");
       return;
@@ -96,17 +96,17 @@ function test_changeDataWhileWorking() {
   // Let's start retrieving the root nsIFile object
   new Promise(function(resolve) {
     function onOpened(message) {
       script.removeMessageListener("dir.opened", onOpened);
       resolve(message.dir);
     }
 
     script.addMessageListener("dir.opened", onOpened);
-    script.sendAsyncMessage("dir.open", { path: 'root' });
+    script.sendAsyncMessage("dir.open", { path: "root" });
   })
 
   // input.click() pointing to the root dir
   .then(function(aDir) {
     MockFilePicker.cleanup();
     MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
     MockFilePicker.useDirectory(aDir);
     var input = document.getElementById("inputFileDirectoryChange");
@@ -118,61 +118,61 @@ function test_changeDataWhileWorking() {
     return new Promise(function(resolve) {
       function onOpened(message) {
         script.removeMessageListener("dir.opened", onOpened);
         script.destroy();
         resolve(message.dir);
       }
 
       script.addMessageListener("dir.opened", onOpened);
-      script.sendAsyncMessage("dir.open", { path: 'test' });
+      script.sendAsyncMessage("dir.open", { path: "test" });
     });
   })
 
   // Now let's click again and wait for onchange.
   .then(function(aDir) {
     return new Promise(function(resolve) {
       MockFilePicker.cleanup();
       MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
       MockFilePicker.useDirectory(aDir);
 
       var input = document.getElementById("inputFileDirectoryChange");
-      input.addEventListener('change', function() {
+      input.addEventListener("change", function() {
         MockFilePicker.cleanup();
         resolve();
       });
 
       input.click();
-    })
+    });
   })
 
   .then(function() {
-    test_fileList('inputFileWebkitDirectory', testDirData);
+    test_fileList("inputFileWebkitDirectory", testDirData);
   });
 }
 
 function test_setup() {
   SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
                                      ["dom.filesystem.pathcheck.disabled", true],
                                      ["dom.webkitBlink.dirPicker.enabled", true]]}, next);
 }
 
-var testDirData = [ { name: 'foo.txt', path: '/foo.txt' },
-                    { name: 'bar.txt', path: '/subdir/bar.txt' }];
+var testDirData = [ { name: "foo.txt", path: "/foo.txt" },
+                    { name: "bar.txt", path: "/subdir/bar.txt" }];
 
 var tests = [
   test_setup,
 
-  function() { populateInputFile('inputFileWebkitDirectory'); },
-  function() { populateInputFile('inputFileWebkitDirectoryAndDirectory'); },
-  function() { populateInputFile('inputFileDirectory'); },
+  function() { populateInputFile("inputFileWebkitDirectory"); },
+  function() { populateInputFile("inputFileWebkitDirectoryAndDirectory"); },
+  function() { populateInputFile("inputFileDirectory"); },
 
-  function() { test_fileList('inputFileWebkitDirectory', testDirData) },
-  function() { test_fileList('inputFileWebkitDirectoryAndDirectory', testDirData) },
-  function() { test_fileList('inputFileDirectory', null); },
+  function() { test_fileList("inputFileWebkitDirectory", testDirData); },
+  function() { test_fileList("inputFileWebkitDirectoryAndDirectory", testDirData); },
+  function() { test_fileList("inputFileDirectory", null); },
 
   test_webkitdirectory_attribute,
 
   test_changeDataWhileWorking,
 ];
 
 function next() {
   if (!tests.length) {
--- a/dom/filesystem/tests/test_worker_basic.html
+++ b/dom/filesystem/tests/test_worker_basic.html
@@ -8,46 +8,46 @@
 </head>
 
 <body>
 <script type="application/javascript">
 
 var fileList;
 
 function create_fileList() {
-  fileList = document.createElement('input');
-  fileList.setAttribute('type', 'file');
+  fileList = document.createElement("input");
+  fileList.setAttribute("type", "file");
   document.body.appendChild(fileList);
 
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
-  script.sendAsyncMessage("dir.open", { path: 'test' });
+  script.sendAsyncMessage("dir.open", { path: "test" });
 }
 
 function test_worker() {
   fileList.getFilesAndDirectories().then(function(array) {
-    var worker = new Worker('worker_basic.js');
+    var worker = new Worker("worker_basic.js");
     worker.onmessage = function(e) {
-      if (e.data.type == 'finish') {
+      if (e.data.type == "finish") {
         next();
         return;
       }
 
-      if (e.data.type == 'test') {
+      if (e.data.type == "test") {
         ok(e.data.test, e.data.message);
       }
-    }
+    };
 
     worker.postMessage(array[0]);
   });
 }
 
 var tests = [
   function() { setup_tests(next); },
 
--- a/dom/filesystem/tests/worker_basic.js
+++ b/dom/filesystem/tests/worker_basic.js
@@ -1,16 +1,17 @@
-importScripts('filesystem_commons.js');
+/* eslint-env worker */
+importScripts("filesystem_commons.js");
 
 function finish() {
-  postMessage({ type: 'finish' });
+  postMessage({ type: "finish" });
 }
 
 function ok(a, msg) {
-  postMessage({ type: 'test', test: !!a, message: msg });
+  postMessage({ type: "test", test: !!a, message: msg });
 }
 
 function is(a, b, msg) {
   ok(a === b, msg);
 }
 
 function isnot(a, b, msg) {
   ok(a != b, msg);
@@ -33,9 +34,9 @@ function next() {
   test();
 }
 
 var directory;
 
 onmessage = function(e) {
   directory = e.data;
   next();
-}
+};
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -1990,19 +1990,17 @@ HTMLFormElement::OnStatusChange(nsIWebPr
                                 nsIRequest* aRequest, nsresult aStatus,
                                 const char16_t* aMessage) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
-                                  nsIRequest* aRequest, uint32_t aOldState,
-                                  uint32_t aState,
-                                  const nsAString& aContentBlockingLogJSON) {
+                                  nsIRequest* aRequest, uint32_t state) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 NS_IMETHODIMP_(int32_t)
 HTMLFormElement::IndexOfControl(nsIFormControl* aControl) {
   int32_t index = 0;
   return mControls->IndexOfControl(aControl, &index) == NS_OK ? index : 0;
--- a/dom/html/nsHTMLDNSPrefetch.cpp
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -499,19 +499,19 @@ NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsDeferrals::OnStatusChange(nsIWebProgress *aWebProgress,
                                                nsIRequest *aRequest,
                                                nsresult aStatus,
                                                const char16_t *aMessage) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(
-    nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aOldState,
-    uint32_t aState, const nsAString &aContentBlockingLogJSON) {
+nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress,
+                                                 nsIRequest *aRequest,
+                                                 uint32_t state) {
   return NS_OK;
 }
 
 //////////// nsIObserver method
 
 NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsDeferrals::Observe(nsISupports *subject, const char *topic,
                                         const char16_t *data) {
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -885,34 +885,37 @@ nsresult ContentChild::ProvideWindowComm
     tabGroup = aTabOpener->TabGroup();
   } else {
     tabGroup = new TabGroup();
   }
 
   TabContext newTabContext = aTabOpener ? *aTabOpener : TabContext();
   RefPtr<TabChild> newChild =
       new TabChild(this, tabId, tabGroup, newTabContext, aChromeFlags);
-  if (NS_FAILED(newChild->Init(aParent))) {
-    return NS_ERROR_ABORT;
-  }
 
   if (aTabOpener) {
     MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
     ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
   }
 
   nsCOMPtr<nsIEventTarget> target =
       tabGroup->EventTargetFor(TaskCategory::Other);
   SetEventTargetForActor(newChild, target);
 
   Unused << SendPBrowserConstructor(
       // We release this ref in DeallocPBrowserChild
       RefPtr<TabChild>(newChild).forget().take(), tabId, TabId(0), *ipcContext,
       aChromeFlags, GetID(), IsForBrowser());
 
+  // Now that |newChild| has had its IPC link established, call |Init| to set it
+  // up.
+  if (NS_FAILED(newChild->Init(aParent))) {
+    return NS_ERROR_ABORT;
+  }
+
   nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
   if (aParent) {
     nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
         nsPIDOMWindowOuter::From(aParent)->GetTop();
     if (parentTopWindow) {
       parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
     }
   }
@@ -2947,16 +2950,23 @@ uint64_t NextWindowID() {
       processID & ((uint64_t(1) << kWindowIDProcessBits) - 1);
 
   // Make sure no actual window ends up with mWindowID == 0.
   uint64_t windowID = ++gNextWindowID;
 
   MOZ_RELEASE_ASSERT(windowID < (uint64_t(1) << kWindowIDWindowBits));
   uint64_t windowBits = windowID & ((uint64_t(1) << kWindowIDWindowBits) - 1);
 
+  // Make sure that the middleman process doesn't generate WindowIDs which
+  // conflict with the process it's wrapping (which shares a ContentParentID
+  // with it).
+  if (recordreplay::IsMiddleman()) {
+    windowBits |= uint64_t(1) << (kWindowIDWindowBits - 1);
+  }
+
   return (processBits << kWindowIDWindowBits) | windowBits;
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvInvokeDragSession(
     nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction) {
   nsCOMPtr<nsIDragService> dragService =
       do_GetService("@mozilla.org/widget/dragservice;1");
   if (dragService) {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2603,17 +2603,17 @@ void ContentParent::InitInternal(Process
   MOZ_ASSERT(opened);
 
   Unused << SendInitRendering(std::move(compositor), std::move(imageBridge),
                               std::move(vrBridge), std::move(videoManager),
                               namespaces);
 
   gpm->AddListener(this);
 
-  if (StaticPrefs::MediaRddProcessEnabled()) {
+  if (StaticPrefs::MediaRddProcessEnabled() && BrowserTabsRemoteAutostart()) {
     RDDProcessManager* rdd = RDDProcessManager::Get();
 
     Endpoint<PRemoteDecoderManagerChild> remoteManager;
     bool rddOpened = rdd->CreateContentBridge(OtherPid(), &remoteManager);
     MOZ_ASSERT(rddOpened);
 
     if (rddOpened) {
       // not using std::move here (like in SendInitRendering above) because
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -21,16 +21,18 @@ using LayoutDeviceIntRect from "Units.h"
 using DesktopIntRect from "Units.h";
 using DesktopToLayoutDeviceScale from "Units.h";
 using CSSToLayoutDeviceScale from "Units.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
+using refcounted class nsIPrincipal from "mozilla/dom/PermissionMessageUtils.h";
+using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 
 
 namespace mozilla {
 namespace dom {
 
 struct MessagePortIdentifier
 {
   nsID uuid;
@@ -178,10 +180,18 @@ struct PerformanceInfo
   // True if the document window is the top window
   bool isTopLevel;
   // Memory
   PerformanceMemoryInfo memory;
   // Counters per category. For workers, a single entry
   CategoryDispatch[] items;
 };
 
+struct WindowGlobalInit
+{
+  nsIPrincipal principal;
+  BrowsingContextId browsingContextId;
+  uint64_t innerWindowId;
+  uint64_t outerWindowId;
+};
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -13,16 +13,17 @@ include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PPluginWidget;
 include protocol PRemotePrintJob;
 include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol PFileDescriptorSet;
 include protocol PIPCBlobInputStream;
 include protocol PPaymentRequest;
+include protocol PWindowGlobal;
 
 include DOMTypes;
 include IPCBlob;
 include IPCStream;
 include JavaScriptTypes;
 include URIParams;
 include PPrintingTypes;
 include PTabContext;
@@ -81,16 +82,17 @@ using mozilla::EventMessage from "mozill
 using nsEventStatus from "mozilla/EventForwards.h";
 using mozilla::Modifiers from "mozilla/EventForwards.h";
 using nsSizeMode from "nsIWidgetListener.h";
 using mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
 using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 using mozilla::FontRange from "ipc/nsGUIEventIPC.h";
 using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 
 namespace mozilla {
 namespace dom {
 
 struct ShowInfo
 {
   nsString name;
   bool fullscreenAllowed;
@@ -113,16 +115,17 @@ nested(upto inside_cpow) sync protocol P
     manager PContent or PContentBridge;
 
     manages PColorPicker;
     manages PDocAccessible;
     manages PFilePicker;
     manages PIndexedDBPermissionRequest;
     manages PPluginWidget;
     manages PPaymentRequest;
+    manages PWindowGlobal;
 
 both:
     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                        Principal aPrincipal, ClonedMessageData aData);
 
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
@@ -140,16 +143,22 @@ parent:
      * in e10s mode. This is always initiated from the child in response
      * to windowed plugin creation.
      */
     sync PPluginWidget();
 
     async PPaymentRequest();
 
     /**
+     * Construct a new WindowGlobal actor for a window global in the given
+     * BrowsingContext and with the given principal.
+     */
+    async PWindowGlobal(WindowGlobalInit init);
+
+    /**
      * Sends an NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW to be adopted by the
      * widget's shareable window on the chrome side. Only used on Windows.
      */
     async SetNativeChildOfShareableWindow(uintptr_t childWindow);
 
     /**
      * When content moves focus from a native plugin window that's a child
      * of the native browser window we need to move native focus to the
@@ -574,16 +583,18 @@ parent:
     async ShowCanvasPermissionPrompt(nsCString aFirstPartyURI);
 
     sync SetSystemFont(nsCString aFontName);
     sync GetSystemFont() returns (nsCString retval);
 
     sync SetPrefersReducedMotionOverrideForTest(bool aValue);
     sync ResetPrefersReducedMotionOverrideForTest();
 
+    async RootBrowsingContext(BrowsingContextId aId);
+
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
      * |Show()| and |Move()| take IntSizes rather than Rects because
      * content processes always render to a virtual <0, 0> top-left
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBrowser;
+include protocol PInProcess;
+
+include DOMTypes;
+
+using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * A PWindowGlobal actor has a lifetime matching that of a single Window Global,
+ * specifically a |nsGlobalWindowInner|. These actors will form a parent/child
+ * link either between the chrome/content process, or will be in-process, for
+ * documents which are loaded in the chrome process.
+ */
+async protocol PWindowGlobal
+{
+  manager PBrowser or PInProcess;
+
+parent:
+  /// Update the URI of the document in this WindowGlobal.
+  async UpdateDocumentURI(nsIURI aUri);
+
+  /// Notify the parent that this PWindowGlobal is now the current global.
+  async BecomeCurrentWindowGlobal();
+
+  async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -117,16 +117,17 @@
 #include "nsQueryObject.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/DocGroup.h"
 #include "nsString.h"
 #include "nsISupportsPrimitives.h"
 #include "mozilla/Telemetry.h"
 #include "nsDocShellLoadState.h"
 #include "nsWebBrowser.h"
+#include "mozilla/dom/WindowGlobalChild.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetChild.h"
 #endif
 
 #ifdef NS_PRINTING
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
@@ -532,16 +533,21 @@ nsresult TabChild::Init(mozIDOMWindowPro
   docShell->SetAffectPrivateSessionLifetime(
       mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME);
   nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(WebNavigation());
   MOZ_ASSERT(loadContext);
   loadContext->SetPrivateBrowsing(OriginAttributesRef().mPrivateBrowsingId > 0);
   loadContext->SetRemoteTabs(mChromeFlags &
                              nsIWebBrowserChrome::CHROME_REMOTE_WINDOW);
 
+  // Send our browsing context to the parent process.
+  RefPtr<BrowsingContext> browsingContext =
+      nsDocShell::Cast(docShell)->GetBrowsingContext();
+  SendRootBrowsingContext(BrowsingContextId(browsingContext->Id()));
+
   // Few lines before, baseWindow->Create() will end up creating a new
   // window root in nsGlobalWindow::SetDocShell.
   // Then this chrome event handler, will be inherited to inner windows.
   // We want to also set it to the docshell so that inner windows
   // and any code that has access to the docshell
   // can all listen to the same chrome event handler.
   // XXX: ideally, we would set a chrome event handler earlier,
   // and all windows, even the root one, will use the docshell one.
@@ -3132,16 +3138,27 @@ PPaymentRequestChild* TabChild::AllocPPa
   return nullptr;
 }
 
 bool TabChild::DeallocPPaymentRequestChild(PPaymentRequestChild* actor) {
   delete actor;
   return true;
 }
 
+PWindowGlobalChild* TabChild::AllocPWindowGlobalChild(const WindowGlobalInit&) {
+  MOZ_CRASH("We should never be manually allocating PWindowGlobalChild actors");
+  return nullptr;
+}
+
+bool TabChild::DeallocPWindowGlobalChild(PWindowGlobalChild* aActor) {
+  // This reference was added in WindowGlobalChild::Create.
+  static_cast<WindowGlobalChild*>(aActor)->Release();
+  return true;
+}
+
 ScreenIntSize TabChild::GetInnerSize() {
   LayoutDeviceIntSize innerSize =
       RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(
       innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 };
 
 ScreenIntRect TabChild::GetOuterRect() {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -652,16 +652,21 @@ class TabChild final : public TabChildBa
   static const nsTHashtable<nsPtrHashKey<TabChild>>& GetVisibleTabs() {
     MOZ_ASSERT(HasVisibleTabs());
     return *sVisibleTabs;
   }
 
  protected:
   virtual ~TabChild();
 
+  virtual PWindowGlobalChild* AllocPWindowGlobalChild(
+      const WindowGlobalInit& aInit) override;
+
+  virtual bool DeallocPWindowGlobalChild(PWindowGlobalChild* aActor) override;
+
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
 
   virtual mozilla::ipc::IPCResult RecvSetDocShellIsActive(
       const bool& aIsActive) override;
 
   virtual mozilla::ipc::IPCResult RecvRenderLayers(
       const bool& aEnabled, const bool& aForce,
       const layers::LayersObserverEpoch& aEpoch) override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -95,16 +95,18 @@
 #include "ImageOps.h"
 #include "UnitTransforms.h"
 #include <algorithm>
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "ProcessPriorityManager.h"
 #include "nsString.h"
 #include "IHistory.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetParent.h"
 #endif
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #include "mozilla/a11y/AccessibleWrap.h"
 #include "mozilla/a11y/Compatibility.h"
@@ -983,16 +985,34 @@ bool TabParent::DeallocPIndexedDBPermiss
     PIndexedDBPermissionRequestParent* aActor) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::indexedDB::DeallocPIndexedDBPermissionRequestParent(
       aActor);
 }
 
+IPCResult TabParent::RecvPWindowGlobalConstructor(
+    PWindowGlobalParent* aActor, const WindowGlobalInit& aInit) {
+  static_cast<WindowGlobalParent*>(aActor)->Init(aInit);
+  return IPC_OK();
+}
+
+PWindowGlobalParent* TabParent::AllocPWindowGlobalParent(
+    const WindowGlobalInit& aInit) {
+  // Reference freed in DeallocPWindowGlobalParent.
+  return do_AddRef(new WindowGlobalParent(aInit, /* inproc */ false)).take();
+}
+
+bool TabParent::DeallocPWindowGlobalParent(PWindowGlobalParent* aActor) {
+  // Free reference from AllocPWindowGlobalParent.
+  static_cast<WindowGlobalParent*>(aActor)->Release();
+  return true;
+}
+
 void TabParent::SendMouseEvent(const nsAString& aType, float aX, float aY,
                                int32_t aButton, int32_t aClickCount,
                                int32_t aModifiers,
                                bool aIgnoreRootScrollFrame) {
   if (!mIsDestroyed) {
     Unused << PBrowserParent::SendMouseEvent(nsString(aType), aX, aY, aButton,
                                              aClickCount, aModifiers,
                                              aIgnoreRootScrollFrame);
@@ -3366,16 +3386,24 @@ mozilla::ipc::IPCResult TabParent::RecvS
 mozilla::ipc::IPCResult TabParent::RecvGetSystemFont(nsCString* aFontName) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     widget->GetSystemFont(*aFontName);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult TabParent::RecvRootBrowsingContext(
+    const BrowsingContextId& aId) {
+  MOZ_ASSERT(!mBrowsingContext, "May only set browsing context once!");
+  mBrowsingContext = ChromeBrowsingContext::Get(aId);
+  MOZ_ASSERT(mBrowsingContext, "Invalid ID!");
+  return IPC_OK();
+}
+
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports* aContext,
                              nsIAuthInformation* aAuthInfo) {
   nsAuthInformationHolder* holder =
       static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(
           mCallbackId, holder->User(), holder->Password(), holder->Domain())) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -62,16 +62,17 @@ struct IMENotification;
 
 namespace gfx {
 class SourceSurface;
 class DataSourceSurface;
 }  // namespace gfx
 
 namespace dom {
 
+class ChromeBrowsingContext;
 class ClonedMessageData;
 class nsIContentParent;
 class Element;
 class DataTransfer;
 
 namespace ipc {
 class StructuredCloneData;
 }  // namespace ipc
@@ -120,16 +121,18 @@ class TabParent final : public PBrowserP
   }
 
   already_AddRefed<nsILoadContext> GetLoadContext();
 
   already_AddRefed<nsIWidget> GetTopLevelWidget();
 
   nsIXULBrowserWindow* GetXULBrowserWindow();
 
+  ChromeBrowsingContext* GetBrowsingContext() { return mBrowsingContext; }
+
   void Destroy();
 
   void RemoveWindowListeners();
 
   void AddWindowListeners();
 
   virtual mozilla::ipc::IPCResult RecvMoveFocus(
       const bool& aForward, const bool& aForDocumentNavigation) override;
@@ -300,16 +303,24 @@ class TabParent final : public PBrowserP
       const uint64_t& aParentID, const uint32_t& aMsaaID,
       const IAccessibleHolder& aDocCOMProxy) override;
 
   /**
    * Return the top level doc accessible parent for this tab.
    */
   a11y::DocAccessibleParent* GetTopLevelDocAccessible() const;
 
+  virtual PWindowGlobalParent* AllocPWindowGlobalParent(
+      const WindowGlobalInit& aInit) override;
+
+  virtual bool DeallocPWindowGlobalParent(PWindowGlobalParent* aActor) override;
+
+  virtual mozilla::ipc::IPCResult RecvPWindowGlobalConstructor(
+      PWindowGlobalParent* aActor, const WindowGlobalInit& aInit) override;
+
   void LoadURL(nsIURI* aURI);
 
   void InitRendering();
   void MaybeShowFrame();
 
   // XXX/cjones: it's not clear what we gain by hiding these
   // message-sending functions under a layer of indirection and
   // eating the return values
@@ -571,16 +582,19 @@ class TabParent final : public PBrowserP
 
   virtual mozilla::ipc::IPCResult RecvSetDimensions(
       const uint32_t& aFlags, const int32_t& aX, const int32_t& aY,
       const int32_t& aCx, const int32_t& aCy) override;
 
   virtual mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(
       const nsCString& aFirstPartyURI) override;
 
+  virtual mozilla::ipc::IPCResult RecvRootBrowsingContext(
+      const BrowsingContextId& aId) override;
+
   mozilla::ipc::IPCResult RecvSetSystemFont(
       const nsCString& aFontName) override;
   mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName) override;
 
   virtual mozilla::ipc::IPCResult RecvVisitURI(
       const URIParams& aURI, const OptionalURIParams& aLastVisitedURI,
       const uint32_t& aFlags) override;
 
@@ -648,16 +662,19 @@ class TabParent final : public PBrowserP
 
   nsCOMPtr<nsILoadContext> mLoadContext;
 
   // We keep a strong reference to the frameloader after we've sent the
   // Destroy message and before we've received __delete__. This allows us to
   // dispatch message manager messages during this time.
   RefPtr<nsFrameLoader> mFrameLoader;
 
+  // The root browsing context loaded in this TabParent.
+  RefPtr<ChromeBrowsingContext> mBrowsingContext;
+
   TabId mTabId;
 
   // When loading a new tab or window via window.open, the child is
   // responsible for loading the URL it wants into the new TabChild. When the
   // parent receives the CreateWindow message, though, it sends a LoadURL
   // message, usually for about:blank. It's important for the about:blank load
   // to get processed because the Firefox frontend expects every new window to
   // immediately start loading something (see bug 1123090). However, we want
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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 "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/ipc/InProcessChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalChild> WGCByIdMap;
+static StaticAutoPtr<WGCByIdMap> gWindowGlobalChildById;
+
+WindowGlobalChild::WindowGlobalChild(nsGlobalWindowInner* aWindow,
+                                     dom::BrowsingContext* aBrowsingContext)
+  : mWindowGlobal(aWindow)
+  , mBrowsingContext(aBrowsingContext)
+  , mInnerWindowId(aWindow->WindowID())
+  , mOuterWindowId(aWindow->GetOuterWindow()->WindowID())
+  , mIPCClosed(true)
+{
+}
+
+already_AddRefed<WindowGlobalChild>
+WindowGlobalChild::Create(nsGlobalWindowInner* aWindow)
+{
+  nsCOMPtr<nsIPrincipal> principal = aWindow->GetPrincipal();
+  MOZ_ASSERT(principal);
+
+  RefPtr<nsDocShell> docshell = nsDocShell::Cast(aWindow->GetDocShell());
+  MOZ_ASSERT(docshell);
+
+  // Initalize our WindowGlobalChild object.
+  RefPtr<dom::BrowsingContext> bc = docshell->GetBrowsingContext();
+  RefPtr<WindowGlobalChild> wgc = new WindowGlobalChild(aWindow, bc);
+
+  WindowGlobalInit init(principal,
+                        BrowsingContextId(wgc->BrowsingContext()->Id()),
+                        wgc->mInnerWindowId,
+                        wgc->mOuterWindowId);
+
+  // Send the link constructor over PInProcessChild or PBrowser.
+  if (XRE_IsParentProcess()) {
+    InProcessChild* ipc = InProcessChild::Singleton();
+    if (!ipc) {
+      return nullptr;
+    }
+
+    // Note: ref is released in DeallocPWindowGlobalChild
+    ipc->SendPWindowGlobalConstructor(do_AddRef(wgc).take(), init);
+  } else {
+    RefPtr<TabChild> tabChild = TabChild::GetFrom(static_cast<mozIDOMWindow*>(aWindow));
+    MOZ_ASSERT(tabChild);
+
+    // Note: ref is released in DeallocPWindowGlobalChild
+    tabChild->SendPWindowGlobalConstructor(do_AddRef(wgc).take(), init);
+  }
+  wgc->mIPCClosed = false;
+
+  // Register this WindowGlobal in the gWindowGlobalParentsById map.
+  if (!gWindowGlobalChildById) {
+    gWindowGlobalChildById = new WGCByIdMap();
+    ClearOnShutdown(&gWindowGlobalChildById);
+  }
+  auto entry = gWindowGlobalChildById->LookupForAdd(wgc->mInnerWindowId);
+  MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalChild entry for ID!");
+  entry.OrInsert([&] { return wgc; });
+
+  return wgc.forget();
+}
+
+/* static */ already_AddRefed<WindowGlobalChild>
+WindowGlobalChild::GetByInnerWindowId(uint64_t aInnerWindowId)
+{
+  if (!gWindowGlobalChildById) {
+    return nullptr;
+  }
+  return gWindowGlobalChildById->Get(aInnerWindowId);
+}
+
+bool
+WindowGlobalChild::IsCurrentGlobal()
+{
+  return !mIPCClosed && mWindowGlobal->IsCurrentInnerWindow();
+}
+
+already_AddRefed<WindowGlobalParent>
+WindowGlobalChild::GetParentActor()
+{
+  if (mIPCClosed) {
+    return nullptr;
+  }
+  IProtocol* otherSide = InProcessChild::ParentActorFor(this);
+  return do_AddRef(static_cast<WindowGlobalParent*>(otherSide));
+}
+
+void
+WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mIPCClosed = true;
+  gWindowGlobalChildById->Remove(mInnerWindowId);
+}
+
+WindowGlobalChild::~WindowGlobalChild()
+{
+  MOZ_ASSERT(!gWindowGlobalChildById ||
+             !gWindowGlobalChildById->Contains(mInnerWindowId));
+}
+
+JSObject*
+WindowGlobalChild::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto)
+{
+  return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+WindowGlobalChild::GetParentObject()
+{
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WindowGlobalChild,
+                                      mWindowGlobal,
+                                      mBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WindowGlobalChild, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WindowGlobalChild, Release)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalChild.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowGlobalChild_h
+#define mozilla_dom_WindowGlobalChild_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PWindowGlobalChild.h"
+#include "nsWrapperCache.h"
+
+class nsGlobalWindowInner;
+class nsDocShell;
+
+namespace mozilla {
+namespace dom  {
+
+class BrowsingContext;
+class WindowGlobalParent;
+
+/**
+ * Actor for a single nsGlobalWindowInner. This actor is used to communicate
+ * information to the parent process asynchronously.
+ */
+class WindowGlobalChild : public nsWrapperCache
+                        , public PWindowGlobalChild
+{
+public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WindowGlobalChild)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WindowGlobalChild)
+
+  static already_AddRefed<WindowGlobalChild>
+  GetByInnerWindowId(uint64_t aInnerWindowId);
+
+  static already_AddRefed<WindowGlobalChild>
+  GetByInnerWindowId(const GlobalObject& aGlobal, uint64_t aInnerWindowId) {
+    return GetByInnerWindowId(aInnerWindowId);
+  }
+
+  dom::BrowsingContext* BrowsingContext() { return mBrowsingContext; }
+  nsGlobalWindowInner* WindowGlobal() { return mWindowGlobal; }
+
+  // Has this actor been shut down
+  bool IsClosed() { return mIPCClosed; }
+
+  // Check if this actor is managed by PInProcess, as-in the document is loaded
+  // in the chrome process.
+  bool IsInProcess() { return XRE_IsParentProcess(); }
+
+  // The Window ID for this WindowGlobal
+  uint64_t InnerWindowId() { return mInnerWindowId; }
+  uint64_t OuterWindowId() { return mOuterWindowId; }
+
+  bool IsCurrentGlobal();
+
+  // Get the other side of this actor if it is an in-process actor. Returns
+  // |nullptr| if the actor has been torn down, or is not in-process.
+  already_AddRefed<WindowGlobalParent> GetParentActor();
+
+  // Create and initialize the WindowGlobalChild object.
+  static already_AddRefed<WindowGlobalChild>
+  Create(nsGlobalWindowInner* aWindow);
+
+  nsISupports* GetParentObject();
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  WindowGlobalChild(nsGlobalWindowInner* aWindow, dom::BrowsingContext* aBc);
+  ~WindowGlobalChild();
+
+  RefPtr<nsGlobalWindowInner> mWindowGlobal;
+  RefPtr<dom::BrowsingContext> mBrowsingContext;
+  uint64_t mInnerWindowId;
+  uint64_t mOuterWindowId;
+  bool mIPCClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_WindowGlobalChild_h)
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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 "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/ipc/InProcessParent.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalParent> WGPByIdMap;
+static StaticAutoPtr<WGPByIdMap> gWindowGlobalParentsById;
+
+WindowGlobalParent::WindowGlobalParent(const WindowGlobalInit& aInit,
+                                       bool aInProcess)
+  : mDocumentPrincipal(aInit.principal())
+  , mInnerWindowId(aInit.innerWindowId())
+  , mOuterWindowId(aInit.outerWindowId())
+  , mInProcess(aInProcess)
+  , mIPCClosed(true)  // Closed until WGP::Init
+{
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only");
+  MOZ_RELEASE_ASSERT(mDocumentPrincipal, "Must have a valid principal");
+
+  // NOTE: mBrowsingContext initialized in Init()
+  MOZ_RELEASE_ASSERT(aInit.browsingContextId() != 0,
+                     "Must be made in BrowsingContext");
+}
+
+void
+WindowGlobalParent::Init(const WindowGlobalInit& aInit)
+{
+  MOZ_ASSERT(Manager(), "Should have a manager!");
+  MOZ_ASSERT(!mFrameLoader, "Cannot Init() a WindowGlobalParent twice!");
+
+  MOZ_ASSERT(mIPCClosed, "IPC shouldn't be open yet");
+  mIPCClosed = false;
+
+  // Register this WindowGlobal in the gWindowGlobalParentsById map.
+  if (!gWindowGlobalParentsById) {
+    gWindowGlobalParentsById = new WGPByIdMap();
+    ClearOnShutdown(&gWindowGlobalParentsById);
+  }
+  auto entry = gWindowGlobalParentsById->LookupForAdd(mInnerWindowId);
+  MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalParent entry for ID!");
+  entry.OrInsert([&] { return this; });
+
+  // Determine which content process the window global is coming from.
+  ContentParentId processId(0);
+  if (!mInProcess) {
+    processId = static_cast<ContentParent*>(Manager()->Manager())->ChildID();
+  }
+
+  mBrowsingContext = ChromeBrowsingContext::Get(aInit.browsingContextId());
+  MOZ_ASSERT(mBrowsingContext);
+
+  // XXX(nika): This won't be the case soon, but for now this is a good
+  // assertion as we can't switch processes. We should relax this eventually.
+  MOZ_ASSERT(mBrowsingContext->IsOwnedByProcess(processId));
+
+  // Attach ourself to the browsing context.
+  mBrowsingContext->RegisterWindowGlobal(this);
+
+  // Determine what toplevel frame element our WindowGlobalParent is being
+  // embedded in.
+  RefPtr<Element> frameElement;
+  if (mInProcess) {
+    // In the in-process case, we can get it from the other side's
+    // WindowGlobalChild.
+    MOZ_ASSERT(Manager()->GetProtocolTypeId() == PInProcessMsgStart);
+    RefPtr<WindowGlobalChild> otherSide = GetChildActor();
+    if (otherSide && otherSide->WindowGlobal()) {
+      // Get the toplevel window from the other side.
+      RefPtr<nsDocShell> docShell = nsDocShell::Cast(otherSide->WindowGlobal()->GetDocShell());
+      if (docShell) {
+        docShell->GetTopFrameElement(getter_AddRefs(frameElement));
+      }
+    }
+  } else {
+    // In the cross-process case, we can get the frame element from our manager.
+    MOZ_ASSERT(Manager()->GetProtocolTypeId() == PBrowserMsgStart);
+    frameElement = static_cast<TabParent*>(Manager())->GetOwnerElement();
+  }
+
+  // Extract the nsFrameLoader from the current frame element. We may not have a
+  // nsFrameLoader if we are a chrome document.
+  nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(frameElement);
+  if (flOwner) {
+    mFrameLoader = flOwner->GetFrameLoader();
+  }
+}
+
+/* static */ already_AddRefed<WindowGlobalParent>
+WindowGlobalParent::GetByInnerWindowId(uint64_t aInnerWindowId)
+{
+  if (!gWindowGlobalParentsById) {
+    return nullptr;
+  }
+  return gWindowGlobalParentsById->Get(aInnerWindowId);
+}
+
+already_AddRefed<WindowGlobalChild>
+WindowGlobalParent::GetChildActor()
+{
+  if (mIPCClosed) {
+    return nullptr;
+  }
+  IProtocol* otherSide = InProcessParent::ChildActorFor(this);
+  return do_AddRef(static_cast<WindowGlobalChild*>(otherSide));
+}
+
+IPCResult
+WindowGlobalParent::RecvUpdateDocumentURI(nsIURI* aURI)
+{
+  // XXX(nika): Assert that the URI change was one which makes sense (either
+  // about:blank -> a real URI, or a legal push/popstate URI change?)
+  mDocumentURI = aURI;
+  return IPC_OK();
+}
+
+IPCResult
+WindowGlobalParent::RecvBecomeCurrentWindowGlobal()
+{
+  mBrowsingContext->SetCurrentWindowGlobal(this);
+  return IPC_OK();
+}
+
+bool
+WindowGlobalParent::IsCurrentGlobal()
+{
+  return !mIPCClosed && mBrowsingContext->GetCurrentWindowGlobal() == this;
+}
+
+void
+WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mIPCClosed = true;
+  gWindowGlobalParentsById->Remove(mInnerWindowId);
+  mBrowsingContext->UnregisterWindowGlobal(this);
+}
+
+WindowGlobalParent::~WindowGlobalParent()
+{
+  MOZ_ASSERT(!gWindowGlobalParentsById ||
+             !gWindowGlobalParentsById->Contains(mInnerWindowId));
+}
+
+JSObject*
+WindowGlobalParent::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto)
+{
+  return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+WindowGlobalParent::GetParentObject()
+{
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WindowGlobalParent,
+                                      mFrameLoader,
+                                      mBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WindowGlobalParent, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WindowGlobalParent, Release)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalParent.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowGlobalParent_h
+#define mozilla_dom_WindowGlobalParent_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PWindowGlobalParent.h"
+#include "nsWrapperCache.h"
+
+class nsIPrincipal;
+class nsIURI;
+class nsFrameLoader;
+
+namespace mozilla {
+namespace dom  {
+
+class ChromeBrowsingContext;
+class WindowGlobalChild;
+
+/**
+ * A handle in the parent process to a specific nsGlobalWindowInner object.
+ */
+class WindowGlobalParent final : public nsWrapperCache
+                               , public PWindowGlobalParent
+{
+public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WindowGlobalParent)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WindowGlobalParent)
+
+  static already_AddRefed<WindowGlobalParent>
+  GetByInnerWindowId(uint64_t aInnerWindowId);
+
+  static already_AddRefed<WindowGlobalParent>
+  GetByInnerWindowId(const GlobalObject& aGlobal, uint64_t aInnerWindowId) {
+    return GetByInnerWindowId(aInnerWindowId);
+  }
+
+  // Has this actor been shut down
+  bool IsClosed() { return mIPCClosed; }
+
+  // Check if this actor is managed by PInProcess, as-in the document is loaded
+  // in-process.
+  bool IsInProcess() { return mInProcess; }
+
+  // Get the other side of this actor if it is an in-process actor. Returns
+  // |nullptr| if the actor has been torn down, or is not in-process.
+  already_AddRefed<WindowGlobalChild> GetChildActor();
+
+  // The principal of this WindowGlobal. This value will not change over the
+  // lifetime of the WindowGlobal object, even to reflect changes in
+  // |document.domain|.
+  nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; }
+
+  // The BrowsingContext which this WindowGlobal has been loaded into.
+  ChromeBrowsingContext* BrowsingContext() { return mBrowsingContext; }
+
+  // Get the root nsFrameLoader object for the tree of BrowsingContext nodes
+  // which this WindowGlobal is a part of. This will be the nsFrameLoader
+  // holding the TabParent for remote tabs, and the root content frameloader for
+  // non-remote tabs.
+  nsFrameLoader* GetRootFrameLoader() { return mFrameLoader; }
+
+  // The current URI which loaded in the document.
+  nsIURI* GetDocumentURI() { return mDocumentURI; }
+
+  // Window IDs for inner/outer windows.
+  uint64_t OuterWindowId() { return mOuterWindowId; }
+  uint64_t InnerWindowId() { return mInnerWindowId; }
+
+  bool IsCurrentGlobal();
+
+  // Create a WindowGlobalParent from over IPC. This method should not be called
+  // from outside of the IPC constructors.
+  WindowGlobalParent(const WindowGlobalInit& aInit, bool aInProcess);
+
+  // Initialize the mFrameLoader fields for a created WindowGlobalParent. Must
+  // be called after setting the Manager actor.
+  void Init(const WindowGlobalInit& aInit);
+
+  nsISupports* GetParentObject();
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  // IPC messages
+  mozilla::ipc::IPCResult RecvUpdateDocumentURI(nsIURI* aURI) override;
+  mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal() override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  ~WindowGlobalParent();
+
+  // NOTE: This document principal doesn't reflect possible |document.domain|
+  // mutations which may have been made in the actual document.
+  nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+  nsCOMPtr<nsIURI> mDocumentURI;
+  RefPtr<nsFrameLoader> mFrameLoader;
+  RefPtr<ChromeBrowsingContext> mBrowsingContext;
+  uint64_t mInnerWindowId;
+  uint64_t mOuterWindowId;
+  bool mInProcess;
+  bool mIPCClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_WindowGlobalParent_h)
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -44,16 +44,18 @@ EXPORTS.mozilla.dom += [
     'nsIContentParent.h',
     'PermissionMessageUtils.h',
     'TabChild.h',
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
     'URLClassifierChild.h',
     'URLClassifierParent.h',
+    'WindowGlobalChild.h',
+    'WindowGlobalParent.h',
 ]
 
 EXPORTS.mozilla += [
     'PreallocatedProcessManager.h',
     'ProcessHangMonitor.h',
     'ProcessHangMonitorIPC.h',
     'ProcessPriorityManager.h',
 ]
@@ -78,16 +80,18 @@ UNIFIED_SOURCES += [
     'SharedMap.cpp',
     'SharedStringMap.cpp',
     'StructuredCloneData.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
+    'WindowGlobalChild.cpp',
+    'WindowGlobalParent.cpp',
 ]
 
 # ContentChild.cpp cannot be compiled in unified mode on  linux due to Time conflict
 SOURCES += [
     'ContentChild.cpp',
     'ProcessHangMonitor.cpp',
 ]
 
@@ -105,16 +109,17 @@ IPDL_SOURCES += [
     'PFilePicker.ipdl',
     'PLoginReputation.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PTabContext.ipdlh',
     'PURLClassifier.ipdl',
     'PURLClassifierInfo.ipdlh',
     'PURLClassifierLocal.ipdl',
+    'PWindowGlobal.ipdl',
     'ServiceWorkerConfiguration.ipdlh',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_TARGET'] == 'Darwin':
--- a/dom/locales/en-US/chrome/plugins.properties
+++ b/dom/locales/en-US/chrome/plugins.properties
@@ -1,34 +1,12 @@
 # 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/.
 
-# LOCALIZATION NOTE:
-#    Those strings are inserted into an HTML page, so all HTML characters
-#    have to be escaped in a way that they show up correctly in HTML!
-
-title_label=About Plugins
-installedplugins_label=Installed plugins
-nopluginsareinstalled_label=No installed plugins found
-findpluginupdates_label=Find updates for installed plugins at
-file_label=File:
-path_label=Path:
-version_label=Version:
-state_label=State:
-state_enabled=Enabled
-state_disabled=Disabled
-mimetype_label=MIME Type
-description_label=Description
-suffixes_label=Suffixes
-learn_more_label=Learn More
-
-deprecation_description=Missing something? Some plugins are no longer supported.
-deprecation_learn_more=Learn More.
-
 # GMP Plugins
 gmp_license_info=License information
 gmp_privacy_info=Privacy Information
 
 openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
 openH264_description2=This plugin is automatically installed by Mozilla to comply with the WebRTC specification and to enable WebRTC calls with devices that require the H.264 video codec. Visit http://www.openh264.org/ to view the codec source code and learn more about the implementation.
 
 cdm_description2=This plugin enables playback of encrypted media in compliance with the Encrypted Media Extensions specification. Encrypted media is typically used by sites to protect against copying of premium media content. Visit https://www.w3.org/TR/encrypted-media/ for more information on Encrypted Media Extensions.
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1118,18 +1118,24 @@ class GetUserMediaStreamRunnable : publi
           mWindowID(aWindowID),
           mGraph(aTrack->GraphImpl()),
           mStream(new nsMainThreadPtrHolder<DOMMediaStream>(
               "TracksCreatedListener::mStream", aStream)),
           mTrack(new nsMainThreadPtrHolder<MediaStreamTrack>(
               "TracksCreatedListener::mTrack", aTrack)) {}
 
     ~TracksCreatedListener() {
-      mHolder.RejectIfExists(
-          MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), __func__);
+      RejectIfExists(MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
+                     __func__);
+    }
+
+    // TODO: The need for this should be removed by an upcoming refactor.
+    void RejectIfExists(RefPtr<MediaMgrError>&& aError,
+                        const char* aMethodName) {
+      mHolder.RejectIfExists(std::move(aError), aMethodName);
     }
 
     void NotifyOutput(MediaStreamGraph* aGraph,
                       StreamTime aCurrentTrackTime) override {
       // It's enough to know that one of the tracks have output, as both tracks
       // are guaranteed to be created in the graph at this point.
 
       if (mDispatchedTracksCreated) {
@@ -1379,30 +1385,30 @@ class GetUserMediaStreamRunnable : publi
               ("GetUserMediaStreamRunnable::Run: starting success callback "
                "following InitializeAsync()"));
           // Initiating and starting devices succeeded.
           track->AddListener(tracksCreatedListener);
           windowListener->ChromeAffectingStateChanged();
           manager->SendPendingGUMRequest();
         },
         [manager = mManager, windowID = mWindowID,
-         holder = std::move(mHolder)](RefPtr<MediaMgrError>&& aError) mutable {
+         tracksCreatedListener](RefPtr<MediaMgrError>&& aError) {
           MOZ_ASSERT(NS_IsMainThread());
           LOG(
               ("GetUserMediaStreamRunnable::Run: starting failure callback "
                "following InitializeAsync()"));
           // Initiating and starting devices failed.
 
           // Only run if the window is still active for our window listener.
           if (!(manager->IsWindowStillActive(windowID))) {
             return;
           }
           // This is safe since we're on main-thread, and the windowlist can
           // only be invalidated from the main-thread (see OnNavigation)
-          holder.Reject(std::move(aError), __func__);
+          tracksCreatedListener->RejectIfExists(std::move(aError), __func__);
         });
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       media::GetPrincipalKey(mPrincipalInfo, true)
           ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                  [](const PrincipalKeyPromise::ResolveOrRejectValue& aValue) {
--- a/dom/media/mp4/DecoderData.cpp
+++ b/dom/media/mp4/DecoderData.cpp
@@ -82,23 +82,22 @@ void MP4AudioInfo::Update(const Mp4parse
     mMimeType = NS_LITERAL_CSTRING("audio/flac");
   } else if (track->codec == MP4PARSE_CODEC_MP3) {
     mMimeType = NS_LITERAL_CSTRING("audio/mpeg");
   }
 
   mRate = audio->sample_rate;
   mChannels = audio->channels;
   mBitDepth = audio->bit_depth;
-  mExtendedProfile = audio->profile;
+  mExtendedProfile = audio->extended_profile;
   mDuration = TimeUnit::FromMicroseconds(track->duration);
   mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
   mTrackId = track->track_id;
 
   // In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT.
-  // Both are from audioObjectType in AudioSpecificConfig.
   if (audio->profile <= 4) {
     mProfile = audio->profile;
   }
 
   if (audio->extra_data.length > 0) {
     mExtraData->AppendElements(audio->extra_data.data,
                                audio->extra_data.length);
   }
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -172,17 +172,16 @@ skip-if = !asan && toolkit != android
 [test_mediaDecoding.html]
 [test_mediaElementAudioSourceNode.html]
 tags=capturestream
 [test_mediaElementAudioSourceNodeFidelity.html]
 tags=capturestream
 skip-if = (toolkit == 'android' && debug) # bug 1339889
 [test_mediaElementAudioSourceNodePassThrough.html]
 tags=capturestream
-skip-if = toolkit == 'android' # bug 1145816
 [test_mediaElementAudioSourceNodeVideo.html]
 tags=capturestream
 skip-if = toolkit == 'android' # bug 1339448
 [test_mediaElementAudioSourceNodeCrossOrigin.html]
 tags=capturestream
 skip-if = toolkit == 'android' # bug 1145816
 [test_mediaStreamAudioDestinationNode.html]
 [test_mediaStreamAudioSourceNode.html]
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -230,13 +230,12 @@ PresentationResponderLoadingCallback::On
     nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus,
     const char16_t* aMessage) {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnSecurityChange(
-    nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aOldState,
-    uint32_t aState, const nsAString& aContentBlockingLogJSON) {
+    nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t state) {
   // Do nothing.
   return NS_OK;
 }
--- a/dom/push/test/test_has_permissions.html
+++ b/dom/push/test/test_has_permissions.html
@@ -5,16 +5,17 @@ Bug 1038811: Push tests.
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1038811</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
   <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038811">Mozilla Bug 1038811</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -23,62 +24,30 @@ http://creativecommons.org/licenses/publ
 </pre>
 
 <script class="testbody" type="text/javascript">
 
   function debug(str) {
   // console.log(str + "\n");
   }
 
-  function start() {
-    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
-    .then((swr) => {
-      registration = swr;
-      return waitForActive(registration);
-    });
-  }
+  add_task(async function start() {
+    await setupPrefsAndMockSocket(new MockWebSocket());
 
-  function unregister() {
-    return registration.unregister().then(function(result) {
-      ok(result, "Unregister should return true.");
-    }, function(e) {
-      dump("Unregistering the SW failed with " + e + "\n");
-    });
-  }
+    var url = "worker.js" + "?" + Math.random();
+    registration = await navigator.serviceWorker.register(url, {scope: "."});
+    await waitForActive(registration);
+  });
 
-  function hasPermission(swr) {
-    var p = new Promise(function(res, rej) {
-      swr.pushManager.permissionState().then(
-        function(state) {
-          debug("state: " + state);
-          ok(["granted", "denied", "prompt"].includes(state), "permissionState() returned a valid state.");
-          res(swr);
-        }, function(error) {
-          ok(false, "permissionState() failed.");
-          res(swr);
-        }
-        );
-    });
-    return p;
-  }
+  add_task(async function hasPermission() {
+    var state = await registration.pushManager.permissionState();
+    debug("state: " + state);
+    ok(["granted", "denied", "prompt"].includes(state), "permissionState() returned a valid state.");
+  });
 
-  function runTest() {
-    start()
-    .then(hasPermission)
-    .then(unregister)
-    .catch(function(e) {
-      ok(false, "Some test failed with error " + e);
-    }).then(SimpleTest.finish);
-  }
-
-  SpecialPowers.addPermission("desktop-notification", false, document);
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
-    ]}, runTest);
-  SimpleTest.waitForExplicitFinish();
+  add_task(async function unregister() {
+    var result = await registration.unregister();
+    ok(result, "Unregister should return true.");
+  });
 
 </script>
 </body>
 </html>
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -97,17 +97,16 @@ class nsMixedContentEvent : public Runna
     NS_ASSERTION(
         sameTypeRoot,
         "No document shell root tree item from document shell tree item!");
 
     // now get the document from sameTypeRoot
     nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
     NS_ASSERTION(rootDoc,
                  "No root document from document shell root tree item.");
-    ContentBlockingLog* contentBlockingLog = rootDoc->GetContentBlockingLog();
 
     // Get eventSink and the current security state from the docShell
     nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
     NS_ASSERTION(eventSink, "No eventSink from docShell.");
     nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
     NS_ASSERTION(rootShell,
                  "No root docshell from document shell root tree item.");
     uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
@@ -141,28 +140,26 @@ class nsMixedContentEvent : public Runna
 
           // If mixed display content is loaded, make sure to include that in
           // the state.
           if (rootDoc->GetHasMixedDisplayContentLoaded()) {
             state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
           }
 
           eventSink->OnSecurityChange(
-              mContext, state,
+              mContext,
               (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
-              contentBlockingLog);
+               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         } else {
           // root not secure, mixed active content loaded in an https subframe
           if (NS_SUCCEEDED(stateRV)) {
             eventSink->OnSecurityChange(
-                mContext, state,
+                mContext,
                 (state |
-                 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
-                contentBlockingLog);
+                 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
           }
         }
       }
 
     } else if (mType == eMixedDisplay) {
       // See if the pref will change here. If it will, only then do we need to
       // call OnSecurityChange() to update the UI.
       if (rootDoc->GetHasMixedDisplayContentLoaded()) {
@@ -183,28 +180,26 @@ class nsMixedContentEvent : public Runna
 
           // If mixed active content is loaded, make sure to include that in the
           // state.
           if (rootDoc->GetHasMixedActiveContentLoaded()) {
             state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
           }
 
           eventSink->OnSecurityChange(
-              mContext, state,
+              mContext,
               (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
-              contentBlockingLog);
+               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         } else {
           // root not secure, mixed display content loaded in an https subframe
           if (NS_SUCCEEDED(stateRV)) {
             eventSink->OnSecurityChange(
-                mContext, state,
+                mContext,
                 (state |
-                 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
-                contentBlockingLog);
+                 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
           }
         }
       }
     }
 
     return NS_OK;
   }
 
@@ -895,17 +890,16 @@ nsresult nsMixedContentBlocker::ShouldLo
       *aDecision = nsIContentPolicy::ACCEPT;
       return NS_OK;
     }
   }
 
   // Get the root document from the sameTypeRoot
   nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
   NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
-  ContentBlockingLog* contentBlockingLog = rootDoc->GetContentBlockingLog();
 
   // Get eventSink and the current security state from the docShell
   nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
   NS_ASSERTION(eventSink, "No eventSink from docShell.");
   nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
   NS_ASSERTION(rootShell,
                "No root docshell from document shell root tree item.");
   uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
@@ -986,43 +980,40 @@ nsresult nsMixedContentBlocker::ShouldLo
 
         // If mixed active content is loaded, make sure to include that in the
         // state.
         if (rootDoc->GetHasMixedActiveContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
         }
 
         eventSink->OnSecurityChange(
-            aRequestingContext, state,
+            aRequestingContext,
             (state |
-             nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
-            contentBlockingLog);
+             nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
       } else {
         // User has overriden the pref and the root is not https;
         // mixed display content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
           eventSink->OnSecurityChange(
-              aRequestingContext, state,
+              aRequestingContext,
               (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT),
-              contentBlockingLog);
+               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         }
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc,
                              eBlocked);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() &&
           NS_SUCCEEDED(stateRV)) {
         rootDoc->SetHasMixedDisplayContentBlocked(true);
         eventSink->OnSecurityChange(
-            aRequestingContext, state,
+            aRequestingContext,
             (state |
-             nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT),
-            contentBlockingLog);
+             nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
       }
     }
     return NS_OK;
 
   } else if (sBlockMixedScript && classification == eMixedScript) {
     // If the content is active content, and the pref says active content should
     // be blocked, block it unless the user has choosen to override the pref
     if (allowMixedContent) {
@@ -1044,30 +1035,29 @@ nsresult nsMixedContentBlocker::ShouldLo
 
         // If mixed display content is loaded, make sure to include that in the
         // state.
         if (rootDoc->GetHasMixedDisplayContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
         }
 
         eventSink->OnSecurityChange(
-            aRequestingContext, state,
-            (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
-            contentBlockingLog);
+            aRequestingContext,
+            (state |
+             nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
 
         return NS_OK;
       } else {
         // User has already overriden the pref and the root is not https;
         // mixed active content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
           eventSink->OnSecurityChange(
-              aRequestingContext, state,
+              aRequestingContext,
               (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT),
-              contentBlockingLog);
+               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         }
         return NS_OK;
       }
     } else {
       // User has not overriden the pref by Disabling protection. Reject the
       // request and update the security state.
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, rootDoc,
@@ -1078,20 +1068,19 @@ nsresult nsMixedContentBlocker::ShouldLo
         return NS_OK;
       }
       rootDoc->SetHasMixedActiveContentBlocked(true);
 
       // The user has not overriden the pref, so make sure they still have an
       // option by calling eventSink which will invoke the doorhanger
       if (NS_SUCCEEDED(stateRV)) {
         eventSink->OnSecurityChange(
-            aRequestingContext, state,
+            aRequestingContext,
             (state |
-             nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT),
-            contentBlockingLog);
+             nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
       }
       return NS_OK;
     }
   } else {
     // The content is not blocked by the mixed content prefs.
 
     // Log a message that we are loading mixed content.
     LogMixedContentMessage(classification, aContentLocation, rootDoc,
--- a/dom/webidl/FrameLoader.webidl
+++ b/dom/webidl/FrameLoader.webidl
@@ -29,16 +29,22 @@ interface FrameLoader {
   /**
    * Get an nsILoadContext for the top-level docshell. For remote
    * frames, a shim is returned that contains private browsing and app
    * information.
    */
   readonly attribute LoadContext loadContext;
 
   /**
+   * Get the root BrowsingContext within the frame.
+   * This may be null immediately after creating a remote frame.
+   */
+  readonly attribute BrowsingContext? browsingContext;
+
+  /**
    * Get the ParentSHistory for the nsFrameLoader. May return null if this
    * frameloader is not for a toplevel frame.
    */
   readonly attribute ParentSHistory? parentSHistory;
 
   /**
    * Find out whether the loader's frame is at too great a depth in
    * the frame tree.  This can be used to decide what operations may
--- a/editor/composer/nsEditingSession.cpp
+++ b/editor/composer/nsEditingSession.cpp
@@ -782,19 +782,17 @@ nsEditingSession::OnStatusChange(nsIWebP
 
 /*---------------------------------------------------------------------------
 
   OnSecurityChange
 
 ----------------------------------------------------------------------------*/
 NS_IMETHODIMP
 nsEditingSession::OnSecurityChange(nsIWebProgress* aWebProgress,
-                                   nsIRequest* aRequest, uint32_t aOldState,
-                                   uint32_t aState,
-                                   const nsAString& aContentBlockingLogJSON) {
+                                   nsIRequest* aRequest, uint32_t state) {
   MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
   return NS_OK;
 }
 
 /*---------------------------------------------------------------------------
 
   IsProgressForTargetDocument
 
--- a/editor/composer/test/test_bug434998.xul
+++ b/editor/composer/test/test_bug434998.xul
@@ -82,18 +82,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
     {
     },
 
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
     {
     },
 
-    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
-                                aContentBlockingLogJSON)
+    onSecurityChange : function(aWebProgress, aRequest, aState)
     {
     },
 
     mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug366682.html
+++ b/editor/libeditor/tests/test_bug366682.html
@@ -38,16 +38,17 @@ function getEditor() {
   var editingSession = SpecialPowers.wrap(win).docShell.editingSession;
   return editingSession.getEditorForWindow(win);
 }
 
 function runTest() {
   editDoc().body.innerHTML = "<div>errror and an other errror</div>";
   gMisspeltWords = ["errror", "errror"];
   editDoc().designMode = "on";
+  editDoc().defaultView.focus();
 
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
                .onSpellCheck(editDoc().documentElement, evalTest);
 }
 
 function evalTest() {
   ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
--- a/editor/libeditor/tests/test_bug607584.xul
+++ b/editor/libeditor/tests/test_bug607584.xul
@@ -88,18 +88,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
       {
       },
   
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
       {
       },
   
-    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
-                                aContentBlockingLogJSON)
+    onSecurityChange : function(aWebProgress, aRequest, aState)
       {
       },
   
       mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug616590.xul
+++ b/editor/libeditor/tests/test_bug616590.xul
@@ -77,18 +77,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
     {
     },
 
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
     {
     },
 
-    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
-                                aContentBlockingLogJSON)
+    onSecurityChange : function(aWebProgress, aRequest, aState)
     {
     },
 
     mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/libeditor/tests/test_bug780908.xul
+++ b/editor/libeditor/tests/test_bug780908.xul
@@ -86,18 +86,17 @@ adapted from test_bug607584.xul by Kent 
     onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
       {
       },
   
     onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
       {
       },
   
-    onSecurityChange : function(aWebProgress, aRequest, aOldState, aState,
-                                aContentBlockingLogJSON)
+    onSecurityChange : function(aWebProgress, aRequest, aState)
       {
       },
   
       mEditor: null
   };
 
   var progress, progressListener;
 
--- a/editor/reftests/reftest.list
+++ b/editor/reftests/reftest.list
@@ -114,17 +114,18 @@ needs-focus != 824080-4.html 824080-5.ht
 needs-focus == 824080-6.html 824080-6-ref.html
 needs-focus pref(layout.accessiblecaret.enabled,false) pref(layout.accessiblecaret.enabled_on_touch,false) == 824080-7.html 824080-7-ref.html
 needs-focus != 824080-6.html 824080-7.html
 # Bug 674927: copy spellcheck-textarea tests to contenteditable
 == spellcheck-contenteditable-attr.html spellcheck-contenteditable-nofocus-ref.html
 fails-if(Android) needs-focus != spellcheck-contenteditable-attr.html spellcheck-contenteditable-ref.html
 needs-focus == spellcheck-contenteditable-focused.html spellcheck-contenteditable-ref.html
 needs-focus == spellcheck-contenteditable-focused-reframe.html spellcheck-contenteditable-ref.html
-== spellcheck-contenteditable-nofocus.html spellcheck-contenteditable-disabled-ref.html
+== spellcheck-contenteditable-nofocus-1.html spellcheck-contenteditable-disabled-ref.html
+== spellcheck-contenteditable-nofocus-2.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-disabled.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-disabled-partial.html spellcheck-contenteditable-disabled-partial-ref.html
 == spellcheck-contenteditable-attr-inherit.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic-inherit.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-property-dynamic.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-property-dynamic-inherit.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic-override.html spellcheck-contenteditable-disabled-ref.html
rename from editor/reftests/spellcheck-contenteditable-nofocus.html
rename to editor/reftests/spellcheck-contenteditable-nofocus-1.html
new file mode 100644
--- /dev/null
+++ b/editor/reftests/spellcheck-contenteditable-nofocus-2.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><body><div contenteditable>blahblahblah</div></body></html>
\ No newline at end of file
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -641,37 +641,40 @@ EditorSpellCheck::UpdateCurrentDictionar
   nsresult rv;
 
   RefPtr<EditorSpellCheck> kungFuDeathGrip = this;
 
   // Get language with html5 algorithm
   nsCOMPtr<nsIContent> rootContent;
   HTMLEditor* htmlEditor = mEditor->AsHTMLEditor();
   if (htmlEditor) {
-    rootContent = htmlEditor->GetActiveEditingHost();
+    rootContent = htmlEditor->GetFocusedContent();
   } else {
     rootContent = mEditor->GetRoot();
   }
 
+  if (!rootContent) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Try to get topmost document's document element for embedded mail editor.
   uint32_t flags = 0;
   mEditor->GetFlags(&flags);
   if (flags & nsIPlaintextEditor::eEditorMailMask) {
     nsCOMPtr<nsIDocument> ownerDoc = rootContent->OwnerDoc();
     NS_ENSURE_TRUE(ownerDoc, NS_ERROR_FAILURE);
     nsIDocument* parentDoc = ownerDoc->GetParentDocument();
     if (parentDoc) {
       rootContent = parentDoc->GetDocumentElement();
+      if (!rootContent) {
+        return NS_ERROR_FAILURE;
+      }
     }
   }
 
-  if (!rootContent) {
-    return NS_ERROR_FAILURE;
-  }
-
   RefPtr<DictionaryFetcher> fetcher =
       new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup);
   rootContent->GetLang(fetcher->mRootContentLang);
   nsCOMPtr<nsIDocument> doc = rootContent->GetComposedDoc();
   NS_ENSURE_STATE(doc);
   doc->GetContentLanguage(fetcher->mRootDocContentLang);
 
   rv = fetcher->Fetch(mEditor);
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -807,31 +807,33 @@ struct ScrollMetadata {
         mLineScrollAmount(0, 0),
         mPageScrollAmount(0, 0),
         mScrollClip(),
         mHasScrollgrab(false),
         mIsLayersIdRoot(false),
         mIsAutoDirRootContentRTL(false),
         mUsesContainerScrolling(false),
         mForceDisableApz(false),
+        mResolutionUpdated(false),
         mOverscrollBehavior() {}
 
   bool operator==(const ScrollMetadata& aOther) const {
     return mMetrics == aOther.mMetrics && mSnapInfo == aOther.mSnapInfo &&
            mScrollParentId == aOther.mScrollParentId &&
            mBackgroundColor == aOther.mBackgroundColor &&
            // don't compare mContentDescription
            mLineScrollAmount == aOther.mLineScrollAmount &&
            mPageScrollAmount == aOther.mPageScrollAmount &&
            mScrollClip == aOther.mScrollClip &&
            mHasScrollgrab == aOther.mHasScrollgrab &&
            mIsLayersIdRoot == aOther.mIsLayersIdRoot &&
            mIsAutoDirRootContentRTL == aOther.mIsAutoDirRootContentRTL &&
            mUsesContainerScrolling == aOther.mUsesContainerScrolling &&
            mForceDisableApz == aOther.mForceDisableApz &&
+           mResolutionUpdated == aOther.mResolutionUpdated &&
            mDisregardedDirection == aOther.mDisregardedDirection &&
            mOverscrollBehavior == aOther.mOverscrollBehavior;
   }
 
   bool operator!=(const ScrollMetadata& aOther) const {
     return !operator==(aOther);
   }
 
@@ -902,16 +904,18 @@ struct ScrollMetadata {
   // Implemented out of line because the implementation needs gfxPrefs.h
   // and we don't want to include that from FrameMetrics.h.
   void SetUsesContainerScrolling(bool aValue);
   bool UsesContainerScrolling() const { return mUsesContainerScrolling; }
   void SetForceDisableApz(bool aForceDisable) {
     mForceDisableApz = aForceDisable;
   }
   bool IsApzForceDisabled() const { return mForceDisableApz; }
+  void SetResolutionUpdated(bool aUpdated) { mResolutionUpdated = aUpdated; }
+  bool IsResolutionUpdated() const { return mResolutionUpdated; }
 
   // For more details about the concept of a disregarded direction, refer to the
   // code which defines mDisregardedDirection.
   Maybe<ScrollDirection> GetDisregardedDirection() const {
     return mDisregardedDirection;
   }
   void SetDisregardedDirection(const Maybe<ScrollDirection>& aValue) {
     mDisregardedDirection = aValue;
@@ -978,16 +982,21 @@ struct ScrollMetadata {
   // True if scrolling using containers, false otherwise. This can be removed
   // when containerful scrolling is eliminated.
   bool mUsesContainerScrolling : 1;
 
   // Whether or not the compositor should actually do APZ-scrolling on this
   // scrollframe.
   bool mForceDisableApz : 1;
 
+  // Whether the pres shell resolution stored in mMetrics reflects a change
+  // originated by the main thread. Plays a similar role for the resolution as
+  // FrameMetrics::mScrollUpdateType) does for the scroll offset.
+  bool mResolutionUpdated : 1;
+
   // The disregarded direction means the direction which is disregarded anyway,
   // even if the scroll frame overflows in that direction and the direction is
   // specified as scrollable. This could happen in some scenarios, for instance,
   // a single-line text control frame should disregard wheel scroll in
   // its block-flow direction even if it overflows in that direction.
   Maybe<ScrollDirection> mDisregardedDirection;
 
   // The overscroll behavior for this scroll frame.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -4413,21 +4413,23 @@ void AsyncPanZoomController::NotifyLayer
                this);
       needContentRepaint = true;
     }
   } else {
     // If we're not taking the aLayerMetrics wholesale we still need to pull
     // in some things into our local Metrics() because these things are
     // determined by Gecko and our copy in Metrics() may be stale.
 
+    // TODO: Rely entirely on |aScrollMetadata.IsResolutionUpdated()| to
+    //       determine which branch to take, and drop the other conditions.
     if (FuzzyEqualsAdditive(Metrics().GetCompositionBounds().Width(),
                             aLayerMetrics.GetCompositionBounds().Width()) &&
         Metrics().GetDevPixelsPerCSSPixel() ==
             aLayerMetrics.GetDevPixelsPerCSSPixel() &&
-        !viewportUpdated) {
+        !viewportUpdated && !aScrollMetadata.IsResolutionUpdated()) {
       // Any change to the pres shell resolution was requested by APZ and is
       // already included in our zoom; however, other components of the
       // cumulative resolution (a parent document's pres-shell resolution, or
       // the css-driven resolution) may have changed, and we need to update
       // our zoom to reflect that. Note that we can't just take
       // aLayerMetrics.mZoom because the APZ may have additional async zoom
       // since the repaint request.
       gfxSize totalResolutionChange = aLayerMetrics.GetCumulativeResolution() /
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -312,17 +312,17 @@ void APZCCallbackHelper::UpdateRootFrame
                                    aRequest.GetPresShellResolution())) {
       return;
     }
 
     // The pres shell resolution is updated by the the async zoom since the
     // last paint.
     presShellResolution =
         aRequest.GetPresShellResolution() * aRequest.GetAsyncZoom().scale;
-    shell->SetResolutionAndScaleTo(presShellResolution);
+    shell->SetResolutionAndScaleTo(presShellResolution, nsGkAtoms::apz);
   }
 
   // Do this as late as possible since scrolling can flush layout. It also
   // adjusts the display port margins, so do it before we set those.
   ScreenMargin displayPortMargins = ScrollFrame(content, aRequest);
 
   SetDisplayPortMargins(shell, content, displayPortMargins,
                         aRequest.CalculateCompositedSizeInCssPixels());
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -334,16 +334,17 @@ struct ParamTraits<mozilla::layers::Scro
     WriteParam(aMsg, aParam.mLineScrollAmount);
     WriteParam(aMsg, aParam.mPageScrollAmount);
     WriteParam(aMsg, aParam.mScrollClip);
     WriteParam(aMsg, aParam.mHasScrollgrab);
     WriteParam(aMsg, aParam.mIsLayersIdRoot);
     WriteParam(aMsg, aParam.mIsAutoDirRootContentRTL);
     WriteParam(aMsg, aParam.mUsesContainerScrolling);
     WriteParam(aMsg, aParam.mForceDisableApz);
+    WriteParam(aMsg, aParam.mResolutionUpdated);
     WriteParam(aMsg, aParam.mDisregardedDirection);
     WriteParam(aMsg, aParam.mOverscrollBehavior);
   }
 
   static bool ReadContentDescription(const Message* aMsg, PickleIterator* aIter,
                                      paramType* aResult) {
     nsCString str;
     if (!ReadParam(aMsg, aIter, &str)) {
@@ -368,16 +369,18 @@ struct ParamTraits<mozilla::layers::Scro
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsLayersIdRoot) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsAutoDirRootContentRTL) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetUsesContainerScrolling) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetForceDisableApz) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult,
+                                &paramType::SetResolutionUpdated) &&
             ReadParam(aMsg, aIter, &aResult->mDisregardedDirection) &&
             ReadParam(aMsg, aIter, &aResult->mOverscrollBehavior));
   }
 };
 
 template <>
 struct ParamTraits<mozilla::layers::TextureFactoryIdentifier> {
   typedef mozilla::layers::TextureFactoryIdentifier paramType;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -914,16 +914,17 @@ void gfxPlatform::Init() {
   gPlatform->InitOMTPConfig();
 
   if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
     GPUProcessManager* gpu = GPUProcessManager::Get();
     gpu->LaunchGPUProcess();
   }
 
   if (XRE_IsParentProcess() &&
+      BrowserTabsRemoteAutostart() &&  // only do rdd process if e10s on
       Preferences::GetBool("media.rdd-process.enabled", false)) {
     RDDProcessManager* rdd = RDDProcessManager::Get();
     if (rdd) {
       rdd->LaunchRDDProcess();
     }
   }
 
   if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-6ca634197cfe26738194f87042020fe838c0047a
+7c4162c581978d1a72ed2271a94382290855e227
--- a/gfx/wr/webrender/res/brush.glsl
+++ b/gfx/wr/webrender/res/brush.glsl
@@ -44,16 +44,17 @@ void main(void) {
         segment_data = vec4(0.0);
     } else {
         int segment_address = ph.specific_prim_address +
                               VECS_PER_SPECIFIC_BRUSH +
                               segment_index * VECS_PER_SEGMENT;
 
         vec4[2] segment_info = fetch_from_gpu_cache_2(segment_address);
         segment_rect = RectWithSize(segment_info[0].xy, segment_info[0].zw);
+        segment_rect.p0 += ph.local_rect.p0;
         segment_data = segment_info[1];
     }
 
     VertexInfo vi;
 
     // Fetch the dynamic picture that we are drawing on.
     PictureTask pic_task = fetch_picture_task(ph.render_task_index);
     ClipArea clip_area = fetch_clip_area(clip_address);
--- a/gfx/wr/webrender/res/brush_blend.glsl
+++ b/gfx/wr/webrender/res/brush_blend.glsl
@@ -143,20 +143,16 @@ vec3 LinearToSrgb(vec3 color) {
     vec3 c1 = color * 12.92;
     vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
     return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
 }
 
 Fragment brush_fs() {
     vec4 Cs = texture(sColor0, vUv);
 
-    if (Cs.a == 0.0) {
-        return Fragment(vec4(0.0)); // could also `discard`
-    }
-
     // Un-premultiply the input.
     float alpha = Cs.a;
     vec3 color = Cs.rgb / Cs.a;
 
     switch (vOp) {
         case 0:
             break;
         case 1:
--- a/gfx/wr/webrender/res/brush_mix_blend.glsl
+++ b/gfx/wr/webrender/res/brush_mix_blend.glsl
@@ -203,26 +203,24 @@ const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 Fragment brush_fs() {
     vec4 Cb = textureLod(sPrevPassColor, vBackdropUv, 0.0);
     vec4 Cs = textureLod(sPrevPassColor, vSrcUv, 0.0);
 
-    if (Cb.a == 0.0) {
-        return Fragment(Cs);
-    }
-    if (Cs.a == 0.0) {
-        return Fragment(vec4(0.0));
+    // The mix-blend-mode functions assume no premultiplied alpha
+    if (Cb.a != 0.0) {
+        Cb.rgb /= Cb.a;
     }
 
-    // The mix-blend-mode functions assume no premultiplied alpha
-    Cb.rgb /= Cb.a;
-    Cs.rgb /= Cs.a;
+    if (Cs.a != 0.0) {
+        Cs.rgb /= Cs.a;
+    }
 
     // Return yellow if none of the branches match (shouldn't happen).
     vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
 
     switch (vOp) {
         case MixBlendMode_Multiply:
             result.rgb = Multiply(Cb.rgb, Cs.rgb);
             break;
--- a/gfx/wr/webrender/res/clip_shared.glsl
+++ b/gfx/wr/webrender/res/clip_shared.glsl
@@ -12,35 +12,41 @@
 #define SEGMENT_CORNER_BL   3
 #define SEGMENT_CORNER_BR   4
 
 in int aClipRenderTaskAddress;
 in int aClipTransformId;
 in int aPrimTransformId;
 in int aClipSegment;
 in ivec4 aClipDataResourceAddress;
+in vec2 aClipLocalPos;
+in vec4 aClipTileRect;
 
 struct ClipMaskInstance {
     int render_task_address;
     int clip_transform_id;
     int prim_transform_id;
     int segment;
     ivec2 clip_data_address;
     ivec2 resource_address;
+    vec2 local_pos;
+    RectWithSize tile_rect;
 };
 
 ClipMaskInstance fetch_clip_item() {
     ClipMaskInstance cmi;
 
     cmi.render_task_address = aClipRenderTaskAddress;
     cmi.clip_transform_id = aClipTransformId;
     cmi.prim_transform_id = aPrimTransformId;
     cmi.segment = aClipSegment;
     cmi.clip_data_address = aClipDataResourceAddress.xy;
     cmi.resource_address = aClipDataResourceAddress.zw;
+    cmi.local_pos = aClipLocalPos;
+    cmi.tile_rect = RectWithSize(aClipTileRect.xy, aClipTileRect.zw);
 
     return cmi;
 }
 
 struct ClipVertexInfo {
     vec3 local_pos;
     RectWithSize clipped_local_rect;
 };
--- a/gfx/wr/webrender/res/cs_clip_box_shadow.glsl
+++ b/gfx/wr/webrender/res/cs_clip_box_shadow.glsl
@@ -41,18 +41,21 @@ BoxShadowData fetch_data(ivec2 address) 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Transform clip_transform = fetch_transform(cmi.clip_transform_id);
     Transform prim_transform = fetch_transform(cmi.prim_transform_id);
     BoxShadowData bs_data = fetch_data(cmi.clip_data_address);
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
+    RectWithSize dest_rect = bs_data.dest_rect;
+    dest_rect.p0 += cmi.local_pos;
+
     ClipVertexInfo vi = write_clip_tile_vertex(
-        bs_data.dest_rect,
+        dest_rect,
         prim_transform,
         clip_transform,
         area
     );
     vLocalPos = vi.local_pos;
     vLayer = res.layer;
     vClipMode = bs_data.clip_mode;
 
@@ -60,39 +63,39 @@ void main(void) {
     vec2 uv1 = res.uv_rect.p1;
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 local_pos = vLocalPos.xy / vLocalPos.z;
 
     switch (bs_data.stretch_mode_x) {
         case MODE_STRETCH: {
             vEdge.x = 0.5;
-            vEdge.z = (bs_data.dest_rect.size.x / bs_data.src_rect_size.x) - 0.5;
-            vUv.x = (local_pos.x - bs_data.dest_rect.p0.x) / bs_data.src_rect_size.x;
+            vEdge.z = (dest_rect.size.x / bs_data.src_rect_size.x) - 0.5;
+            vUv.x = (local_pos.x - dest_rect.p0.x) / bs_data.src_rect_size.x;
             break;
         }
         case MODE_SIMPLE:
         default: {
             vEdge.xz = vec2(1.0);
-            vUv.x = (local_pos.x - bs_data.dest_rect.p0.x) / bs_data.dest_rect.size.x;
+            vUv.x = (local_pos.x - dest_rect.p0.x) / dest_rect.size.x;
             break;
         }
     }
 
     switch (bs_data.stretch_mode_y) {
         case MODE_STRETCH: {
             vEdge.y = 0.5;
-            vEdge.w = (bs_data.dest_rect.size.y / bs_data.src_rect_size.y) - 0.5;
-            vUv.y = (local_pos.y - bs_data.dest_rect.p0.y) / bs_data.src_rect_size.y;
+            vEdge.w = (dest_rect.size.y / bs_data.src_rect_size.y) - 0.5;
+            vUv.y = (local_pos.y - dest_rect.p0.y) / bs_data.src_rect_size.y;
             break;
         }
         case MODE_SIMPLE:
         default: {
             vEdge.yw = vec2(1.0);
-            vUv.y = (local_pos.y - bs_data.dest_rect.p0.y) / bs_data.dest_rect.size.y;
+            vUv.y = (local_pos.y - dest_rect.p0.y) / dest_rect.size.y;
             break;
         }
     }
 
     vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
     vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
 }
 #endif
--- a/gfx/wr/webrender/res/cs_clip_image.glsl
+++ b/gfx/wr/webrender/res/cs_clip_image.glsl
@@ -8,46 +8,43 @@ varying vec2 vLocalPos;
 varying vec2 vClipMaskImageUv;
 
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
 flat varying float vLayer;
 
 #ifdef WR_VERTEX_SHADER
 struct ImageMaskData {
-    RectWithSize local_mask_rect;
-    RectWithSize local_tile_rect;
+    vec2 local_mask_size;
 };
 
 ImageMaskData fetch_mask_data(ivec2 address) {
-    vec4 data[2] = fetch_from_gpu_cache_2_direct(address);
-    RectWithSize mask_rect = RectWithSize(data[0].xy, data[0].zw);
-    RectWithSize tile_rect = RectWithSize(data[1].xy, data[1].zw);
-    ImageMaskData mask_data = ImageMaskData(mask_rect, tile_rect);
+    vec4 data = fetch_from_gpu_cache_1_direct(address);
+    ImageMaskData mask_data = ImageMaskData(data.xy);
     return mask_data;
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Transform clip_transform = fetch_transform(cmi.clip_transform_id);
     Transform prim_transform = fetch_transform(cmi.prim_transform_id);
     ImageMaskData mask = fetch_mask_data(cmi.clip_data_address);
-    RectWithSize local_rect = mask.local_mask_rect;
+    RectWithSize local_rect = RectWithSize(cmi.local_pos, mask.local_mask_size);
     ImageResource res = fetch_image_resource_direct(cmi.resource_address);
 
     ClipVertexInfo vi = write_clip_tile_vertex(
         local_rect,
         prim_transform,
         clip_transform,
         area
     );
     vLocalPos = vi.local_pos.xy / vi.local_pos.z;
     vLayer = res.layer;
-    vClipMaskImageUv = (vLocalPos - mask.local_tile_rect.p0) / mask.local_tile_rect.size;
+    vClipMaskImageUv = (vLocalPos - cmi.tile_rect.p0) / cmi.tile_rect.size;
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
     vec4 inner_rect = vec4(res.uv_rect.p0, res.uv_rect.p1);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
 #endif
 
--- a/gfx/wr/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/wr/webrender/res/cs_clip_rectangle.glsl
@@ -58,17 +58,19 @@ ClipData fetch_clip(ivec2 address) {
 }
 
 void main(void) {
     ClipMaskInstance cmi = fetch_clip_item();
     ClipArea area = fetch_clip_area(cmi.render_task_address);
     Transform clip_transform = fetch_transform(cmi.clip_transform_id);
     Transform prim_transform = fetch_transform(cmi.prim_transform_id);
     ClipData clip = fetch_clip(cmi.clip_data_address);
+
     RectWithSize local_rect = clip.rect.rect;
+    local_rect.p0 = cmi.local_pos;
 
     ClipVertexInfo vi = write_clip_tile_vertex(
         local_rect,
         prim_transform,
         clip_transform,
         area
     );
 
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -581,17 +581,17 @@ impl AlphaBatchBuilder {
 
                 let batch_key = BatchKey {
                     blend_mode: BlendMode::PremultipliedDestOut,
                     kind: BatchKind::Brush(BrushBatchKind::Solid),
                     textures: BatchTextures::no_texture(),
                 };
 
                 let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: 0,
+                    segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: 0,
                 });
 
                 self.batch_list.push_single_instance(
@@ -857,17 +857,17 @@ impl AlphaBatchBuilder {
 
                 let batch_key = BatchKey {
                     blend_mode,
                     kind: BatchKind::Brush(batch_kind),
                     textures: textures,
                 };
 
                 let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: 0,
+                    segment_index: INVALID_SEGMENT_INDEX,
                     edge_flags: EdgeAaSegmentMask::all(),
                     clip_task_address,
                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                     prim_header_index,
                     user_data: segment_user_data,
                 });
 
                 self.batch_list.push_single_instance(
@@ -1102,17 +1102,17 @@ impl AlphaBatchBuilder {
                                         let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
-                                            segment_index: 0,
+                                            segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             clip_task_address,
                                             user_data: uv_rect_address.as_int(),
                                         };
 
                                         self.batch_list.push_single_instance(
                                             key,
@@ -1184,26 +1184,26 @@ impl AlphaBatchBuilder {
                                             ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
                                         ]);
 
                                         let shadow_instance = BrushInstance {
                                             prim_header_index: shadow_prim_header_index,
                                             clip_task_address,
-                                            segment_index: 0,
+                                            segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: shadow_uv_rect_address,
                                         };
 
                                         let content_instance = BrushInstance {
                                             prim_header_index: content_prim_header_index,
                                             clip_task_address,
-                                            segment_index: 0,
+                                            segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: content_uv_rect_address,
                                         };
 
                                         self.batch_list.push_single_instance(
                                             shadow_key,
                                             bounding_rect,
@@ -1278,17 +1278,17 @@ impl AlphaBatchBuilder {
                                             uv_rect_address.as_int(),
                                             filter_mode,
                                             user_data,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
-                                            segment_index: 0,
+                                            segment_index: INVALID_SEGMENT_INDEX,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
                                             user_data: 0,
                                         };
 
                                         self.batch_list.push_single_instance(
                                             key,
                                             bounding_rect,
@@ -1323,17 +1323,17 @@ impl AlphaBatchBuilder {
                                     mode as u32 as i32,
                                     backdrop_task_address.0 as i32,
                                     source_task_address.0 as i32,
                                 ]);
 
                                 let instance = BrushInstance {
                                     prim_header_index,
                                     clip_task_address,
-                                    segment_index: 0,
+                                    segment_index: INVALID_SEGMENT_INDEX,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
                                     user_data: 0,
                                 };
 
                                 self.batch_list.push_single_instance(
                                     key,
                                     bounding_rect,
@@ -1363,17 +1363,17 @@ impl AlphaBatchBuilder {
                                     ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                     RasterizationSpace::Screen as i32,
                                     get_shader_opacity(1.0),
                                 ]);
 
                                 let instance = BrushInstance {
                                     prim_header_index,
                                     clip_task_address,
-                                    segment_index: 0,
+                                    segment_index: INVALID_SEGMENT_INDEX,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::empty(),
                                     user_data: uv_rect_address,
                                 };
 
                                 self.batch_list.push_single_instance(
                                     key,
                                     bounding_rect,
@@ -1966,17 +1966,17 @@ impl AlphaBatchBuilder {
         bounding_rect: &PictureRect,
         edge_flags: EdgeAaSegmentMask,
         uv_rect_address: GpuCacheAddress,
         z_id: ZBufferId,
     ) {
         let base_instance = BrushInstance {
             prim_header_index,
             clip_task_address,
-            segment_index: 0,
+            segment_index: INVALID_SEGMENT_INDEX,
             edge_flags,
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
             user_data: uv_rect_address.as_int(),
         };
 
         let batch_key = BatchKey {
             blend_mode,
             kind: BatchKind::Brush(batch_kind),
@@ -2183,17 +2183,17 @@ fn add_gradient_tiles(
             ..*base_prim_header
         };
         let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
 
         batch.push(PrimitiveInstanceData::from(
             BrushInstance {
                 prim_header_index,
                 clip_task_address,
-                segment_index: 0,
+                segment_index: INVALID_SEGMENT_INDEX,
                 edge_flags: EdgeAaSegmentMask::all(),
                 brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                 user_data: 0,
             }
         ));
     }
 }
 
@@ -2437,24 +2437,27 @@ impl ClipBatcher {
             box_shadows: FastHashMap::default(),
         }
     }
 
     pub fn add_clip_region(
         &mut self,
         task_address: RenderTaskAddress,
         clip_data_address: GpuCacheAddress,
+        local_pos: LayoutPoint,
     ) {
         let instance = ClipMaskInstance {
             render_task_address: task_address,
             clip_transform_id: TransformPaletteId::IDENTITY,
             prim_transform_id: TransformPaletteId::IDENTITY,
             segment: 0,
             clip_data_address,
             resource_address: GpuCacheAddress::invalid(),
+            local_pos,
+            tile_rect: LayoutRect::zero(),
         };
 
         self.rectangles.push(instance);
     }
 
     pub fn add(
         &mut self,
         task_address: RenderTaskAddress,
@@ -2485,57 +2488,63 @@ impl ClipBatcher {
 
             let instance = ClipMaskInstance {
                 render_task_address: task_address,
                 clip_transform_id,
                 prim_transform_id,
                 segment: 0,
                 clip_data_address: GpuCacheAddress::invalid(),
                 resource_address: GpuCacheAddress::invalid(),
+                local_pos: clip_instance.local_pos,