Merge mozilla-central to mozilla-inbound.
authorCosmin Sabou <csabou@mozilla.com>
Tue, 27 Aug 2019 12:45:36 +0300
changeset 554011 993cf82f6bbf09537f4cec8144cb69bfdf26bff3
parent 554010 677f1b4ac3292096396125dfb2a31c9fc4a5803b (current diff)
parent 553757 c1835ddb51e2c60b6286a12baf3e6c35e13217ed (diff)
child 554012 6fdb1282586def80e1a0d8c4c85f84e44a860b66
child 554014 543547cf233a08968b6c77ef43cd5dc273a86137
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone70.0a1
first release with
nightly linux32
993cf82f6bbf / 70.0a1 / 20190827094743 / files
nightly linux64
993cf82f6bbf / 70.0a1 / 20190827094743 / files
nightly mac
993cf82f6bbf / 70.0a1 / 20190827094743 / files
nightly win32
993cf82f6bbf / 70.0a1 / 20190827094743 / files
nightly win64
993cf82f6bbf / 70.0a1 / 20190827094743 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound.
.cargo/.gitignore
browser/components/aboutlogins/LoginBreaches.jsm
browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
netwerk/system/linux/nsNotifyAddrListener_Linux.h
testing/web-platform/meta/speech-api/SpeechSynthesis-speak-twice.html.ini
testing/web-platform/meta/workers/SharedWorker-exception-propagation.html.ini
toolkit/components/passwordmgr/test/unit/test_vulnerable_passwords.js
deleted file mode 100644
--- a/.cargo/.gitignore
+++ /dev/null
@@ -1,1 +0,0 @@
-config
copy from .cargo/config.in
copy to .cargo/config
--- a/.cargo/config.in
+++ b/.cargo/config
@@ -1,45 +1,41 @@
-# Note: if you add more configure substitutions here with required values
-# you will also need to fix the sed commands in:
-# taskcluster/scripts/builder/build-sm-mozjs-crate.sh
-# taskcluster/scripts/builder/build-sm-rust-bindings.sh
+# This file contains vendoring instructions for cargo.
+# It was generated by `mach vendor rust`.
+# Please do not edit.
 
-[source.crates-io]
-registry = 'https://github.com/rust-lang/crates.io-index'
-replace-with = 'vendored-sources'
-
-[source."https://github.com/servo/serde"]
-git = "https://github.com/servo/serde"
-branch = "deserialize_from_enums10"
+[source."https://github.com/hsivonen/packed_simd"]
+branch = "rust_1_32"
+git = "https://github.com/hsivonen/packed_simd"
 replace-with = "vendored-sources"
 
-[source."https://github.com/retep998/winapi-rs"]
+[source."https://github.com/froydnj/winapi-rs"]
+branch = "aarch64"
 git = "https://github.com/froydnj/winapi-rs"
-branch = "aarch64"
 replace-with = "vendored-sources"
 
-[source."https://github.com/rust-lang-nursery/packed_simd"]
-git = "https://github.com/hsivonen/packed_simd"
-branch = "rust_1_32"
+[source."https://github.com/alexcrichton/mio-named-pipes"]
+branch = "master"
+git = "https://github.com/alexcrichton/mio-named-pipes"
+replace-with = "vendored-sources"
+
+[source."https://github.com/NikVolf/tokio-named-pipes"]
+branch = "stable"
+git = "https://github.com/NikVolf/tokio-named-pipes"
 replace-with = "vendored-sources"
 
 [source."https://github.com/CraneStation/Cranelift"]
 git = "https://github.com/CraneStation/Cranelift"
+replace-with = "vendored-sources"
 rev = "164f91a1f473e582e18e48d056c51787d9a1c24d"
-replace-with = "vendored-sources"
 
-[source."https://github.com/ChunMinChang/coreaudio-sys"]
-git = "https://github.com/ChunMinChang/coreaudio-sys"
-branch = "gecko-build"
+[source.crates-io]
 replace-with = "vendored-sources"
 
-[source."https://github.com/alexcrichton/mio-named-pipes"]
-git = "https://github.com/alexcrichton/mio-named-pipes"
-replace-with = "vendored-sources"
-
-[source."https://github.com/NikVolf/tokio-named-pipes"]
-git = "https://github.com/NikVolf/tokio-named-pipes"
-branch = "stable"
-replace-with = "vendored-sources"
-
+# Take advantage of the fact that cargo will treat lines starting with #
+# as comments to add preprocessing directives for when this file is included
+# from .cargo/config.in.
+#define REPLACE_NAME vendored-sources
+#define VENDORED_DIRECTORY third_party/rust
+#ifndef top_srcdir
 [source.vendored-sources]
-directory = '@top_srcdir@/third_party/rust'
+directory = "third_party/rust"
+#endif
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -1,45 +1,10 @@
-# Note: if you add more configure substitutions here with required values
-# you will also need to fix the sed commands in:
-# taskcluster/scripts/builder/build-sm-mozjs-crate.sh
-# taskcluster/scripts/builder/build-sm-rust-bindings.sh
-
-[source.crates-io]
-registry = 'https://github.com/rust-lang/crates.io-index'
-replace-with = 'vendored-sources'
+# Please do not edit this file.
 
-[source."https://github.com/servo/serde"]
-git = "https://github.com/servo/serde"
-branch = "deserialize_from_enums10"
-replace-with = "vendored-sources"
-
-[source."https://github.com/retep998/winapi-rs"]
-git = "https://github.com/froydnj/winapi-rs"
-branch = "aarch64"
-replace-with = "vendored-sources"
+# Note: this file is only really needed when objdir is not a subdirectory of
+# the top source directory.
 
-[source."https://github.com/rust-lang-nursery/packed_simd"]
-git = "https://github.com/hsivonen/packed_simd"
-branch = "rust_1_32"
-replace-with = "vendored-sources"
-
-[source."https://github.com/CraneStation/Cranelift"]
-git = "https://github.com/CraneStation/Cranelift"
-rev = "164f91a1f473e582e18e48d056c51787d9a1c24d"
-replace-with = "vendored-sources"
+#include config
+#filter substitution
 
-[source."https://github.com/ChunMinChang/coreaudio-sys"]
-git = "https://github.com/ChunMinChang/coreaudio-sys"
-branch = "gecko-build"
-replace-with = "vendored-sources"
-
-[source."https://github.com/alexcrichton/mio-named-pipes"]
-git = "https://github.com/alexcrichton/mio-named-pipes"
-replace-with = "vendored-sources"
-
-[source."https://github.com/NikVolf/tokio-named-pipes"]
-git = "https://github.com/NikVolf/tokio-named-pipes"
-branch = "stable"
-replace-with = "vendored-sources"
-
-[source.vendored-sources]
-directory = '@top_srcdir@/third_party/rust'
+[source.@REPLACE_NAME@]
+directory = "@top_srcdir@/@VENDORED_DIRECTORY@"
--- a/accessible/base/Logging.cpp
+++ b/accessible/base/Logging.cpp
@@ -353,17 +353,17 @@ static void GetDocLoadEventType(AccEvent
 }
 
 static void DescribeNode(nsINode* aNode, nsAString& aOutDescription) {
   if (!aNode) {
     aOutDescription.AppendLiteral("null");
     return;
   }
 
-  aOutDescription.AppendPrintf("%p, ", (void*)aNode);
+  aOutDescription.AppendPrintf("0x%p, ", (void*)aNode);
   aOutDescription.Append(aNode->NodeInfo()->QualifiedName());
 
   if (!aNode->IsElement()) {
     return;
   }
 
   dom::Element* elm = aNode->AsElement();
 
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -71,16 +71,17 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_ui.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/KeyboardEventBinding.h"
 #include "mozilla/dom/TreeWalker.h"
 
 using namespace mozilla;
@@ -244,17 +245,17 @@ KeyBinding Accessible::AccessKey() const
     }
 
     if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
   }
 
   if (!key) return KeyBinding();
 
   // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
-  switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) {
+  switch (StaticPrefs::ui_key_generalAccessKey()) {
     case -1:
       break;
     case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
       return KeyBinding(key, KeyBinding::kShift);
     case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
       return KeyBinding(key, KeyBinding::kControl);
     case dom::KeyboardEvent_Binding::DOM_VK_ALT:
       return KeyBinding(key, KeyBinding::kAlt);
@@ -270,20 +271,22 @@ KeyBinding Accessible::AccessKey() const
 
   nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
   if (!treeItem) return KeyBinding();
 
   nsresult rv = NS_ERROR_FAILURE;
   int32_t modifierMask = 0;
   switch (treeItem->ItemType()) {
     case nsIDocShellTreeItem::typeChrome:
-      rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask);
+      modifierMask = StaticPrefs::ui_key_chromeAccess();
+      rv = NS_OK;
       break;
     case nsIDocShellTreeItem::typeContent:
-      rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask);
+      modifierMask = StaticPrefs::ui_key_contentAccess();
+      rv = NS_OK;
       break;
   }
 
   return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
 }
 
 KeyBinding Accessible::KeyboardShortcut() const { return KeyBinding(); }
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/crashtests/1572811.html
@@ -0,0 +1,9 @@
+<script>
+function go() {
+  a.style.overflow = "auto";
+}
+</script>
+<body onload=go()>
+<table id="a" background="3">
+<th>
+<textarea style="position:absolute">A</textarea>
--- a/accessible/tests/crashtests/crashtests.list
+++ b/accessible/tests/crashtests/crashtests.list
@@ -1,11 +1,12 @@
 load 448064.xhtml # This test instantiates a11y, so be careful about adding tests before it
 load chrome://reftest/content/crashtests/accessible/tests/crashtests/471493.xul
 asserts-if(!browserIsRemote,2) load 884202.html
+load 1572811.html
 load 890760.html
 load 893515.html
 load 1072792.xhtml
 load 1380199.html
 load 1402999.html
 load 1463962.html
 load 1484778.html
 load 1494707.html
--- a/browser/app/profile/channel-prefs.js
+++ b/browser/app/profile/channel-prefs.js
@@ -1,5 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This pref is in its own file for complex reasons. See the comment in
+// browser/app/Makefile.in, bug 756325, and bug 1431342 for details. Do not add
+// other prefs to this file.
+
 pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1,13 +1,17 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+// Non-static prefs that are specific to desktop Firefox belong in this file
+// (unless there is a compelling and documented reason for them to belong in
+// another file).
+//
 // Please indent all prefs defined within #ifdef/#ifndef conditions. This
 // improves readability, particular for conditional blocks that exceed a single
 // screen.
 
 #filter substitution
 
 #ifdef XP_UNIX
   #ifndef XP_MACOSX
@@ -154,17 +158,17 @@ pref("app.update.url", "https://aus5.moz
 // app.update.interval is in branding section
 // app.update.promptWaitTime is in branding section
 
 // Whether or not to attempt using the service for updates.
 #ifdef MOZ_MAINTENANCE_SERVICE
   pref("app.update.service.enabled", true);
 #endif
 
-#ifdef XP_WIN
+#ifdef MOZ_BITS_DOWNLOAD
   // If set to true, the Update Service will attempt to use Windows BITS to
   // download updates and will fallback to downloading internally if that fails.
   pref("app.update.BITS.enabled", true);
 #endif
 
 // Symmetric (can be overridden by individual extensions) update preferences.
 // e.g.
 //  extensions.{GUID}.update.enabled
@@ -2036,17 +2040,17 @@ pref("devtools.eyedropper.zoom", 6);
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
 // Length to collapse attributes
 pref("devtools.markup.collapseAttributeLength", 120);
 // Whether to auto-beautify the HTML on copy.
 pref("devtools.markup.beautifyOnCopy", false);
 // Whether or not the DOM mutation breakpoints context menu are enabled in the
 // markup view.
-pref("devtools.markup.mutationBreakpoints.enabled", false);
+pref("devtools.markup.mutationBreakpoints.enabled", true);
 
 // DevTools default color unit
 pref("devtools.defaultColorUnit", "authored");
 
 // Enable the Memory tools
 pref("devtools.memory.enabled", true);
 
 pref("devtools.memory.custom-census-displays", "{}");
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -1,14 +1,14 @@
-
-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This file contains branding-specific prefs.
+
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%a2/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
 // Give the user x seconds to react before showing the big UI. default=192 hours
 pref("app.update.promptWaitTime", 691200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This file contains branding-specific prefs.
+
 pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 7200); // 2 hours
 // Give the user x seconds to react before showing the big UI. default=12 hours
 pref("app.update.promptWaitTime", 43200);
 // URL user can browse to manually if for some reason all update installation
--- a/browser/branding/official/pref/firefox-branding.js
+++ b/browser/branding/official/pref/firefox-branding.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This file contains branding-specific prefs.
+
 pref("startup.homepage_override_url", "");
 pref("startup.homepage_welcome_url", "about:welcome");
 pref("startup.homepage_welcome_url.additional", "");
 // Interval: Time between checks for a new version (in seconds)
 pref("app.update.interval", 43200); // 12 hours
 // Give the user x seconds to react before showing the big UI. default=192 hours
 pref("app.update.promptWaitTime", 691200);
 // app.update.url.manual: URL user can browse to manually if for some reason
--- a/browser/branding/unofficial/pref/firefox-branding.js
+++ b/browser/branding/unofficial/pref/firefox-branding.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This file contains branding-specific prefs.
+
 pref("startup.homepage_override_url", "");
 pref("startup.homepage_welcome_url", "");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 86400); // 24 hours
 // Give the user x seconds to react before showing the big UI. default=24 hours
 pref("app.update.promptWaitTime", 86400);
 // URL user can browse to manually if for some reason all update installation
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -502,17 +502,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Corroborate: "resource://gre/modules/Corroborate.jsm",
   Discovery: "resource:///modules/Discovery.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   FirefoxMonitor: "resource:///modules/FirefoxMonitor.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   HybridContentTelemetry: "resource://gre/modules/HybridContentTelemetry.jsm",
   Integration: "resource://gre/modules/Integration.jsm",
-  LoginBreaches: "resource:///modules/LoginBreaches.jsm",
   LiveBookmarkMigrator: "resource:///modules/LiveBookmarkMigrator.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   Normandy: "resource://normandy/Normandy.jsm",
   ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PdfJs: "resource://pdf.js/PdfJs.jsm",
@@ -975,18 +974,16 @@ BrowserGlue.prototype = {
         } else if (data == "migrateMatchBucketsPrefForUI66") {
           this._migrateMatchBucketsPrefForUI66().then(() => {
             Services.obs.notifyObservers(
               null,
               "browser-glue-test",
               "migrateMatchBucketsPrefForUI66-done"
             );
           });
-        } else if (data == "add-breaches-sync-handler") {
-          this._addBreachesSyncHandler();
         }
         break;
       case "initial-migration-will-import-default-bookmarks":
         this._migrationImportsDefaultBookmarks = true;
         break;
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
@@ -2190,44 +2187,27 @@ BrowserGlue.prototype = {
       this._gmpInstallManager = new obj.GMPInstallManager();
       // We don't really care about the results, if someone is interested they
       // can check the log.
       this._gmpInstallManager.simpleCheckAndInstall().catch(() => {});
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       RemoteSettings.init();
-      this._addBreachesSyncHandler();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       PublicSuffixList.init();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       RemoteSecuritySettings.init();
     });
   },
 
-  _addBreachesSyncHandler() {
-    if (
-      Services.prefs.getBoolPref(
-        "signon.management.page.breach-alerts.enabled",
-        false
-      )
-    ) {
-      RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).on(
-        "sync",
-        async event => {
-          await LoginBreaches.update(event.data.current);
-        }
-      );
-    }
-  },
-
   _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
     // If user has already dismissed quit request, then do nothing
     if (aCancelQuit instanceof Ci.nsISupportsPRBool && aCancelQuit.data) {
       return;
     }
 
     // There are several cases where we won't show a dialog here:
     // 1. There is only 1 tab open in 1 window
--- a/browser/components/about/AboutProtectionsHandler.jsm
+++ b/browser/components/about/AboutProtectionsHandler.jsm
@@ -10,22 +10,30 @@ var EXPORTED_SYMBOLS = ["AboutProtection
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 const { RemotePages } = ChromeUtils.import(
   "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
 );
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+ChromeUtils.defineModuleGetter(
+  this,
+  "fxAccounts",
+  "resource://gre/modules/FxAccounts.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
+);
+
 XPCOMUtils.defineLazyModuleGetters(this, {
-  fxAccounts: "resource://gre/modules/FxAccounts.jsm",
   FXA_PWDMGR_HOST: "resource://gre/modules/FxAccountsCommon.js",
   FXA_PWDMGR_REALM: "resource://gre/modules/FxAccountsCommon.js",
-  LoginBreaches: "resource:///modules/LoginBreaches.jsm",
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "TrackingDBService",
   "@mozilla.org/tracking-db-service;1",
   "nsITrackingDBService"
 );
@@ -200,17 +208,17 @@ var AboutProtectionsHandler = {
     try {
       if ((await fxAccounts.accountStatus()) && token) {
         monitorData = await this.fetchUserBreachStats(token);
 
         // Get the stats for number of potentially breached Lockwise passwords if no master
         // password is set.
         if (!LoginHelper.isMasterPasswordSet()) {
           const logins = await LoginHelper.getAllUserFacingLogins();
-          potentiallyBreachedLogins = await LoginBreaches.getPotentialBreachesByLoginGUID(
+          potentiallyBreachedLogins = await LoginHelper.getBreachesForLogins(
             logins
           );
         }
       } else {
         // If no account exists, then the user is not logged in with an fxAccount.
         monitorData = {
           errorMessage: "No account",
         };
--- a/browser/components/aboutlogins/AboutLoginsParent.jsm
+++ b/browser/components/aboutlogins/AboutLoginsParent.jsm
@@ -4,26 +4,47 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["AboutLoginsParent"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "E10SUtils",
+  "resource://gre/modules/E10SUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "MigrationUtils",
+  "resource:///modules/MigrationUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "Services",
+  "resource://gre/modules/Services.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "UIState",
+  "resource://services-sync/UIState.jsm"
+);
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  E10SUtils: "resource://gre/modules/E10SUtils.jsm",
-  LoginBreaches: "resource:////modules/LoginBreaches.jsm",
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
-  MigrationUtils: "resource:///modules/MigrationUtils.jsm",
-  Services: "resource://gre/modules/Services.jsm",
-  UIState: "resource://services-sync/UIState.jsm",
-  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
-});
+ChromeUtils.defineModuleGetter(
+  this,
+  "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm"
+);
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   return LoginHelper.createLogger("AboutLoginsParent");
 });
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "BREACH_ALERTS_ENABLED",
   "signon.management.page.breach-alerts.enabled",
@@ -59,22 +80,27 @@ const augmentVanillaLoginObject = login 
 };
 
 var AboutLoginsParent = {
   _l10n: null,
   _subscribers: new WeakSet(),
 
   // Listeners are added in BrowserGlue.jsm
   async receiveMessage(message) {
-    // Only respond to messages sent from about:logins.
-    if (
-      message.target.remoteType != EXPECTED_ABOUTLOGINS_REMOTE_TYPE ||
-      message.target.contentPrincipal.originNoSuffix != ABOUT_LOGINS_ORIGIN
-    ) {
-      return;
+    // Only respond to messages sent from a privlegedabout process. Ideally
+    // we would also check the contentPrincipal.originNoSuffix but this
+    // check has been removed due to bug 1576722.
+    if (message.target.remoteType != EXPECTED_ABOUTLOGINS_REMOTE_TYPE) {
+      throw new Error(
+        `AboutLoginsParent: Received ${
+          message.name
+        } message the remote type didn't match expectations: ${
+          message.target.remoteType
+        } == ${EXPECTED_ABOUTLOGINS_REMOTE_TYPE}`
+      );
     }
 
     switch (message.name) {
       case "AboutLogins:CreateLogin": {
         let newLogin = message.data.login;
         // Remove the path from the origin, if it was provided.
         let origin = LoginHelper.getLoginOrigin(newLogin.origin);
         if (!origin) {
@@ -95,19 +121,19 @@ var AboutLoginsParent = {
       case "AboutLogins:DeleteLogin": {
         let login = LoginHelper.vanillaObjectToLogin(message.data.login);
         Services.logins.removeLogin(login);
         break;
       }
       case "AboutLogins:DismissBreachAlert": {
         const login = message.data.login;
 
-        await LoginBreaches.recordDismissal(login.guid);
+        await LoginHelper.recordBreachAlertDismissal(login.guid);
         const logins = await this.getAllLogins();
-        const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+        const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
           logins
         );
         const messageManager = message.target.messageManager;
         messageManager.sendAsyncMessage(
           "AboutLogins:UpdateBreaches",
           breachesByLoginGUID
         );
         break;
@@ -402,17 +428,17 @@ var AboutLoginsParent = {
           };
 
           messageManager.sendAsyncMessage(
             "AboutLogins:LocalizeBadges",
             selectedBadgeLanguages
           );
 
           if (BREACH_ALERTS_ENABLED) {
-            const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+            const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
               logins
             );
             messageManager.sendAsyncMessage(
               "AboutLogins:UpdateBreaches",
               breachesByLoginGUID
             );
           }
 
deleted file mode 100644
--- a/browser/components/aboutlogins/LoginBreaches.jsm
+++ /dev/null
@@ -1,171 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Manages breach alerts for saved logins using data from Firefox Monitor via
- * RemoteSettings.
- */
-
-"use strict";
-
-const EXPORTED_SYMBOLS = ["LoginBreaches"];
-
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { XPCOMUtils } = ChromeUtils.import(
-  "resource://gre/modules/XPCOMUtils.jsm"
-);
-
-XPCOMUtils.defineLazyModuleGetters(this, {
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
-  RemoteSettings: "resource://services-settings/remote-settings.js",
-  RemoteSettingsClient: "resource://services-settings/RemoteSettingsClient.jsm",
-});
-
-this.LoginBreaches = {
-  REMOTE_SETTINGS_COLLECTION: "fxmonitor-breaches",
-
-  async recordDismissal(loginGuid) {
-    await Services.logins.initializationPromise;
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-
-    return storageJSON.recordBreachAlertDismissal(loginGuid);
-  },
-
-  async update(breaches = null) {
-    const logins = await LoginHelper.getAllUserFacingLogins();
-    await this.getPotentialBreachesByLoginGUID(logins, breaches);
-  },
-
-  /**
-   * Return a Map of login GUIDs to a potential breach affecting that login
-   * by considering only breaches affecting passwords.
-   *
-   * This only uses the breach `Domain` and `timePasswordChanged` to determine
-   * if a login may be breached which means it may contain false-positives if
-   * login timestamps are incorrect, the user didn't save their password change
-   * in Firefox, or the breach didn't contain all accounts, etc. As a result,
-   * consumers should avoid making stronger claims than the data supports.
-   *
-   * @param {nsILoginInfo[]} logins Saved logins to check for potential breaches.
-   * @param {object[]} [breaches = null] Only ones involving passwords will be used.
-   * @returns {Map} with a key for each login GUID potentially in a breach.
-   */
-  async getPotentialBreachesByLoginGUID(logins, breaches = null) {
-    const breachesByLoginGUID = new Map();
-    if (!breaches) {
-      try {
-        breaches = await RemoteSettings(this.REMOTE_SETTINGS_COLLECTION).get();
-      } catch (ex) {
-        if (ex instanceof RemoteSettingsClient.UnknownCollectionError) {
-          log.warn(
-            "Could not get Remote Settings collection.",
-            this.REMOTE_SETTINGS_COLLECTION,
-            ex
-          );
-          return breachesByLoginGUID;
-        }
-        throw ex;
-      }
-    }
-    const BREACH_ALERT_URL = Services.prefs.getStringPref(
-      "signon.management.page.breachAlertUrl"
-    );
-    const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
-
-    await Services.logins.initializationPromise;
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-    const dismissedBreachAlertsByLoginGUID = storageJSON.getBreachAlertDismissalsByLoginGUID();
-
-    // Determine potentially breached logins by checking their origin and the last time
-    // they were changed. It's important to note here that we are NOT considering the
-    // username and password of that login.
-    for (const login of logins) {
-      const loginURI = Services.io.newURI(login.origin);
-      let loginHost;
-      try {
-        // nsIURI.host can throw if the URI scheme doesn't have a host.
-        loginHost = loginURI.host;
-      } catch (ex) {
-        continue;
-      }
-      for (const breach of breaches) {
-        if (
-          !breach.Domain ||
-          !Services.eTLD.hasRootDomain(loginHost, breach.Domain) ||
-          !this._breachInvolvedPasswords(breach) ||
-          !this._breachWasAfterPasswordLastChanged(breach, login)
-        ) {
-          continue;
-        }
-
-        if (!storageJSON.isPotentiallyVulnerablePassword(login)) {
-          storageJSON.addPotentiallyVulnerablePassword(login);
-        }
-
-        if (
-          this._breachAlertIsDismissed(
-            login,
-            breach,
-            dismissedBreachAlertsByLoginGUID
-          )
-        ) {
-          continue;
-        }
-
-        let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
-        breach.breachAlertURL = breachAlertURL.href;
-        breachesByLoginGUID.set(login.guid, breach);
-      }
-    }
-    return breachesByLoginGUID;
-  },
-
-  /**
-   * Return information about logins using passwords that were potentially in a
-   * breach.
-   * @see the caveats in the documentation for `getPotentialBreachesByLoginGUID`.
-   *
-   * @param {nsILoginInfo[]} logins to check the passwords of.
-   * @returns {Map} from login GUID to `true` for logins that have a password
-   *                that may be vulnerable.
-   */
-  getPotentiallyVulnerablePasswordsByLoginGUID(logins) {
-    const vulnerablePasswordsByLoginGUID = new Map();
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-    for (const login of logins) {
-      if (storageJSON.isPotentiallyVulnerablePassword(login)) {
-        vulnerablePasswordsByLoginGUID.set(login.guid, true);
-      }
-    }
-    return vulnerablePasswordsByLoginGUID;
-  },
-
-  _breachAlertIsDismissed(login, breach, dismissedBreachAlerts) {
-    const breachAddedDate = new Date(breach.AddedDate).getTime();
-    const breachAlertIsDismissed =
-      dismissedBreachAlerts[login.guid] &&
-      dismissedBreachAlerts[login.guid].timeBreachAlertDismissed >
-        breachAddedDate;
-    return breachAlertIsDismissed;
-  },
-
-  _breachInvolvedPasswords(breach) {
-    return (
-      breach.hasOwnProperty("DataClasses") &&
-      breach.DataClasses.includes("Passwords")
-    );
-  },
-
-  _breachWasAfterPasswordLastChanged(breach, login) {
-    const breachDate = new Date(breach.BreachDate).getTime();
-    return login.timePasswordChanged < breachDate;
-  },
-};
-
-XPCOMUtils.defineLazyGetter(this, "log", () => {
-  return LoginHelper.createLogger("LoginBreaches");
-});
--- a/browser/components/aboutlogins/content/aboutLogins.html
+++ b/browser/components/aboutlogins/content/aboutLogins.html
@@ -237,25 +237,25 @@
         <button role="menuitem" class="menuitem-button menuitem-mobile menuitem-mobile-ios ghost-button" data-event-name="AboutLoginsOpenMobileIos" data-l10n-id="menu-menuitem-iphone-app"></button>
       </ul>
     </template>
 
     <template id="login-footer-template">
       <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-footer.css">
       <footer>
-        <div class="footer-content message-bar">
-          <p>
-            <strong data-l10n-id="login-app-promo-title"></strong><br>
-            <span data-l10n-id="login-app-promo-subtitle"></span>
-          </p>
+        <p>
+          <strong data-l10n-id="login-app-promo-title"></strong><br>
+          <span data-l10n-id="login-app-promo-subtitle"></span>
+        </p>
+        <div class="buttons">
           <button class="app-store" data-event-name="AboutLoginsOpenMobileIos">
             <img class="image-app-store" data-l10n-id="login-app-promo-apple" src="" alt=""/>
           </button>
           <button class="play-store" data-event-name="AboutLoginsOpenMobileAndroid">
             <img class="image-play-store" data-l10n-id="login-app-promo-android" src="" alt=""/>
           </button>
-          <button class="close" data-event-name="AboutLoginsHideFooter"></button>
         </div>
+        <button class="close" data-event-name="AboutLoginsHideFooter"></button>
       </footer>
     </template>
   </body>
 </html>
--- a/browser/components/aboutlogins/content/components/login-footer.css
+++ b/browser/components/aboutlogins/content/components/login-footer.css
@@ -1,58 +1,62 @@
 /* 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/. */
 
 footer {
-  position: fixed;
-  margin-inline-start: 0;
-  bottom: 40px;
+  background-color: var(--in-content-box-background);
+  border-radius: 4px;
+  box-shadow: var(--shadow-10);
+  position: relative;
+  padding-block: 8px;
+  padding-inline: 40px;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
 }
 
-.footer-content {
-  background-color: var(--in-content-box-background);
-  box-shadow: var(--shadow-10);
-  max-width: 700px;
-  position: relative;
-  display: flex;
-  flex-flow: row nowrap;
-  align-items: center;
-  padding-inline-start: 40px;
-  padding-inline-end: 80px;
+footer > p {
+  display: inline-block;
+  line-height: 1.5;
+  padding-inline-end: 70px;
+  margin: 0;
+  margin-inline-end: auto;
 }
 
-.footer-content > p {
-  font-size: 15px;
-  margin: 0;
-  line-height: 1.5;
-  padding-inline-end: 40px;
+footer > .buttons {
+  display: inline-block;
+  margin-inline-end: auto;
 }
 
-.footer-content button {
-  background-color: transparent;
+footer button {
+  background-color: transparent !important; /* override common.css */
   padding: 0;
+  cursor: pointer;
+  vertical-align: middle;
 }
 
-.footer-content button:hover {
-  cursor: pointer;
-  background-color: transparent;
+footer .app-store {
+  margin-inline-start: 0;
 }
 
-.footer-content button img {
+footer button:not(.close) img {
   width: auto;
-  height: 40px;
   display: block;
 }
 
-.footer-content button + button img {
+footer .app-store img {
+  height: 40px;
+}
+
+footer .play-store img {
   height: 60px;
 }
 
-.footer-content .close {
+footer .close {
   -moz-context-properties: fill, fill-opacity;
   background-image: url("chrome://global/skin/icons/close.svg");
   background-repeat: no-repeat;
   background-position: center;
   cursor: pointer;
   display: block;
   fill: currentColor;
   fill-opacity: 0;
--- a/browser/components/aboutlogins/content/components/login-item.css
+++ b/browser/components/aboutlogins/content/components/login-item.css
@@ -1,21 +1,27 @@
 /* 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/. */
 
 :host {
   padding: 40px;
+  display: flex;
+  flex-direction: column;
 
   --reveal-checkbox-opacity: .8;
   --reveal-checkbox-opacity-hover: .6;
   --reveal-checkbox-opacity-active: 1;
   --success-color: #00c100;
 }
 
+form {
+  flex-grow: 1;
+}
+
 :host([data-editing]) .edit-button,
 :host([data-editing]) .copy-button,
 :host([data-editing]) login-footer,
 :host([data-is-new-login]) .delete-button,
 :host([data-is-new-login]) .origin-saved-value,
 :host([data-is-new-login]) .meta-info,
 :host([data-is-new-login]) login-footer,
 :host([data-is-new-login]) .login-item-title,
@@ -49,17 +55,16 @@ input[type="url"][readOnly] {
 }
 
 .title {
   margin-top: 0;
   margin-bottom: 0;
   flex-grow: 1;
   overflow: hidden;
   text-overflow: ellipsis;
-  white-space: nowrap;
 }
 
 .delete-button,
 .edit-button {
   background-repeat: no-repeat;
   background-position: 8px;
   -moz-context-properties: fill;
   fill: currentColor;
@@ -219,30 +224,33 @@ input[type="url"][readOnly]:hover:active
 .login-item-favicon-wrapper {
   margin-inline-end: 12px;
   height: 24px;
   width: 24px;
   flex-shrink: 0;
   background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   background-repeat: no-repeat;
   background-size: contain;
-  -moz-context-properties: fill;
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
+  fill-opacity: 0.8;
 }
 
 .login-item-favicon-wrapper.hide-default-favicon {
   background-image: none;
 }
 
 .breach-alert {
   border-radius: 8px;
   border: 1px solid var(--in-content-border-color);
   background-color: var(--yellow-10);
   background-image: url("chrome://global/skin/icons/warning.svg");
   background-repeat: no-repeat;
   background-position: left 10px top 10px;
+  color: #0C0C0D;
   -moz-context-properties: fill;
   fill: var(--red-90);
   box-shadow: 0 2px 8px 0 rgba(12,12,13,0.1);
   font-size: .9em;
   font-weight: 300;
   line-height: 1.4;
   padding-block: 12px;
   padding-inline-start: 36px;
@@ -268,19 +276,19 @@ a.breach-alert-link {
   position: absolute;
   background-image: url("chrome://global/skin/icons/close.svg");
   background-repeat: no-repeat;
   background-size: contain;
   min-height: 16px;
   min-width: 16px;
   -moz-context-properties: fill, fill-opacity;
   fill-opacity: 0;
-  fill: var(--grey-90);
+  fill: currentColor;
   inset-inline-end: 12px;
-  inset-block-start: 12px
+  inset-block-start: 12px;
 }
 
 .dismiss-breach-alert,
 .dismiss-breach-alert:hover {
   background-color: transparent;
 }
 
 @supports -moz-bool-pref("browser.in-content.dark-mode") {
@@ -289,16 +297,11 @@ a.breach-alert-link {
       --reveal-checkbox-opacity: .8;
       --reveal-checkbox-opacity-hover: 1;
       --reveal-checkbox-opacity-active: .6;
       --success-color: #86DE74;
     }
 
     .breach-alert {
       box-shadow: 0 2px 8px 0 rgba(249,249,250,0.1);
-      color: #0C0C0D;
-    }
-
-    .login-item-favicon-wrapper {
-      fill: var(--in-content-border-hover);
     }
   }
 }
--- a/browser/components/aboutlogins/content/components/login-list.css
+++ b/browser/components/aboutlogins/content/components/login-list.css
@@ -17,17 +17,19 @@
   border-bottom: 1px solid var(--in-content-box-border-color);
   background-color: var(--in-content-box-background);
   color: var(--in-content-deemphasized-text);
   font-size: 0.8em;
 }
 
 #login-sort {
   background-color: transparent;
+  margin-inline-start: 0;
   padding-inline-start: 0;
+  padding-inline-end: 16px;
   min-height: initial;
   font: inherit;
   font-weight: 600;
   color: var(--in-content-text-color) !important;
 }
 
 #login-sort > option {
   font-weight: normal;
@@ -63,31 +65,32 @@
 }
 
 ol {
   margin-top: 0;
   margin-bottom: 0;
   padding-inline-start: 0;
   overflow: hidden auto;
   flex-grow: 1;
-  box-shadow: inset 0 -10px 10px -10px var(--grey-90-a20);
+  box-shadow: inset 0 -1px var(--in-content-box-border-color);
 }
 
 .create-login-button {
   margin: 16px;
 }
 
 .login-list-item {
   display: flex;
   align-items: center;
   padding: 10px;
   padding-inline-end: 18px;
   padding-inline-start: 12px;
   border-inline-start: 4px solid transparent;
   border-bottom: 1px solid var(--in-content-box-border-color);
+  user-select: none;
 }
 
 .login-list-item:hover {
   background-color: var(--in-content-box-background-hover);
 }
 
 .login-list-item:hover:active {
   background-color: var(--in-content-box-background-active);
@@ -112,26 +115,27 @@ ol {
   overflow: hidden;
 }
 
 .title,
 .username {
   display: block;
   text-overflow: ellipsis;
   overflow: hidden;
-  white-space: nowrap;
 }
 
 .favicon-wrapper {
   height: 16px;
   width: 16px;
   background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   background-repeat: no-repeat;
   margin-inline-end: 12px;
-  -moz-context-properties: fill;
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
+  fill-opacity: 0.8;
 }
 
 .favicon-wrapper.hide-default-favicon {
   background-image: none;
 }
 
 .favicon {
   width: 16px;
@@ -155,14 +159,10 @@ ol {
   background-position: left 24px center;
 }
 
 @supports -moz-bool-pref("browser.in-content.dark-mode") {
   @media (prefers-color-scheme: dark) {
     .login-list-item.breached {
       fill: var(--red-60);
     }
-
-    .favicon-wrapper {
-      fill: var(--in-content-border-hover);
-    }
   }
 }
--- a/browser/components/aboutlogins/moz.build
+++ b/browser/components/aboutlogins/moz.build
@@ -6,17 +6,16 @@
 
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Password Manager')
 
 EXTRA_JS_MODULES += [
     'AboutLoginsParent.jsm',
-    'LoginBreaches.jsm',
 ]
 
 FINAL_TARGET_FILES.actors += [
     'AboutLoginsChild.jsm',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
--- a/browser/components/aboutlogins/tests/browser/browser.ini
+++ b/browser/components/aboutlogins/tests/browser/browser.ini
@@ -22,9 +22,10 @@ skip-if = asan || debug || verify # bug 
 skip-if = (os == 'linux') # bug 1569789
 [browser_noLoginsView.js]
 [browser_openFiltered.js]
 [browser_openImport.js]
 skip-if = (os != "win") # import is only available on Windows
 [browser_openPreferences.js]
 [browser_openPreferencesExternal.js]
 [browser_openSite.js]
+[browser_sessionRestore.js]
 [browser_updateLogin.js]
--- a/browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
+++ b/browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
@@ -1,15 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let { LoginBreaches } = ChromeUtils.import(
-  "resource:///modules/LoginBreaches.jsm"
-);
-
 const TEST_BREACHES = [
   {
     AddedDate: "2019-12-20T23:56:26Z",
     BreachDate: "2018-12-16",
     Domain: "breached.com",
     Name: "Breached",
     PwnCount: 1643100,
     DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
@@ -30,17 +26,17 @@ add_task(async function setup() {
     BrowserTestUtils.removeTab(gBrowser.selectedTab);
     Services.logins.removeAllLogins();
   });
 });
 
 add_task(async function test_show_login() {
   let browser = gBrowser.selectedBrowser;
   TEST_LOGIN3.timePasswordChanged = 12345;
-  let testBreaches = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  let testBreaches = await LoginHelper.getBreachesForLogins(
     [TEST_LOGIN3],
     TEST_BREACHES
   );
   browser.messageManager.sendAsyncMessage(
     "AboutLogins:UpdateBreaches",
     testBreaches
   );
   await ContentTask.spawn(browser, TEST_LOGIN3, async () => {
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/tests/browser/browser_sessionRestore.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TabState } = ChromeUtils.import(
+  "resource:///modules/sessionstore/TabState.jsm"
+);
+
+async function checkLoginDisplayed(browser, testGuid) {
+  await ContentTask.spawn(browser, testGuid, async function(guid) {
+    let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
+    let loginFound = await ContentTaskUtils.waitForCondition(() => {
+      return (
+        loginList._loginGuidsSortedOrder.length == 1 &&
+        loginList._loginGuidsSortedOrder[0] == guid
+      );
+    }, "Waiting for login to be displayed in page");
+    ok(loginFound, "Confirming that login is displayed in page");
+  });
+}
+
+add_task(async function() {
+  TEST_LOGIN1 = await addLogin(TEST_LOGIN1);
+  registerCleanupFunction(() => {
+    Services.logins.removeAllLogins();
+  });
+
+  const testGuid = TEST_LOGIN1.guid;
+  const tab = BrowserTestUtils.addTab(gBrowser, "about:logins");
+  const browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await checkLoginDisplayed(browser, testGuid);
+
+  BrowserTestUtils.removeTab(tab);
+  info("Adding a lazy about:logins tab...");
+  let lazyTab = BrowserTestUtils.addTab(gBrowser, "about:logins", {
+    createLazyBrowser: true,
+  });
+
+  is(lazyTab.linkedPanel, "", "Tab is lazy");
+  let tabLoaded = new Promise(resolve => {
+    gBrowser.addTabsProgressListener({
+      async onLocationChange(aBrowser) {
+        if (lazyTab.linkedBrowser == aBrowser) {
+          gBrowser.removeTabsProgressListener(this);
+          await Promise.resolve();
+          resolve();
+        }
+      },
+    });
+  });
+
+  info("Switching tab to cause it to get restored");
+  await BrowserTestUtils.switchTab(gBrowser, lazyTab);
+
+  await tabLoaded;
+
+  let lazyBrowser = lazyTab.linkedBrowser;
+  await checkLoginDisplayed(lazyBrowser, testGuid);
+
+  BrowserTestUtils.removeTab(lazyTab);
+});
rename from browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
rename to browser/components/aboutlogins/tests/unit/test_getBreachesForLogins.js
--- a/browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
+++ b/browser/components/aboutlogins/tests/unit/test_getBreachesForLogins.js
@@ -1,26 +1,23 @@
 /**
- * Test LoginBreaches.getPotentialBreachesByLoginGUID
+ * Test LoginHelper.getBreachesForLogins
  */
 
 "use strict";
 
-const { RemoteSettings } = ChromeUtils.import(
-  "resource://services-settings/remote-settings.js"
-);
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
-  Ci.nsIObserver
+const { AboutLoginsParent } = ChromeUtils.import(
+  "resource:///modules/AboutLoginsParent.jsm"
 );
 
 ChromeUtils.defineModuleGetter(
   this,
-  "LoginBreaches",
-  "resource:///modules/LoginBreaches.jsm"
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
 );
 
 const TEST_BREACHES = [
   {
     AddedDate: "2018-12-20T23:56:26Z",
     BreachDate: "2018-12-16",
     Domain: "breached.com",
     Name: "Breached",
@@ -57,17 +54,17 @@ const TEST_BREACHES = [
   },
 ];
 
 const NOT_BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
   origin: "https://www.example.com",
   formActionOrigin: "https://www.example.com",
   username: "username",
   password: "password",
-  timePasswordChanged: new Date("2018-12-15").getTime(),
+  timePasswordChanged: Date.now(),
 });
 const BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
   origin: "https://www.breached.com",
   formActionOrigin: "https://www.breached.com",
   username: "username",
   password: "password",
   timePasswordChanged: new Date("2018-12-15").getTime(),
 });
@@ -96,198 +93,135 @@ const LOGIN_FOR_BREACHED_SITE_WITHOUT_PA
 const LOGIN_WITH_NON_STANDARD_URI = LoginTestUtils.testData.formLogin({
   origin: "someApp://random/path/to/login",
   formActionOrigin: "someApp://random/path/to/login",
   username: "username",
   password: "password",
   timePasswordChanged: new Date("2018-12-15").getTime(),
 });
 
-add_task(async function test_notBreachedLogin() {
+add_task(async function test_getBreachesForLogins_notBreachedLogin() {
   Services.logins.addLogin(NOT_BREACHED_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     0,
     "Should be 0 breached logins."
   );
 });
 
-add_task(async function test_breachedLogin() {
+add_task(async function test_getBreachesForLogins_breachedLogin() {
   Services.logins.addLogin(BREACHED_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN, BREACHED_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login: " + BREACHED_LOGIN.origin
   );
 });
 
-add_task(async function test_notBreachedSubdomain() {
+add_task(async function test_getBreachesForLogins_notBreachedSubdomain() {
   Services.logins.addLogin(NOT_BREACHED_SUBDOMAIN_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN, NOT_BREACHED_SUBDOMAIN_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     0,
     "Should be 0 breached logins."
   );
 });
 
-add_task(async function test_breachedSubdomain() {
+add_task(async function test_getBreachesForLogins_breachedSubdomain() {
   Services.logins.addLogin(BREACHED_SUBDOMAIN_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_SUBDOMAIN_LOGIN, BREACHED_SUBDOMAIN_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login: " + BREACHED_SUBDOMAIN_LOGIN.origin
   );
 });
 
-add_task(async function test_breachedSiteWithoutPasswords() {
-  Services.logins.addLogin(LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS);
+add_task(
+  async function test_getBreachesForLogins_breachedSiteWithoutPasswords() {
+    Services.logins.addLogin(LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    0,
-    "Should be 0 breached login: " +
-      LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
-  );
-});
-
-add_task(async function test_breachAlertHiddenAfterDismissal() {
-  BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";
-
-  await Services.logins.initializationPromise;
-  const storageJSON = Services.logins.wrappedJSObject._storage.wrappedJSObject;
-
-  storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
+      TEST_BREACHES
+    );
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      0,
+      "Should be 0 breached login: " +
+        LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
+    );
+  }
+);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    0,
-    "Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
-  );
+add_task(
+  async function test_getBreachesForLogins_breachAlertHiddenAfterDismissal() {
+    BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";
 
-  info("Clear login storage");
-  Services.logins.removeAllLogins();
+    await Services.logins.initializationPromise;
+    const storageJSON =
+      Services.logins.wrappedJSObject._storage.wrappedJSObject;
+
+    storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);
 
-  const breachesByLoginGUID2 = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID2.size,
-    1,
-    "Breached login should re-appear after clearing storage: " +
-      BREACHED_LOGIN.origin
-  );
-});
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
+      TEST_BREACHES
+    );
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      0,
+      "Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
+    );
+  }
+);
 
-add_task(async function test_newBreachAfterDismissal() {
+add_task(async function test_getBreachesForLogins_newBreachAfterDismissal() {
   TEST_BREACHES[0].AddedDate = new Date().toISOString();
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
     TEST_BREACHES
   );
 
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login after new breach following the dismissal of a previous breach: " +
       BREACHED_LOGIN.origin
   );
 });
 
-add_task(async function test_ExceptionsThrownByNonStandardURIsAreCaught() {
-  Services.logins.addLogin(LOGIN_WITH_NON_STANDARD_URI);
-
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [LOGIN_WITH_NON_STANDARD_URI, BREACHED_LOGIN],
-    TEST_BREACHES
-  );
-
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    1,
-    "Exceptions thrown by logins with non-standard URIs should be caught."
-  );
-});
+add_task(
+  async function test_getBreachesForLogins_ExceptionsThrownByNonStandardURIsAreCaught() {
+    Services.logins.addLogin(LOGIN_WITH_NON_STANDARD_URI);
 
-add_task(async function test_updateBreachesFromRemoteSettingsSync() {
-  const login = NOT_BREACHED_SUBDOMAIN_LOGIN;
-  const nowExampleIsInBreachedRecords = [
-    {
-      AddedDate: "2018-12-20T23:56:26Z",
-      BreachDate: "2018-12-16",
-      Domain: "not-breached-subdomain.host.com",
-      Name: "not-breached-subdomain.host.com is now breached!",
-      PwnCount: 1643100,
-      DataClasses: [
-        "Email addresses",
-        "Usernames",
-        "Passwords",
-        "IP addresses",
-      ],
-      _status: "synced",
-      id: "047940fe-d2fd-4314-b636-b4a952ee0044",
-      last_modified: "1541615610052",
-      schema: "1541615609018",
-    },
-  ];
-  async function emitSync() {
-    await RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).emit(
-      "sync",
-      { data: { current: nowExampleIsInBreachedRecords } }
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [LOGIN_WITH_NON_STANDARD_URI, BREACHED_LOGIN],
+      TEST_BREACHES
+    );
+
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      1,
+      "Exceptions thrown by logins with non-standard URIs should be caught."
     );
   }
-
-  const beforeSyncBreachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [login]
-  );
-  Assert.strictEqual(
-    beforeSyncBreachesByLoginGUID.size,
-    0,
-    "Should be 0 breached login before not-breached-subdomain.host.com is added to fxmonitor-breaches collection and synced: "
-  );
-  gBrowserGlue.observe(null, "browser-glue-test", "add-breaches-sync-handler");
-  const collection = await RemoteSettings(
-    LoginBreaches.REMOTE_SETTINGS_COLLECTION
-  ).openCollection();
-  await collection.create(nowExampleIsInBreachedRecords[0], {
-    useRecordId: true,
-  });
-  await collection.db.saveLastModified(42);
-  await emitSync();
-
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [login]
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    1,
-    "Should be 1 breached login after not-breached-subdomain.host.com is added to fxmonitor-breaches collection and synced: "
-  );
-});
+);
--- a/browser/components/aboutlogins/tests/unit/xpcshell.ini
+++ b/browser/components/aboutlogins/tests/unit/xpcshell.ini
@@ -1,5 +1,5 @@
 [DEFAULT]
 head = head.js
 firefox-appdir = browser
 
-[test_getPotentialBreachesByLoginGUID.js]
+[test_getBreachesForLogins.js]
--- a/browser/components/newtab/lib/ToolbarPanelHub.jsm
+++ b/browser/components/newtab/lib/ToolbarPanelHub.jsm
@@ -156,31 +156,16 @@ class _ToolbarPanelHub {
     const messages = (await this.messages).sort(this._sortWhatsNewMessages);
     const container = doc.getElementById(containerId);
 
     if (messages && !container.querySelector(".whatsNew-message")) {
       let previousDate = 0;
       // Get and store any variable part of the message content
       this.state.contentArguments = await this._contentArguments();
       for (let message of messages) {
-        // Only render date if it is different from the one rendered before.
-        if (message.content.published_date !== previousDate) {
-          container.appendChild(
-            this._createElement(doc, "p", {
-              classList: "whatsNew-message-date",
-              content: new Date(
-                message.content.published_date
-              ).toLocaleDateString("default", {
-                month: "long",
-                day: "numeric",
-                year: "numeric",
-              }),
-            })
-          );
-        }
         container.appendChild(
           this._createMessageElements(win, doc, message, previousDate)
         );
         previousDate = message.content.published_date;
       }
     }
 
     this._onPanelHidden(win);
@@ -223,16 +208,33 @@ class _ToolbarPanelHub {
     });
   }
 
   _createMessageElements(win, doc, message, previousDate) {
     const { content } = message;
     const messageEl = this._createElement(doc, "div");
     messageEl.classList.add("whatsNew-message");
 
+    // Only render date if it is different from the one rendered before.
+    if (content.published_date !== previousDate) {
+      messageEl.appendChild(
+        this._createElement(doc, "p", {
+          classList: "whatsNew-message-date",
+          content: new Date(content.published_date).toLocaleDateString(
+            "default",
+            {
+              month: "long",
+              day: "numeric",
+              year: "numeric",
+            }
+          ),
+        })
+      );
+    }
+
     const wrapperEl = this._createElement(doc, "button");
     // istanbul ignore next
     wrapperEl.doCommand = () => {};
     wrapperEl.classList.add("whatsNew-message-body");
     messageEl.appendChild(wrapperEl);
 
     if (content.icon_url) {
       wrapperEl.classList.add("has-icon");
@@ -250,17 +252,17 @@ class _ToolbarPanelHub {
         this._createElement(doc, "a", {
           classList: "text-link",
           content: content.link_text,
         })
       );
     }
 
     // Attach event listener on entire message container
-    this._attachClickListener(win, wrapperEl, message);
+    this._attachClickListener(win, messageEl, message);
 
     return messageEl;
   }
 
   /**
    * Return message title (optional subtitle) and body
    */
   _createMessageContent(win, doc, content) {
--- a/browser/components/resistfingerprinting/test/browser/browser.ini
+++ b/browser/components/resistfingerprinting/test/browser/browser.ini
@@ -25,10 +25,11 @@ support-files =
 [browser_roundedWindow_open_min_outer.js]
 [browser_roundedWindow_windowSetting_max_inner.js]
 [browser_roundedWindow_windowSetting_max_outer.js]
 [browser_roundedWindow_windowSetting_mid_inner.js]
 [browser_roundedWindow_windowSetting_mid_outer.js]
 [browser_roundedWindow_windowSetting_min_inner.js]
 [browser_roundedWindow_windowSetting_min_outer.js]
 [browser_spoofing_keyboard_event.js]
+skip-if = debug && os == "linux" && bits == 64 #Bug 1518179
 [browser_timezone.js]
 [browser_bug1369357_site_specific_zoom_level.js]
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -885,17 +885,17 @@ class UrlbarInput {
     this._layoutBreakoutPlaceholder.style.height = px(inputRect.height);
     this.textbox.before(this._layoutBreakoutPlaceholder);
     this.setAttribute("breakout", "true");
   }
 
   endLayoutBreakout(force) {
     if (
       !force &&
-      (this.isOpen ||
+      (this.view.isOpen ||
         (this.focused && !this.textbox.classList.contains("hidden-focus")))
     ) {
       return;
     }
     this.removeAttribute("breakout");
     if (this._layoutBreakoutPlaceholder) {
       this._layoutBreakoutPlaceholder.remove();
       this._layoutBreakoutPlaceholder = null;
--- a/browser/config/whats_new_page.yml
+++ b/browser/config/whats_new_page.yml
@@ -14,53 +14,43 @@
       release-types: [release, release-rc]
       products: [firefox]
       update-channel: release
       # e.g.: ["<61.0"]. {version.major_number} reflects the current version.
       # This is done by taskgraph.
       versions: ["<{version.major_number}.0"]
       locales:
           - be
-          - bs
-          - cak
           - cs
           - cy
           - da
           - de
-          - el
           - en-CA
           - en-GB
           - en-US
           - es-AR
-          - es-CL
           - es-ES
           - es-MX
           - fr
           - fy-NL
-          - gn
           - hu
           - ia
           - id
           - it
           - ka
+          - lt
           - nl
           - nn-NO
           - pl
           - pt-BR
-          - pt-PT
-          - rm
-          - ro
           - ru
           - sk
           - sl
-          - sq
           - sv-SE
-          - th
           - tr
-          - uk
           - vi
           - zh-CN
           - zh-TW
 - type: show-url
   # yamllint disable-line rule:line-length
   url: "https://www.mozilla.org/%LOCALE%/{product}/{version.major_number}.0beta/whatsnew/?oldversion=%OLD_VERSION%"
   conditions:
       blob-types: [wnp]
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -262,21 +262,16 @@
 #urlbar:not(.hidden-focus)[focused="true"],
 #searchbar:focus-within {
   box-shadow: 0 0 0 1px var(--toolbar-field-focus-border-color) inset,
               0 0 0 1px var(--toolbar-field-focus-border-color);
 }
 
 %include ../shared/identity-block/identity-block.inc.css
 
-#identity-box:not(:active):-moz-focusring,
-#tracking-protection-icon-container:not(:active):-moz-focusring {
-  border-inline-end-style: none;
-}
-
 #wrapper-urlbar-container[place="palette"] {
   max-width: 20em;
 }
 
 #pageAction-urlbar-shareURL,
 #pageAction-panel-shareURL {
   list-style-image: url("chrome://browser/skin/share.svg");
 }
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1786,20 +1786,16 @@ toolbarpaletteitem[place="menu-panel"] >
 }
 
 /* What's New panel */
 #customizationui-widget-multiview #PanelUI-whatsNew {
   min-width: @menuPanelWidth@;
   max-width: @menuPanelWidth@;
 }
 
-#PanelUI-whatsNew-content {
-  padding: 0 10px;
-}
-
 #protections-popup-main-header-label {
   margin-inline-start: 30px;
   text-align: center;
 }
 
 #protections-popup #messaging-system-message-container {
   height: 260px;
   overflow: hidden;
@@ -1835,37 +1831,55 @@ toolbarpaletteitem[place="menu-panel"] >
 #customizationui-widget-multiview #PanelUI-whatsNew-content {
   height: 586px;
 }
 
 #PanelUI-whatsNew-title {
   display: none;
 }
 
+/**
+ * The Applications Menu container has some extra padding that does not
+ * exist when the What's New dropdown is shown directly from the toolbar
+ */
+#PanelUI-whatsNew-content {
+  margin-top: -6px;
+}
+
+#customizationui-widget-multiview #PanelUI-whatsNew-content {
+  margin: 0;
+}
+
 #customizationui-widget-multiview #PanelUI-whatsNew-title {
   display: flex;
-  margin: 0 0 6px;
 }
 
 #PanelUI-whatsNew .whatsNew-message {
-  border-bottom: 1px solid var(--panel-separator-color);
   cursor: pointer;
-  margin: 0 0 6px;
-  padding-bottom: 10px;
+  margin: 0;
+  padding: 0 12px;
+}
+
+#PanelUI-whatsNew .whatsNew-message::before {
+  content: "";
+  display: block;
+  height: 1px;
+  background: var(--panel-separator-color);
 }
 
 #PanelUI-whatsNew .whatsNew-message-date {
   font-size: 11px;
-  margin: 0;
-  opacity: 0.6;
-  padding: 0 6px;
+  margin: 0 -12px;
+  margin-top: -1px; /* Hide the border separator between messages */
+  padding: 6px 16px;
+  background: var(--toolbar-bgcolor);
 }
 
 #PanelUI-whatsNew .whatsNew-message-body {
-  padding: 6px;
+  padding: 6px 6px 18px;
   text-align: inherit;
   text-decoration: none;
   color: inherit;
   background: none;
   border: none;
   cursor: pointer;
 }
 
@@ -1889,16 +1903,29 @@ toolbarpaletteitem[place="menu-panel"] >
 #PanelUI-whatsNew .whatsNew-message-title,
 #protections-popup-message .whatsNew-message-title {
   font-size: 17px;
   font-weight: 600;
   line-height: 24px;
   margin: 2px 0;
 }
 
+#PanelUI-whatsNew .whatsNew-message-title-large {
+  font-size: 28px;
+  margin: 2px 0;
+  font-weight: 300;
+}
+
+#PanelUI-whatsNew .whatsNew-message-subtitle {
+  margin: 2px 0;
+  font-size: 11px;
+  color: #949494;
+  font-weight: normal;
+}
+
 #PanelUI-whatsNew .whatsNew-message-body p,
 #protections-popup-message .whatsNew-message-body p {
   font-size: 13px;
   line-height: 18px;
 }
 
 #PanelUI-whatsNew .text-link,
 #protections-popup-message .text-link {
--- a/build/clang-plugin/moz.build
+++ b/build/clang-plugin/moz.build
@@ -68,17 +68,17 @@ HOST_COMPILE_FLAGS['STL'] = []
 HOST_COMPILE_FLAGS['VISIBILITY'] = []
 
 # libc++ is required to build plugins against clang on OS X.
 if CONFIG['HOST_OS_ARCH'] == 'Darwin':
     HOST_CXXFLAGS += ['-stdlib=libc++']
 
 # As of clang 8, llvm-config doesn't output the flags used to build clang
 # itself, so we don't end up with -fPIC as a side effect. llvm.org/PR8220
-if CONFIG['HOST_OS_ARCH'] != 'Windows':
+if CONFIG['HOST_OS_ARCH'] != 'WINNT':
     HOST_CXXFLAGS += ['-fPIC']
 
 DIRS += [
     'tests',
 ]
 
 
 # In the current moz.build world, we need to override essentially every
--- a/build/gyp_includes/common.gypi
+++ b/build/gyp_includes/common.gypi
@@ -1001,61 +1001,16 @@
           ['branding=="Chrome" and buildtype=="Official"', {
             'ios_breakpad%': 1,
           }, { # else: branding!="Chrome" or buildtype!="Official"
             'ios_breakpad%': 0,
           }],
         ],
       }],  # OS=="ios"
       ['OS=="android"', {
-        # Location of Android NDK.
-        'variables': {
-          'variables': {
-            'variables': {
-              'android_ndk_root%': '<!(/bin/echo -n $ANDROID_NDK_ROOT)',
-            },
-            'android_ndk_root%': '<(android_ndk_root)',
-            'conditions': [
-              ['target_arch == "ia32"', {
-                'android_app_abi%': 'x86',
-                'android_ndk_sysroot%': '<(android_ndk_root)/platforms/android-16/arch-x86',
-              }],
-              ['target_arch == "x64"', {
-                'android_app_abi%': 'x86_64',
-                'android_ndk_sysroot%': '<(android_ndk_root)/platforms/android-21/arch-x86_64',
-              }],
-              ['target_arch=="arm"', {
-                'android_ndk_sysroot%': '<(android_ndk_root)/platforms/android-16/arch-arm',
-                'conditions': [
-                  ['armv7==0', {
-                    'android_app_abi%': 'armeabi',
-                  }, {
-                    'android_app_abi%': 'armeabi-v7a',
-                  }],
-                ],
-              }],
-              ['target_arch=="arm64"', {
-                'android_app_abi%': 'arm64-v8a',
-                'android_ndk_sysroot%': '<(android_ndk_root)/platforms/android-21/arch-arm64',
-              }],
-            ],
-          },
-          'android_ndk_root%': '<(android_ndk_root)',
-          'android_app_abi%': '<(android_app_abi)',
-          'android_ndk_sysroot%': '<(android_ndk_sysroot)',
-        },
-        'android_ndk_root%': '<(android_ndk_root)',
-        'android_ndk_sysroot': '<(android_ndk_sysroot)',
-        'android_ndk_include': '<(android_ndk_sysroot)/usr/include',
-        'android_ndk_lib': '<(android_ndk_sysroot)/usr/lib',
-        'android_app_abi%': '<(android_app_abi)',
-
-        # Location of the "strip" binary, used by both gyp and scripts.
-        'android_strip%' : '<!(/bin/echo -n <(android_toolchain)/*-strip)',
-
         # Provides an absolute path to PRODUCT_DIR (e.g. out/Release). Used
         # to specify the output directory for Ant in the Android build.
         'ant_build_out': '`cd <(PRODUCT_DIR) && pwd -P`',
 
         # Uses Android's crash report system
         'linux_breakpad%': 0,
 
         # Always uses openssl.
@@ -3576,52 +3531,16 @@
     }],
     ['enable_new_npdevice_api==1', {
       'target_defaults': {
         'defines': [
           'ENABLE_NEW_NPDEVICE_API',
         ],
       },
     }],
-    ['clang==1', {
-      'conditions': [
-        ['OS=="android"', {
-          # Android could use the goma with clang.
-          'make_global_settings': [
-            ['CC', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang)'],
-            ['CXX', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang++)'],
-            ['LINK', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang++)'],
-            ['CC.host', '$(CC)'],
-            ['CXX.host', '$(CXX)'],
-            ['LINK.host', '$(LINK)'],
-          ],
-        }, {
-          'make_global_settings': [
-            ['CC', '<(make_clang_dir)/bin/clang'],
-            ['CXX', '<(make_clang_dir)/bin/clang++'],
-            ['LINK', '$(CXX)'],
-            ['CC.host', '$(CC)'],
-            ['CXX.host', '$(CXX)'],
-            ['LINK.host', '$(LINK)'],
-          ],
-        }],
-      ],
-    }],
-    ['OS=="android" and clang==0', {
-      # Hardcode the compiler names in the Makefile so that
-      # it won't depend on the environment at make time.
-      'make_global_settings': [
-        ['CC', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <(android_toolchain)/*-gcc)'],
-        ['CXX', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <(android_toolchain)/*-g++)'],
-        ['LINK', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <(android_toolchain)/*-gcc)'],
-        ['CC.host', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <!(which gcc))'],
-        ['CXX.host', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <!(which g++))'],
-        ['LINK.host', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} <!(which g++))'],
-      ],
-    }],
   ],
   'xcode_settings': {
     # DON'T ADD ANYTHING NEW TO THIS BLOCK UNLESS YOU REALLY REALLY NEED IT!
     # This block adds *project-wide* configuration settings to each project
     # file.  It's almost always wrong to put things here.  Specify your
     # custom xcode_settings in target_defaults to add them to targets instead.
 
     'conditions': [
--- a/build/moz.configure/bindgen.configure
+++ b/build/moz.configure/bindgen.configure
@@ -10,17 +10,17 @@ cbindgen_is_needed = depends(build_proje
 option(env='CBINDGEN', nargs=1, when=cbindgen_is_needed,
        help='Path to cbindgen')
 
 
 @imports(_from='textwrap', _import='dedent')
 def check_cbindgen_version(cbindgen, fatal=False):
     log.debug("trying cbindgen: %s" % cbindgen)
 
-    cbindgen_min_version = Version('0.9.0')
+    cbindgen_min_version = Version('0.9.1')
 
     # cbindgen x.y.z
     version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
     log.debug("%s has version %s" % (cbindgen, version))
     if version >= cbindgen_min_version:
         return True
     if not fatal:
         return False
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -784,17 +784,17 @@ def real_host(value, shell):
         if arch == 'AMD64':
             return split_triplet('x86_64-pc-mingw32')
         elif arch == 'x86':
             return split_triplet('i686-pc-mingw32')
 
     if not value:
         config_guess = os.path.join(os.path.dirname(__file__), '..',
                                     'autoconf', 'config.guess')
-        host = check_cmd_output(shell, config_guess, universal_newlines=True).strip()
+        host = check_cmd_output(shell, config_guess).strip()
         try:
             return split_triplet(host)
         except ValueError:
             pass
     else:
         host = value[0]
 
     host = config_sub(shell, host)
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -2020,32 +2020,30 @@ def select_linker(linker, c_compiler, de
         # "ld64: For information on command line options please use 'man ld'."
         # but that would require doing two attempts, one with --version, that
         # would fail, and another with --help.
         # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message
         # specific to it on stderr when it fails to process --version.
         env = dict(os.environ)
         env['LD_PRINT_OPTIONS'] = '1'
         retcode, stdout, stderr = get_cmd_output(*cmd, env=env)
-        cmd_output = stdout.decode('utf-8')
-        stderr = stderr.decode('utf-8')
         if retcode == 1 and 'Logging ld64 options' in stderr:
             kind = 'ld64'
 
         elif retcode != 0:
             return None
 
-        elif 'GNU ld' in cmd_output:
+        elif 'GNU ld' in stdout:
             # We are using the normal linker
             kind = 'bfd'
 
-        elif 'GNU gold' in cmd_output:
+        elif 'GNU gold' in stdout:
             kind = 'gold'
 
-        elif 'LLD' in cmd_output:
+        elif 'LLD' in stdout:
             kind = 'lld'
 
         else:
             kind = 'unknown'
 
         return namespace(
             KIND=kind,
             LINKER_FLAG=linker_flag,
--- a/config/recurse.mk
+++ b/config/recurse.mk
@@ -191,16 +191,18 @@ endif
 ifeq ($(MOZ_WIDGET_TOOLKIT),gtk)
 toolkit/library/target: widget/gtk/mozgtk/gtk3/target
 endif
 endif
 # Most things are built during compile (target/host), but some things happen during export
 # Those need to depend on config/export for system wrappers.
 $(addprefix build/unix/stdc++compat/,target host) build/clang-plugin/host: config/export
 
+$(rust_targets): $(DEPTH)/.cargo/config
+
 # When building gtest as part of the build (LINK_GTEST_DURING_COMPILE),
 # force the build system to get to it first, so that it can be linked
 # quickly without LTO, allowing the build system to go ahead with
 # plain gkrust and libxul while libxul-gtest is being linked and
 # dump-sym'ed.
 ifneq (,$(filter toolkit/library/gtest/rust/target,$(compile_targets)))
 toolkit/library/rust/target: toolkit/library/gtest/rust/target
 endif
--- a/devtools/client/debugger/src/utils/prefs.js
+++ b/devtools/client/debugger/src/utils/prefs.js
@@ -20,23 +20,23 @@ if (isDevelopment()) {
   pref("devtools.debugger.auto-pretty-print", false);
   pref("devtools.source-map.client-service.enabled", true);
   pref("devtools.chrome.enabled", false);
   pref("devtools.debugger.pause-on-exceptions", false);
   pref("devtools.debugger.pause-on-caught-exceptions", false);
   pref("devtools.debugger.ignore-caught-exceptions", true);
   pref("devtools.debugger.call-stack-visible", true);
   pref("devtools.debugger.scopes-visible", true);
-  pref("devtools.debugger.component-visible", true);
-  pref("devtools.debugger.workers-visible", true);
-  pref("devtools.debugger.expressions-visible", true);
-  pref("devtools.debugger.xhr-breakpoints-visible", true);
+  pref("devtools.debugger.component-visible", false);
+  pref("devtools.debugger.workers-visible", false);
+  pref("devtools.debugger.expressions-visible", false);
+  pref("devtools.debugger.xhr-breakpoints-visible", false);
   pref("devtools.debugger.breakpoints-visible", true);
-  pref("devtools.debugger.event-listeners-visible", true);
-  pref("devtools.debugger.dom-mutation-breakpoints-visible", true);
+  pref("devtools.debugger.event-listeners-visible", false);
+  pref("devtools.debugger.dom-mutation-breakpoints-visible", false);
   pref("devtools.debugger.start-panel-collapsed", false);
   pref("devtools.debugger.end-panel-collapsed", false);
   pref("devtools.debugger.start-panel-size", 300);
   pref("devtools.debugger.end-panel-size", 300);
   pref("devtools.debugger.tabsBlackBoxed", "[]");
   pref("devtools.debugger.ui.editor-wrapping", false);
   pref("devtools.debugger.ui.framework-grouping-on", true);
   pref("devtools.debugger.pending-selected-location", "{}");
--- a/devtools/client/debugger/test/mochitest/browser_dbg-windowless-workers.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-windowless-workers.js
@@ -32,16 +32,19 @@ function getLabel(dbg, index) {
 
 function getValue(dbg, index) {
   return findElement(dbg, "expressionValue", index).innerText;
 }
 
 // Test basic windowless worker functionality: the main thread and worker can be
 // separately controlled from the same debugger.
 add_task(async function() {
+  await pushPref("devtools.debugger.features.windowless-workers", true);
+  await pushPref("devtools.debugger.workers-visible", true);
+
   const dbg = await initDebugger("doc-windowless-workers.html");
   const mainThread = dbg.toolbox.threadFront.actor;
 
   const workers = await getThreads(dbg);
   ok(workers.length == 2, "Got two workers");
   const thread1 = workers[0].actor;
   const thread2 = workers[1].actor;
 
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -31,23 +31,23 @@ pref("devtools.debugger.ui.panes-instrum
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
 pref("devtools.debugger.ui.variables-only-enum-visible", false);
 pref("devtools.debugger.ui.variables-searchbox-visible", false);
 pref("devtools.debugger.ui.framework-grouping-on", true);
 pref("devtools.debugger.ui.editor-wrapping", false);
 pref("devtools.debugger.call-stack-visible", true);
 pref("devtools.debugger.scopes-visible", true);
-pref("devtools.debugger.component-visible", true);
-pref("devtools.debugger.workers-visible", true);
+pref("devtools.debugger.component-visible", false);
+pref("devtools.debugger.workers-visible", false);
 pref("devtools.debugger.breakpoints-visible", true);
-pref("devtools.debugger.expressions-visible", true);
-pref("devtools.debugger.dom-mutation-breakpoints-visible", true);
-pref("devtools.debugger.xhr-breakpoints-visible", true);
-pref("devtools.debugger.event-listeners-visible", true);
+pref("devtools.debugger.expressions-visible", false);
+pref("devtools.debugger.dom-mutation-breakpoints-visible", false);
+pref("devtools.debugger.xhr-breakpoints-visible", false);
+pref("devtools.debugger.event-listeners-visible", false);
 pref("devtools.debugger.start-panel-collapsed", false);
 pref("devtools.debugger.end-panel-collapsed", false);
 pref("devtools.debugger.start-panel-size", 300);
 pref("devtools.debugger.end-panel-size", 300);
 pref("devtools.debugger.tabs", "[]");
 pref("devtools.debugger.tabsBlackBoxed", "[]");
 pref("devtools.debugger.pending-selected-location", "{}");
 pref("devtools.debugger.pending-breakpoints", "{}");
@@ -75,12 +75,12 @@ pref("devtools.debugger.features.outline
 pref("devtools.debugger.features.component-pane", false);
 pref("devtools.debugger.features.async-stepping", false);
 pref("devtools.debugger.features.skip-pausing", true);
 pref("devtools.debugger.features.autocomplete-expressions", false);
 pref("devtools.debugger.features.map-expression-bindings", true);
 pref("devtools.debugger.features.xhr-breakpoints", true);
 pref("devtools.debugger.features.original-blackbox", true);
 pref("devtools.debugger.features.event-listeners-breakpoints", true);
-pref("devtools.debugger.features.dom-mutation-breakpoints", false);
+pref("devtools.debugger.features.dom-mutation-breakpoints", true);
 pref("devtools.debugger.features.log-points", true);
 pref("devtools.debugger.features.overlay-step-buttons", false);
 pref("devtools.debugger.features.inline-preview", false);
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -422,83 +422,115 @@
 
 .webreplay-player {
   -moz-appearance: none;
   background: var(--theme-tab-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   box-sizing: border-box;
   min-height: 29px;
 
+  --progressbar-transition: 200ms;
+  --command-button-size: 14px;
+  --command-button-primary-size: 20px;
+}
+
+.theme-light .webreplay-player {
+  --commandbar-button-hover-background: #efefef;
   --progress-recording-background: hsl(0, 100%, 97%);
   --progress-playing-background: hsl(207, 100%, 97%);
-
-  --progress-bar-transition: 200ms;
-
   --recording-marker-background: hsl(14.9, 100%, 67%);
   --recording-marker-background-hover: hsl(14.9, 100%, 47%);
-  --command-button-size: 14px;
-  --command-button-primary-size: 20px;
+  --replaying-marker-background: var(--blue-40);
+  --replaying-marker-background-hover: var(--blue-60);
+  --progress-recording-line: #d0021b;
+  --progressbar-background: #fff;
+  --progressbar-line-color: var(--blue-40);
+  --proggressbar-border-color: #bfc9d2;
+  --tick-future-background: #bfc9d2;
+  --tick-background: var(--blue-50);
+  --tick-recording-background: #d0021b;
+  --replay-head-background: var(--purple-50);
+  --command-button-background: #6a6a6a;
+}
+
+.theme-dark .webreplay-player {
+  --commandbar-button-hover-background: #1a1a1a;
+  --progress-recording-background: #310707;
+  --progress-playing-background: #071a2b;
+  --progress-recording-line: #ff2038;
+  --recording-marker-background: #9b3131;
+  --recording-marker-background-hover: #a82323;
+  --replaying-marker-background: #266fb1;
+  --replaying-marker-background-hover: #3a8edb;
+  --progressbar-background: #0c0c0d;
+  --proggressbar-border-color: #31313d;
+  --progressbar-line-color: #0a4786;
+  --tick-future-background: #bfc9d2;
+  --tick-background: var(--blue-50);
+  --tick-recording-background: #e77884;
+  --replay-head-background: var(--theme-highlight-purple);
+  --command-button-background: var(--grey-20);
 }
 
 .webreplay-player .overlay-container {
   display: flex;
 }
 
 .webreplay-player .progressBar {
   position: relative;
   width: 100%;
   height: 20px;
-  background: #fff;
+  background: var(--progressbar-background);
   margin: 4px 10px 4px 0;
-  border: 1px solid #bfc9d2;
+  border: 1px solid var(--proggressbar-border-color);
   overflow: hidden;
 }
 
 .webreplay-player .progress {
   position: absolute;
   width: 100%;
   height: 100%;
   background: var(--progress-playing-background);
-  transition-duration: var(--progress-bar-transition);
+  transition-duration: var(--progressbar-transition);
 }
 
 .webreplay-player #overlay:not(.recording) .progress::after {
-  background: var(--purple-50);
+  background: var(--replay-head-background);
   width: 1px;
   height: 100%;
   right: -0.5px;
   opacity: 0.4;
   display: block;
   content: "";
   position: absolute;
 }
 
 .webreplay-player .recording .progress {
   background: var(--progress-recording-background);
-  transition-duration: var(--progress-bar-transition);
+  transition-duration: var(--progressbar-transition);
 }
 
 .webreplay-player .message {
   position: absolute;
   height: 100%;
   width: 7px;
   height: 7px;
   border-radius: 4.5px;
   top: calc(50% - 3.5px);
-  background: var(--blue-40);
+  background: var(--replaying-marker-background);
   transition-duration: 100ms;
 }
 
 .webreplay-player .message.overlayed {
   border: 1px solid var(--progress-playing-background);
   top: 5.5px;
 }
 
 .webreplay-player .message.overlayed.future {
-  border-color: #fff;
+  border-color: var(--progressbar-background);
 }
 
 .webreplay-player .message.highlighted {
   background-color: var(--blue-60);
   transform: scale(1.25);
   transition-duration: 100ms;
 }
 
@@ -518,17 +550,17 @@
   background: var(--recording-marker-background);
 }
 
 .webreplay-player .recording .message:hover {
   background: var(--recording-marker-background-hover);
 }
 
 .webreplay-player .message:hover {
-  background: var(--blue-60);
+  background: var(--replaying-marker-background-hover);
   cursor: pointer;
 }
 
 .webreplay-player .message:hover::before {
   transform: scale(0.1);
 }
 
 .webreplay-player .commands {
@@ -544,29 +576,29 @@
 .webreplay-player .command-button.primary {
   min-width: 24px;
 }
 
 .webreplay-player .btn {
   width: var(--command-button-size);
   height: var(--command-button-size);
   mask-size: var(--command-button-size);
-  background: #6a6a6a;
+  background: var(--command-button-background);
   align-self: center;
   margin: 0 auto;
 }
 
 .webreplay-player .primary .btn {
   width: var(--command-button-primary-size);
   height: var(--command-button-primary-size);
   mask-size: var(--command-button-primary-size);
 }
 
 .webreplay-player .command-button.active:hover {
-  background: #efefef;
+  background: var(--commandbar-button-hover-background);
   cursor: pointer;
 }
 
 .webreplay-player .command-button.active {
   opacity: 1;
 }
 
 .webreplay-player div.command-button .rewind {
@@ -580,29 +612,29 @@
 
 .webreplay-player div.command-button .next {
   margin-right: 8px;
 }
 
 .webreplay-player .progress-line {
   width: 0%;
   height: 1px;
-  background: var(--blue-40);
+  background: var(--progressbar-line-color);
   position: absolute;
   left: 0;
   top: 50%;
-  transition-duration: var(--progress-bar-transition);
+  transition-duration: var(--progressbar-transition);
 }
 
 .webreplay-player .progress-line.end {
   opacity: 0.3;
 }
 
 .webreplay-player .recording .progress-line {
-  background: #d0021b;
+  background: var(--progress-recording-line);
   opacity: 0.3;
 }
 
 .webreplay-player .tick {
   position: absolute;
   height: 100%;
 }
 
@@ -613,38 +645,37 @@
   right: 0;
   position: absolute;
   content: "";
   display: block;
 }
 
 .webreplay-player .recording .tick::before,
 .webreplay-player .recording .tick::after {
-  background: #d0021b;
+  background: var(--tick-recording-background);
 }
 
 .webreplay-player .tick.future::before,
 .webreplay-player .tick.future::after {
-  background: #bfc9d2;
+  background: var(--tick-future-background);
 }
 
 .webreplay-player .tick::before,
 .webreplay-player .tick::after {
-  background: var(--blue-50);
+  background: var(--tick-background);
 }
 
 .webreplay-player .tick::after {
   bottom: 0;
 }
 
 .webreplay-player .tick::before {
   top: 0;
 }
 
-
 .webreplay-player #overlay:hover .tick {
   opacity: 1;
 }
 
 .webreplay-player #overlay .tick {
   opacity: 0.5;
 }
 
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -66,16 +66,17 @@ const proto = {
    *          - decrementGripDepth
    *              Decrement the actor's grip depth
    *          - globalDebugObject
    *              The Debuggee Global Object as given by the ThreadActor
    */
   initialize(
     obj,
     {
+      thread,
       createValueGrip: createValueGripHook,
       sources,
       createEnvironmentActor,
       getGripDepth,
       incrementGripDepth,
       decrementGripDepth,
       getGlobalDebugObject,
     },
@@ -84,31 +85,89 @@ const proto = {
     assert(
       !obj.optimizedOut,
       "Should not create object actors for optimized out values!"
     );
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.conn = conn;
     this.obj = obj;
+    this.thread = thread;
     this.hooks = {
       createValueGrip: createValueGripHook,
       sources,
       createEnvironmentActor,
       getGripDepth,
       incrementGripDepth,
       decrementGripDepth,
       getGlobalDebugObject,
     };
+    this._originalDescriptors = new Map();
   },
 
   rawValue: function() {
     return this.obj.unsafeDereference();
   },
 
+  addWatchpoint(property, label, watchpointType) {
+    if (this._originalDescriptors.has(property)) {
+      return;
+    }
+    const desc = this.obj.getOwnPropertyDescriptor(property);
+
+    //If there is already a setter or getter, don't add watchpoint.
+    if (desc.set || desc.get) {
+      return;
+    }
+
+    this._originalDescriptors.set(property, desc);
+
+    const pauseAndRespond = () => {
+      const frame = this.thread.dbg.getNewestFrame();
+      this.thread._pauseAndRespond(frame, {
+        type: "watchpoint",
+        message: label,
+      });
+    };
+
+    if (watchpointType === "get") {
+      this.obj.defineProperty(property, {
+        configurable: desc.configurable,
+        enumerable: desc.enumerable,
+        set: this.obj.makeDebuggeeValue(v => {
+          desc.value = v;
+        }),
+        get: this.obj.makeDebuggeeValue(() => {
+          pauseAndRespond();
+        }),
+      });
+    }
+
+    if (watchpointType === "set") {
+      this.obj.defineProperty(property, {
+        configurable: desc.configurable,
+        enumerable: desc.enumerable,
+        set: this.obj.makeDebuggeeValue(v => {
+          desc.value = v;
+          pauseAndRespond();
+        }),
+      });
+    }
+  },
+
+  removeWatchpoint(property) {
+    if (!this._originalDescriptors.has(property)) {
+      return;
+    }
+
+    const desc = this._originalDescriptors.get(property);
+    this._originalDescriptors.delete(property);
+    this.obj.defineProperty(property, desc);
+  },
+
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   form: function() {
     const g = {
       type: "object",
       actor: this.actorID,
     };
@@ -723,20 +782,24 @@ const proto = {
     const retval = {
       configurable: desc.configurable,
       enumerable: desc.enumerable,
     };
 
     if ("value" in desc) {
       retval.writable = desc.writable;
       retval.value = this.hooks.createValueGrip(desc.value);
+    } else if (this._originalDescriptors.has(name)) {
+      const value = this._originalDescriptors.get(name).value;
+      retval.value = this.hooks.createValueGrip(value);
     } else {
       if ("get" in desc) {
         retval.get = this.hooks.createValueGrip(desc.get);
       }
+
       if ("set" in desc) {
         retval.set = this.hooks.createValueGrip(desc.set);
       }
     }
     return retval;
   },
 
   /**
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1620,16 +1620,17 @@ const ThreadActor = ActorClassWithSpec(t
 
     if (this.threadLifetimePool.objectActors.has(value)) {
       return this.threadLifetimePool.objectActors.get(value).form();
     }
 
     const actor = new PauseScopedObjectActor(
       value,
       {
+        thread: this,
         getGripDepth: () => this._gripDepth,
         incrementGripDepth: () => this._gripDepth++,
         decrementGripDepth: () => this._gripDepth--,
         createValueGrip: v => {
           if (this._pausePool) {
             return createValueGrip(v, this._pausePool, this.pauseObjectGrip);
           }
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_watchpoint-01.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow */
+
+"use strict";
+
+/*
+Tests adding set and get watchpoints.
+Tests removing a watchpoint.
+*/
+
+add_task(
+  threadFrontTest(async args => {
+    await testSetWatchpoint(args);
+    await testGetWatchpoint(args);
+    await testRemoveWatchpoint(args);
+  })
+);
+
+async function testSetWatchpoint({ threadFront, debuggee }) {
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        debugger;                         // 3
+        obj.a = 2;                        // 4
+      }                                   // 
+      stopMe({a: 1})`,                           
+      debuggee,
+      "1.8",
+      "test_watchpoint-01.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+
+  //Test that we paused on the debugger statement.
+  Assert.equal(packet.frame.where.line, 3);
+
+  //Add set watchpoint.
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "set");
+
+  //Test that watchpoint triggers pause on set.
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 4);
+  Assert.equal(packet2.why.type, "watchpoint");
+  Assert.equal(obj.preview.ownProperties.a.value, 1);
+  
+  await resume(threadFront);
+}
+
+async function testGetWatchpoint({ threadFront, debuggee }) {
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        debugger;                         // 3
+        obj.a + 4;                        // 4
+      }                                   // 
+      stopMe({a: 1})`,                           
+      debuggee,
+      "1.8",
+      "test_watchpoint-01.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+
+  //Test that we paused on the debugger statement.
+  Assert.equal(packet.frame.where.line, 3);
+
+  //Add get watchpoint.
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "get");
+
+  //Test that watchpoint triggers pause on get.
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 4);
+  Assert.equal(packet2.why.type, "watchpoint");
+  Assert.equal(obj.preview.ownProperties.a.value, 1);
+  
+  await resume(threadFront);
+}
+
+async function testRemoveWatchpoint({ threadFront, debuggee }) {
+  function evaluateTestCode(debuggee) {
+    /* eslint-disable */
+    Cu.evalInSandbox(
+      `                                   // 1
+      function stopMe(obj) {              // 2
+        debugger;                         // 3
+        obj.a = 2;                        // 4
+        debugger;                         // 5
+      }                                   // 
+      stopMe({a: 1})`,                           
+      debuggee,
+      "1.8",
+      "test_watchpoint-01.js",
+    );
+    /* eslint-disable */
+  }
+
+  const packet = await executeOnNextTickAndWaitForPause(
+    () => evaluateTestCode(debuggee),
+    threadFront
+  );
+  
+  //Test that we paused on the debugger statement.
+  Assert.equal(packet.frame.where.line, 3);
+
+  //Add and then remove set watchpoint.
+  const args = packet.frame.arguments;
+  const obj = args[0];
+  const objClient = threadFront.pauseGrip(obj);
+  await objClient.addWatchpoint("a", "obj.a", "set");
+  await objClient.removeWatchpoint("a");
+
+  //Test that we do not pause on set. 
+  const packet2 = await resumeAndWaitForPause(threadFront);
+  Assert.equal(packet2.frame.where.line, 5);
+  
+  await resume(threadFront);
+}
\ No newline at end of file
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -210,16 +210,17 @@ skip-if = true # breakpoint sliding is n
 [test_pause_exceptions-04.js]
 [test_longstringactor.js]
 [test_longstringgrips-01.js]
 [test_source-01.js]
 [test_source-02.js]
 [test_source-03.js]
 [test_source-04.js]
 [test_wasm_source-01.js]
+[test_watchpoint-01.js]
 [test_breakpoint-actor-map.js]
 skip-if = true # tests for breakpoint actors are obsolete bug 1524374
 [test_unsafeDereference.js]
 [test_add_actors.js]
 [test_ignore_caught_exceptions.js]
 [test_ignore_no_interface_exceptions.js]
 [test_requestTypes.js]
 reason = bug 937197
--- a/devtools/shared/client/object-client.js
+++ b/devtools/shared/client/object-client.js
@@ -388,11 +388,21 @@ ObjectClient.prototype = {
         if (response.error === "unrecognizedPacketType") {
           const { proxyTarget, proxyHandler } = this._grip;
           return { proxyTarget, proxyHandler };
         }
         return response;
       },
     }
   ),
+  addWatchpoint: DebuggerClient.requester({
+    type: "addWatchpoint",
+    property: arg(0),
+    label: arg(1),
+    watchpointType: arg(2),
+  }),
+  removeWatchpoint: DebuggerClient.requester({
+    type: "removeWatchpoint",
+    property: arg(0),
+  }),
 };
 
 module.exports = ObjectClient;
--- a/devtools/shared/specs/object.js
+++ b/devtools/shared/specs/object.js
@@ -104,17 +104,16 @@ types.addDictType("object.originalSource
 
 types.addDictType("object.proxySlots", {
   proxyTarget: "object.descriptor",
   proxyHandler: "object.descriptor",
 });
 
 const objectSpec = generateActorSpec({
   typeName: "obj",
-
   methods: {
     allocationStack: {
       request: {},
       response: {
         allocationStack: RetVal("array:object.originalSourceLocation"),
       },
     },
     decompile: {
@@ -202,16 +201,31 @@ const objectSpec = generateActorSpec({
       response: {
         rejectionStack: RetVal("array:object.originalSourceLocation"),
       },
     },
     proxySlots: {
       request: {},
       response: RetVal("object.proxySlots"),
     },
+    addWatchpoint: {
+      request: {
+        property: Arg(0, "string"),
+        label: Arg(1, "string"),
+        watchpointType: Arg(2, "string"),
+      },
+      response: {},
+    },
+    removeWatchpoint: {
+      request: {
+        property: Arg(0, "string"),
+      },
+      response: {},
+    },
+
     release: { release: true },
     scope: {
       request: {},
       response: RetVal("object.scope"),
     },
     // Needed for the PauseScopedObjectActor which extends the ObjectActor.
     threadGrip: {
       request: {},
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -151,16 +151,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCredentials)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddonManager)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -221,16 +222,17 @@ void Navigator::Invalidate() {
   mVRGetDisplaysPromises.Clear();
 
   if (mVRServiceTest) {
     mVRServiceTest->Shutdown();
     mVRServiceTest = nullptr;
   }
 
   mMediaCapabilities = nullptr;
+  mAddonManager = nullptr;
 }
 
 void Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType,
                              ErrorResult& aRv) const {
   nsCOMPtr<nsPIDOMWindowInner> window;
 
   if (mWindow) {
     window = mWindow;
@@ -1823,15 +1825,33 @@ dom::MediaCapabilities* Navigator::Media
 
 Clipboard* Navigator::Clipboard() {
   if (!mClipboard) {
     mClipboard = new dom::Clipboard(GetWindow());
   }
   return mClipboard;
 }
 
+AddonManager* Navigator::GetMozAddonManager(ErrorResult& aRv) {
+  if (!mAddonManager) {
+    nsPIDOMWindowInner* win = GetWindow();
+    if (!win) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    mAddonManager = ConstructJSImplementation<AddonManager>(
+        "@mozilla.org/addon-web-api/manager;1", win->AsGlobal(), aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  return mAddonManager;
+}
+
 /* static */
 bool Navigator::Webdriver() {
   return Preferences::GetBool("marionette.enabled", false);
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Navigator_h
 #define mozilla_dom_Navigator_h
 
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/AddonManagerBinding.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/ErrorResult.h"
 #include "nsWrapperCache.h"
 #include "nsHashKeys.h"
 #include "nsInterfaceHashtable.h"
 #include "nsString.h"
@@ -23,16 +24,17 @@ class nsPluginArray;
 class nsMimeTypeArray;
 class nsPIDOMWindowInner;
 class nsIDOMNavigatorSystemMessages;
 class nsIPrincipal;
 class nsIURI;
 
 namespace mozilla {
 namespace dom {
+class AddonManager;
 class BodyExtractorBase;
 class Geolocation;
 class systemMessageCallback;
 class MediaDevices;
 struct MediaStreamConstraints;
 class WakeLock;
 class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
 class ServiceWorkerContainer;
@@ -203,16 +205,18 @@ class Navigator final : public nsISuppor
   void GetLanguages(nsTArray<nsString>& aLanguages);
 
   StorageManager* Storage();
 
   static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
 
   dom::MediaCapabilities* MediaCapabilities();
 
+  AddonManager* GetMozAddonManager(ErrorResult& aRv);
+
   // WebIDL helper methods
   static bool HasUserMediaSupport(JSContext* /* unused */,
                                   JSObject* /* unused */);
 
   nsPIDOMWindowInner* GetParentObject() const { return GetWindow(); }
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> aGivenProto) override;
@@ -261,14 +265,15 @@ class Navigator final : public nsISuppor
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<Presentation> mPresentation;
   RefPtr<GamepadServiceTest> mGamepadServiceTest;
   nsTArray<RefPtr<Promise>> mVRGetDisplaysPromises;
   RefPtr<VRServiceTest> mVRServiceTest;
   nsTArray<uint32_t> mRequestedVibrationPattern;
   RefPtr<StorageManager> mStorageManager;
   RefPtr<dom::MediaCapabilities> mMediaCapabilities;
+  RefPtr<AddonManager> mAddonManager;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_Navigator_h
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1924,43 +1924,16 @@ class CGClassConstructor(CGAbstractStati
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
               "${ctorName}", "constructor", DOM, cx,
               uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
             """,
             ctorName=ctorName)
 
-# Encapsulate the constructor in a helper method to share genConstructorBody with CGJSImplMethod.
-class CGConstructNavigatorObject(CGAbstractMethod):
-    """
-    Construct a new JS-implemented WebIDL DOM object, for use on navigator.
-    """
-    def __init__(self, descriptor):
-        args = [Argument('JSContext*', 'cx'),
-                Argument('JS::Handle<JSObject*>', 'obj'),
-                Argument('ErrorResult&', 'aRv')]
-        rtype = 'already_AddRefed<%s>' % descriptor.name
-        CGAbstractMethod.__init__(self, descriptor, "ConstructNavigatorObject",
-                                  rtype, args)
-
-    def definition_body(self):
-        if not self.descriptor.interface.isJSImplemented():
-            raise TypeError("Only JS-implemented classes are currently supported "
-                            "on navigator. See bug 856820.")
-
-        return dedent(
-            """
-            GlobalObject global(cx, obj);
-            if (global.Failed()) {
-              aRv.Throw(NS_ERROR_FAILURE);
-              return nullptr;
-            }
-            """) + genConstructorBody(self.descriptor)
-
 
 def NamedConstructorName(m):
     return '_' + m.identifier.name
 
 
 class CGNamedConstructors(CGThing):
     def __init__(self, descriptor):
         self.descriptor = descriptor
@@ -2617,33 +2590,28 @@ class MethodDefiner(PropertyDefiner):
         return self.generatePrefableArray(
             array, name,
             self.formatSpec,
             '  JS_FS_END',
             'JSFunctionSpec',
             self.condition, functools.partial(self.specData, unforgeable=self.unforgeable))
 
 
-def isNonExposedNavigatorObjectGetter(attr, descriptor):
-    return (attr.navigatorObjectGetter and
-            not descriptor.getDescriptor(attr.type.inner.identifier.name).register)
-
 class AttrDefiner(PropertyDefiner):
     def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
         self.name = name
         # Ignore non-static attributes for interfaces without a proto object
         if descriptor.interface.hasInterfacePrototypeObject() or static:
             idlAttrs = [m for m in descriptor.interface.members if
                         m.isAttr() and m.isStatic() == static and
                         MemberIsUnforgeable(m, descriptor) == unforgeable and
                         (not crossOriginOnly or m.getExtendedAttribute("CrossOriginReadable") or
-                         m.getExtendedAttribute("CrossOriginWritable")) and
-                        not isNonExposedNavigatorObjectGetter(m, descriptor)]
+                         m.getExtendedAttribute("CrossOriginWritable"))]
         else:
             idlAttrs = []
 
         attributes = []
         for attr in idlAttrs:
             attributes.extend(self.attrData(attr, unforgeable))
         self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
         self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
@@ -3519,19 +3487,19 @@ def getConditionList(idlobj, cxName, obj
     if idlobj.getExtendedAttribute("SecureContext"):
         conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" % (cxName, objName))
 
     return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
 
 
 class CGConstructorEnabled(CGAbstractMethod):
     """
-    A method for testing whether we should be exposing this interface
-    object or navigator property.  This can perform various tests
-    depending on what conditions are specified on the interface.
+    A method for testing whether we should be exposing this interface object.
+    This can perform various tests depending on what conditions are specified
+    on the interface.
     """
     def __init__(self, descriptor):
         CGAbstractMethod.__init__(self, descriptor,
                                   'ConstructorEnabled', 'bool',
                                   [Argument("JSContext*", "aCx"),
                                    Argument("JS::Handle<JSObject*>", "aObj")])
 
     def definition_body(self):
@@ -8718,38 +8686,16 @@ class CGGetterCall(CGPerSignatureCall):
             nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
         CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
                                     attr.isStatic(), descriptor, attr,
                                     getter=True, useCounterName=useCounterName,
                                     dontSetSlot=dontSetSlot,
                                     extendedAttributes=extendedAttributes)
 
 
-class CGNavigatorGetterCall(CGPerSignatureCall):
-    """
-    A class to generate a native object getter call for an IDL getter for a
-    property generated by NavigatorProperty.
-    """
-    def __init__(self, returnType, _, descriptor, attr,
-                 dontSetSlot=False):
-        nativeMethodName = "%s::ConstructNavigatorObject" % (toBindingNamespace(returnType.inner.identifier.name))
-        CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
-                                    True, descriptor, attr, getter=True,
-                                    dontSetSlot=dontSetSlot)
-
-    def getArguments(self):
-        # The navigator object should be associated with the global of
-        # the navigator it's coming from, which will be the global of
-        # the object whose slot it gets cached in.  That's stored in
-        # "slotStorage".
-        return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object],
-                              self.idlNode),
-                 "slotStorage")]
-
-
 class FakeIdentifier():
     def __init__(self, name):
         self.name = name
 
 
 class FakeArgument():
     """
     A class that quacks like an IDLArgument.  This is used to make
@@ -9295,23 +9241,21 @@ class CGSpecializedGetter(CGAbstractStat
             call=CGGetterCall(remoteType, nativeName, self.descriptor, self.attr, dontSetSlot=True,
                               extendedAttributes=extendedAttributes).define())
         if self.attr.slotIndices is not None:
             # We're going to store this return value in a slot on some object,
             # to cache it.  The question is, which object?  For dictionary and
             # sequence return values, we want to use a slot on the Xray expando
             # if we're called via Xrays, and a slot on our reflector otherwise.
             # On the other hand, when dealing with some interfacce types
-            # (navigator properties, window.document) we want to avoid calling
-            # the getter more than once.  In the case of navigator properties
-            # that's because the getter actually creates a new object each time.
-            # In the case of window.document, it's because the getter can start
-            # returning null, which would get hidden in he non-Xray case by the
-            # fact that it's [StoreOnSlot], so the cached version is always
-            # around.
+            # (e.g. window.document) we want to avoid calling the getter more
+            # than once.  In the case of window.document, it's because the
+            # getter can start returning null, which would get hidden in the
+            # non-Xray case by the fact that it's [StoreOnSlot], so the cached
+            # version is always around.
             #
             # The upshot is that we use the reflector slot for any getter whose
             # type is a gecko interface, whether we're called via Xrays or not.
             # Since [Cached] and [StoreInSlot] cannot be used with "NewObject",
             # we know that in the interface type case the returned object is
             # wrappercached.  So creating Xrays to it is reasonable.
             if mayUseXrayExpandoSlots(self.descriptor, self.attr):
                 prefix += fill(
@@ -9349,22 +9293,18 @@ class CGSpecializedGetter(CGAbstractStat
                     // so wrap into the caller compartment as needed.
                     return ${maybeWrap}(cx, args.rval());
                   }
                 }
 
                 """,
                 maybeWrap=getMaybeWrapValueFuncForType(self.attr.type))
 
-        if self.attr.navigatorObjectGetter:
-            cgGetterCall = CGNavigatorGetterCall
-        else:
-            cgGetterCall = CGGetterCall
         return (prefix +
-                cgGetterCall(type, nativeName,
+                CGGetterCall(type, nativeName,
                              self.descriptor, self.attr).define())
 
     def auto_profiler_label(self):
         interface_name = self.descriptor.interface.identifier.name
         attr_name = self.attr.identifier.name
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
@@ -13206,18 +13146,16 @@ class CGDescriptor(CGThing):
                                     "%s" % m.location)
                 if m.getExtendedAttribute("Unscopable"):
                     assert not m.isStatic()
                     unscopableNames.append(m.identifier.name)
                 if m.isStatic():
                     assert descriptor.interface.hasInterfaceObject()
                     cgThings.append(CGStaticGetter(descriptor, m))
                 elif descriptor.interface.hasInterfacePrototypeObject():
-                    if isNonExposedNavigatorObjectGetter(m, descriptor):
-                        continue
                     specializedGetter = CGSpecializedGetter(descriptor, m)
                     cgThings.append(specializedGetter)
                     if m.type.isPromise():
                         cgThings.append(CGGetterPromiseWrapper(descriptor, specializedGetter))
                     if props.isCrossOriginGetter:
                         needCrossOriginPropertyArrays = True
                 if not m.readonly:
                     if m.isStatic():
@@ -13239,18 +13177,16 @@ class CGDescriptor(CGThing):
                     descriptor.interface.hasInterfacePrototypeObject()):
                     cgThings.append(CGMemberJITInfo(descriptor, m))
             if m.isConst() and m.type.isPrimitive():
                 cgThings.append(CGConstDefinition(m))
 
         if defaultToJSONMethod:
             cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod))
             cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod))
-        if descriptor.interface.isNavigatorProperty():
-            cgThings.append(CGConstructNavigatorObject(descriptor))
 
         if descriptor.concrete and not descriptor.proxy:
             if wantsAddProperty(descriptor):
                 cgThings.append(CGAddPropertyHook(descriptor))
 
             # Always have a finalize hook, regardless of whether the class
             # wants a custom hook.
             cgThings.append(CGClassFinalizeHook(descriptor))
@@ -13280,17 +13216,17 @@ class CGDescriptor(CGThing):
             cgThings.append(CGEnumerateHook(descriptor))
 
         if descriptor.hasNamedPropertiesObject:
             cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
 
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor, properties))
 
-        if ((descriptor.interface.hasInterfaceObject() or descriptor.interface.isNavigatorProperty()) and
+        if (descriptor.interface.hasInterfaceObject() and
             not descriptor.interface.isExternal() and
             descriptor.isExposedConditionally()):
             cgThings.append(CGConstructorEnabled(descriptor))
 
         if (descriptor.interface.hasMembersInSlots() and
             descriptor.interface.hasChildInterfaces()):
             raise TypeError("We don't support members in slots on "
                             "non-leaf interfaces like %s" %
@@ -15920,33 +15856,29 @@ class CGJSImplMethod(CGJSImplMember):
                 impl->mImpl->__Init(${args});
                 if (aRv.Failed()) {
                   return nullptr;
                 }
                 """,
                 args=", ".join(constructorArgs))
         else:
             initCall = ""
-        return genConstructorBody(self.descriptor, initCall)
-
-
-def genConstructorBody(descriptor, initCall=""):
-    return fill(
-        """
-        RefPtr<${implClass}> impl =
-          ConstructJSImplementation<${implClass}>("${contractId}", global, aRv);
-        if (aRv.Failed()) {
-          return nullptr;
-        }
-        $*{initCall}
-        return impl.forget();
-        """,
-        contractId=descriptor.interface.getJSImplementation(),
-        implClass=descriptor.name,
-        initCall=initCall)
+        return fill(
+            """
+            RefPtr<${implClass}> impl =
+              ConstructJSImplementation<${implClass}>("${contractId}", global, aRv);
+            if (aRv.Failed()) {
+              return nullptr;
+            }
+            $*{initCall}
+            return impl.forget();
+            """,
+            contractId=self.descriptor.interface.getJSImplementation(),
+            implClass=self.descriptor.name,
+            initCall=initCall)
 
 
 # We're always fallible
 def callbackGetterName(attr, descriptor):
     return "Get" + MakeNativeName(
         descriptor.binaryNameFor(attr.identifier.name))
 
 
@@ -17918,19 +17850,16 @@ class GlobalGenRoots():
         # Add the includes
         defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
                           for desc in config.getDescriptors(hasInterfaceObject=True,
                                                             isExposedInWindow=True,
                                                             register=True)]
         defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h')
         defineIncludes.append('mozilla/dom/PrototypeList.h')
         defineIncludes.append('mozilla/PerfectHash.h')
-        defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface)
-                               for desc in config.getDescriptors(isNavigatorProperty=True,
-                                                                 register=True)])
         curr = CGHeaders([], [], [], [], [], defineIncludes, 'RegisterBindings',
                          curr)
 
         # Add include guards.
         curr = CGIncludeGuard('RegisterBindings', curr)
 
         # Done.
         return curr
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -78,41 +78,29 @@ class Configuration(DescriptorProvider):
             iface = thing
             # Our build system doesn't support dep builds involving
             # addition/removal of partial interfaces that appear in a different
             # .webidl file than the interface they are extending.  Make sure we
             # don't have any of those.  See similar block above for "implements"
             # statements!
             if not iface.isExternal():
                 for partialIface in iface.getPartials():
-                    if (partialIface.filename() != iface.filename() and
-                        # Unfortunately, NavigatorProperty does exactly the
-                        # thing we're trying to prevent here.  I'm not sure how
-                        # to deal with that, short of effectively requiring a
-                        # clobber when NavigatorProperty is added/removed and
-                        # whitelisting the things it outputs here as
-                        # restrictively as I can.
-                        (partialIface.identifier.name != "Navigator" or
-                         len(partialIface.members) != 1 or
-                         partialIface.members[0].location != partialIface.location or
-                         partialIface.members[0].identifier.location.filename() !=
-                           "<builtin>")):
+                    if partialIface.filename() != iface.filename():
                         raise TypeError(
                             "The binding build system doesn't really support "
                             "partial interfaces which don't appear in the "
                             "file in which the interface they are extending is "
                             "defined.  Don't do this.\n"
                             "%s\n"
                             "%s" %
                             (partialIface.location, iface.location))
                 if not (iface.getExtendedAttribute("ChromeOnly") or
                         iface.getExtendedAttribute("Func") == ["IsChromeOrXBL"] or
                         iface.getExtendedAttribute("Func") == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"] or
-                        not (iface.hasInterfaceObject() or
-                             iface.isNavigatorProperty()) or
+                        not iface.hasInterfaceObject() or
                         isInWebIDLRoot(iface.filename())):
                     raise TypeError(
                         "Interfaces which are exposed to the web may only be "
                         "defined in a DOM WebIDL root %r. Consider marking "
                         "the interface [ChromeOnly] or [Func='IsChromeOrXBL'] "
                         "if you do not want it exposed to the web.\n"
                         "%s" %
                         (webRoots, iface.location))
@@ -237,18 +225,16 @@ class Configuration(DescriptorProvider):
             elif key == 'hasInterfaceOrInterfacePrototypeObject':
                 getter = lambda x: x.hasInterfaceOrInterfacePrototypeObject()
             elif key == 'isCallback':
                 getter = lambda x: x.interface.isCallback()
             elif key == 'isExternal':
                 getter = lambda x: x.interface.isExternal()
             elif key == 'isJSImplemented':
                 getter = lambda x: x.interface.isJSImplemented()
-            elif key == 'isNavigatorProperty':
-                getter = lambda x: x.interface.isNavigatorProperty()
             elif key == 'isExposedInAnyWorker':
                 getter = lambda x: x.interface.isExposedInAnyWorker()
             elif key == 'isExposedInWorkerDebugger':
                 getter = lambda x: x.interface.isExposedInWorkerDebugger()
             elif key == 'isExposedInAnyWorklet':
                 getter = lambda x: x.interface.isExposedInAnyWorklet()
             elif key == 'isExposedInWindow':
                 getter = lambda x: x.interface.isExposedInWindow()
@@ -570,23 +556,16 @@ class Descriptor(DescriptorProvider):
                     add('all', [config], attribute)
 
         if self.interface.isJSImplemented():
             addExtendedAttribute('implicitJSContext', ['constructor'])
         else:
             for attribute in ['implicitJSContext']:
                 addExtendedAttribute(attribute, desc.get(attribute, {}))
 
-        if self.interface.identifier.name == 'Navigator':
-            for m in self.interface.members:
-                if m.isAttr() and m.navigatorObjectGetter:
-                    # These getters call ConstructNavigatorObject to construct
-                    # the value, and ConstructNavigatorObject needs a JSContext.
-                    self.extendedAttributes['all'].setdefault(m.identifier.name, []).append('implicitJSContext')
-
         self._binaryNames = desc.get('binaryNames', {})
         self._binaryNames.setdefault('__legacycaller', 'LegacyCall')
         self._binaryNames.setdefault('__stringifier', 'Stringify')
 
         if not self.interface.isExternal():
             def isTestInterface(iface):
                 return (iface.identifier.name in ["TestInterface",
                                                   "TestJSImplInterface",
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -549,19 +549,16 @@ class IDLExternalInterface(IDLObjectWith
         return None
 
     def isJSImplemented(self):
         return False
 
     def hasProbablyShortLivingWrapper(self):
         return False
 
-    def isNavigatorProperty(self):
-        return False
-
     def isSerializable(self):
         return False
 
     def _getDependentObjects(self):
         return set()
 
 
 class IDLPartialDictionary(IDLObject):
@@ -1457,22 +1454,21 @@ class IDLInterfaceOrNamespace(IDLInterfa
             # Check that the name of a [BindingAlias] doesn't conflict with an
             # interface member.
             if member.isAttr():
                 for bindingAlias in member.bindingAliases:
                     checkDuplicateNames(member, bindingAlias, "BindingAlias")
 
 
         # Conditional exposure makes no sense for interfaces with no
-        # interface object, unless they're navigator properties.
+        # interface object.
         # And SecureContext makes sense for interfaces with no interface object,
         # since it is also propagated to interface members.
         if (self.isExposedConditionally(exclusions=["SecureContext"]) and
-            not self.hasInterfaceObject() and
-            not self.isNavigatorProperty()):
+            not self.hasInterfaceObject()):
             raise WebIDLError("Interface with no interface object is "
                               "exposed conditionally",
                               [self.location])
 
         # Value iterators are only allowed on interfaces with indexed getters,
         # and pair iterators are only allowed on interfaces without indexed
         # getters.
         if self.isIterable():
@@ -1635,49 +1631,16 @@ class IDLInterfaceOrNamespace(IDLInterfa
     def hasProbablyShortLivingWrapper(self):
         current = self
         while current:
             if current.getExtendedAttribute("ProbablyShortLivingWrapper"):
                 return True
             current = current.parent
         return False
 
-    def isNavigatorProperty(self):
-        naviProp = self.getExtendedAttribute("NavigatorProperty")
-        if not naviProp:
-            return False
-        assert len(naviProp) == 1
-        assert isinstance(naviProp, list)
-        assert len(naviProp[0]) != 0
-        return True
-
-    def getNavigatorProperty(self):
-        naviProp = self.getExtendedAttribute("NavigatorProperty")
-        if not naviProp:
-            return None
-        assert len(naviProp) == 1
-        assert isinstance(naviProp, list)
-        assert len(naviProp[0]) != 0
-        conditionExtendedAttributes = self._extendedAttrDict.viewkeys() & IDLInterfaceOrNamespace.conditionExtendedAttributes
-        attr = IDLAttribute(self.location,
-                            IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), naviProp[0]),
-                            IDLUnresolvedType(self.location, IDLUnresolvedIdentifier(self.location, self.identifier.name)),
-                            True,
-                            extendedAttrDict={ a: self._extendedAttrDict[a] for a in conditionExtendedAttributes },
-                            navigatorObjectGetter=True)
-        attr._exposureGlobalNames = self._exposureGlobalNames
-        # We're abusing Constant a little bit here, because we need Cached. The
-        # getter will create a new object every time, but we're never going to
-        # clear the cached value.
-        extendedAttrs = [ IDLExtendedAttribute(self.location, ("Throws", )),
-                          IDLExtendedAttribute(self.location, ("Cached", )),
-                          IDLExtendedAttribute(self.location, ("Constant", )) ]
-        attr.addExtendedAttributes(extendedAttrs)
-        return attr
-
     def hasChildInterfaces(self):
         return self._hasChildInterfaces
 
     def isOnGlobalProtoChain(self):
         return self._isOnGlobalProtoChain
 
     def _getDependentObjects(self):
         deps = set(self.members)
@@ -1889,17 +1852,16 @@ class IDLInterface(IDLInterfaceOrNamespa
                     raise WebIDLError("[%s] must take no arguments" % identifier,
                                       [attr.location])
             elif identifier == "Exposed":
                 convertExposedAttrToGlobalNameSet(attr,
                                                   self._exposureGlobalNames)
             elif (identifier == "Pref" or
                   identifier == "JSImplementation" or
                   identifier == "HeaderFile" or
-                  identifier == "NavigatorProperty" or
                   identifier == "Func" or
                   identifier == "Deprecated"):
                 # Known extended attributes that take a string value
                 if not attr.hasValue():
                     raise WebIDLError("[%s] must have a value" % identifier,
                                       [attr.location])
             else:
                 raise WebIDLError("Unknown extended attribute %s on interface" % identifier,
@@ -4325,17 +4287,17 @@ class IDLConst(IDLInterfaceMember):
 
     def _getDependentObjects(self):
         return set([self.type, self.value])
 
 
 class IDLAttribute(IDLInterfaceMember):
     def __init__(self, location, identifier, type, readonly, inherit=False,
                  static=False, stringifier=False, maplikeOrSetlike=None,
-                 extendedAttrDict=None, navigatorObjectGetter=False):
+                 extendedAttrDict=None):
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Attr,
                                     extendedAttrDict=extendedAttrDict)
 
         assert isinstance(type, IDLType)
         self.type = type
         self.readonly = readonly
         self.inherit = inherit
@@ -4343,17 +4305,16 @@ class IDLAttribute(IDLInterfaceMember):
         self.lenientThis = False
         self._unforgeable = False
         self.stringifier = stringifier
         self.slotIndices = None
         assert maplikeOrSetlike is None or isinstance(maplikeOrSetlike, IDLMaplikeOrSetlike)
         self.maplikeOrSetlike = maplikeOrSetlike
         self.dependsOn = "Everything"
         self.affects = "Everything"
-        self.navigatorObjectGetter = navigatorObjectGetter
         self.bindingAliases = []
 
         if static and identifier.name == "prototype":
             raise WebIDLError("The identifier of a static attribute must not be 'prototype'",
                               [location])
 
         if readonly and inherit:
             raise WebIDLError("An attribute cannot be both 'readonly' and 'inherit'",
@@ -7427,33 +7388,19 @@ class Parser(Tokenizer):
 
     def finish(self):
         # If we have interfaces that are iterable, create their
         # iterator interfaces and add them to the productions array.
         interfaceStatements = []
         for p in self._productions:
             if isinstance(p, IDLInterface):
                 interfaceStatements.append(p)
-                if p.identifier.name == "Navigator":
-                    navigatorInterface = p
 
         iterableIteratorIface = None
         for iface in interfaceStatements:
-            navigatorProperty = iface.getNavigatorProperty()
-            if navigatorProperty:
-                # We're generating a partial interface to add a readonly
-                # property to the Navigator interface for every interface
-                # annotated with NavigatorProperty.
-                partialInterface = IDLPartialInterfaceOrNamespace(
-                    iface.location,
-                    IDLUnresolvedIdentifier(iface.location, "Navigator"),
-                    [ navigatorProperty ],
-                    navigatorInterface)
-                self._productions.append(partialInterface)
-
             iterable = None
             # We haven't run finish() on the interface yet, so we don't know
             # whether our interface is maplike/setlike/iterable or not. This
             # means we have to loop through the members to see if we have an
             # iterable member.
             for m in iface.members:
                 if isinstance(m, IDLIterable):
                     iterable = m
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -836,24 +836,16 @@ interface TestJSImplInterface {
 
   // [NeedsWindowsUndef] test generation
   [NeedsWindowsUndef]
   const unsigned long NO_ERROR = 0xffffffff;
 
   // If you add things here, add them to TestCodeGen as well
 };
 
-[NavigatorProperty="TestNavigator", JSImplementation="@mozilla.org/test;1"]
-interface TestNavigator {
-};
-
-[Constructor, NavigatorProperty="TestNavigatorWithConstructor", JSImplementation="@mozilla.org/test;1"]
-interface TestNavigatorWithConstructor {
-};
-
 interface TestCImplementedInterface : TestJSImplInterface {
 };
 
 interface TestCImplementedInterface2 {
 };
 
 [NoInterfaceObject,
  JSImplementation="@mozilla.org/test-js-impl-interface;2"]
--- a/dom/media/webspeech/synth/SpeechSynthesis.cpp
+++ b/dom/media/webspeech/synth/SpeechSynthesis.cpp
@@ -71,37 +71,28 @@ SpeechSynthesis::SpeechSynthesis(nsPIDOM
 SpeechSynthesis::~SpeechSynthesis() {}
 
 JSObject* SpeechSynthesis::WrapObject(JSContext* aCx,
                                       JS::Handle<JSObject*> aGivenProto) {
   return SpeechSynthesis_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 bool SpeechSynthesis::Pending() const {
-  switch (mSpeechQueue.Length()) {
-    case 0:
-      return false;
-
-    case 1:
-      return mSpeechQueue.ElementAt(0)->GetState() ==
-             SpeechSynthesisUtterance::STATE_PENDING;
-
-    default:
-      return true;
-  }
+  // If we don't have any task, nothing is pending. If we have only one task,
+  // check if that task is currently pending. If we have more than one task,
+  // then the tasks after the first one are definitely pending.
+  return mSpeechQueue.Length() > 1 ||
+         (mSpeechQueue.Length() == 1 &&
+          (!mCurrentTask || mCurrentTask->IsPending()));
 }
 
 bool SpeechSynthesis::Speaking() const {
-  if (!mSpeechQueue.IsEmpty() && mSpeechQueue.ElementAt(0)->GetState() ==
-                                     SpeechSynthesisUtterance::STATE_SPEAKING) {
-    return true;
-  }
-
-  // Returns global speaking state if global queue is enabled. Or false.
-  return nsSynthVoiceRegistry::GetInstance()->IsSpeaking();
+  // Check global speaking state if there is no active speaking task.
+  return (!mSpeechQueue.IsEmpty() && HasSpeakingTask()) ||
+         nsSynthVoiceRegistry::GetInstance()->IsSpeaking();
 }
 
 bool SpeechSynthesis::Paused() const {
   return mHoldQueue || (mCurrentTask && mCurrentTask->IsPrePaused()) ||
          (!mSpeechQueue.IsEmpty() && mSpeechQueue.ElementAt(0)->IsPaused());
 }
 
 bool SpeechSynthesis::HasEmptyQueue() const {
@@ -121,23 +112,17 @@ bool SpeechSynthesis::HasVoices() const 
   return voiceCount != 0;
 }
 
 void SpeechSynthesis::Speak(SpeechSynthesisUtterance& aUtterance) {
   if (!mInnerID) {
     return;
   }
 
-  if (aUtterance.mState != SpeechSynthesisUtterance::STATE_NONE) {
-    // XXX: Should probably raise an error
-    return;
-  }
-
   mSpeechQueue.AppendElement(&aUtterance);
-  aUtterance.mState = SpeechSynthesisUtterance::STATE_PENDING;
 
   // If we only have one item in the queue, we aren't pre-paused, and
   // we have voices available, speak it.
   if (mSpeechQueue.Length() == 1 && !mCurrentTask && !mHoldQueue &&
       HasVoices()) {
     AdvanceQueue();
   }
 }
@@ -168,18 +153,17 @@ void SpeechSynthesis::AdvanceQueue() {
       nsSynthVoiceRegistry::GetInstance()->SpeakUtterance(*utterance, docLang);
 
   if (mCurrentTask) {
     mCurrentTask->SetSpeechSynthesis(this);
   }
 }
 
 void SpeechSynthesis::Cancel() {
-  if (!mSpeechQueue.IsEmpty() && mSpeechQueue.ElementAt(0)->GetState() ==
-                                     SpeechSynthesisUtterance::STATE_SPEAKING) {
+  if (!mSpeechQueue.IsEmpty() && HasSpeakingTask()) {
     // Remove all queued utterances except for current one, we will remove it
     // in OnEnd
     mSpeechQueue.RemoveElementsAt(1, mSpeechQueue.Length() - 1);
   } else {
     mSpeechQueue.Clear();
   }
 
   if (mCurrentTask) {
@@ -187,19 +171,17 @@ void SpeechSynthesis::Cancel() {
   }
 }
 
 void SpeechSynthesis::Pause() {
   if (Paused()) {
     return;
   }
 
-  if (mCurrentTask && !mSpeechQueue.IsEmpty() &&
-      mSpeechQueue.ElementAt(0)->GetState() ==
-          SpeechSynthesisUtterance::STATE_SPEAKING) {
+  if (!mSpeechQueue.IsEmpty() && HasSpeakingTask()) {
     mCurrentTask->Pause();
   } else {
     mHoldQueue = true;
   }
 }
 
 void SpeechSynthesis::Resume() {
   if (!Paused()) {
--- a/dom/media/webspeech/synth/SpeechSynthesis.h
+++ b/dom/media/webspeech/synth/SpeechSynthesis.h
@@ -65,16 +65,20 @@ class SpeechSynthesis final : public DOM
 
  private:
   virtual ~SpeechSynthesis();
 
   void AdvanceQueue();
 
   bool HasVoices() const;
 
+  bool HasSpeakingTask() const {
+    return mCurrentTask && mCurrentTask->IsSpeaking();
+  }
+
   nsTArray<RefPtr<SpeechSynthesisUtterance> > mSpeechQueue;
 
   RefPtr<nsSpeechTask> mCurrentTask;
 
   nsRefPtrHashtable<nsStringHashKey, SpeechSynthesisVoice> mVoiceCache;
 
   bool mHoldQueue;
 
--- a/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
+++ b/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
@@ -29,17 +29,16 @@ NS_IMPL_RELEASE_INHERITED(SpeechSynthesi
 
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(
     nsPIDOMWindowInner* aOwnerWindow, const nsAString& text)
     : DOMEventTargetHelper(aOwnerWindow),
       mText(text),
       mVolume(1),
       mRate(1),
       mPitch(1),
-      mState(STATE_NONE),
       mPaused(false) {}
 
 SpeechSynthesisUtterance::~SpeechSynthesisUtterance() {}
 
 JSObject* SpeechSynthesisUtterance::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return SpeechSynthesisUtterance_Binding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/media/webspeech/synth/SpeechSynthesisUtterance.h
+++ b/dom/media/webspeech/synth/SpeechSynthesisUtterance.h
@@ -65,20 +65,16 @@ class SpeechSynthesisUtterance final : p
   void SetRate(float aRate);
 
   float Pitch() const;
 
   void SetPitch(float aPitch);
 
   void GetChosenVoiceURI(nsString& aResult) const;
 
-  enum { STATE_NONE, STATE_PENDING, STATE_SPEAKING, STATE_ENDED };
-
-  uint32_t GetState() { return mState; }
-
   bool IsPaused() { return mPaused; }
 
   IMPL_EVENT_HANDLER(start)
   IMPL_EVENT_HANDLER(end)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(pause)
   IMPL_EVENT_HANDLER(resume)
   IMPL_EVENT_HANDLER(mark)
@@ -99,18 +95,16 @@ class SpeechSynthesisUtterance final : p
   float mVolume;
 
   float mRate;
 
   float mPitch;
 
   nsString mChosenVoiceURI;
 
-  uint32_t mState;
-
   bool mPaused;
 
   RefPtr<SpeechSynthesisVoice> mVoice;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -36,31 +36,33 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeech
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
 
 nsSpeechTask::nsSpeechTask(SpeechSynthesisUtterance* aUtterance, bool aIsChrome)
     : mUtterance(aUtterance),
       mInited(false),
       mPrePaused(false),
       mPreCanceled(false),
       mCallback(nullptr),
-      mIsChrome(aIsChrome) {
+      mIsChrome(aIsChrome),
+      mState(STATE_PENDING) {
   mText = aUtterance->mText;
   mVolume = aUtterance->Volume();
 }
 
 nsSpeechTask::nsSpeechTask(float aVolume, const nsAString& aText,
                            bool aIsChrome)
     : mUtterance(nullptr),
       mVolume(aVolume),
       mText(aText),
       mInited(false),
       mPrePaused(false),
       mPreCanceled(false),
       mCallback(nullptr),
-      mIsChrome(aIsChrome) {}
+      mIsChrome(aIsChrome),
+      mState(STATE_PENDING) {}
 
 nsSpeechTask::~nsSpeechTask() { LOG(LogLevel::Debug, ("~nsSpeechTask")); }
 
 void nsSpeechTask::Init() { mInited = true; }
 
 void nsSpeechTask::SetChosenVoiceURI(const nsAString& aUri) {
   mChosenVoiceURI = aUri;
 }
@@ -85,24 +87,23 @@ nsSpeechTask::DispatchStart() {
 nsresult nsSpeechTask::DispatchStartImpl() {
   return DispatchStartImpl(mChosenVoiceURI);
 }
 
 nsresult nsSpeechTask::DispatchStartImpl(const nsAString& aUri) {
   LOG(LogLevel::Debug, ("nsSpeechTask::DispatchStartImpl"));
 
   MOZ_ASSERT(mUtterance);
-  if (NS_WARN_IF(
-          !(mUtterance->mState == SpeechSynthesisUtterance::STATE_PENDING))) {
+  if (NS_WARN_IF(mState != STATE_PENDING)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   CreateAudioChannelAgent();
 
-  mUtterance->mState = SpeechSynthesisUtterance::STATE_SPEAKING;
+  mState = STATE_SPEAKING;
   mUtterance->mChosenVoiceURI = aUri;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("start"), 0,
                                            nullptr, 0, EmptyString());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -119,116 +120,114 @@ nsSpeechTask::DispatchEnd(float aElapsed
 
 nsresult nsSpeechTask::DispatchEndImpl(float aElapsedTime,
                                        uint32_t aCharIndex) {
   LOG(LogLevel::Debug, ("nsSpeechTask::DispatchEndImpl"));
 
   DestroyAudioChannelAgent();
 
   MOZ_ASSERT(mUtterance);
-  if (NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
+  if (NS_WARN_IF(mState == STATE_ENDED)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
 
   if (mSpeechSynthesis) {
     mSpeechSynthesis->OnEnd(this);
   }
 
-  if (utterance->mState == SpeechSynthesisUtterance::STATE_PENDING) {
-    utterance->mState = SpeechSynthesisUtterance::STATE_NONE;
-  } else {
-    utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
-    utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
-                                            aCharIndex, nullptr, aElapsedTime,
-                                            EmptyString());
-  }
+  mState = STATE_ENDED;
+  utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"), aCharIndex,
+                                          nullptr, aElapsedTime, EmptyString());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex) {
   return DispatchPauseImpl(aElapsedTime, aCharIndex);
 }
 
 nsresult nsSpeechTask::DispatchPauseImpl(float aElapsedTime,
                                          uint32_t aCharIndex) {
   LOG(LogLevel::Debug, ("nsSpeechTask::DispatchPauseImpl"));
   MOZ_ASSERT(mUtterance);
   if (NS_WARN_IF(mUtterance->mPaused)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  if (NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
+  if (NS_WARN_IF(mState == STATE_ENDED)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   mUtterance->mPaused = true;
-  if (mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) {
+  if (mState == STATE_SPEAKING) {
     mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("pause"),
                                              aCharIndex, nullptr, aElapsedTime,
                                              EmptyString());
   }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchResume(float aElapsedTime, uint32_t aCharIndex) {
   return DispatchResumeImpl(aElapsedTime, aCharIndex);
 }
 
 nsresult nsSpeechTask::DispatchResumeImpl(float aElapsedTime,
                                           uint32_t aCharIndex) {
   LOG(LogLevel::Debug, ("nsSpeechTask::DispatchResumeImpl"));
   MOZ_ASSERT(mUtterance);
   if (NS_WARN_IF(!(mUtterance->mPaused))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  if (NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
+  if (NS_WARN_IF(mState == STATE_ENDED)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   mUtterance->mPaused = false;
-  if (mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) {
+  if (mState == STATE_SPEAKING) {
     mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("resume"),
                                              aCharIndex, nullptr, aElapsedTime,
                                              EmptyString());
   }
 
   return NS_OK;
 }
 
 void nsSpeechTask::ForceError(float aElapsedTime, uint32_t aCharIndex) {
   DispatchError(aElapsedTime, aCharIndex);
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchError(float aElapsedTime, uint32_t aCharIndex) {
-  LOG(LogLevel::Debug, ("nsSpeechTask::DispatchError"));
-
   if (!mPreCanceled) {
     nsSynthVoiceRegistry::GetInstance()->SpeakNext();
   }
 
   return DispatchErrorImpl(aElapsedTime, aCharIndex);
 }
 
 nsresult nsSpeechTask::DispatchErrorImpl(float aElapsedTime,
                                          uint32_t aCharIndex) {
+  LOG(LogLevel::Debug, ("nsSpeechTask::DispatchErrorImpl"));
+
+  DestroyAudioChannelAgent();
+
   MOZ_ASSERT(mUtterance);
-  if (NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
+  if (NS_WARN_IF(mState == STATE_ENDED)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (mSpeechSynthesis) {
     mSpeechSynthesis->OnEnd(this);
   }
 
-  mUtterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
+  mState = STATE_ENDED;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("error"),
                                            aCharIndex, nullptr, aElapsedTime,
                                            EmptyString());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchBoundary(const nsAString& aName, float aElapsedTime,
@@ -239,18 +238,17 @@ nsSpeechTask::DispatchBoundary(const nsA
 }
 
 nsresult nsSpeechTask::DispatchBoundaryImpl(const nsAString& aName,
                                             float aElapsedTime,
                                             uint32_t aCharIndex,
                                             uint32_t aCharLength,
                                             uint8_t argc) {
   MOZ_ASSERT(mUtterance);
-  if (NS_WARN_IF(
-          !(mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING))) {
+  if (NS_WARN_IF(mState != STATE_SPEAKING)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   mUtterance->DispatchSpeechSynthesisEvent(
       NS_LITERAL_STRING("boundary"), aCharIndex,
       argc ? static_cast<Nullable<uint32_t> >(aCharLength) : nullptr,
       aElapsedTime, aName);
 
   return NS_OK;
@@ -261,21 +259,19 @@ nsSpeechTask::DispatchMark(const nsAStri
                            uint32_t aCharIndex) {
   return DispatchMarkImpl(aName, aElapsedTime, aCharIndex);
 }
 
 nsresult nsSpeechTask::DispatchMarkImpl(const nsAString& aName,
                                         float aElapsedTime,
                                         uint32_t aCharIndex) {
   MOZ_ASSERT(mUtterance);
-  if (NS_WARN_IF(
-          !(mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING))) {
+  if (NS_WARN_IF(mState != STATE_SPEAKING)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-
   mUtterance->DispatchSpeechSynthesisEvent(
       NS_LITERAL_STRING("mark"), aCharIndex, nullptr, aElapsedTime, aName);
   return NS_OK;
 }
 
 void nsSpeechTask::Pause() {
   MOZ_ASSERT(XRE_IsParentProcess());
 
--- a/dom/media/webspeech/synth/nsSpeechTask.h
+++ b/dom/media/webspeech/synth/nsSpeechTask.h
@@ -55,16 +55,24 @@ class nsSpeechTask : public nsISpeechTas
   void ForceError(float aElapsedTime, uint32_t aCharIndex);
 
   bool IsPreCanceled() { return mPreCanceled; };
 
   bool IsPrePaused() { return mPrePaused; }
 
   bool IsChrome() { return mIsChrome; }
 
+  enum { STATE_PENDING, STATE_SPEAKING, STATE_ENDED };
+
+  uint32_t GetState() const { return mState; }
+
+  bool IsSpeaking() const { return mState == STATE_SPEAKING; }
+
+  bool IsPending() const { return mState == STATE_PENDING; }
+
  protected:
   virtual ~nsSpeechTask();
 
   nsresult DispatchStartImpl();
 
   virtual nsresult DispatchStartImpl(const nsAString& aUri);
 
   virtual nsresult DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex);
@@ -105,14 +113,16 @@ class nsSpeechTask : public nsISpeechTas
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   RefPtr<SpeechSynthesis> mSpeechSynthesis;
 
   nsString mChosenVoiceURI;
 
   bool mIsChrome;
+
+  uint32_t mState;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/webidl/AddonManager.webidl
+++ b/dom/webidl/AddonManager.webidl
@@ -51,17 +51,16 @@ dictionary addonInstallOptions {
   // If a non-empty string is passed for "hash", it is used to verify the
   // checksum of the downloaded XPI before installing.  If is omitted or if
   // it is null or empty string, no checksum verification is performed.
   DOMString? hash = null;
 };
 
 [HeaderFile="mozilla/AddonManagerWebAPI.h",
  Func="mozilla::AddonManagerWebAPI::IsAPIEnabled",
- NavigatorProperty="mozAddonManager",
  JSImplementation="@mozilla.org/addon-web-api/manager;1",
  WantsEventListenerHooks]
 interface AddonManager : EventTarget {
   /**
    * Gets information about an add-on
    *
    * @param  id
    *         The ID of the add-on to test for.
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -189,16 +189,22 @@ partial interface Navigator {
   [Throws, Constant, Cached, NeedsCallerType]
   readonly attribute DOMString buildID;
 
   // WebKit/Blink/Trident/Presto support this.
   [Affects=Nothing, DependsOn=Nothing]
   boolean javaEnabled();
 };
 
+// Addon manager bits
+partial interface Navigator {
+  [Throws, Func="mozilla::AddonManagerWebAPI::IsAPIEnabled"]
+  readonly attribute AddonManager mozAddonManager;
+};
+
 // NetworkInformation
 partial interface Navigator {
   [Throws, Pref="dom.netinfo.enabled"]
   readonly attribute NetworkInformation connection;
 };
 
 // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#navigator-interface-extension
 partial interface Navigator {
--- a/gfx/wr/direct-composition/src/lib.rs
+++ b/gfx/wr/direct-composition/src/lib.rs
@@ -23,17 +23,17 @@ use winapi::um::dcomp::IDCompositionVisu
 mod com;
 mod egl;
 
 pub struct DirectComposition {
     d3d_device: ComPtr<ID3D11Device>,
     dxgi_factory: ComPtr<IDXGIFactory2>,
 
     egl: Rc<egl::SharedEglThings>,
-    pub gleam: Rc<gleam::gl::Gl>,
+    pub gleam: Rc<dyn gleam::gl::Gl>,
 
     composition_device: ComPtr<IDCompositionDevice>,
     root_visual: ComPtr<IDCompositionVisual>,
 
     #[allow(unused)]  // Needs to be kept alive
     composition_target: ComPtr<IDCompositionTarget>,
 }
 
@@ -145,17 +145,17 @@ impl DirectComposition {
     }
 }
 
 /// A DirectComposition "visual" configured for rendering with Direct3D.
 pub struct AngleVisual {
     visual: ComPtr<IDCompositionVisual>,
     swap_chain: ComPtr<winapi::shared::dxgi1_2::IDXGISwapChain1>,
     egl: egl::PerVisualEglThings,
-    pub gleam: Rc<gleam::gl::Gl>,
+    pub gleam: Rc<dyn gleam::gl::Gl>,
 }
 
 impl AngleVisual {
     pub fn set_offset_x(&self, offset_x: f32) {
         unsafe {
             self.visual.SetOffsetX_1(offset_x).check_hresult()
         }
     }
--- a/gfx/wr/direct-composition/src/main_windows.rs
+++ b/gfx/wr/direct-composition/src/main_windows.rs
@@ -189,17 +189,17 @@ impl Drop for Rectangle {
 
 #[derive(Clone)]
 struct Notifier {
     events_proxy: winit::EventsLoopProxy,
     tx: mpsc::Sender<()>,
 }
 
 impl api::RenderNotifier for Notifier {
-    fn clone(&self) -> Box<api::RenderNotifier> {
+    fn clone(&self) -> Box<dyn api::RenderNotifier> {
         Box::new(Clone::clone(self))
     }
 
     fn wake_up(&self) {
         self.tx.send(()).unwrap();
         let _ = self.events_proxy.wakeup();
     }
 
--- a/ipc/mscom/Utils.cpp
+++ b/ipc/mscom/Utils.cpp
@@ -45,16 +45,40 @@ bool IsCurrentThreadMTA() {
   HRESULT hr = CoGetApartmentType(&aptType, &aptTypeQualifier);
   if (FAILED(hr)) {
     return false;
   }
 
   return aptType == APTTYPE_MTA;
 }
 
+bool IsCurrentThreadExplicitMTA() {
+  APTTYPE aptType;
+  APTTYPEQUALIFIER aptTypeQualifier;
+  HRESULT hr = CoGetApartmentType(&aptType, &aptTypeQualifier);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  return aptType == APTTYPE_MTA &&
+         aptTypeQualifier != APTTYPEQUALIFIER_IMPLICIT_MTA;
+}
+
+bool IsCurrentThreadImplicitMTA() {
+  APTTYPE aptType;
+  APTTYPEQUALIFIER aptTypeQualifier;
+  HRESULT hr = CoGetApartmentType(&aptType, &aptTypeQualifier);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  return aptType == APTTYPE_MTA &&
+         aptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA;
+}
+
 bool IsProxy(IUnknown* aUnknown) {
   if (!aUnknown) {
     return false;
   }
 
   // Only proxies implement this interface, so if it is present then we must
   // be dealing with a proxied object.
   RefPtr<IClientSecurity> clientSecurity;
--- a/ipc/mscom/Utils.h
+++ b/ipc/mscom/Utils.h
@@ -17,16 +17,18 @@
 struct IStream;
 struct IUnknown;
 
 namespace mozilla {
 namespace mscom {
 
 bool IsCOMInitializedOnCurrentThread();
 bool IsCurrentThreadMTA();
+bool IsCurrentThreadExplicitMTA();
+bool IsCurrentThreadImplicitMTA();
 bool IsProxy(IUnknown* aUnknown);
 bool IsValidGUID(REFGUID aCheckGuid);
 uintptr_t GetContainingModuleHandle();
 
 /**
  * Given a buffer, create a new IStream object.
  * @param aBuf Buffer containing data to initialize the stream. This parameter
  *             may be nullptr, causing the stream to be created with aBufLen
--- a/js/src/make-source-package.sh
+++ b/js/src/make-source-package.sh
@@ -124,16 +124,17 @@ case $cmd in
     cp -pPR \
         ${TOPSRCDIR}/build \
         ${TOPSRCDIR}/config \
         ${TOPSRCDIR}/python \
         ${tgtpath}/
 
     ${MKDIR} -p ${tgtpath}/.cargo
     cp -pPR \
+        ${TOPSRCDIR}/.cargo/config \
         ${TOPSRCDIR}/.cargo/config.in \
         ${tgtpath}/.cargo/
 
     ${MKDIR} -p ${tgtpath}/third_party
     cp -pPR \
         ${TOPSRCDIR}/third_party/python \
         ${TOPSRCDIR}/third_party/rust \
         ${tgtpath}/third_party/
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -43,19 +43,26 @@ for gcfile in ['devtools/rootAnalysis', 
 for stlfile in ['jsdate.*', 'jsnum.*']:
     with Files(stlfile):
         BUG_COMPONENT = component_stl
 
 with Files('builtin/intl/*'):
     BUG_COMPONENT = component_intl
 
 if not CONFIG['JS_DISABLE_SHELL']:
-    DIRS += ['shell']
+    DIRS += [
+        'rust',
+        'shell',
+    ]
 
-TEST_DIRS += ['jsapi-tests', 'tests', 'gdb']
+    TEST_DIRS += [
+        'gdb',
+        'jsapi-tests',
+        'tests',
+    ]
 
 if CONFIG['FUZZING_INTERFACES']:
     TEST_DIRS += [
         'fuzz-tests',
     ]
 
 if CONFIG['FUZZING_INTERFACES'] and CONFIG['LIBFUZZER']:
     # In addition to regular coverage provided by trace-pc-guard,
@@ -403,17 +410,16 @@ if CONFIG['MOZ_VTUNE']:
     SOURCES['vtune/ittnotify_static.c'].flags += ['-Wno-varargs', '-Wno-unknown-pragmas']
 
 DIRS += [
     'build',
     'debugger',
     'frontend',
     'gc',
     'jit',
-    'rust',
     'wasm',
     'zydis',
 ]
 
 FINAL_LIBRARY = 'js'
 
 # Prepare self-hosted JS code for embedding
 GENERATED_FILES += [('selfhosted.out.h', 'selfhosted.js')]
--- a/js/src/wasm/cranelift/Cargo.toml
+++ b/js/src/wasm/cranelift/Cargo.toml
@@ -9,17 +9,17 @@ crate-type = ["rlib"]
 name = "baldrdash"
 
 [dependencies]
 # The build system redirects the versions of cranelift-codegen and
 # cranelift-wasm to pinned commits. If you want to update Cranelift in Gecko,
 # you should update the following files:
 # - $TOP_LEVEL/Cargo.toml, look for the revision (rev) hashes of both cranelift
 # dependencies (codegen and wasm).
-# - $TOP_LEVEL/.cargo/config.in, look for the revision (rev) field of the
+# - $TOP_LEVEL/.cargo/config, look for the revision (rev) field of the
 # Cranelift source.
 cranelift-codegen = { version = "0.40", default-features = false }
 cranelift-wasm = "0.40"
 target-lexicon = "0.4.0"
 log = { version = "0.4.6", default-features = false, features = ["release_max_level_info"] }
 env_logger = "0.5.6"
 
 [build-dependencies]
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -717,16 +717,21 @@ static bool RecomputePosition(nsIFrame* 
   if (aFrame->DescendantMayDependOnItsStaticPosition()) {
     return false;
   }
 
   aFrame->SchedulePaint();
 
   // For relative positioning, we can simply update the frame rect
   if (display->IsRelativelyPositionedStyle()) {
+    if (aFrame->IsGridItem()) {
+      // A grid item's CB is its grid area, not the parent frame content area
+      // as is assumed below.
+      return false;
+    }
     // Move the frame
     if (display->mPosition == NS_STYLE_POSITION_STICKY) {
       // Update sticky positioning for an entire element at once, starting with
       // the first continuation or ib-split sibling.
       // It's rare that the frame we already have isn't already the first
       // continuation or ib-split sibling, but it can happen when styles differ
       // across continuations such as ::first-line or ::first-letter, and in
       // those cases we will generally (but maybe not always) do the work twice.
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -27,16 +27,17 @@
 #include "mozilla/dom/MouseEvent.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
 #include "mozilla/TextEvents.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // Constants
 const uint32_t kMaxDropDownRows = 20;  // matches the setting for 4.x browsers
@@ -115,17 +116,17 @@ nsListControlFrame::nsListControlFrame(C
   mControlSelectMode = false;
 }
 
 //---------------------------------------------------------
 nsListControlFrame::~nsListControlFrame() { mComboboxFrame = nullptr; }
 
 static bool ShouldFireDropDownEvent() {
   return (XRE_IsContentProcess() &&
-          Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) ||
+          StaticPrefs::browser_tabs_remote_desktopbehavior()) ||
          Preferences::GetBool("dom.select_popup_in_parent.enabled", false);
 }
 
 // for Bug 47302 (remove this comment later)
 void nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
                                      PostDestroyData& aPostDestroyData) {
   // get the receiver interface from the browser button's content node
   NS_ENSURE_TRUE_VOID(mContent);
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2559,37 +2559,38 @@ bool nsIFrame::FormsBackdropRoot(const n
   // TODO(cbrewster): Check will-change attributes
 
   return false;
 }
 
 Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
                                             const nsStyleEffects* aEffects,
                                             const nsSize& aSize) const {
-  if (!(aEffects->mClipFlags & NS_STYLE_CLIP_RECT) ||
+  if (aEffects->mClip.IsAuto() ||
       !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
     return Nothing();
   }
 
-  nsRect rect = aEffects->mClip;
+  auto& clipRect = aEffects->mClip.AsRect();
+  nsRect rect = clipRect.ToLayoutRect();
   if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
                  StyleBoxDecorationBreak::Slice)) {
     // The clip applies to the joined boxes so it's relative the first
     // continuation.
     nscoord y = 0;
     for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
       y += f->GetRect().height;
     }
     rect.MoveBy(nsPoint(0, -y));
   }
 
-  if (NS_STYLE_CLIP_RIGHT_AUTO & aEffects->mClipFlags) {
+  if (clipRect.right.IsAuto()) {
     rect.width = aSize.width - rect.x;
   }
-  if (NS_STYLE_CLIP_BOTTOM_AUTO & aEffects->mClipFlags) {
+  if (clipRect.bottom.IsAuto()) {
     rect.height = aSize.height - rect.y;
   }
   return Some(rect);
 }
 
 /**
  * If the CSS 'overflow' property applies to this frame, and is not
  * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
@@ -4016,18 +4017,17 @@ void nsIFrame::BuildDisplayListForChild(
   const bool isPositioned = disp->IsAbsPosContainingBlock(child);
 
   const bool isStackingContext =
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT) ||
       child->IsStackingContext(disp, pos, effects, isPositioned);
 
   if (pseudoStackingContext || isStackingContext || isPositioned ||
       isPlaceholder || (!isSVG && disp->IsFloating(child)) ||
-      (isSVG && (effects->mClipFlags & NS_STYLE_CLIP_RECT) &&
-       IsSVGContentWithCSSClip(child))) {
+      (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) {
     pseudoStackingContext = true;
     awayFromCommonPath = true;
   }
 
   NS_ASSERTION(!isStackingContext || pseudoStackingContext,
                "Stacking contexts must also be pseudo-stacking-contexts");
 
   nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3940,16 +3940,20 @@ class nsIFrame : public nsQueryFrame {
   virtual void FindCloserFrameForSelection(
       const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame);
 
   /**
    * Is this a flex item? (i.e. a non-abs-pos child of a flex container)
    */
   inline bool IsFlexItem() const;
   /**
+   * Is this a grid item? (i.e. a non-abs-pos child of a grid container)
+   */
+  inline bool IsGridItem() const;
+  /**
    * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid
    * container)
    */
   inline bool IsFlexOrGridItem() const;
   inline bool IsFlexOrGridContainer() const;
 
   /**
    * @return true if this frame is used as a table caption.
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -14,16 +14,21 @@
 #include "nsCSSAnonBoxes.h"
 #include "nsFrameManager.h"
 
 bool nsIFrame::IsFlexItem() const {
   return GetParent() && GetParent()->IsFlexContainerFrame() &&
          !(GetStateBits() & NS_FRAME_OUT_OF_FLOW);
 }
 
+bool nsIFrame::IsGridItem() const {
+  return GetParent() && GetParent()->IsGridContainerFrame() &&
+         !(GetStateBits() & NS_FRAME_OUT_OF_FLOW);
+}
+
 bool nsIFrame::IsFlexOrGridContainer() const {
   return IsFlexContainerFrame() || IsGridContainerFrame();
 }
 
 bool nsIFrame::IsFlexOrGridItem() const {
   return !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && GetParent() &&
          GetParent()->IsFlexOrGridContainer();
 }
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -624,17 +624,17 @@ Maybe<StyleComputedFontStretchRange> Fon
   StyleComputedFontStretchRange range;
   if (!Servo_FontFaceRule_GetFontStretch(GetData(), &range)) {
     return Nothing();
   }
   return Some(range);
 }
 
 Maybe<StyleComputedFontStyleDescriptor> FontFace::GetFontStyle() const {
-  StyleComputedFontStyleDescriptor descriptor;
+  auto descriptor = StyleComputedFontStyleDescriptor::Normal();
   if (!Servo_FontFaceRule_GetFontStyle(GetData(), &descriptor)) {
     return Nothing();
   }
   return Some(descriptor);
 }
 
 Maybe<StyleFontDisplay> FontFace::GetFontDisplay() const {
   StyleFontDisplay display;
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -200,17 +200,17 @@ void FontFaceSet::RemoveDOMContentLoaded
                                          this, false);
   }
 }
 
 void FontFaceSet::ParseFontShorthandForMatching(
     const nsAString& aFont, RefPtr<SharedFontList>& aFamilyList,
     FontWeight& aWeight, FontStretch& aStretch, FontSlantStyle& aStyle,
     ErrorResult& aRv) {
-  StyleComputedFontStyleDescriptor style;
+  auto style = StyleComputedFontStyleDescriptor::Normal();
   float stretch;
   float weight;
 
   RefPtr<URLExtraData> url = ServoCSSParser::GetURLExtraData(mDocument);
   if (!ServoCSSParser::ParseFontShorthandForMatching(aFont, url, aFamilyList,
                                                      style, stretch, weight)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
--- a/layout/style/GeckoBindings.cpp
+++ b/layout/style/GeckoBindings.cpp
@@ -1147,17 +1147,19 @@ void Gecko_CopyImageValueFrom(nsStyleIma
   MOZ_ASSERT(aImage);
   MOZ_ASSERT(aOther);
 
   *aImage = *aOther;
 }
 
 void Gecko_InitializeImageCropRect(nsStyleImage* aImage) {
   MOZ_ASSERT(aImage);
-  aImage->SetCropRect(MakeUnique<nsStyleImage::CropRect>());
+  auto zero = StyleNumberOrPercentage::Number(0);
+  aImage->SetCropRect(MakeUnique<nsStyleImage::CropRect>(
+      nsStyleImage::CropRect{zero, zero, zero, zero}));
 }
 
 void Gecko_SetCursorArrayLength(nsStyleUI* aStyleUI, size_t aLen) {
   aStyleUI->mCursorImages.Clear();
   aStyleUI->mCursorImages.SetLength(aLen);
 }
 
 void Gecko_SetCursorImageValue(nsCursorImage* aCursor,
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -518,16 +518,17 @@ cbindgen-types = [
     { gecko = "StyleGenericSVGPaint", servo = "values::generics::svg::SVGPaint" },
     { gecko = "StyleGenericTrackRepeat", servo = "values::generics::grid::TrackRepeat" },
     { gecko = "StyleGenericTrackListValue", servo = "values::generics::grid::TrackListValue" },
     { gecko = "StyleGenericTrackList", servo = "values::generics::grid::TrackList" },
     { gecko = "StyleGenericGridTemplateComponent", servo = "values::generics::grid::GridTemplateComponent" },
     { gecko = "StyleTextEmphasisStyle", servo = "values::computed::text::TextEmphasisStyle" },
     { gecko = "StyleVariantAlternatesList", servo = "values::specified::font::VariantAlternatesList" },
     { gecko = "StyleSVGPaintOrder", servo = "values::specified::svg::SVGPaintOrder" },
+    { gecko = "StyleClipRectOrAuto", servo = "values::computed::ClipRectOrAuto" },
 ]
 
 mapped-generic-types = [
     { generic = true, gecko = "mozilla::RustCell", servo = "::std::cell::Cell" },
     { generic = false, gecko = "ServoNodeData", servo = "AtomicRefCell<ElementData>" },
     { generic = false, gecko = "mozilla::ServoWritingMode", servo = "::logical_geometry::WritingMode" },
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
--- a/layout/style/ServoStyleConstsInlines.h
+++ b/layout/style/ServoStyleConstsInlines.h
@@ -584,16 +584,21 @@ inline bool LengthOrAuto::IsLength() con
 }
 
 template <>
 inline const Length& LengthOrAuto::AsLength() const {
   return AsLengthPercentage();
 }
 
 template <>
+inline nscoord LengthOrAuto::ToLength() const {
+  return AsLength().ToAppUnits();
+}
+
+template <>
 inline bool StyleFlexBasis::IsAuto() const {
   return IsSize() && AsSize().IsAuto();
 }
 
 template <>
 inline bool StyleSize::BehavesLikeInitialValueOnBlockAxis() const {
   return IsAuto() || IsExtremumLength();
 }
@@ -685,11 +690,20 @@ StyleGridTemplateComponent::GetRepeatAut
     return nullptr;
   }
   return &TrackListValues()[*index].AsTrackRepeat();
 }
 
 constexpr const auto kPaintOrderShift = StylePAINT_ORDER_SHIFT;
 constexpr const auto kPaintOrderMask = StylePAINT_ORDER_MASK;
 
+template <>
+inline nsRect StyleGenericClipRect<LengthOrAuto>::ToLayoutRect(nscoord aAutoSize) const {
+  nscoord x = left.IsLength() ? left.ToLength() : 0;
+  nscoord y = top.IsLength() ? top.ToLength() : 0;
+  nscoord width = right.IsLength() ? right.ToLength() - x : aAutoSize;
+  nscoord height = bottom.IsLength() ? bottom.ToLength() - y : aAutoSize;
+  return nsRect(x, y, width, height);
+}
+
 }  // namespace mozilla
 
 #endif
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -537,16 +537,17 @@ nsChangeHint nsStyleOutline::CalcDiffere
 }
 
 // --------------------
 // nsStyleList
 //
 nsStyleList::nsStyleList(const Document& aDocument)
     : mListStylePosition(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE),
       mQuotes(StyleQuotes::Auto()),
+      mImageRegion(StyleClipRectOrAuto::Auto()),
       mMozListReversed(StyleMozListReversed::False) {
   MOZ_COUNT_CTOR(nsStyleList);
   MOZ_ASSERT(NS_IsMainThread());
 
   mCounterStyle = nsGkAtoms::disc;
 }
 
 nsStyleList::~nsStyleList() { MOZ_COUNT_DTOR(nsStyleList); }
@@ -601,19 +602,20 @@ nsChangeHint nsStyleList::CalcDifference
   if (mMozListReversed != aNewData.mMozListReversed) {
     return NS_STYLE_HINT_REFLOW;
   }
   // list-style-image and -moz-image-region may affect some XUL elements
   // regardless of display value, so we still need to check them.
   if (!DefinitelyEqualImages(mListStyleImage, aNewData.mListStyleImage)) {
     return NS_STYLE_HINT_REFLOW;
   }
-  if (!mImageRegion.IsEqualInterior(aNewData.mImageRegion)) {
-    if (mImageRegion.width != aNewData.mImageRegion.width ||
-        mImageRegion.height != aNewData.mImageRegion.height) {
+  if (mImageRegion != aNewData.mImageRegion) {
+    nsRect region = GetImageRegion();
+    nsRect newRegion = aNewData.GetImageRegion();
+    if (region.width != newRegion.width || region.height != newRegion.height) {
       return NS_STYLE_HINT_REFLOW;
     }
     return NS_STYLE_HINT_VISUAL;
   }
   return hint;
 }
 
 already_AddRefed<nsIURI> nsStyleList::GetListStyleImageURI() const {
@@ -2692,16 +2694,19 @@ nsStyleDisplay::nsStyleDisplay(const Doc
       mTouchAction(StyleTouchAction_AUTO),
       mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO),
       mOverscrollBehaviorX(StyleOverscrollBehavior::Auto),
       mOverscrollBehaviorY(StyleOverscrollBehavior::Auto),
       mOverflowAnchor(StyleOverflowAnchor::Auto),
       mScrollSnapType(
           {StyleScrollSnapAxis::Both, StyleScrollSnapStrictness::None}),
       mLineClamp(0),
+      mRotate(StyleRotate::None()),
+      mTranslate(StyleTranslate::None()),
+      mScale(StyleScale::None()),
       mBackfaceVisibility(NS_STYLE_BACKFACE_VISIBILITY_VISIBLE),
       mTransformStyle(NS_STYLE_TRANSFORM_STYLE_FLAT),
       mTransformBox(StyleGeometryBox::BorderBox),
       mOffsetPath(StyleOffsetPath::None()),
       mOffsetDistance(LengthPercentage::Zero()),
       mOffsetRotate{true, StyleAngle{0.0}},
       mOffsetAnchor(StylePositionOrAuto::Auto()),
       mTransformOrigin{LengthPercentage::FromPercentage(0.5),
@@ -3725,54 +3730,66 @@ nsChangeHint nsStyleUIReset::CalcDiffere
   return hint;
 }
 
 //-----------------------
 // nsStyleEffects
 //
 
 nsStyleEffects::nsStyleEffects(const Document&)
-    : mClip(0, 0, 0, 0),
+    : mClip(StyleClipRectOrAuto::Auto()),
       mOpacity(1.0f),
-      mClipFlags(NS_STYLE_CLIP_AUTO),
       mMixBlendMode(NS_STYLE_BLEND_NORMAL) {
   MOZ_COUNT_CTOR(nsStyleEffects);
 }
 
 nsStyleEffects::nsStyleEffects(const nsStyleEffects& aSource)
     : mFilters(aSource.mFilters),
       mBoxShadow(aSource.mBoxShadow),
       mBackdropFilters(aSource.mBackdropFilters),
       mClip(aSource.mClip),
       mOpacity(aSource.mOpacity),
-      mClipFlags(aSource.mClipFlags),
       mMixBlendMode(aSource.mMixBlendMode) {
   MOZ_COUNT_CTOR(nsStyleEffects);
 }
 
 nsStyleEffects::~nsStyleEffects() { MOZ_COUNT_DTOR(nsStyleEffects); }
 
+static bool AnyAutonessChanged(const StyleClipRectOrAuto& aOld,
+                               const StyleClipRectOrAuto& aNew) {
+  if (aOld.IsAuto() != aNew.IsAuto()) {
+    return true;
+  }
+  if (aOld.IsAuto()) {
+    return false;
+  }
+  auto& oldRect = aOld.AsRect();
+  auto& newRect = aNew.AsRect();
+  return oldRect.top.IsAuto() != newRect.top.IsAuto() ||
+         oldRect.right.IsAuto() != newRect.right.IsAuto() ||
+         oldRect.bottom.IsAuto() != newRect.bottom.IsAuto() ||
+         oldRect.left.IsAuto() != newRect.left.IsAuto();
+}
+
 nsChangeHint nsStyleEffects::CalcDifference(
     const nsStyleEffects& aNewData) const {
   nsChangeHint hint = nsChangeHint(0);
 
   if (mBoxShadow != aNewData.mBoxShadow) {
     // Update overflow regions & trigger DLBI to be sure it's noticed.
     // Also request a repaint, since it's possible that only the color
     // of the shadow is changing (and UpdateOverflow/SchedulePaint won't
     // repaint for that, since they won't know what needs invalidating.)
     hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint |
             nsChangeHint_RepaintFrame;
   }
 
-  if (mClipFlags != aNewData.mClipFlags) {
+  if (AnyAutonessChanged(mClip, aNewData.mClip)) {
     hint |= nsChangeHint_AllReflowHints | nsChangeHint_RepaintFrame;
-  }
-
-  if (!mClip.IsEqualInterior(aNewData.mClip)) {
+  } else if (mClip != aNewData.mClip) {
     // If the clip has changed, we just need to update overflow areas. DLBI
     // will handle the invalidation.
     hint |= nsChangeHint_UpdateOverflow | nsChangeHint_SchedulePaint;
   }
 
   if (mOpacity != aNewData.mOpacity) {
     // If we're going from the optimized >=0.99 opacity value to 1.0 or back,
     // then repaint the frame because DLBI will not catch the invalidation.
@@ -3807,20 +3824,16 @@ nsChangeHint nsStyleEffects::CalcDiffere
     // A change from/to being a containing block for position:fixed.
     hint |= nsChangeHint_UpdateContainingBlock;
   }
 
   if (mBackdropFilters != aNewData.mBackdropFilters) {
     hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
   }
 
-  if (!hint && !mClip.IsEqualEdges(aNewData.mClip)) {
-    hint |= nsChangeHint_NeutralChange;
-  }
-
   return hint;
 }
 
 static bool TransformOperationHasPercent(const StyleTransformOperation& aOp) {
   switch (aOp.tag) {
     case StyleTransformOperation::Tag::TranslateX:
       return aOp.AsTranslateX().HasPercent();
     case StyleTransformOperation::Tag::TranslateY:
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -929,29 +929,36 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   nsChangeHint CalcDifference(const nsStyleList& aNewData,
                               const nsStyleDisplay& aOldDisplay) const;
 
   imgRequestProxy* GetListStyleImage() const {
     return mListStyleImage ? mListStyleImage->get() : nullptr;
   }
 
+  nsRect GetImageRegion() const {
+    if (!mImageRegion.IsRect()) {
+      return nsRect();
+    }
+    return mImageRegion.AsRect().ToLayoutRect(0);
+  }
+
   already_AddRefed<nsIURI> GetListStyleImageURI() const;
 
   uint8_t mListStylePosition;
   RefPtr<nsStyleImageRequest> mListStyleImage;
 
   mozilla::CounterStylePtr mCounterStyle;
 
  private:
   nsStyleList& operator=(const nsStyleList& aOther) = delete;
 
  public:
   mozilla::StyleQuotes mQuotes;
-  nsRect mImageRegion;  // the rect to use within an image
+  mozilla::StyleClipRectOrAuto mImageRegion;  // the rect to use within an image
   mozilla::StyleMozListReversed
       mMozListReversed;  // true in an <ol reversed> scope
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition {
   using LengthPercentageOrAuto = mozilla::LengthPercentageOrAuto;
   using Position = mozilla::Position;
   template <typename T>
@@ -2391,19 +2398,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   bool HasMixBlendMode() const {
     return mMixBlendMode != NS_STYLE_BLEND_NORMAL;
   }
 
   mozilla::StyleOwnedSlice<mozilla::StyleFilter> mFilters;
   mozilla::StyleOwnedSlice<mozilla::StyleBoxShadow> mBoxShadow;
   mozilla::StyleOwnedSlice<mozilla::StyleFilter> mBackdropFilters;
-  nsRect mClip;  // offsets from UL border edge
+  mozilla::StyleClipRectOrAuto mClip;  // offsets from UL border edge
   float mOpacity;
-  uint8_t mClipFlags;     // bitfield of NS_STYLE_CLIP_* values
   uint8_t mMixBlendMode;  // NS_STYLE_BLEND_*
 };
 
 #define STATIC_ASSERT_TYPE_LAYOUTS_MATCH(T1, T2)           \
   static_assert(sizeof(T1) == sizeof(T2),                  \
                 "Size mismatch between " #T1 " and " #T2); \
   static_assert(alignof(T1) == alignof(T2),                \
                 "Align mismatch between " #T1 " and " #T2);
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -2493,26 +2493,18 @@ function test_rect_transition(prop) {
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", "");
   is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)",
      "rect-valued property " + prop + ": interpolation of rects");
   check_distance(prop, "rect(4px, 16px, 12px, 6px)",
                        "rect(3px, 13px, 10px, 5px)",
                        "rect(0px, 4px, 4px, 2px)");
   div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", "");
-  if (prop == "clip") {
-    is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
-       "rect-valued property " + prop + ": can't interpolate auto components");
-  } else {
-    is(prop, "-moz-image-region");
-    // Bug 1390067: -moz-image-region should behave just like clip does.
-    todo_is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
-            "rect-valued property " + prop +
-            ": can't interpolate auto components");
-  }
+  is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
+     "rect-valued property " + prop + ": can't interpolate auto components");
   div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", "");
   div.style.setProperty(prop, "auto", "");
   is(cs.getPropertyValue(prop), "auto",
      "rect-valued property " + prop + ": can't interpolate auto components");
 
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", "");
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -922,48 +922,45 @@ bool nsSVGUtils::HitTestRect(const gfx::
          p.y <= rect.YMost();
 }
 
 gfxRect nsSVGUtils::GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY,
                                         float aWidth, float aHeight) {
   const nsStyleDisplay* disp = aFrame->StyleDisplay();
   const nsStyleEffects* effects = aFrame->StyleEffects();
 
-  if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) {
-    NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO,
-                 "We don't know about this type of clip.");
+  bool clipApplies =
+      disp->mOverflowX == StyleOverflow::Hidden ||
+      disp->mOverflowY == StyleOverflow::Hidden;
+
+  if (!clipApplies || effects->mClip.IsAuto()) {
     return gfxRect(aX, aY, aWidth, aHeight);
   }
 
-  if (disp->mOverflowX == StyleOverflow::Hidden ||
-      disp->mOverflowY == StyleOverflow::Hidden) {
-    nsIntRect clipPxRect = effects->mClip.ToOutsidePixels(
-        aFrame->PresContext()->AppUnitsPerDevPixel());
-    gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width,
-                               clipPxRect.height);
-
-    if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) {
-      clipRect.width = aWidth - clipRect.X();
-    }
-    if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) {
-      clipRect.height = aHeight - clipRect.Y();
-    }
-
-    if (disp->mOverflowX != StyleOverflow::Hidden) {
-      clipRect.x = aX;
-      clipRect.width = aWidth;
-    }
-    if (disp->mOverflowY != StyleOverflow::Hidden) {
-      clipRect.y = aY;
-      clipRect.height = aHeight;
-    }
-
-    return clipRect;
+  auto& rect = effects->mClip.AsRect();
+  nsRect coordClipRect = rect.ToLayoutRect();
+  nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
+      aFrame->PresContext()->AppUnitsPerDevPixel());
+  gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width,
+                             clipPxRect.height);
+  if (rect.right.IsAuto()) {
+    clipRect.width = aWidth - clipRect.X();
   }
-  return gfxRect(aX, aY, aWidth, aHeight);
+  if (rect.bottom.IsAuto()) {
+    clipRect.height = aHeight - clipRect.Y();
+  }
+  if (disp->mOverflowX != StyleOverflow::Hidden) {
+    clipRect.x = aX;
+    clipRect.width = aWidth;
+  }
+  if (disp->mOverflowY != StyleOverflow::Hidden) {
+    clipRect.y = aY;
+    clipRect.height = aHeight;
+  }
+  return clipRect;
 }
 
 void nsSVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
                              const gfxRect& aRect) {
   if (aCTM.IsSingular()) {
     return;
   }
 
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -612,17 +612,17 @@ bool nsImageBoxFrame::CanOptimizeToImage
 // date.
 //
 /* virtual */
 void nsImageBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
   nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle);
 
   // Fetch our subrect.
   const nsStyleList* myList = StyleList();
-  mSubRect = myList->mImageRegion;  // before |mSuppressStyleCheck| test!
+  mSubRect = myList->GetImageRegion();  // before |mSuppressStyleCheck| test!
 
   if (mUseSrcAttr || mSuppressStyleCheck)
     return;  // No more work required, since the image isn't specified by style.
 
   // If we're using a native theme implementation, we shouldn't draw anything.
   const nsStyleDisplay* disp = StyleDisplay();
   if (disp->HasAppearance() && nsBox::gTheme &&
       nsBox::gTheme->ThemeSupportsWidget(nullptr, this, disp->mAppearance))
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -1968,35 +1968,36 @@ nsRect nsTreeBodyFrame::GetImageSize(int
   // Don't change this, otherwise things start to go crazy.
   bool useImageRegion = true;
   nsCOMPtr<imgIContainer> image;
   GetImage(aRowIndex, aCol, aUseContext, aComputedStyle, useImageRegion,
            getter_AddRefs(image));
 
   const nsStylePosition* myPosition = aComputedStyle->StylePosition();
   const nsStyleList* myList = aComputedStyle->StyleList();
-
+  nsRect imageRegion = myList->GetImageRegion();
   if (useImageRegion) {
-    r.x += myList->mImageRegion.x;
-    r.y += myList->mImageRegion.y;
+    r.x += imageRegion.x;
+    r.y += imageRegion.y;
   }
 
   if (myPosition->mWidth.ConvertsToLength()) {
     int32_t val = myPosition->mWidth.ToLength();
     r.width += val;
-  } else if (useImageRegion && myList->mImageRegion.width > 0)
-    r.width += myList->mImageRegion.width;
-  else
+  } else if (useImageRegion && imageRegion.width > 0) {
+    r.width += imageRegion.width;
+  } else {
     needWidth = true;
+  }
 
   if (myPosition->mHeight.ConvertsToLength()) {
     int32_t val = myPosition->mHeight.ToLength();
     r.height += val;
-  } else if (useImageRegion && myList->mImageRegion.height > 0)
-    r.height += myList->mImageRegion.height;
+  } else if (useImageRegion && imageRegion.height > 0)
+    r.height += imageRegion.height;
   else
     needHeight = true;
 
   if (image) {
     if (needWidth || needHeight) {
       // Get the natural image size.
 
       if (needWidth) {
@@ -2057,31 +2058,31 @@ nsSize nsTreeBodyFrame::GetImageDestSize
     needHeight = true;
   }
 
   if (needWidth || needHeight) {
     // We need to get the size of the image/region.
     nsSize imageSize(0, 0);
 
     const nsStyleList* myList = aComputedStyle->StyleList();
-
-    if (useImageRegion && myList->mImageRegion.width > 0) {
+    nsRect imageRegion = myList->GetImageRegion();
+    if (useImageRegion && imageRegion.width > 0) {
       // CSS has specified an image region.
       // Use the width of the image region.
-      imageSize.width = myList->mImageRegion.width;
+      imageSize.width = imageRegion.width;
     } else if (image) {
       nscoord width;
       image->GetWidth(&width);
       imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
     }
 
-    if (useImageRegion && myList->mImageRegion.height > 0) {
+    if (useImageRegion && imageRegion.height > 0) {
       // CSS has specified an image region.
       // Use the height of the image region.
-      imageSize.height = myList->mImageRegion.height;
+      imageSize.height = imageRegion.height;
     } else if (image) {
       nscoord height;
       image->GetHeight(&height);
       imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
     }
 
     if (needWidth) {
       if (!needHeight && imageSize.height != 0) {
@@ -2114,33 +2115,35 @@ nsSize nsTreeBodyFrame::GetImageDestSize
 // The width and height reflect the image region specified in CSS, or
 // the natural size of the image.
 // The width and height do not include borders and padding.
 // The width and height do not reflect the destination size specified
 // in CSS.
 nsRect nsTreeBodyFrame::GetImageSourceRect(ComputedStyle* aComputedStyle,
                                            bool useImageRegion,
                                            imgIContainer* image) {
-  nsRect r(0, 0, 0, 0);
-
   const nsStyleList* myList = aComputedStyle->StyleList();
-
-  if (useImageRegion &&
-      (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) {
-    // CSS has specified an image region.
-    r = myList->mImageRegion;
-  } else if (image) {
-    // Use the actual image size.
-    nscoord coord;
-    image->GetWidth(&coord);
+  // CSS has specified an image region.
+  if (useImageRegion && myList->mImageRegion.IsRect()) {
+    return myList->GetImageRegion();
+  }
+
+  if (!image) {
+    return nsRect();
+  }
+
+  nsRect r;
+  // Use the actual image size.
+  nscoord coord;
+  if (NS_SUCCEEDED(image->GetWidth(&coord))) {
     r.width = nsPresContext::CSSPixelsToAppUnits(coord);
-    image->GetHeight(&coord);
+  }
+  if (NS_SUCCEEDED(image->GetHeight(&coord))) {
     r.height = nsPresContext::CSSPixelsToAppUnits(coord);
   }
-
   return r;
 }
 
 int32_t nsTreeBodyFrame::GetRowHeight() {
   // Look up the correct height.  It is equal to the specified height
   // + the specified margins.
   mScratchArray.Clear();
   ComputedStyle* rowContext =
--- a/mobile/android/app/geckoview-prefs.js
+++ b/mobile/android/app/geckoview-prefs.js
@@ -1,12 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// Non-static prefs that are specific to GeckoView belong in this file (unless
+// there is a compelling and documented reason for them to belong in another
+// file). Note that non-static prefs that are shared by GeckoView and Firefox
+// for Android are in mobile.js, which this file includes.
+//
 // Please indent all prefs defined within #ifdef/#ifndef conditions. This
 // improves readability, particular for conditional blocks that exceed a single
 // screen.
 
 #filter substitution
 
 #include mobile.js
 
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// Non-static prefs that are specific to Firefox on Android belong in this file
+// (unless there is a compelling and documented reason for them to belong in
+// another file).
+//
 // Please indent all prefs defined within #ifdef/#ifndef conditions. This
 // improves readability, particular for conditional blocks that exceed a single
 // screen.
 
 #filter substitution
 
 // For browser.js element
 //
@@ -604,18 +608,16 @@ pref("ui.scrolling.max_event_acceleratio
 pref("ui.scrolling.overscroll_decel_rate", -1);
 // The fraction of the surface which can be overscrolled before it must snap back, in 1000ths.
 pref("ui.scrolling.overscroll_snap_limit", -1);
 // The minimum amount of space that must be present for an axis to be considered scrollable,
 // in 1/1000ths of pixels.
 pref("ui.scrolling.min_scrollable_distance", -1);
 // The axis lock mode for panning behaviour - set between standard, free and sticky
 pref("ui.scrolling.axis_lock_mode", "standard");
-// Negate scroll, true will make the mouse scroll wheel move the screen the same direction as with most desktops or laptops.
-pref("ui.scrolling.negate_wheel_scroll", true);
 // Determine the dead zone for gamepad joysticks. Higher values result in larger dead zones; use a negative value to
 // auto-detect based on reported hardware values
 pref("ui.scrolling.gamepad_dead_zone", 115);
 
 // Prefs for fling acceleration
 pref("ui.scrolling.fling_accel_interval", -1);
 pref("ui.scrolling.fling_accel_base_multiplier", -1);
 pref("ui.scrolling.fling_accel_supplemental_multiplier", -1);
@@ -798,18 +800,16 @@ pref("dom.audiochannel.mediaControl", tr
 pref("media.block-autoplay-until-in-foreground", false);
 
 // Space separated list of URLS that are allowed to send objects (instead of
 // only strings) through webchannels. This list is duplicated in browser/app/profile/firefox.js
 pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://support.mozilla.org https://install.mozilla.org");
 
 pref("media.openUnsupportedTypeWithExternalApp", true);
 
-pref("dom.keyboardevent.dispatch_during_composition", true);
-
 // Ask for permission when enumerating WebRTC devices.
 pref("media.navigator.permission.device", true);
 
 // Allow system add-on updates
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 pref("extensions.systemAddon.update.enabled", true);
 
 // E10s stuff. We don't support 'file' or 'priveleged' process types.
--- a/mobile/android/installer/mobile-l10n.js
+++ b/mobile/android/installer/mobile-l10n.js
@@ -1,6 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// This pref is in its own file for complex reasons. See bug 1428099 for
+// details. Do not add other prefs to this file.
+
 // Inherit locale from the OS, used for multi-locale builds
 pref("intl.locale.requested", "");
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -314,23 +314,31 @@ REMOVED/DEPRECATED: Use 'mach lint --lin
         # documents; this works around "error: unmappable character
         # for encoding ASCII" in exoplayer2.  See
         # https://discuss.gradle.org/t/unmappable-character-for-encoding-ascii-when-building-a-utf-8-project/10692/11  # NOQA: E501
         # and especially https://stackoverflow.com/a/21755671.
 
         if self.substs.get('MOZ_AUTOMATION'):
             gradle_flags += ['--console=plain']
 
+        env = os.environ.copy()
+        env.update({
+            'GRADLE_OPTS': '-Dfile.encoding=utf-8',
+            'JAVA_HOME': java_home,
+            'JAVA_TOOL_OPTIONS': '-Dfile.encoding=utf-8',
+        })
+        # Set ANDROID_SDK_ROOT if --with-android-sdk was set.
+        # See https://bugzilla.mozilla.org/show_bug.cgi?id=1576471
+        android_sdk_root = self.substs.get('ANDROID_SDK_ROOT', '')
+        if android_sdk_root:
+            env['ANDROID_SDK_ROOT'] = android_sdk_root
+
         return self.run_process(
             [self.substs['GRADLE']] + gradle_flags + args,
-            append_env={
-                'GRADLE_OPTS': '-Dfile.encoding=utf-8',
-                'JAVA_HOME': java_home,
-                'JAVA_TOOL_OPTIONS': '-Dfile.encoding=utf-8',
-            },
+            explicit_env=env,
             pass_thru=True,  # Allow user to run gradle interactively.
             ensure_exit_code=False,  # Don't throw on non-zero exit code.
             cwd=mozpath.join(self.topsrcdir))
 
     @Command('gradle-install', category='devenv',
              conditions=[REMOVED])
     def gradle_install_REMOVED(self):
         pass
--- a/modules/libpref/greprefs.js
+++ b/modules/libpref/greprefs.js
@@ -1,1 +1,3 @@
+// Do not add anything else to this file. Additions should go in all.js.
+
 #include init/all.js
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -173,16 +173,29 @@
   value: false
   mirror: always
 
 - name: accessibility.AOM.enabled
   type: bool
   value: false
   mirror: always
 
+#ifdef ANDROID
+  #---------------------------------------------------------------------------
+  # Prefs starting with "android."
+  #---------------------------------------------------------------------------
+
+  # On Android, we want an opaque background to be visible under the page,
+  # so layout should not force a default background.
+-   name: android.widget_paints_background
+    type: bool
+    value: true
+    mirror: always
+#endif
+
 #---------------------------------------------------------------------------
 # Prefs starting with "apz."
 # The apz prefs are explained in AsyncPanZoomController.cpp
 #---------------------------------------------------------------------------
 
 - name: apz.allow_double_tap_zooming
   type: RelaxedAtomicBool
   value: true
@@ -861,16 +874,21 @@
 
 # ContentSessionStore prefs
 # Maximum number of bytes of DOMSessionStorage data we collect per origin.
 - name: browser.sessionstore.dom_storage_limit
   type: uint32_t
   value: 2048
   mirror: always
 
+- name: browser.tabs.remote.desktopbehavior
+  type: bool
+  value: false
+  mirror: always
+
 - name: browser.tabs.remote.force-paint
   type: bool
   value: true
   mirror: always
 
 # When this pref is enabled document loads with a mismatched
 # Cross-Origin-Embedder-Policy header will fail to load
 - name: browser.tabs.remote.useCrossOriginEmbedderPolicy
@@ -1596,16 +1614,31 @@
   type: RelaxedAtomicBool
 #if defined(XP_WIN) || defined(XP_MACOSX)
   value: false
 #else
   value: true
 #endif
   mirror: always
 
+# If this is true, TextEventDispatcher dispatches keydown and keyup events
+# even during composition (keypress events are never fired during composition
+# even if this is true).
+- name: dom.keyboardevent.dispatch_during_composition
+  type: bool
+  value: true
+  mirror: always
+
+# If this is true, keypress events for non-printable keys are dispatched only
+# for event listeners of the system event group in web content.
+- name: dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content
+  type: bool
+  value: true
+  mirror: always
+
 # If this is true, "keypress" event's keyCode value and charCode value always
 # become same if the event is not created/initialized by JS.
 - name: dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value
   type: bool
   value: true
   mirror: always
 
 - name: dom.largeAllocation.forceEnable
@@ -2361,16 +2394,25 @@
   type: RelaxedAtomicBool
 #ifdef ANDROID
   value: false
 #else
   value: true
 #endif
   mirror: always
 
+#ifdef XP_WIN
+  # Control firing WidgetMouseEvent by handling Windows pointer messages or
+  # mouse messages.
+-   name: dom.w3c_pointer_events.dispatch_by_pointer_messages
+    type: bool
+    value: false
+    mirror: always
+#endif
+
 # If the value is >= 0, it will be used for max touch points in child processes.
 - name: dom.maxtouchpoints.testing.value
   type: int32_t
   value: -1
   mirror: always
 
 # If the pref is set to true, pointer events are enabled on GeckoView, but only
 # in the case it is using separate child processes.
@@ -2601,16 +2643,25 @@
 # This pref governs whether we run webextensions in a separate process (true)
 # or the parent/main process (false)
 - name: extensions.webextensions.remote
   type: bool
   value: false
   mirror: always
 
 #---------------------------------------------------------------------------
+# Prefs starting with "findbar."
+#---------------------------------------------------------------------------
+
+- name: findbar.modalHighlight
+  type: bool
+  value: false
+  mirror: always
+
+#---------------------------------------------------------------------------
 # Prefs starting with "fission."
 #---------------------------------------------------------------------------
 
 # Whether to enable Fission.
 # Overridden in all.js on RELEASE_OR_BETA in order to add the locked attribute.
 - name: fission.autostart
   type: bool
   value: false
@@ -3602,16 +3653,27 @@
   value: true
   mirror: always
 
 - name: intl.charset.fallback.tld
   type: bool
   value: true
   mirror: always
 
+# If true, dispatch the keydown and keyup events on any web apps even during
+# composition.
+- name: intl.ime.hack.on_any_apps.fire_key_events_for_composition
+  type: bool
+#ifdef MOZ_WIDGET_ANDROID
+  value: @IS_EARLY_BETA_OR_EARLIER@
+#else
+  value: false
+#endif
+  mirror: always
+
 #---------------------------------------------------------------------------
 # Prefs starting with "javascript."
 #---------------------------------------------------------------------------
 
 - name: javascript.options.compact_on_user_inactive
   type: bool
   value: true
   mirror: always
@@ -4304,16 +4366,25 @@
 
 # Is path() supported in clip-path?
 - name: layout.css.clip-path-path.enabled
   type: RelaxedAtomicBool
   value: false
   mirror: always
   rust: true
 
+# Set the number of device pixels per CSS pixel. A value <= 0 means choose
+# automatically based on user settings for the platform (e.g., "UI scale factor"
+# on Mac). A positive value is used as-is. This effectively controls the size
+# of a CSS "px". This is only used for windows on the screen, not for printing.
+- name: layout.css.devPixelsPerPx
+  type: AtomicFloat
+  value: -1.0f
+  mirror: always
+
 # text underline offset
 - name: layout.css.text-underline-offset.enabled
   type: bool
   value: @IS_EARLY_BETA_OR_EARLIER@
   mirror: always
 
 # text decoration thickness
 - name: layout.css.text-decoration-thickness.enabled
@@ -6059,16 +6130,59 @@
   mirror: always
 
 # Whether to run proxy checks when processing Alt-Svc headers.
 - name: network.http.altsvc.proxy_checks
   type: bool
   value: true
   mirror: always
 
+# Single TRR request timeout, in milliseconds
+- name: network.trr.request_timeout_ms
+  type: uint32_t
+  value: 1500
+  mirror: always
+
+# Single TRR request timeout, in milliseconds for mode 3
+- name: network.trr.request_timeout_mode_trronly_ms
+  type: uint32_t
+  value: 30000
+  mirror: always
+
+# Allow the network changed event to get sent when a network topology or setup
+# change is noticed while running.
+- name: network.notify.changed
+  type: RelaxedAtomicBool
+  value: true
+  mirror: always
+
+# Allow network detection of IPv6 related changes (bug 1245059)
+- name: network.notify.IPv6
+  type: RelaxedAtomicBool
+# ifdef XP_WIN
+  value: false
+# else
+  value: true
+# endif
+  mirror: always
+
+# IP addresses that are used by netlink service to check whether default route
+# is used for outgoing traffic. They are used just to check routing rules,
+# no packets are sent to those hosts. Initially, addresses of host
+# detectportal.firefox.com were used but they don't necessarily need to be
+# updated when addresses of this host change.
+- name: network.netlink.route.check.IPv4
+  type: String
+  value: "23.219.91.27"
+  mirror: never
+- name: network.netlink.route.check.IPv6
+  type: String
+  value: "2a02:26f0:40::17db:5b1b"
+  mirror: never
+
 #---------------------------------------------------------------------------
 # Prefs starting with "nglayout."
 #---------------------------------------------------------------------------
 
 # Enable/disable display list invalidation logging --- useful for debugging.
 - name: nglayout.debug.invalidation
   type: bool
   value: false
@@ -6608,44 +6722,95 @@
   type: RelaxedAtomicBool
   value: false
   mirror: always
 
 #---------------------------------------------------------------------------
 # Prefs starting with "ui."
 #---------------------------------------------------------------------------
 
+- name: ui.key.generalAccessKey
+  type: int32_t
+  value: -1
+  mirror: always
+
+# Only used if generalAccessKey is -1.
+- name: ui.key.chromeAccess
+  type: int32_t
+#ifdef XP_MACOSX
+  # 0 = disabled, 1 = Shift, 2 = Ctrl, 4 = Alt, 3 =  ctrl+shift, 8 = Meta
+  value: 2
+#else
+  # 0 = disabled, 1 = Shift, 2 = Ctrl, 4 = Alt, 5 =  Alt+Shift,
+  # 8 = Meta, 16 = Win
+  value: 4
+#endif
+  mirror: always
+
+# Only used if generalAccessKey is -1.
+- name: ui.key.contentAccess
+  type: int32_t
+#ifdef XP_MACOSX
+  # 0 = disabled, 1 = Shift, 2 = Ctrl, 4 = Alt, 3 = ctrl+shift, 8 = Meta
+  value: 6
+#else
+  # 0 = disabled, 1 = Shift, 2 = Ctrl, 4 = Alt, 5 =  Alt+Shift,
+  # 8 = Meta, 16 = Win
+  value: 5
+#endif
+  mirror: always
+
+# Negate scroll, true will make the mouse scroll wheel move the screen the
+# same direction as with most desktops or laptops.
+- name: ui.scrolling.negate_wheel_scroll
+  type: bool
+  value: @IS_ANDROID@
+  mirror: always
+
 # If the user puts a finger down on an element and we think the user might be
 # executing a pan gesture, how long do we wait before tentatively deciding the
 # gesture is actually a tap and activating the target element?
 - name: ui.touch_activation.delay_ms
   type: int32_t
   value: 100
   mirror: always
 
 # If the user has clicked an element, how long do we keep the :active state
 # before it is cleared by the mouse sequences fired after a
 # touchstart/touchend.
 - name: ui.touch_activation.duration_ms
   type: int32_t
   value: 10
   mirror: always
 
+- name: ui.use_native_colors
+  type: RelaxedAtomicBool
+  value: true
+  mirror: always
+
 # Prevent system colors from being exposed to CSS or canvas.
 - name: ui.use_standins_for_native_colors
   type: RelaxedAtomicBool
   value: false
   mirror: always
 
 # Disable page loading activity cursor by default.
 - name: ui.use_activity_cursor
   type: bool
   value: false
   mirror: always
 
+# Whether context menus should only appear on mouseup instead of mousedown,
+# on OSes where they normally appear on mousedown (macOS, *nix).
+# Note: ignored on Windows (context menus always use mouseup).
+- name: ui.context_menus.after_mouseup
+  type: bool
+  value: false
+  mirror: always
+
 - name: ui.click_hold_context_menus.delay
   type: RelaxedAtomicInt32
   value: 500
   mirror: always
 
 #---------------------------------------------------------------------------
 # Prefs starting with "view_source."
 #---------------------------------------------------------------------------
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1,28 +1,28 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* The prefs in this file are shipped with the GRE and should apply to all
- * embedding situations. Application-specific preferences belong somewhere else,
- * such as browser/app/profile/firefox.js or mobile/android/app/mobile.js.
- *
- * NOTE: Not all prefs should be defined in this (or any other) data file.
- * Static prefs, especially VarCache prefs, are defined in StaticPrefList.yaml.
- * Those prefs should *not* appear in this file.
- *
- * For the syntax used by this file, consult the comments at the top of
- * modules/libpref/parser/src/lib.rs.
- *
- * Please indent all prefs defined within #ifdef/#ifndef conditions. This
- * improves readability, particular for conditional blocks that exceed a single
- * screen.
- */
+// The prefs in this file are shipped with the GRE and should apply to all
+// embedding situations. Application-specific preferences belong somewhere
+// else, such as browser/app/profile/firefox.js or
+// mobile/android/app/mobile.js.
+//
+// NOTE: Not all prefs should be defined in this (or any other) data file.
+// Static prefs, especially VarCache prefs, are defined in StaticPrefList.yaml.
+// Those prefs should *not* appear in this file.
+//
+// For the syntax used by this file, consult the comments at the top of
+// modules/libpref/parser/src/lib.rs.
+//
+// Please indent all prefs defined within #ifdef/#ifndef conditions. This
+// improves readability, particular for conditional blocks that exceed a single
+// screen.
 
 pref("security.tls.version.min", 1);
 pref("security.tls.version.max", 4);
 pref("security.tls.version.fallback-limit", 4);
 pref("security.tls.insecure_fallback_hosts", "");
 // Turn off post-handshake authentication for TLS 1.3 by default,
 // until the incompatibility with HTTP/2 is resolved:
 // https://tools.ietf.org/html/draft-davidben-http2-tls13-00
@@ -280,26 +280,16 @@ pref("dom.serviceWorkers.idle_extended_t
 
 // The amount of time (milliseconds) an update request is delayed when triggered
 // by a service worker that doesn't control any clients.
 pref("dom.serviceWorkers.update_delay", 1000);
 
 // Enable test for 24 hours update, service workers will always treat last update check time is over 24 hours
 pref("dom.serviceWorkers.testUpdateOverOneDay", false);
 
-// If this is true, TextEventDispatcher dispatches keydown and keyup events
-// even during composition (keypress events are never fired during composition
-// even if this is true).
-pref("dom.keyboardevent.dispatch_during_composition", true);
-
-// If this is true, TextEventDispatcher dispatches keypress event with setting
-// WidgetEvent::mFlags::mOnlySystemGroupDispatchInContent to true if it won't
-// cause inputting printable character.
-pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", true);
-
 // Blacklist of domains of web apps which are not aware of strict keypress
 // dispatching behavior.  This is comma separated list.  If you need to match
 // all sub-domains, you can specify it as "*.example.com".  Additionally, you
 // can limit the path.  E.g., "example.com/foo" means "example.com/foo*".  So,
 // if you need to limit under a directory, the path should end with "/" like
 // "example.com/foo/".  Note that this cannot limit port number for now.
 pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys", "www.icloud.com");
 // Pref for end-users and policy to add additional values.
@@ -332,24 +322,20 @@ pref("dom.inputevent.datatransfer.enable
   // - A subset of Cloudflare
   pref("dom.script_loader.binast_encoding.domain.restrict.list", "*.facebook.com,static.xx.fbcdn.net,*.cloudflare.com,*.cloudflarestream.com,unpkg.com");
 #endif
 
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_total_viewers", -1);
 
-pref("ui.use_native_colors", true);
 pref("ui.click_hold_context_menus", false);
 // 0 = false, 1 = true, 2 = autodetect.
 pref("ui.android.mouse_as_touch", 1);
 
-// Pop up context menu on mouseup instead of mousedown, if that's the OS default.
-// Note: ignored on Windows (context menus always use mouseup)
-pref("ui.context_menus.after_mouseup", false);
 // Duration of timeout of incremental search in menus (ms).  0 means infinite.
 pref("ui.menu.incremental_search.timeout", 1000);
 // If true, all popups won't hide automatically on blur
 pref("ui.popup.disable_autohide", false);
 
 // 0 = default: always, except in high contrast mode
 // 1 = always
 // 2 = never
@@ -1025,17 +1011,16 @@ pref("accessibility.typeaheadfind.soundU
 pref("accessibility.typeaheadfind.enablesound", true);
 #ifdef XP_MACOSX
   pref("accessibility.typeaheadfind.prefillwithselection", false);
 #else
   pref("accessibility.typeaheadfind.prefillwithselection", true);
 #endif
 pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
 pref("findbar.highlightAll", false);
-pref("findbar.modalHighlight", false);
 pref("findbar.entireword", false);
 pref("findbar.iteratorTimeout", 100);
 
 // use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
 pref("gfx.use_text_smoothing_setting", false);
 
 // Number of characters to consider emphasizing for rich autocomplete results
 pref("toolkit.autocomplete.richBoundaryCutoff", 200);
@@ -1484,27 +1469,16 @@ pref("logging.config.clear_on_startup", 
 // If there is ever a security firedrill that requires
 // us to block certian ports global, this is the pref
 // to use.  Is is a comma delimited list of port numbers
 // for example:
 //   pref("network.security.ports.banned", "1,2,3,4,5");
 // prevents necko connecting to ports 1-5 unless the protocol
 // overrides.
 
-// Allow the network changed event to get sent when a network topology or
-// setup change is noticed while running.
-pref("network.notify.changed", true);
-
-// Allow network detection of IPv6 related changes (bug 1245059)
-#if defined(XP_WIN)
-  pref("network.notify.IPv6", false);
-#else
-  pref("network.notify.IPv6", true);
-#endif
-
 // Transmit UDP busy-work to the LAN when anticipating low latency
 // network reads and on wifi to mitigate 802.11 Power Save Polling delays
 pref("network.tickle-wifi.enabled", false);
 pref("network.tickle-wifi.duration", 400);
 pref("network.tickle-wifi.delay", 16);
 
 // Turn off interprocess security checks. Needed to run xpcshell tests.
 pref("network.disable.ipc.security", true);
@@ -2300,30 +2274,22 @@ pref("intl.regional_prefs.use_os_locales
 // for ISO-8859-1
 pref("intl.fallbackCharsetList.ISO-8859-1", "windows-1252");
 pref("font.language.group",                 "chrome://global/locale/intl.properties");
 
 // Android-specific pref to control if keydown and keyup events are fired even
 // in during composition.  Note that those prefs are ignored if
 // "dom.keyboardevent.dispatch_during_composition" is false.
 #ifdef MOZ_WIDGET_ANDROID
-  // If true, dispatch the keydown and keyup events on any web apps even during
-  // composition.
-  #ifdef EARLY_BETA_OR_EARLIER
-    pref("intl.ime.hack.on_any_apps.fire_key_events_for_composition", true);
-  #else // #ifdef EARLY_BETA_OR_EARLIER
-    pref("intl.ime.hack.on_any_apps.fire_key_events_for_composition", false);
-  #endif // #ifdef EARLY_BETA_OR_EARLIER #else
-  // If true and the above pref is false, dispatch the keydown and keyup events
-  // only on IME-unaware web apps.  So, this supports web apps which listen to
-  // only keydown or keyup event to get a change to do something at every text
-  // input.
+  // If true and intl.ime.hack.on_any_apps.fire_key_events_for_composition is
+  // false, dispatch the keydown and keyup events only on IME-unaware web apps.
+  // So, this supports web apps which listen to only keydown or keyup events
+  // to get a change to do something at every text input.
   pref("intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition", true);
-#else // MOZ_WIDGET_ANDROID
-  pref("intl.ime.hack.on_any_apps.fire_key_events_for_composition", false);
+#else
   pref("intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition", false);
 #endif // MOZ_WIDGET_ANDROID
 
 // If you use legacy Chinese IME which puts an ideographic space to composition
 // string as placeholder, this pref might be useful.  If this is true and when
 // web contents forcibly commits composition (e.g., moving focus), the
 // ideographic space will be ignored (i.e., commits with empty string).
 pref("intl.ime.remove_placeholder_character_at_commit", false);
@@ -2651,23 +2617,16 @@ pref("services.blocklist.gfx.collection"
 pref("services.blocklist.gfx.checked", 0);
 pref("services.blocklist.gfx.signer", "remote-settings.content-signature.mozilla.org");
 
 // Modifier key prefs: default to Windows settings,
 // menu access key = alt, accelerator key = control.
 // Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js
 pref("ui.key.accelKey", 17);
 pref("ui.key.menuAccessKey", 18);
-pref("ui.key.generalAccessKey", -1);
-
-// If generalAccessKey is -1, use the following two prefs instead.
-// Use 0 for disabled, 1 for Shift, 2 for Ctrl, 4 for Alt, 8 for Meta, 16 for Win
-// (values can be combined, e.g. 5 for Alt+Shift)
-pref("ui.key.chromeAccess", 4);
-pref("ui.key.contentAccess", 5);
 
 pref("ui.key.menuAccessKeyFocuses", false); // overridden below
 
 // Middle-mouse handling
 pref("middlemouse.paste", false);
 pref("middlemouse.contentLoadURL", false);
 pref("middlemouse.scrollbarPosition", false);
 
@@ -2899,22 +2858,16 @@ pref("layout.selection.caret_style", 0);
 pref("layout.css.report_errors", true);
 
 // Override DPI. A value of -1 means use the maximum of 96 and the system DPI.
 // A value of 0 means use the system DPI. A positive value is used as the DPI.
 // This sets the physical size of a device pixel and thus controls the
 // interpretation of physical units such as "pt".
 pref("layout.css.dpi", -1);
 
-// Set the number of device pixels per CSS pixel. A value <= 0 means choose
-// automatically based on user settings for the platform (e.g., "UI scale factor"
-// on Mac). A positive value is used as-is. This effectively controls the size
-// of a CSS "px". This is only used for windows on the screen, not for printing.
-pref("layout.css.devPixelsPerPx", "-1.0");
-
 // Set the threshold distance in CSS pixels below which scrolling will snap to
 // an edge, when scroll snapping is set to "proximity".
 pref("layout.css.scroll-snap.proximity-threshold", 200);
 
 // When selecting the snap point for CSS scroll snapping, the velocity of the
 // scroll frame is clamped to this speed, in CSS pixels / s.
 pref("layout.css.scroll-snap.prediction-max-velocity", 2000);
 
@@ -3939,27 +3892,16 @@ pref("ui.mouse.radius.inputSource.touchO
   pref("font.weight-override.HelveticaNeue-Light", 300); // Ensure Light > Thin (200)
   pref("font.weight-override.HelveticaNeue-LightItalic", 300);
   pref("font.weight-override.HelveticaNeue-MediumItalic", 500); // Harmonize MediumItalic with Medium
 
   // Override the Windows settings: no menu key, meta accelerator key. ctrl for general access key in HTML/XUL
   // Use 17 for Ctrl, 18 for Option, 224 for Cmd, 0 for none
   pref("ui.key.menuAccessKey", 0);
   pref("ui.key.accelKey", 224);
-  // (pinkerton, joki, saari) IE5 for mac uses Control for access keys. The HTML4 spec
-  // suggests to use command on mac, but this really sucks (imagine someone having a "q"
-  // as an access key and not letting you quit the app!). As a result, we've made a
-  // command decision 1 day before tree lockdown to change it to the control key.
-  pref("ui.key.generalAccessKey", -1);
-
-  // If generalAccessKey is -1, use the following two prefs instead.
-  // Use 0 for disabled, 1 for Shift, 2 for Ctrl, 4 for Alt, 8 for Meta (Cmd)
-  // (values can be combined, e.g. 3 for Ctrl+Shift)
-  pref("ui.key.chromeAccess", 2);
-  pref("ui.key.contentAccess", 6);
 
   // See bug 404131, topmost <panel> element wins to Dashboard on MacOSX.
   pref("ui.panel.default_level_parent", false);
 
   pref("ui.plugin.cancel_composition_at_input_source_changed", false);
 
   pref("mousewheel.system_scroll_override_on_root_content.enabled", false);
 
@@ -4845,22 +4787,16 @@ pref("dom.push.http2.retryInterval", 500
 // 0 - disabled, 1 - enabled, 2 - autodetect
 // Autodetection is currently only supported on Windows and GTK3
 #if defined(XP_MACOSX)
   pref("dom.w3c_touch_events.enabled", 0);
 #else
   pref("dom.w3c_touch_events.enabled", 2);
 #endif
 
-// Control firing WidgetMouseEvent by handling Windows pointer messages or mouse
-// messages.
-#if defined(XP_WIN)
-  pref("dom.w3c_pointer_events.dispatch_by_pointer_messages", false);
-#endif
-
 // W3C pointer events draft
 pref("dom.w3c_pointer_events.implicit_capture", false);
 
 // W3C MediaDevices devicechange fake event
 pref("media.ondevicechange.fakeDeviceChangeEvent.enabled", false);
 
 // How long must we wait before declaring that a window is a "ghost" (i.e., a
 // likely leak)?  This should be longer than it usually takes for an eligible
@@ -5001,18 +4937,16 @@ pref("network.trr.useGET", false);
 // from the DOH end point to ensure proper configuration
 pref("network.trr.confirmationNS", "example.com");
 // hardcode the resolution of the hostname in network.trr.uri instead of
 // relying on the system resolver to do it for you
 pref("network.trr.bootstrapAddress", "");
 // TRR blacklist entry expire time (in seconds). Default is one minute.
 // Meant to survive basically a page load.
 pref("network.trr.blacklist-duration", 60);
-// Single TRR request timeout, in milliseconds
-pref("network.trr.request-timeout", 1500);
 // Allow AAAA entries to be used "early", before the A results are in
 pref("network.trr.early-AAAA", false);
 // When true, it only sends AAAA when the system has IPv6 connectivity
 pref("network.trr.skip-AAAA-when-not-supported", true);
 // When true, the DNS request will wait for both A and AAAA responses
 // (if both have been requested) before notifying the listeners.
 // When true, it effectively cancels `network.trr.early-AAAA`
 pref("network.trr.wait-for-A-and-AAAA", true);
--- a/modules/libpref/moz.build
+++ b/modules/libpref/moz.build
@@ -34,16 +34,17 @@ pref_groups = [
     'channelclassifier',
     'clipboard',
     'content',
     'device',
     'devtools',
     'dom',
     'editor',
     'extensions',
+    'findbar',
     'fission',
     'font',
     'full_screen_api',
     'general',
     'geo',
     'gfx',
     'gl',
     'html5',
@@ -76,17 +77,18 @@ pref_groups = [
     'view_source',
     'webgl',
     'widget',
     'xul',
     'zoom',
 ]
 if CONFIG['OS_TARGET'] == 'Android':
     pref_groups += [
-        'consoleservice'
+        'android',
+        'consoleservice',
     ]
 if CONFIG['FUZZING']:
     pref_groups += [
         'fuzzing'
     ]
 pref_groups = tuple(sorted(pref_groups))
 
 # Note: generate_static_pref_list.py relies on StaticPrefListAll.h being first.
--- a/moz.build
+++ b/moz.build
@@ -168,11 +168,13 @@ DIRS += [
 ]
 
 if CONFIG['MOZ_BUILD_APP']:
     # Bring in the configuration for the configured application.
     include('/' + CONFIG['MOZ_BUILD_APP'] + '/app.mozbuild')
 else:
     include('/toolkit/toolkit.mozbuild')
 
-CONFIGURE_SUBST_FILES += ['.cargo/config']
+OBJDIR_PP_FILES['.cargo'] += ['.cargo/config.in']
+
+DEFINES['top_srcdir'] = TOPSRCDIR
 
 include('build/templates.mozbuild')
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/net/NetworkConnectivityService.h"
 #include "mozilla/net/SocketProcessHost.h"
 #include "mozilla/net/SocketProcessParent.h"
 #include "mozilla/net/SSLTokensCache.h"
 #include "mozilla/Unused.h"
 #include "nsContentSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsExceptionHandler.h"
+#include "mozilla/StaticPrefs_network.h"
 
 #ifdef MOZ_WIDGET_GTK
 #  include "nsGIOProtocolHandler.h"
 #endif
 
 namespace mozilla {
 namespace net {
 
@@ -78,17 +79,16 @@ using mozilla::dom::ServiceWorkerDescrip
 #define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
 #define OFFLINE_MIRRORS_CONNECTIVITY "network.offline-mirrors-connectivity"
 
 // Nb: these have been misnomers since bug 715770 removed the buffer cache.
 // "network.segment.count" and "network.segment.size" would be better names,
 // but the old names are still used to preserve backward compatibility.
 #define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
 #define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
-#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
 #define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
 #define WEBRTC_PREF_PREFIX "media.peerconnection."
 #define NETWORK_DNS_PREF "network.dns."
 
 #define MAX_RECURSION_COUNT 50
 
 nsIOService* gIOService;
 static bool gHasWarnedUploadChannel2;
@@ -197,32 +197,30 @@ nsIOService::nsIOService()
       mOfflineMirrorsConnectivity(true),
       mSettingOffline(false),
       mSetOfflineValue(false),
       mSocketProcessLaunchComplete(false),
       mShutdown(false),
       mHttpHandlerAlreadyShutingDown(false),
       mNetworkLinkServiceInitialized(false),
       mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY),
-      mNetworkNotifyChanged(true),
       mTotalRequests(0),
       mCacheWon(0),
       mNetWon(0),
       mLastOfflineStateChange(PR_IntervalNow()),
       mLastConnectivityChange(PR_IntervalNow()),
       mLastNetworkLinkChange(PR_IntervalNow()),
       mNetTearingDownStarted(0),
       mSocketProcess(nullptr) {}
 
 static const char* gCallbackPrefs[] = {
     PORT_PREF_PREFIX,
     MANAGE_OFFLINE_STATUS_PREF,
     NECKO_BUFFER_CACHE_COUNT_PREF,
     NECKO_BUFFER_CACHE_SIZE_PREF,
-    NETWORK_NOTIFY_CHANGED_PREF,
     NETWORK_CAPTIVE_PORTAL_PREF,
     nullptr,
 };
 
 static const char* gCallbackPrefsForSocketProcess[] = {
     WEBRTC_PREF_PREFIX,
     NETWORK_DNS_PREF,
     nullptr,
@@ -1262,24 +1260,16 @@ void nsIOService::PrefsChanged(const cha
        * is pretty crazy.  if you remove this, consider adding some
        * integer rollover test.
        */
       if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size;
     NS_WARNING_ASSERTION(!(size & (size - 1)),
                          "network segment size is not a power of 2!");
   }
 
-  if (!pref || strcmp(pref, NETWORK_NOTIFY_CHANGED_PREF) == 0) {
-    bool allow;
-    nsresult rv = Preferences::GetBool(NETWORK_NOTIFY_CHANGED_PREF, &allow);
-    if (NS_SUCCEEDED(rv)) {
-      mNetworkNotifyChanged = allow;
-    }
-  }
-
   if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
     nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF,
                                        &gCaptivePortalEnabled);
     if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
       if (gCaptivePortalEnabled) {
         static_cast<CaptivePortalService*>(mCaptivePortalService.get())
             ->Start();
       } else {
@@ -1341,17 +1331,17 @@ class nsWakeupNotifier : public Runnable
 };
 
 NS_IMETHODIMP
 nsIOService::NotifyWakeup() {
   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
 
   NS_ASSERTION(observerService, "The observer service should not be null");
 
-  if (observerService && mNetworkNotifyChanged) {
+  if (observerService && StaticPrefs::network_notify_changed()) {
     (void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
                                            (u"" NS_NETWORK_LINK_DATA_CHANGED));
   }
 
   RecheckCaptivePortal();
 
   return NS_OK;
 }
--- a/netwerk/base/nsIOService.h
+++ b/netwerk/base/nsIOService.h
@@ -211,18 +211,16 @@ class nsIOService final : public nsIIOSe
   // Cached protocol handlers, only accessed on the main thread
   nsWeakPtr mWeakHandler[NS_N(gScheme)];
 
   // cached categories
   nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
   nsTArray<int32_t> mRestrictedPortList;
 
-  bool mNetworkNotifyChanged;
-
   static bool sIsDataURIUniqueOpaqueOrigin;
   static bool sBlockToplevelDataUriNavigations;
 
   uint32_t mTotalRequests;
   uint32_t mCacheWon;
   uint32_t mNetWon;
 
   // These timestamps are needed for collecting telemetry on PR_Connect,
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -602,18 +602,18 @@ elif toolkit == 'cocoa':
     }
 elif toolkit == 'android':
     link_service = {
         'type': 'nsAndroidNetworkLinkService',
         'headers': ['/netwerk/system/android/nsAndroidNetworkLinkService.h'],
     }
 elif buildconfig.substs['OS_ARCH'] == 'Linux':
     link_service = {
-        'type': 'nsNotifyAddrListener',
-        'headers': ['/netwerk/system/linux/nsNotifyAddrListener_Linux.h'],
+        'type': 'nsNetworkLinkService',
+        'headers': ['/netwerk/system/linux/nsNetworkLinkService.h'],
         'init_method': 'Init',
     }
 
 if link_service:
     Classes += [
         dict({
             'cid': '{75a500a2-0030-40f7-86f8-63f225b940ae}',
             'contract_ids': ['@mozilla.org/network/network-link-service;1'],
--- a/netwerk/dns/TRRService.cpp
+++ b/netwerk/dns/TRRService.cpp
@@ -8,16 +8,17 @@
 #include "nsIObserverService.h"
 #include "nsIURIMutator.h"
 #include "nsNetUtil.h"
 #include "nsStandardURL.h"
 #include "TRR.h"
 #include "TRRService.h"
 
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/Tokenizer.h"
 
 static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
 static const char kClearPrivateData[] = "clear-private-data";
 static const char kPurge[] = "browser:purge-session-history";
 static const char kDisableIpv6Pref[] = "network.dns.disableIPv6";
 static const char kCaptivedetectCanonicalURL[] = "captivedetect.canonicalURL";
 
@@ -34,17 +35,16 @@ extern mozilla::LazyLogModule gHostResol
 TRRService* gTRRService = nullptr;
 
 NS_IMPL_ISUPPORTS(TRRService, nsIObserver, nsISupportsWeakReference)
 
 TRRService::TRRService()
     : mInitialized(false),
       mMode(0),
       mTRRBlacklistExpireTime(72 * 3600),
-      mTRRTimeout(3000),
       mLock("trrservice"),
       mConfirmationNS(NS_LITERAL_CSTRING("example.com")),
       mWaitForCaptive(true),
       mRfc1918(false),
       mCaptiveIsPassed(false),
       mUseGET(false),
       mDisableECS(true),
       mDisableAfterFails(5),
@@ -250,23 +250,16 @@ nsresult TRRService::ReadPrefs(const cha
   if (!name || !strcmp(name, TRR_PREF("blacklist-duration"))) {
     // prefs is given in number of seconds
     uint32_t secs;
     if (NS_SUCCEEDED(
             Preferences::GetUint(TRR_PREF("blacklist-duration"), &secs))) {
       mTRRBlacklistExpireTime = secs;
     }
   }
-  if (!name || !strcmp(name, TRR_PREF("request-timeout"))) {
-    // number of milliseconds
-    uint32_t ms;
-    if (NS_SUCCEEDED(Preferences::GetUint(TRR_PREF("request-timeout"), &ms))) {
-      mTRRTimeout = ms;
-    }
-  }
   if (!name || !strcmp(name, TRR_PREF("early-AAAA"))) {
     bool tmp;
     if (NS_SUCCEEDED(Preferences::GetBool(TRR_PREF("early-AAAA"), &tmp))) {
       mEarlyAAAA = tmp;
     }
   }
 
   if (!name || !strcmp(name, TRR_PREF("skip-AAAA-when-not-supported"))) {
@@ -340,16 +333,24 @@ nsresult TRRService::GetURI(nsCString& r
 }
 
 nsresult TRRService::GetCredentials(nsCString& result) {
   MutexAutoLock lock(mLock);
   result = mPrivateCred;
   return NS_OK;
 }
 
+uint32_t TRRService::GetRequestTimeout() {
+  if (mMode == MODE_TRRONLY) {
+    return StaticPrefs::network_trr_request_timeout_mode_trronly_ms();
+  }
+
+  return StaticPrefs::network_trr_request_timeout_ms();
+}
+
 nsresult TRRService::Start() {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
   if (!mInitialized) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   return NS_OK;
 }
 
--- a/netwerk/dns/TRRService.h
+++ b/netwerk/dns/TRRService.h
@@ -37,17 +37,17 @@ class TRRService : public nsIObserver,
   bool UseGET() { return mUseGET; }
   bool EarlyAAAA() { return mEarlyAAAA; }
   bool CheckIPv6Connectivity() { return mCheckIPv6Connectivity; }
   bool WaitForAllResponses() { return mWaitForAllResponses; }
   bool DisableIPv6() { return mDisableIPv6; }
   bool DisableECS() { return mDisableECS; }
   nsresult GetURI(nsCString& result);
   nsresult GetCredentials(nsCString& result);
-  uint32_t GetRequestTimeout() { return mTRRTimeout; }
+  uint32_t GetRequestTimeout();
 
   LookupStatus CompleteLookup(nsHostRecord*, nsresult, mozilla::net::AddrInfo*,
                               bool pb,
                               const nsACString& aOriginSuffix) override;
   LookupStatus CompleteLookupByType(nsHostRecord*, nsresult,
                                     const nsTArray<nsCString>*, uint32_t,
                                     bool pb) override;
   void TRRBlacklist(const nsACString& host, const nsACString& originSuffix,
@@ -69,17 +69,16 @@ class TRRService : public nsIObserver,
   void MaybeConfirm();
   void MaybeConfirm_locked();
   friend class ::nsDNSService;
   void GetParentalControlEnabledInternal();
 
   bool mInitialized;
   Atomic<uint32_t, Relaxed> mMode;
   Atomic<uint32_t, Relaxed> mTRRBlacklistExpireTime;
-  Atomic<uint32_t, Relaxed> mTRRTimeout;
 
   Mutex mLock;
 
   nsCString mPrivateURI;   // main thread only
   nsCString mPrivateCred;  // main thread only
   nsCString mConfirmationNS;
   nsCString mBootstrapAddr;
 
--- a/netwerk/system/linux/moz.build
+++ b/netwerk/system/linux/moz.build
@@ -1,12 +1,12 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['OS_ARCH'] == 'Linux':
     SOURCES += [
-        'nsNotifyAddrListener_Linux.cpp',
+        'nsNetworkLinkService.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
rename from netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
rename to netwerk/system/linux/nsNetworkLinkService.cpp
--- a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp
+++ b/netwerk/system/linux/nsNetworkLinkService.cpp
@@ -1,617 +1,150 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set et sw=2 ts=4: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include <stdarg.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <errno.h>
-#include <ifaddrs.h>
-#include <net/if.h>
-
-#include "nsThreadUtils.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
-#include "nsNotifyAddrListener_Linux.h"
+#include "nsNetworkLinkService.h"
 #include "nsString.h"
 #include "mozilla/Logging.h"
 
-#include "mozilla/Base64.h"
-#include "mozilla/FileUtils.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/Services.h"
-#include "mozilla/SHA1.h"
-#include "mozilla/Sprintf.h"
-#include "mozilla/Telemetry.h"
-#include "../../base/IPv6Utils.h"
-
-/* a shorter name that better explains what it does */
-#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
-
-// period during which to absorb subsequent network change events, in
-// milliseconds
-static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
 
 using namespace mozilla;
 
-static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+static LazyLogModule gNotifyAddrLog("nsNetworkLinkService");
 #define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
 
-#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
-
-NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsINetworkLinkService, nsIRunnable,
-                  nsIObserver)
+NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
 
-nsNotifyAddrListener::nsNotifyAddrListener()
-    : mMutex("nsNotifyAddrListener::mMutex"),
-      mLinkUp(true),  // assume true by default
-      mStatusKnown(false),
-      mAllowChangedEvent(true),
-      mCoalescingActive(false) {
-  mShutdownPipe[0] = -1;
-  mShutdownPipe[1] = -1;
-}
-
-nsNotifyAddrListener::~nsNotifyAddrListener() {
-  MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed");
-
-  if (mShutdownPipe[0] != -1) {
-    EINTR_RETRY(close(mShutdownPipe[0]));
-  }
-  if (mShutdownPipe[1] != -1) {
-    EINTR_RETRY(close(mShutdownPipe[1]));
-  }
-}
+nsNetworkLinkService::nsNetworkLinkService() : mStatusIsKnown(false) {}
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetIsLinkUp(bool* aIsUp) {
-  // XXX This function has not yet been implemented for this platform
-  *aIsUp = mLinkUp;
+nsNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+  if (!mNetlinkSvc) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mNetlinkSvc->GetIsLinkUp(aIsUp);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetLinkStatusKnown(bool* aIsUp) {
-  // XXX This function has not yet been implemented for this platform
-  *aIsUp = mStatusKnown;
+nsNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+  *aIsKnown = mStatusIsKnown;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetLinkType(uint32_t* aLinkType) {
+nsNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
   NS_ENSURE_ARG_POINTER(aLinkType);
 
   // XXX This function has not yet been implemented for this platform
   *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNotifyAddrListener::GetNetworkID(nsACString& aNetworkID) {
-  MutexAutoLock lock(mMutex);
-  aNetworkID = mNetworkId;
+nsNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
+  if (!mNetlinkSvc) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mNetlinkSvc->GetNetworkID(aNetworkID);
   return NS_OK;
 }
 
-//
-// Figure out the current IPv4 "network identification" string.
-//
-// It detects the IP of the default gateway in the routing table, then the MAC
-// address of that IP in the ARP table before it hashes that string (to avoid
-// information leakage).
-//
-static bool ipv4NetworkId(SHA1Sum* sha1) {
-  const char* kProcRoute = "/proc/net/route"; /* IPv4 routes */
-  const char* kProcArp = "/proc/net/arp";
-  bool found = false;
-
-  FILE* froute = fopen(kProcRoute, "r");
-  if (froute) {
-    char buffer[512];
-    uint32_t gw = 0;
-    char* l = fgets(buffer, sizeof(buffer), froute);
-    if (l) {
-      /* skip the title line  */
-      while (l) {
-        char interf[32];
-        uint32_t dest;
-        uint32_t gateway;
-        l = fgets(buffer, sizeof(buffer), froute);
-        if (l) {
-          buffer[511] = 0; /* as a precaution */
-          int val = sscanf(buffer, "%31s %x %x", interf, &dest, &gateway);
-          if ((3 == val) && !dest) {
-            gw = gateway;
-            break;
-          }
-        }
-      }
-    }
-    fclose(froute);
-
-    if (gw) {
-      /* create a string to search for in the arp table */
-      char searchfor[16];
-      SprintfLiteral(searchfor, "%d.%d.%d.%d", gw & 0xff, (gw >> 8) & 0xff,
-                     (gw >> 16) & 0xff, gw >> 24);
-
-      FILE* farp = fopen(kProcArp, "r");
-      if (farp) {
-        l = fgets(buffer, sizeof(buffer), farp);
-        while (l) {
-          /* skip the title line  */
-          l = fgets(buffer, sizeof(buffer), farp);
-          if (l) {
-            buffer[511] = 0; /* as a precaution */
-            int p[4];
-            char type[16];
-            char flags[16];
-            char hw[32];
-            if (7 == sscanf(buffer, "%u.%u.%u.%u %15s %15s %31s", &p[0], &p[1],
-                            &p[2], &p[3], type, flags, hw)) {
-              uint32_t searchip =
-                  p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
-              if (gw == searchip) {
-                LOG(("networkid: MAC %s\n", hw));
-                nsAutoCString mac(hw);
-                sha1->update(mac.get(), mac.Length());
-                found = true;
-                break;
-              }
-            }
-          }
-        }
-        fclose(farp);
-      } /* if (farp) */
-    }   /* if (gw) */
-  }     /* if (froute) */
-  return found;
-}
-
-// Figure out the current IPv6 "network identification" string.
-//
-static bool ipv6NetworkId(SHA1Sum* sha1) {
-  bool found = false;
-  FILE* ifs = fopen("/proc/net/if_inet6", "r");
-  if (ifs) {
-    char buffer[512];
-    char ip6[40];
-    int devnum;
-    int preflen;
-    int scope;
-    int flags;
-    char name[40];
-
-    char* l = fgets(buffer, sizeof(buffer), ifs);
-    // 2a001a28120000090000000000000002 02 40 00 80   eth0
-    // +------------------------------+ ++ ++ ++ ++   ++
-    // |                                |  |  |  |    |
-    // 1                                2  3  4  5    6
-    //
-    // 1. IPv6 address displayed in 32 hexadecimal chars without colons as
-    //    separator
-    //
-    // 2. Netlink device number (interface index) in hexadecimal.
-    //
-    // 3. Prefix length in hexadecimal number of bits
-    //
-    // 4. Scope value (see kernel source include/net/ipv6.h and
-    //    net/ipv6/addrconf.c for more)
-    //
-    // 5. Interface flags (see include/linux/rtnetlink.h and net/ipv6/addrconf.c
-    //    for more)
-    //
-    // 6. Device name
-    //
-    while (l) {
-      memset(ip6, 0, sizeof(ip6));
-      if (6 == sscanf(buffer, "%32[0-9a-f] %02x %02x %02x %02x %31s", ip6,
-                      &devnum, &preflen, &scope, &flags, name)) {
-        unsigned char id6[16];
-        memset(id6, 0, sizeof(id6));
-
-        for (int i = 0; i < 16; i++) {
-          char buf[3];
-          buf[0] = ip6[i * 2];
-          buf[1] = ip6[i * 2 + 1];
-          buf[2] = 0;
-          // convert from hex
-          id6[i] = (unsigned char)strtol(buf, nullptr, 16);
-        }
-
-        if (net::utils::ipv6_scope(id6) == IPV6_SCOPE_GLOBAL) {
-          unsigned char prefix[16];
-          memset(prefix, 0, sizeof(prefix));
-          uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
-          int bits = preflen;
-          for (int i = 0; i < 16; i++) {
-            uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
-            prefix[i] = id6[i] & mask;
-            bits -= 8;
-            if (bits <= 0) {
-              break;
-            }
-          }
-          // We hash the IPv6 prefix and prefix length in order to
-          // differentiate between networks with a different prefix length
-          // For example: 2a00:/16 and 2a00:0/32
-          sha1->update(prefix, 16);
-          sha1->update(&preflen, sizeof(preflen));
-          found = true;
-          LOG(("networkid: found global IPv6 address %s/%d\n", ip6, preflen));
-        }
-      }
-      l = fgets(buffer, sizeof(buffer), ifs);
-    }
-    fclose(ifs);
-  }
-  return found;
-}
-
-// Figure out the "network identification".
-//
-void nsNotifyAddrListener::calculateNetworkId(void) {
-  MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
-  SHA1Sum sha1;
-  bool found4 = ipv4NetworkId(&sha1);
-  bool found6 = ipv6NetworkId(&sha1);
-
-  if (found4 || found6) {
-    // This 'addition' could potentially be a fixed number from the
-    // profile or something.
-    nsAutoCString addition("local-rubbish");
-    nsAutoCString output;
-    sha1.update(addition.get(), addition.Length());
-    uint8_t digest[SHA1Sum::kHashSize];
-    sha1.finish(digest);
-    nsAutoCString newString(reinterpret_cast<char*>(digest),
-                            SHA1Sum::kHashSize);
-    nsresult rv = Base64Encode(newString, output);
-    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
-    LOG(("networkid: id %s\n", output.get()));
-    MutexAutoLock lock(mMutex);
-    if (mNetworkId != output) {
-      // new id
-      if (found4 && !found6) {
-        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1);  // IPv4 only
-      } else if (!found4 && found6) {
-        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3);  // IPv6 only
-      } else {
-        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4);  // Both!
-      }
-      mNetworkId = output;
-    } else {
-      // same id
-      LOG(("Same network id"));
-      Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
-    }
-  } else {
-    // no id
-    LOG(("No network id"));
-    MutexAutoLock lock(mMutex);
-    mNetworkId.Truncate();
-    Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
-  }
-}
-
-//
-// Check if there's a network interface available to do networking on.
-//
-void nsNotifyAddrListener::checkLink(void) {
-  struct ifaddrs* list;
-  struct ifaddrs* ifa;
-  bool link = false;
-  bool prevLinkUp = mLinkUp;
-
-  if (getifaddrs(&list)) return;
-
-  // Walk through the linked list, maintaining head pointer so we can free
-  // list later
-
-  for (ifa = list; ifa != nullptr; ifa = ifa->ifa_next) {
-    int family;
-    if (ifa->ifa_addr == nullptr) continue;
-
-    family = ifa->ifa_addr->sa_family;
-
-    if ((family == AF_INET || family == AF_INET6) &&
-        (ifa->ifa_flags & IFF_RUNNING) && !(ifa->ifa_flags & IFF_LOOPBACK)) {
-      // An interface that is UP and not loopback
-      link = true;
-      break;
-    }
-  }
-  mLinkUp = link;
-  freeifaddrs(list);
-
-  if (prevLinkUp != mLinkUp) {
-    // UP/DOWN status changed, send appropriate UP/DOWN event
-    SendEvent(mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
-  }
-}
-
-void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket) {
-  struct nlmsghdr* nlh;
-
-  // The buffer size below, (4095) was chosen partly based on testing and
-  // partly on existing sample source code using this size. It needs to be
-  // large enough to hold the netlink messages from the kernel.
-  char buffer[4095];
-  struct rtattr* attr;
-  int attr_len;
-  const struct ifaddrmsg* newifam;
-
-  ssize_t rc = EINTR_RETRY(recv(aNetlinkSocket, buffer, sizeof(buffer), 0));
-  if (rc < 0) {
-    return;
-  }
-  size_t netlink_bytes = rc;
-
-  nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
-
-  bool networkChange = false;
-
-  for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
-    char prefixaddr[INET6_ADDRSTRLEN];
-    char localaddr[INET6_ADDRSTRLEN];
-    char* addr = nullptr;
-    prefixaddr[0] = localaddr[0] = '\0';
-
-    if (NLMSG_DONE == nlh->nlmsg_type) {
-      break;
-    }
-
-    LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
-    newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
-
-    if ((newifam->ifa_family != AF_INET) && (newifam->ifa_family != AF_INET6)) {
-      continue;
-    }
-
-    attr = IFA_RTA(newifam);
-    attr_len = IFA_PAYLOAD(nlh);
-    for (; attr_len && RTA_OK(attr, attr_len);
-         attr = RTA_NEXT(attr, attr_len)) {
-      if (attr->rta_type == IFA_ADDRESS) {
-        if (newifam->ifa_family == AF_INET) {
-          struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
-          inet_ntop(AF_INET, in, prefixaddr, INET_ADDRSTRLEN);
-        } else {
-          struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
-          inet_ntop(AF_INET6, in, prefixaddr, INET6_ADDRSTRLEN);
-        }
-      } else if (attr->rta_type == IFA_LOCAL) {
-        if (newifam->ifa_family == AF_INET) {
-          struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
-          inet_ntop(AF_INET, in, localaddr, INET_ADDRSTRLEN);
-        } else {
-          struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
-          inet_ntop(AF_INET6, in, localaddr, INET6_ADDRSTRLEN);
-        }
-      }
-    }
-    if (localaddr[0]) {
-      addr = localaddr;
-    } else if (prefixaddr[0]) {
-      addr = prefixaddr;
-    } else {
-      continue;
-    }
-    if (nlh->nlmsg_type == RTM_NEWADDR) {
-      LOG(
-          ("nsNotifyAddrListener::OnNetlinkMessage: a new address "
-           "- %s.",
-           addr));
-      struct ifaddrmsg* ifam;
-      nsCString addrStr;
-      addrStr.Assign(addr);
-      if (auto entry = mAddressInfo.LookupForAdd(addrStr)) {
-        ifam = entry.Data();
-        LOG(
-            ("nsNotifyAddrListener::OnNetlinkMessage: the address "
-             "already known."));
-        if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
-          LOG(
-              ("nsNotifyAddrListener::OnNetlinkMessage: but "
-               "the address info has been changed."));
-          networkChange = true;
-          memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
-        }
-      } else {
-        networkChange = true;
-        ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
-        memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
-        entry.OrInsert([ifam]() { return ifam; });
-      }
-    } else {
-      LOG(
-          ("nsNotifyAddrListener::OnNetlinkMessage: an address "
-           "has been deleted - %s.",
-           addr));
-      networkChange = true;
-      nsCString addrStr;
-      addrStr.Assign(addr);
-      mAddressInfo.Remove(addrStr);
-    }
-  }
-
-  if (networkChange && mAllowChangedEvent) {
-    NetworkChanged();
-  }
-
-  if (networkChange) {
-    checkLink();
-  }
-}
-
 NS_IMETHODIMP
-nsNotifyAddrListener::Run() {
-  int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-  if (netlinkSocket < 0) {
-    return NS_ERROR_FAILURE;
-  }
-
-  struct sockaddr_nl addr;
-  memset(&addr, 0, sizeof(addr));  // clear addr
-
-  addr.nl_family = AF_NETLINK;
-  addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
-
-  if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
-    // failure!
-    EINTR_RETRY(close(netlinkSocket));
-    return NS_ERROR_FAILURE;
-  }
-
-  // switch the socket into non-blocking
-  int flags = fcntl(netlinkSocket, F_GETFL, 0);
-  (void)fcntl(netlinkSocket, F_SETFL, flags | O_NONBLOCK);
-
-  struct pollfd fds[2];
-  fds[0].fd = mShutdownPipe[0];
-  fds[0].events = POLLIN;
-  fds[0].revents = 0;
-
-  fds[1].fd = netlinkSocket;
-  fds[1].events = POLLIN;
-  fds[1].revents = 0;
-
-  calculateNetworkId();
-
-  nsresult rv = NS_OK;
-  bool shutdown = false;
-  int pollWait = -1;
-  while (!shutdown) {
-    int rc = EINTR_RETRY(poll(fds, 2, pollWait));
-
-    if (rc > 0) {
-      if (fds[0].revents & POLLIN) {
-        // shutdown, abort the loop!
-        LOG(("thread shutdown received, dying...\n"));
-        shutdown = true;
-      } else if (fds[1].revents & POLLIN) {
-        LOG(("netlink message received, handling it...\n"));
-        OnNetlinkMessage(netlinkSocket);
-      }
-    } else if (rc < 0) {
-      rv = NS_ERROR_FAILURE;
-      break;
-    }
-    if (mCoalescingActive) {
-      // check if coalescing period should continue
-      double period = (TimeStamp::Now() - mChangeTime).ToMilliseconds();
-      if (period >= kNetworkChangeCoalescingPeriod) {
-        SendEvent(NS_NETWORK_LINK_DATA_CHANGED);
-        mCoalescingActive = false;
-        pollWait = -1;  // restore to default
-      } else {
-        // wait no longer than to the end of the period
-        pollWait = static_cast<int>(kNetworkChangeCoalescingPeriod - period);
-      }
-    }
-  }
-
-  EINTR_RETRY(close(netlinkSocket));
-
-  return rv;
-}
-
-NS_IMETHODIMP
-nsNotifyAddrListener::Observe(nsISupports* subject, const char* topic,
+nsNetworkLinkService::Observe(nsISupports* subject, const char* topic,
                               const char16_t* data) {
   if (!strcmp("xpcom-shutdown-threads", topic)) {
     Shutdown();
   }
 
   return NS_OK;
 }
 
-nsresult nsNotifyAddrListener::Init(void) {
+nsresult nsNetworkLinkService::Init() {
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
-  if (!observerService) return NS_ERROR_FAILURE;
-
-  nsresult rv =
-      observerService->AddObserver(this, "xpcom-shutdown-threads", false);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF,
-                               true);
-
-  if (-1 == pipe(mShutdownPipe)) {
+  if (!observerService) {
     return NS_ERROR_FAILURE;
   }
 
-  rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
+  nsresult rv;
+  rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mNetlinkSvc = new mozilla::net::NetlinkService();
+  rv = mNetlinkSvc->Init(this);
+  if (NS_FAILED(rv)) {
+    mNetlinkSvc = nullptr;
+    LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+         static_cast<uint32_t>(rv)));
+    return rv;
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-nsresult nsNotifyAddrListener::Shutdown(void) {
+nsresult nsNetworkLinkService::Shutdown() {
   // remove xpcom shutdown observer
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (observerService)
     observerService->RemoveObserver(this, "xpcom-shutdown-threads");
 
-  LOG(("write() to signal thread shutdown\n"));
-
-  // awake the thread to make it terminate
-  ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
-  LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
-
-  nsresult rv = mThread->Shutdown();
-
-  // Have to break the cycle here, otherwise nsNotifyAddrListener holds
-  // onto the thread and the thread holds onto the nsNotifyAddrListener
-  // via its mRunnable
-  mThread = nullptr;
-
-  return rv;
-}
+  if (mNetlinkSvc) {
+    mNetlinkSvc->Shutdown();
+    mNetlinkSvc = nullptr;
+  }
 
-/*
- * A network event has been registered. Delay the actual sending of the event
- * for a while and absorb subsequent events in the mean time in an effort to
- * squash potentially many triggers into a single event.
- * Only ever called from the same thread.
- */
-nsresult nsNotifyAddrListener::NetworkChanged() {
-  if (mCoalescingActive) {
-    LOG(("NetworkChanged: absorbed an event (coalescing active)\n"));
-  } else {
-    // A fresh trigger!
-    mChangeTime = TimeStamp::Now();
-    mCoalescingActive = true;
-    LOG(("NetworkChanged: coalescing period started\n"));
-  }
   return NS_OK;
 }
 
-/* Sends the given event.  Assumes aEventID never goes out of scope (static
+void nsNetworkLinkService::OnNetworkChanged() {
+  if (StaticPrefs::network_notify_changed()) {
+    RefPtr<nsNetworkLinkService> self = this;
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "nsNetworkLinkService::OnNetworkChanged",
+        [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_CHANGED); }));
+  }
+}
+
+void nsNetworkLinkService::OnLinkUp() {
+  RefPtr<nsNetworkLinkService> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "nsNetworkLinkService::OnLinkUp",
+      [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_UP); }));
+}
+
+void nsNetworkLinkService::OnLinkDown() {
+  RefPtr<nsNetworkLinkService> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "nsNetworkLinkService::OnLinkDown",
+      [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_DOWN); }));
+}
+
+void nsNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+/* Sends the given event. Assumes aEventID never goes out of scope (static
  * strings are ideal).
  */
-nsresult nsNotifyAddrListener::SendEvent(const char* aEventID) {
-  if (!aEventID) return NS_ERROR_NULL_POINTER;
+void nsNetworkLinkService::SendEvent(const char* aEventID) {
+  MOZ_ASSERT(NS_IsMainThread());
 
   LOG(("SendEvent: %s\n", aEventID));
-  nsresult rv = NS_OK;
-  calculateNetworkId();
-  nsCOMPtr<nsIRunnable> event = new ChangeEvent(this, aEventID);
-  if (NS_FAILED(rv = NS_DispatchToMainThread(event)))
-    NS_WARNING("Failed to dispatch ChangeEvent");
-  return rv;
-}
 
-NS_IMETHODIMP
-nsNotifyAddrListener::ChangeEvent::Run() {
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
-  if (observerService)
-    observerService->NotifyObservers(mService, NS_NETWORK_LINK_TOPIC,
-                                     NS_ConvertASCIItoUTF16(mEventID).get());
-  return NS_OK;
+
+  if (observerService) {
+    observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+                                     NS_NETWORK_LINK_TOPIC,
+                                     NS_ConvertASCIItoUTF16(aEventID).get());
+  }
 }
rename from netwerk/system/linux/nsNotifyAddrListener_Linux.h
rename to netwerk/system/linux/nsNetworkLinkService.h
--- a/netwerk/system/linux/nsNotifyAddrListener_Linux.h
+++ b/netwerk/system/linux/nsNetworkLinkService.h
@@ -1,104 +1,45 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set et sw=2 ts=4: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#ifndef NSNOTIFYADDRLISTENER_LINUX_H_
-#define NSNOTIFYADDRLISTENER_LINUX_H_
-
-#include <sys/socket.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <arpa/inet.h>
-#include <unistd.h>
+#ifndef NSNETWORKLINKSERVICE_LINUX_H_
+#define NSNETWORKLINKSERVICE_LINUX_H_
 
 #include "nsINetworkLinkService.h"
-#include "nsIRunnable.h"
 #include "nsIObserver.h"
-#include "nsThreadUtils.h"
-#include "nsCOMPtr.h"
+#include "../netlink/NetlinkService.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/Atomics.h"
-#include "mozilla/Mutex.h"
-#include "mozilla/TimeStamp.h"
-#include "nsITimer.h"
-#include "nsClassHashtable.h"
 
-class nsNotifyAddrListener : public nsINetworkLinkService,
-                             public nsIRunnable,
-                             public nsIObserver {
-  virtual ~nsNotifyAddrListener();
-
+class nsNetworkLinkService : public nsINetworkLinkService,
+                             public nsIObserver,
+                             public mozilla::net::NetlinkServiceListener {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSINETWORKLINKSERVICE
-  NS_DECL_NSIRUNNABLE
   NS_DECL_NSIOBSERVER
 
-  nsNotifyAddrListener();
-  nsresult Init(void);
+  nsNetworkLinkService();
+  nsresult Init();
+
+  void OnNetworkChanged() override;
+  void OnLinkUp() override;
+  void OnLinkDown() override;
+  void OnLinkStatusKnown() override;
 
  private:
-  class ChangeEvent : public mozilla::Runnable {
-   public:
-    NS_DECL_NSIRUNNABLE
-    ChangeEvent(nsINetworkLinkService* aService, const char* aEventID)
-        : mozilla::Runnable("nsNotifyAddrListener::ChangeEvent"),
-          mService(aService),
-          mEventID(aEventID) {}
-
-   private:
-    nsCOMPtr<nsINetworkLinkService> mService;
-    const char* mEventID;
-  };
+  virtual ~nsNetworkLinkService() = default;
 
   // Called when xpcom-shutdown-threads is received.
-  nsresult Shutdown(void);
-
-  // Called when a network change was detected
-  nsresult NetworkChanged();
+  nsresult Shutdown();
 
   // Sends the network event.
-  nsresult SendEvent(const char* aEventID);
-
-  // Figure out the current "network identification"
-  void calculateNetworkId(void);
-
-  mozilla::Mutex mMutex;
-  nsCString mNetworkId;
-
-  // Checks if there's a network "link"
-  void checkLink(void);
-
-  // Deals with incoming NETLINK messages.
-  void OnNetlinkMessage(int NetlinkSocket);
-
-  nsCOMPtr<nsIThread> mThread;
-
-  // The network is up.
-  bool mLinkUp;
+  void SendEvent(const char* aEventID);
 
-  // The network's up/down status is known.
-  bool mStatusKnown;
-
-  // A pipe to signal shutdown with.
-  int mShutdownPipe[2];
-
-  // Network changed events are enabled
-  bool mAllowChangedEvent;
+  mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
 
-  // Flag set while coalescing change events
-  bool mCoalescingActive;
-
-  // Time stamp for first event during coalescing
-  mozilla::TimeStamp mChangeTime;
-
-  // Seen Ip addresses. For Ipv6 addresses some time router renews their
-  // lifetime and we should not detect this as a network link change, so we
-  // keep info about all seen addresses.
-  nsClassHashtable<nsCStringHashKey, struct ifaddrmsg> mAddressInfo;
+  RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
 };
 
-#endif /* NSNOTIFYADDRLISTENER_LINUX_H_ */
+#endif /* NSNETWORKLINKSERVICE_LINUX_H_ */
--- a/netwerk/system/mac/nsNetworkLinkService.h
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -25,19 +25,16 @@ class nsNetworkLinkService : public nsIN
 
  protected:
   virtual ~nsNetworkLinkService();
 
  private:
   bool mLinkUp;
   bool mStatusKnown;
 
-  // Toggles allowing the sending of network-changed event.
-  bool mAllowChangedEvent;
-
   SCNetworkReachabilityRef mReachability;
   CFRunLoopRef mCFRunLoop;
   CFRunLoopSourceRef mRunLoopSource;
   SCDynamicStoreRef mStoreRef;
 
   void UpdateReachability();
   void SendEvent(bool aNetworkChanged);
   static void ReachabilityChanged(SCNetworkReachabilityRef target,
--- a/netwerk/system/mac/nsNetworkLinkService.mm
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -18,17 +18,17 @@
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsCRT.h"
 #include "nsNetCID.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Telemetry.h"
 #include "nsNetworkLinkService.h"
 #include "../../base/IPv6Utils.h"
 
 #import <Cocoa/Cocoa.h>
 #import <netinet/in.h>
@@ -67,17 +67,16 @@ static void CFReleaseSafe(CFTypeRef cf) 
   }
 }
 
 NS_IMPL_ISUPPORTS(nsNetworkLinkService, nsINetworkLinkService, nsIObserver)
 
 nsNetworkLinkService::nsNetworkLinkService()
     : mLinkUp(true),
       mStatusKnown(false),
-      mAllowChangedEvent(true),
       mReachability(nullptr),
       mCFRunLoop(nullptr),
       mRunLoopSource(nullptr),
       mStoreRef(nullptr),
       mMutex("nsNetworkLinkService::mMutex") {}
 
 nsNetworkLinkService::~nsNetworkLinkService() = default;
 
@@ -402,18 +401,16 @@ nsresult nsNetworkLinkService::Init(void
 
   nsCOMPtr<nsIObserverService> observerService =
       do_GetService("@mozilla.org/observer-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = observerService->AddObserver(this, "xpcom-shutdown", false);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF, true);
-
   // If the network reachability API can reach 0.0.0.0 without
   // requiring a connection, there is a network interface available.
   struct sockaddr_in addr;
   bzero(&addr, sizeof(addr));
   addr.sin_len = sizeof(addr);
   addr.sin_family = AF_INET;
   mReachability = ::SCNetworkReachabilityCreateWithAddress(nullptr, (struct sockaddr*)&addr);
   if (!mReachability) {
@@ -548,17 +545,17 @@ void nsNetworkLinkService::UpdateReachab
 void nsNetworkLinkService::SendEvent(bool aNetworkChanged) {
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   if (!observerService) {
     return;
   }
 
   const char* event;
   if (aNetworkChanged) {
-    if (!mAllowChangedEvent) {
+    if (!StaticPrefs::network_notify_changed()) {
       return;
     }
     event = NS_NETWORK_LINK_DATA_CHANGED;
   } else if (!mStatusKnown) {
     event = NS_NETWORK_LINK_DATA_UNKNOWN;
   } else {
     event = mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN;
   }
--- a/netwerk/system/moz.build
+++ b/netwerk/system/moz.build
@@ -9,9 +9,12 @@ if CONFIG['OS_ARCH'] == 'WINNT':
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     DIRS += ['mac']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['android']
 
 elif CONFIG['OS_ARCH'] == 'Linux':
-    DIRS += ['linux']
+    DIRS += [
+        'linux',
+        'netlink'
+    ]
new file mode 100644
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -0,0 +1,1449 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <arpa/inet.h>
+#include <linux/if_ether.h>
+#include <net/if.h>
+#include <poll.h>
+#include <linux/rtnetlink.h>
+
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "NetlinkService.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+
+/* a shorter name that better explains what it does */
+#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
+
+namespace mozilla {
+namespace net {
+
+// period during which to absorb subsequent network change events, in
+// milliseconds
+static const unsigned int kNetworkChangeCoalescingPeriod = 1000;
+
+static LazyLogModule gNlSvcLog("NetlinkService");
+#define LOG(args) MOZ_LOG(gNlSvcLog, mozilla::LogLevel::Debug, args)
+
+typedef union {
+  struct in_addr addr4;
+  struct in6_addr addr6;
+} in_common_addr;
+
+static void GetAddrStr(const in_common_addr* aAddr, uint8_t aFamily,
+                       nsACString& _retval) {
+  char addr[INET6_ADDRSTRLEN];
+  addr[0] = 0;
+
+  if (aFamily == AF_INET) {
+    inet_ntop(AF_INET, &(aAddr->addr4), addr, INET_ADDRSTRLEN);
+  } else {
+    inet_ntop(AF_INET6, &(aAddr->addr6), addr, INET6_ADDRSTRLEN);
+  }
+  _retval.Assign(addr);
+}
+
+class NetlinkAddress {
+ public:
+  NetlinkAddress() {}
+
+  uint8_t Family() const { return mIfam.ifa_family; }
+  uint32_t GetIndex() const { return mIfam.ifa_index; }
+  uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; }
+  const in_common_addr* GetAddrPtr() const { return &mAddr; }
+
+  bool Equals(const NetlinkAddress* aOther) const {
+    if (mIfam.ifa_family != aOther->mIfam.ifa_family) {
+      return false;
+    }
+    if (mIfam.ifa_index != aOther->mIfam.ifa_index) {
+      // addresses are different when they are on a different interface
+      return false;
+    }
+    if (mIfam.ifa_prefixlen != aOther->mIfam.ifa_prefixlen) {
+      // It's possible to have two equal addresses with a different netmask on
+      // the same interface, so we need to check prefixlen too.
+      return false;
+    }
+    size_t addrSize = (mIfam.ifa_family == AF_INET) ? sizeof(mAddr.addr4)
+                                                    : sizeof(mAddr.addr6);
+    return memcmp(&mAddr, aOther->GetAddrPtr(), addrSize) == 0;
+  }
+
+  bool Init(struct nlmsghdr* aNlh) {
+    struct ifaddrmsg* ifam;
+    struct rtattr* attr;
+    int len;
+
+    ifam = (ifaddrmsg*)NLMSG_DATA(aNlh);
+    len = IFA_PAYLOAD(aNlh);
+
+    if (ifam->ifa_family != AF_INET && ifam->ifa_family != AF_INET6) {
+      return false;
+    }
+
+    bool hasAddr = false;
+    for (attr = IFA_RTA(ifam); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+      if (attr->rta_type == IFA_ADDRESS || attr->rta_type == IFA_LOCAL) {
+        memcpy(&mAddr, RTA_DATA(attr),
+               ifam->ifa_family == AF_INET ? sizeof(mAddr.addr4)
+                                           : sizeof(mAddr.addr6));
+        hasAddr = true;
+        if (attr->rta_type == IFA_LOCAL) {
+          // local address is preferred, so don't continue parsing other
+          // attributes
+          break;
+        }
+      }
+    }
+
+    if (!hasAddr) {
+      return false;
+    }
+
+    memcpy(&mIfam, (ifaddrmsg*)NLMSG_DATA(aNlh), sizeof(mIfam));
+    return true;
+  }
+
+ private:
+  in_common_addr mAddr;
+  struct ifaddrmsg mIfam;
+};
+
+class NetlinkNeighbor {
+ public:
+  NetlinkNeighbor() : mHasMAC(false) {}
+
+  uint8_t Family() const { return mNeigh.ndm_family; }
+  const in_common_addr* GetAddrPtr() const { return &mAddr; }
+  const uint8_t* GetMACPtr() const { return mMAC; }
+  bool HasMAC() const { return mHasMAC; };
+
+#ifdef NL_DEBUG_LOG
+  void GetAsString(nsACString& _retval) const {
+    nsAutoCString addrStr;
+    _retval.Assign("addr=");
+    GetAddrStr(&mAddr, mNeigh.ndm_family, addrStr);
+    _retval.Append(addrStr);
+    if (mNeigh.ndm_family == AF_INET) {
+      _retval.Append(" family=AF_INET if=");
+    } else {
+      _retval.Append(" family=AF_INET6 if=");
+    }
+    _retval.AppendInt(mNeigh.ndm_ifindex);
+    if (mHasMAC) {
+      _retval.Append(" mac=");
+      _retval.Append(nsPrintfCString("%02x:%02x:%02x:%02x:%02x:%02x", mMAC[0],
+                                     mMAC[1], mMAC[2], mMAC[3], mMAC[4],
+                                     mMAC[5]));
+    }
+  }
+#endif
+
+  bool Init(struct nlmsghdr* aNlh) {
+    struct ndmsg* neigh;
+    struct rtattr* attr;
+    int len;
+
+    neigh = (ndmsg*)NLMSG_DATA(aNlh);
+    len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh));
+
+    if (neigh->ndm_family != AF_INET && neigh->ndm_family != AF_INET6) {
+      return false;
+    }
+
+    bool hasDST = false;
+    for (attr = RTM_RTA(neigh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+      if (attr->rta_type == NDA_LLADDR) {
+        memcpy(mMAC, RTA_DATA(attr), ETH_ALEN);
+        mHasMAC = true;
+      }
+
+      if (attr->rta_type == NDA_DST) {
+        memcpy(&mAddr, RTA_DATA(attr),
+               neigh->ndm_family == AF_INET ? sizeof(mAddr.addr4)
+                                            : sizeof(mAddr.addr6));
+        hasDST = true;
+      }
+    }
+
+    if (!hasDST) {
+      return false;
+    }
+
+    memcpy(&mNeigh, (ndmsg*)NLMSG_DATA(aNlh), sizeof(mNeigh));
+    return true;
+  }
+
+ private:
+  bool mHasMAC;
+  uint8_t mMAC[ETH_ALEN];
+  in_common_addr mAddr;
+  struct ndmsg mNeigh;
+};
+
+class NetlinkLink {
+ public:
+  NetlinkLink() {}
+
+  bool IsUp() const {
+    return (mIface.ifi_flags & IFF_RUNNING) &&
+           !(mIface.ifi_flags & IFF_LOOPBACK);
+  }
+
+  void GetName(nsACString& _retval) const { _retval = mName; }
+
+  uint32_t GetIndex() const { return mIface.ifi_index; }
+
+  bool Init(struct nlmsghdr* aNlh) {
+    struct ifinfomsg* iface;
+    struct rtattr* attr;
+    int len;
+
+    iface = (ifinfomsg*)NLMSG_DATA(aNlh);
+    len = aNlh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
+
+    bool hasName = false;
+    for (attr = IFLA_RTA(iface); RTA_OK(attr, len);
+         attr = RTA_NEXT(attr, len)) {
+      if (attr->rta_type == IFLA_IFNAME) {
+        mName.Assign((char*)RTA_DATA(attr));
+        hasName = true;
+        break;
+      }
+    }
+
+    if (!hasName) {
+      return false;
+    }
+
+    memcpy(&mIface, (ifinfomsg*)NLMSG_DATA(aNlh), sizeof(mIface));
+    return true;
+  }
+
+ private:
+  nsCString mName;
+  struct ifinfomsg mIface;
+};
+
+class NetlinkRoute {
+ public:
+  NetlinkRoute()
+      : mHasGWAddr(false),
+        mHasPrefSrcAddr(false),
+        mHasDstAddr(false),
+        mHasOif(false) {}
+
+  bool IsUnicast() const { return mRtm.rtm_type == RTN_UNICAST; }
+  bool IsDefault() const { return mRtm.rtm_dst_len == 0; }
+  bool HasOif() const { return mHasOif; }
+  uint8_t Oif() const { return mOif; }
+  uint8_t Family() const { return mRtm.rtm_family; }
+  bool HasPrefSrcAddr() const { return mHasPrefSrcAddr; }
+  const in_common_addr* GetGWAddrPtr() const {
+    return mHasGWAddr ? &mGWAddr : nullptr;
+  }
+  const in_common_addr* GetPrefSrcAddrPtr() const {
+    return mHasPrefSrcAddr ? &mPrefSrcAddr : nullptr;
+  }
+
+  bool Equals(const NetlinkRoute* aOther) const {
+    size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+                                                   : sizeof(mDstAddr.addr6);
+    if (memcmp(&mRtm, &(aOther->mRtm), sizeof(mRtm))) {
+      return false;
+    }
+    if (mHasOif != aOther->mHasOif || mOif != aOther->mOif) {
+      return false;
+    }
+    if ((mHasGWAddr != aOther->mHasGWAddr) ||
+        (mHasGWAddr && memcmp(&mGWAddr, &(aOther->mGWAddr), addrSize))) {
+      return false;
+    }
+    if ((mHasDstAddr != aOther->mHasDstAddr) ||
+        (mHasDstAddr && memcmp(&mDstAddr, &(aOther->mDstAddr), addrSize))) {
+      return false;
+    }
+    if ((mHasPrefSrcAddr != aOther->mHasPrefSrcAddr) ||
+        (mHasPrefSrcAddr &&
+         memcmp(&mPrefSrcAddr, &(aOther->mPrefSrcAddr), addrSize))) {
+      return false;
+    }
+    return true;
+  }
+
+  bool GatewayEquals(const NetlinkNeighbor* aNeigh) const {
+    if (!mHasGWAddr) {
+      return false;
+    }
+    if (aNeigh->Family() != mRtm.rtm_family) {
+      return false;
+    }
+    size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+                                                   : sizeof(mGWAddr.addr6);
+    return memcmp(&mGWAddr, aNeigh->GetAddrPtr(), addrSize) == 0;
+  }
+
+  bool GatewayEquals(const NetlinkRoute* aRoute) const {
+    if (!mHasGWAddr || !aRoute->mHasGWAddr) {
+      return false;
+    }
+    if (mRtm.rtm_family != aRoute->mRtm.rtm_family) {
+      return false;
+    }
+    size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+                                                   : sizeof(mGWAddr.addr6);
+    return memcmp(&mGWAddr, &(aRoute->mGWAddr), addrSize) == 0;
+  }
+
+  bool PrefSrcAddrEquals(const NetlinkAddress* aAddress) const {
+    if (!mHasPrefSrcAddr) {
+      return false;
+    }
+    if (mRtm.rtm_family != aAddress->Family()) {
+      return false;
+    }
+    size_t addrSize = (mRtm.rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+                                                   : sizeof(mPrefSrcAddr.addr6);
+    return memcmp(&mPrefSrcAddr, aAddress->GetAddrPtr(), addrSize) == 0;
+  }
+
+#ifdef NL_DEBUG_LOG
+  void GetAsString(nsACString& _retval) const {
+    nsAutoCString addrStr;
+    _retval.Assign("table=");
+    _retval.AppendInt(mRtm.rtm_table);
+    _retval.Append(" type=");
+    _retval.AppendInt(mRtm.rtm_type);
+    if (mRtm.rtm_family == AF_INET) {
+      _retval.Append(" family=AF_INET dst=");
+      addrStr.Assign("0.0.0.0/");
+    } else {
+      _retval.Append(" family=AF_INET6 dst=");
+      addrStr.Assign("::/");
+    }
+    if (mHasDstAddr) {
+      GetAddrStr(&mDstAddr, mRtm.rtm_family, addrStr);
+      addrStr.Append("/");
+    }
+    _retval.Append(addrStr);
+    _retval.AppendInt(mRtm.rtm_dst_len);
+    if (mHasPrefSrcAddr) {
+      _retval.Append(" src=");
+      GetAddrStr(&mPrefSrcAddr, mRtm.rtm_family, addrStr);
+      _retval.Append(addrStr);
+    }
+    if (mHasGWAddr) {
+      _retval.Append(" via=");
+      GetAddrStr(&mGWAddr, mRtm.rtm_family, addrStr);
+      _retval.Append(addrStr);
+    }
+    if (mHasOif) {
+      _retval.Append(" oif=");
+      _retval.AppendInt(mOif);
+    }
+  }
+#endif
+
+  bool Init(struct nlmsghdr* aNlh) {
+    struct rtmsg* rtm;
+    struct rtattr* attr;
+    int len;
+
+    rtm = (rtmsg*)NLMSG_DATA(aNlh);
+    len = RTM_PAYLOAD(aNlh);
+
+    if (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6) {
+      return false;
+    }
+
+    for (attr = RTM_RTA(rtm); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
+      if (attr->rta_type == RTA_DST) {
+        memcpy(&mDstAddr, RTA_DATA(attr),
+               (rtm->rtm_family == AF_INET) ? sizeof(mDstAddr.addr4)
+                                            : sizeof(mDstAddr.addr6));
+        mHasDstAddr = true;
+      } else if (attr->rta_type == RTA_GATEWAY) {
+        memcpy(&mGWAddr, RTA_DATA(attr),
+               (rtm->rtm_family == AF_INET) ? sizeof(mGWAddr.addr4)
+                                            : sizeof(mGWAddr.addr6));
+        mHasGWAddr = true;
+      } else if (attr->rta_type == RTA_PREFSRC) {
+        memcpy(&mPrefSrcAddr, RTA_DATA(attr),
+               (rtm->rtm_family == AF_INET) ? sizeof(mPrefSrcAddr.addr4)
+                                            : sizeof(mPrefSrcAddr.addr6));
+        mHasPrefSrcAddr = true;
+      } else if (attr->rta_type == RTA_OIF) {
+        mOif = *(uint32_t*)RTA_DATA(attr);
+        mHasOif = true;
+      }
+    }
+
+    memcpy(&mRtm, (rtmsg*)NLMSG_DATA(aNlh), sizeof(mRtm));
+    return true;
+  }
+
+ private:
+  bool mHasGWAddr : 1;
+  bool mHasPrefSrcAddr : 1;
+  bool mHasDstAddr : 1;
+  bool mHasOif : 1;
+
+  in_common_addr mGWAddr;
+  in_common_addr mDstAddr;
+  in_common_addr mPrefSrcAddr;
+
+  uint32_t mOif;
+  struct rtmsg mRtm;
+};
+
+class NetlinkMsg {
+ public:
+  static uint8_t const kGenMsg = 1;
+  static uint8_t const kRtMsg = 2;
+
+  NetlinkMsg() : mIsPending(false) {}
+  virtual ~NetlinkMsg() = default;
+
+  virtual bool Send(int aFD) = 0;
+  virtual bool IsPending() { return mIsPending; }
+  virtual uint32_t SeqId() = 0;
+  virtual uint8_t Family() = 0;
+  virtual uint8_t MsgType() = 0;
+
+ protected:
+  bool SendRequest(int aFD, void* aRequest, uint32_t aRequestLength) {
+    MOZ_ASSERT(!mIsPending, "Request has been already sent!");
+
+    struct sockaddr_nl kernel;
+    memset(&kernel, 0, sizeof(kernel));
+    kernel.nl_family = AF_NETLINK;
+    kernel.nl_groups = 0;
+
+    struct iovec io;
+    memset(&io, 0, sizeof(io));
+    io.iov_base = aRequest;
+    io.iov_len = aRequestLength;
+
+    struct msghdr rtnl_msg;
+    memset(&rtnl_msg, 0, sizeof(rtnl_msg));
+    rtnl_msg.msg_iov = &io;
+    rtnl_msg.msg_iovlen = 1;
+    rtnl_msg.msg_name = &kernel;
+    rtnl_msg.msg_namelen = sizeof(kernel);
+
+    ssize_t rc = EINTR_RETRY(sendmsg(aFD, (struct msghdr*)&rtnl_msg, 0));
+    if (rc > 0 && (uint32_t)rc == aRequestLength) {
+      mIsPending = true;
+    }
+
+    return mIsPending;
+  }
+
+  bool mIsPending;
+};
+
+class NetlinkGenMsg : public NetlinkMsg {
+ public:
+  NetlinkGenMsg(uint16_t aMsgType, uint8_t aFamily, uint32_t aSeqId) {
+    memset(&mReq, 0, sizeof(mReq));
+
+    mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+    mReq.hdr.nlmsg_type = aMsgType;
+    mReq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+    mReq.hdr.nlmsg_seq = aSeqId;
+    mReq.hdr.nlmsg_pid = 0;
+
+    mReq.gen.rtgen_family = aFamily;
+  }
+
+  virtual bool Send(int aFD) {
+    return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+  }
+
+  virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+  virtual uint8_t Family() { return mReq.gen.rtgen_family; }
+  virtual uint8_t MsgType() { return kGenMsg; }
+
+ private:
+  struct {
+    struct nlmsghdr hdr;
+    struct rtgenmsg gen;
+  } mReq;
+};
+
+class NetlinkRtMsg : public NetlinkMsg {
+ public:
+  NetlinkRtMsg(uint8_t aFamily, void* aAddress, uint32_t aSeqId) {
+    MOZ_ASSERT(aFamily == AF_INET || aFamily == AF_INET6);
+
+    memset(&mReq, 0, sizeof(mReq));
+
+    mReq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+    mReq.hdr.nlmsg_type = RTM_GETROUTE;
+    mReq.hdr.nlmsg_flags = NLM_F_REQUEST;
+    mReq.hdr.nlmsg_seq = aSeqId;
+    mReq.hdr.nlmsg_pid = 0;
+
+    mReq.rtm.rtm_family = aFamily;
+    mReq.rtm.rtm_flags = 0;
+    mReq.rtm.rtm_dst_len = aFamily == AF_INET ? 32 : 128;
+
+    struct rtattr* rta;
+    rta = (struct rtattr*)(((char*)&mReq) + NLMSG_ALIGN(mReq.hdr.nlmsg_len));
+    rta->rta_type = RTA_DST;
+    size_t addrSize =
+        aFamily == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
+    rta->rta_len = RTA_LENGTH(addrSize);
+    memcpy(RTA_DATA(rta), aAddress, addrSize);
+    mReq.hdr.nlmsg_len = NLMSG_ALIGN(mReq.hdr.nlmsg_len) + RTA_LENGTH(addrSize);
+  }
+
+  virtual bool Send(int aFD) {
+    return SendRequest(aFD, &mReq, mReq.hdr.nlmsg_len);
+  }
+
+  virtual uint32_t SeqId() { return mReq.hdr.nlmsg_seq; }
+  virtual uint8_t Family() { return mReq.rtm.rtm_family; }
+  virtual uint8_t MsgType() { return kRtMsg; }
+
+ private:
+  struct {
+    struct nlmsghdr hdr;
+    struct rtmsg rtm;
+    unsigned char data[1024];
+  } mReq;
+};
+
+NS_IMPL_ISUPPORTS(NetlinkService, nsIRunnable)
+
+NetlinkService::NetlinkService()
+    : mMutex("NetlinkService::mMutex"),
+      mInitialScanFinished(false),
+      mDoRouteCheckIPv4(false),
+      mDoRouteCheckIPv6(false),
+      mMsgId(1),
+      mLinkUp(true),
+      mRecalculateNetworkId(false) {
+  mPid = getpid();
+  mShutdownPipe[0] = -1;
+  mShutdownPipe[1] = -1;
+}
+
+NetlinkService::~NetlinkService() {
+  MOZ_ASSERT(!mThread, "NetlinkService thread shutdown failed");
+
+  if (mShutdownPipe[0] != -1) {
+    EINTR_RETRY(close(mShutdownPipe[0]));
+  }
+  if (mShutdownPipe[1] != -1) {
+    EINTR_RETRY(close(mShutdownPipe[1]));
+  }
+}
+
+void NetlinkService::OnNetlinkMessage(int aNetlinkSocket) {
+  // The buffer size 4096 is a common page size, which is a recommended limit
+  // for netlink messages.
+  char buffer[4096];
+
+  struct sockaddr_nl kernel;
+  memset(&kernel, 0, sizeof(kernel));
+  kernel.nl_family = AF_NETLINK;
+  kernel.nl_groups = 0;
+
+  struct iovec io;
+  memset(&io, 0, sizeof(io));
+  io.iov_base = buffer;
+  io.iov_len = sizeof(buffer);
+
+  struct msghdr rtnl_reply;
+  memset(&rtnl_reply, 0, sizeof(rtnl_reply));
+  rtnl_reply.msg_iov = &io;
+  rtnl_reply.msg_iovlen = 1;
+  rtnl_reply.msg_name = &kernel;
+  rtnl_reply.msg_namelen = sizeof(kernel);
+
+  ssize_t rc = EINTR_RETRY(recvmsg(aNetlinkSocket, &rtnl_reply, MSG_DONTWAIT));
+  if (rc < 0) {
+    return;
+  }
+  size_t netlink_bytes = rc;
+
+  struct nlmsghdr* nlh = reinterpret_cast<struct nlmsghdr*>(buffer);
+
+  for (; NLMSG_OK(nlh, netlink_bytes); nlh = NLMSG_NEXT(nlh, netlink_bytes)) {
+    // If PID in the message is our PID, then it's a response to our request.
+    // Otherwise it's a multicast message.
+    bool isResponse = (pid_t)nlh->nlmsg_pid == mPid;
+    if (isResponse) {
+      if (!mOutgoingMessages.Length() || !mOutgoingMessages[0]->IsPending()) {
+        // There is no enqueued message pending?
+        LOG((
+            "Ignoring message seq_id %u, because there is no associated message"
+            " pending",
+            nlh->nlmsg_seq));
+        continue;
+      }
+
+      if (mOutgoingMessages[0]->SeqId() != nlh->nlmsg_seq) {
+        LOG(("Received unexpected seq_id [received=%u, expected=%u]",
+             nlh->nlmsg_seq, mOutgoingMessages[0]->SeqId()));
+        RemovePendingMsg();
+        continue;
+      }
+    }
+
+    switch (nlh->nlmsg_type) {
+      case NLMSG_DONE: /* Message signalling end of dump for responses to
+                          request containing NLM_F_DUMP flag */
+        MOZ_ASSERT(
+            isResponse);  // Could broadcasted message be reply to NLM_F_DUMP?
+        if (isResponse) {
+          RemovePendingMsg();
+        }
+        break;
+      case NLMSG_ERROR:
+        if (isResponse) {
+          if (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg) {
+            OnRouteCheckResult(nullptr);
+          }
+          RemovePendingMsg();
+        }
+        break;
+      case RTM_NEWLINK:
+      case RTM_DELLINK:
+        MOZ_ASSERT(!isResponse ||
+                   (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+        OnLinkMessage(nlh);
+        break;
+      case RTM_NEWADDR:
+      case RTM_DELADDR:
+        MOZ_ASSERT(!isResponse ||
+                   (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+        OnAddrMessage(nlh);
+        break;
+      case RTM_NEWROUTE:
+      case RTM_DELROUTE:
+        if (isResponse && ((nlh->nlmsg_flags & NLM_F_MULTI) != NLM_F_MULTI)) {
+          // If it's not multipart message, then it must be response to a route
+          // check.
+          MOZ_ASSERT(mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+          OnRouteCheckResult(nlh);
+          RemovePendingMsg();
+        } else {
+          OnRouteMessage(nlh);
+        }
+        break;
+      case RTM_NEWNEIGH:
+      case RTM_DELNEIGH:
+        MOZ_ASSERT(!isResponse ||
+                   (nlh->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI);
+        OnNeighborMessage(nlh);
+        break;
+      default:
+        break;
+    }
+  }
+}
+
+void NetlinkService::OnLinkMessage(struct nlmsghdr* aNlh) {
+  LOG(("NetlinkService::OnLinkMessage"));
+  nsAutoPtr<NetlinkLink> link(new NetlinkLink());
+  if (!link->Init(aNlh)) {
+    return;
+  }
+
+  uint32_t linkIndex = link->GetIndex();
+  nsAutoCString linkName;
+  link->GetName(linkName);
+  if (aNlh->nlmsg_type == RTM_NEWLINK) {
+    LOG(("Adding new link [index=%u, name=%s]", linkIndex, linkName.get()));
+    mLinks.Put(linkIndex, link.forget());
+  } else {
+    LOG(("Removing link [index=%u, name=%s]", linkIndex, linkName.get()));
+    mLinks.Remove(linkIndex);
+  }
+
+  CheckLinks();
+}
+
+void NetlinkService::CheckLinks() {
+  if (!mInitialScanFinished) {
+    // Wait until we get all links via netlink
+    return;
+  }
+
+  bool newLinkUp = false;
+  for (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) {
+    if (iter.Data()->IsUp()) {
+      newLinkUp = true;
+      break;
+    }
+  }
+
+  if (mLinkUp != newLinkUp) {
+    RefPtr<NetlinkServiceListener> listener;
+    {
+      MutexAutoLock lock(mMutex);
+      listener = mListener;
+      mLinkUp = newLinkUp;
+    }
+    if (mLinkUp) {
+      if (listener) {
+        listener->OnLinkUp();
+      }
+    } else {
+      if (listener) {
+        listener->OnLinkDown();
+      }
+    }
+  }
+}
+
+void NetlinkService::OnAddrMessage(struct nlmsghdr* aNlh) {
+  LOG(("NetlinkService::OnAddrMessage"));
+  nsAutoPtr<NetlinkAddress> address(new NetlinkAddress());
+  if (!address->Init(aNlh)) {
+    return;
+  }
+
+  uint32_t ifIdx = address->GetIndex();
+
+  nsAutoCString addrStr;
+  GetAddrStr(address->GetAddrPtr(), address->Family(), addrStr);
+
+  // There might be already an equal address in the array even in case of
+  // RTM_NEWADDR message, e.g. when lifetime of IPv6 address is renewed. Remove
+  // existing equal address in case of RTM_DELADDR as well as RTM_NEWADDR
+  // message and add a new one in the latter case.
+  for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+    if (mAddresses[i]->Equals(address)) {
+      LOG(("Removing address [ifidx=%u, addr=%s/%u]", mAddresses[i]->GetIndex(),
+           addrStr.get(), mAddresses[i]->GetPrefixLen()));
+      mAddresses.RemoveElementAt(i);
+      break;
+    }
+  }
+
+  if (aNlh->nlmsg_type == RTM_NEWADDR) {
+    LOG(("Adding address [ifidx=%u, addr=%s/%u]", address->GetIndex(),
+         addrStr.get(), address->GetPrefixLen()));
+    mAddresses.AppendElement(address.forget());
+  }
+
+  NetlinkLink* link;
+  if (mLinks.Get(ifIdx, &link)) {
+    if (link->IsUp()) {
+      // Address changed on a link that is up. This might change network ID.
+      TriggerNetworkIDCalculation();
+    }
+  }
+}
+
+void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) {
+  LOG(("NetlinkService::OnRouteMessage"));
+  nsAutoPtr<NetlinkRoute> route(new NetlinkRoute());
+  if (!route->Init(aNlh)) {
+    return;
+  }
+
+#ifdef NL_DEBUG_LOG
+  nsAutoCString routeDbgStr;
+  route->GetAsString(routeDbgStr);
+#endif
+
+  if (!route->IsUnicast()) {
+    // Use only unicast routes
+#ifdef NL_DEBUG_LOG
+    LOG(("Ignoring non-unicast route: %s", routeDbgStr.get()));
+#else
+    LOG(("Ignoring non-unicast route"));
+#endif
+    return;
+  }
+
+  // Adding/removing any unicast route might change network ID
+  TriggerNetworkIDCalculation();
+
+  if (!route->IsDefault()) {
+    // Store only default routes
+#ifdef NL_DEBUG_LOG
+    LOG(("Not storing non-unicast route: %s", routeDbgStr.get()));
+#else
+    LOG(("Not storing non-unicast route"));
+#endif
+    return;
+  }
+
+  nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
+  if (route->Family() == AF_INET) {
+    routesPtr = &mIPv4Routes;
+  } else {
+    routesPtr = &mIPv6Routes;
+  }
+
+  for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) {
+    if ((*routesPtr)[i]->Equals(route)) {
+      // We shouldn't find equal route when adding a new one, but just in case
+      // it can happen remove the old one to avoid duplicities.
+#ifdef NL_DEBUG_LOG
+      LOG(("Removing default route: %s", routeDbgStr.get()));
+#else
+      LOG(("Removing default route"));
+#endif
+      (*routesPtr).RemoveElementAt(i);
+      break;
+    }
+  }
+
+  if (aNlh->nlmsg_type == RTM_NEWROUTE) {
+#ifdef NL_DEBUG_LOG
+    LOG(("Adding default route: %s", routeDbgStr.get()));
+#else
+    LOG(("Adding default route"));
+#endif
+    (*routesPtr).AppendElement(route.forget());
+  }
+}
+
+void NetlinkService::OnNeighborMessage(struct nlmsghdr* aNlh) {
+  LOG(("NetlinkService::OnNeighborMessage"));
+  nsAutoPtr<NetlinkNeighbor> neigh(new NetlinkNeighbor());
+  if (!neigh->Init(aNlh)) {
+    return;
+  }
+
+  nsAutoCString addrStr;
+  GetAddrStr(neigh->GetAddrPtr(), neigh->Family(), addrStr);
+
+  nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
+  nsAutoPtr<NetlinkRoute>* routeCheckResultPtr;
+  if (neigh->Family() == AF_INET) {
+    routesPtr = &mIPv4Routes;
+    routeCheckResultPtr = &mIPv4RouteCheckResult;
+  } else {
+    routesPtr = &mIPv6Routes;
+    routeCheckResultPtr = &mIPv6RouteCheckResult;
+  }
+
+  if (aNlh->nlmsg_type == RTM_NEWNEIGH) {
+    if (!mRecalculateNetworkId && neigh->HasMAC()) {
+      NetlinkNeighbor* oldNeigh = nullptr;
+      mNeighbors.Get(addrStr, &oldNeigh);
+
+      if (!oldNeigh || !oldNeigh->HasMAC()) {
+        // The MAC address was added, if it's a host from some of the saved
+        // routing tables we should recalculate network ID
+        for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) {
+          if ((*routesPtr)[i]->GatewayEquals(neigh)) {
+            TriggerNetworkIDCalculation();
+            break;
+          }
+        }
+        if (!mRecalculateNetworkId && (*routeCheckResultPtr) &&
+            (*routeCheckResultPtr)->GatewayEquals(neigh)) {
+          TriggerNetworkIDCalculation();
+        }
+      }
+    }
+
+#ifdef NL_DEBUG_LOG
+    nsAutoCString neighDbgStr;
+    neigh->GetAsString(neighDbgStr);
+    LOG(("Adding neighbor: %s", neighDbgStr.get()));
+#else
+    LOG(("Adding neighbor %s", addrStr.get()));
+#endif
+    mNeighbors.Put(addrStr, neigh.forget());
+  } else {
+#ifdef NL_DEBUG_LOG
+    LOG(("Removing neighbor %s", addrStr.get()));
+#endif
+    mNeighbors.Remove(addrStr);
+  }
+}
+
+void NetlinkService::OnRouteCheckResult(struct nlmsghdr* aNlh) {
+  LOG(("NetlinkService::OnRouteCheckResult"));
+  nsAutoPtr<NetlinkRoute> route;
+
+  if (aNlh) {
+    route = new NetlinkRoute();
+    if (!route->Init(aNlh)) {
+      route = nullptr;
+    } else if (!route->IsUnicast()) {
+#ifdef NL_DEBUG_LOG
+      nsAutoCString routeDbgStr;
+      route->GetAsString(routeDbgStr);
+      LOG(("Ignoring non-unicast route: %s", routeDbgStr.get()));
+#else
+      LOG(("Ignoring non-unicast route"));
+#endif
+      route = nullptr;
+    }
+  }
+
+  nsAutoPtr<NetlinkRoute>* routeCheckResultPtr;
+  if (mOutgoingMessages[0]->Family() == AF_INET) {
+    routeCheckResultPtr = &mIPv4RouteCheckResult;
+  } else {
+    routeCheckResultPtr = &mIPv6RouteCheckResult;
+  }
+
+  if (route) {
+#ifdef NL_DEBUG_LOG
+    nsAutoCString routeDbgStr;
+    route->GetAsString(routeDbgStr);
+    LOG(("Storing route: %s", routeDbgStr.get()));
+#else
+    LOG(("Storing result for the check"));
+#endif
+  } else {
+    LOG(("Clearing result for the checkd"));
+  }
+
+  (*routeCheckResultPtr) = route.forget();
+}
+
+void NetlinkService::EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily) {
+  NetlinkGenMsg* msg = new NetlinkGenMsg(aMsgType, aFamily, ++mMsgId);
+  mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::EnqueueRtMsg(uint8_t aFamily, void* aAddress) {
+  NetlinkRtMsg* msg = new NetlinkRtMsg(aFamily, aAddress, ++mMsgId);
+  mOutgoingMessages.AppendElement(msg);
+}
+
+void NetlinkService::RemovePendingMsg() {
+  MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+
+  DebugOnly<bool> isRtMessage =
+      (mOutgoingMessages[0]->MsgType() == NetlinkMsg::kRtMsg);
+
+  mOutgoingMessages.RemoveElementAt(0);
+  if (!mOutgoingMessages.Length()) {
+    if (!mInitialScanFinished) {
+      // Now we've received all initial data from the kernel. Perform a link
+      // check and trigger network ID calculation even if it wasn't triggered
+      // by the incoming messages.
+      mInitialScanFinished = true;
+
+      CheckLinks();
+      TriggerNetworkIDCalculation();
+
+      // Link status should be known by now.
+      RefPtr<NetlinkServiceListener> listener;
+      {
+        MutexAutoLock lock(mMutex);
+        listener = mListener;
+      }
+      if (listener) {
+        listener->OnLinkStatusKnown();
+      }
+    } else {
+      // We've received last response for route check, calculate ID now
+      MOZ_ASSERT(isRtMessage);
+      CalculateNetworkID();
+    }
+  }
+}
+
+NS_IMETHODIMP
+NetlinkService::Run() {
+  int netlinkSocket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (netlinkSocket < 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  struct sockaddr_nl addr;
+  memset(&addr, 0, sizeof(addr));
+
+  addr.nl_family = AF_NETLINK;
+  addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK |
+                   RTMGRP_NEIGH | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
+
+  if (bind(netlinkSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+    // failure!
+    EINTR_RETRY(close(netlinkSocket));
+    return NS_ERROR_FAILURE;
+  }
+
+  struct pollfd fds[2];
+  fds[0].fd = mShutdownPipe[0];
+  fds[0].events = POLLIN;
+  fds[0].revents = 0;
+
+  fds[1].fd = netlinkSocket;
+  fds[1].events = POLLIN;
+  fds[1].revents = 0;
+
+  // send all requests to get initial network information
+  EnqueueGenMsg(RTM_GETLINK, AF_PACKET);
+  EnqueueGenMsg(RTM_GETNEIGH, AF_INET);
+  EnqueueGenMsg(RTM_GETNEIGH, AF_INET6);
+  EnqueueGenMsg(RTM_GETADDR, AF_PACKET);
+  EnqueueGenMsg(RTM_GETROUTE, AF_PACKET);
+
+  nsresult rv = NS_OK;
+  bool shutdown = false;
+  while (!shutdown) {
+    if (mOutgoingMessages.Length() && !mOutgoingMessages[0]->IsPending()) {
+      if (!mOutgoingMessages[0]->Send(netlinkSocket)) {
+        LOG(("Failed to send netlink message"));
+        mOutgoingMessages.RemoveElementAt(0);
+        // try to send another message if available before polling
+        continue;
+      }
+    }
+
+    int rc = EINTR_RETRY(poll(fds, 2, GetPollWait()));
+
+    if (rc > 0) {
+      if (fds[0].revents & POLLIN) {
+        // shutdown, abort the loop!
+        LOG(("thread shutdown received, dying...\n"));
+        shutdown = true;
+      } else if (fds[1].revents & POLLIN) {
+        LOG(("netlink message received, handling it...\n"));
+        OnNetlinkMessage(netlinkSocket);
+      }
+    } else if (rc < 0) {
+      rv = NS_ERROR_FAILURE;
+      break;
+    }
+  }
+
+  EINTR_RETRY(close(netlinkSocket));
+
+  return rv;
+}
+
+nsresult NetlinkService::Init(NetlinkServiceListener* aListener) {
+  nsresult rv;
+
+  mListener = aListener;
+
+  nsAutoCString routecheckIP;
+
+  rv =
+      Preferences::GetCString("network.netlink.route.check.IPv4", routecheckIP);
+  if (NS_SUCCEEDED(rv)) {
+    if (inet_pton(AF_INET, routecheckIP.get(), &mRouteCheckIPv4) == 1) {
+      mDoRouteCheckIPv4 = true;
+    }
+  }
+
+  rv =
+      Preferences::GetCString("network.netlink.route.check.IPv6", routecheckIP);
+  if (NS_SUCCEEDED(rv)) {
+    if (inet_pton(AF_INET6, routecheckIP.get(), &mRouteCheckIPv6) == 1) {
+      mDoRouteCheckIPv6 = true;
+    }
+  }
+
+  if (pipe(mShutdownPipe) == -1) {
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = NS_NewNamedThread("Netlink Monitor", getter_AddRefs(mThread), this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult NetlinkService::Shutdown() {
+  LOG(("write() to signal thread shutdown\n"));
+
+  {
+    MutexAutoLock lock(mMutex);
+    mListener = nullptr;
+  }
+
+  // awake the thread to make it terminate
+  ssize_t rc = EINTR_RETRY(write(mShutdownPipe[1], "1", 1));
+  LOG(("write() returned %d, errno == %d\n", (int)rc, errno));
+
+  nsresult rv = mThread->Shutdown();
+
+  // Have to break the cycle here, otherwise NetlinkService holds
+  // onto the thread and the thread holds onto the NetlinkService
+  // via its mRunnable
+  mThread = nullptr;
+
+  return rv;
+}
+
+/*
+ * A network event that might change network ID has been registered. Delay
+ * network ID calculation and sending of the event in case it changed for
+ * a while. Absorbing potential subsequent events increases chance of successful
+ * network ID calculation (e.g. MAC address of the router might be discovered in
+ * the meantime)
+ */
+void NetlinkService::TriggerNetworkIDCalculation() {
+  LOG(("NetlinkService::TriggerNetworkIDCalculation"));
+
+  if (mRecalculateNetworkId) {
+    return;
+  }
+
+  mRecalculateNetworkId = true;
+  mTriggerTime = TimeStamp::Now();
+}
+
+int NetlinkService::GetPollWait() {
+  if (!mRecalculateNetworkId) {
+    return -1;
+  }
+
+  if (mOutgoingMessages.Length()) {
+    MOZ_ASSERT(mOutgoingMessages[0]->IsPending());
+    // Message is pending, we don't have to set timeout because we'll receive
+    // reply from kernel ASAP
+    return -1;
+  }
+
+  MOZ_ASSERT(mInitialScanFinished);
+
+  double period = (TimeStamp::Now() - mTriggerTime).ToMilliseconds();
+  if (period >= kNetworkChangeCoalescingPeriod) {
+    // Coalescing time has elapsed, do route check
+    if (!mDoRouteCheckIPv4 && !mDoRouteCheckIPv6) {
+      // If route checking is disabled for whatever reason, calculate ID now
+      CalculateNetworkID();
+      return -1;
+    }
+
+    // Otherwise send route check messages and calculate network ID after the
+    // response is received
+    if (mDoRouteCheckIPv4) {
+      EnqueueRtMsg(AF_INET, &mRouteCheckIPv4);
+    }
+    if (mDoRouteCheckIPv6) {
+      EnqueueRtMsg(AF_INET6, &mRouteCheckIPv6);
+    }
+
+    // Return 0 to make sure we start sending enqueued messages immediately
+    return 0;
+  }
+
+  return static_cast<int>(kNetworkChangeCoalescingPeriod - period);
+}
+
+class NeighborComparator {
+ public:
+  bool Equals(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+    return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) == 0);
+  }
+  bool LessThan(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
+    return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) < 0);
+  }
+};
+
+bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
+  LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
+       aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
+
+  bool retval = false;
+
+  nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
+  nsAutoPtr<NetlinkRoute>* routeCheckResultPtr;
+  if (aFamily == AF_INET) {
+    routesPtr = &mIPv4Routes;
+    routeCheckResultPtr = &mIPv4RouteCheckResult;
+  } else {
+    routesPtr = &mIPv6Routes;
+    routeCheckResultPtr = &mIPv6RouteCheckResult;
+  }
+
+  // All known GW neighbors
+  nsTArray<NetlinkNeighbor*> gwNeighbors;
+
+  // Check all default routes and try to get MAC of the gateway
+  for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) {
+#ifdef NL_DEBUG_LOG
+    nsAutoCString routeDbgStr;
+    (*routesPtr)[i]->GetAsString(routeDbgStr);
+    LOG(("Checking default route: %s", routeDbgStr.get()));
+#endif
+    nsAutoCString addrStr;
+    const in_common_addr* addrPtr = (*routesPtr)[i]->GetGWAddrPtr();
+    if (!addrPtr) {
+      LOG(("There is no GW address in default route."));
+      continue;
+    }
+
+    GetAddrStr(addrPtr, (*routesPtr)[i]->Family(), addrStr);
+    NetlinkNeighbor* neigh = nullptr;
+    if (!mNeighbors.Get(addrStr, &neigh)) {
+      LOG(("Neighbor %s not found in hashtable.", addrStr.get()));
+      continue;
+    }
+
+    if (!neigh->HasMAC()) {
+      // We don't know MAC address
+      LOG(("We have no MAC for neighbor %s.", addrStr.get()));
+      continue;
+    }
+
+    if (gwNeighbors.IndexOf(neigh) != gwNeighbors.NoIndex) {
+      // avoid host duplicities
+      LOG(("Neighbor %s is already selected for hashing.", addrStr.get()));
+      continue;
+    }
+
+    LOG(("Neighbor %s will be used for network ID.", addrStr.get()));
+    gwNeighbors.AppendElement(neigh);
+  }
+
+  // Sort them so we always have the same network ID on the same network
+  gwNeighbors.Sort(NeighborComparator());
+
+  for (uint32_t i = 0; i < gwNeighbors.Length(); ++i) {
+#ifdef NL_DEBUG_LOG
+    nsAutoCString neighDbgStr;
+    gwNeighbors[i]->GetAsString(neighDbgStr);
+    LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+#endif
+    aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN);
+    retval = true;
+  }
+
+  if (!*routeCheckResultPtr) {
+    LOG(("There is no route check result."));
+    return retval;
+  }
+
+  // Check whether we know next hop for mRouteCheckIPv4/6 host
+  const in_common_addr* addrPtr = (*routeCheckResultPtr)->GetGWAddrPtr();
+  if (addrPtr) {
+    // If we know MAC address of the next hop for mRouteCheckIPv4/6 host, hash
+    // it even if it's MAC of some of the default routes we've checked above.
+    // This ensures that if we have 2 different default routes and next hop for
+    // mRouteCheckIPv4/6 changes from one default route to the other, we'll
+    // detect it as a network change.
+    nsAutoCString addrStr;
+    GetAddrStr(addrPtr, (*routeCheckResultPtr)->Family(), addrStr);
+    LOG(("Next hop for the checked host is %s.", addrStr.get()));
+
+    NetlinkNeighbor* neigh = nullptr;
+    if (!mNeighbors.Get(addrStr, &neigh)) {
+      LOG(("Neighbor %s not found in hashtable.", addrStr.get()));
+      return retval;
+    }
+
+    if (!neigh->HasMAC()) {
+      LOG(("We have no MAC for neighbor %s.", addrStr.get()));
+      return retval;
+    }
+
+#ifdef NL_DEBUG_LOG
+    nsAutoCString neighDbgStr;
+    neigh->GetAsString(neighDbgStr);
+    LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
+#else
+    LOG(("Hashing MAC address of neighbor %s", addrStr.get()));
+#endif
+    aSHA1->update(neigh->GetMACPtr(), ETH_ALEN);
+    retval = true;
+  } else if ((*routeCheckResultPtr)->HasOif()) {
+    // The traffic is routed directly via an interface. It's likely VPN tun
+    // device. Probably the best we can do is to hash name of the interface
+    // (e.g. "tun1") and network address. Using host address would cause that
+    // network ID would be different every time the VPN give us a different IP
+    // address.
+    nsAutoCString linkName;
+    NetlinkLink* link = nullptr;
+    uint32_t ifIdx = (*routeCheckResultPtr)->Oif();
+    if (!mLinks.Get(ifIdx, &link)) {
+      LOG(("Cannot find link with index %u ??", ifIdx));
+      return retval;
+    }
+    link->GetName(linkName);
+
+    bool hasSrcAddr = (*routeCheckResultPtr)->HasPrefSrcAddr();
+    if (!hasSrcAddr) {
+      LOG(("There is no preferred source address."));
+    }
+
+    NetlinkAddress* linkAddress = nullptr;
+    // Find network address of the interface matching the source address. In
+    // theory there could be multiple addresses with different prefix length.
+    // Get the one with smallest prefix length.
+    for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+      if (mAddresses[i]->GetIndex() != ifIdx) {
+        continue;
+      }
+      if (!hasSrcAddr) {
+        // there is no preferred src, match just the family
+        if (mAddresses[i]->Family() != aFamily) {
+          continue;
+        }
+      } else if (!(*routeCheckResultPtr)->PrefSrcAddrEquals(mAddresses[i])) {
+        continue;
+      }
+
+      if (!linkAddress ||
+          linkAddress->GetPrefixLen() > mAddresses[i]->GetPrefixLen()) {
+        // We have no address yet or this one has smaller prefix length, use it.
+        linkAddress = mAddresses[i];
+      }
+    }
+
+    if (!linkAddress) {
+      // There is no address in our array?
+      nsAutoCString dbgStr;
+#ifdef NL_DEBUG_LOG
+      (*routeCheckResultPtr)->GetAsString(dbgStr);
+      LOG(("No address found for preferred source address in route: %s",
+           dbgStr.get()));
+#else
+      GetAddrStr((*routeCheckResultPtr)->GetPrefSrcAddrPtr(), aFamily, dbgStr);
+      LOG(("No address found for preferred source address %s", dbgStr.get()));
+#endif
+      return retval;
+    }
+
+    in_common_addr prefix;
+    int32_t prefixSize = (aFamily == AF_INET) ? (int32_t)sizeof(prefix.addr4)
+                                              : (int32_t)sizeof(prefix.addr6);
+    memcpy(&prefix, linkAddress->GetAddrPtr(), prefixSize);
+    uint8_t maskit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+    int32_t bits = linkAddress->GetPrefixLen();
+    if (bits > prefixSize * 8) {
+      MOZ_ASSERT(false, "Unexpected prefix length!");
+      LOG(("Unexpected prefix length %d, maximum for this family is %d", bits,
+           prefixSize * 8));
+      return retval;
+    }
+    for (int32_t i = 0; i < prefixSize; i++) {
+      uint8_t mask = (bits >= 8) ? 0xff : maskit[bits];
+      ((unsigned char*)&prefix)[i] &= mask;
+      bits -= 8;
+      if (bits <= 0) {
+        bits = 0;
+      }
+    }
+
+    nsAutoCString addrStr;
+    GetAddrStr(&prefix, aFamily, addrStr);
+    LOG(("Hashing link name %s and network address %s/%u", linkName.get(),
+         addrStr.get(), linkAddress->GetPrefixLen()));
+    aSHA1->update(linkName.BeginReading(), linkName.Length());
+    aSHA1->update(&prefix, prefixSize);
+    aSHA1->update(&bits, sizeof(bits));
+    retval = true;
+  } else {
+    // This is strange, there is neither next hop nor output interface.
+#ifdef NL_DEBUG_LOG
+    nsAutoCString routeDbgStr;
+    (*routeCheckResultPtr)->GetAsString(routeDbgStr);
+    LOG(("Neither GW address nor output interface found in route: %s",
+         routeDbgStr.get()));
+#else
+    LOG(("Neither GW address nor output interface found in route"));
+#endif
+  }
+
+  return retval;
+}
+
+// Figure out the "network identification".
+void NetlinkService::CalculateNetworkID() {
+  MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+  MOZ_ASSERT(mRecalculateNetworkId);
+
+  mRecalculateNetworkId = false;
+
+  SHA1Sum sha1;
+
+  bool idChanged = false;
+  bool found4 = CalculateIDForFamily(AF_INET, &sha1);
+  bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
+
+  if (found4 || found6) {
+    // This 'addition' could potentially be a fixed number from the
+    // profile or something.
+    nsAutoCString addition("local-rubbish");
+    nsAutoCString output;
+    sha1.update(addition.get(), addition.Length());
+    uint8_t digest[SHA1Sum::kHashSize];
+    sha1.finish(digest);
+    nsAutoCString newString(reinterpret_cast<char*>(digest),
+                            SHA1Sum::kHashSize);
+    nsresult rv = Base64Encode(newString, output);
+    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+    LOG(("networkid: id %s\n", output.get()));
+    MutexAutoLock lock(mMutex);
+    if (mNetworkId != output) {
+      // new id
+      if (found4 && !found6) {
+        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 1);  // IPv4 only
+      } else if (!found4 && found6) {
+        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 3);  // IPv6 only
+      } else {
+        Telemetry::Accumulate(Telemetry::NETWORK_ID2, 4);  // Both!
+      }
+      mNetworkId = output;
+      idChanged = true;
+    } else {
+      // same id
+      LOG(("Same network id"));
+      Telemetry::Accumulate(Telemetry::NETWORK_ID2, 2);
+    }
+  } else {
+    // no id
+    LOG(("No network id"));
+    MutexAutoLock lock(mMutex);
+    if (!mNetworkId.IsEmpty()) {
+      mNetworkId.Truncate();
+      idChanged = true;
+      Telemetry::Accumulate(Telemetry::NETWORK_ID2, 0);
+    }
+  }
+
+  // If this is first time we calculate network ID, don't report it as a network
+  // change. We've started with an empty ID and we've just calculated the
+  // correct ID. The network hasn't really changed.
+  static bool initialIDCalculation = true;
+
+  if (idChanged && !initialIDCalculation) {
+    RefPtr<NetlinkServiceListener> listener;
+    {
+      MutexAutoLock lock(mMutex);
+      listener = mListener;
+    }
+    if (listener) {
+      listener->OnNetworkChanged();
+    }
+  }
+
+  initialIDCalculation = false;
+}
+
+void NetlinkService::GetNetworkID(nsACString& aNetworkID) {
+  MutexAutoLock lock(mMutex);
+  aNetworkID = mNetworkId;
+}
+
+void NetlinkService::GetIsLinkUp(bool* aIsUp) {
+  MutexAutoLock lock(mMutex);
+  *aIsUp = mLinkUp;
+}
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et sw=2 ts=4: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NETLINKSERVICE_H_
+#define NETLINKSERVICE_H_
+
+#include <netinet/in.h>
+#include <linux/netlink.h>
+
+#include "nsINetworkLinkService.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsITimer.h"
+#include "nsClassHashtable.h"
+#include "mozilla/SHA1.h"
+
+namespace mozilla {
+namespace net {
+
+#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
+#  define NL_DEBUG_LOG
+#endif
+
+class NetlinkAddress;
+class NetlinkNeighbor;
+class NetlinkLink;
+class NetlinkRoute;
+class NetlinkMsg;
+
+class NetlinkServiceListener : public nsISupports {
+ public:
+  virtual void OnNetworkChanged() = 0;
+  virtual void OnLinkUp() = 0;
+  virtual void OnLinkDown() = 0;
+  virtual void OnLinkStatusKnown() = 0;
+
+ protected:
+  virtual ~NetlinkServiceListener() = default;
+};
+
+class NetlinkService : public nsIRunnable {
+  virtual ~NetlinkService();
+
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+
+  NetlinkService();
+  nsresult Init(NetlinkServiceListener* aListener);
+  nsresult Shutdown();
+  void GetNetworkID(nsACString& aNetworkID);
+  void GetIsLinkUp(bool* aIsUp);
+
+ private:
+  void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily);
+  void EnqueueRtMsg(uint8_t aFamily, void* aAddress);
+  void RemovePendingMsg();
+
+  mozilla::Mutex mMutex;
+
+  void OnNetlinkMessage(int aNetlinkSocket);
+  void OnLinkMessage(struct nlmsghdr* aNlh);
+  void OnAddrMessage(struct nlmsghdr* aNlh);
+  void OnRouteMessage(struct nlmsghdr* aNlh);
+  void OnNeighborMessage(struct nlmsghdr* aNlh);
+  void OnRouteCheckResult(struct nlmsghdr* aNlh);
+
+  void CheckLinks();
+
+  void TriggerNetworkIDCalculation();
+  int GetPollWait();
+  bool CalculateIDForFamily(uint8_t aFamily, mozilla::SHA1Sum* aSHA1);
+  void CalculateNetworkID();
+
+  nsCOMPtr<nsIThread> mThread;
+
+  bool mInitialScanFinished;
+
+  // A pipe to signal shutdown with.
+  int mShutdownPipe[2];
+
+  // Is true if preference network.netlink.route.check.IPv4 was successfully
+  // parsed and stored to mRouteCheckIPv4
+  bool mDoRouteCheckIPv4;
+  struct in_addr mRouteCheckIPv4;
+
+  // Is true if preference network.netlink.route.check.IPv6 was successfully
+  // parsed and stored to mRouteCheckIPv6
+  bool mDoRouteCheckIPv6;
+  struct in6_addr mRouteCheckIPv6;
+
+  pid_t mPid;
+  uint32_t mMsgId;
+
+  bool mLinkUp;
+
+  // Flag indicating that network ID could change and should be recalculated.
+  // Calculation is postponed until we receive responses to all enqueued
+  // messages.
+  bool mRecalculateNetworkId;
+
+  // Time stamp of setting mRecalculateNetworkId to true
+  mozilla::TimeStamp mTriggerTime;
+
+  nsCString mNetworkId;
+
+  // All IPv4 and IPv6 addresses received via netlink
+  nsTArray<nsAutoPtr<NetlinkAddress> > mAddresses;
+  // All neighbors, key is an address
+  nsClassHashtable<nsCStringHashKey, NetlinkNeighbor> mNeighbors;
+  // All interfaces keyed by interface index
+  nsClassHashtable<nsUint32HashKey, NetlinkLink> mLinks;
+  // Default IPv4 routes
+  nsTArray<nsAutoPtr<NetlinkRoute> > mIPv4Routes;
+  // Default IPv6 routes
+  nsTArray<nsAutoPtr<NetlinkRoute> > mIPv6Routes;
+
+  // Route for mRouteCheckIPv4 address
+  nsAutoPtr<NetlinkRoute> mIPv4RouteCheckResult;
+  // Route for mRouteCheckIPv6 address
+  nsAutoPtr<NetlinkRoute> mIPv6RouteCheckResult;
+
+  nsTArray<nsAutoPtr<NetlinkMsg> > mOutgoingMessages;
+
+  RefPtr<NetlinkServiceListener> mListener;
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif /* NETLINKSERVICE_H_ */
new file mode 100644
--- /dev/null
+++ b/netwerk/system/netlink/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_ARCH'] == 'Linux':
+    SOURCES += [
+        'NetlinkService.cpp',
+    ]
+
+FINAL_LIBRARY = 'xul'
--- a/netwerk/system/win32/nsNotifyAddrListener.cpp
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -29,17 +29,17 @@
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsNotifyAddrListener.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Services.h"
 #include "nsCRT.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/Base64.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Telemetry.h"
 #include <iptypes.h>
 #include <iphlpapi.h>
 
 using namespace mozilla;
@@ -110,18 +110,16 @@ NS_IMPL_ISUPPORTS(nsNotifyAddrListener, 
 nsNotifyAddrListener::nsNotifyAddrListener()
     : mLinkUp(true),  // assume true by default
       mStatusKnown(false),
       mCheckAttempted(false),
       mMutex("nsNotifyAddrListener::mMutex"),
       mCheckEvent(nullptr),
       mShutdown(false),
       mIPInterfaceChecksum(0),
-      mAllowChangedEvent(true),
-      mIPv6Changes(false),
       mCoalescingActive(false) {
   InitIphlpapi();
 }
 
 nsNotifyAddrListener::~nsNotifyAddrListener() {
   NS_ASSERTION(!mThread, "nsNotifyAddrListener thread shutdown failed");
   FreeDynamicLibraries();
 }
@@ -271,17 +269,18 @@ nsNotifyAddrListener::nextCoalesceWaitTi
 NS_IMETHODIMP
 nsNotifyAddrListener::Run() {
   mStartTime = TimeStamp::Now();
 
   calculateNetworkId();
 
   DWORD waitTime = INFINITE;
 
-  if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 || !mIPv6Changes) {
+  if (!sNotifyIpInterfaceChange || !sCancelMibChangeNotify2 ||
+      !StaticPrefs::network_notify_IPv6()) {
     // For Windows versions which are older than Vista which lack
     // NotifyIpInterfaceChange. Note this means no IPv6 support.
     HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
     NS_ENSURE_TRUE(ev, NS_ERROR_OUT_OF_MEMORY);
 
     HANDLE handles[2] = {ev, mCheckEvent};
     OVERLAPPED overlapped = {0};
     bool shuttingDown = false;
@@ -345,20 +344,16 @@ nsresult nsNotifyAddrListener::Init(void
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (!observerService) return NS_ERROR_FAILURE;
 
   nsresult rv =
       observerService->AddObserver(this, "xpcom-shutdown-threads", false);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  Preferences::AddBoolVarCache(&mAllowChangedEvent, NETWORK_NOTIFY_CHANGED_PREF,
-                               true);
-  Preferences::AddBoolVarCache(&mIPv6Changes, NETWORK_NOTIFY_IPV6_PREF, false);
-
   mCheckEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
   NS_ENSURE_TRUE(mCheckEvent, NS_ERROR_OUT_OF_MEMORY);
 
   rv = NS_NewNamedThread("Link Monitor", getter_AddRefs(mThread), this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
@@ -644,17 +639,18 @@ void nsNotifyAddrListener::CheckLinkStat
       mLinkUp = true;
     }
 
     if (mLinkUp && (prevCsum != mIPInterfaceChecksum)) {
       TimeDuration since = TimeStamp::Now() - mStartTime;
 
       // Network is online. Topology has changed. Always send CHANGED
       // before UP - if allowed to and having cooled down.
-      if (mAllowChangedEvent && (since.ToMilliseconds() > 2000)) {
+      if (StaticPrefs::network_notify_changed() &&
+          (since.ToMilliseconds() > 2000)) {
         NetworkChanged();
       }
     }
     if (prevLinkUp != mLinkUp) {
       // UP/DOWN status changed, send appropriate UP/DOWN event
       SendEvent(mLinkUp ? NS_NETWORK_LINK_DATA_UP : NS_NETWORK_LINK_DATA_DOWN);
     }
   }
--- a/netwerk/system/win32/nsNotifyAddrListener.h
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -83,22 +83,16 @@ class nsNotifyAddrListener : public nsIN
 
   // This is a checksum of various meta data for all network interfaces
   // considered UP at last check.
   ULONG mIPInterfaceChecksum;
 
   // start time of the checking
   mozilla::TimeStamp mStartTime;
 
-  // Network changed events are enabled
-  bool mAllowChangedEvent;
-
-  // Check for IPv6 network changes
-  bool mIPv6Changes;
-
   // Flag set while coalescing change events
   bool mCoalescingActive;
 
   // Time stamp for first event during coalescing
   mozilla::TimeStamp mChangeTime;
 };
 
 #endif /* NSNOTIFYADDRLISTENER_H_ */
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -870,16 +870,18 @@ class RecursiveMakeBackend(CommonBackend
             root_mk.add_statement('%s_dirs := %s' % (tier, ' '.join(all_dirs)))
 
         # Need a list of compile targets because we can't use pattern rules:
         # https://savannah.gnu.org/bugs/index.php?42833
         root_mk.add_statement('compile_targets := %s' % ' '.join(sorted(
             set(self._compile_graph.keys()) | all_compile_deps)))
         root_mk.add_statement('syms_targets := %s' % ' '.join(sorted(
             set('%s/syms' % d for d in self._no_skip['syms']))))
+        root_mk.add_statement('rust_targets := %s' % ' '.join(sorted(
+            self._rust_targets)))
 
         root_mk.add_statement('non_default_tiers := %s' % ' '.join(sorted(
             non_default_roots.keys())))
 
         for category, graphs in non_default_graphs.iteritems():
             category_dirs = [mozpath.dirname(target)
                              for target in graphs.keys()]
             root_mk.add_statement('%s_dirs := %s' % (category,
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -6,25 +6,46 @@ from __future__ import absolute_import, 
 
 import errno
 import hashlib
 import logging
 import os
 import re
 import subprocess
 import sys
+from collections import OrderedDict
 from distutils.version import LooseVersion
+from itertools import dropwhile
 
+import pytoml
 import mozpack.path as mozpath
 from mozbuild.base import (
     BuildEnvironmentNotFoundException,
     MozbuildObject,
 )
 
 
+CARGO_CONFIG_TEMPLATE = '''\
+# This file contains vendoring instructions for cargo.
+# It was generated by `mach vendor rust`.
+# Please do not edit.
+
+{config}
+
+# Take advantage of the fact that cargo will treat lines starting with #
+# as comments to add preprocessing directives for when this file is included
+# from .cargo/config.in.
+#define REPLACE_NAME {replace_name}
+#define VENDORED_DIRECTORY {directory}
+#ifndef top_srcdir
+{replace}
+#endif
+'''
+
+
 class VendorRust(MozbuildObject):
     def get_cargo_path(self):
         try:
             return self.substs['CARGO']
         except (BuildEnvironmentNotFoundException, KeyError):
             # Default if this tree isn't configured.
             from mozfile import which
             cargo = which('cargo')
@@ -312,22 +333,93 @@ license file's hash.
 
         cargo = self._ensure_cargo()
         if not cargo:
             return
 
         relative_vendor_dir = 'third_party/rust'
         vendor_dir = mozpath.join(self.topsrcdir, relative_vendor_dir)
 
+        cargo_config = os.path.join(self.topsrcdir, '.cargo', 'config')
+        # First, remove .cargo/config
+        try:
+            os.remove(cargo_config)
+        except Exception:
+            pass
+
         # We use check_call instead of mozprocess to ensure errors are displayed.
         # We do an |update -p| here to regenerate the Cargo.lock file with minimal
         # changes. See bug 1324462
         subprocess.check_call([cargo, 'update', '-p', 'gkrust'], cwd=self.topsrcdir)
 
-        subprocess.check_call([cargo, 'vendor', '--quiet', vendor_dir], cwd=self.topsrcdir)
+        output = subprocess.check_output([cargo, 'vendor', vendor_dir],
+                                         stderr=subprocess.STDOUT,
+                                         cwd=self.topsrcdir)
+
+        # Get the snippet of configuration that cargo vendor outputs, and
+        # update .cargo/config with it.
+        # XXX(bug 1576765): Hopefully do something better after
+        # https://github.com/rust-lang/cargo/issues/7280 is addressed.
+        config = '\n'.join(dropwhile(lambda l: not l.startswith('['),
+                                     output.splitlines()))
+
+        # The config is toml, parse it as such.
+        config = pytoml.loads(config)
+
+        # For each replace-with, extract their configuration and update the
+        # corresponding directory to be relative to topsrcdir.
+        replaces = {
+            v['replace-with']
+            for v in config['source'].values()
+            if 'replace-with' in v
+        }
+
+        # We only really expect one replace-with
+        if len(replaces) != 1:
+            self.log(
+                logging.ERROR, 'vendor_failed', {},
+                '''cargo vendor didn't output a unique replace-with. Found: %s.''' % replaces)
+            sys.exit(1)
+
+        replace_name = replaces.pop()
+        replace = config['source'].pop(replace_name)
+        replace['directory'] = mozpath.relpath(
+            mozpath.normsep(os.path.normcase(replace['directory'])),
+            mozpath.normsep(os.path.normcase(self.topsrcdir)),
+        )
+
+        # Introduce some determinism for the output.
+        def recursive_sort(obj):
+            if isinstance(obj, dict):
+                return OrderedDict(sorted(
+                    (k, recursive_sort(v)) for k, v in obj.items()))
+            if isinstance(obj, list):
+                return [recursive_sort(o) for o in obj]
+            return obj
+
+        config = recursive_sort(config)
+
+        # Normalize pytoml output:
+        # - removing empty lines
+        # - remove empty [section]
+        def toml_dump(data):
+            dump = pytoml.dumps(data)
+            if isinstance(data, dict):
+                for k, v in data.items():
+                    if all(isinstance(v2, dict) for v2 in v.values()):
+                        dump = dump.replace('[%s]' % k, '')
+            return dump.strip()
+
+        with open(cargo_config, 'w') as fh:
+            fh.write(CARGO_CONFIG_TEMPLATE.format(
+                config=toml_dump(config),
+                replace_name=replace_name,
+                directory=replace['directory'],
+                replace=toml_dump({'source': {replace_name: replace}}),
+            ))
 
         if not self._check_licenses(vendor_dir):
             self.log(
                 logging.ERROR, 'license_check_failed', {},
                 '''The changes from `mach vendor rust` will NOT be added to version control.''')
             sys.exit(1)
 
         self.repository.add_remove_files(vendor_dir)
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -340,20 +340,17 @@ def set_gecko_property(ffi_name, expr):
                     structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)},
             % endfor
         };
         ${set_gecko_property(gecko_ffi_name, "result")}
     }
 </%def>
 
 <%def name="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type='u8')">
-    // FIXME: We introduced non_upper_case_globals for -moz-appearance only
-    //        since the prefix of Gecko value starts with ThemeWidgetType_NS_THEME.
-    //        We should remove this after fix bug 1371809.
-    #[allow(non_snake_case, non_upper_case_globals)]
+    #[allow(non_snake_case)]
     pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
         use crate::properties::longhands::${ident}::computed_value::T as Keyword;
         // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts
 
         // Some constant macros in the gecko are defined as negative integer(e.g. font-stretch).
         // And they are convert to signed integer in Rust bindings. We need to cast then
         // as signed type when we have both signed/unsigned integer in order to use them
         // as match's arms.
@@ -2160,17 +2157,17 @@ fn static_assert() {
                   skip_longhands="${skip_background_longhands}">
 
     <% impl_common_image_layer_properties("background") %>
     <% impl_simple_image_array_property("attachment", "background", "mImage", "mAttachment", "Background") %>
     <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %>
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="List"
-                  skip_longhands="list-style-image list-style-type -moz-image-region">
+                  skip_longhands="list-style-image list-style-type">
 
     pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) {
         match image {
             UrlOrNone::None => {
                 unsafe {
                     Gecko_SetListStyleImageNone(&mut *self.gecko);
                 }
             }
@@ -2230,188 +2227,22 @@ fn static_assert() {
         use crate::values::generics::CounterStyleOrNone;
 
         let result = CounterStyleOrNone::from_gecko_value(&self.gecko.mCounterStyle);
         match result {
             Either::First(counter_style) => T::CounterStyle(counter_style),
             Either::Second(string) => T::String(string),
         }
     }
-
-    #[allow(non_snake_case)]
-    pub fn set__moz_image_region(&mut self, v: longhands::_moz_image_region::computed_value::T) {
-        use crate::values::Either;
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-
-        match v {
-            Either::Second(_auto) => {
-                self.gecko.mImageRegion.x = 0;
-                self.gecko.mImageRegion.y = 0;
-                self.gecko.mImageRegion.width = 0;
-                self.gecko.mImageRegion.height = 0;
-            }
-            Either::First(rect) => {
-                self.gecko.mImageRegion.x = match rect.left {
-                    LengthPercentage(v) => v.to_i32_au(),
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.y = match rect.top {
-                    LengthPercentage(v) => v.to_i32_au(),
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.height = match rect.bottom {
-                    LengthPercentage(value) => (Au::from(value) - Au(self.gecko.mImageRegion.y)).0,
-                    Auto => 0,
-                };
-                self.gecko.mImageRegion.width = match rect.right {
-                    LengthPercentage(value) => (Au::from(value) - Au(self.gecko.mImageRegion.x)).0,
-                    Auto => 0,
-                };
-            }
-        }
-    }
-
-    #[allow(non_snake_case)]
-    pub fn clone__moz_image_region(&self) -> longhands::_moz_image_region::computed_value::T {
-        use crate::values::{Auto, Either};
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-        use crate::values::computed::ClipRect;
-
-        // There is no ideal way to detect auto type for structs::nsRect and its components, so
-        // if all components are zero, we use Auto.
-        if self.gecko.mImageRegion.x == 0 &&
-           self.gecko.mImageRegion.y == 0 &&
-           self.gecko.mImageRegion.width == 0 &&
-           self.gecko.mImageRegion.height == 0 {
-           return Either::Second(Auto);
-        }
-
-        Either::First(ClipRect {
-            top: LengthPercentage(Au(self.gecko.mImageRegion.y).into()),
-            right: LengthPercentage(Au(self.gecko.mImageRegion.width + self.gecko.mImageRegion.x).into()),
-            bottom: LengthPercentage(Au(self.gecko.mImageRegion.height + self.gecko.mImageRegion.y).into()),
-            left: LengthPercentage(Au(self.gecko.mImageRegion.x).into()),
-        })
-    }
-
-    ${impl_simple_copy('_moz_image_region', 'mImageRegion')}
-
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Table">
 </%self:impl_trait>
 
-<%self:impl_trait style_struct_name="Effects" skip_longhands="clip">
-    pub fn set_clip(&mut self, v: longhands::clip::computed_value::T) {
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_RECT;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_LEFT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_TOP_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_RIGHT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_BOTTOM_AUTO;
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-        use crate::values::Either;
-
-        match v {
-            Either::First(rect) => {
-                self.gecko.mClipFlags = NS_STYLE_CLIP_RECT as u8;
-                self.gecko.mClip.x = match rect.left {
-                    LengthPercentage(l) => l.to_i32_au(),
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_LEFT_AUTO as u8;
-                        0
-                    }
-                };
-
-                self.gecko.mClip.y = match rect.top {
-                    LengthPercentage(l) => l.to_i32_au(),
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_TOP_AUTO as u8;
-                        0
-                    }
-                };
-
-                self.gecko.mClip.height = match rect.bottom {
-                    LengthPercentage(l) => (Au::from(l) - Au(self.gecko.mClip.y)).0,
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_BOTTOM_AUTO as u8;
-                        1 << 30 // NS_MAXSIZE
-                    }
-                };
-
-                self.gecko.mClip.width = match rect.right {
-                    LengthPercentage(l) => (Au::from(l) - Au(self.gecko.mClip.x)).0,
-                    Auto => {
-                        self.gecko.mClipFlags |= NS_STYLE_CLIP_RIGHT_AUTO as u8;
-                        1 << 30 // NS_MAXSIZE
-                    }
-                };
-            },
-            Either::Second(_auto) => {
-                self.gecko.mClipFlags = NS_STYLE_CLIP_AUTO as u8;
-                self.gecko.mClip.x = 0;
-                self.gecko.mClip.y = 0;
-                self.gecko.mClip.width = 0;
-                self.gecko.mClip.height = 0;
-            }
-        }
-    }
-
-    pub fn copy_clip_from(&mut self, other: &Self) {
-        self.gecko.mClip = other.gecko.mClip;
-        self.gecko.mClipFlags = other.gecko.mClipFlags;
-    }
-
-    pub fn reset_clip(&mut self, other: &Self) {
-        self.copy_clip_from(other)
-    }
-
-    pub fn clone_clip(&self) -> longhands::clip::computed_value::T {
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_BOTTOM_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_LEFT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_RIGHT_AUTO;
-        use crate::gecko_bindings::structs::NS_STYLE_CLIP_TOP_AUTO;
-        use crate::values::generics::length::LengthPercentageOrAuto::*;
-        use crate::values::computed::{ClipRect, ClipRectOrAuto};
-        use crate::values::Either;
-
-        if self.gecko.mClipFlags == NS_STYLE_CLIP_AUTO as u8 {
-            return ClipRectOrAuto::auto()
-        }
-        let left = if self.gecko.mClipFlags & NS_STYLE_CLIP_LEFT_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.x, 0);
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.x).into())
-        };
-
-        let top = if self.gecko.mClipFlags & NS_STYLE_CLIP_TOP_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.y, 0);
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.y).into())
-        };
-
-        let bottom = if self.gecko.mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.height, 1 << 30); // NS_MAXSIZE
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.y + self.gecko.mClip.height).into())
-        };
-
-        let right = if self.gecko.mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO as u8 != 0 {
-            debug_assert_eq!(self.gecko.mClip.width, 1 << 30); // NS_MAXSIZE
-            Auto
-        } else {
-            LengthPercentage(Au(self.gecko.mClip.x + self.gecko.mClip.width).into())
-        };
-
-        Either::First(ClipRect { top, right, bottom, left })
-    }
+<%self:impl_trait style_struct_name="Effects">
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedBox">
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedTable"
                   skip_longhands="border-spacing">
 
--- a/servo/components/style/properties/longhands/list.mako.rs
+++ b/servo/components/style/properties/longhands/list.mako.rs
@@ -71,16 +71,17 @@
     servo_restyle_damage="rebuild_and_reflow",
 )}
 
 ${helpers.predefined_type(
     "-moz-image-region",
     "ClipRectOrAuto",
     "computed::ClipRectOrAuto::auto()",
     engines="gecko",
+    gecko_ffi_name="mImageRegion",
     animation_value_type="ComputedValue",
     boxed=True,
     spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)",
 )}
 
 ${helpers.predefined_type(
     "-moz-list-reversed",
     "MozListReversed",
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -678,21 +678,21 @@ impl From<CSSInteger> for PositiveIntege
     fn from(int: CSSInteger) -> PositiveInteger {
         GreaterThanOrEqualToOne::<CSSInteger>(int)
     }
 }
 
 /// A computed positive `<integer>` value or `none`.
 pub type PositiveIntegerOrNone = Either<PositiveInteger, None_>;
 
-/// rect(...)
-pub type ClipRect = generics::ClipRect<LengthOrAuto>;
+/// rect(...) | auto
+pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
 
 /// rect(...) | auto
-pub type ClipRectOrAuto = Either<ClipRect, Auto>;
+pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
 
 /// The computed value of a grid `<track-breadth>`
 pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
 
 /// The computed value of a grid `<track-size>`
 pub type TrackSize = GenericTrackSize<LengthPercentage>;
 
 /// The computed value of a grid `<track-size>+`
@@ -702,23 +702,8 @@ pub type ImplicitGridTracks = GenericImp
 /// (could also be `<auto-track-list>` or `<explicit-track-list>`)
 pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
 
 /// The computed value of a `<grid-line>`.
 pub type GridLine = GenericGridLine<Integer>;
 
 /// `<grid-template-rows> | <grid-template-columns>`
 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
-
-impl ClipRectOrAuto {
-    /// Return an auto (default for clip-rect and image-region) value
-    pub fn auto() -> Self {
-        Either::Second(Auto)
-    }
-
-    /// Check if it is auto
-    pub fn is_auto(&self) -> bool {
-        match *self {
-            Either::Second(_) => true,
-            _ => false,
-        }
-    }
-}
--- a/servo/components/style/values/computed/text.rs
+++ b/servo/components/style/values/computed/text.rs
@@ -193,18 +193,16 @@ impl TextDecorationsInEffect {
         result.overline |= line.contains(TextDecorationLine::OVERLINE);
         result.line_through |= line.contains(TextDecorationLine::LINE_THROUGH);
 
         result
     }
 }
 
 /// Computed value for the text-emphasis-style property
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)]
 #[allow(missing_docs)]
 #[repr(C, u8)]
 pub enum TextEmphasisStyle {
     /// [ <fill> || <shape> ]
     Keyword {
         #[css(skip_if = "TextEmphasisFillMode::is_filled")]
         fill: TextEmphasisFillMode,
--- a/servo/components/style/values/generics/effects.rs
+++ b/servo/components/style/values/generics/effects.rs
@@ -29,18 +29,16 @@ pub struct GenericBoxShadow<Color, SizeL
     #[animation(constant)]
     #[css(represents_keyword)]
     pub inset: bool,
 }
 
 pub use self::GenericBoxShadow as BoxShadow;
 
 /// A generic value for a single `filter`.
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[animation(no_bound(U))]
 #[derive(
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
--- a/servo/components/style/values/generics/grid.rs
+++ b/servo/components/style/values/generics/grid.rs
@@ -180,18 +180,16 @@ impl Parse for GridLine<specified::Integ
         Ok(grid_line)
     }
 }
 
 /// A track breadth for explicit grid track sizing. It's generic solely to
 /// avoid re-implementing it for the computed type.
 ///
 /// <https://drafts.csswg.org/css-grid/#typedef-track-breadth>
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToComputedValue,
@@ -225,18 +223,16 @@ impl<L> TrackBreadth<L> {
         matches!(*self, TrackBreadth::Breadth(..))
     }
 }
 
 /// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is
 /// generic only to avoid code bloat. It only takes `<length-percentage>`
 ///
 /// <https://drafts.csswg.org/css-grid/#typedef-track-size>
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToComputedValue,
     ToResolvedValue,
@@ -489,18 +485,16 @@ impl<L: ToCss, I: ToCss> ToCss for Track
 
         dest.write_str(")")?;
 
         Ok(())
     }
 }
 
 /// Track list values. Can be <track-size> or <track-repeat>
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToComputedValue,
@@ -724,18 +718,16 @@ impl ToCss for LineNameList {
             }
         }
 
         Ok(())
     }
 }
 
 /// Variants for `<grid-template-rows> | <grid-template-columns>`
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToComputedValue,
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -267,14 +267,52 @@ pub struct ZeroToOne<T>(pub T);
     ToAnimatedValue,
     ToAnimatedZero,
     ToComputedValue,
     ToCss,
     ToResolvedValue,
     ToShmem,
 )]
 #[css(function = "rect", comma)]
-pub struct ClipRect<LengthOrAuto> {
+#[repr(C)]
+pub struct GenericClipRect<LengthOrAuto> {
     pub top: LengthOrAuto,
     pub right: LengthOrAuto,
     pub bottom: LengthOrAuto,
     pub left: LengthOrAuto,
 }
+
+pub use self::GenericClipRect as ClipRect;
+
+/// Either a clip-rect or `auto`.
+#[allow(missing_docs)]
+#[derive(
+    Animate,
+    Clone,
+    ComputeSquaredDistance,
+    Copy,
+    Debug,
+    MallocSizeOf,
+    Parse,
+    PartialEq,
+    SpecifiedValueInfo,
+    ToAnimatedValue,
+    ToAnimatedZero,
+    ToComputedValue,
+    ToCss,
+    ToResolvedValue,
+    ToShmem,
+)]
+#[repr(C, u8)]
+pub enum GenericClipRectOrAuto<R> {
+    Auto,
+    Rect(R),
+}
+
+pub use self::GenericClipRectOrAuto as ClipRectOrAuto;
+
+impl<L> ClipRectOrAuto<L> {
+    /// Returns the `auto` value.
+    #[inline]
+    pub fn auto() -> Self {
+        ClipRectOrAuto::Auto
+    }
+}
--- a/servo/components/style/values/generics/svg.rs
+++ b/servo/components/style/values/generics/svg.rs
@@ -4,17 +4,16 @@
 
 //! Generic types for CSS values in SVG
 
 use crate::parser::{Parse, ParserContext};
 use cssparser::Parser;
 use style_traits::ParseError;
 
 /// The fallback of an SVG paint server value.
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
     Parse,
@@ -38,18 +37,16 @@ pub enum GenericSVGPaintFallback<C> {
     Color(C),
 }
 
 pub use self::GenericSVGPaintFallback as SVGPaintFallback;
 
 /// An SVG paint value
 ///
 /// <https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint>
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[animation(no_bound(Url))]
 #[derive(
     Animate,
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
@@ -79,18 +76,16 @@ impl<C, U> Default for SVGPaint<C, U> {
         }
     }
 }
 
 /// An SVG paint value without the fallback.
 ///
 /// Whereas the spec only allows PaintServer to have a fallback, Gecko lets the
 /// context properties have a fallback as well.
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[animation(no_bound(U))]
 #[derive(
     Animate,
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -146,17 +146,16 @@ fn is_same<N: PartialEq>(x: &N, y: &N) -
     SpecifiedValueInfo,
     ToComputedValue,
     ToCss,
     ToResolvedValue,
     ToShmem,
 )]
 #[repr(C, u8)]
 /// A single operation in the list of a `transform` value
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>
 where
     Angle: Zero,
     LengthPercentage: Zero,
     Number: PartialEq,
 {
     /// Represents a 2D 2x3 matrix.
     Matrix(GenericMatrix<Number>),
--- a/servo/components/style/values/generics/url.rs
+++ b/servo/components/style/values/generics/url.rs
@@ -1,17 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 //! Generic types for url properties.
 
 /// An image url or none, used for example in list-style-image
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
     Parse,
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -1086,18 +1086,16 @@ bitflags! {
     }
 }
 
 #[derive(
     Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToResolvedValue, ToShmem,
 )]
 #[repr(C, u8)]
 /// Set of variant alternates
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 pub enum VariantAlternates {
     /// Enables display of stylistic alternates
     #[css(function)]
     Stylistic(CustomIdent),
     /// Enables display with stylistic sets
     #[css(comma, function)]
     Styleset(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
     /// Enables display of specific character variants
--- a/servo/components/style/values/specified/list.rs
+++ b/servo/components/style/values/specified/list.rs
@@ -121,18 +121,16 @@ pub struct QuotePair {
 pub struct QuoteList(
     #[css(iterable, if_empty = "none")]
     #[ignore_malloc_size_of = "Arc"]
     pub crate::ArcSlice<QuotePair>,
 );
 
 /// Specified and computed `quotes` property: `auto`, `none`, or a list
 /// of characters.
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Clone,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
     ToComputedValue,
     ToCss,
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -8,17 +8,17 @@
 
 use super::computed::transform::DirectionVector;
 use super::computed::{Context, ToComputedValue};
 use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
 use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
 use super::generics::transform::IsParallelTo;
 use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
-use super::{Auto, CSSFloat, CSSInteger, Either, None_};
+use super::{CSSFloat, CSSInteger, Either, None_};
 use crate::context::QuirksMode;
 use crate::parser::{Parse, ParserContext};
 use crate::values::serialize_atom_identifier;
 use crate::values::specified::calc::CalcNode;
 use crate::{Atom, Namespace, Prefix, Zero};
 use cssparser::{Parser, Token};
 use num_traits::One;
 use std::f32;
@@ -634,30 +634,30 @@ pub type TrackList = GenericTrackList<Le
 
 /// The specified value of a `<grid-line>`.
 pub type GridLine = GenericGridLine<Integer>;
 
 /// `<grid-template-rows> | <grid-template-columns>`
 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
 
 /// rect(...)
-pub type ClipRect = generics::ClipRect<LengthOrAuto>;
+pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
 
 impl Parse for ClipRect {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         Self::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
 impl ClipRect {
     /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks.
-    pub fn parse_quirky<'i, 't>(
+    fn parse_quirky<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
         input.expect_function_matching("rect")?;
 
         fn parse_argument<'i, 't>(
             context: &ParserContext,
@@ -691,30 +691,30 @@ impl ClipRect {
                 bottom,
                 left,
             })
         })
     }
 }
 
 /// rect(...) | auto
-pub type ClipRectOrAuto = Either<ClipRect, Auto>;
+pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
 
 impl ClipRectOrAuto {
     /// Parses a ClipRect or Auto, allowing quirks.
     pub fn parse_quirky<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
         if let Ok(v) = input.try(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
-            Ok(Either::First(v))
-        } else {
-            Auto::parse(context, input).map(Either::Second)
+            return Ok(generics::GenericClipRectOrAuto::Rect(v))
         }
+        input.expect_ident_matching("auto")?;
+        Ok(generics::GenericClipRectOrAuto::Auto)
     }
 }
 
 /// Whether quirks are allowed in this context.
 #[derive(Clone, Copy, PartialEq)]
 pub enum AllowQuirks {
     /// Quirks are not allowed.
     No,
--- a/servo/components/style/values/specified/motion.rs
+++ b/servo/components/style/values/specified/motion.rs
@@ -10,17 +10,16 @@ use crate::values::computed::{Context, T
 use crate::values::specified::{Angle, SVGPathData};
 use crate::Zero;
 use cssparser::Parser;
 use style_traits::{ParseError, StyleParseErrorKind};
 
 /// The offset-path value.
 ///
 /// https://drafts.fxtf.org/motion-1/#offset-path-property
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
     Animate,
     Clone,
     ComputeSquaredDistance,
     Debug,
     MallocSizeOf,
     PartialEq,
     SpecifiedValueInfo,
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -702,18 +702,16 @@ fn is_name_code_point(c: char) -> bool {
         c == '-'
 }
 
 /// This property specifies named grid areas.
 ///
 /// The syntax of this property also provides a visualization of the structure
 /// of the grid, making the overall layout of the grid container easier to
 /// understand.
-///
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[repr(C, u8)]
 #[derive(
     Clone,
     Debug,
     MallocSizeOf,
     Parse,
     PartialEq,
     SpecifiedValueInfo,
--- a/servo/components/style/values/specified/text.rs
+++ b/servo/components/style/values/specified/text.rs
@@ -127,17 +127,16 @@ impl ToComputedValue for LineHeight {
             GenericLineHeight::Length(ref length) => {
                 GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
             },
         }
     }
 }
 
 /// A generic value for the `text-overflow` property.
-/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
 #[repr(C, u8)]
 pub enum TextOverflowSide {
     /// Clip inline content.
     Clip,
     /// Render ellipsis to represent clipped inline content.
     Ellipsis,
     /// Render a given string to represent clipped inline content.
--- a/servo/ports/geckolib/cbindgen.toml
+++ b/servo/ports/geckolib/cbindgen.toml
@@ -43,16 +43,18 @@ derive_neq = true
 
 [macro_expansion]
 bitflags = true
 
 [enum]
 derive_helper_methods = true
 derive_const_casts = true
 derive_tagged_enum_destructor = true
+derive_tagged_enum_copy_constructor = true
+private_default_tagged_enum_constructor = true
 cast_assert_name = "MOZ_ASSERT"
 
 [export]
 prefix = "Style"
 exclude = [
   "NS_LogCtor",
   "NS_LogDtor",
 ]
@@ -166,16 +168,17 @@ include = [
   "ImplicitGridTracks",
   "SVGPaint",
   "SVGPaintKind",
   "GridTemplateComponent",
   "TextEmphasisStyle",
   "VariantAlternatesList",
   "PaintOrder",
   "SVGPaintOrder",
+  "ClipRectOrAuto",
 ]
 item_types = ["enums", "structs", "typedefs", "functions", "constants"]
 renaming_overrides_prefixing = true
 
 # Prevent some renaming for Gecko types that cbindgen doesn't otherwise understand.
 [export.rename]
 "nscolor" = "nscolor"
 "nsAtom" = "nsAtom"
@@ -325,25 +328,16 @@ renaming_overrides_prefixing = true
   static inline StyleRestyleHint ForAnimations();
 """
 
 "TextTransform" = """
   static inline StyleTextTransform None();
   inline bool IsNone() const;
 """
 
-"Quotes" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleQuotes() {}
- public:
-"""
-
 # TODO(emilio): Add hooks to cbindgen to be able to generate MOZ_MUST_USE_TYPE
 # or MOZ_MUST_USE on the functions.
 "Owned" = """
   UniquePtr<GeckoType> Consume() {
     UniquePtr<GeckoType> ret(ptr);
     ptr = nullptr;
     return ret;
   }
@@ -513,57 +507,22 @@ renaming_overrides_prefixing = true
 """
 
 "GenericTransform" = """
   inline Span<const T> Operations() const;
   inline bool IsNone() const;
   bool HasPercent() const;
 """
 
-"GenericTransformOperation" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericTransformOperation() {}
- public:
-"""
-
-"GenericUrlOrNone" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericUrlOrNone() {}
- public:
-"""
 "Angle" = """
   inline static StyleAngle Zero();
   inline float ToDegrees() const;
   inline double ToRadians() const;
 """
 
-"OffsetPath" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleOffsetPath() {}
- public:
-"""
-
-"TextOverflowSide" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleTextOverflowSide() {}
- public:
-"""
-
 "TextOverflow" = """
   StyleTextOverflow()
     : first(StyleTextOverflowSide::Clip()),
       second(StyleTextOverflowSide::Clip()),
       sides_are_logical(true) {}
 """
 
 "UrlExtraData" = """
@@ -612,96 +571,38 @@ renaming_overrides_prefixing = true
   already_AddRefed<nsIURI> ResolveLocalRef(const nsIContent* aContent) const;
   imgRequestProxy* LoadImage(mozilla::dom::Document&);
 """
 
 "GenericGradient" = """
   bool IsOpaque() const;
 """
 
-"GridTemplateAreas" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGridTemplateAreas() {}
- public:
-"""
-
 "GenericGridLine" = """
   // Returns the `auto` value.
   inline StyleGenericGridLine();
   inline bool IsAuto() const;
   // The line name, or nsGkAtoms::_empty if not present.
   inline nsAtom* LineName() const;
 """
 
 "GenericTrackBreadth" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericTrackBreadth() {}
- public:
   inline bool HasPercent() const;
 """
 
 "GenericTrackSize" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericTrackSize() {}
- public:
   // Implemented in nsGridContainerFrame.cpp
   inline const StyleGenericTrackBreadth<L>& GetMin() const;
   inline const StyleGenericTrackBreadth<L>& GetMax() const;
 """
 
-"GenericSVGPaintKind" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericSVGPaintKind() {}
- public:
-"""
-
-"GenericSVGPaintFallback" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericSVGPaintFallback() {}
- public:
-"""
-
 "GenericGridTemplateComponent" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleGenericGridTemplateComponent() {}
- public:
   inline Maybe<size_t> RepeatAutoIndex() const;
   inline const StyleGenericTrackRepeat<L, I>* GetRepeatAutoValue() const;
   inline bool HasRepeatAuto() const;
   inline Span<const StyleOwnedSlice<StyleCustomIdent>> LineNameLists(bool aIsSubgrid) const;
   inline Span<const StyleGenericTrackListValue<L, I>> TrackListValues() const;
 """
 
-"TextEmphasisStyle" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleTextEmphasisStyle() {}
- public:
+"GenericClipRect" = """
+  // Get the layout rect, replacing auto right / bottom values for aAutoSize.
+  inline nsRect ToLayoutRect(nscoord aAutoSize = NS_MAXSIZE) const;
 """
-
-"VariantAlternates" = """
- private:
-  // Private default constructor without initialization so that the helper
-  // constructor functions still work as expected. They take care of
-  // initializing the fields properly.
-  StyleVariantAlternates() {}
- public:
-"""
--- a/taskcluster/ci/fetch/toolchains.yml
+++ b/taskcluster/ci/fetch/toolchains.yml
@@ -251,22 +251,22 @@ wine-3.0.3:
         type: static-url
         url: http://dl.winehq.org/wine/source/3.0/wine-3.0.3.tar.xz
         sha256: eb645999ea6f6455a5275bf267e19a32497c8f5aac818ea40afe7c8c396a4da1
         size: 19735412
         gpg-signature:
             sig-url: "{url}.sign"
             key-path: build/unix/build-gcc/DA23579A74D4AD9AF9D3F945CEFAC8EAAF17519D.key
 
-cbindgen-0.9.0:
+cbindgen-0.9.1:
     description: cbindgen source code
     fetch:
         type: git
         repo: https://github.com/eqrion/cbindgen
-        revision: e19526e00b3fe6921b881682147a1fe5d6b64124
+        revision: 8e4db4c17fbdc0cfa9b98cfe9d47ca6263858def
 
 cctools-port:
     description: cctools-port source code
     fetch:
         type: git
         repo: https://github.com/tpoechtrager/cctools-port
         revision: 3f979bbcd7ee29d79fb93f829edf3d1d16441147
 
--- a/taskcluster/ci/release-partner-repack-beetmover/kind.yml
+++ b/taskcluster/ci/release-partner-repack-beetmover/kind.yml
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 ---
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
+    - taskgraph.transforms.chunk_partners:transforms
     - taskgraph.transforms.name_sanity:transforms
     - taskgraph.transforms.beetmover_repackage_partner:transforms
     - taskgraph.transforms.task:transforms
 
 kind-dependencies:
     - release-partner-repack-repackage-signing
 
 only-for-build-platforms:
--- a/taskcluster/ci/release-partner-repack-repackage-signing/kind.yml
+++ b/taskcluster/ci/release-partner-repack-repackage-signing/kind.yml
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 ---
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
+    - taskgraph.transforms.chunk_partners:transforms
     - taskgraph.transforms.name_sanity:transforms
     - taskgraph.transforms.repackage_signing_partner:transforms
     - taskgraph.transforms.task:transforms
 
 kind-dependencies:
     - release-partner-repack-chunking-dummy  # Linux
     - release-partner-repack-repackage  # Windows, Mac
 
--- a/taskcluster/ci/toolchain/cbindgen.yml
+++ b/taskcluster/ci/toolchain/cbindgen.yml
@@ -9,17 +9,17 @@ job-defaults:
         max-run-time: 3600
     run:
         script: build-cbindgen.sh
         toolchain-artifact: public/build/cbindgen.tar.xz
     fetches:
         fetch:
             # If you update this, make sure to update the minimum version in
             # build/moz.configure/bindgen.configure as well.
-            - cbindgen-0.9.0
+            - cbindgen-0.9.1
 
 linux64-cbindgen:
     treeherder:
         symbol: TL(cbindgen)
     worker:
         max-run-time: 1800
     run:
         arguments: ['x86_64-unknown-linux-gnu']
--- a/taskcluster/ci/toolchain/dist-toolchains.yml
+++ b/taskcluster/ci/toolchain/dist-toolchains.yml
@@ -30,10 +30,10 @@ rustc-dist-toolchain:
         symbol: TL(rustc-dist)
     worker:
         max-run-time: 1800
     run:
         arguments: ['rustc']
         toolchain-artifact: public/build/rustc-dist-toolchain.tar.xz
     fetches:
         toolchain:
-            - linux64-rust-macos-1.36
+            - linux64-rust-cross-1.37
             - linux64-sccache
--- a/taskcluster/ci/toolchain/rust.yml
+++ b/taskcluster/ci/toolchain/rust.yml
@@ -149,16 +149,31 @@ linux64-rust-macos-1.35:
     run:
         arguments: [
             '--channel', '1.35.0',
             '--host', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-apple-darwin',
         ]
 
+linux64-rust-cross-1.37:
+    description: "rust repack with macos and windows cross support"
+    treeherder:
+        symbol: TL(rust-cross)
+    run:
+        arguments: [
+            '--channel', '1.37.0',
+            '--host', 'x86_64-unknown-linux-gnu',
+            '--target', 'x86_64-unknown-linux-gnu',
+            '--target', 'x86_64-apple-darwin',
+            '--target', 'x86_64-pc-windows-msvc',
+            '--target', 'i686-pc-windows-msvc',
+            '--target', 'aarch64-pc-windows-msvc',
+        ]
+
 linux64-rust-macos-1.36:
     description: "rust repack with macos-cross support"
     treeherder:
         symbol: TL(rust-macos-1.36)
     run:
         arguments: [
             '--channel', '1.36.0',
             '--host', 'x86_64-unknown-linux-gnu',
--- a/taskcluster/scripts/builder/build-sm-mozjs-crate.sh
+++ b/taskcluster/scripts/builder/build-sm-mozjs-crate.sh
@@ -1,19 +1,14 @@
 #!/usr/bin/env bash
 
 set -xe
 
 source $(dirname $0)/sm-tooltool-config.sh
 
-# Ensure that we have a .config/cargo that points us to our vendored crates
-# rather than to crates.io.
-cd "$SRCDIR/.cargo"
-sed -e "s|@top_srcdir@|$SRCDIR|" -e 's|@[^@]*@||g' < config.in > config
-
 cd "$SRCDIR/js/src"
 
 export PATH="$PATH:$MOZ_FETCHES_DIR/cargo/bin:$MOZ_FETCHES_DIR/rustc/bin"
 export RUST_BACKTRACE=1
 export AUTOMATION=1
 
 cargo build --verbose --frozen --features debugmozjs
 cargo build --verbose --frozen
--- a/taskcluster/scripts/builder/build-sm-rust-bindings.sh
+++ b/taskcluster/scripts/builder/build-sm-rust-bindings.sh
@@ -1,19 +1,14 @@
 #!/usr/bin/env bash
 
 set -xe
 
 source $(dirname $0)/sm-tooltool-config.sh
 
-# Ensure that we have a .config/cargo that points us to our vendored crates
-# rather than to crates.io.
-cd "$SRCDIR/.cargo"
-sed -e "s|@top_srcdir@|$SRCDIR|" -e 's|@[^@]*@||g' < config.in > config
-
 cd "$SRCDIR/js/rust"
 
 export LD_LIBRARY_PATH="$MOZ_FETCHES_DIR/gcc/lib64"
 # Enable backtraces if we panic.
 export RUST_BACKTRACE=1
 
 cargo test --verbose --frozen --features debugmozjs
 cargo test --verbose --frozen
--- a/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
@@ -44,16 +44,17 @@ beetmover_description_schema = schema.ex
 
     Required('partner-bucket-scope'): optionally_keyed_by('release-level', basestring),
     Required('partner-public-path'): Any(None, basestring),
     Required('partner-private-path'): Any(None, basestring),
 
     Optional('extra'): object,
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
+    Optional('priority'): task_description_schema['priority'],
 })
 
 transforms = TransformSequence()
 transforms.add(check_if_partners_enabled)
 transforms.add_validate(beetmover_description_schema)
 
 
 @transforms.add
@@ -118,16 +119,20 @@ def make_task_description(config, jobs):
             'shipping-product': job.get('shipping-product'),
             'partner-private-path': job['partner-private-path'],
             'partner-public-path': job['partner-public-path'],
             'partner-bucket-scope': job['partner-bucket-scope'],
             'extra': {
                 'repack_id': repack_id,
             },
         }
+        # we may have reduced the priority for partner jobs, otherwise task.py will set it
+        if job.get('priority'):
+            task['priority'] = job['priority']
+
         yield task
 
 
 def populate_scopes_and_worker_type(config, job, bucket_scope, partner_public=False):
     action_scope = add_scope_prefix(config, 'beetmover:action:push-to-partner')
 
     task = deepcopy(job)
     task['scopes'] = [bucket_scope, action_scope]
--- a/taskcluster/taskgraph/transforms/chunk_partners.py
+++ b/taskcluster/taskgraph/transforms/chunk_partners.py
@@ -6,19 +6,24 @@ Chunk the partner repack tasks by subpar
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 
 from mozbuild.chunkify import chunkify
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.partners import get_partner_config_by_kind, locales_per_build_platform
+from taskgraph.util.partners import (
+    get_partner_config_by_kind,
+    locales_per_build_platform,
+    apply_partner_priority,
+)
 
 transforms = TransformSequence()
+transforms.add(apply_partner_priority)
 
 
 used_repack_ids_by_platform = {}
 
 
 def _check_repack_ids_by_platform(platform, repack_id):
     """avoid dup chunks, since mac signing and repackages both chunk"""
     if used_repack_ids_by_platform.get(platform, {}).get(repack_id):
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -62,16 +62,17 @@ job_description_schema = Schema({
     Optional('run-on-projects'): task_description_schema['run-on-projects'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('coalesce'): task_description_schema['coalesce'],
     Optional('always-target'): task_description_schema['always-target'],
     Exclusive('optimization', 'optimization'): task_description_schema['optimization'],
     Optional('needs-sccache'): task_description_schema['needs-sccache'],
     Optional('release-artifacts'): task_description_schema['release-artifacts'],
+    Optional('priority'): task_description_schema['priority'],
 
     # The "when" section contains descriptions of the circumstances under which
     # this task should be included in the task graph.  This will be converted
     # into an optimization, so it cannot be specified in a job description that
     # also gives 'optimization'.
     Exclusive('when', 'optimization'): {
         # This task only needs to be run if a file matching one of the given
         # patterns has changed in the push.  The patterns use the mozpack
--- a/taskcluster/taskgraph/transforms/partner_repack.py
+++ b/taskcluster/taskgraph/transforms/partner_repack.py
@@ -8,20 +8,22 @@ Transform the partner repack task into a
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.schema import resolve_keyed_by
 from taskgraph.util.scriptworker import get_release_config
 from taskgraph.util.partners import (
     check_if_partners_enabled,
     get_partner_url_config,
+    apply_partner_priority,
 )
 
 
 transforms = TransformSequence()
+transforms.add(apply_partner_priority)
 
 
 @transforms.add
 def populate_repack_manifests_url(config, tasks):
     for task in tasks:
         partner_url_config = get_partner_url_config(config.params, config.graph_config)
 
         for k in partner_url_config:
--- a/taskcluster/taskgraph/transforms/repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_partner.py
@@ -61,17 +61,20 @@ packaging_description_schema = schema.ex
 
         # Additional paths to look for mozharness configs in. These should be
         # relative to the base of the source checkout
         Optional('config-paths'): [basestring],
 
         # if true, perform a checkout of a comm-central based branch inside the
         # gecko checkout
         Required('comm-checkout', default=False): bool,
-    }
+    },
+
+    # Override the default priority for the project
+    Optional('priority'): task_description_schema['priority'],
 })
 
 transforms = TransformSequence()
 transforms.add(check_if_partners_enabled)
 transforms.add_validate(packaging_description_schema)
 
 
 @transforms.add
@@ -208,16 +211,19 @@ def make_job_description(config, jobs):
             'worker': worker,
             'run': run,
             'fetches': _generate_download_config(dep_job, build_platform,
                                                  signing_task, partner=repack_id,
                                                  project=config.params["project"],
                                                  repack_stub_installer=repack_stub_installer),
         }
 
+        # we may have reduced the priority for partner jobs, otherwise task.py will set it
+        if job.get('priority'):
+            task['priority'] = job['priority']
         if build_platform.startswith('macosx'):
             task.setdefault('fetches', {}).setdefault('toolchain', []).extend([
                 'linux64-libdmg',
                 'linux64-hfsplus',
                 'linux64-node',
             ])
         yield task
 
--- a/taskcluster/taskgraph/transforms/repackage_signing_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing_partner.py
@@ -22,16 +22,17 @@ from voluptuous import Required, Optiona
 transforms = TransformSequence()
 
 repackage_signing_description_schema = schema.extend({
     Required('depname', default='repackage'): basestring,
     Optional('label'): basestring,
     Optional('extra'): object,
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
+    Optional('priority'): task_description_schema['priority'],
 })
 
 transforms.add(check_if_partners_enabled)
 transforms.add_validate(repackage_signing_description_schema)
 
 
 @transforms.add
 def make_repackage_signing_description(config, jobs):
@@ -126,10 +127,13 @@ def make_repackage_signing_description(c
             'scopes': scopes,
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'extra': {
                 'repack_id': repack_id,
             }
         }
+        # we may have reduced the priority for partner jobs, otherwise task.py will set it
+        if job.get('priority'):
+            task['priority'] = job['priority']
 
         yield task
--- a/taskcluster/taskgraph/transforms/signing.py
+++ b/taskcluster/taskgraph/transforms/signing.py
@@ -60,16 +60,19 @@ signing_description_schema = schema.exte
     Optional('shipping-product'): task_description_schema['shipping-product'],
 
     # Optional control for how long a task may run (aka maxRunTime)
     Optional('max-run-time'): int,
     Optional('extra'): {basestring: object},
 
     # Max number of partner repacks per chunk
     Optional('repacks-per-chunk'): int,
+
+    # Override the default priority for the project
+    Optional('priority'): task_description_schema['priority'],
 })
 
 
 @transforms.add
 def set_defaults(config, jobs):
     for job in jobs:
         job.setdefault('depname', 'build')
         yield job
@@ -191,16 +194,19 @@ def make_task_description(config, jobs):
             if job.get('entitlements-url'):
                 task['worker']['entitlements-url'] = job['entitlements-url']
 
         task['worker-type'] = worker_type_alias
         if treeherder:
             task['treeherder'] = treeherder
         if job.get('extra'):
             task['extra'] = job['extra']
+        # we may have reduced the priority for partner jobs, otherwise task.py will set it
+        if job.get('priority'):
+            task['priority'] = job['priority']
 
         yield task
 
 
 def _generate_treeherder_platform(dep_th_platform, build_platform, build_type):
     if '-pgo' in build_platform:
         actual_build_type = 'pgo'
     elif '-ccov' in build_platform:
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -231,17 +231,20 @@ task_description_schema = Schema({
 
     # Set of artifacts relevant to release tasks
     Optional('release-artifacts'): [basestring],
 
     # information specific to the worker implementation that will run this task
     Optional('worker'): {
         Required('implementation'): basestring,
         Extra: object,
-    }
+    },
+
+    # Override the default priority for the project