Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorDaniel Varga <dvarga@mozilla.com>
Wed, 16 Jan 2019 06:57:53 +0200
changeset 514070 3b1fbe2830f3a4b385f07ae0d2403611e666a821
parent 514069 ded17dffa465c4a04bcbdb4dcb672f09d093593a (current diff)
parent 514024 e4ac2508e8edc50da4bf7ed30803338a81ba026c (diff)
child 514071 1230184adda1d000a3b339a599416c015eea4119
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
browser/app/winlauncher/LauncherResult.h
browser/app/winlauncher/NativeNt.h
browser/app/winlauncher/test/TestNativeNt.cpp
taskcluster/docker/index-task/npm-shrinkwrap.json
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1290,23 +1290,17 @@ pref("browser.library.activity-stream.en
 // The remote FxA root content URL for the Activity Stream firstrun page.
 pref("browser.newtabpage.activity-stream.fxaccounts.endpoint", "https://accounts.firefox.com/");
 
 // The pref that controls if the search shortcuts experiment is on
 pref("browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", true);
 
 // ASRouter provider configuration
 pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "{\"id\":\"cfr\",\"enabled\":true,\"type\":\"local\",\"localProvider\":\"CFRMessageProvider\",\"frequency\":{\"custom\":[{\"period\":\"daily\",\"cap\":1}]}}");
-
-#ifdef EARLY_BETA_OR_EARLIER
 pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":true,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
-#else
-pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":false,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
-#endif
-
 
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
 // (this pref has no effect if more than 6 hours have passed since the last crash)
@@ -1795,8 +1789,13 @@ pref("browser.discovery.containers.enabl
 pref("browser.discovery.sites", "addons.mozilla.org");
 
 pref("browser.engagement.recent_visited_origins.expiry", 86400); // 24 * 60 * 60 (24 hours in seconds)
 
 // Show the warning page for the new about config. Will replace general.warnOnAboutConfig.
 #ifdef NIGHTLY_BUILD
 pref("browser.aboutConfig.showWarning", true);
 #endif
+
+#if defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS)
+// Launcher process is disabled by default, will be selectively enabled via SHIELD
+pref("browser.launcherProcess.enabled", false);
+#endif // defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS)
--- a/browser/app/winlauncher/DllBlocklistWin.cpp
+++ b/browser/app/winlauncher/DllBlocklistWin.cpp
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
-#include "NativeNt.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/Types.h"
 #include "mozilla/WindowsDllBlocklist.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 
 #define MOZ_LITERAL_UNICODE_STRING(s)                                        \
   {                                                                          \
     /* Length of the string in bytes, less the null terminator */            \
     sizeof(s) - sizeof(wchar_t), /* Length of the string in bytes, including \
--- a/browser/app/winlauncher/DllBlocklistWin.h
+++ b/browser/app/winlauncher/DllBlocklistWin.h
@@ -4,17 +4,17 @@
  * 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/. */
 
 #ifndef mozilla_DllBlocklistWin_h
 #define mozilla_DllBlocklistWin_h
 
 #include <windows.h>
 
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 
 namespace mozilla {
 
 LauncherVoidResult InitializeDllBlocklistOOP(HANDLE aChildProcess);
 
 }  // namespace mozilla
 
 #endif  // mozilla_DllBlocklistWin_h
--- a/browser/app/winlauncher/ErrorHandler.cpp
+++ b/browser/app/winlauncher/ErrorHandler.cpp
@@ -1,21 +1,28 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "ErrorHandler.h"
 
+#include "mozilla/LauncherRegistryInfo.h"
+#include "mozilla/Unused.h"
+
 namespace mozilla {
 
 void HandleLauncherError(const LauncherError& aError) {
   // This is a placeholder error handler. We'll add telemetry and a fallback
   // error log in future revisions.
+
+  LauncherRegistryInfo regInfo;
+  Unused << regInfo.DisableDueToFailure();
+
   WindowsError::UniqueString msg = aError.mError.AsString();
   if (!msg) {
     return;
   }
 
   ::MessageBoxW(nullptr, msg.get(), L"Firefox", MB_OK | MB_ICONERROR);
 }
 
--- a/browser/app/winlauncher/ErrorHandler.h
+++ b/browser/app/winlauncher/ErrorHandler.h
@@ -3,18 +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 https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ErrorHandler_h
 #define mozilla_ErrorHandler_h
 
 #include "mozilla/Assertions.h"
-
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 
 namespace mozilla {
 
 /**
  * All launcher process error handling should live in the implementation of
  * this function.
  */
 void HandleLauncherError(const LauncherError& aError);
--- a/browser/app/winlauncher/LaunchUnelevated.cpp
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -3,22 +3,21 @@
 /* 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/. */
 
 #include "LaunchUnelevated.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/mscom/COMApartmentRegion.h"
 #include "mozilla/RefPtr.h"
 #include "nsWindowsHelpers.h"
 
-#include "LauncherResult.h"
-
 // For _bstr_t and _variant_t
 #include <comdef.h>
 #include <comutil.h>
 
 #include <windows.h>
 #include <exdisp.h>
 #include <objbase.h>
 #include <servprov.h>
--- a/browser/app/winlauncher/LaunchUnelevated.h
+++ b/browser/app/winlauncher/LaunchUnelevated.h
@@ -3,17 +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 https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_LaunchUnelevated_h
 #define mozilla_LaunchUnelevated_h
 
 #include "LauncherProcessWin.h"
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/Maybe.h"
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 enum class ElevationState {
   eNormalUser = 0,
   eElevated = (1 << 0),
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -8,42 +8,43 @@
 
 #include <io.h>  // For printf_stderr
 #include <string.h>
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/LauncherRegistryInfo.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/SafeMode.h"
 #include "mozilla/Sprintf.h"  // For printf_stderr
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
 #include <windows.h>
 #include <processthreadsapi.h>
 
 #include "DllBlocklistWin.h"
 #include "ErrorHandler.h"
-#include "LauncherResult.h"
 #include "LaunchUnelevated.h"
 #include "ProcThreadAttributes.h"
 
 #if defined(MOZ_LAUNCHER_PROCESS)
 #include "SameBinary.h"
 #endif  // defined(MOZ_LAUNCHER_PROCESS)
 
 /**
  * At this point the child process has been created in a suspended state. Any
  * additional startup work (eg, blocklist setup) should go here.
  *
- * @return true if browser startup should proceed, otherwise false.
+ * @return Ok if browser startup should proceed
  */
 static mozilla::LauncherVoidResult PostCreationSetup(HANDLE aChildProcess,
                                                      HANDLE aChildMainThread,
                                                      const bool aIsSafeMode) {
   // The launcher process's DLL blocking code is incompatible with ASAN because
   // it is able to execute before ASAN itself has even initialized.
   // Also, the AArch64 build doesn't yet have a working interceptor.
 #if defined(MOZ_ASAN) || defined(_M_ARM64)
@@ -139,49 +140,77 @@ static void MaybeBreakForBrowserDebuggin
   }
 
   DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
   printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n  debug me @ %lu\n\n",
                 ::GetCurrentProcessId());
   ::Sleep(pauseLenMs);
 }
 
-namespace mozilla {
-
-bool RunAsLauncherProcess(int& argc, wchar_t** argv) {
+static bool DoLauncherProcessChecks(int& argc, wchar_t** argv) {
   // NB: We run all tests in this function instead of returning early in order
   // to ensure that all side effects take place, such as clearing environment
   // variables.
   bool result = false;
 
 #if defined(MOZ_LAUNCHER_PROCESS)
-  LauncherResult<bool> isSame = IsSameBinaryAsParentProcess();
+  mozilla::LauncherResult<bool> isSame = mozilla::IsSameBinaryAsParentProcess();
   if (isSame.isOk()) {
     result = !isSame.unwrap();
   } else {
     HandleLauncherError(isSame.unwrapErr());
   }
 #endif  // defined(MOZ_LAUNCHER_PROCESS)
 
   if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
     mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
     result = true;
   }
 
-  result |=
-      CheckArg(argc, argv, L"launcher", static_cast<const wchar_t**>(nullptr),
-               CheckArgFlag::RemoveArg) == ARG_FOUND;
+  result |= mozilla::CheckArg(
+                argc, argv, L"launcher", static_cast<const wchar_t**>(nullptr),
+                mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
+
+  return result;
+}
+
+namespace mozilla {
+
+bool RunAsLauncherProcess(int& argc, wchar_t** argv) {
+  LauncherRegistryInfo::ProcessType desiredType =
+      DoLauncherProcessChecks(argc, argv)
+          ? LauncherRegistryInfo::ProcessType::Launcher
+          : LauncherRegistryInfo::ProcessType::Browser;
 
-  if (!result) {
+  // If we're looking at browser, return fast when we're a child process.
+  if (desiredType == LauncherRegistryInfo::ProcessType::Browser &&
+      mozilla::CheckArg(argc, argv, L"contentproc",
+                        static_cast<const wchar_t**>(nullptr),
+                        mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
+    return false;
+  }
+
+  LauncherRegistryInfo regInfo;
+  LauncherResult<LauncherRegistryInfo::ProcessType> runAsType =
+      regInfo.Check(desiredType);
+
+  if (runAsType.isErr()) {
+    HandleLauncherError(runAsType);
+    // If there is an error, we should always fall back to returning false
+    // for safety's sake.
+    return false;
+  }
+
+  if (runAsType.unwrap() == LauncherRegistryInfo::ProcessType::Browser) {
     // In this case, we will be proceeding to run as the browser.
     // We should check MOZ_DEBUG_BROWSER_* env vars.
     MaybeBreakForBrowserDebugging();
   }
 
-  return result;
+  return runAsType.unwrap() == LauncherRegistryInfo::ProcessType::Launcher;
 }
 
 int LauncherMain(int argc, wchar_t* argv[]) {
   // Make sure that the launcher process itself has image load policies set
   if (IsWin10AnniversaryUpdateOrLater()) {
     const DynamicallyLinkedFunctionPtr<decltype(&SetProcessMitigationPolicy)>
         pSetProcessMitigationPolicy(L"kernel32.dll",
                                     "SetProcessMitigationPolicy");
--- a/browser/app/winlauncher/SameBinary.h
+++ b/browser/app/winlauncher/SameBinary.h
@@ -2,18 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_SameBinary_h
 #define mozilla_SameBinary_h
 
-#include "LauncherResult.h"
-#include "NativeNt.h"
+#include "mozilla/LauncherResult.h"
+#include "mozilla/NativeNt.h"
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 static inline mozilla::LauncherResult<bool> IsSameBinaryAsParentProcess() {
   mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
   if (parentPid.isErr()) {
     return LAUNCHER_ERROR_FROM_RESULT(parentPid);
--- a/browser/app/winlauncher/moz.build
+++ b/browser/app/winlauncher/moz.build
@@ -4,28 +4,32 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Library('winlauncher')
 
 FORCE_STATIC_LIB = True
 
 UNIFIED_SOURCES += [
+    '/toolkit/xre/LauncherRegistryInfo.cpp',
     'DllBlocklistWin.cpp',
     'ErrorHandler.cpp',
     'LauncherProcessWin.cpp',
     'LaunchUnelevated.cpp',
 ]
 
 OS_LIBS += [
     'ntdll',
     'oleaut32',
     'ole32',
 ]
 
 TEST_DIRS += [
     'test',
 ]
 
+for var in ('MOZ_APP_BASENAME', 'MOZ_APP_VENDOR'):
+    DEFINES[var] = '"%s"' % CONFIG[var]
+
 DisableStlWrapping()
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
--- a/browser/app/winlauncher/test/TestSameBinary.cpp
+++ b/browser/app/winlauncher/test/TestSameBinary.cpp
@@ -1,21 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
-#include "NativeNt.h"
 #include "SameBinary.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/Move.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Vector.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 
--- a/browser/app/winlauncher/test/moz.build
+++ b/browser/app/winlauncher/test/moz.build
@@ -3,17 +3,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/.
 
 DisableStlWrapping()
 
 GeckoCppUnitTests(
     [
-        'TestNativeNt',
         'TestSameBinary',
     ],
     linkage=None
 )
 
 LOCAL_INCLUDES += [
     '/browser/app/winlauncher',
 ]
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -528,27 +528,29 @@ var gPrivacyPane = {
 
   /**
    * Selects the right items of the new Cookies & Site Data UI.
    */
   networkCookieBehaviorReadPrefs() {
     let behavior = Preferences.get("network.cookie.cookieBehavior").value;
     let blockCookiesMenu = document.getElementById("blockCookiesMenu");
     let deleteOnCloseCheckbox = document.getElementById("deleteOnClose");
+    let deleteOnCloseNote = document.getElementById("deleteOnCloseNote");
 
     let blockCookies = (behavior != Ci.nsICookieService.BEHAVIOR_ACCEPT);
     let cookieBehaviorLocked = Services.prefs.prefIsLocked("network.cookie.cookieBehavior");
     let blockCookiesControlsDisabled = !blockCookies || cookieBehaviorLocked;
     blockCookiesMenu.disabled = blockCookiesControlsDisabled;
 
     let completelyBlockCookies = (behavior == Ci.nsICookieService.BEHAVIOR_REJECT);
     let privateBrowsing = Preferences.get("browser.privatebrowsing.autostart").value;
     let cookieExpirationLocked = Services.prefs.prefIsLocked("network.cookie.lifetimePolicy");
     deleteOnCloseCheckbox.disabled = privateBrowsing || completelyBlockCookies ||
                                      cookieExpirationLocked;
+    deleteOnCloseNote.hidden = !privateBrowsing;
 
     switch (behavior) {
       case Ci.nsICookieService.BEHAVIOR_ACCEPT:
         break;
       case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
         blockCookiesMenu.value = "all-third-parties";
         break;
       case Ci.nsICookieService.BEHAVIOR_REJECT:
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -210,16 +210,19 @@
 
   <hbox data-subcategory="sitedata" align="baseline">
     <vbox flex="1">
       <description class="description-with-side-element" flex="1">
         <html:span id="totalSiteDataSize" class="tail-with-learn-more"></html:span>
         <label id="siteDataLearnMoreLink"
           class="learnMore text-link" data-l10n-id="sitedata-learn-more"/>
       </description>
+      <hbox flex="1" id="deleteOnCloseNote" class="info-panel">
+        <description flex="1" data-l10n-id="sitedata-delete-on-close-private-browsing" />
+      </hbox>
       <hbox id="keepRow"
             align="center">
         <checkbox id="deleteOnClose"
                   data-l10n-id="sitedata-delete-on-close"
                   preference="network.cookie.lifetimePolicy"
                   onsyncfrompreference="return gPrivacyPane.readDeleteOnClose();"
                   onsynctopreference="return gPrivacyPane.writeDeleteOnClose();"
                   flex="1" />
--- a/browser/components/preferences/in-content/tests/browser_cloud_storage.js
+++ b/browser/components/preferences/in-content/tests/browser_cloud_storage.js
@@ -1,24 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.defineModuleGetter(this, "CloudStorage",
+                               "resource://gre/modules/CloudStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "NetUtil",
-                               "resource://gre/modules/NetUtil.jsm");
 
 const DROPBOX_DOWNLOAD_FOLDER = "Dropbox";
-const DROPBOX_CONFIG_FOLDER = (AppConstants.platform === "win") ? "Dropbox" :
-                                                                  ".dropbox";
-const DROPBOX_KEY = "Dropbox";
 const CLOUD_SERVICES_PREF = "cloud.services.";
 
 function create_subdir(dir, subdirname) {
   let subdir = dir.clone();
   subdir.append(subdirname);
   if (subdir.exists()) {
     subdir.remove(true);
   }
@@ -55,70 +52,54 @@ function registerFakePath(key, folderNam
     dirsvc.undefine(key);
     if (originalFile) {
       dirsvc.set(key, originalFile);
     }
   });
 }
 
 async function mock_dropbox() {
-  let discoveryFile = null;
-  if (AppConstants.platform === "win") {
-    discoveryFile = FileUtils.getFile("LocalAppData", [DROPBOX_CONFIG_FOLDER]);
-  } else {
-    discoveryFile = FileUtils.getFile("Home", [DROPBOX_CONFIG_FOLDER]);
-  }
-  discoveryFile.append("info.json");
-  if (!discoveryFile.exists()) {
-    discoveryFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  }
-  console.log(discoveryFile.path);
   // Mock Dropbox Download folder in Home directory
   let downloadFolder = FileUtils.getFile("Home", [DROPBOX_DOWNLOAD_FOLDER, "Downloads"]);
   if (!downloadFolder.exists()) {
     downloadFolder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   }
   console.log(downloadFolder.path);
 
   registerCleanupFunction(() => {
-    if (discoveryFile.exists()) {
-      discoveryFile.remove(true);
-    }
     if (downloadFolder.exists()) {
       downloadFolder.remove(true);
     }
   });
-
-  let data = '{"personal": {"path": "Test"}}';
-  let ostream = FileUtils.openSafeFileOutputStream(discoveryFile);
-  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                  createInstance(Ci.nsIScriptableUnicodeConverter);
-  converter.charset = "UTF-8";
-  let istream = converter.convertToInputStream(data);
-  return new Promise(resolve => {
-    NetUtil.asyncCopy(istream, ostream, resolve);
-  });
 }
 
 add_task(async function setup() {
-  // Create mock Dropbox discovery and download folder for cloudstorage API
-  // to detect dropbox provider app
+  // Create mock Dropbox download folder for cloudstorage API
   // Set prefs required to display second radio option
   // 'Save to Dropbox' under Downloads
+  let folderName = "CloudStorage";
+  registerFakePath("Home", folderName);
+  await mock_dropbox();
   await SpecialPowers.pushPrefEnv({set: [
     [CLOUD_SERVICES_PREF + "api.enabled", true],
     [CLOUD_SERVICES_PREF + "storage.key", "Dropbox"],
   ]});
+});
 
-  let folderName = "CloudStorage";
-  registerFakePath("Home", folderName);
-  registerFakePath("LocalAppData", folderName);
-  let status = await mock_dropbox();
-  ok(Components.isSuccessCode(status),
-    "Cloud Storage: dropbox mockup should be created successfully. status: " + status);
+add_task(async function test_initProvider() {
+  // Get preferred provider key
+  let preferredProvider = await CloudStorage.getPreferredProvider();
+  is(preferredProvider, "Dropbox", "Cloud Storage preferred provider key");
+
+  let isInitialized = await CloudStorage.init();
+  is(isInitialized, true, "Providers Metadata successfully initialized");
+
+  // Get preferred provider in use display name
+  let providerDisplayName = await CloudStorage.getProviderIfInUse();
+  is(providerDisplayName, "Dropbox", "Cloud Storage preferred provider display name");
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
   let doc = gBrowser.selectedBrowser.contentDocument;
   let saveWhereOptions = doc.getElementById("saveWhere");
   let saveToCloud = doc.getElementById("saveToCloud");
   is(saveWhereOptions.itemCount, 3, "Radio options count");
--- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -107,16 +107,17 @@ function test_dependent_elements(win) {
   historymode.value = "custom";
   controlChanged(historymode);
   expect_disabled(false);
   check_independents(false);
 }
 
 function test_dependent_cookie_elements(win) {
   let deleteOnCloseCheckbox = win.document.getElementById("deleteOnClose");
+  let deleteOnCloseNote = win.document.getElementById("deleteOnCloseNote");
   let blockCookiesMenu = win.document.getElementById("blockCookiesMenu");
 
   let controls = [blockCookiesMenu, deleteOnCloseCheckbox];
   controls.forEach(function(control) {
     ok(control, "the dependent cookie controls should exist");
   });
   let blockCookiesCheckbox = win.document.getElementById("contentBlockingBlockCookiesCheckbox");
   ok(blockCookiesCheckbox, "the block cookies checkbox should exist");
@@ -131,42 +132,50 @@ function test_dependent_cookie_elements(
   blockCookiesCheckbox.checked = true;
   controlChanged(blockCookiesCheckbox);
   expect_disabled(false);
 
   blockCookiesCheckbox.checked = false;
   controlChanged(blockCookiesCheckbox);
   expect_disabled(true, [blockCookiesMenu]);
   expect_disabled(false, [deleteOnCloseCheckbox]);
+  is_element_hidden(deleteOnCloseNote,
+    "The notice for delete on close in permanent private browsing mode should be hidden.");
 
   blockCookiesMenu.value = "always";
   controlChanged(blockCookiesMenu);
   expect_disabled(true, [deleteOnCloseCheckbox]);
   expect_disabled(false, [blockCookiesMenu]);
+  is_element_hidden(deleteOnCloseNote,
+    "The notice for delete on close in permanent private browsing mode should be hidden.");
 
   if (win.contentBlockingCookiesAndSiteDataRejectTrackersEnabled) {
     blockCookiesMenu.value = "trackers";
   } else {
     blockCookiesMenu.value = "unvisited";
   }
   controlChanged(blockCookiesMenu);
   expect_disabled(false);
 
   let historymode = win.document.getElementById("historyMode");
 
   // The History mode setting for "never remember history" should still
   // disable the "keep cookies until..." menu.
   historymode.value = "dontremember";
   controlChanged(historymode);
   expect_disabled(true, [deleteOnCloseCheckbox]);
+  is_element_visible(deleteOnCloseNote,
+    "The notice for delete on close in permanent private browsing mode should be visible.");
   expect_disabled(false, [blockCookiesMenu]);
 
   historymode.value = "remember";
   controlChanged(historymode);
   expect_disabled(false);
+  is_element_hidden(deleteOnCloseNote,
+    "The notice for delete on close in permanent private browsing mode should be hidden.");
 }
 
 function test_dependent_clearonclose_elements(win) {
   let historymode = win.document.getElementById("historyMode");
   ok(historymode, "history mode menulist should exist");
   let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
   ok(pbautostart, "the private browsing auto-start checkbox should exist");
   let alwaysclear = win.document.getElementById("alwaysClear");
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -257,17 +257,22 @@ class UrlbarView {
     item.appendChild(content);
 
     let typeIcon = this._createElement("span");
     typeIcon.className = "urlbarView-type-icon";
     content.appendChild(typeIcon);
 
     let favicon = this._createElement("img");
     favicon.className = "urlbarView-favicon";
-    favicon.src = result.payload.icon || "chrome://mozapps/skin/places/defaultFavicon.svg";
+    if (result.type == UrlbarUtils.MATCH_TYPE.SEARCH ||
+        result.type == UrlbarUtils.MATCH_TYPE.KEYWORD) {
+      favicon.src = "chrome://browser/skin/search-glass.svg";
+    } else {
+      favicon.src = result.payload.icon || "chrome://mozapps/skin/places/defaultFavicon.svg";
+    }
     content.appendChild(favicon);
 
     let title = this._createElement("span");
     title.className = "urlbarView-title";
     this._addTextContentWithHighlights(
       title,
       ...(result.title ?
           [result.title, result.titleHighlights] :
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -755,16 +755,18 @@ sitedata-total-size-calculating = Calculating site data and cache size…
 sitedata-total-size = Your stored cookies, site data and cache are currently using { $value } { $unit } of disk space.
 
 sitedata-learn-more = Learn more
 
 sitedata-delete-on-close =
     .label = Delete cookies and site data when { -brand-short-name } is closed
     .accesskey = c
 
+sitedata-delete-on-close-private-browsing = In permanent private browsing mode, cookies and site data will always be cleared when { -brand-short-name } is closed.
+
 sitedata-allow-cookies-option =
     .label = Accept cookies and site data
     .accesskey = A
 
 sitedata-disallow-cookies-option =
     .label = Block cookies and site data
     .accesskey = B
 
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -187,31 +187,38 @@ button > hbox > label {
 }
 
 /* General Pane */
 
 #isDefaultLabel {
   font-weight: 600;
 }
 
+.info-panel,
 .extension-controlled {
   margin-top: 18px !important;
   margin-bottom: 18px !important;
   background: var(--grey-20);
   border-radius: 5px;
-  -moz-padding-end: 10px;
+  padding-inline-end: 10px;
 }
 
+.info-panel > description,
 .extension-controlled > description {
   line-height: 16px;
   background: url(chrome://browser/skin/identity-icon.svg) 10px 14px no-repeat;
   padding: 10px;
-  -moz-padding-start: 30px;
+  padding-inline-start: 32px;
 }
 
+.info-panel > description {
+  line-height: unset;
+}
+
+.info-panel > description:-moz-locale-dir(rtl),
 .extension-controlled > description:-moz-locale-dir(rtl) {
   background-position-x: right 10px;
 }
 
 .extension-controlled-icon {
   height: 20px;
   margin: 2px 0 6px;
   vertical-align: middle;
--- a/browser/themes/shared/incontentprefs/privacy.css
+++ b/browser/themes/shared/incontentprefs/privacy.css
@@ -239,8 +239,13 @@
 .reject-trackers-warning-icon:-moz-locale-dir(rtl) {
   background-position-x: right 0;
 }
 
 #contentBlockingChangeCookieSettings {
   padding: 0.25em 0.75em;
   margin: 4px 8px;
 }
+
+#deleteOnCloseNote {
+  margin-bottom: 0 !important; /* Overrides .info-panel margin-bottom. */
+  margin-inline-end: 32px;
+}
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -1203,46 +1203,8 @@ def project_flag(env=None, set_for_old_c
         add_old_configure_assignment(env, option_implementation)
 
 # milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set.
 
 
 @depends(milestone)
 def enabled_in_nightly(milestone):
     return milestone.is_nightly
-
-# Set the MOZ_CONFIGURE_OPTIONS variable with all the options that
-# were passed somehow (environment, command line, mozconfig)
-
-
-@dependable
-@imports(_from='mozbuild.shellutil', _import='quote')
-@imports('__sandbox__')
-def all_configure_options():
-    result = []
-    previous = None
-    for option in __sandbox__._options.itervalues():
-        # __sandbox__._options contains items for both option.name and
-        # option.env. But it's also an OrderedDict, meaning both are
-        # consecutive.
-        # Also ignore OLD_CONFIGURE and MOZCONFIG because they're not
-        # interesting.
-        if option == previous or option.env in ('OLD_CONFIGURE', 'MOZCONFIG'):
-            continue
-        previous = option
-        value = __sandbox__._value_for(option)
-        # We only want options that were explicitly given on the command
-        # line, the environment, or mozconfig, and that differ from the
-        # defaults.
-        if (value is not None and value.origin not in ('default', 'implied') and
-                value != option.default):
-            result.append(__sandbox__._raw_options[option])
-        # We however always include options that are sent to old configure
-        # because we don't know their actual defaults. (Keep the conditions
-        # separate for ease of understanding and ease of removal)
-        elif (option.help == 'Help missing for old configure options' and
-                option in __sandbox__._raw_options):
-            result.append(__sandbox__._raw_options[option])
-
-    return quote(*result)
-
-
-set_config('MOZ_CONFIGURE_OPTIONS', all_configure_options)
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -521,22 +521,24 @@ Inspector.prototype = {
    *
    * @return {Boolean} true if the inspector should be in landscape mode.
    */
   useLandscapeMode: function() {
     if (!this.panelDoc) {
       return true;
     }
 
-    const { clientWidth } = this.panelDoc.getElementById("inspector-splitter-box");
+    const splitterBox = this.panelDoc.getElementById("inspector-splitter-box");
+    const { width } = window.windowUtils.getBoundsWithoutFlushing(splitterBox);
+
     return this.is3PaneModeEnabled &&
            (this.toolbox.hostType == Toolbox.HostType.LEFT ||
             this.toolbox.hostType == Toolbox.HostType.RIGHT) ?
-      clientWidth > SIDE_PORTAIT_MODE_WIDTH_THRESHOLD :
-      clientWidth > PORTRAIT_MODE_WIDTH_THRESHOLD;
+      width > SIDE_PORTAIT_MODE_WIDTH_THRESHOLD :
+      width > PORTRAIT_MODE_WIDTH_THRESHOLD;
   },
 
   /**
    * Build Splitter located between the main and side area of
    * the Inspector panel.
    */
   setupSplitter: function() {
     const { width, height, splitSidebarWidth } = this.getSidebarSize();
@@ -579,27 +581,17 @@ Inspector.prototype = {
   },
 
   _onLazyPanelResize: async function() {
     // We can be called on a closed window because of the deferred task.
     if (window.closed) {
       return;
     }
 
-    // Use window.top because promiseDocumentFlushed() in a subframe doesn't
-    // work, see https://bugzilla.mozilla.org/show_bug.cgi?id=1441173
-    const useLandscapeMode = await window.top.promiseDocumentFlushed(() => {
-      return this.useLandscapeMode();
-    });
-
-    if (window.closed) {
-      return;
-    }
-
-    this.splitBox.setState({ vert: useLandscapeMode });
+    this.splitBox.setState({ vert: this.useLandscapeMode() });
     this.emit("inspector-resize");
   },
 
   /**
    * If Toolbox width is less than 600 px, the splitter changes its mode
    * to `horizontal` to support portrait view.
    */
   onPanelWindowResize: function() {
--- a/devtools/client/themes/images/add.svg
+++ b/devtools/client/themes/images/add.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
-  <path d="M8 8v5.5c0 .276-.224.5-.5.5s-.5-.224-.5-.5V8H1.5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5H7V1.5c0-.276.224-.5.5-.5s.5.224.5.5V7h5.5c.276 0 .5.224.5.5s-.224.5-.5.5H8z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M14 7H9V2a1 1 0 0 0-2 0v5H2a1 1 0 1 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2z"/>
 </svg>
--- a/devtools/client/themes/images/clear.svg
+++ b/devtools/client/themes/images/clear.svg
@@ -1,7 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
-  <path d="M6 3h3V2H6v1zm-5 .5c0-.3.2-.5.5-.5h12a.5.5 0 1 1 0 1h-12a.5.5 0 0 1-.5-.5zM5 3V2c0-.6.4-1 1-1h3c.6 0 1 .4 1 1v1H5zm1 8V6a.5.5 0 0 0-1 0v5a.5.5 0 1 0 1 0zm2 0V6a.5.5 0 0 0-1 0v5a.5.5 0 1 0 1 0zm2 0V6a.5.5 0 0 0-1 0v5a.5.5 0 1 0 1 0z"/>
-  <path d="M4 4v9h7V4H4zm0-1h7c.6 0 1 .4 1 1v9c0 .6-.4 1-1 1H4a1 1 0 0 1-1-1V4c0-.6.4-1 1-1z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M6.5 12a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5zm2 0a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5zm2 0a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5zM14 2h-3.05a2.5 2.5 0 0 0-4.9 0H3a1 1 0 0 0 0 2v9a3 3 0 0 0 3 3h5a3 3 0 0 0 3-3V4a1 1 0 0 0 0-2zM8.5 1a1.489 1.489 0 0 1 1.391 1H7.109A1.489 1.489 0 0 1 8.5 1zM12 13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4h7z"/>
 </svg>
--- a/devtools/client/themes/images/import.svg
+++ b/devtools/client/themes/images/import.svg
@@ -1,8 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
-  <path d="M7.864 1.417c-.123-.13-.305-.185-.48-.144-.173.04-.312.172-.363.343-.05.17-.007.357.116.487l4 4.243c.19.2.506.21.707.02.2-.188.21-.505.02-.706l-4-4.243z"/>
-  <path d="M7.136 1.414l-4 4.243c-.19.2-.18.518.02.707.202.19.52.18.708-.02l4-4.244c.123-.13.166-.316.115-.487-.052-.17-.19-.302-.365-.343-.174-.04-.356.014-.48.144zM1.5 8c-.276 0-.5.224-.5.5v5c0 .2.224.5.5.5h12c.276 0 .5-.3.5-.5v-5c0-.276-.224-.5-.5-.5h-3c-.28 0-.5.224-.5.5s.22.5.5.5H13v4H2V9h2.5c.27 0 .5-.224.5-.5S4.77 8 4.5 8h-3z"/>
-  <path d="M7 2v9c0 .276.224.5.5.5s.5-.224.5-.5V2c0-.276-.224-.5-.5-.5S7 1.724 7 2z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M13.374 1H4.623A2.83 2.83 0 0 0 2 4v4h2V4a.928.928 0 0 1 .833-1h8.333A.928.928 0 0 1 14 4v8a.928.928 0 0 1-.833 1H4.833A.928.928 0 0 1 4 12v-1H2v1a2.833 2.833 0 0 0 2.627 3h9.623A1.888 1.888 0 0 0 16 13V4a2.833 2.833 0 0 0-2.626-3zM7.146 11.146a.5.5 0 1 0 .707.707l2-2a.5.5 0 0 0 0-.707l-2-2a.5.5 0 0 0-.707.707L8.293 9H1.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 0-1 0v2A1.5 1.5 0 0 0 1.5 10h6.793z"/>
 </svg>
--- a/devtools/client/themes/images/profiler-stopwatch.svg
+++ b/devtools/client/themes/images/profiler-stopwatch.svg
@@ -1,11 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
-  <g fill-rule="evenodd">
-    <path d="M15 9.004C14.51 12.394 11.578 15 8.035 15 4.15 15 1 11.866 1 8s3.15-7 7.036-7c1.941 0 3.7.783 4.972 2.048l-.709.709A6.027 6.027 0 0 0 8.036 2c-3.33 0-6.03 2.686-6.03 6s2.7 6 6.03 6a6.023 6.023 0 0 0 5.946-4.993l1.017-.003z"/>
-    <path d="M4.137 9H3.1a5.002 5.002 0 0 0 9.8 0h-.965a4.023 4.023 0 0 1-3.9 3 4.023 4.023 0 0 1-3.898-3z" fill-opacity=".5"/>
-    <path d="M8.036 11a2.994 2.994 0 0 0 2.987-3c0-1.657-1.338-3-2.987-3a2.994 2.994 0 0 0-2.988 3c0 1.657 1.338 3 2.988 3zm0-1c1.11 0 2.011-.895 2.011-2s-.9-2-2.011-2c-1.111 0-2.012.895-2.012 2s.9 2 2.012 2z"/>
-    <path d="M10.354 6.354l4-4a.5.5 0 0 0-.708-.708l-4 4a.5.5 0 1 0 .708.708z"/>
-  </g>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M10.85 6.85a.5.5 0 0 0-.7-.7l-2.5 2.5.7.7 2.5-2.5zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1zM8 4a5 5 0 1 0 0 10A5 5 0 0 0 8 4zM1 9a7 7 0 1 1 14 0A7 7 0 0 1 1 9z"/>
 </svg>
--- a/devtools/client/themes/images/reload.svg
+++ b/devtools/client/themes/images/reload.svg
@@ -1,7 +1,6 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill">
-    <path d="M13.917 7C13.44 4.162 10.973 2 8 2 4.686 2 2 4.686 2 8s2.686 6 6 6c2.22 0 4.16-1.207 5.197-3H12c-.912 1.214-2.364 2-4 2-2.76 0-5-2.24-5-5s2.24-5 5-5c2.42 0 4.437 1.718 4.9 4h1.017z"/>
-    <path d="M14 1L8 7h6V1zm-1 1L9 6h4V2z" fill-rule="evenodd"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M15 1a1 1 0 0 0-1 1v2.418A6.995 6.995 0 1 0 8 15a6.954 6.954 0 0 0 4.95-2.05 1 1 0 0 0-1.414-1.414A5.019 5.019 0 1 1 12.549 6H10a1 1 0 0 0 0 2h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/>
 </svg>
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -19,17 +19,16 @@
 // loading its content, to reduce CPU/memory/IO contention.
 #define DEFAULT_ALLOCATE_DELAY 1000
 
 using namespace mozilla;
 using namespace mozilla::hal;
 using namespace mozilla::dom;
 
 namespace mozilla {
-
 /**
  * This singleton class implements the static methods on
  * PreallocatedProcessManager.
  */
 class PreallocatedProcessManagerImpl final : public nsIObserver {
  public:
   static PreallocatedProcessManagerImpl* Singleton();
 
@@ -39,16 +38,17 @@ class PreallocatedProcessManagerImpl fin
   // See comments on PreallocatedProcessManager for these methods.
   void AddBlocker(ContentParent* aParent);
   void RemoveBlocker(ContentParent* aParent);
   already_AddRefed<ContentParent> Take();
   bool Provide(ContentParent* aParent);
 
  private:
   static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
+  static uint32_t sPrelaunchDelayMS;
 
   PreallocatedProcessManagerImpl();
   ~PreallocatedProcessManagerImpl();
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
 
   void Init();
 
   bool CanAllocate();
@@ -69,16 +69,18 @@ class PreallocatedProcessManagerImpl fin
   RefPtr<ContentParent> mPreallocatedProcess;
   nsTHashtable<nsUint64HashKey> mBlockers;
 
   bool IsEmpty() const { return !mPreallocatedProcess && !mLaunchInProgress; }
 };
 
 /* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
     PreallocatedProcessManagerImpl::sSingleton;
+/* static */ uint32_t
+    PreallocatedProcessManagerImpl::sPrelaunchDelayMS = 0;
 
 /* static */ PreallocatedProcessManagerImpl*
 PreallocatedProcessManagerImpl::Singleton() {
   MOZ_ASSERT(NS_IsMainThread());
   if (!sSingleton) {
     sSingleton = new PreallocatedProcessManagerImpl();
     sSingleton->Init();
     ClearOnShutdown(&sSingleton);
@@ -94,16 +96,20 @@ PreallocatedProcessManagerImpl::Prealloc
 
 PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
   // This shouldn't happen, because the promise callbacks should
   // hold strong references, but let't make absolutely sure:
   MOZ_RELEASE_ASSERT(!mLaunchInProgress);
 }
 
 void PreallocatedProcessManagerImpl::Init() {
+  Preferences::AddUintVarCache(
+    &sPrelaunchDelayMS,
+    "dom.ipc.processPrelaunch.delayMs",
+    DEFAULT_ALLOCATE_DELAY);
   Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
   // We have to respect processCount at all time. This is especially important
   // for testing.
   Preferences::AddStrongObserver(this, "dom.ipc.processCount");
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->AddObserver(this, "ipc:content-shutdown",
                     /* weakRef = */ false);
@@ -228,18 +234,17 @@ bool PreallocatedProcessManagerImpl::Can
 void PreallocatedProcessManagerImpl::AllocateAfterDelay() {
   if (!mEnabled) {
     return;
   }
 
   NS_DelayedDispatchToCurrentThread(
       NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this,
                         &PreallocatedProcessManagerImpl::AllocateOnIdle),
-      Preferences::GetUint("dom.ipc.processPrelaunch.delayMs",
-                           DEFAULT_ALLOCATE_DELAY));
+      sPrelaunchDelayMS);
 }
 
 void PreallocatedProcessManagerImpl::AllocateOnIdle() {
   if (!mEnabled) {
     return;
   }
 
   NS_IdleDispatchToCurrentThread(
--- a/dom/media/tests/mochitest/test_peerConnection_stats.html
+++ b/dom/media/tests/mochitest/test_peerConnection_stats.html
@@ -40,24 +40,24 @@ const statsExpectedByType = {
     ],
     unimplemented: [
       "mediaTrackId",
       "transportId",
       "codecId",
       "packetsDiscarded",
       "associateStatsId",
       "sliCount",
-      "qpSum",
       "packetsRepaired",
       "fractionLost",
       "burstPacketsLost",
       "burstLossCount",
       "burstDiscardCount",
       "gapDiscardRate",
       "gapLossRate",
+      "qpSum", // Not yet implemented for inbound media, see bug 1519590
     ],
     deprecated: [
       "mozRtt",
       "isRemote",
     ],
   },
   "outbound-rtp": {
     expected: [
@@ -78,23 +78,23 @@ const statsExpectedByType = {
       "droppedFrames",
       "bitrateMean",
       "bitrateStdDev",
       "framerateMean",
       "framerateStdDev",
       "framesEncoded",
       "firCount",
       "pliCount",
+      "qpSum",
     ],
     unimplemented: [
       "mediaTrackId",
       "transportId",
       "codecId",
       "sliCount",
-      "qpSum",
       "targetBitrate",
     ],
     deprecated: [
       "isRemote",
     ],
   },
   "remote-inbound-rtp": {
     expected: [
@@ -116,17 +116,16 @@ const statsExpectedByType = {
     ],
     unimplemented: [
       "mediaTrackId",
       "transportId",
       "codecId",
       "packetsDiscarded",
       "associateStatsId",
       "sliCount",
-      "qpSum",
       "packetsRepaired",
       "fractionLost",
       "burstPacketsLost",
       "burstLossCount",
       "burstDiscardCount",
       "gapDiscardRate",
       "gapLossRate",
     ],
@@ -150,17 +149,16 @@ const statsExpectedByType = {
     optional: [
       "nackCount",
     ],
     unimplemented: [
       "mediaTrackId",
       "transportId",
       "codecId",
       "sliCount",
-      "qpSum",
       "targetBitrate",
     ],
     deprecated: [
       "isRemote",
     ],
   },
   "csrc": { skip: true },
   "codec": { skip: true },
@@ -505,16 +503,17 @@ var pedanticChecks = report => {
         // framerateStdDev
         // special exception, TODO: Bug 1341533
         if (stat.framerateStdDev !== undefined) {
           // TODO: uncomment when Bug 1341533 lands
           // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120,
           //   stat.type + ".framerateStdDev is sane. value="
           //   + stat.framerateStdDev);
         }
+
       }
     } else if (stat.type == "remote-inbound-rtp") {
       // roundTripTime
       ok(stat.roundTripTime >= 0, stat.type + ".roundTripTime is sane with" +
           "value of:" + stat.roundTripTime);
       //
       // Required fields
       //
@@ -613,16 +612,21 @@ var pedanticChecks = report => {
         ok(stat.droppedFrames >= 0 && stat.droppedFrames < 100000,
           stat.type + ".droppedFrames is a sane number. value="
           + stat.droppedFrames);
 
         // framesEncoded
         ok(stat.framesEncoded >= 0 && stat.framesEncoded < 100000, stat.type
           + ".framesEncoded is a sane number for a short test. value="
           + stat.framesEncoded);
+
+        // qpSum
+        // techinically optional but should be supported for all of our codecs (on the encode side, see bug 1519590)
+        ok(stat.qpSum >= 0,
+          `${stat.type} qpSum is a sane number. value=${stat.qpSum}`);
       }
     } else if (stat.type == "remote-outbound-rtp") {
       //
       // Required fields
       //
 
       // packetsSent
       ok(stat.packetsSent > 0 && stat.packetsSent < 10000,
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -27,33 +27,34 @@ dictionary RTCStats {
   RTCStatsType type;
   DOMString id;
 };
 
 dictionary RTCRtpStreamStats : RTCStats {
   unsigned long ssrc;
   DOMString mediaType;
   DOMString kind;
+  DOMString transportId;
+  DOMString codecId;
+  // Local only measurements, RTCP related but not communicated via RTCP. Not
+  // present in RTCP case. See Bug 1367562
+  unsigned long firCount;
+  unsigned long pliCount;
+  unsigned long nackCount;
+  unsigned long long qpSum;
+
   DOMString remoteId; // See Bug 1515716
   DOMString localId;  // See Bug 1515716
   DOMString mediaTrackId;
-  DOMString transportId;
-  DOMString codecId;
 
   // Video encoder/decoder measurements, not present in RTCP case
   double bitrateMean;
   double bitrateStdDev;
   double framerateMean;
   double framerateStdDev;
-
-  // Local only measurements, RTCP related but not communicated via RTCP. Not
-  // present in RTCP case.
-  unsigned long firCount;
-  unsigned long pliCount;
-  unsigned long nackCount;
 };
 
 dictionary RTCInboundRTPStreamStats : RTCRtpStreamStats {
   unsigned long packetsReceived;
   unsigned long long bytesReceived;
   double jitter;
   unsigned long packetsLost;
   long roundTripTime;
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -484,16 +484,31 @@ partial interface Window {
    *
    *   The callback will be called asynchronously if a flush is pending.
    *
    * The expected execution order is that all pending callbacks will
    * be fired first (and in the order that they were queued) and then the
    * Promise resolution handlers will all be invoked later on during the
    * next microtask checkpoint.
    *
+   * Using window.top.promiseDocumentFlushed in combination with a callback
+   * that is querying items in a window that might be swapped out via
+   * nsFrameLoader::SwapWithOtherLoader is highly discouraged. For example:
+   *
+   *   let result = await window.top.promiseDocumentFlushed(() => {
+   *     return window.document.body.getBoundingClientRect();
+   *   });
+   *
+   *   If "window" might get swapped out via nsFrameLoader::SwapWithOtherLoader
+   *   at any time, then the callback might get called when the new host window
+   *   will still incur layout flushes, since it's only the original host window
+   *   that's being monitored via window.top.promiseDocumentFlushed.
+   *
+   *   See bug 1519407 for further details.
+   *
    * promiseDocumentFlushed does not support re-entrancy - so calling it from
    * within a promiseDocumentFlushed callback will result in the inner call
    * throwing an NS_ERROR_FAILURE exception, and the outer Promise rejecting
    * with that exception.
    *
    * The callback function *must not make any changes which would require
    * a style or layout flush*.
    *
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -238,16 +238,17 @@ class LayerManager : public FrameRecorde
   typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
 
  public:
   LayerManager()
       : mDestroyed(false),
         mSnapEffectiveTransforms(true),
         mId(0),
         mInTransaction(false),
+        mContainsSVG(false),
         mPaintedPixelCount(0) {}
 
   /**
    * Release layers and resources held by this layer manager, and mark
    * it as destroyed.  Should do any cleanup necessary in preparation
    * for its widget going away.  After this call, only user data calls
    * are valid on the layer manager.
    */
@@ -737,16 +738,18 @@ class LayerManager : public FrameRecorde
     MOZ_ASSERT(mPayload.Length() < 10000);
   }
   void RegisterPayload(const InfallibleTArray<CompositionPayload>& aPayload) {
     mPayload.AppendElements(aPayload);
     MOZ_ASSERT(mPayload.Length() < 10000);
   }
   void PayloadPresented();
 
+  void SetContainsSVG(bool aContainsSVG) { mContainsSVG = aContainsSVG; }
+
  protected:
   RefPtr<Layer> mRoot;
   gfx::UserData mUserData;
   bool mDestroyed;
   bool mSnapEffectiveTransforms;
 
   nsIntRegion mRegionToClear;
 
@@ -758,16 +761,19 @@ class LayerManager : public FrameRecorde
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
 
   // Print interesting information about this into layerscope packet.
   // Internally used to implement Dump().
   virtual void DumpPacket(layerscope::LayersPacket* aPacket);
 
   uint64_t mId;
   bool mInTransaction;
+
+  // Used for tracking CONTENT_FRAME_TIME_WITH_SVG
+  bool mContainsSVG;
   // The time when painting most recently finished. This is recorded so that
   // we can time any play-pending animations from this point.
   TimeStamp mAnimationReadyTime;
   // The count of pixels that were painted in the current transaction.
   uint32_t mPaintedPixelCount;
   // The payload associated with currently pending painting work, for
   // client layer managers that typically means payload that is part of the
   // 'upcoming transaction', for HostLayerManagers this typically means
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -691,17 +691,17 @@ void ClientLayerManager::ForwardTransact
 
   // forward this transaction's changeset to our LayerManagerComposite
   bool sent = false;
   bool ok = mForwarder->EndTransaction(
       mRegionToClear, mLatestTransactionId, aScheduleComposite,
       mPaintSequenceNumber, mIsRepeatTransaction,
       mTransactionIdAllocator->GetVsyncId(),
       mTransactionIdAllocator->GetVsyncStart(), refreshStart, mTransactionStart,
-      mURL, &sent, mPayload);
+      mContainsSVG, mURL, &sent, mPayload);
 
   if (ok) {
     if (sent) {
       // Our payload has now been dispatched.
       mPayload.Clear();
       mNeedsComposite = false;
     }
   } else if (HasShadowManager()) {
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2495,20 +2495,24 @@ int32_t RecordContentFrameTime(
     // child pipelines contained within a render, after it finishes, but I
     // can't see how to query what child pipeline would have been rendered,
     // when we choose to not do it.
     if (fracLatencyNorm < 200) {
       // Success
       Telemetry::AccumulateCategorical(
           LABELS_CONTENT_FRAME_TIME_REASON::OnTime);
     } else {
-      if (aCompositeId == VsyncId() || aTxnId >= aCompositeId) {
-        // Vsync ids are nonsensical, possibly something got trigged from
+      if (aCompositeId == VsyncId()) {
+        // aCompositeId is 0, possibly something got trigged from
         // outside vsync?
         Telemetry::AccumulateCategorical(
+            LABELS_CONTENT_FRAME_TIME_REASON::NoVsyncNoId);
+      } else if (aTxnId >= aCompositeId) {
+        // Vsync ids are nonsensical, maybe we're trying to catch up?
+        Telemetry::AccumulateCategorical(
             LABELS_CONTENT_FRAME_TIME_REASON::NoVsync);
       } else if (aCompositeId - aTxnId > 1) {
         // Composite started late (and maybe took too long as well)
         if (aFullPaintTime >= TimeDuration::FromMilliseconds(20)) {
           Telemetry::AccumulateCategorical(
               LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLong);
         } else if (aFullPaintTime >= TimeDuration::FromMilliseconds(10)) {
           Telemetry::AccumulateCategorical(
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -388,17 +388,18 @@ void CrossProcessCompositorBridgeParent:
       Telemetry::CONTENT_FULL_PAINT_TIME,
       static_cast<uint32_t>(
           (endTime - aInfo.transactionStart()).ToMilliseconds()));
 
   RegisterPayload(aLayerTree, aInfo.payload());
 
   aLayerTree->SetPendingTransactionId(
       aInfo.id(), aInfo.vsyncId(), aInfo.vsyncStart(), aInfo.refreshStart(),
-      aInfo.transactionStart(), endTime, aInfo.url(), aInfo.fwdTime());
+      aInfo.transactionStart(), endTime, aInfo.containsSVG(), aInfo.url(),
+      aInfo.fwdTime());
 }
 
 void CrossProcessCompositorBridgeParent::DidCompositeLocked(
     LayersId aId, const VsyncId& aVsyncId, TimeStamp& aCompositeStart,
     TimeStamp& aCompositeEnd) {
   sIndirectLayerTreesLock->AssertCurrentThreadOwns();
   if (LayerTransactionParent* layerTree = sIndirectLayerTrees[aId].mLayerTree) {
     TransactionId transactionId =
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -884,17 +884,17 @@ bool LayerTransactionParent::IsSameProce
   return OtherPid() == base::GetCurrentProcId();
 }
 
 TransactionId LayerTransactionParent::FlushTransactionId(
     const VsyncId& aId, TimeStamp& aCompositeEnd) {
   if (mId.IsValid() && mPendingTransaction.IsValid() && !mVsyncRate.IsZero()) {
     RecordContentFrameTime(mTxnVsyncId, mVsyncStartTime, mTxnStartTime, aId,
                            aCompositeEnd, mTxnEndTime - mTxnStartTime,
-                           mVsyncRate, false, false);
+                           mVsyncRate, mContainsSVG, false);
   }
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   if (mPendingTransaction.IsValid()) {
     if (mRefreshStartTime) {
       int32_t latencyMs =
           lround((aCompositeEnd - mRefreshStartTime).ToMilliseconds());
       printf_stderr(
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -71,25 +71,26 @@ class LayerTransactionParent final : pub
 
   bool IsSameProcess() const override;
 
   const TransactionId& GetPendingTransactionId() { return mPendingTransaction; }
   void SetPendingTransactionId(TransactionId aId, const VsyncId& aVsyncId,
                                const TimeStamp& aVsyncStartTime,
                                const TimeStamp& aRefreshStartTime,
                                const TimeStamp& aTxnStartTime,
-                               const TimeStamp& aTxnEndTime,
+                               const TimeStamp& aTxnEndTime, bool aContainsSVG,
                                const nsCString& aURL,
                                const TimeStamp& aFwdTime) {
     mPendingTransaction = aId;
     mTxnVsyncId = aVsyncId;
     mVsyncStartTime = aVsyncStartTime;
     mRefreshStartTime = aRefreshStartTime;
     mTxnStartTime = aTxnStartTime;
     mTxnEndTime = aTxnEndTime;
+    mContainsSVG = aContainsSVG;
     mTxnURL = aURL;
     mFwdTime = aFwdTime;
   }
   TransactionId FlushTransactionId(const VsyncId& aId,
                                    TimeStamp& aCompositeEnd);
 
   // CompositableParentManager
   void SendAsyncMessage(
@@ -210,16 +211,17 @@ class LayerTransactionParent final : pub
   TransactionId mPendingTransaction;
   VsyncId mTxnVsyncId;
   TimeStamp mVsyncStartTime;
   TimeStamp mRefreshStartTime;
   TimeStamp mTxnStartTime;
   TimeStamp mTxnEndTime;
   TimeStamp mFwdTime;
   nsCString mTxnURL;
+  bool mContainsSVG;
 
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
   // live shadow layers, because some of them might be orphaned from
   // the layer tree.  This happens in Destroy() above.  After we
   // Destroy() ourself, there's a window in which that information
   // hasn't yet propagated back to the child side and it might still
   // send us layer transactions.  We want to ignore those transactions
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -554,16 +554,17 @@ struct TransactionInfo
   FocusTarget focusTarget;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
   VsyncId vsyncId;
   TimeStamp vsyncStart;
   TimeStamp refreshStart;
   TimeStamp transactionStart;
+  bool containsSVG;
   nsCString url;
   TimeStamp fwdTime;
   /* This provides some timing information on any content that is meant to be
    * presented during this transaction.
    */
   CompositionPayload[] payload;
 };
 
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -526,18 +526,19 @@ void ShadowLayerForwarder::SendPaintTime
 }
 
 bool ShadowLayerForwarder::EndTransaction(
     const nsIntRegion& aRegionToClear, TransactionId aId,
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
     bool aIsRepeatTransaction, const mozilla::VsyncId& aVsyncId,
     const mozilla::TimeStamp& aVsyncStart,
     const mozilla::TimeStamp& aRefreshStart,
-    const mozilla::TimeStamp& aTransactionStart, const nsCString& aURL,
-    bool* aSent, const InfallibleTArray<CompositionPayload>& aPayload) {
+    const mozilla::TimeStamp& aTransactionStart, bool aContainsSVG,
+    const nsCString& aURL, bool* aSent,
+    const InfallibleTArray<CompositionPayload>& aPayload) {
   *aSent = false;
 
   TransactionInfo info;
 
   MOZ_ASSERT(IPCOpen(), "no manager to forward to");
   if (!IPCOpen()) {
     return false;
   }
@@ -672,16 +673,17 @@ bool ShadowLayerForwarder::EndTransactio
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
   info.vsyncId() = aVsyncId;
   info.vsyncStart() = aVsyncStart;
   info.refreshStart() = aRefreshStart;
   info.transactionStart() = aTransactionStart;
   info.url() = aURL;
+  info.containsSVG() = aContainsSVG;
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   info.fwdTime() = TimeStamp::Now();
 #endif
   info.payload() = aPayload;
 
   TargetConfig targetConfig(mTxn->mTargetBounds, mTxn->mTargetRotation,
                             mTxn->mTargetOrientation, aRegionToClear);
   info.targetConfig() = targetConfig;
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -251,17 +251,17 @@ class ShadowLayerForwarder final : publi
    */
   bool EndTransaction(const nsIntRegion& aRegionToClear, TransactionId aId,
                       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
                       bool aIsRepeatTransaction,
                       const mozilla::VsyncId& aVsyncId,
                       const mozilla::TimeStamp& aVsyncTime,
                       const mozilla::TimeStamp& aRefreshStart,
                       const mozilla::TimeStamp& aTransactionStart,
-                      const nsCString& aURL, bool* aSent,
+                      bool aContainsSVG, const nsCString& aURL, bool* aSent,
                       const InfallibleTArray<CompositionPayload>& aPayload =
                           InfallibleTArray<CompositionPayload>());
 
   /**
    * Set an actor through which layer updates will be pushed.
    */
   void SetShadowManager(PLayerTransactionChild* aShadowManager);
 
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -376,17 +376,16 @@ struct DIGroup {
 
   void ComputeGeometryChange(nsDisplayItem* aItem, BlobItemData* aData,
                              Matrix& aMatrix, nsDisplayListBuilder* aBuilder) {
     // If the frame is marked as invalidated, and didn't specify a rect to
     // invalidate then we want to invalidate both the old and new bounds,
     // otherwise we only want to invalidate the changed areas. If we do get an
     // invalid rect, then we want to add this on top of the change areas.
     nsRect invalid;
-    nsRegion combined;
     const DisplayItemClip& clip = aItem->GetClip();
 
     int32_t appUnitsPerDevPixel =
         aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
     MOZ_RELEASE_ASSERT(mAppUnitsPerDevPixel == appUnitsPerDevPixel);
     LayoutDeviceRect bounds =
         LayoutDeviceRect::FromAppUnits(mGroupBounds, appUnitsPerDevPixel);
     LayoutDeviceIntPoint offset = RoundedToInt(bounds.TopLeft());
@@ -402,93 +401,82 @@ struct DIGroup {
     GP("pre mInvalidRect: %s %p-%d - inv: %d %d %d %d\n", aItem->Name(),
        aItem->Frame(), aItem->GetPerFrameKey(), mInvalidRect.x, mInvalidRect.y,
        mInvalidRect.width, mInvalidRect.height);
     if (!aData->mGeometry) {
       // This item is being added for the first time, invalidate its entire
       // area.
       UniquePtr<nsDisplayItemGeometry> geometry(
           aItem->AllocateGeometry(aBuilder));
-      combined = clip.ApplyNonRoundedIntersection(
+      nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
           geometry->ComputeInvalidationRegion());
       aData->mGeometry = std::move(geometry);
-      nsRect bounds = combined.GetBounds();
 
       IntRect transformedRect =
-          ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+          ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                         mLayerBounds.TopLeft());
       aData->mRect = transformedRect.Intersect(mImageBounds);
-      GP("CGC %s %d %d %d %d\n", aItem->Name(), bounds.x, bounds.y,
-         bounds.width, bounds.height);
+      GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x,
+         clippedBounds.y, clippedBounds.width, clippedBounds.height);
       GP("%d %d,  %f %f\n", mLayerBounds.TopLeft().x, mLayerBounds.TopLeft().y,
          aMatrix._11, aMatrix._22);
       GP("mRect %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
          aData->mRect.width, aData->mRect.height);
       InvalidateRect(aData->mRect);
       aData->mInvalid = true;
     } else if (aData->mInvalid ||
                /* XXX: handle image load invalidation */ (
                    aItem->IsInvalid(invalid) && invalid.IsEmpty())) {
       MOZ_RELEASE_ASSERT(mLayerBounds.TopLeft() == aData->mGroupOffset);
       UniquePtr<nsDisplayItemGeometry> geometry(
           aItem->AllocateGeometry(aBuilder));
-      /* Instead of doing this dance, let's just invalidate the old rect and the
-       * new rect.
-      combined =
-        aData->mClip.ApplyNonRoundedIntersection(
-          aData->mGeometry->ComputeInvalidationRegion());
-      combined.MoveBy(shift);
-      combined.Or(combined,
-                  clip.ApplyNonRoundedIntersection(
-                    geometry->ComputeInvalidationRegion()));
-      aData->mGeometry = std::move(geometry);
-      */
-      combined = clip.ApplyNonRoundedIntersection(
+      nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
           geometry->ComputeInvalidationRegion());
       aData->mGeometry = std::move(geometry);
 
       GP("matrix: %f %f\n", aMatrix._31, aMatrix._32);
       GP("frame invalid invalidate: %s\n", aItem->Name());
       GP("old rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
          aData->mRect.width, aData->mRect.height);
       InvalidateRect(aData->mRect.Intersect(mImageBounds));
       // We want to snap to outside pixels. When should we multiply by the
       // matrix?
       // XXX: TransformBounds is expensive. We should avoid doing it if we have
       // no transform
       IntRect transformedRect =
-          ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+          ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                         mLayerBounds.TopLeft());
       aData->mRect = transformedRect.Intersect(mImageBounds);
       InvalidateRect(aData->mRect);
       GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y,
          aData->mRect.width, aData->mRect.height);
       aData->mInvalid = true;
     } else {
       MOZ_RELEASE_ASSERT(mLayerBounds.TopLeft() == aData->mGroupOffset);
       GP("else invalidate: %s\n", aItem->Name());
+      nsRegion combined;
       // this includes situations like reflow changing the position
       aItem->ComputeInvalidationRegion(aBuilder, aData->mGeometry.get(),
                                        &combined);
       if (!combined.IsEmpty()) {
         // There might be no point in doing this elaborate tracking here to get
         // smaller areas
         InvalidateRect(aData->mRect.Intersect(
             mImageBounds));  // invalidate the old area -- in theory combined
                              // should take care of this
         UniquePtr<nsDisplayItemGeometry> geometry(
             aItem->AllocateGeometry(aBuilder));
         // invalidate the invalidated area.
 
         aData->mGeometry = std::move(geometry);
 
-        combined = clip.ApplyNonRoundedIntersection(
+        nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
             aData->mGeometry->ComputeInvalidationRegion());
         IntRect transformedRect =
-            ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+            ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                           mLayerBounds.TopLeft());
         aData->mRect = transformedRect.Intersect(mImageBounds);
         InvalidateRect(aData->mRect);
 
         // CGC invariant broken
         if (!mInvalidRect.Contains(aData->mRect)) {
           gfxCriticalError()
               << "CGC-"
@@ -506,20 +494,20 @@ struct DIGroup {
             // the bounds of layer items can change on us without
             // ComputeInvalidationRegion returning any change. Other items
             // shouldn't have any hidden geometry change.
             MOZ_RELEASE_ASSERT(
                 geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds));
           } else {
             aData->mGeometry = std::move(geometry);
           }
-          combined = clip.ApplyNonRoundedIntersection(
+          nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
               aData->mGeometry->ComputeInvalidationRegion());
           IntRect transformedRect =
-              ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+              ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                             mLayerBounds.TopLeft());
           InvalidateRect(aData->mRect.Intersect(mImageBounds));
           aData->mRect = transformedRect.Intersect(mImageBounds);
           InvalidateRect(aData->mRect);
 
           GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
              aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
 
@@ -536,93 +524,93 @@ struct DIGroup {
           if (!IsContainerLayerItem(aItem)) {
             // the bounds of layer items can change on us
             // other items shouldn't
             MOZ_RELEASE_ASSERT(
                 geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds));
           } else {
             aData->mGeometry = std::move(geometry);
           }
-          combined = clip.ApplyNonRoundedIntersection(
+          nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
               aData->mGeometry->ComputeInvalidationRegion());
           IntRect transformedRect =
-              ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+              ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                             mLayerBounds.TopLeft());
           InvalidateRect(aData->mRect.Intersect(mImageBounds));
           aData->mRect = transformedRect.Intersect(mImageBounds);
           InvalidateRect(aData->mRect);
 
           GP("TransformChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
              aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
         } else if (IsContainerLayerItem(aItem)) {
           UniquePtr<nsDisplayItemGeometry> geometry(
               aItem->AllocateGeometry(aBuilder));
           // we need to catch bounds changes of containers so that we continue
           // to have the correct bounds rects in the recording
           if (UpdateContainerLayerPropertiesAndDetectChange(aItem, aData,
                                                             *geometry)) {
-            combined = clip.ApplyNonRoundedIntersection(
+            nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
                 geometry->ComputeInvalidationRegion());
             aData->mGeometry = std::move(geometry);
             IntRect transformedRect =
-                ToDeviceSpace(combined.GetBounds(), aMatrix,
+                ToDeviceSpace(clippedBounds, aMatrix,
                               appUnitsPerDevPixel, mLayerBounds.TopLeft());
             InvalidateRect(aData->mRect.Intersect(mImageBounds));
             aData->mRect = transformedRect.Intersect(mImageBounds);
             InvalidateRect(aData->mRect);
             GP("UpdateContainerLayerPropertiesAndDetectChange change\n");
           } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) {
             // Make sure we update mRect for mImageBounds changes
-            combined = clip.ApplyNonRoundedIntersection(
+            nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
                 geometry->ComputeInvalidationRegion());
             IntRect transformedRect =
-                ToDeviceSpace(combined.GetBounds(), aMatrix,
+                ToDeviceSpace(clippedBounds, aMatrix,
                               appUnitsPerDevPixel, mLayerBounds.TopLeft());
             // The invalid rect should contain the old rect and the new rect
             // but may not because the parent may have been removed.
             InvalidateRect(aData->mRect);
             aData->mRect = transformedRect.Intersect(mImageBounds);
             InvalidateRect(aData->mRect);
             GP("ContainerLayer image rect bounds change\n");
           } else {
             // XXX: this code can eventually be deleted/made debug only
-            combined = clip.ApplyNonRoundedIntersection(
+            nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
                 geometry->ComputeInvalidationRegion());
             IntRect transformedRect =
-                ToDeviceSpace(combined.GetBounds(), aMatrix,
+                ToDeviceSpace(clippedBounds, aMatrix,
                               appUnitsPerDevPixel, mLayerBounds.TopLeft());
             auto rect = transformedRect.Intersect(mImageBounds);
             GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(),
                aData->mRect.x, aData->mRect.y, aData->mRect.XMost(),
                aData->mRect.YMost());
             MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
           }
         } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) {
           // Make sure we update mRect for mImageBounds changes
           UniquePtr<nsDisplayItemGeometry> geometry(
               aItem->AllocateGeometry(aBuilder));
-          combined = clip.ApplyNonRoundedIntersection(
+          nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
               geometry->ComputeInvalidationRegion());
           IntRect transformedRect =
-              ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+              ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                             mLayerBounds.TopLeft());
           // The invalid rect should contain the old rect and the new rect
           // but may not because the parent may have been removed.
           InvalidateRect(aData->mRect);
           aData->mRect = transformedRect.Intersect(mImageBounds);
           InvalidateRect(aData->mRect);
           GP("image rect bounds change\n");
         } else {
           // XXX: this code can eventually be deleted/made debug only
           UniquePtr<nsDisplayItemGeometry> geometry(
               aItem->AllocateGeometry(aBuilder));
-          combined = clip.ApplyNonRoundedIntersection(
+          nsRect clippedBounds = clip.ApplyNonRoundedIntersection(
               geometry->ComputeInvalidationRegion());
           IntRect transformedRect =
-              ToDeviceSpace(combined.GetBounds(), aMatrix, appUnitsPerDevPixel,
+              ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel,
                             mLayerBounds.TopLeft());
           auto rect = transformedRect.Intersect(mImageBounds);
           GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x,
              aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost());
           MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect));
         }
       }
     }
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -16,17 +16,17 @@ use app_units::Au;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use intern::{Handle, Internable, InternDebug};
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
+use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList, TileCache};
 use prim_store::{PrimitiveInstance, PrimitiveKeyKind, PrimitiveSceneData};
 use prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
 use prim_store::{PrimitiveStoreStats, ScrollNodeAndClipChain, PictureIndex};
 use prim_store::{register_prim_chase_id, get_line_decoration_sizes};
 use prim_store::borders::{ImageBorder, NormalBorderPrim};
 use prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
 use prim_store::image::{Image, YuvImage};
 use prim_store::line_dec::{LineDecoration, LineDecorationCacheKey};
@@ -328,48 +328,55 @@ impl<'a> DisplayListFlattener<'a> {
             &self.resources,
         );
 
         // Now, create a picture with tile caching enabled that will hold all
         // of the primitives selected as belonging to the main scroll root.
         let pic_key = PictureKey::new(
             true,
             LayoutSize::zero(),
-            LayoutRect::max_rect(),
             Picture {
                 composite_mode_key: PictureCompositeKey::Identity,
             },
         );
 
         let pic_data_handle = self.resources
             .picture_interner
             .intern(&pic_key, || {
                 PrimitiveSceneData {
-                    prim_relative_clip_rect: LayoutRect::max_rect(),
                     prim_size: LayoutSize::zero(),
                     is_backface_visible: true,
                 }
             }
         );
 
+        let tile_cache = TileCache::new(
+            main_scroll_root,
+            &prim_list.prim_instances,
+            *self.pipeline_clip_chain_stack.last().unwrap(),
+            &self.prim_store.pictures,
+        );
+
         let pic_index = self.prim_store.pictures.alloc().init(PicturePrimitive::new_image(
             Some(PictureCompositeMode::TileCache { clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0) }),
             Picture3DContext::Out,
             self.scene.root_pipeline_id.unwrap(),
             None,
             true,
             RasterSpace::Screen,
             prim_list,
             main_scroll_root,
             LayoutRect::max_rect(),
             &self.clip_store,
+            Some(tile_cache),
         ));
 
         let instance = PrimitiveInstance::new(
             LayoutPoint::zero(),
+            LayoutRect::max_rect(),
             PrimitiveInstanceKind::Picture {
                 data_handle: pic_data_handle,
                 pic_index: PictureIndex(pic_index)
             },
             ClipChainId::NONE,
             main_scroll_root,
         );
 
@@ -1048,40 +1055,35 @@ impl<'a> DisplayListFlattener<'a> {
         spatial_node_index: SpatialNodeIndex,
         prim: P,
     ) -> PrimitiveInstance
     where
         P: Internable<InternData=PrimitiveSceneData>,
         P::Source: AsInstanceKind<Handle<P::Marker>> + InternDebug,
         DocumentResources: InternerMut<P>,
     {
-        let offset = info.rect.origin.to_vector();
-        let prim_relative_clip_rect = info.clip_rect
-            .translate(&-offset)
-            .into();
-
         // Build a primitive key.
-        let prim_key = prim.build_key(info, prim_relative_clip_rect);
+        let prim_key = prim.build_key(info);
 
         let interner = self.resources.interner_mut();
         let prim_data_handle =
             interner
             .intern(&prim_key, || {
                 PrimitiveSceneData {
-                    prim_relative_clip_rect,
                     prim_size: info.rect.size,
                     is_backface_visible: info.is_backface_visible,
                 }
             });
 
         let instance_kind = prim_key.as_instance_kind(prim_data_handle,
                                                       &mut self.prim_store);
 
         PrimitiveInstance::new(
             info.rect.origin,
+            info.clip_rect,
             instance_kind,
             clip_chain_id,
             spatial_node_index,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
@@ -1383,16 +1385,17 @@ impl<'a> DisplayListFlattener<'a> {
                 stacking_context.requested_raster_space,
                 PrimitiveList::new(
                     stacking_context.primitives,
                     &self.resources,
                 ),
                 stacking_context.spatial_node_index,
                 max_clip,
                 &self.clip_store,
+                None,
             ))
         );
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
 
         let mut current_pic_index = leaf_pic_index;
         let mut cur_instance = create_prim_instance(
@@ -1429,16 +1432,17 @@ impl<'a> DisplayListFlattener<'a> {
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         prims,
                         &self.resources,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
                     &self.clip_store,
+                    None,
                 ))
             );
 
             cur_instance = create_prim_instance(
                 current_pic_index,
                 PictureCompositeKey::Identity,
                 stacking_context.is_backface_visible,
                 stacking_context.clip_chain_id,
@@ -1463,16 +1467,17 @@ impl<'a> DisplayListFlattener<'a> {
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.resources,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
                     &self.clip_store,
+                    None,
                 ))
             );
 
             current_pic_index = filter_pic_index;
             cur_instance = create_prim_instance(
                 current_pic_index,
                 composite_mode.into(),
                 stacking_context.is_backface_visible,
@@ -1505,16 +1510,17 @@ impl<'a> DisplayListFlattener<'a> {
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.resources,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
                     &self.clip_store,
+                    None,
                 ))
             );
 
             current_pic_index = blend_pic_index;
             cur_instance = create_prim_instance(
                 blend_pic_index,
                 composite_mode.into(),
                 stacking_context.is_backface_visible,
@@ -1840,39 +1846,39 @@ impl<'a> DisplayListFlattener<'a> {
                                 raster_space,
                                 PrimitiveList::new(
                                     prims,
                                     &self.resources,
                                 ),
                                 pending_shadow.clip_and_scroll.spatial_node_index,
                                 max_clip,
                                 &self.clip_store,
+                                None,
                             ))
                         );
 
                         let shadow_pic_key = PictureKey::new(
                             true,
                             LayoutSize::zero(),
-                            LayoutRect::max_rect(),
                             Picture { composite_mode_key },
                         );
 
                         let shadow_prim_data_handle = self.resources
                             .picture_interner
                             .intern(&shadow_pic_key, || {
                                 PrimitiveSceneData {
-                                    prim_relative_clip_rect: LayoutRect::max_rect(),
                                     prim_size: LayoutSize::zero(),
                                     is_backface_visible: true,
                                 }
                             }
                         );
 
                         let shadow_prim_instance = PrimitiveInstance::new(
                             LayoutPoint::zero(),
+                            LayoutRect::max_rect(),
                             PrimitiveInstanceKind::Picture {
                                 data_handle: shadow_prim_data_handle,
                                 pic_index: shadow_pic_index
                             },
                             pending_shadow.clip_and_scroll.clip_chain_id,
                             pending_shadow.clip_and_scroll.spatial_node_index,
                         );
 
@@ -2612,16 +2618,17 @@ impl FlattenedStackingContext {
                 self.requested_raster_space,
                 PrimitiveList::new(
                     mem::replace(&mut self.primitives, Vec::new()),
                     resources,
                 ),
                 self.spatial_node_index,
                 LayoutRect::max_rect(),
                 clip_store,
+                None,
             ))
         );
 
         let prim_instance = create_prim_instance(
             pic_index,
             PictureCompositeKey::Identity,
             self.is_backface_visible,
             self.clip_chain_id,
@@ -2694,33 +2701,32 @@ fn create_prim_instance(
     is_backface_visible: bool,
     clip_chain_id: ClipChainId,
     spatial_node_index: SpatialNodeIndex,
     resources: &mut DocumentResources,
 ) -> PrimitiveInstance {
     let pic_key = PictureKey::new(
         is_backface_visible,
         LayoutSize::zero(),
-        LayoutRect::max_rect(),
         Picture { composite_mode_key },
     );
 
     let data_handle = resources
         .picture_interner
         .intern(&pic_key, || {
             PrimitiveSceneData {
-                prim_relative_clip_rect: LayoutRect::max_rect(),
                 prim_size: LayoutSize::zero(),
                 is_backface_visible,
             }
         }
     );
 
     PrimitiveInstance::new(
         LayoutPoint::zero(),
+        LayoutRect::max_rect(),
         PrimitiveInstanceKind::Picture {
             data_handle,
             pic_index,
         },
         clip_chain_id,
         spatial_node_index,
     )
 }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -8,17 +8,17 @@ use api::{LayoutPoint, LayoutRect, Layou
 use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
-use picture::{TileCacheUpdateState, RetainedTiles};
+use picture::{RetainedTiles, TileCache};
 use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer};
 #[cfg(feature = "replay")]
 use prim_store::{PrimitiveStoreStats};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameResources, FrameStamp};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
@@ -70,23 +70,28 @@ pub struct FrameBuilder {
     pub config: FrameBuilderConfig,
 }
 
 pub struct FrameVisibilityContext<'a> {
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub screen_world_rect: WorldRect,
     pub device_pixel_scale: DevicePixelScale,
     pub surfaces: &'a [SurfaceInfo],
+    pub debug_flags: DebugFlags,
+    pub scene_properties: &'a SceneProperties,
 }
 
 pub struct FrameVisibilityState<'a> {
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub scratch: &'a mut PrimitiveScratchBuffer,
+    pub tile_cache: Option<TileCache>,
+    pub retained_tiles: &'a mut RetainedTiles,
+    pub resources: &'a mut FrameResources,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
@@ -203,30 +208,32 @@ impl FrameBuilder {
         }
     }
 
     /// Destroy an existing frame builder. This is called just before
     /// a frame builder is replaced with a newly built scene.
     pub fn destroy(
         self,
         retained_tiles: &mut RetainedTiles,
+        clip_scroll_tree: &ClipScrollTree,
     ) {
         self.prim_store.destroy(
             retained_tiles,
+            clip_scroll_tree,
         );
 
         // In general, the pending retained tiles are consumed by the frame
         // builder the first time a frame is built after a new scene has
         // arrived. However, if two scenes arrive in quick succession, the
         // frame builder may not have had a chance to build a frame and
         // consume the pending tiles. In this case, the pending tiles will
         // be lost, causing a full invalidation of the entire screen. To
         // avoid this, if there are still pending tiles, include them in
         // the retained tiles passed to the next frame builder.
-        retained_tiles.tiles.extend(self.pending_retained_tiles.tiles);
+        retained_tiles.merge(self.pending_retained_tiles);
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
@@ -297,54 +304,41 @@ impl FrameBuilder {
             self.root_pic_index,
             &mut pic_update_state,
             &frame_context,
             gpu_cache,
             resources,
             &self.clip_store,
         );
 
-        // Update the state of any picture tile caches. This is a no-op on most
-        // frames (it only does work the first time a new scene is built, or if
-        // the tile-relative transform dependencies have changed).
-        let mut tile_cache_state = TileCacheUpdateState::new();
-        self.prim_store.update_tile_cache(
-            self.root_pic_index,
-            &mut tile_cache_state,
-            &frame_context,
-            resource_cache,
-            resources,
-            &self.clip_store,
-            &pic_update_state.surfaces,
-            gpu_cache,
-            &mut retained_tiles,
-            scratch,
-        );
-
         {
             let visibility_context = FrameVisibilityContext {
                 device_pixel_scale,
                 clip_scroll_tree,
                 screen_world_rect,
                 surfaces: pic_update_state.surfaces,
+                debug_flags,
+                scene_properties,
             };
 
             let mut visibility_state = FrameVisibilityState {
                 resource_cache,
                 gpu_cache,
                 clip_store: &mut self.clip_store,
                 scratch,
+                tile_cache: None,
+                retained_tiles: &mut retained_tiles,
+                resources,
             };
 
             self.prim_store.update_visibility(
                 self.root_pic_index,
                 ROOT_SURFACE_INDEX,
                 &visibility_context,
                 &mut visibility_state,
-                resources,
             );
         }
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
--- a/gfx/wr/webrender/src/intern.rs
+++ b/gfx/wr/webrender/src/intern.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{LayoutPrimitiveInfo, LayoutRect};
+use api::{LayoutPrimitiveInfo};
 use internal_types::FastHashMap;
 use malloc_size_of::MallocSizeOf;
 use profiler::ResourceProfileCounter;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::marker::PhantomData;
 use std::{mem, ops, u64};
 use std::sync::atomic::{AtomicUsize, Ordering};
@@ -422,11 +422,10 @@ pub trait Internable {
     type Source: Eq + Hash + Clone + Debug + MallocSizeOf;
     type StoreData: From<Self::Source> + MallocSizeOf;
     type InternData: MallocSizeOf;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> Self::Source;
 }
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,44 +1,45 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
-use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode};
+use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode, LayoutSize};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
 #[cfg(feature = "debug_renderer")]
 use api::{DebugFlags, DeviceVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipStore, ClipChainId, ClipChainNode, ClipItem};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 #[cfg(feature = "debug_renderer")]
 use debug_colors;
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
+use frame_builder::FrameVisibilityContext;
 use intern::ItemUid;
 use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use plane_split::{Clipper, Polygon, Splitter};
 use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
-use prim_store::{get_raster_rects, CoordinateSpaceMapping, PrimitiveScratchBuffer};
+use prim_store::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey};
 use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
 use print_tree::PrintTreePrinter;
 use render_backend::FrameResources;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::DocumentResources;
 use smallvec::SmallVec;
-use surface::{SurfaceDescriptor, TransformKey};
+use surface::{SurfaceDescriptor};
 use std::{mem, u16};
 use texture_cache::{Eviction, TextureCacheHandle};
 use tiling::RenderTargetKind;
 use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
@@ -54,25 +55,37 @@ use util::{ComparableVec, TransformedRec
 struct PictureInfo {
     /// The spatial node for this picture.
     spatial_node_index: SpatialNodeIndex,
 }
 
 /// Stores a list of cached picture tiles that are retained
 /// between new scenes.
 pub struct RetainedTiles {
+    /// The tiles retained between display lists.
     pub tiles: Vec<Tile>,
+    /// List of reference primitives that we will compare
+    /// to try and correlate the positioning of items
+    /// between display lists.
+    pub ref_prims: FastHashMap<ItemUid, WorldPoint>,
 }
 
 impl RetainedTiles {
     pub fn new() -> Self {
         RetainedTiles {
             tiles: Vec::new(),
+            ref_prims: FastHashMap::default(),
         }
     }
+
+    /// Merge items from one retained tiles into another.
+    pub fn merge(&mut self, other: RetainedTiles) {
+        self.tiles.extend(other.tiles);
+        self.ref_prims.extend(other.ref_prims);
+    }
 }
 
 /// Unit for tile coordinates.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct TileCoordinate;
 
 // Geometry types for tile coordinates.
 pub type TileOffset = TypedPoint2D<i32, TileCoordinate>;
@@ -92,25 +105,22 @@ const FRAMES_BEFORE_CACHING: usize = 2;
 //           profiling / telemetry to see when it makes sense
 //           to cache a picture.
 const MAX_CACHE_SIZE: f32 = 2048.0;
 /// The maximum size per axis of a surface,
 ///  in WorldPixel coordinates.
 const MAX_SURFACE_SIZE: f32 = 4096.0;
 
 
-#[derive(Debug)]
-pub struct GlobalTransformInfo {
-    /// Current (quantized) value of the transform, that is
-    /// independent of the value of the spatial node index.
-    /// Only calculated on first use.
-    current: Option<TransformKey>,
-    /// Tiles check this to see if the dependencies have changed.
-    changed: bool,
-}
+/// The number of primitives to search for, trying to correlate
+/// the offset between one display list and another.
+const MAX_PRIMS_TO_CORRELATE: usize = 64;
+/// The minmum number of primitives we need to correlate in
+/// order to consider it a success.
+const MIN_PRIMS_TO_CORRELATE: usize = MAX_PRIMS_TO_CORRELATE / 4;
 
 /// Information about the state of an opacity binding.
 #[derive(Debug)]
 pub struct OpacityBindingInfo {
     /// The current value retrieved from dynamic scene properties.
     value: f32,
     /// True if it was changed (or is new) since the last frame build.
     changed: bool,
@@ -128,17 +138,17 @@ impl From<PropertyBinding<f32>> for Opac
         match binding {
             PropertyBinding::Binding(key, _) => OpacityBinding::Binding(key.id),
             PropertyBinding::Value(value) => OpacityBinding::Value(value),
         }
     }
 }
 
 /// A stable ID for a given tile, to help debugging.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 struct TileId(usize);
 
 /// Information about a cached tile.
 #[derive(Debug)]
 pub struct Tile {
     /// The current world rect of thie tile.
     world_rect: WorldRect,
     /// The current local rect of this tile.
@@ -230,16 +240,18 @@ pub struct PrimitiveDescriptor {
     /// Uniquely identifies the content of the primitive template.
     prim_uid: ItemUid,
     /// The origin in world space of this primitive.
     origin: WorldPoint,
     /// The first clip in the clip_uids array of clips that affect this tile.
     first_clip: u16,
     /// The number of clips that affect this primitive instance.
     clip_count: u16,
+    /// The combined local clips + prim rect for this primitive.
+    world_culling_rect: WorldRect,
 }
 
 /// Uniquely describes the content of this tile, in a way that can be
 /// (reasonably) efficiently hashed and compared.
 #[derive(Debug)]
 pub struct TileDescriptor {
     /// List of primitive instance unique identifiers. The uid is guaranteed
     /// to uniquely describe the content of the primitive template, while
@@ -248,28 +260,28 @@ pub struct TileDescriptor {
 
     /// List of clip node unique identifiers. The uid is guaranteed
     /// to uniquely describe the content of the clip node.
     clip_uids: ComparableVec<ItemUid>,
 
     /// List of local offsets of the clip node origins. This
     /// ensures that if a clip node is supplied but has a different
     /// transform between frames that the tile is invalidated.
-    clip_vertices: ComparableVec<LayoutPoint>,
+    clip_vertices: ComparableVec<PointKey>,
 
     /// List of image keys that this tile depends on.
     image_keys: ComparableVec<ImageKey>,
 
     /// The set of opacity bindings that this tile depends on.
     // TODO(gw): Ugh, get rid of all opacity binding support!
     opacity_bindings: ComparableVec<OpacityBinding>,
 
-    /// List of the (quantized) transforms that we care about
+    /// List of the effects of transforms that we care about
     /// tracking for this tile.
-    transforms: ComparableVec<TransformKey>,
+    transforms: ComparableVec<PointKey>,
 }
 
 impl TileDescriptor {
     fn new() -> Self {
         TileDescriptor {
             prims: ComparableVec::new(),
             clip_uids: ComparableVec::new(),
             clip_vertices: ComparableVec::new(),
@@ -317,19 +329,16 @@ pub struct TileCache {
     /// The positioning node for this tile cache.
     spatial_node_index: SpatialNodeIndex,
     /// List of tiles present in this picture (stored as a 2D array)
     pub tiles: Vec<Tile>,
     /// A helper struct to map local rects into world coords.
     map_local_to_world: SpaceMapper<LayoutPixel, WorldPixel>,
     /// A list of tiles to draw during batching.
     pub tiles_to_draw: Vec<TileIndex>,
-    /// List of transform keys - used to check if transforms
-    /// have changed.
-    transforms: Vec<GlobalTransformInfo>,
     /// List of opacity bindings, with some extra information
     /// about whether they changed since last frame.
     opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     /// If Some(..) the region that is dirty in this picture.
     pub dirty_region: Option<DirtyRegion>,
     /// If true, we need to update the prim dependencies, due
     /// to relative transforms changing. The dependencies are
     /// stored in each tile, and are a list of things that
@@ -350,39 +359,99 @@ pub struct TileCache {
     /// The current world bounding rect of this tile cache. This is used
     /// to derive a local clip rect, such that we don't obscure in the
     /// z-buffer any items placed earlier in the render order (such as
     /// scroll bars in gecko, when the content overflows under the
     /// scroll bar).
     world_bounding_rect: WorldRect,
     /// Counter for the next id to assign for a new tile.
     next_id: usize,
+    /// List of reference primitive information used for
+    /// correlating the position between display lists.
+    reference_prims: Vec<ReferencePrimitive>,
+    /// The root clip chain for this tile cache.
+    root_clip_chain_id: ClipChainId,
+}
+
+/// Stores information about a primitive in the cache that we will
+/// try to use to correlate positions between display lists.
+struct ReferencePrimitive {
+    uid: ItemUid,
+    local_pos: LayoutPoint,
+    spatial_node_index: SpatialNodeIndex,
+}
+
+/// Collect a sample of primitives from the prim list that can
+/// be used to correlate positions.
+// TODO(gw): Investigate best how to select which primitives to select.
+fn collect_ref_prims(
+    prim_instances: &[PrimitiveInstance],
+    ref_prims: &mut Vec<ReferencePrimitive>,
+    pictures: &[PicturePrimitive],
+) {
+    for prim_instance in prim_instances {
+        if ref_prims.len() >= MAX_PRIMS_TO_CORRELATE {
+            return;
+        }
+
+        match prim_instance.kind {
+            PrimitiveInstanceKind::Picture { pic_index, .. } => {
+                collect_ref_prims(
+                    &pictures[pic_index.0].prim_list.prim_instances,
+                    ref_prims,
+                    pictures,
+                );
+            }
+            _ => {
+                ref_prims.push(ReferencePrimitive {
+                    uid: prim_instance.uid(),
+                    local_pos: prim_instance.prim_origin,
+                    spatial_node_index: prim_instance.spatial_node_index,
+                });
+            }
+        }
+    }
 }
 
 impl TileCache {
-    pub fn new(spatial_node_index: SpatialNodeIndex) -> Self {
+    pub fn new(
+        spatial_node_index: SpatialNodeIndex,
+        prim_instances: &[PrimitiveInstance],
+        root_clip_chain_id: ClipChainId,
+        pictures: &[PicturePrimitive],
+    ) -> Self {
+        // Build the list of reference primitives
+        // for this picture cache.
+        let mut reference_prims = Vec::with_capacity(MAX_PRIMS_TO_CORRELATE);
+        collect_ref_prims(
+            prim_instances,
+            &mut reference_prims,
+            pictures,
+        );
+
         TileCache {
             spatial_node_index,
             tiles: Vec::new(),
             map_local_to_world: SpaceMapper::new(
                 ROOT_SPATIAL_NODE_INDEX,
                 WorldRect::zero(),
             ),
             tiles_to_draw: Vec::new(),
-            transforms: Vec::new(),
             opacity_bindings: FastHashMap::default(),
             dirty_region: None,
             needs_update: true,
             world_origin: WorldPoint::zero(),
             world_tile_size: WorldSize::zero(),
             tile_count: TileSize::zero(),
             scroll_offset: None,
             pending_blits: Vec::new(),
             world_bounding_rect: WorldRect::zero(),
             next_id: 0,
+            reference_prims,
+            root_clip_chain_id,
         }
     }
 
     fn next_id(&mut self) -> TileId {
         let id = TileId(self.next_id);
         self.next_id += 1;
         id
     }
@@ -408,17 +477,17 @@ impl TileCache {
 
         (p0, p1)
     }
 
     /// Update transforms, opacity bindings and tile rects.
     pub fn pre_update(
         &mut self,
         pic_rect: LayoutRect,
-        frame_context: &FrameBuildingContext,
+        frame_context: &FrameVisibilityContext,
         resource_cache: &ResourceCache,
         retained_tiles: &mut RetainedTiles,
     ) {
         // Work out the scroll offset to apply to the world reference point.
         let scroll_transform = frame_context.clip_scroll_tree.get_relative_transform(
             ROOT_SPATIAL_NODE_INDEX,
             self.spatial_node_index,
         ).expect("bug: unable to get scroll transform");
@@ -428,69 +497,53 @@ impl TileCache {
         );
         let scroll_delta = match self.scroll_offset {
             Some(prev) => prev - scroll_offset,
             None => WorldVector2D::zero(),
         };
         self.scroll_offset = Some(scroll_offset);
 
         // Pull any retained tiles from the previous scene.
-        if !retained_tiles.tiles.is_empty() {
+        let world_offset = if retained_tiles.tiles.is_empty() {
+            None
+        } else {
             assert!(self.tiles.is_empty());
             self.tiles = mem::replace(&mut retained_tiles.tiles, Vec::new());
-        }
+
+            // Get the positions of the reference primitives for this
+            // new display list.
+            let mut new_prim_map = FastHashMap::default();
+            build_ref_prims(
+                &self.reference_prims,
+                &mut new_prim_map,
+                frame_context.clip_scroll_tree,
+            );
+
+            // Attempt to correlate them to work out which offset to apply.
+            correlate_prim_maps(
+                &retained_tiles.ref_prims,
+                &new_prim_map,
+            )
+        }.unwrap_or(WorldVector2D::zero());
 
         // Assume no tiles are valid to draw by default
         self.tiles_to_draw.clear();
 
         self.map_local_to_world = SpaceMapper::new(
             ROOT_SPATIAL_NODE_INDEX,
             frame_context.screen_world_rect,
         );
 
         let world_mapper = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             self.spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
-        // Walk the transforms and see if we need to rebuild the primitive
-        // dependencies for each tile.
-        // TODO(gw): We could be smarter here and only rebuild for the primitives
-        //           which are affected by transforms that have changed.
-        if self.transforms.len() == frame_context.clip_scroll_tree.spatial_nodes.len() {
-            for (i, transform) in self.transforms.iter_mut().enumerate() {
-                // If this relative transform was used on the previous frame,
-                // update it and store whether it changed for use during
-                // tile invalidation later.
-                if let Some(ref mut current) = transform.current {
-                    let mapping: CoordinateSpaceMapping<LayoutPixel, PicturePixel> = CoordinateSpaceMapping::new(
-                        self.spatial_node_index,
-                        SpatialNodeIndex::new(i),
-                        frame_context.clip_scroll_tree,
-                    ).expect("todo: handle invalid mappings");
-
-                    let key = mapping.into();
-                    transform.changed = key != *current;
-                    *current = key;
-                }
-            }
-        } else {
-            // If the size of the transforms array changed, just invalidate all the transforms for now.
-            self.transforms.clear();
-
-            for _ in 0 .. frame_context.clip_scroll_tree.spatial_nodes.len() {
-                self.transforms.push(GlobalTransformInfo {
-                    current: None,
-                    changed: true,
-                });
-            }
-        };
-
         // Do a hacky diff of opacity binding values from the last frame. This is
         // used later on during tile invalidation tests.
         let current_properties = frame_context.scene_properties.float_properties();
         let old_properties = mem::replace(&mut self.opacity_bindings, FastHashMap::default());
 
         for (id, value) in current_properties {
             let changed = match old_properties.get(id) {
                 Some(old_property) => !old_property.value.approx_eq(value),
@@ -521,17 +574,17 @@ impl TileCache {
 
         // Get a reference point that serves as an origin that all tiles we create
         // must be aligned to. This ensures that tiles get reused correctly between
         // scrolls and display list changes, even with the different local coord
         // systems that gecko supplies.
         let mut world_ref_point = if self.tiles.is_empty() {
             needed_world_rect.origin.floor()
         } else {
-            self.tiles[0].world_rect.origin
+            self.tiles[0].world_rect.origin + world_offset
         };
 
         // Apply the scroll delta so that existing tiles still get used.
         world_ref_point += scroll_delta;
 
         // Work out the required device rect that we need to cover the screen,
         // given the world reference point constraint.
         let device_ref_point = world_ref_point * frame_context.device_pixel_scale;
@@ -570,17 +623,20 @@ impl TileCache {
         let y_tiles = ((p1.y - p0.y) / TILE_SIZE_HEIGHT as f32).round() as i32;
 
         // Step through any old tiles, and retain them if we can. They are keyed only on
         // the (scroll adjusted) world position, relying on the descriptor content checks
         // later to invalidate them if the content has changed.
         let mut old_tiles = FastHashMap::default();
         for tile in self.tiles.drain(..) {
             let tile_device_pos = (tile.world_rect.origin + scroll_delta) * frame_context.device_pixel_scale;
-            let key = (tile_device_pos.x.round() as i32, tile_device_pos.y.round() as i32);
+            let key = (
+                (tile_device_pos.x + world_offset.x).round() as i32,
+                (tile_device_pos.y + world_offset.y).round() as i32,
+            );
             old_tiles.insert(key, tile);
         }
 
         // Store parameters about the current tiling rect for use during dependency updates.
         self.world_origin = WorldPoint::new(
             p0.x / frame_context.device_pixel_scale.0,
             p0.y / frame_context.device_pixel_scale.0,
         );
@@ -665,72 +721,50 @@ impl TileCache {
             }
         }
     }
 
     /// Update the dependencies for each tile for a given primitive instance.
     pub fn update_prim_dependencies(
         &mut self,
         prim_instance: &PrimitiveInstance,
-        prim_list: &PrimitiveList,
         clip_scroll_tree: &ClipScrollTree,
         resources: &FrameResources,
         clip_chain_nodes: &[ClipChainNode],
         pictures: &[PicturePrimitive],
         resource_cache: &ResourceCache,
         opacity_binding_store: &OpacityBindingStorage,
         image_instances: &ImageInstanceStorage,
     ) {
         if !self.needs_update {
             return;
         }
 
-        // We need to ensure that if a primitive belongs to a cluster that has
-        // been marked invisible, we exclude it here. Otherwise, we may end up
-        // with a primitive that is outside the bounding rect of the calculated
-        // picture rect (which takes the cluster visibility into account).
-        if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
-            return;
-        }
-
         self.map_local_to_world.set_target_spatial_node(
             prim_instance.spatial_node_index,
             clip_scroll_tree,
         );
 
         let prim_data = &resources.as_common_data(&prim_instance);
 
-        let (prim_rect, clip_rect) = match prim_instance.kind {
+        let prim_rect = match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let pic = &pictures[pic_index.0];
-                (pic.local_rect, LayoutRect::max_rect())
+                pic.local_rect
             }
             _ => {
-                let prim_rect = LayoutRect::new(
+                LayoutRect::new(
                     prim_instance.prim_origin,
                     prim_data.prim_size,
-                );
-                let clip_rect = prim_data
-                    .prim_relative_clip_rect
-                    .translate(&prim_instance.prim_origin.to_vector());
-
-                (prim_rect, clip_rect)
+                )
             }
         };
 
-        // Map the primitive local rect into the picture space.
-        // TODO(gw): We should maybe store this in the primitive template
-        //           during interning so that we never have to calculate
-        //           it during frame building.
-        let culling_rect = match prim_rect.intersection(&clip_rect) {
-            Some(rect) => rect,
-            None => return,
-        };
-
-        let world_rect = match self.map_local_to_world.map(&culling_rect) {
+        // Map the primitive local rect into world space.
+        let world_rect = match self.map_local_to_world.map(&prim_rect) {
             Some(rect) => rect,
             None => {
                 return;
             }
         };
 
         // If the rect is invalid, no need to create dependencies.
         // TODO(gw): Need to handle pictures with filters here.
@@ -739,28 +773,24 @@ impl TileCache {
         }
 
         // Get the tile coordinates in the picture space.
         let (p0, p1) = self.get_tile_coords_for_rect(&world_rect);
 
         // Build the list of resources that this primitive has dependencies on.
         let mut opacity_bindings: SmallVec<[OpacityBinding; 4]> = SmallVec::new();
         let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
-        let mut clip_vertices: SmallVec<[LayoutPoint; 8]> = SmallVec::new();
+        let mut clip_vertices: SmallVec<[WorldPoint; 8]> = SmallVec::new();
         let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
-        let mut current_clip_chain_id = prim_instance.clip_chain_id;
         let mut clip_spatial_nodes = FastHashSet::default();
 
         // TODO(gw): We only care about world clip rects that don't have the main
         //           scroll root as an ancestor. It may be a worthwhile optimization
         //           to check for these and skip them.
-        // TODO(gw): We could also trivially track and exclude the root iframe / content
-        //           clip chain id, since we know that will exist on every item but never
-        //           actually be relevant.
-        let mut world_clips: FastHashMap<RectangleKey, SpatialNodeIndex> = FastHashMap::default();
+        let mut world_clips: SmallVec<[(RectangleKey, SpatialNodeIndex); 4]> = SmallVec::default();
 
         // Some primitives can not be cached (e.g. external video images)
         let is_cacheable = prim_instance.is_cacheable(
             &resources,
             resource_cache,
         );
 
         // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
@@ -824,54 +854,71 @@ impl TileCache {
                 // These don't contribute dependencies
                 true
             }
         };
 
         // The transforms of any clips that are relative to the picture may affect
         // the content rendered by this primitive.
         let mut world_clip_rect = world_rect;
+        let mut culling_rect = prim_rect
+            .intersection(&prim_instance.local_clip_rect)
+            .unwrap_or(LayoutRect::zero());
+
+        let mut current_clip_chain_id = prim_instance.clip_chain_id;
         while current_clip_chain_id != ClipChainId::NONE {
             let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
             let clip_node = &resources.clip_data_store[clip_chain_node.handle];
 
+            // We can skip the root clip node - it will be taken care of by the
+            // world bounding rect calculated for the cache.
+            if current_clip_chain_id == self.root_clip_chain_id {
+                current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+                continue;
+            }
+
             self.map_local_to_world.set_target_spatial_node(
                 clip_chain_node.spatial_node_index,
                 clip_scroll_tree,
             );
 
             // Clips that are simple rects and handled by collapsing them into a single
             // clip rect. This avoids the need to store vertices for these cases, and also
             // allows easy calculation of the overall bounds of the tile cache.
             let add_to_clip_deps = match clip_node.item {
                 ClipItem::Rectangle(size, ClipMode::Clip) => {
                     let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_chain_node.spatial_node_index.0 as usize];
 
-                    // Clips that are not in the root coordinate system are not axis-aligned,
-                    // so we need to treat them as normal style clips with vertices.
-                    if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
-                        let local_rect = LayoutRect::new(
-                            clip_chain_node.local_pos,
-                            size,
-                        );
+                    let local_clip_rect = LayoutRect::new(
+                        clip_chain_node.local_pos,
+                        size,
+                    );
 
-                        match self.map_local_to_world.map(&local_rect) {
+                    // If the clip rect is in the same spatial node, it can be handled by the
+                    // local clip rect.
+                    if clip_chain_node.spatial_node_index == prim_instance.spatial_node_index {
+                        culling_rect = culling_rect.intersection(&local_clip_rect).unwrap_or(LayoutRect::zero());
+                        false
+                    } else if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
+                        // Clips that are not in the root coordinate system are not axis-aligned,
+                        // so we need to treat them as normal style clips with vertices.
+                        match self.map_local_to_world.map(&local_clip_rect) {
                             Some(clip_world_rect) => {
                                 // Even if this ends up getting clipped out by the current clip
                                 // stack, we want to ensure the primitive gets added to the tiles
                                 // below, to ensure invalidation isn't tripped up by the wrong
                                 // number of primitives that affect this tile.
                                 world_clip_rect = world_clip_rect
                                     .intersection(&clip_world_rect)
                                     .unwrap_or(WorldRect::zero());
 
-                                world_clips.insert(
+                                world_clips.push((
                                     clip_world_rect.into(),
                                     clip_chain_node.spatial_node_index,
-                                );
+                                ));
 
                                 false
                             }
                             None => {
                                 true
                             }
                         }
                     } else {
@@ -881,43 +928,68 @@ impl TileCache {
                 ClipItem::Rectangle(_, ClipMode::ClipOut) |
                 ClipItem::RoundedRectangle(..) |
                 ClipItem::Image { .. } |
                 ClipItem::BoxShadow(..) => {
                     true
                 }
             };
 
-            clip_vertices.push(clip_chain_node.local_pos);
-            clip_chain_uids.push(clip_chain_node.handle.uid());
+            if add_to_clip_deps {
+                clip_chain_uids.push(clip_chain_node.handle.uid());
+                clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
 
-            if add_to_clip_deps {
-                clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
+                let local_clip_rect = LayoutRect::new(
+                    clip_chain_node.local_pos,
+                    LayoutSize::zero(),
+                );
+                if let Some(world_clip_rect) = self.map_local_to_world.map(&local_clip_rect) {
+                    clip_vertices.push(world_clip_rect.origin);
+                }
             }
 
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         if include_clip_rect {
             self.world_bounding_rect = self.world_bounding_rect.union(&world_clip_rect);
         }
 
+        self.map_local_to_world.set_target_spatial_node(
+            prim_instance.spatial_node_index,
+            clip_scroll_tree,
+        );
+        let world_culling_rect = self
+            .map_local_to_world
+            .map(&culling_rect)
+            .expect("bug: unable to map local clip rect");
+
         // Normalize the tile coordinates before adding to tile dependencies.
         // For each affected tile, mark any of the primitive dependencies.
         for y in p0.y .. p1.y {
             for x in p0.x .. p1.x {
                 // If the primitive exists on tiles outside the selected tile cache
                 // area, just ignore those.
                 if x < 0 || x >= self.tile_count.width || y < 0 || y >= self.tile_count.height {
                     continue;
                 }
 
                 let index = (y * self.tile_count.width + x) as usize;
                 let tile = &mut self.tiles[index];
 
+                // Store the local clip rect by calculating what portion
+                // of the tile it covers.
+                let world_culling_rect = world_culling_rect
+                    .intersection(&tile.world_rect)
+                    .map(|rect| {
+                        rect.translate(&-tile.world_rect.origin.to_vector())
+                    })
+                    .unwrap_or(WorldRect::zero())
+                    .round();
+
                 // Work out the needed rect for the primitive on this tile.
                 // TODO(gw): We should be able to remove this for any tile that is not
                 //           a partially clipped tile, which would be a significant
                 //           optimization for the common case (non-clipped tiles).
 
                 // Mark if the tile is cacheable at all.
                 tile.is_same_content &= is_cacheable;
 
@@ -925,22 +997,26 @@ impl TileCache {
                 tile.descriptor.image_keys.extend_from_slice(&image_keys);
 
                 // // Include any opacity bindings this primitive depends on.
                 tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
 
                 // Update the tile descriptor, used for tile comparison during scene swaps.
                 tile.descriptor.prims.push(PrimitiveDescriptor {
                     prim_uid: prim_instance.uid(),
-                    origin: world_rect.origin - tile.world_rect.origin.to_vector(),
+                    origin: (world_rect.origin - tile.world_rect.origin.to_vector()).round(),
                     first_clip: tile.descriptor.clip_uids.len() as u16,
                     clip_count: clip_chain_uids.len() as u16,
+                    world_culling_rect,
                 });
                 tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
-                tile.descriptor.clip_vertices.extend_from_slice(&clip_vertices);
+                for clip_vertex in &clip_vertices {
+                    let clip_vertex = (*clip_vertex - tile.world_rect.origin.to_vector()).round();
+                    tile.descriptor.clip_vertices.push(clip_vertex.into());
+                }
 
                 tile.transforms.insert(prim_instance.spatial_node_index);
                 for spatial_node_index in &clip_spatial_nodes {
                     tile.transforms.insert(*spatial_node_index);
                 }
                 for (world_rect, spatial_node_index) in &world_clips {
                     tile.potential_clips.insert(world_rect.clone(), *spatial_node_index);
                 }
@@ -950,17 +1026,17 @@ impl TileCache {
 
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        frame_context: &FrameBuildingContext,
+        frame_context: &FrameVisibilityContext,
         _scratch: &mut PrimitiveScratchBuffer,
     ) -> LayoutRect {
         let mut dirty_world_rect = WorldRect::zero();
 
         self.dirty_region = None;
         self.pending_blits.clear();
 
         let descriptor = ImageDescriptor::new(
@@ -999,22 +1075,26 @@ impl TileCache {
                     tile.transforms.insert(*spatial_node_index);
                 }
             }
 
             // Update tile transforms
             let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect();
             transform_spatial_nodes.sort();
             for spatial_node_index in transform_spatial_nodes {
-                let mapping: CoordinateSpaceMapping<LayoutPixel, PicturePixel> = CoordinateSpaceMapping::new(
+                let xf = frame_context.clip_scroll_tree.get_relative_transform(
                     self.spatial_node_index,
                     spatial_node_index,
-                    frame_context.clip_scroll_tree,
-                ).expect("todo: handle invalid mappings");
-                tile.descriptor.transforms.push(mapping.into());
+                ).expect("BUG: unable to get relative transform");
+                // Store the result of transforming a fixed point by this
+                // transform.
+                // TODO(gw): This could in theory give incorrect results for a
+                //           primitive behind the near plane.
+                let key = xf.transform_point2d(&LayoutPoint::zero()).unwrap_or(LayoutPoint::zero()).round();
+                tile.descriptor.transforms.push(key.into());
             }
 
             // Invalidate if the backing texture was evicted.
             if resource_cache.texture_cache.is_allocated(&tile.handle) {
                 // Request the backing texture so it won't get evicted this frame.
                 // We specifically want to mark the tile texture as used, even
                 // if it's detected not visible below and skipped. This is because
                 // we maintain the set of tiles we care about based on visibility
@@ -1141,29 +1221,16 @@ impl TileCache {
                 dirty_device_rect: dirty_device_rect.round().to_i32(),
             })
         };
 
         local_clip_rect
     }
 }
 
-/// State structure that is used during the tile cache update picture traversal.
-pub struct TileCacheUpdateState {
-    pub tile_cache: Option<TileCache>,
-}
-
-impl TileCacheUpdateState {
-    pub fn new() -> Self {
-        TileCacheUpdateState {
-            tile_cache: None,
-        }
-    }
-}
-
 /// Maintains a stack of picture and surface information, that
 /// is used during the initial picture traversal.
 pub struct PictureUpdateState<'a> {
     pub surfaces: &'a mut Vec<SurfaceInfo>,
     surface_stack: Vec<SurfaceIndex>,
     picture_stack: Vec<PictureInfo>,
 }
 
@@ -1540,20 +1607,17 @@ impl PrimitiveList {
             // calculated during the picture traversal dynamically). If not
             // a picture, include a minimal bounding rect in the cluster bounds.
             let cluster = &mut clusters[cluster_index];
             if !is_pic {
                 let prim_rect = LayoutRect::new(
                     prim_instance.prim_origin,
                     prim_data.prim_size,
                 );
-                let clip_rect = prim_data
-                    .prim_relative_clip_rect
-                    .translate(&prim_instance.prim_origin.to_vector());
-                let culling_rect = clip_rect
+                let culling_rect = prim_instance.local_clip_rect
                     .intersection(&prim_rect)
                     .unwrap_or(LayoutRect::zero());
 
                 cluster.bounding_rect = cluster.bounding_rect.union(&culling_rect);
             }
 
             prim_instance.cluster_index = ClusterIndex(cluster_index as u16);
         }
@@ -1676,18 +1740,27 @@ impl PicturePrimitive {
 
     /// Destroy an existing picture. This is called just before
     /// a frame builder is replaced with a newly built scene. It
     /// gives a picture a chance to retain any cached tiles that
     /// may be useful during the next scene build.
     pub fn destroy(
         mut self,
         retained_tiles: &mut RetainedTiles,
+        clip_scroll_tree: &ClipScrollTree,
     ) {
         if let Some(tile_cache) = self.tile_cache.take() {
+            // Calculate and store positions of the reference
+            // primitives for this tile cache.
+            build_ref_prims(
+                &tile_cache.reference_prims,
+                &mut retained_tiles.ref_prims,
+                clip_scroll_tree,
+            );
+
             for tile in tile_cache.tiles {
                 retained_tiles.tiles.push(tile);
             }
         }
     }
 
     pub fn new_image(
         requested_composite_mode: Option<PictureCompositeMode>,
@@ -1695,16 +1768,17 @@ impl PicturePrimitive {
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
         prim_list: PrimitiveList,
         spatial_node_index: SpatialNodeIndex,
         local_clip_rect: LayoutRect,
         clip_store: &ClipStore,
+        tile_cache: Option<TileCache>,
     ) -> Self {
         // For now, only create a cache descriptor for blur filters (which
         // includes text shadows). We can incrementally expand this to
         // handle more composite modes.
         let create_cache_descriptor = match requested_composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                 blur_radius > 0.0
             }
@@ -1718,25 +1792,16 @@ impl PicturePrimitive {
                 &prim_list.prim_instances,
                 spatial_node_index,
                 clip_store,
             )
         } else {
             None
         };
 
-        let tile_cache = match requested_composite_mode {
-            Some(PictureCompositeMode::TileCache { .. }) => {
-                Some(TileCache::new(spatial_node_index))
-            }
-            Some(_) | None => {
-                None
-            }
-        };
-
         PicturePrimitive {
             surface_desc,
             prim_list,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             context_3d,
@@ -2106,44 +2171,16 @@ impl PicturePrimitive {
                 establishes_raster_root: surface_spatial_node_index == surface.raster_spatial_node_index,
                 surface_index: state.push_surface(surface),
             });
         }
 
         Some(mem::replace(&mut self.prim_list.pictures, SmallVec::new()))
     }
 
-    /// Update the primitive dependencies for any active tile caches,
-    /// but only *if* the transforms have made the mappings out of date.
-    pub fn update_prim_dependencies(
-        &self,
-        tile_cache: &mut TileCache,
-        frame_context: &FrameBuildingContext,
-        resource_cache: &mut ResourceCache,
-        resources: &FrameResources,
-        pictures: &[PicturePrimitive],
-        clip_store: &ClipStore,
-        opacity_binding_store: &OpacityBindingStorage,
-        image_instances: &ImageInstanceStorage,
-    ) {
-        for prim_instance in &self.prim_list.prim_instances {
-            tile_cache.update_prim_dependencies(
-                prim_instance,
-                &self.prim_list,
-                &frame_context.clip_scroll_tree,
-                resources,
-                &clip_store.clip_chain_nodes,
-                pictures,
-                resource_cache,
-                opacity_binding_store,
-                image_instances,
-            );
-        }
-    }
-
     /// Called after updating child pictures during the initial
     /// picture traversal.
     pub fn post_update(
         &mut self,
         child_pictures: PictureList,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
         gpu_cache: &mut GpuCache,
@@ -2769,8 +2806,87 @@ fn create_raster_mappers(
         raster_spatial_node_index,
         surface_spatial_node_index,
         raster_bounds,
         clip_scroll_tree,
     );
 
     (map_raster_to_world, map_pic_to_raster)
 }
+
+// Convert a list of reference primitives into a map of prim uid -> world position.
+fn build_ref_prims(
+    ref_prims: &[ReferencePrimitive],
+    prim_map: &mut FastHashMap<ItemUid, WorldPoint>,
+    clip_scroll_tree: &ClipScrollTree,
+) {
+    prim_map.clear();
+
+    let mut map_local_to_world = SpaceMapper::new(
+        ROOT_SPATIAL_NODE_INDEX,
+        WorldRect::zero(),
+    );
+
+    for ref_prim in ref_prims {
+        map_local_to_world.set_target_spatial_node(
+            ref_prim.spatial_node_index,
+            clip_scroll_tree,
+        );
+
+        // We only care about the origin.
+        // TODO(gw): Consider adding a map_point to SpaceMapper.
+        let rect = LayoutRect::new(
+            ref_prim.local_pos,
+            LayoutSize::zero(),
+        );
+
+        if let Some(rect) = map_local_to_world.map(&rect) {
+            prim_map.insert(ref_prim.uid, rect.origin);
+        }
+    }
+}
+
+// Attempt to correlate the offset between two display lists by
+// comparing the offsets between a small number of primitives in
+// each display list.
+// TODO(gw): This is basically a horrible hack - there must be a better
+//           way to achieve this!
+fn correlate_prim_maps(
+    old_prims: &FastHashMap<ItemUid, WorldPoint>,
+    new_prims: &FastHashMap<ItemUid, WorldPoint>,
+) -> Option<WorldVector2D> {
+    let mut map: FastHashMap<VectorKey, usize> = FastHashMap::default();
+
+    // Find primitives with the same uid, find the difference
+    // between them and store the frequency of this offset
+    // in a hash map.
+    for (uid, old_point) in old_prims {
+        if let Some(new_point) = new_prims.get(uid) {
+            let key = (*new_point - *old_point).round().into();
+
+            let key_count = map.entry(key).or_insert(0);
+            *key_count += 1;
+        }
+    }
+
+    // Calculate the mode (the most common frequency of offset). This
+    // can be different for some primitives, if they've animated, or
+    // are attached to a different scroll node etc.
+    map.into_iter()
+        .max_by_key(|&(_, count)| count)
+        .and_then(|(offset, count)| {
+            // We will assume we can use the calculated offset if:
+            // (a) We found more than one quarter of the selected
+            //     reference primitives to have the same offset.
+            // (b) The display lists both had the same number of
+            //     primitives, and we exactly matched. This handles
+            //     edge cases like scenes where there are very
+            //     few primitives, while excluding edge cases like
+            //     dl_mutate that have thousands of primitives with
+            //     the same uid.
+            if (count >= MIN_PRIMS_TO_CORRELATE) ||
+               (count == old_prims.len() && count == new_prims.len()) {
+                Some(offset.into())
+            } else {
+                None
+            }
+        })
+}
--- a/gfx/wr/webrender/src/prim_store/borders.rs
+++ b/gfx/wr/webrender/src/prim_store/borders.rs
@@ -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/. */
 
 use api::{
-    AuHelpers, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets,
+    AuHelpers, LayoutPrimitiveInfo, LayoutSideOffsets,
     LayoutSideOffsetsAu, LayoutSize, NormalBorder, PremultipliedColorF,
     Shadow
 };
 use border::create_border_segments;
 use border::NormalBorderAu;
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use frame_builder::{FrameBuildingState};
 use gpu_cache::GpuDataRequest;
@@ -30,23 +30,21 @@ pub struct NormalBorderPrim {
     pub widths: LayoutSideOffsetsAu,
 }
 
 pub type NormalBorderKey = PrimKey<NormalBorderPrim>;
 
 impl NormalBorderKey {
     pub fn new(
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
         normal_border: NormalBorderPrim,
     ) -> Self {
         NormalBorderKey {
             common: PrimKeyCommonData::with_info(
                 info,
-                prim_relative_clip_rect,
             ),
             kind: normal_border,
         }
     }
 }
 
 impl intern::InternDebug for NormalBorderKey {}
 
@@ -175,21 +173,19 @@ impl intern::Internable for NormalBorder
     type Source = NormalBorderKey;
     type StoreData = NormalBorderTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> NormalBorderKey {
         NormalBorderKey::new(
             info,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 impl CreateShadow for NormalBorderPrim {
     fn create_shadow(&self, shadow: &Shadow) -> Self {
         let border = self.border.with_color(shadow.color.into());
@@ -217,23 +213,21 @@ pub struct ImageBorder {
     pub nine_patch: NinePatchDescriptor,
 }
 
 pub type ImageBorderKey = PrimKey<ImageBorder>;
 
 impl ImageBorderKey {
     pub fn new(
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
         image_border: ImageBorder,
     ) -> Self {
         ImageBorderKey {
             common: PrimKeyCommonData::with_info(
                 info,
-                prim_relative_clip_rect,
             ),
             kind: image_border,
         }
     }
 }
 
 impl intern::InternDebug for ImageBorderKey {}
 
@@ -356,21 +350,19 @@ impl intern::Internable for ImageBorder 
     type Source = ImageBorderKey;
     type StoreData = ImageBorderTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> ImageBorderKey {
         ImageBorderKey::new(
             info,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 impl IsVisible for ImageBorder {
     fn is_visible(&self) -> bool {
         true
@@ -383,14 +375,14 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<NormalBorderPrim>(), 84, "NormalBorderPrim size changed");
-    assert_eq!(mem::size_of::<NormalBorderTemplate>(), 224, "NormalBorderTemplate size changed");
-    assert_eq!(mem::size_of::<NormalBorderKey>(), 112, "NormalBorderKey size changed");
+    assert_eq!(mem::size_of::<NormalBorderTemplate>(), 208, "NormalBorderTemplate size changed");
+    assert_eq!(mem::size_of::<NormalBorderKey>(), 96, "NormalBorderKey size changed");
     assert_eq!(mem::size_of::<ImageBorder>(), 92, "ImageBorder size changed");
-    assert_eq!(mem::size_of::<ImageBorderTemplate>(), 88, "ImageBorderTemplate size changed");
-    assert_eq!(mem::size_of::<ImageBorderKey>(), 120, "ImageBorderKey size changed");
+    assert_eq!(mem::size_of::<ImageBorderTemplate>(), 72, "ImageBorderTemplate size changed");
+    assert_eq!(mem::size_of::<ImageBorderKey>(), 104, "ImageBorderKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/gradient.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{
     ColorF, ColorU,ExtendMode, GradientStop, LayoutPoint, LayoutSize,
-    LayoutPrimitiveInfo, LayoutRect, PremultipliedColorF
+    LayoutPrimitiveInfo, PremultipliedColorF
 };
 use display_list_flattener::{AsInstanceKind, IsVisible};
 use frame_builder::FrameBuildingState;
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
 use intern::{DataStore, Handle, Internable, InternDebug, Interner, UpdateList};
 use prim_store::{BrushSegment, GradientTileRange};
 use prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData};
 use prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
@@ -50,24 +50,22 @@ pub struct LinearGradientKey {
     pub reverse_stops: bool,
     pub nine_patch: Option<Box<NinePatchDescriptor>>,
 }
 
 impl LinearGradientKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         linear_grad: LinearGradient,
     ) -> Self {
         LinearGradientKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             extend_mode: linear_grad.extend_mode,
             start_point: linear_grad.start_point,
             end_point: linear_grad.end_point,
             stretch_size: linear_grad.stretch_size,
             tile_spacing: linear_grad.tile_spacing,
             stops: linear_grad.stops,
             reverse_stops: linear_grad.reverse_stops,
@@ -253,22 +251,20 @@ impl Internable for LinearGradient {
     type Source = LinearGradientKey;
     type StoreData = LinearGradientTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect
     ) -> LinearGradientKey {
         LinearGradientKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self
         )
     }
 }
 
 impl IsVisible for LinearGradient {
     fn is_visible(&self) -> bool {
         true
@@ -311,24 +307,22 @@ pub struct RadialGradientKey {
     pub tile_spacing: SizeKey,
     pub nine_patch: Option<Box<NinePatchDescriptor>>,
 }
 
 impl RadialGradientKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         radial_grad: RadialGradient,
     ) -> Self {
         RadialGradientKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             extend_mode: radial_grad.extend_mode,
             center: radial_grad.center,
             params: radial_grad.params,
             stretch_size: radial_grad.stretch_size,
             stops: radial_grad.stops,
             tile_spacing: radial_grad.tile_spacing,
             nine_patch: radial_grad.nine_patch,
@@ -483,22 +477,20 @@ impl Internable for RadialGradient {
     type Source = RadialGradientKey;
     type StoreData = RadialGradientTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> RadialGradientKey {
         RadialGradientKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 impl IsVisible for RadialGradient {
     fn is_visible(&self) -> bool {
         true
@@ -721,15 +713,15 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<LinearGradient>(), 72, "LinearGradient size changed");
-    assert_eq!(mem::size_of::<LinearGradientTemplate>(), 128, "LinearGradientTemplate size changed");
-    assert_eq!(mem::size_of::<LinearGradientKey>(), 96, "LinearGradientKey size changed");
+    assert_eq!(mem::size_of::<LinearGradientTemplate>(), 112, "LinearGradientTemplate size changed");
+    assert_eq!(mem::size_of::<LinearGradientKey>(), 80, "LinearGradientKey size changed");
 
     assert_eq!(mem::size_of::<RadialGradient>(), 72, "RadialGradient size changed");
-    assert_eq!(mem::size_of::<RadialGradientTemplate>(), 136, "RadialGradientTemplate size changed");
-    assert_eq!(mem::size_of::<RadialGradientKey>(), 104, "RadialGradientKey size changed");
+    assert_eq!(mem::size_of::<RadialGradientTemplate>(), 120, "RadialGradientTemplate size changed");
+    assert_eq!(mem::size_of::<RadialGradientKey>(), 88, "RadialGradientKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/image.rs
+++ b/gfx/wr/webrender/src/prim_store/image.rs
@@ -79,25 +79,23 @@ pub struct Image {
 }
 
 pub type ImageKey = PrimKey<Image>;
 
 impl ImageKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         image: Image,
     ) -> Self {
 
         ImageKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             kind: image,
         }
     }
 }
 
 impl InternDebug for ImageKey {}
 
@@ -350,22 +348,20 @@ impl Internable for Image {
     type Source = ImageKey;
     type StoreData = ImageTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> ImageKey {
         ImageKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self
         )
     }
 }
 
 impl CreateShadow for Image {
     fn create_shadow(&self, shadow: &Shadow) -> Self {
         Image {
@@ -400,25 +396,23 @@ pub struct YuvImage {
 }
 
 pub type YuvImageKey = PrimKey<YuvImage>;
 
 impl YuvImageKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         yuv_image: YuvImage,
     ) -> Self {
 
         YuvImageKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             kind: yuv_image,
         }
     }
 }
 
 impl InternDebug for YuvImageKey {}
 
@@ -528,22 +522,20 @@ impl Internable for YuvImage {
     type Source = YuvImageKey;
     type StoreData = YuvImageTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> YuvImageKey {
         YuvImageKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self
         )
     }
 }
 
 impl IsVisible for YuvImage {
     fn is_visible(&self) -> bool {
         true
@@ -556,14 +548,14 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<Image>(), 56, "Image size changed");
-    assert_eq!(mem::size_of::<ImageTemplate>(), 124, "ImageTemplate size changed");
-    assert_eq!(mem::size_of::<ImageKey>(), 84, "ImageKey size changed");
+    assert_eq!(mem::size_of::<ImageTemplate>(), 108, "ImageTemplate size changed");
+    assert_eq!(mem::size_of::<ImageKey>(), 68, "ImageKey size changed");
     assert_eq!(mem::size_of::<YuvImage>(), 36, "YuvImage size changed");
-    assert_eq!(mem::size_of::<YuvImageTemplate>(), 72, "YuvImageTemplate size changed");
-    assert_eq!(mem::size_of::<YuvImageKey>(), 64, "YuvImageKey size changed");
+    assert_eq!(mem::size_of::<YuvImageTemplate>(), 56, "YuvImageTemplate size changed");
+    assert_eq!(mem::size_of::<YuvImageKey>(), 48, "YuvImageKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/line_dec.rs
+++ b/gfx/wr/webrender/src/prim_store/line_dec.rs
@@ -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/. */
 
 use api::{
-    ColorF, ColorU, LayoutPrimitiveInfo, LayoutRect, LayoutSizeAu,
+    ColorF, ColorU, LayoutPrimitiveInfo, LayoutSizeAu,
     LineOrientation, LineStyle, PremultipliedColorF, Shadow,
 };
 use app_units::Au;
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use frame_builder::{FrameBuildingState};
 use gpu_cache::GpuDataRequest;
 use intern;
 use prim_store::{
@@ -40,23 +40,21 @@ pub struct LineDecoration {
     pub color: ColorU,
 }
 
 pub type LineDecorationKey = PrimKey<LineDecoration>;
 
 impl LineDecorationKey {
     pub fn new(
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
         line_dec: LineDecoration,
     ) -> Self {
         LineDecorationKey {
             common: PrimKeyCommonData::with_info(
                 info,
-                prim_relative_clip_rect,
             ),
             kind: line_dec,
         }
     }
 }
 
 impl intern::InternDebug for LineDecorationKey {}
 
@@ -150,21 +148,19 @@ impl intern::Internable for LineDecorati
     type Source = LineDecorationKey;
     type StoreData = LineDecorationTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> LineDecorationKey {
         LineDecorationKey::new(
             info,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 impl CreateShadow for LineDecoration {
     fn create_shadow(&self, shadow: &Shadow) -> Self {
         LineDecoration {
@@ -186,11 +182,11 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<LineDecoration>(), 20, "LineDecoration size changed");
-    assert_eq!(mem::size_of::<LineDecorationTemplate>(), 68, "LineDecorationTemplate size changed");
-    assert_eq!(mem::size_of::<LineDecorationKey>(), 48, "LineDecorationKey size changed");
+    assert_eq!(mem::size_of::<LineDecorationTemplate>(), 52, "LineDecorationTemplate size changed");
+    assert_eq!(mem::size_of::<LineDecorationKey>(), 32, "LineDecorationKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -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/. */
 
 use api::{BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
-use api::{DeviceIntRect, DevicePixelScale, DeviceRect};
+use api::{DeviceIntRect, DevicePixelScale, DeviceRect, WorldVector2D};
 use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, WorldPoint, WorldSize};
 use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
 use api::{PremultipliedColorF, PropertyBinding, Shadow};
 use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, AuHelpers};
 use api::{LayoutPrimitiveInfo};
 #[cfg(feature = "debug_renderer")]
 use api::DevicePoint;
@@ -24,38 +24,38 @@ use euclid::{SideOffsets2D, TypedTransfo
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
-use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState, TileCacheUpdateState};
-use picture::{ClusterIndex, PrimitiveList, SurfaceIndex, SurfaceInfo, RetainedTiles, RasterConfig};
+use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState};
+use picture::{ClusterIndex, PrimitiveList, SurfaceIndex, RetainedTiles, RasterConfig};
 use prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
 use prim_store::gradient::{LinearGradientDataHandle, RadialGradientDataHandle};
 use prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
 use prim_store::line_dec::LineDecorationDataHandle;
 use prim_store::picture::PictureDataHandle;
 use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
 #[cfg(debug_assertions)]
 use render_backend::{FrameId};
 use render_backend::FrameResources;
 use render_task::{RenderTask, RenderTaskCacheKey, to_cache_size};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
-use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
+use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
-use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec};
+use util::{ScaleOffset, MatrixHelpers, recycle_vec, MaxRect};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
 pub mod line_dec;
 pub mod picture;
@@ -346,17 +346,16 @@ impl GpuCacheAddress {
 /// The information about an interned primitive that
 /// is stored and available in the scene builder
 /// thread.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(MallocSizeOf)]
 pub struct PrimitiveSceneData {
     pub prim_size: LayoutSize,
-    pub prim_relative_clip_rect: LayoutRect,
     pub is_backface_visible: bool,
 }
 
 /// Information specific to a primitive type that
 /// uniquely identifies a primitive template by key.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
@@ -536,25 +535,40 @@ impl hash::Hash for VectorKey {
 }
 
 impl From<VectorKey> for LayoutVector2D {
     fn from(key: VectorKey) -> LayoutVector2D {
         LayoutVector2D::new(key.x, key.y)
     }
 }
 
+impl From<VectorKey> for WorldVector2D {
+    fn from(key: VectorKey) -> WorldVector2D {
+        WorldVector2D::new(key.x, key.y)
+    }
+}
+
 impl From<LayoutVector2D> for VectorKey {
     fn from(vec: LayoutVector2D) -> VectorKey {
         VectorKey {
             x: vec.x,
             y: vec.y,
         }
     }
 }
 
+impl From<WorldVector2D> for VectorKey {
+    fn from(vec: WorldVector2D) -> VectorKey {
+        VectorKey {
+            x: vec.x,
+            y: vec.y,
+        }
+    }
+}
+
 /// A hashable point for using as a key during primitive interning.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, MallocSizeOf, PartialEq)]
 pub struct PointKey {
     pub x: f32,
     pub y: f32,
 }
@@ -578,34 +592,40 @@ impl From<LayoutPoint> for PointKey {
     fn from(p: LayoutPoint) -> PointKey {
         PointKey {
             x: p.x,
             y: p.y,
         }
     }
 }
 
+impl From<WorldPoint> for PointKey {
+    fn from(p: WorldPoint) -> PointKey {
+        PointKey {
+            x: p.x,
+            y: p.y,
+        }
+    }
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
 pub struct PrimKeyCommonData {
     pub is_backface_visible: bool,
     pub prim_size: SizeKey,
-    pub prim_relative_clip_rect: RectangleKey,
 }
 
 impl PrimKeyCommonData {
     pub fn with_info(
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> Self {
         PrimKeyCommonData {
             is_backface_visible: info.is_backface_visible,
             prim_size: info.rect.size.into(),
-            prim_relative_clip_rect: prim_relative_clip_rect.into(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
 pub struct PrimKey<T: MallocSizeOf> {
@@ -620,24 +640,22 @@ pub struct PrimitiveKey {
     pub common: PrimKeyCommonData,
     pub kind: PrimitiveKeyKind,
 }
 
 impl PrimitiveKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         kind: PrimitiveKeyKind,
     ) -> Self {
         PrimitiveKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             kind,
         }
     }
 }
 
 impl intern::InternDebug for PrimitiveKey {}
 
@@ -697,31 +715,29 @@ impl PrimitiveKeyKind {
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(MallocSizeOf)]
 pub struct PrimTemplateCommonData {
     pub is_backface_visible: bool,
     pub prim_size: LayoutSize,
-    pub prim_relative_clip_rect: LayoutRect,
     pub opacity: PrimitiveOpacity,
     /// The GPU cache handle for a primitive template. Since this structure
     /// is retained across display lists by interning, this GPU cache handle
     /// also remains valid, which reduces the number of updates to the GPU
     /// cache when a new display list is processed.
     pub gpu_cache_handle: GpuCacheHandle,
 }
 
 impl PrimTemplateCommonData {
     pub fn with_key_common(common: PrimKeyCommonData) -> Self {
         PrimTemplateCommonData {
             is_backface_visible: common.is_backface_visible,
             prim_size: common.prim_size.into(),
-            prim_relative_clip_rect: common.prim_relative_clip_rect.into(),
             gpu_cache_handle: GpuCacheHandle::new(),
             opacity: PrimitiveOpacity::translucent(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -813,22 +829,20 @@ impl intern::Internable for PrimitiveKey
     type Marker = PrimitiveDataMarker;
     type Source = PrimitiveKey;
     type StoreData = PrimitiveTemplate;
     type InternData = PrimitiveSceneData;
 
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> PrimitiveKey {
         PrimitiveKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
 pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
 pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
@@ -1388,16 +1402,19 @@ pub struct PrimitiveInstance {
     /// the relevant information for the primitive
     /// can be found.
     pub kind: PrimitiveInstanceKind,
 
     /// Local space origin of this primitive. The size
     /// of the primitive is defined by the template.
     pub prim_origin: LayoutPoint,
 
+    /// Local space clip rect for this instance
+    pub local_clip_rect: LayoutRect,
+
     #[cfg(debug_assertions)]
     pub id: PrimitiveDebugId,
 
     /// The last frame ID (of the `RenderTaskTree`) this primitive
     /// was prepared for rendering in.
     #[cfg(debug_assertions)]
     pub prepared_frame_id: FrameId,
 
@@ -1415,22 +1432,24 @@ pub struct PrimitiveInstance {
 
     /// ID of the spatial node that this primitive is positioned by.
     pub spatial_node_index: SpatialNodeIndex,
 }
 
 impl PrimitiveInstance {
     pub fn new(
         prim_origin: LayoutPoint,
+        local_clip_rect: LayoutRect,
         kind: PrimitiveInstanceKind,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         PrimitiveInstance {
             prim_origin,
+            local_clip_rect,
             kind,
             #[cfg(debug_assertions)]
             prepared_frame_id: FrameId::INVALID,
             #[cfg(debug_assertions)]
             id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)),
             visibility_info: PrimitiveVisibilityIndex::INVALID,
             clip_chain_id,
             spatial_node_index,
@@ -1684,20 +1703,22 @@ impl PrimitiveStore {
         self.pictures[root.0].print(&self.pictures, root, &mut pt);
     }
 
     /// Destroy an existing primitive store. This is called just before
     /// a primitive store is replaced with a newly built scene.
     pub fn destroy(
         self,
         retained_tiles: &mut RetainedTiles,
+        clip_scroll_tree: &ClipScrollTree,
     ) {
         for pic in self.pictures {
             pic.destroy(
                 retained_tiles,
+                clip_scroll_tree,
             );
         }
     }
 
     /// Returns the total count of primitive instances contained in pictures.
     pub fn prim_count(&self) -> usize {
         self.pictures
             .iter()
@@ -1744,27 +1765,42 @@ impl PrimitiveStore {
     /// Update visibility pass - update each primitive visibility struct, and
     /// build the clip chain instance if appropriate.
     pub fn update_visibility(
         &mut self,
         pic_index: PictureIndex,
         parent_surface_index: SurfaceIndex,
         frame_context: &FrameVisibilityContext,
         frame_state: &mut FrameVisibilityState,
-        resources: &mut FrameResources,
     ) {
         let (mut prim_list, surface_index, apply_local_clip_rect) = {
             let pic = &mut self.pictures[pic_index.0];
 
             let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty());
             let surface_index = match pic.raster_config {
                 Some(ref raster_config) => raster_config.surface_index,
                 None => parent_surface_index,
             };
 
+            if let Some(mut tile_cache) = pic.tile_cache.take() {
+                debug_assert!(frame_state.tile_cache.is_none());
+
+                // If we have a tile cache for this picture, see if any of the
+                // relative transforms have changed, which means we need to
+                // re-map the dependencies of any child primitives.
+                tile_cache.pre_update(
+                    pic.local_rect,
+                    frame_context,
+                    frame_state.resource_cache,
+                    frame_state.retained_tiles,
+                );
+
+                frame_state.tile_cache = Some(tile_cache);
+            }
+
             (prim_list, surface_index, pic.apply_local_clip_rect)
         };
 
         let surface = &frame_context.surfaces[surface_index.0 as usize];
 
         let mut map_local_to_surface = surface
             .map_local_to_surface
             .clone();
@@ -1802,17 +1838,17 @@ impl PrimitiveStore {
                 prim_instance.spatial_node_index,
             );
 
             map_local_to_surface.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
-            let (is_passthrough, prim_local_rect, prim_local_clip_rect, clip_node_collector) = match prim_instance.kind {
+            let (is_passthrough, prim_local_rect, clip_node_collector) = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
                     if !self.pictures[pic_index.0].is_visible() {
                         continue;
                     }
 
                     if let Some(ref raster_config) = self.pictures[pic_index.0].raster_config {
                         if raster_config.establishes_raster_root {
                             let surface = &frame_context.surfaces[raster_config.surface_index.0 as usize];
@@ -1820,52 +1856,48 @@ impl PrimitiveStore {
                         }
                     }
 
                     self.update_visibility(
                         pic_index,
                         surface_index,
                         frame_context,
                         frame_state,
-                        resources,
                     );
 
                     let pic = &self.pictures[pic_index.0];
 
                     let clip_node_collector = pic.raster_config.as_ref().and_then(|rc| {
                         if rc.establishes_raster_root {
                             Some(frame_state.clip_store.pop_raster_root())
                         } else {
                             None
                         }
                     });
 
-                    (pic.raster_config.is_none(), pic.local_rect, LayoutRect::max_rect(), clip_node_collector)
+                    (pic.raster_config.is_none(), pic.local_rect, clip_node_collector)
                 }
                 _ => {
-                    let prim_data = &resources.as_common_data(&prim_instance);
+                    let prim_data = &frame_state.resources.as_common_data(&prim_instance);
 
                     let prim_rect = LayoutRect::new(
                         prim_instance.prim_origin,
                         prim_data.prim_size,
                     );
-                    let clip_rect = prim_data
-                        .prim_relative_clip_rect
-                        .translate(&LayoutVector2D::new(prim_instance.prim_origin.x, prim_instance.prim_origin.y));
-
-                    (false, prim_rect, clip_rect, None)
+
+                    (false, prim_rect, None)
                 }
             };
 
             if is_passthrough {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
-                        clipped_world_rect: WorldRect::zero(),
+                        clipped_world_rect: WorldRect::max_rect(),
                         clip_chain: ClipChainInstance::empty(),
                         clip_task_index: ClipTaskIndex::INVALID,
                         combined_local_clip_rect: LayoutRect::zero(),
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
             } else {
@@ -1878,46 +1910,59 @@ impl PrimitiveStore {
 
                 // Inflate the local rect for this primitive by the inflation factor of
                 // the picture context. This ensures that even if the primitive itself
                 // is not visible, any effects from the blur radius will be correctly
                 // taken into account.
                 let inflation_factor = surface.inflation_factor;
                 let local_rect = prim_local_rect
                     .inflate(inflation_factor, inflation_factor)
-                    .intersection(&prim_local_clip_rect);
+                    .intersection(&prim_instance.local_clip_rect);
                 let local_rect = match local_rect {
                     Some(local_rect) => local_rect,
                     None => {
                         if prim_instance.is_chased() {
                             println!("\tculled for being out of the local clip rectangle: {:?}",
-                                prim_local_clip_rect);
+                                prim_instance.local_clip_rect);
                         }
                         continue;
                     }
                 };
 
                 let clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         prim_instance,
                         local_rect,
-                        prim_local_clip_rect,
+                        prim_instance.local_clip_rect,
                         prim_context.spatial_node_index,
                         &map_local_to_surface,
                         &map_surface_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &frame_context.screen_world_rect,
                         clip_node_collector.as_ref(),
-                        &mut resources.clip_data_store,
+                        &mut frame_state.resources.clip_data_store,
                     );
 
+                if let Some(ref mut tile_cache) = frame_state.tile_cache {
+                    tile_cache.update_prim_dependencies(
+                        prim_instance,
+                        frame_context.clip_scroll_tree,
+                        frame_state.resources,
+                        &frame_state.clip_store.clip_chain_nodes,
+                        &self.pictures,
+                        frame_state.resource_cache,
+                        &self.opacity_bindings,
+                        &self.images,
+                    );
+                }
+
                 let clip_chain = match clip_chain {
                     Some(clip_chain) => clip_chain,
                     None => {
                         if prim_instance.is_chased() {
                             println!("\tunable to build the clip chain, skipping");
                         }
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         continue;
@@ -1929,17 +1974,17 @@ impl PrimitiveStore {
                         clip_chain.clips_range,
                         if apply_local_clip_rect { "(applied)" } else { "" },
                     );
                 }
 
                 let combined_local_clip_rect = if apply_local_clip_rect {
                     clip_chain.local_clip_rect
                 } else {
-                    prim_local_clip_rect
+                    prim_instance.local_clip_rect
                 };
 
                 // Check if the clip bounding rect (in pic space) is visible on screen
                 // This includes both the prim bounding rect + local prim clip rect!
                 let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
                     Some(world_rect) => world_rect,
                     None => {
                         continue;
@@ -1964,106 +2009,33 @@ impl PrimitiveStore {
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
             }
 
         }
 
-        // frame_state.pop_picture();
-        self.pictures[pic_index.0].prim_list = prim_list;
-    }
-
-    /// Update any picture tile caches for a subset of the picture tree.
-    /// This is often a no-op that exits very quickly, unless a new scene
-    /// has arrived, or the relative transforms have changed.
-    pub fn update_tile_cache(
-        &mut self,
-        pic_index: PictureIndex,
-        state: &mut TileCacheUpdateState,
-        frame_context: &FrameBuildingContext,
-        resource_cache: &mut ResourceCache,
-        resources: &FrameResources,
-        clip_store: &ClipStore,
-        surfaces: &[SurfaceInfo],
-        gpu_cache: &mut GpuCache,
-        retained_tiles: &mut RetainedTiles,
-        scratch: &mut PrimitiveScratchBuffer,
-    ) {
-        let children = {
-            let pic = &mut self.pictures[pic_index.0];
-            // Only update the tile cache if we ended up selecting tile caching for the
-            // composite mode of this picture. In some cases, even if the requested
-            // composite mode was tile caching, WR may choose not to draw this picture
-            // with tile cache enabled. For now, this is only in the case of very large
-            // picture rects, but in future we may do it for performance reasons too.
-            if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
-                debug_assert!(state.tile_cache.is_none());
-                let mut tile_cache = pic.tile_cache.take().unwrap();
-
-                // If we have a tile cache for this picture, see if any of the
-                // relative transforms have changed, which means we need to
-                // re-map the dependencies of any child primitives.
-                tile_cache.pre_update(
-                    pic.local_rect,
-                    frame_context,
-                    resource_cache,
-                    retained_tiles,
-                );
-
-                state.tile_cache = Some(tile_cache);
-            }
-            mem::replace(&mut pic.prim_list.pictures, SmallVec::new())
-        };
-
-        if let Some(ref mut tile_cache) = state.tile_cache {
-            self.pictures[pic_index.0].update_prim_dependencies(
-                tile_cache,
-                frame_context,
-                resource_cache,
-                resources,
-                &self.pictures,
-                clip_store,
-                &self.opacity_bindings,
-                &self.images,
-            );
-        }
-
-        for child_pic_index in &children {
-            self.update_tile_cache(
-                *child_pic_index,
-                state,
-                frame_context,
-                resource_cache,
-                resources,
-                clip_store,
-                surfaces,
-                gpu_cache,
-                retained_tiles,
-                scratch,
-            );
-        }
-
         let pic = &mut self.pictures[pic_index.0];
+
         if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
-            let mut tile_cache = state.tile_cache.take().unwrap();
+            let mut tile_cache = frame_state.tile_cache.take().unwrap();
 
             // Build the dirty region(s) for this tile cache.
             pic.local_clip_rect = tile_cache.post_update(
-                resource_cache,
-                gpu_cache,
+                frame_state.resource_cache,
+                frame_state.gpu_cache,
                 frame_context,
-                scratch,
+                frame_state.scratch,
             );
 
             pic.tile_cache = Some(tile_cache);
         }
 
-        pic.prim_list.pictures = children;
+        pic.prim_list = prim_list;
     }
 
     pub fn get_opacity_binding(
         &self,
         opacity_binding_index: OpacityBindingIndex,
     ) -> f32 {
         if opacity_binding_index == OpacityBindingIndex::INVALID {
             1.0
@@ -2378,16 +2350,35 @@ impl PrimitiveStore {
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         for (plane_split_anchor, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
             if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
                 continue;
             }
 
+            // The original clipped world rect was calculated during the initial visibility pass.
+            // However, it's possible that the dirty rect has got smaller, if tiles were not
+            // dirty. Intersecting with the dirty rect here eliminates preparing any primitives
+            // outside the dirty rect, and reduces the size of any off-screen surface allocations
+            // for clip masks / render tasks that we make.
+            {
+                let visibility_info = &mut scratch.prim_info[prim_instance.visibility_info.0 as usize];
+
+                match visibility_info.clipped_world_rect.intersection(&pic_context.dirty_world_rect) {
+                    Some(rect) => {
+                        visibility_info.clipped_world_rect = rect;
+                    }
+                    None => {
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
+                        continue;
+                    }
+                }
+            }
+
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
 
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
@@ -3078,17 +3069,16 @@ impl<'a> GpuDataRequest<'a> {
         }
 
         false
     }
 
 impl PrimitiveInstance {
     fn build_segments_if_needed(
         &mut self,
-        prim_local_clip_rect: LayoutRect,
         prim_clip_chain: &ClipChainInstance,
         frame_state: &mut FrameBuildingState,
         prim_store: &mut PrimitiveStore,
         resources: &FrameResources,
         segments_store: &mut SegmentStorage,
         segment_instances_store: &mut SegmentInstanceStorage,
     ) {
         let prim_data = &resources.as_common_data(self);
@@ -3129,17 +3119,17 @@ impl PrimitiveInstance {
             }
         };
 
         if *segment_instance_index == SegmentInstanceIndex::INVALID {
             let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new();
 
             if write_brush_segment_description(
                 prim_local_rect,
-                prim_local_clip_rect,
+                self.local_clip_rect,
                 prim_clip_chain,
                 &mut frame_state.segment_builder,
                 frame_state.clip_store,
                 resources,
             ) {
                 frame_state.segment_builder.build(|segment| {
                     segments.push(
                         BrushSegment::new(
@@ -3166,17 +3156,16 @@ impl PrimitiveInstance {
                 *segment_instance_index = segment_instances_store.push(instance);
             };
         }
     }
 
     fn update_clip_task_for_brush(
         &self,
         prim_info: &mut PrimitiveVisibility,
-        prim_local_clip_rect: LayoutRect,
         root_spatial_node_index: SpatialNodeIndex,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         prim_store: &PrimitiveStore,
         resources: &mut FrameResources,
@@ -3289,17 +3278,17 @@ impl PrimitiveInstance {
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         self,
                         segment.local_rect.translate(&LayoutVector2D::new(
                             self.prim_origin.x,
                             self.prim_origin.y,
                         )),
-                        prim_local_clip_rect,
+                        self.local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &pic_context.dirty_world_rect,
@@ -3337,35 +3326,28 @@ impl PrimitiveInstance {
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let prim_info = &mut scratch.prim_info[self.visibility_info.0 as usize];
 
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect);
         }
 
-        let prim_clip_rect = resources
-            .as_common_data(self)
-            .prim_relative_clip_rect
-            .translate(&self.prim_origin.to_vector());
-
         self.build_segments_if_needed(
-            prim_clip_rect,
             &prim_info.clip_chain,
             frame_state,
             prim_store,
             resources,
             &mut scratch.segments,
             &mut scratch.segment_instances,
         );
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
             prim_info,
-            prim_clip_rect,
             root_spatial_node_index,
             prim_context,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
             prim_store,
             resources,
@@ -3512,15 +3494,15 @@ fn update_opacity_binding(
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveInstance>(), 80, "PrimitiveInstance size changed");
+    assert_eq!(mem::size_of::<PrimitiveInstance>(), 96, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 40, "PrimitiveInstanceKind size changed");
-    assert_eq!(mem::size_of::<PrimitiveTemplate>(), 56, "PrimitiveTemplate size changed");
+    assert_eq!(mem::size_of::<PrimitiveTemplate>(), 40, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 20, "PrimitiveTemplateKind size changed");
-    assert_eq!(mem::size_of::<PrimitiveKey>(), 36, "PrimitiveKey size changed");
+    assert_eq!(mem::size_of::<PrimitiveKey>(), 20, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 5, "PrimitiveKeyKind size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -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/. */
 
 use api::{
-    ColorU, FilterOp, LayoutRect, LayoutSize, LayoutPrimitiveInfo, MixBlendMode,
+    ColorU, FilterOp, LayoutSize, LayoutPrimitiveInfo, MixBlendMode,
     PropertyBinding, PropertyBindingId,
 };
 use app_units::Au;
 use display_list_flattener::{AsInstanceKind, IsVisible};
 use intern::{DataStore, Handle, Internable, Interner, InternDebug, UpdateList};
 use picture::PictureCompositeMode;
 use prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
@@ -133,25 +133,23 @@ pub struct Picture {
 }
 
 pub type PictureKey = PrimKey<Picture>;
 
 impl PictureKey {
     pub fn new(
         is_backface_visible: bool,
         prim_size: LayoutSize,
-        prim_relative_clip_rect: LayoutRect,
         pic: Picture,
     ) -> Self {
 
         PictureKey {
             common: PrimKeyCommonData {
                 is_backface_visible,
                 prim_size: prim_size.into(),
-                prim_relative_clip_rect: prim_relative_clip_rect.into(),
             },
             kind: pic,
         }
     }
 }
 
 impl InternDebug for PictureKey {}
 
@@ -202,22 +200,20 @@ impl Internable for Picture {
     type Source = PictureKey;
     type StoreData = PictureTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> PictureKey {
         PictureKey::new(
             info.is_backface_visible,
             info.rect.size,
-            prim_relative_clip_rect,
             self
         )
     }
 }
 
 impl IsVisible for Picture {
     fn is_visible(&self) -> bool {
         true
@@ -230,11 +226,11 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<Picture>(), 84, "Picture size changed");
-    assert_eq!(mem::size_of::<PictureTemplate>(), 36, "PictureTemplate size changed");
-    assert_eq!(mem::size_of::<PictureKey>(), 112, "PictureKey size changed");
+    assert_eq!(mem::size_of::<PictureTemplate>(), 20, "PictureTemplate size changed");
+    assert_eq!(mem::size_of::<PictureKey>(), 96, "PictureKey size changed");
 }
--- a/gfx/wr/webrender/src/prim_store/text_run.rs
+++ b/gfx/wr/webrender/src/prim_store/text_run.rs
@@ -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/. */
 
 use api::{ColorF, DevicePixelScale, GlyphInstance, LayoutPrimitiveInfo};
-use api::{LayoutRect, LayoutToWorldTransform, RasterSpace};
+use api::{LayoutToWorldTransform, RasterSpace};
 use api::{LayoutVector2D, Shadow};
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use frame_builder::{FrameBuildingState, PictureContext};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::GpuCache;
 use intern;
 use prim_store::{PrimitiveOpacity, PrimitiveSceneData,  PrimitiveScratchBuffer};
 use prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData, VectorKey};
@@ -30,23 +30,21 @@ pub struct TextRunKey {
     pub offset: VectorKey,
     pub glyphs: Vec<GlyphInstance>,
     pub shadow: bool,
 }
 
 impl TextRunKey {
     pub fn new(
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
         text_run: TextRun,
     ) -> Self {
         TextRunKey {
             common: PrimKeyCommonData::with_info(
                 info,
-                prim_relative_clip_rect,
             ),
             font: text_run.font,
             offset: text_run.offset.into(),
             glyphs: text_run.glyphs,
             shadow: text_run.shadow,
         }
     }
 }
@@ -182,21 +180,19 @@ impl intern::Internable for TextRun {
     type Source = TextRunKey;
     type StoreData = TextRunTemplate;
     type InternData = PrimitiveSceneData;
 
     /// Build a new key from self with `info`.
     fn build_key(
         self,
         info: &LayoutPrimitiveInfo,
-        prim_relative_clip_rect: LayoutRect,
     ) -> TextRunKey {
         TextRunKey::new(
             info,
-            prim_relative_clip_rect,
             self,
         )
     }
 }
 
 impl CreateShadow for TextRun {
     fn create_shadow(&self, shadow: &Shadow) -> Self {
         let mut font = FontInstance {
@@ -336,12 +332,12 @@ fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
     assert_eq!(mem::size_of::<TextRun>(), 112, "TextRun size changed");
-    assert_eq!(mem::size_of::<TextRunTemplate>(), 144, "TextRunTemplate size changed");
-    assert_eq!(mem::size_of::<TextRunKey>(), 136, "TextRunKey size changed");
+    assert_eq!(mem::size_of::<TextRunTemplate>(), 128, "TextRunTemplate size changed");
+    assert_eq!(mem::size_of::<TextRunKey>(), 120, "TextRunKey size changed");
     assert_eq!(mem::size_of::<TextRunPrimitive>(), 88, "TextRunPrimitive size changed");
 }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -600,16 +600,17 @@ impl Document {
 
         // Give the old frame builder a chance to destroy any resources.
         // Right now, all this does is build a hash map of any cached
         // surface tiles, that can be provided to the next frame builder.
         let mut retained_tiles = RetainedTiles::new();
         if let Some(frame_builder) = self.frame_builder.take() {
             frame_builder.destroy(
                 &mut retained_tiles,
+                &self.clip_scroll_tree,
             );
         }
 
         // Provide any cached tiles from the previous frame builder to
         // the newly built one.
         built_scene.frame_builder.set_retained_tiles(retained_tiles);
 
         self.frame_builder = Some(built_scene.frame_builder);
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -2476,17 +2476,17 @@ static const JSFunctionSpec ReadableStre
 
 CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount,
            ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
 
 static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
                                           HandleValue V, HandleValue arg);
 
 static void ReadableStreamControllerClearAlgorithms(
-    ReadableStreamController* controller);
+    Handle<ReadableStreamController*> controller);
 
 /**
  * Unified implementation of ReadableStream controllers' [[CancelSteps]]
  * internal methods.
  * Streams spec, 3.8.5.1. [[CancelSteps]] ( reason )
  * and
  * Streams spec, 3.10.5.1. [[CancelSteps]] ( reason )
  */
@@ -2630,19 +2630,22 @@ static JSObject* ReadableStreamDefaultCo
   if (unwrappedQueue && unwrappedQueue->length() != 0) {
     // Step a: Let chunk be ! DequeueValue(this).
     RootedValue chunk(cx);
     if (!DequeueValue(cx, unwrappedController, &chunk)) {
       return nullptr;
     }
 
     // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
-    //         perform ! ReadableStreamClose(stream).
     if (unwrappedController->closeRequested() &&
         unwrappedQueue->length() == 0) {
+      // Step i: Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
+      ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+      // Step ii: Perform ! ReadableStreamClose(stream).
       if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
         return nullptr;
       }
     }
 
     // Step c: Otherwise, perform
     //         ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
     else {
@@ -2937,22 +2940,24 @@ static bool ReadableStreamControllerShou
 
 /**
  * Streams spec, 3.9.4.
  *      ReadableStreamDefaultControllerClearAlgorithms ( controller )
  * and 3.12.4.
  *      ReadableByteStreamControllerClearAlgorithms ( controller )
  */
 static void ReadableStreamControllerClearAlgorithms(
-    ReadableStreamController* controller) {
+    Handle<ReadableStreamController*> controller) {
   // Step 1: Set controller.[[pullAlgorithm]] to undefined.
+  // Step 2: Set controller.[[cancelAlgorithm]] to undefined.
+  // (In this implementation, the UnderlyingSource slot is part of the
+  // representation of these algorithms.)
   controller->setPullMethod(UndefinedHandleValue);
-
-  // Step 2: Set controller.[[cancelAlgorithm]] to undefined.
   controller->setCancelMethod(UndefinedHandleValue);
+  ReadableStreamController::clearUnderlyingSource(controller);
 
   // Step 3 (of 3.9.4 only) : Set controller.[[strategySizeAlgorithm]] to
   // undefined.
   if (controller->is<ReadableStreamDefaultController>()) {
     controller->as<ReadableStreamDefaultController>().setStrategySize(
         UndefinedHandleValue);
   }
 }
@@ -2970,20 +2975,24 @@ static MOZ_MUST_USE bool ReadableStreamD
   //         ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
   //         is true.
   MOZ_ASSERT(!unwrappedController->closeRequested());
   MOZ_ASSERT(unwrappedStream->readable());
 
   // Step 3: Set controller.[[closeRequested]] to true.
   unwrappedController->setCloseRequested();
 
-  // Step 5: If controller.[[queue]] is empty, perform
-  //         ! ReadableStreamClose(stream).
+  // Step 4: If controller.[[queue]] is empty,
   Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
   if (unwrappedQueue->length() == 0) {
+    // Step a: Perform
+    //         ! ReadableStreamDefaultControllerClearAlgorithms(controller).
+    ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+    // Step b: Perform ! ReadableStreamClose(stream).
     return ReadableStreamCloseInternal(cx, unwrappedStream);
   }
 
   return true;
 }
 
 static MOZ_MUST_USE bool EnqueueValueWithSize(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
@@ -3103,17 +3112,22 @@ static MOZ_MUST_USE bool ReadableStreamC
     }
   }
 
   // Step 3 (or 4): Perform ! ResetQueue(controller).
   if (!ResetQueue(cx, unwrappedController)) {
     return false;
   }
 
-  // Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
+  // Step 4 (or 5):
+  //      Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller)
+  //      (or ReadableByteStreamControllerClearAlgorithms(controller)).
+  ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+  // Step 5 (or 6): Perform ! ReadableStreamError(stream, e).
   return ReadableStreamErrorInternal(cx, unwrappedStream, e);
 }
 
 /**
  * Streams spec, 3.9.8.
  *      ReadableStreamDefaultControllerGetDesiredSize ( controller )
  * Streams spec 3.12.14.
  *      ReadableByteStreamControllerGetDesiredSize ( controller )
@@ -3873,17 +3887,20 @@ static MOZ_MUST_USE bool ReadableByteStr
       }
 
       // Step iii: Throw e.
       cx->setPendingException(e);
       return false;
     }
   }
 
-  // Step 6: Perform ! ReadableStreamClose(stream).
+  // Step 6: Perform ! ReadableByteStreamControllerClearAlgorithms(controller).
+  ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+  // Step 7: Perform ! ReadableStreamClose(stream).
   return ReadableStreamCloseInternal(cx, unwrappedStream);
 }
 
 // Streams spec, 3.12.11. ReadableByteStreamControllerError ( controller, e )
 // Unified with 3.9.7 above.
 
 // Streams spec 3.12.14.
 //      ReadableByteStreamControllerGetDesiredSize ( controller )
@@ -3902,16 +3919,20 @@ static MOZ_MUST_USE bool ReadableByteStr
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
   MOZ_ASSERT(unwrappedStream->readable());
 
   // Step 2: If controller.[[queueTotalSize]] is 0 and
   //         controller.[[closeRequested]] is true,
   if (unwrappedController->queueTotalSize() == 0 &&
       unwrappedController->closeRequested()) {
     // Step a: Perform
+    //         ! ReadableByteStreamControllerClearAlgorithms(controller).
+    ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+    // Step b: Perform
     //         ! ReadableStreamClose(controller.[[controlledReadableStream]]).
     return ReadableStreamCloseInternal(cx, unwrappedStream);
   }
 
   // Step 3: Otherwise,
   // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
   return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
 }
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -311,16 +311,24 @@ class ReadableStreamController : public 
     MOZ_ASSERT(hasExternalSource());
     return static_cast<JS::ReadableStreamUnderlyingSource*>(
         underlyingSource().toPrivate());
   }
   void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
     setUnderlyingSource(JS::PrivateValue(underlyingSource));
     addFlags(Flag_ExternalSource);
   }
+  static void clearUnderlyingSource(
+      JS::Handle<ReadableStreamController*> controller) {
+    if (controller->hasExternalSource()) {
+      controller->externalSource()->finalize();
+      controller->setFlags(controller->flags() & ~Flag_ExternalSource);
+    }
+    controller->setUnderlyingSource(JS::UndefinedHandleValue);
+  }
   double strategyHWM() const {
     return getFixedSlot(Slot_StrategyHWM).toNumber();
   }
   void setStrategyHWM(double highWaterMark) {
     setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark));
   }
   uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
   void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/getPrototype-cycle-for-in.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 6
+timeout(0.5);
+
+var proxy = new Proxy({}, {
+    getPrototypeOf() {
+        return proxy;
+    }
+});
+
+var obj = {a: 1, b: 2, __proto__: proxy};
+for (var x in obj) {}
+assertEq(0, 1); // Should timeout.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/getPrototype-cycle-hasInstance.js
@@ -0,0 +1,11 @@
+// |jit-test| exitstatus: 6
+timeout(0.5)
+
+var proxy = new Proxy({}, {
+    getPrototypeOf() {
+        return proxy;
+    }
+});
+
+var x = proxy instanceof function() {};
+assertEq(0, 1); // Should timeout.
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -537,16 +537,20 @@ static bool Snapshot(JSContext* cx, Hand
     if (flags & JSITER_OWNONLY) {
       break;
     }
 
     if (!GetPrototype(cx, pobj, &pobj)) {
       return false;
     }
 
+    // The [[Prototype]] chain might be cyclic.
+    if (!CheckForInterrupt(cx)) {
+      return false;
+    }
   } while (pobj != nullptr);
 
 #ifdef JS_MORE_DETERMINISTIC
 
   /*
    * In some cases the enumeration order for an object depends on the
    * execution mode (interpreter vs. JIT), especially for native objects
    * with a class enumerate hook (where resolving a property changes the
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -3395,16 +3395,20 @@ bool js::ToPropertyKeySlow(JSContext* cx
 }
 
 /* * */
 
 bool js::IsPrototypeOf(JSContext* cx, HandleObject protoObj, JSObject* obj,
                        bool* result) {
   RootedObject obj2(cx, obj);
   for (;;) {
+    // The [[Prototype]] chain might be cyclic.
+    if (!CheckForInterrupt(cx)) {
+      return false;
+    }
     if (!GetPrototype(cx, obj2, &obj2)) {
       return false;
     }
     if (!obj2) {
       *result = false;
       return true;
     }
     if (obj2 == protoObj) {
--- a/js/src/vm/JSScript-inl.h
+++ b/js/src/vm/JSScript-inl.h
@@ -171,24 +171,14 @@ inline bool JSScript::ensureHasAnalyzedA
   }
   return js::jit::AnalyzeArgumentsUsage(cx, this);
 }
 
 inline bool JSScript::isDebuggee() const {
   return realm_->debuggerObservesAllExecution() || hasDebugScript();
 }
 
-inline bool JSScript::trackRecordReplayProgress() const {
-  // Progress is only tracked when recording or replaying, and only for
-  // scripts associated with the main thread's runtime. Whether self hosted
-  // scripts execute may depend on performed Ion optimizations (for example,
-  // self hosted TypedObject logic), so they are ignored.
-  return MOZ_UNLIKELY(mozilla::recordreplay::IsRecordingOrReplaying()) &&
-         !runtimeFromAnyThread()->parentRuntime && !selfHosted() &&
-         mozilla::recordreplay::ShouldUpdateProgressCounter(filename());
-}
-
 inline js::jit::ICScript* JSScript::icScript() const {
   MOZ_ASSERT(hasICScript());
   return types_->icScript();
 }
 
 #endif /* vm_JSScript_inl_h */
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -3126,16 +3126,27 @@ JSScript::JSScript(JS::Realm* realm, uin
   uint8_t* stubEntry = nullptr;
 #endif
 
   return new (script)
       JSScript(cx->realm(), stubEntry, sourceObject, sourceStart, sourceEnd,
                toStringStart, toStringEnd);
 }
 
+static bool ShouldTrackRecordReplayProgress(JSScript* script) {
+  // Progress is only tracked when recording or replaying, and only for
+  // scripts associated with the main thread's runtime. Whether self hosted
+  // scripts execute may depend on performed Ion optimizations (for example,
+  // self hosted TypedObject logic), so they are ignored.
+  return MOZ_UNLIKELY(mozilla::recordreplay::IsRecordingOrReplaying()) &&
+         !script->runtimeFromAnyThread()->parentRuntime &&
+         !script->selfHosted() &&
+         mozilla::recordreplay::ShouldUpdateProgressCounter(script->filename());
+}
+
 /* static */ JSScript* JSScript::Create(
     JSContext* cx, const ReadOnlyCompileOptions& options,
     HandleScriptSourceObject sourceObject, uint32_t sourceStart,
     uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd) {
   RootedScript script(cx, JSScript::New(cx, sourceObject, sourceStart,
                                         sourceEnd, toStringStart, toStringEnd));
   if (!script) {
     return nullptr;
@@ -3143,16 +3154,19 @@ JSScript::JSScript(JS::Realm* realm, uin
 
   // Record compile options that get checked at runtime.
   script->setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
   script->setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
   script->setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
   script->setFlag(MutableFlags::HideScriptFromDebugger,
                   options.hideScriptFromDebugger);
 
+  script->setFlag(ImmutableFlags::TrackRecordReplayProgress,
+                  ShouldTrackRecordReplayProgress(script));
+
   if (cx->runtime()->lcovOutput().isEnabled()) {
     if (!script->initScriptName(cx)) {
       return nullptr;
     }
   }
 
   return script;
 }
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1689,16 +1689,20 @@ class JSScript : public js::gc::TenuredC
     // Set if this function has a rest parameter.
     HasRest = 1 << 20,
 
     // See comments below.
     ArgsHasVarBinding = 1 << 21,
 
     // Script came from eval().
     IsForEval = 1 << 22,
+
+    // Whether the record/replay execution progress counter (see RecordReplay.h)
+    // should be updated as this script runs.
+    TrackRecordReplayProgress = 1 << 23,
   };
   // Note: don't make this a bitfield! It makes it hard to read these flags
   // from JIT code.
   uint32_t immutableFlags_ = 0;
 
   // Mutable flags typically store information about runtime or deoptimization
   // behavior of this script. This is only public for the JITs.
  public:
@@ -2727,19 +2731,19 @@ class JSScript : public js::gc::TenuredC
     operator JS::HandleScript() const { return script_; }
     explicit operator bool() const { return script_; }
 
    private:
     void holdScript(JS::HandleFunction fun);
     void dropScript();
   };
 
-  // Return whether the record/replay execution progress counter
-  // (see RecordReplay.h) should be updated as this script runs.
-  inline bool trackRecordReplayProgress() const;
+  bool trackRecordReplayProgress() const {
+    return hasFlag(ImmutableFlags::TrackRecordReplayProgress);
+  }
 };
 
 /* If this fails, add/remove padding within JSScript. */
 static_assert(
     sizeof(JSScript) % js::gc::CellAlignBytes == 0,
     "Size of JSScript must be an integral multiple of js::gc::CellAlignBytes");
 
 namespace js {
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -9369,19 +9369,19 @@ bool PresShell::VerifyIncrementalReflow(
 
   // Now that the document has been reflowed, use its frame tree to
   // compare against our frame tree.
   nsIFrame* root1 = mFrameConstructor->GetRootFrame();
   nsIFrame* root2 = sh->GetRootFrame();
   bool ok = CompareTrees(mPresContext, root1, cx, root2);
   if (!ok && (VERIFY_REFLOW_NOISY & gVerifyReflowFlags)) {
     printf("Verify reflow failed, primary tree:\n");
-    root1->List(stdout, 0);
+    root1->List(stdout);
     printf("Verification tree:\n");
-    root2->List(stdout, 0);
+    root2->List(stdout);
   }
 
 #if 0
   // Sample code for dumping page to png
   // XXX Needs to be made more flexible
   if (!ok) {
     nsString stra;
     static int num = 0;
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -372,16 +372,22 @@ static inline bool IsDisplayContents(con
  * True if aFrame is an instance of an SVG frame class or is an inline/block
  * frame being used for SVG text.
  */
 static bool IsFrameForSVG(const nsIFrame* aFrame) {
   return aFrame->IsFrameOfType(nsIFrame::eSVG) ||
          nsSVGUtils::IsInSVGTextSubtree(aFrame);
 }
 
+static bool IsLastContinuationForColumnContent(const nsIFrame* aFrame) {
+  MOZ_ASSERT(aFrame);
+  return aFrame->Style()->GetPseudo() == nsCSSAnonBoxes::columnContent() &&
+         !aFrame->GetNextContinuation();
+}
+
 /**
  * Returns true iff aFrame explicitly prevents its descendants from floating
  * (at least, down to the level of descendants which themselves are
  * float-containing blocks -- those will manage the floating status of any
  * lower-level descendents inside them, of course).
  */
 static bool ShouldSuppressFloatingOfDescendants(nsIFrame* aFrame) {
   return aFrame->IsFlexOrGridContainer() || aFrame->IsXULBoxFrame() ||
@@ -5845,30 +5851,30 @@ static nsIFrame* GetInsertNextSibling(ns
                                       nsIFrame* aPrevSibling) {
   if (aPrevSibling) {
     return aPrevSibling->GetNextSibling();
   }
 
   return aParentFrame->PrincipalChildList().FirstChild();
 }
 
-/**
- * This function is called by ContentAppended() and ContentInserted() when
- * appending flowed frames to a parent's principal child list. It handles the
- * case where the parent is the trailing inline of an {ib} split.
- */
 void nsCSSFrameConstructor::AppendFramesToParent(
     nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
     nsFrameItems& aFrameList, nsIFrame* aPrevSibling, bool aIsRecursiveCall) {
   MOZ_ASSERT(
       !IsFramePartOfIBSplit(aParentFrame) || !GetIBSplitSibling(aParentFrame) ||
           !GetIBSplitSibling(aParentFrame)->PrincipalChildList().FirstChild(),
       "aParentFrame has a ib-split sibling with kids?");
   MOZ_ASSERT(!aPrevSibling || aPrevSibling->GetParent() == aParentFrame,
              "Parent and prevsibling don't match");
+  MOZ_ASSERT(
+      !aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+          !IsFramePartOfIBSplit(aParentFrame),
+      "We should have wiped aParentFrame in WipeContainingBlock() "
+      "if it's part of an IB split!");
 
   nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling);
 
   NS_ASSERTION(nextSibling || !aParentFrame->GetNextContinuation() ||
                    !aParentFrame->GetNextContinuation()
                         ->PrincipalChildList()
                         .FirstChild() ||
                    aIsRecursiveCall,
@@ -5933,16 +5939,42 @@ void nsCSSFrameConstructor::AppendFrames
       // Recurse so we create new ib siblings as needed for aParentFrame's
       // parent
       return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings,
                                   aParentFrame, true);
     }
     return;
   }
 
+  // If we're appending a list of frames at the last continuations of a
+  // ::-moz-column-content, we may need to create column-span siblings for them.
+  if (!nextSibling && IsLastContinuationForColumnContent(aParentFrame)) {
+    // Extract any initial non-column-span kids, and append them to
+    // ::-moz-column-content's child list.
+    nsFrameList initialNonColumnSpanKids =
+        aFrameList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+    AppendFrames(aParentFrame, kPrincipalList, initialNonColumnSpanKids);
+
+    if (aFrameList.IsEmpty()) {
+      // No more kids to process (there weren't any column-span kids).
+      return;
+    }
+
+    nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
+        aState, aParentFrame, aFrameList,
+        aParentFrame->IsAbsPosContainingBlock() ? aParentFrame : nullptr);
+    FinishBuildingColumns(aState,
+                          GetMultiColumnContainingBlockFor(aParentFrame),
+                          aParentFrame, columnSpanSiblings);
+
+    MOZ_ASSERT(columnSpanSiblings.IsEmpty(),
+               "The column-span siblings should be moved to the proper place!");
+    return;
+  }
+
   // Insert the frames after our aPrevSibling
   InsertFrames(aParentFrame, kPrincipalList, aPrevSibling, aFrameList);
 }
 
 // This gets called to see if the frames corresponding to aSibling and aContent
 // should be siblings in the frame tree. Although (1) rows and cols, (2) row
 // groups and col groups, (3) row groups and captions, (4) legends and content
 // inside fieldsets, (5) popups and other kids of the menu are siblings from a
@@ -6855,17 +6887,17 @@ void nsCSSFrameConstructor::ContentAppen
   // Recover first-letter frames
   if (haveFirstLetterStyle) {
     RecoverLetterFrames(containingBlock);
   }
 
 #ifdef DEBUG
   if (gReallyNoisyContentUpdates) {
     printf("nsCSSFrameConstructor::ContentAppended: resulting frame model:\n");
-    parentFrame->List(stdout, 0);
+    parentFrame->List(stdout);
   }
 #endif
 
 #ifdef ACCESSIBILITY
   if (nsAccessibilityService* accService = nsIPresShell::AccService()) {
     accService->ContentRangeInserted(mPresShell, aFirstNewContent, nullptr);
   }
 #endif
@@ -6958,17 +6990,17 @@ void nsCSSFrameConstructor::ContentRange
     // Create frames for the document element and its child elements
     if (ConstructDocElementFrame(docElement, aFrameState)) {
       InvalidateCanvasIfNeeded(mPresShell, aStartChild);
 #ifdef DEBUG
       if (gReallyNoisyContentUpdates) {
         printf(
             "nsCSSFrameConstructor::ContentRangeInserted: resulting frame "
             "model:\n");
-        mRootElementFrame->List(stdout, 0);
+        mRootElementFrame->List(stdout);
       }
 #endif
     }
 
     if (aFrameState) {
       // Restore frame state for the root scroll frame if there is one
       if (nsIFrame* rootScrollFrame = mPresShell->GetRootScrollFrame()) {
         RestoreFrameStateFor(rootScrollFrame, aFrameState);
@@ -7320,17 +7352,17 @@ void nsCSSFrameConstructor::ContentRange
     RecoverLetterFrames(state.mFloatedItems.containingBlock);
   }
 
 #ifdef DEBUG
   if (gReallyNoisyContentUpdates && insertion.mParentFrame) {
     printf(
         "nsCSSFrameConstructor::ContentRangeInserted: resulting frame "
         "model:\n");
-    insertion.mParentFrame->List(stdout, 0);
+    insertion.mParentFrame->List(stdout);
   }
 #endif
 
 #ifdef ACCESSIBILITY
   if (nsAccessibilityService* accService = nsIPresShell::AccService()) {
     accService->ContentRangeInserted(mPresShell, aStartChild, aEndChild);
   }
 #endif
@@ -7451,20 +7483,19 @@ bool nsCSSFrameConstructor::ContentRemov
   };
 
   if (!childFrame && CouldHaveBeenDisplayContents(aChild)) {
     // NOTE(emilio): We may iterate through ::before and ::after here and they
     // may be gone after the respective ContentRemoved call. Right now
     // StyleChildrenIterator handles that properly, so it's not an issue.
     StyleChildrenIterator iter(aChild);
     for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
-      if (c->GetPrimaryFrame() || CouldHaveBeenDisplayContents(aChild)) {
+      if (c->GetPrimaryFrame() || CouldHaveBeenDisplayContents(c)) {
         LAYOUT_PHASE_TEMP_EXIT();
-        bool didReconstruct =
-            ContentRemoved(c, nullptr, REMOVE_FOR_RECONSTRUCTION);
+        bool didReconstruct = ContentRemoved(c, nullptr, aFlags);
         LAYOUT_PHASE_TEMP_REENTER();
         if (didReconstruct) {
           return true;
         }
       }
     }
     return false;
   }
@@ -7576,17 +7607,17 @@ bool nsCSSFrameConstructor::ContentRemov
 #endif
     }
 
 #ifdef DEBUG
     if (gReallyNoisyContentUpdates) {
       printf("nsCSSFrameConstructor::ContentRemoved: childFrame=");
       nsFrame::ListTag(stdout, childFrame);
       putchar('\n');
-      parentFrame->List(stdout, 0);
+      parentFrame->List(stdout);
     }
 #endif
 
     // Notify the parent frame that it should delete the frame
     if (childFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
       childFrame = childFrame->GetPlaceholderFrame();
       NS_ASSERTION(childFrame, "Missing placeholder frame for out of flow.");
       parentFrame = childFrame->GetParent();
@@ -7647,17 +7678,17 @@ bool nsCSSFrameConstructor::ContentRemov
         ReframeTextIfNeeded(aOldNextSibling);
         LAYOUT_PHASE_TEMP_REENTER();
       }
     }
 
 #ifdef DEBUG
     if (gReallyNoisyContentUpdates && parentFrame) {
       printf("nsCSSFrameConstructor::ContentRemoved: resulting frame model:\n");
-      parentFrame->List(stdout, 0);
+      parentFrame->List(stdout);
     }
 #endif
   }
 
   return false;
 }
 
 /**
@@ -10588,17 +10619,17 @@ void nsCSSFrameConstructor::ConstructBlo
     // Create the outside bullet on ColumnSetWrapper so that the position of
     // the bullet is correct.
     blockFrameToCreateBullet = static_cast<nsBlockFrame*>(*aNewFrame);
   }
 
   CreateBulletFrameForListItemIfNeeded(blockFrameToCreateBullet);
 
   if (childItems.IsEmpty()) {
-    // No more column-span kids need to be processed.
+    // No more kids to process (there weren't any column-span kids).
     return;
   }
 
   nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
       aState, blockFrame, childItems, aPositionedFrameForAbsPosContainer);
 
   if (needsColumn) {
     // We're constructing a column container; need to finish building it.
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -445,21 +445,29 @@ class nsCSSFrameConstructor final : publ
 
   // aParentFrame may be null; this method doesn't use it directly in any case.
   void CreateGeneratedContentItem(nsFrameConstructorState& aState,
                                   nsContainerFrame* aParentFrame,
                                   Element& aOriginatingElement, ComputedStyle&,
                                   CSSPseudoElementType aPseudoElement,
                                   FrameConstructionItemList& aItems);
 
-  // This method can change aFrameList: it can chop off the beginning and put
-  // it in aParentFrame while putting the remainder into a ib-split sibling of
-  // aParentFrame.  aPrevSibling must be the frame after which aFrameList is to
-  // be placed on aParentFrame's principal child list.  It may be null if
-  // aFrameList is being added at the beginning of the child list.
+  // This method is called by ContentAppended() and ContentRangeInserted() when
+  // appending flowed frames to a parent's principal child list. It handles the
+  // case where the parent is the trailing inline of an ib-split or is the last
+  // continuation of a ::-moz-column-content in an nsColumnSetFrame.
+  //
+  // This method can change aFrameList: it can chop off the beginning and put it
+  // in aParentFrame while either putting the remainder into an ib-split sibling
+  // of aParentFrame or creating aParentFrame's column-span siblings for the
+  // remainder.
+  //
+  // aPrevSibling must be the frame after which aFrameList is to be placed on
+  // aParentFrame's principal child list. It may be null if aFrameList is being
+  // added at the beginning of the child list.
   void AppendFramesToParent(nsFrameConstructorState& aState,
                             nsContainerFrame* aParentFrame,
                             nsFrameItems& aFrameList, nsIFrame* aPrevSibling,
                             bool aIsRecursiveCall = false);
 
   // BEGIN TABLE SECTION
   /**
    * Construct a table wrapper frame. This is the FrameConstructionData
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1503420.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>
+    Test for Bug 1503420: Test we don't reframe multi-column containing block
+    when appending a block containing a spanner kid.
+  </title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script>
+  const utils = SpecialPowers.getDOMWindowUtils(window);
+
+  function appendBlock() {
+    // Create a subtree like the following, and append it to columns.
+    // <div>
+    //   <h3>spanner</h3>
+    //   block2
+    // </div>
+    var spanner = document.createElement("h3");
+    var spannerText = document.createTextNode("spanner");
+    spanner.appendChild(spannerText);
+
+    var block2 = document.createElement("div");
+    var block2Text = document.createTextNode("block2");
+    block2.appendChild(spanner);
+    block2.appendChild(block2Text)
+
+    var column = document.getElementById("column");
+    column.appendChild(block2);
+  }
+
+  function runTest() {
+    document.documentElement.offsetTop;
+    // We expected to construct 6 more frames.
+    // 1) Block frame for <div>
+    // 2) Block frame for <h3>
+    // 3) Text frame for "spanner"
+    // 4) Text frame for "block2"
+    // 5) Column-span wrapper for <h3>, which is a sibling of <div>
+    // 6) Column-span wrapper for 5), which is a sibling of <article>
+    // Note: creating a continuation frame doesn't increase the count.
+    const expectedFrameConstructCount = utils.framesConstructed + 6;
+
+    appendBlock();
+    document.documentElement.offsetTop;
+
+    window.parent.postMessage({
+      "actualResult": utils.framesConstructed,
+      "expectedResult": expectedFrameConstructCount,
+      "message": "We shouldn't construct unexpected frames.",
+    }, "*");
+
+    window.parent.postMessage({"done": true}, "*");
+  }
+  </script>
+
+  <style>
+  #column {
+    column-count: 3;
+    column-rule: 6px solid;
+    width: 400px;
+    outline: 1px solid black;
+  }
+  h3 {
+    column-span: all;
+    outline: 1px solid blue;
+  }
+  </style>
+
+  <body onload="runTest();">
+    <article id="column">
+      <div>block1</div>
+    </article>
+  </body>
+</html>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -148,16 +148,19 @@ support-files = bug1226904.html
 [test_emulateMedium.html]
 [test_event_target_iframe_oop.html]
 skip-if = e10s # bug 1020135, nested oop iframes not supported
 support-files = bug921928_event_target_iframe_apps_oop.html
 [test_event_target_radius.html]
 skip-if = toolkit == 'android' # Bug 1355836
 [test_flush_on_paint.html]
 skip-if = true # Bug 688128
+[test_frame_reconstruction_for_column_span.html]
+support-files =
+  bug1503420.html
 [test_frame_reconstruction_for_pseudo_elements.html]
 [test_frame_reconstruction_for_svg_transforms.html]
 [test_frame_reconstruction_scroll_restore.html]
 [test_getBoxQuads_convertPointRectQuad.html]
 support-files =
   file_getBoxQuads_convertPointRectQuad_frame1.html
   file_getBoxQuads_convertPointRectQuad_frame2.html
 [test_getClientRects_emptytext.html]
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_for_column_span.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>
+    Loader to test frame construction for column span.
+  </title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script>
+  SimpleTest.waitForExplicitFinish();
+
+  function receiveMessage(event)
+  {
+    console.log(event);
+    if (event.data.done) {
+      window.removeEventListener("message", receiveMessage);
+      SimpleTest.finish();
+      return;
+    }
+
+    is(event.data.actualResult, event.data.expectedResult,
+       event.data.message);
+  }
+
+  function prepareTest() {
+    window.addEventListener("message", receiveMessage);
+
+    SpecialPowers.pushPrefEnv({
+      "set": [
+        ["layout.css.column-span.enabled", true]
+      ]
+    }, function () {
+      let iframe = document.getElementById("testFrame");
+      iframe.src = "bug1503420.html";
+    });
+  }
+  </script>
+
+  <body onload="prepareTest()">
+    <iframe id="testFrame" height="500" width="500"></iframe>
+  </body>
+</html>
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -1656,16 +1656,24 @@ class FLBDisplayItemIterator : protected
       return false;
     }
 
     if (!mNext->ShouldFlattenAway(mBuilder)) {
       return false;
     }
 
     const DisplayItemType type = mNext->GetType();
+
+    if (type == DisplayItemType::TYPE_SVG_WRAPPER) {
+      // We mark SetContainsSVG for the CONTENT_FRAME_TIME_WITH_SVG metric
+      if (RefPtr<LayerManager> lm = mBuilder->GetWidgetLayerManager()) {
+        lm->SetContainsSVG(true);
+      }
+    }
+
     if (type != DisplayItemType::TYPE_OPACITY &&
         type != DisplayItemType::TYPE_TRANSFORM) {
       return true;
     }
 
     if (type == DisplayItemType::TYPE_OPACITY) {
       nsDisplayOpacity* opacity = static_cast<nsDisplayOpacity*>(mNext);
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2555,16 +2555,18 @@ already_AddRefed<LayerManager> nsDisplay
 
   RefPtr<LayerManager> layerManager;
   bool widgetTransaction = false;
   bool doBeginTransaction = true;
   nsView* view = nullptr;
   if (aFlags & PAINT_USE_WIDGET_LAYERS) {
     layerManager = aBuilder->GetWidgetLayerManager(&view);
     if (layerManager) {
+      layerManager->SetContainsSVG(false);
+
       doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION);
       widgetTransaction = true;
     }
   }
   if (!layerManager) {
     if (!aCtx) {
       NS_WARNING("Nowhere to paint into");
       return nullptr;
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -221,18 +221,18 @@ class WebrtcAudioConduit : public AudioS
   bool GetSendPacketTypeStats(
       webrtc::RtcpPacketTypeCounter* aPacketCounts) override;
 
   bool GetRecvPacketTypeStats(
       webrtc::RtcpPacketTypeCounter* aPacketCounts) override;
 
   bool GetVideoEncoderStats(double* framerateMean, double* framerateStdDev,
                             double* bitrateMean, double* bitrateStdDev,
-                            uint32_t* droppedFrames,
-                            uint32_t* framesEncoded) override {
+                            uint32_t* droppedFrames, uint32_t* framesEncoded,
+                            Maybe<uint64_t>* qpSum) override {
     return false;
   }
   bool GetVideoDecoderStats(double* framerateMean, double* framerateStdDev,
                             double* bitrateMean, double* bitrateStdDev,
                             uint32_t* discardedPackets,
                             uint32_t* framesDecoded) override {
     return false;
   }
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -226,17 +226,18 @@ class MediaSessionConduit {
 
   virtual bool GetRecvPacketTypeStats(
       webrtc::RtcpPacketTypeCounter* aPacketCounts) = 0;
 
   virtual bool GetVideoEncoderStats(double* framerateMean,
                                     double* framerateStdDev,
                                     double* bitrateMean, double* bitrateStdDev,
                                     uint32_t* droppedFrames,
-                                    uint32_t* framesEncoded) = 0;
+                                    uint32_t* framesEncoded,
+                                    Maybe<uint64_t>* qpSum) = 0;
   virtual bool GetVideoDecoderStats(double* framerateMean,
                                     double* framerateStdDev,
                                     double* bitrateMean, double* bitrateStdDev,
                                     uint32_t* discardedPackets,
                                     uint32_t* framesDecoded) = 0;
   virtual bool GetRTPStats(unsigned int* jitterMs,
                            unsigned int* cumulativeLost) = 0;
   virtual bool GetRTCPReceiverReport(uint32_t* jitterMs,
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -294,16 +294,21 @@ uint64_t WebrtcVideoConduit::SendStreamS
 }
 
 uint32_t WebrtcVideoConduit::SendStreamStatistics::PacketsReceived() const {
   ASSERT_ON_THREAD(mStatsThread);
 
   return mPacketsReceived;
 }
 
+Maybe<uint64_t> WebrtcVideoConduit::SendStreamStatistics::QpSum() const {
+  ASSERT_ON_THREAD(mStatsThread);
+  return mQpSum;
+}
+
 void WebrtcVideoConduit::SendStreamStatistics::Update(
     const webrtc::VideoSendStream::Stats& aStats, uint32_t aConfiguredSsrc) {
   ASSERT_ON_THREAD(mStatsThread);
 
   mSsrcFound = false;
 
   if (aStats.substreams.empty()) {
     CSFLogVerbose(LOGTAG, "%s stats.substreams is empty", __FUNCTION__);
@@ -317,16 +322,21 @@ void WebrtcVideoConduit::SendStreamStati
                 __FUNCTION__, this);
     return;
   }
 
   mSsrcFound = true;
 
   StreamStatistics::Update(aStats.encode_frame_rate, aStats.media_bitrate_bps,
                            ind->second.rtcp_packet_type_counts);
+  if (aStats.qp_sum) {
+    mQpSum = Some(aStats.qp_sum.value());
+  } else {
+    mQpSum = Nothing();
+  }
 
   const webrtc::FrameCounts& fc = ind->second.frame_counts;
   mFramesEncoded = fc.key_frames + fc.delta_frames;
   CSFLogVerbose(
       LOGTAG, "%s: framerate: %u, bitrate: %u, dropped frames delta: %u",
       __FUNCTION__, aStats.encode_frame_rate, aStats.media_bitrate_bps,
       mFramesDeliveredToEncoder - mFramesEncoded - mDroppedFrames);
   mDroppedFrames = mFramesDeliveredToEncoder - mFramesEncoded;
@@ -1121,27 +1131,29 @@ void WebrtcVideoConduit::UpdateVideoStat
         "WebrtcVideoConduit::SendStreamStatsUpdater");
   } else {
     mVideoStatsTimer->Cancel();
   }
 }
 
 bool WebrtcVideoConduit::GetVideoEncoderStats(
     double* framerateMean, double* framerateStdDev, double* bitrateMean,
-    double* bitrateStdDev, uint32_t* droppedFrames, uint32_t* framesEncoded) {
+    double* bitrateStdDev, uint32_t* droppedFrames, uint32_t* framesEncoded,
+    Maybe<uint64_t>* qpSum) {
   ASSERT_ON_THREAD(mStsThread);
 
   MutexAutoLock lock(mMutex);
   if (!mEngineTransmitting || !mSendStream) {
     return false;
   }
   mSendStreamStats.GetVideoStreamStats(*framerateMean, *framerateStdDev,
                                        *bitrateMean, *bitrateStdDev);
   *droppedFrames = mSendStreamStats.DroppedFrames();
   *framesEncoded = mSendStreamStats.FramesEncoded();
+  *qpSum = mSendStreamStats.QpSum();
   return true;
 }
 
 bool WebrtcVideoConduit::GetVideoDecoderStats(double* framerateMean,
                                               double* framerateStdDev,
                                               double* bitrateMean,
                                               double* bitrateStdDev,
                                               uint32_t* discardedPackets,
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -251,18 +251,18 @@ class WebrtcVideoConduit
 
   bool GetRecvPacketTypeStats(
       webrtc::RtcpPacketTypeCounter* aPacketCounts) override;
 
   void PollStats();
   void UpdateVideoStatsTimer();
   bool GetVideoEncoderStats(double* framerateMean, double* framerateStdDev,
                             double* bitrateMean, double* bitrateStdDev,
-                            uint32_t* droppedFrames,
-                            uint32_t* framesEncoded) override;
+                            uint32_t* droppedFrames, uint32_t* framesEncoded,
+                            Maybe<uint64_t>* qpSum) override;
   bool GetVideoDecoderStats(double* framerateMean, double* framerateStdDev,
                             double* bitrateMean, double* bitrateStdDev,
                             uint32_t* discardedPackets,
                             uint32_t* framesDecoded) override;
   bool GetRTPStats(unsigned int* jitterMs,
                    unsigned int* cumulativeLost) override;
   bool GetRTCPReceiverReport(uint32_t* jitterMs, uint32_t* packetsReceived,
                              uint64_t* bytesReceived, uint32_t* cumulativeLost,
@@ -355,27 +355,29 @@ class WebrtcVideoConduit
      */
     void FrameDeliveredToEncoder();
 
     bool SsrcFound() const;
     uint32_t JitterMs() const;
     uint32_t PacketsLost() const;
     uint64_t BytesReceived() const;
     uint32_t PacketsReceived() const;
+    Maybe<uint64_t> QpSum() const;
 
    private:
     uint32_t mDroppedFrames = 0;
     uint32_t mFramesEncoded = 0;
     int32_t mFramesDeliveredToEncoder = 0;
 
     bool mSsrcFound = false;
     uint32_t mJitterMs = 0;
     uint32_t mPacketsLost = 0;
     uint64_t mBytesReceived = 0;
     uint32_t mPacketsReceived = 0;
+    Maybe<uint64_t> mQpSum;
   };
 
   /**
    * Statistics for receiving streams. Single threaded.
    */
   class ReceiveStreamStatistics : public StreamStatistics {
    public:
     explicit ReceiveStreamStatistics(nsCOMPtr<nsIEventTarget> aStatsThread)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2863,25 +2863,27 @@ RefPtr<RTCStatsQueryPromise> PeerConnect
           // Lastly, fill in video encoder stats if this is video
           if (!isAudio) {
             double framerateMean;
             double framerateStdDev;
             double bitrateMean;
             double bitrateStdDev;
             uint32_t droppedFrames;
             uint32_t framesEncoded;
+            Maybe<uint64_t> qpSum;
             if (mp.Conduit()->GetVideoEncoderStats(
                     &framerateMean, &framerateStdDev, &bitrateMean,
-                    &bitrateStdDev, &droppedFrames, &framesEncoded)) {
+                    &bitrateStdDev, &droppedFrames, &framesEncoded, &qpSum)) {
               s.mFramerateMean.Construct(framerateMean);
               s.mFramerateStdDev.Construct(framerateStdDev);
               s.mBitrateMean.Construct(bitrateMean);
               s.mBitrateStdDev.Construct(bitrateStdDev);
               s.mDroppedFrames.Construct(droppedFrames);
               s.mFramesEncoded.Construct(framesEncoded);
+              qpSum.apply([&s](uint64_t aQp) { s.mQpSum.Construct(aQp); });
             }
           }
           query->report->mOutboundRTPStreamStats.Value().AppendElement(
               s, fallible);
         }
         break;
       }
       case MediaPipeline::DirectionType::RECEIVE: {
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -700,19 +700,19 @@ package org.mozilla.geckoview {
   @android.support.annotation.UiThread public class GeckoView extends android.widget.FrameLayout {
     ctor public GeckoView(android.content.Context);
     ctor public GeckoView(android.content.Context, android.util.AttributeSet);
     method public void coverUntilFirstPaint(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.gecko.EventDispatcher getEventDispatcher();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.PanZoomController getPanZoomController();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession getSession();
-    method @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession releaseSession();
-    method public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
-    method public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntime);
+    method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession releaseSession();
+    method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
+    method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntime);
     method public boolean shouldPinOnScreen();
     field protected final org.mozilla.geckoview.GeckoView.Display mDisplay;
     field protected org.mozilla.geckoview.GeckoRuntime mRuntime;
     field protected org.mozilla.geckoview.GeckoSession mSession;
     field protected android.view.SurfaceView mSurfaceView;
   }
 
   @android.support.annotation.AnyThread public class GeckoWebExecutor {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -237,16 +237,24 @@ public class GeckoView extends FrameLayo
     }
 
     /* package */ void setActive(final boolean active) {
         if (mSession != null) {
             mSession.setActive(active);
         }
     }
 
+    /**
+     * Unsets the current session from this instance and returns it, if any. You must call
+     * this before {@link #setSession(GeckoSession)} if there is already an open session
+     * set for this instance.
+     *
+     * @return The {@link GeckoSession} that was set for this instance. May be null.
+     */
+    @UiThread
     public @Nullable GeckoSession releaseSession() {
         ThreadUtils.assertOnUiThread();
 
         if (mSession == null) {
             return null;
         }
 
         // Cover the view while we are not drawing to the surface.
@@ -274,37 +282,46 @@ public class GeckoView extends FrameLayo
         }
         mSession = null;
         mRuntime = null;
         return session;
     }
 
     /**
      * Attach a session to this view. The session should be opened before
-     * attaching.
+     * attaching. If this instance already has an open session, you must use
+     * {@link #releaseSession()} first, otherwise {@link IllegalStateException}
+     * will be thrown. This is to avoid potentially leaking the currently opened session.
      *
      * @param session The session to be attached.
+     * @throws IllegalArgumentException if an existing open session is already set.
      */
+    @UiThread
     public void setSession(@NonNull final GeckoSession session) {
         ThreadUtils.assertOnUiThread();
 
         if (!session.isOpen()) {
             throw new IllegalArgumentException("Session must be open before attaching");
         }
 
         setSession(session, session.getRuntime());
     }
 
     /**
      * Attach a session to this view. The session should be opened before
      * attaching or a runtime needs to be provided for automatic opening.
+     * If this instance already has an open session, you must use
+     * {@link #releaseSession()} first, otherwise {@link IllegalStateException}
+     * will be thrown. This is to avoid potentially leaking the currently opened session.
      *
      * @param session The session to be attached.
      * @param runtime The runtime to be used for opening the session.
+     * @throws IllegalArgumentException if an existing open session is already set.
      */
+    @UiThread
     public void setSession(@NonNull final GeckoSession session,
                            @Nullable final GeckoRuntime runtime) {
         ThreadUtils.assertOnUiThread();
 
         if (mSession != null && mSession.isOpen()) {
             throw new IllegalStateException("Current session is open");
         }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -16,16 +16,18 @@ layout: geckoview
 
 - Added methods for each setting in [`GeckoSessionSettings`][66.3]
 
 [66.3]: ../GeckoSessionSettings.html
 
 - Added GeckoRuntimeSetting for enabling desktop viewport. Desktop viewport is
   no longer set by `USER_AGENT_MODE_DESKTOP` and must be set separately.
 
+- Added `@UiThread` to `GeckoSession.releaseSession` and `GeckoSession.setSession`
+
 ## v65
 - Moved [`CompositorController`][65.1], [`DynamicToolbarAnimator`][65.2],
   [`OverscrollEdgeEffect`][65.3], [`PanZoomController`][65.4] from
   `org.mozilla.gecko.gfx` to [`org.mozilla.geckoview`][65.5]
 
 [65.1]: ../CompositorController.html
 [65.2]: ../DynamicToolbarAnimator.html
 [65.3]: ../OverscrollEdgeEffect.html
@@ -104,9 +106,9 @@ layout: geckoview
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 9fb5334f4d7d0c79f963ef9c8e14795e4965e852
+[api-version]: e7a6a3ed65c75f7cb278b693adfa09cae5238ca2
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5558,18 +5558,23 @@ pref("urlclassifier.passwordAllowTable",
 pref("urlclassifier.trackingAnnotationTable", "test-track-simple,base-track-digest256,content-track-digest256");
 #else
 pref("urlclassifier.trackingAnnotationTable", "test-track-simple,base-track-digest256");
 #endif
 pref("urlclassifier.trackingAnnotationWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 pref("urlclassifier.trackingTable", "test-track-simple,base-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
+pref("urlclassifier.features.fingerprinting.blacklistTables", "");
+pref("urlclassifier.features.fingerprinting.whitelistTables", "");
+pref("urlclassifier.features.cryptomining.blacklistTables", "");
+pref("urlclassifier.features.cryptomining.whitelistTables", "");
+
 // These tables will never trigger a gethash call.
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256");
 
 // Number of random entries to send with a gethash request
 pref("urlclassifier.gethashnoise", 4);
 
 // Gethash timeout for Safe Browsing
 pref("urlclassifier.gethash.timeout_ms", 5000);
 // Update server response timeout for Safe Browsing
 pref("urlclassifier.update.response_timeout_ms", 30000);
@@ -5628,17 +5633,17 @@ pref("browser.safebrowsing.provider.goog
 pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing");
 pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
 pref("browser.safebrowsing.provider.google4.dataSharing.enabled", false);
 
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 
 // Mozilla Safe Browsing provider (for tracking protection and plugin blocking)
 pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
-pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256");
+pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 // Set to a date in the past to force immediate download in new profiles.
 pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.base", "moz-std");
 pref("browser.safebrowsing.provider.mozilla.lists.content", "moz-full");
rename from browser/app/winlauncher/NativeNt.h
rename to mozglue/misc/NativeNt.h
--- a/browser/app/winlauncher/NativeNt.h
+++ b/mozglue/misc/NativeNt.h
@@ -2,30 +2,29 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_NativeNt_h
 #define mozilla_NativeNt_h
 
-#if defined(MOZILLA_INTERNAL_API)
-#error \
-    "This header is for initial process initialization. You don't want to be including this here."
-#endif  // defined(MOZILLA_INTERNAL_API)
-
 #include <stdint.h>
 #include <windows.h>
 #include <winnt.h>
 #include <winternl.h>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/LauncherResult.h"
 
-#include "LauncherResult.h"
+// The declarations within this #if block are intended to be used for initial
+// process initialization ONLY. You probably don't want to be using these in
+// normal Gecko code!
+#if !defined(MOZILLA_INTERNAL_API)
 
 extern "C" {
 
 #if !defined(STATUS_ACCESS_DENIED)
 #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
 #endif  // !defined(STATUS_ACCESS_DENIED)
 
 #if !defined(STATUS_DLL_NOT_FOUND)
@@ -75,19 +74,23 @@ PVOID NTAPI RtlReAllocateHeap(PVOID aHea
 BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
 
 VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
 
 VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
 
 }  // extern "C"
 
+#endif  // !defined(MOZILLA_INTERNAL_API)
+
 namespace mozilla {
 namespace nt {
 
+#if !defined(MOZILLA_INTERNAL_API)
+
 struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
   MemorySectionNameBuf() {
     mSectionFileName.Length = 0;
     mSectionFileName.MaximumLength = sizeof(mBuf);
     mSectionFileName.Buffer = mBuf;
   }
 
   WCHAR mBuf[MAX_PATH];
@@ -197,16 +200,18 @@ inline void GetLeafName(PUNICODE_STRING 
 
   // At this point, either cur points to the final backslash, or it points to
   // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
   aDestString->Buffer = cur + 1;
   aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
   aDestString->MaximumLength = aDestString->Length;
 }
 
+#endif  // !defined(MOZILLA_INTERNAL_API)
+
 inline char EnsureLowerCaseASCII(char aChar) {
   if (aChar >= 'A' && aChar <= 'Z') {
     aChar -= 'A' - 'a';
   }
 
   return aChar;
 }
 
--- a/mozglue/misc/moz.build
+++ b/mozglue/misc/moz.build
@@ -33,16 +33,17 @@ OS_LIBS += CONFIG['REALTIME_LIBS']
 DEFINES['IMPL_MFBT'] = True
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS += [
         'nsWindowsDllInterceptor.h',
     ]
     EXPORTS.mozilla += [
         'DynamicallyLinkedFunctionPtr.h',
+        'NativeNt.h',
         'WindowsMapRemoteView.h',
     ]
     EXPORTS.mozilla.interceptor += [
         'interceptor/MMPolicies.h',
         'interceptor/PatcherBase.h',
         'interceptor/PatcherDetour.h',
         'interceptor/PatcherNopSpace.h',
         'interceptor/TargetFunction.h',
rename from browser/app/winlauncher/test/TestNativeNt.cpp
rename to mozglue/tests/TestNativeNt.cpp
--- a/browser/app/winlauncher/test/TestNativeNt.cpp
+++ b/mozglue/tests/TestNativeNt.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
-#include "NativeNt.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/UniquePtr.h"
 
 #include <stdio.h>
 #include <windows.h>
 
 const wchar_t kNormal[] = L"Foo.dll";
 const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll";
 const wchar_t kHex15[] = L"ABCDEF012345678.dll";
@@ -33,17 +33,17 @@ const char kFailFmt[] =
 
 #define EXPECT_FAIL(fn, varName) RUN_TEST(fn, varName, false)
 
 #define EXPECT_SUCCESS(fn, varName) RUN_TEST(fn, varName, true)
 
 using namespace mozilla;
 using namespace mozilla::nt;
 
-extern "C" int wmain(int argc, wchar_t* argv[]) {
+int main(int argc, char* argv[]) {
   UNICODE_STRING normal;
   ::RtlInitUnicodeString(&normal, kNormal);
 
   UNICODE_STRING hex12;
   ::RtlInitUnicodeString(&hex12, kHex12);
 
   UNICODE_STRING hex16;
   ::RtlInitUnicodeString(&hex16, kHex16);
--- a/mozglue/tests/moz.build
+++ b/mozglue/tests/moz.build
@@ -10,12 +10,19 @@ GeckoCppUnitTests([
     'ShowSSEConfig',
 ], linkage=None)
 
 CppUnitTests([
     'TestPrintf',
 ])
 
 if CONFIG['OS_ARCH'] == 'WINNT':
+    GeckoCppUnitTests([
+        'TestNativeNt',
+    ], linkage=None)
     TEST_DIRS += [
         'interceptor',
         'gtest',
     ]
+    OS_LIBS += [
+        'mincore',
+        'ntdll',
+    ]
--- a/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
@@ -1,71 +1,20 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from mozunit import main
-from mozpack import path as mozpath
 
 from common import BaseConfigureTest
 
 
 class TestMozConfigure(BaseConfigureTest):
-    def test_moz_configure_options(self):
-        def get_value_for(args=[], environ={}, mozconfig=''):
-            sandbox = self.get_sandbox({}, {}, args, environ, mozconfig)
-
-            # Add a fake old-configure option
-            sandbox.option_impl('--with-foo', nargs='*',
-                                help='Help missing for old configure options')
-
-            # Remove all implied options, otherwise, getting
-            # all_configure_options below triggers them, and that triggers
-            # configure parts that aren't expected to run during this test.
-            del sandbox._implied_options[:]
-            result = sandbox._value_for(sandbox['all_configure_options'])
-            shell = mozpath.abspath('/bin/sh')
-            return result.replace('CONFIG_SHELL=%s ' % shell, '')
-
-        self.assertEquals('--enable-application=browser',
-                          get_value_for(['--enable-application=browser']))
-
-        self.assertEquals('--enable-application=browser '
-                          'MOZ_VTUNE=1',
-                          get_value_for(['--enable-application=browser',
-                                         'MOZ_VTUNE=1']))
-
-        value = get_value_for(
-            environ={'MOZ_VTUNE': '1'},
-            mozconfig='ac_add_options --enable-project=js')
-
-        self.assertEquals('--enable-project=js MOZ_VTUNE=1',
-                          value)
-
-        # --disable-js-shell is the default, so it's filtered out.
-        self.assertEquals('--enable-application=browser',
-                          get_value_for(['--enable-application=browser',
-                                         '--disable-js-shell']))
-
-        # Normally, --without-foo would be filtered out because that's the
-        # default, but since it is a (fake) old-configure option, it always
-        # appears.
-        self.assertEquals('--enable-application=browser --without-foo',
-                          get_value_for(['--enable-application=browser',
-                                         '--without-foo']))
-        self.assertEquals('--enable-application=browser --with-foo',
-                          get_value_for(['--enable-application=browser',
-                                         '--with-foo']))
-
-        self.assertEquals("--enable-application=browser '--with-foo=foo bar'",
-                          get_value_for(['--enable-application=browser',
-                                         '--with-foo=foo bar']))
-
     def test_nsis_version(self):
         this = self
 
         class FakeNSIS(object):
             def __init__(self, version):
                 self.version = version
 
             def __call__(self, stdin, args):
--- a/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
@@ -7,19 +7,70 @@ from __future__ import absolute_import, 
 import os
 
 from StringIO import StringIO
 from buildconfig import topsrcdir
 from common import BaseConfigureTest
 from mozunit import MockedOpen, main
 from mozbuild.configure.options import InvalidOptionError
 from mozbuild.configure.util import Version
+from mozpack import path as mozpath
 
 
 class TestToolkitMozConfigure(BaseConfigureTest):
+    def test_moz_configure_options(self):
+        def get_value_for(args=[], environ={}, mozconfig=''):
+            sandbox = self.get_sandbox({}, {}, args, environ, mozconfig)
+
+            # Add a fake old-configure option
+            sandbox.option_impl('--with-foo', nargs='*',
+                                help='Help missing for old configure options')
+
+            # Remove all implied options, otherwise, getting
+            # all_configure_options below triggers them, and that triggers
+            # configure parts that aren't expected to run during this test.
+            del sandbox._implied_options[:]
+            result = sandbox._value_for(sandbox['all_configure_options'])
+            shell = mozpath.abspath('/bin/sh')
+            return result.replace('CONFIG_SHELL=%s ' % shell, '')
+
+        self.assertEquals('--enable-application=browser',
+                          get_value_for(['--enable-application=browser']))
+
+        self.assertEquals('--enable-application=browser '
+                          'MOZ_VTUNE=1',
+                          get_value_for(['--enable-application=browser',
+                                         'MOZ_VTUNE=1']))
+
+        value = get_value_for(
+            environ={'MOZ_VTUNE': '1'},
+            mozconfig='ac_add_options --enable-application=browser')
+
+        self.assertEquals('--enable-application=browser MOZ_VTUNE=1',
+                          value)
+
+        # --disable-js-shell is the default, so it's filtered out.
+        self.assertEquals('--enable-application=browser',
+                          get_value_for(['--enable-application=browser',
+                                         '--disable-js-shell']))
+
+        # Normally, --without-foo would be filtered out because that's the
+        # default, but since it is a (fake) old-configure option, it always
+        # appears.
+        self.assertEquals('--enable-application=browser --without-foo',
+                          get_value_for(['--enable-application=browser',
+                                         '--without-foo']))
+        self.assertEquals('--enable-application=browser --with-foo',
+                          get_value_for(['--enable-application=browser',
+                                         '--with-foo']))
+
+        self.assertEquals("--enable-application=browser '--with-foo=foo bar'",
+                          get_value_for(['--enable-application=browser',
+                                         '--with-foo=foo bar']))
+
     def test_developer_options(self, milestone='42.0a1'):
         def get_value(args=[], environ={}):
             sandbox = self.get_sandbox({}, {}, args, environ)
             return sandbox._value_for(sandbox['developer_options'])
 
         milestone_path = os.path.join(topsrcdir, 'config', 'milestone.txt')
         with MockedOpen({milestone_path: milestone}):
             # developer options are enabled by default on "nightly" milestone
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/RemoteSecuritySettings.jsm
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js", {});
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
+
+XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
+
+XPCOMUtils.defineLazyGetter(this, "baseAttachmentsURL", async () => {
+  const server = Services.prefs.getCharPref("services.settings.server");
+  const serverInfo = await (await fetch(`${server}/`)).json();
+  const {capabilities: {attachments: {base_url}}} = serverInfo;
+  return base_url;
+});
+
+const INTERMEDIATES_ENABLED_PREF          = "security.remote_settings.intermediates.enabled";
+const INTERMEDIATES_COLLECTION_PREF      = "security.remote_settings.intermediates.collection";
+const INTERMEDIATES_BUCKET_PREF          = "security.remote_settings.intermediates.bucket";
+const INTERMEDIATES_SIGNER_PREF          = "security.remote_settings.intermediates.signer";
+const INTERMEDIATES_CHECKED_SECONDS_PREF = "security.remote_settings.intermediates.checked";
+
+let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+
+function getHash(aStr) {
+  // return the two-digit hexadecimal code for a byte
+  let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+  hasher.init(Ci.nsICryptoHash.SHA256);
+  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+  stringStream.data = aStr;
+  hasher.updateFromStream(stringStream, -1);
+
+  // convert the binary hash data to a hex string.
+  return hasher.finish(true);
+}
+
+// Remove all colons from a string
+function stripColons(hexString) {
+  return hexString.replace(/:/g, "");
+}
+
+this.RemoteSecuritySettings = class RemoteSecuritySettings {
+    constructor() {
+        this.onSync = this.onSync.bind(this);
+        this.client = RemoteSettings(Services.prefs.getCharPref(INTERMEDIATES_COLLECTION_PREF), {
+          bucketNamePref: INTERMEDIATES_BUCKET_PREF,
+          lastCheckTimePref: INTERMEDIATES_CHECKED_SECONDS_PREF,
+          signerName: Services.prefs.getCharPref(INTERMEDIATES_SIGNER_PREF),
+          localFields: ["cert_import_complete"],
+        });
+        this.client.on("sync", this.onSync);
+    }
+    async onSync(event) {
+        const {
+          data: {deleted, current},
+        } = event;
+
+        if (!Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true)) {
+          return;
+        }
+
+        const col = await this.client.openCollection();
+
+        this.removeCerts(deleted);
+
+        // Bug 1429800: once the CertStateService has the correct interface, also
+        // store the whitelist status and crlite enrollment status
+
+        // Download attachments that are awaiting download, up to a max.
+        const maxDownloadsPerRun = 100;
+
+        // Bug 1519256: Move this to a separate method that's on a separate timer
+        // with a higher frequency (so we can attempt to download outstanding
+        // certs more than once daily)
+
+        let waiting = current.filter(record => !record.cert_import_complete);
+        await Promise.all(waiting.slice(0, maxDownloadsPerRun)
+          .map(record => this.maybeDownloadAttachment(record, col))
+        );
+
+        // Bug 1519273 - Log telemetry after a sync
+    }
+
+    /**
+     * Downloads the attachment data of the given record. Does not retry,
+     * leaving that to the caller.
+     * @param  {AttachmentRecord} record The data to obtain
+     * @return {Promise}          resolves to a Uint8Array on success
+     */
+    async _downloadAttachmentBytes(record) {
+      const {attachment: {location}} = record;
+      const remoteFilePath = (await baseAttachmentsURL) + location;
+      const headers = new Headers();
+      headers.set("Accept-Encoding", "gzip");
+
+      return fetch(remoteFilePath, {headers})
+      .then(resp => {
+        if (!resp.ok) {
+          Cu.reportError(`Failed to fetch ${remoteFilePath}: ${resp.status}`);
+          return Promise.reject();
+        }
+        return resp.arrayBuffer();
+      })
+      .then(buffer => new Uint8Array(buffer));
+    }
+
+    /**
+     * Attempts to download the attachment, assuming it's not been processed
+     * already. Does not retry, and always resolves (e.g., does not reject upon
+     * failure.) Errors are reported via Cu.reportError; If you need to know
+     * success/failure, check record.cert_import_complete.
+     * @param  {AttachmentRecord} record defines which data to obtain
+     * @param  {KintoCollection}  col The kinto collection to update
+     * @return {Promise}          a Promise representing the transaction
+     */
+    async maybeDownloadAttachment(record, col) {
+      const {attachment: {hash, size}} = record;
+
+      return this._downloadAttachmentBytes(record)
+      .then(function(attachmentData) {
+        if (!attachmentData || attachmentData.length == 0) {
+          // Bug 1519273 - Log telemetry for these rejections
+          return Promise.reject();
+        }
+
+        // check the length
+        if (attachmentData.length !== size) {
+          return Promise.reject();
+        }
+
+        // check the hash
+        let dataAsString = gTextDecoder.decode(attachmentData);
+        let calculatedHash = getHash(dataAsString);
+        if (calculatedHash !== hash) {
+          return Promise.reject();
+        }
+
+        // split off the header and footer, base64 decode, construct the cert
+        // from the resulting DER data.
+        let b64data = dataAsString.split("-----")[2].replace(/\s/g, "");
+        let certDer = atob(b64data);
+
+        // We can assume that roots obtained from remote-settings are part of
+        // the root program. If they aren't, they won't be used for path-
+        // building or have trust anyway, so just add it to the DB.
+        certdb.addCert(certDer, ",,");
+
+        record.cert_import_complete = true;
+        return col.update(record);
+      })
+      .catch(() => {
+        // Don't abort the outer Promise.all because of an error. Errors were
+        // sent using Cu.reportError()
+      });
+    }
+
+    async maybeSync(expectedTimestamp, options) {
+      return this.client.maybeSync(expectedTimestamp, options);
+    }
+
+    // Note that removing certificates from the DB will likely not have an
+    // effect until restart.
+    async removeCerts(records) {
+      let recordsToRemove = records;
+
+      for (let cert of certdb.getCerts().getEnumerator()) {
+        let certHash = stripColons(cert.sha256Fingerprint);
+        for (let i = 0; i < recordsToRemove.length; i++) {
+          let record = recordsToRemove[i];
+          if (record.pubKeyHash == certHash) {
+            certdb.deleteCertificate(cert);
+            recordsToRemove.splice(i, 1);
+            break;
+          }
+        }
+      }
+
+      if (recordsToRemove.length > 0) {
+        Cu.reportError(`Failed to remove ${recordsToRemove.length} intermediate certificates`);
+      }
+    }
+};
+
+const EXPORTED_SYMBOLS = ["RemoteSecuritySettings"];
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -50,16 +50,17 @@ if CONFIG['MOZ_XUL']:
     ]
 
 XPIDL_MODULE = 'pipnss'
 
 # These aren't actually used in production code yet, so we don't want to
 # ship them with the browser.
 TESTING_JS_MODULES.psm += [
     'DER.jsm',
+    'RemoteSecuritySettings.jsm',
     'X509.jsm',
 ]
 
 EXPORTS += [
     'CryptoTask.h',
     'nsClientAuthRemember.h',
     'nsNSSCallbacks.h',
     'nsNSSCertificate.h',
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -147,8 +147,15 @@ pref("security.pki.mitm_canary_issuer.en
 
 // It is set to true when a non-built-in root certificate is detected on a
 // Firefox update service's connection.
 // This value is set automatically.
 // The difference between security.pki.mitm_canary_issuer and this pref is that
 // here the root is trusted but not a built-in, whereas for
 // security.pki.mitm_canary_issuer.enabled, the root is not trusted.
 pref("security.pki.mitm_detected", false);
+
+// Intermediate CA Preloading settings
+pref("security.remote_settings.intermediates.enabled", false);
+pref("security.remote_settings.intermediates.bucket", "security-state");
+pref("security.remote_settings.intermediates.collection", "intermediates");
+pref("security.remote_settings.intermediates.checked", 0);
+pref("security.remote_settings.intermediates.signer", "onecrl.content-signature.mozilla.org");
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads.js
@@ -0,0 +1,437 @@
+
+// -*- 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/.
+
+"use strict";
+do_get_profile(); // must be called before getting nsIX509CertDB
+
+const { RemoteSecuritySettings } = ChromeUtils.import("resource://testing-common/psm/RemoteSecuritySettings.jsm", {});
+
+let remoteSecSetting = new RemoteSecuritySettings();
+let server;
+
+let intermediate1Data;
+let intermediate2Data;
+
+let currentTime = 0;
+
+function cyclingIteratorGenerator(items, count = null) {
+  return () => cyclingIterator(items, count);
+}
+
+function* cyclingIterator(items, count = null) {
+  if (count == null) {
+    count = items.length;
+  }
+  for (let i = 0; i < count; i++) {
+    yield items[i % items.length];
+  }
+}
+
+function getTime() {
+  currentTime = currentTime + 1000 * 60 * 60 * 12;
+  return currentTime;
+}
+
+function getHash(aStr) {
+  let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+  hasher.init(Ci.nsICryptoHash.SHA256);
+  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+  stringStream.data = aStr;
+  hasher.updateFromStream(stringStream, -1);
+
+  // convert the binary hash data to a hex string.
+  return hasher.finish(true);
+}
+
+function setupKintoPreloadServer(certGenerator, options = {
+  attachmentCB: null,
+  hashFunc: null,
+  lengthFunc: null,
+}) {
+  const dummyServerURL = `http://localhost:${server.identity.primaryPort}/v1`;
+  Services.prefs.setCharPref("services.settings.server", dummyServerURL);
+  Services.prefs.setBoolPref("services.settings.verify_signature", false);
+
+  const configPath = "/v1/";
+  const recordsPath = "/v1/buckets/security-state/collections/intermediates/records";
+  const attachmentsPath = "/attachments/";
+
+  if (options.hashFunc == null) {
+    options.hashFunc = getHash;
+  }
+  if (options.lengthFunc == null) {
+    options.lengthFunc = arr => arr.length;
+  }
+
+  function setHeader(response, headers) {
+    for (let headerLine of headers) {
+      let headerElements = headerLine.split(":");
+      response.setHeader(headerElements[0], headerElements[1].trimLeft());
+    }
+    response.setHeader("Date", (new Date()).toUTCString());
+  }
+
+  // Basic server information, all static
+  server.registerPathHandler(configPath, (request, response) => {
+    try {
+      const respData = getResponseData(request, server.identity.primaryPort);
+      if (!respData) {
+        do_throw(`unexpected ${request.method} request for ${request.path}?${request.queryString}`);
+        return;
+      }
+
+      response.setStatusLine(null, respData.status.status,
+                             respData.status.statusText);
+      setHeader(response, respData.responseHeaders);
+      response.write(respData.responseBody);
+    } catch (e) {
+      info(e);
+    }
+  });
+
+  // Lists of certs
+  server.registerPathHandler(recordsPath, (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    setHeader(response, [
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress",
+        "Etag: \"1000\"",
+    ]);
+
+    let output = [];
+    let count = 1;
+    let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+
+    let certIterator = certGenerator();
+    let result = certIterator.next();
+    while (!result.done) {
+      let certBytes = result.value;
+      let cert = certDB.constructX509FromBase64(pemToBase64(certBytes));
+
+      output.push({
+        "details": {
+          "who": "",
+          "why": "",
+          "name": "",
+          "created": "",
+        },
+        "subject": "",
+        "attachment": {
+          "hash": options.hashFunc(certBytes),
+          "size": options.lengthFunc(certBytes),
+          "filename": `intermediate certificate #${count}.pem`,
+          "location": `int${count}`,
+          "mimetype": "application/x-pem-file",
+        },
+        "whitelist": false,
+        "pubKeyHash": cert.sha256Fingerprint,
+        "crlite_enrolled": "true",
+        "id": `78cf8900-fdea-4ce5-f8fb-${count}`,
+        "last_modified": 1000,
+      });
+
+      count++;
+      result = certIterator.next();
+    }
+
+    response.write(JSON.stringify({ data: output }));
+  });
+
+  // Certificate data
+  server.registerPrefixHandler(attachmentsPath, (request, response) => {
+    setHeader(response, [
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
+        "Content-Type: application/x-pem-file; charset=UTF-8",
+        "Server: waitress",
+        "Etag: \"1000\"",
+    ]);
+
+    let identifier = request.path.match(/\d+$/)[0];
+    let count = 1;
+
+    let certIterator = certGenerator();
+    let result = certIterator.next();
+    while (!result.done) {
+      // Could do the modulus of the certIterator to get the right data,
+      // but that requires plumbing through knowledge of those offsets, so
+      // let's just loop. It's not that slow.
+
+      if (count == identifier) {
+        response.setStatusLine(null, 200, "OK");
+        response.write(result.value);
+        if (options.attachmentCB) {
+          options.attachmentCB(identifier, true);
+        }
+        return;
+      }
+
+      count++;
+      result = certIterator.next();
+    }
+
+    response.setStatusLine(null, 404, `Identifier ${identifier} Not Found`);
+    if (options.attachmentCB) {
+      options.attachmentCB(identifier, false);
+    }
+  });
+}
+
+add_task(async function test_preload_empty() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", true);
+
+  let countDownloadAttempts = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([]),
+    found => { countDownloadAttempts++; }
+  );
+
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+  // load the first root and end entity, ignore the initial intermediate
+  addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
+
+  let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
+  notEqual(ee_cert, null, "EE cert should have successfully loaded");
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countDownloadAttempts, 0, "There should have been no downloads");
+
+  // check that ee cert 1 is unknown
+  await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+});
+
+add_task(async function test_preload_disabled() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", false);
+
+  let countDownloadAttempts = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([intermediate1Data]),
+    {attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; }}
+  );
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countDownloadAttempts, 0, "There should have been no downloads");
+});
+
+add_task(async function test_preload_invalid_hash() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", true);
+
+  let countDownloadAttempts = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([intermediate1Data]),
+    {
+      attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; },
+      hashFunc: data => "invalidHash",
+    }
+  );
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countDownloadAttempts, 1, "There should have been one download attempt");
+
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+  // load the first root and end entity, ignore the initial intermediate
+  addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
+
+  let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
+  notEqual(ee_cert, null, "EE cert should have successfully loaded");
+
+  // We should still have a missing intermediate.
+  await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+});
+
+add_task(async function test_preload_invalid_length() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", true);
+
+  let countDownloadAttempts = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([intermediate1Data]),
+    {
+      attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; },
+      lengthFunc: data => 42,
+    }
+  );
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countDownloadAttempts, 1, "There should have been one download attempt");
+
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+  // load the first root and end entity, ignore the initial intermediate
+  addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
+
+  let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
+  notEqual(ee_cert, null, "EE cert should have successfully loaded");
+
+  // We should still have a missing intermediate.
+  await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+});
+
+add_task(async function test_preload_basic() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", true);
+
+  let countDownloadAttempts = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([intermediate1Data, intermediate2Data]),
+    {attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; }}
+  );
+
+  let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+  // load the first root and end entity, ignore the initial intermediate
+  addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
+
+  let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
+  notEqual(ee_cert, null, "EE cert should have successfully loaded");
+
+  // load the second end entity, ignore both intermediate and root
+  let ee_cert_2 = constructCertFromFile("test_intermediate_preloads/ee2.pem");
+  notEqual(ee_cert_2, null, "EE cert 2 should have successfully loaded");
+
+  // check that the missing intermediate causes an unknown issuer error, as
+  // expected, in both cases
+  await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+  await checkCertErrorGeneric(certDB, ee_cert_2, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countDownloadAttempts, 2, "There should have been 2 downloads");
+
+  // check that ee cert 1 verifies now the update has happened and there is
+  // an intermediate
+  await checkCertErrorGeneric(certDB, ee_cert, PRErrorCodeSuccess,
+                              certificateUsageSSLServer);
+
+  // check that ee cert 2 does not verify - since we don't know the issuer of
+  // this certificate
+  await checkCertErrorGeneric(certDB, ee_cert_2, SEC_ERROR_UNKNOWN_ISSUER,
+                              certificateUsageSSLServer);
+});
+
+
+add_task(async function test_preload_200() {
+  Services.prefs.setBoolPref("security.remote_settings.intermediates.enabled", true);
+
+  let countDownloadedAttachments = 0;
+  let countMissingAttachments = 0;
+  setupKintoPreloadServer(
+    cyclingIteratorGenerator([intermediate1Data, intermediate2Data], 200),
+    {
+      attachmentCB: (identifier, attachmentFound) => {
+        if (!attachmentFound) {
+          countMissingAttachments++;
+        } else {
+          countDownloadedAttachments++;
+        }
+      },
+    }
+  );
+
+  // sync to the kinto server.
+  await remoteSecSetting.maybeSync(getTime());
+
+  Assert.equal(countMissingAttachments, 0, "There should have been no missing attachments");
+  Assert.equal(countDownloadedAttachments, 100, "There should have been only 100 downloaded");
+
+  // sync to the kinto server again
+  await remoteSecSetting.maybeSync(getTime());
+
+  await Promise.resolve();
+
+  Assert.equal(countMissingAttachments, 0, "There should have been no missing attachments");
+  Assert.equal(countDownloadedAttachments, 198, "There should have been now 198 downloaded, because 2 existed in an earlier test");
+});
+
+
+function run_test() {
+  // Ensure that signature verification is disabled to prevent interference
+  // with basic certificate sync tests
+  Services.prefs.setBoolPref("services.blocklist.signing.enforced", false);
+
+  let intermediate1File = do_get_file("test_intermediate_preloads/int.pem", false);
+  intermediate1Data = readFile(intermediate1File);
+
+  let intermediate2File = do_get_file("test_intermediate_preloads/int2.pem", false);
+  intermediate2Data = readFile(intermediate2File);
+
+  // Set up an HTTP Server
+  server = new HttpServer();
+  server.start(-1);
+
+  run_next_test();
+
+  registerCleanupFunction(function() {
+    server.stop(() => { });
+  });
+}
+
+// get a response for a given request from sample data
+function getResponseData(req, port) {
+  info(`Resource requested: ${req.method}:${req.path}?${req.queryString}\n\n`);
+  const cannedResponses = {
+    "OPTIONS": {
+      "responseHeaders": [
+        "Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page",
+        "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
+        "Access-Control-Allow-Origin: *",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress",
+      ],
+      "status": {status: 200, statusText: "OK"},
+      "responseBody": "null",
+    },
+    "GET:/v1/": {
+      "responseHeaders": [
+        "Access-Control-Allow-Origin: *",
+        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
+        "Content-Type: application/json; charset=UTF-8",
+        "Server: waitress",
+      ],
+      "status": {status: 200, statusText: "OK"},
+      "responseBody": JSON.stringify({
+        "settings": {
+          "batch_max_requests": 25,
+        },
+        "url": `http://localhost:${port}/v1/`,
+        "documentation": "https://kinto.readthedocs.org/",
+        "version": "1.5.1",
+        "commit": "cbc6f58",
+        "hello": "kinto",
+        "capabilities": {
+          "attachments": {
+            "base_url": `http://localhost:${port}/attachments/`,
+          },
+        },
+      }),
+    },
+  };
+  let result = cannedResponses[`${req.method}:${req.path}?${req.queryString}`] ||
+               cannedResponses[`${req.method}:${req.path}`] ||
+               cannedResponses[req.method];
+  return result;
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+TCCAeGgAwIBAgIUN/Y56TvJcL2liqk2Feh/QfKrlLwwDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwaaW50ZXJtZWRpYXRlLXByZWxvYWRpbmctY2EwIhgPMjAx
+MDAxMDEwMDAwMDBaGA8yMDUwMDEwMTAwMDAwMFowJTEjMCEGA1UEAwwaaW50ZXJt
+ZWRpYXRlLXByZWxvYWRpbmctY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwG
+m24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJr
+bA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4
+SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3
+/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+Z
+FzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYD
+VR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBSPwr2BfSHT3saxwx6YGEautZx
+w/sdM9AJAubFLqDd3MYHtzCZcQXaeDGbAzvo8m/PKA4Yt+UYbKyDnRR8sLA4f/iu
+z1zHeenlzBWpRVHu/++ZSk/ESwn0zLprIsOcXjaYkbfrqcEGNWvLJzpT4T36Gr9t
+DvxHnpsaMsJviZS3WHzTSoioWkcRyF78bYa51ZJWYJHFKZQppqhJ+jcoJhiomRlc
+WwhI8NAU3dOOFJuEg/z+vQpcEQi0rRW9J6X/15BUZRQlF5Hs2wilGa8ViNX2+B5I
+kjbmNrdT5hcnGEfR7JpHFuihFdxQc4CFY87u1chI8yaHLhhriUP6Jq0+J5ur
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ca.pem.certspec
@@ -0,0 +1,5 @@
+issuer:intermediate-preloading-ca
+subject:intermediate-preloading-ca
+validity:20100101-20500101
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5TCCAc2gAwIBAgIUKWcVgS9ewqHTZ/zYT3TVfz5E3GEwDQYJKoZIhvcNAQEL
+BQAwLzEtMCsGA1UEAwwkaW50ZXJtZWRpYXRlLXByZWxvYWRpbmctaW50ZXJtZWRp
+YXRlMCIYDzIwMTcxMTI3MDAwMDAwWhgPMjAyMDAyMDUwMDAwMDBaMA0xCzAJBgNV
+BAMMAmVlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB
+/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRx
+CHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMC
+OosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdm
+Wqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGz
+ey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUD
+rNoYCjXtjQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B
+AQsFAAOCAQEAfeCXPOT0iBdAdMMQsRLX/T4PjORcHCJE679gaaCH408zDnR5y4N1
+hXYhIqePF64Xj/CItUBjF5H29K0lsitkwAd5R0kdKo58ieBPAuo940u/aTgSWteb
+clDWDn2b3ACL+6N1nCB4t10yCq0xiUG0KYEntea7fNVoyzJCuo2BA+cZn+zdV8e4
+/bsG2oasvp3rAo72P+8fyhBdwJnL/pltAg+SgyyEDFrWhQwWcrLaq30AP0w0ItO+
+ctgVqlXDsWyVOTtxGKX7l6aoIyDZ8ypYKT/vDBdRD98kJO8JTLCB9D/+fqNIZ+RR
+oCsZYzAGCIQJQMy8AG3nELNnugutqPAarg==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee.pem.certspec
@@ -0,0 +1,3 @@
+issuer:intermediate-preloading-intermediate
+subject:ee
+extension:extKeyUsage:serverAuth
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAc+gAwIBAgIUP/k97g9sNmKzpKUGr6rLH9tuefIwDQYJKoZIhvcNAQEL
+BQAwMDEuMCwGA1UEAwwlaW50ZXJtZWRpYXRlLXByZWxvYWRpbmctaW50ZXJtZWRp
+YXRlMjAiGA8yMDE3MTEyNzAwMDAwMFoYDzIwMjAwMjA1MDAwMDAwWjAOMQwwCgYD
+VQQDDANlZTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W
+1kH9braIBjYQPTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtq
+ZHEIeqVap0WH9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx
+0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthV
+t2Zaqn4CkC86exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo
+4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx
+1QOs2hgKNe2NAgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3
+DQEBCwUAA4IBAQCvme71ToYWZRNZWpv3ug9kcH4//B5rmQvPRqJjf+TtBsO284MN
+avtknmZBkTOUtZvRIBZxa736s7pgtrs+3cx8K6ssyLh05fWl+FCtKyesu57njxb5
+Y6sNHh8AYkoC9RYFUHiQBZqW8KJENN57aP7iXffB9oQx7BNSv82Qmtb93YwDYmOu
+9UTKiIuvWJiu7wMOGllRzQ42F2E4V9HWTSEmsdyw7RjAknMZt34DU0GeAW18K+G9
+VYpXSTPiHcVYn9inuoWO1n7h84oJwJYx23sN9q79fcDGUWbNR4QqZ90yHBxw75Ts
+OSXMtREbIRZW5NnOc2fqwPgRootdyLQhoWuu
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/ee2.pem.certspec
@@ -0,0 +1,3 @@
+issuer:intermediate-preloading-intermediate2
+subject:ee2
+extension:extKeyUsage:serverAuth
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIUTXfPu5ok43b8qQI8ttEhSBy8GA0wDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwaaW50ZXJtZWRpYXRlLXByZWxvYWRpbmctY2EwIhgPMjAx
+NzExMjcwMDAwMDBaGA8yMDIwMDIwNTAwMDAwMFowLzEtMCsGA1UEAwwkaW50ZXJt
+ZWRpYXRlLXByZWxvYWRpbmctaW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvB
+xyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmT
+qyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5
+kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYS
+wHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwk
+BCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRME
+BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAEZc5zgkFof6G
+/r2xV3++hfvwfMe2QwaDfb0fyyq2kLgDxT3c/AIWkSYw5WM3EplS7jNWeBPUEcQD
+cCuY7D34sVEFmmmY1rSd7BmVJMQ8dDkEr/v1wu+uDveRJIhDUpGRWPuml5AJUmR7
+qCZonfvtj5C5EkWX6LiqSf39RCWedShr9z8C68qdVmexD40svIet3SP0SJDrurOm
+KLJ0e7sw2jx19O6A3P7SB9cHsRUET2BRqR0BeHVqrWxHbujCPA2odKX+cA89Q1PF
+cUc5V9SWv6fHVr1Gy2hchPhBPY2Zu5gsn1AWpo+s5jMrbU4gfLiAufH4kqpeeFwc
+xG04TgZ9VA==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int.pem.certspec
@@ -0,0 +1,4 @@
+issuer:intermediate-preloading-ca
+subject:intermediate-preloading-intermediate
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBTCCAe2gAwIBAgIUIx42GCoUnPyFRd6qXriQSLixdjMwDQYJKoZIhvcNAQEL
+BQAwJjEkMCIGA1UEAwwbaW50ZXJtZWRpYXRlLXByZWxvYWRpbmctY2EyMCIYDzIw
+MTcxMTI3MDAwMDAwWhgPMjAyMDAyMDUwMDAwMDBaMDAxLjAsBgNVBAMMJWludGVy
+bWVkaWF0ZS1wcmVsb2FkaW5nLWludGVybWVkaWF0ZTIwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk
+e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg
+KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI
+YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi
+lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL
+HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGjHTAbMAwGA1Ud
+EwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBjFei0ALlP
+HDJMW6ARi4cKunZ2FML0lkG+R9b92j5pWTvDy+1UZw4sZXMAFcujzs6mMNYL4/yO
+k5weX7c6hZkVTcZsqQHgnyvEwF80OEgmFpBQiDECsULnKF+Pu5rfeQmrJLBIB1pC
+VnF5C9BhkZGHW3Ic+U/r+0k8UKE5hpZ7QtNtyLdKDWIdD90hCBonhX3Jjk9NtY+T
+1qzGKg6Ldn2osLlUpJWZB6TOui62hoJvIBD14yVDVy3mPT/L5ueZ7GqpiqRh9qyG
+U2GmvnpAGGi28dUpScykXnONlhg45YXGm1usLcVFMRcU8DcekdXKQgCITbtQUg2i
+TV3KCNEAdyn8
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/int2.pem.certspec
@@ -0,0 +1,4 @@
+issuer:intermediate-preloading-ca2
+subject:intermediate-preloading-intermediate2
+extension:basicConstraints:cA,
+extension:keyUsage:keyCertSign,cRLSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads/moz.build
@@ -0,0 +1,17 @@
+# -*- 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/.
+
+# Temporarily disabled. See bug 1256495.
+#test_certificates = (
+#    'ca.pem',
+#    'ee.pem',
+#    'ee2.pem',
+#    'int.pem',
+#    'int2.pem',
+#)
+#
+#for test_certificate in test_certificates:
+#    GeneratedTestCertificate(test_certificate)
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -17,16 +17,17 @@ support-files =
   test_cert_version/**
   test_cert_utf8/**
   test_certDB_import/**
   test_certviewer_invalid_oids/**
   test_content_signing/**
   test_ct/**
   test_ev_certs/**
   test_intermediate_basic_usage_constraints/**
+  test_intermediate_preloads/**
   test_keysize/**
   test_keysize_ev/**
   test_missing_intermediate/**
   test_name_constraints/**
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
   test_sdr_preexisting/**
@@ -94,16 +95,19 @@ run-sequentially = hardcoded ports
 [test_forget_about_site_security_headers.js]
 skip-if = toolkit == 'android'
 [test_hash_algorithms.js]
 [test_hash_algorithms_wrap.js]
 # bug 1124289 - run_test_in_child violates the sandbox on android
 skip-if = toolkit == 'android'
 [test_hmac.js]
 [test_intermediate_basic_usage_constraints.js]
+[test_intermediate_preloads.js]
+# Bug 1520297 - do something to handle tighter resource constraints on Android
+skip-if = toolkit == 'android'
 [test_imminent_distrust.js]
 run-sequentially = hardcoded ports
 [test_js_cert_override_service.js]
 run-sequentially = hardcoded ports
 [test_keysize.js]
 [test_keysize_ev.js]
 run-sequentially = hardcoded ports
 [test_local_cert.js]
--- a/taskcluster/docker/index-task/Dockerfile
+++ b/taskcluster/docker/index-task/Dockerfile
@@ -1,11 +1,11 @@
-FROM node:6-alpine
+FROM node:10-alpine
 
 ENV       NODE_ENV        production
 RUN       mkdir /app
 ADD       insert-indexes.js   /app/
 ADD       package.json        /app/
-ADD       npm-shrinkwrap.json /app/
+ADD       yarn.lock           /app/
 WORKDIR   /app
-RUN       npm install && npm cache clean
+RUN       yarn --frozen-lockfile && yarn cache clean
 
 ENTRYPOINT ["node"]
--- a/taskcluster/docker/index-task/insert-indexes.js
+++ b/taskcluster/docker/index-task/insert-indexes.js
@@ -1,21 +1,22 @@
 let taskcluster = require("taskcluster-client");
 
 // Create instance of index client
 let index = new taskcluster.Index({
   delayFactor:    750,  // Good solid delay for background process
   retries:        8,    // A few extra retries for robustness
-  baseUrl:        "taskcluster/index/v1",
+  rootUrl:        process.env.TASKCLUSTER_PROXY_URL || process.env.TASKCLUSTER_ROOT_URL,
 });
 
 // Create queue instance for fetching taskId
 let queue = new taskcluster.Queue({
-    delayFactor:    750,  // Good solid delay for background process
-    retries:        8,    // A few extra retries for robustness
+  delayFactor:    750,  // Good solid delay for background process
+  retries:        8,    // A few extra retries for robustness
+  rootUrl:        process.env.TASKCLUSTER_PROXY_URL || process.env.TASKCLUSTER_ROOT_URL,
 });
 
 // Load input
 let taskId = process.env.TARGET_TASKID;
 let rank = parseInt(process.env.INDEX_RANK, 10);
 let namespaces = process.argv.slice(2);
 
 // Validate input
deleted file mode 100644
--- a/taskcluster/docker/index-task/npm-shrinkwrap.json
+++ /dev/null
@@ -1,309 +0,0 @@
-{
-  "dependencies": {
-    "amqplib": {
-      "version": "0.5.1",
-      "from": "amqplib@>=0.5.1 <0.6.0",
-      "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz"
-    },
-    "asap": {
-      "version": "1.0.0",
-      "from": "asap@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz"
-    },
-    "async": {
-      "version": "0.9.2",
-      "from": "async@>=0.9.0 <0.10.0",
-      "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
-    },
-    "bitsyntax": {
-      "version": "0.0.4",
-      "from": "bitsyntax@>=0.0.4 <0.1.0",
-      "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz"
-    },
-    "bluebird": {
-      "version": "3.4.7",
-      "from": "bluebird@>=3.4.6 <4.0.0",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz"
-    },
-    "boom": {
-      "version": "2.10.1",
-      "from": "boom@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
-    },
-    "buffer-more-ints": {
-      "version": "0.0.2",
-      "from": "buffer-more-ints@0.0.2",
-      "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz"
-    },
-    "combined-stream": {
-      "version": "0.0.7",
-      "from": "combined-stream@>=0.0.4 <0.1.0",
-      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz"
-    },
-    "component-emitter": {
-      "version": "1.2.1",
-      "from": "component-emitter@>=1.2.0 <1.3.0",
-      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz"
-    },
-    "cookiejar": {
-      "version": "2.0.6",
-      "from": "cookiejar@2.0.6",
-      "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.6.tgz"
-    },
-    "core-util-is": {
-      "version": "1.0.2",
-      "from": "core-util-is@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
-    },
-    "cryptiles": {
-      "version": "2.0.5",
-      "from": "cryptiles@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
-    },
-    "debug": {
-      "version": "2.6.0",
-      "from": "debug@>=2.1.3 <3.0.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz"
-    },
-    "delayed-stream": {
-      "version": "0.0.5",
-      "from": "delayed-stream@0.0.5",
-      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
-    },
-    "eventsource": {
-      "version": "0.1.6",
-      "from": "eventsource@0.1.6",
-      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz",
-      "optional": true
-    },
-    "extend": {
-      "version": "3.0.0",
-      "from": "extend@3.0.0",
-      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
-    },
-    "faye-websocket": {
-      "version": "0.11.1",
-      "from": "faye-websocket@>=0.11.0 <0.12.0",
-      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
-      "optional": true
-    },
-    "form-data": {
-      "version": "0.2.0",
-      "from": "form-data@0.2.0",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz"
-    },
-    "formidable": {
-      "version": "1.0.17",
-      "from": "formidable@>=1.0.14 <1.1.0",
-      "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz"
-    },
-    "hawk": {
-      "version": "2.3.1",
-      "from": "hawk@>=2.3.1 <3.0.0",
-      "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz"
-    },
-    "hoek": {
-      "version": "2.16.3",
-      "from": "hoek@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
-    },
-    "inherits": {
-      "version": "2.0.3",
-      "from": "inherits@>=2.0.1 <2.1.0",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
-    },
-    "isarray": {
-      "version": "0.0.1",
-      "from": "isarray@0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-    },
-    "json3": {
-      "version": "3.3.2",
-      "from": "json3@>=3.3.2 <4.0.0",
-      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
-      "optional": true
-    },
-    "lodash": {
-      "version": "3.10.1",
-      "from": "lodash@>=3.6.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-    },
-    "methods": {
-      "version": "1.1.2",
-      "from": "methods@>=1.1.1 <1.2.0",
-      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
-    },
-    "mime": {
-      "version": "1.3.4",
-      "from": "mime@1.3.4",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
-    },
-    "mime-db": {
-      "version": "1.12.0",
-      "from": "mime-db@>=1.12.0 <1.13.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz"
-    },
-    "mime-types": {
-      "version": "2.0.14",
-      "from": "mime-types@>=2.0.3 <2.1.0",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz"
-    },
-    "ms": {
-      "version": "0.7.2",
-      "from": "ms@0.7.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"
-    },
-    "original": {
-      "version": "1.0.0",
-      "from": "original@>=0.0.5",
-      "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz",
-      "optional": true,
-      "dependencies": {
-        "url-parse": {
-          "version": "1.0.5",
-          "from": "url-parse@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz",
-          "optional": true
-        }
-      }
-    },
-    "promise": {
-      "version": "6.1.0",
-      "from": "promise@>=6.1.0 <7.0.0",
-      "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz"
-    },
-    "qs": {
-      "version": "2.3.3",
-      "from": "qs@2.3.3",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz"
-    },
-    "querystringify": {
-      "version": "0.0.4",
-      "from": "querystringify@>=0.0.0 <0.1.0",
-      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz"
-    },
-    "readable-stream": {
-      "version": "1.1.14",
-      "from": "readable-stream@>=1.0.0 <2.0.0 >=1.1.9",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz"
-    },
-    "reduce-component": {
-      "version": "1.0.1",
-      "from": "reduce-component@1.0.1",
-      "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz"
-    },
-    "requires-port": {
-      "version": "1.0.0",
-      "from": "requires-port@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
-    },
-    "slugid": {
-      "version": "1.1.0",
-      "from": "slugid@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/slugid/-/slugid-1.1.0.tgz"
-    },
-    "sntp": {
-      "version": "1.0.9",
-      "from": "sntp@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
-    },
-    "sockjs-client": {
-      "version": "1.1.2",
-      "from": "sockjs-client@>=1.0.3 <2.0.0",
-      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.2.tgz",
-      "optional": true
-    },
-    "string_decoder": {
-      "version": "0.10.31",
-      "from": "string_decoder@>=0.10.0 <0.11.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-    },
-    "superagent": {
-      "version": "1.7.2",
-      "from": "superagent@>=1.7.0 <1.8.0",
-      "resolved": "https://registry.npmjs.org/superagent/-/superagent-1.7.2.tgz",
-      "dependencies": {
-        "readable-stream": {
-          "version": "1.0.27-1",
-          "from": "readable-stream@1.0.27-1",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz"
-        }
-      }
-    },
-    "superagent-hawk": {
-      "version": "0.0.6",
-      "from": "superagent-hawk@>=0.0.6 <0.0.7",
-      "resolved": "https://registry.npmjs.org/superagent-hawk/-/superagent-hawk-0.0.6.tgz",
-      "dependencies": {
-        "boom": {
-          "version": "0.4.2",
-          "from": "boom@>=0.4.0 <0.5.0",
-          "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz"
-        },
-        "cryptiles": {
-          "version": "0.2.2",
-          "from": "cryptiles@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz"
-        },
-        "hawk": {
-          "version": "1.0.0",
-          "from": "hawk@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz"
-        },
-        "hoek": {
-          "version": "0.9.1",
-          "from": "hoek@>=0.9.0 <0.10.0",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz"
-        },
-        "qs": {
-          "version": "0.6.6",
-          "from": "qs@>=0.6.6 <0.7.0",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
-        },
-        "sntp": {
-          "version": "0.2.4",
-          "from": "sntp@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz"
-        }
-      }
-    },
-    "superagent-promise": {
-      "version": "0.2.0",
-      "from": "superagent-promise@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/superagent-promise/-/superagent-promise-0.2.0.tgz"
-    },
-    "taskcluster-client": {
-      "version": "1.6.3",
-      "from": "taskcluster-client@>=1.6.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/taskcluster-client/-/taskcluster-client-1.6.3.tgz"
-    },
-    "url-join": {
-      "version": "0.0.1",
-      "from": "url-join@>=0.0.1 <0.0.2",
-      "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz"
-    },
-    "url-parse": {
-      "version": "1.1.7",
-      "from": "url-parse@>=1.1.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.7.tgz",
-      "optional": true
-    },
-    "uuid": {
-      "version": "2.0.3",
-      "from": "uuid@>=2.0.1 <3.0.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz"
-    },
-    "websocket-driver": {
-      "version": "0.6.5",
-      "from": "websocket-driver@>=0.5.1",
-      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
-      "optional": true
-    },
-    "websocket-extensions": {
-      "version": "0.1.1",
-      "from": "websocket-extensions@>=0.1.1",
-      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz",
-      "optional": true
-    }
-  }
-}
--- a/taskcluster/docker/index-task/package.json
+++ b/taskcluster/docker/index-task/package.json
@@ -1,9 +1,12 @@
 {
   "private": true,
-  "scripts":{
+  "scripts": {
     "start": "node index.js"
   },
   "dependencies": {
-    "taskcluster-client": "^1.6.2"
+    "taskcluster-client": "^12.2.0"
+  },
+  "engines": {
+    "node": "10"
   }
 }
new file mode 100644
--- /dev/null
+++ b/taskcluster/docker/index-task/yarn.lock
@@ -0,0 +1,326 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+amqplib@^0.5.1:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.5.3.tgz#7ccfc85d12ee7cd3c6dc861bb07f0648ec3d7193"
+  integrity sha512-ZOdUhMxcF+u62rPI+hMtU1NBXSDFQ3eCJJrenamtdQ7YYwh7RZJHOIM1gonVbZ5PyVdYH4xqBPje9OYqk7fnqw==
+  dependencies:
+    bitsyntax "~0.1.0"
+    bluebird "^3.5.2"
+    buffer-more-ints "~1.0.0"
+    readable-stream "1.x >=1.1.9"
+    safe-buffer "~5.1.2"
+    url-parse "~1.4.3"
+
+asap@~2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+bitsyntax@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/bitsyntax/-/bitsyntax-0.1.0.tgz#b0c59acef03505de5a2ed62a2f763c56ae1d6205"
+  integrity sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==
+  dependencies:
+    buffer-more-ints "~1.0.0"
+    debug "~2.6.9"
+    safe-buffer "~5.1.2"
+
+bluebird@^3.5.2:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
+  integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+
+boom@4.x.x:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+  integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE=
+  dependencies:
+    hoek "4.x.x"
+
+boom@5.x.x:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+  integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==
+  dependencies:
+    hoek "4.x.x"
+
+buffer-more-ints@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422"
+  integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==
+
+combined-stream@^1.0.6:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
+  integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+component-emitter@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
+
+cookiejar@^2.1.0:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
+  integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
+
+core-util-is@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+cryptiles@3.x.x:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.4.tgz#769a68c95612b56faadfcebf57ac86479cbe8322"
+  integrity sha512-8I1sgZHfVwcSOY6mSGpVU3lw/GSIZvusg8dD2+OGehCJpOhQRLNcH0qb9upQnOH4XhgxxFJSg6E2kx95deb1Tw==
+  dependencies:
+    boom "5.x.x"
+
+debug@^3.1.0:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+  dependencies:
+    ms "^2.1.1"
+
+debug@~2.6.9:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+  dependencies:
+    ms "2.0.0"
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+extend@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+  integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+form-data@^2.3.1:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
+  integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
+formidable@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659"
+  integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==
+
+hawk@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+  integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==
+  dependencies:
+    boom "4.x.x"
+    cryptiles "3.x.x"
+    hoek "4.x.x"
+    sntp "2.x.x"
+
+hoek@4.x.x:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
+  integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
+
+inherits@~2.0.1, inherits@~2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+isarray@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+lodash@^4.17.4:
+  version "4.17.11"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+
+methods@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+  integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
+mime-db@~1.37.0:
+  version "1.37.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
+  integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
+
+mime-types@^2.1.12:
+  version "2.1.21"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
+  integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
+  dependencies:
+    mime-db "~1.37.0"
+
+mime@^1.4.1:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
+  integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+
+process-nextick-args@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
+  integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
+
+promise@^8.0.1:
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.2.tgz#9dcd0672192c589477d56891271bdc27547ae9f0"
+  integrity sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==
+  dependencies:
+    asap "~2.0.6"
+
+qs@^6.5.1:
+  version "6.6.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2"
+  integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==
+
+querystringify@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef"
+  integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==
+
+"readable-stream@1.x >=1.1.9":
+  version "1.1.14"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+  integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
+readable-stream@^2.3.5:
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
+  integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
+requires-port@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@~5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+slugid@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/slugid/-/slugid-1.1.0.tgz#e09f00899c09f5a7058edc36dd49f046fd50a82a"
+  integrity sha1-4J8AiZwJ9acFjtw23UnwRv1QqCo=
+  dependencies:
+    uuid "^2.0.1"
+
+sntp@2.x.x:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
+  integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==
+  dependencies:
+    hoek "4.x.x"
+
+string_decoder@~0.10.x:
+  version "0.10.31"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
+superagent@~3.8.1:
+  version "3.8.3"
+  resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
+  integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==
+  dependencies:
+    component-emitter "^1.2.0"
+    cookiejar "^2.1.0"
+    debug "^3.1.0"
+    extend "^3.0.0"
+    form-data "^2.3.1"
+    formidable "^1.2.0"
+    methods "^1.1.1"
+    mime "^1.4.1"
+    qs "^6.5.1"
+    readable-stream "^2.3.5"
+
+taskcluster-client@^12.2.0:
+  version "12.2.0"
+  resolved "https://registry.yarnpkg.com/taskcluster-client/-/taskcluster-client-12.2.0.tgz#423aee3b17566d14f8ad23e4e47532265a74fb89"
+  integrity sha512-2Fu5ICS2663kC2t8ymJYzRDnipj3DsCK//b+H/83RjJvC6cWZ0akKzq0ySvPlNA6ic2UcL4I03bJTCJYBX1dqg==
+  dependencies:
+    amqplib "^0.5.1"
+    debug "^3.1.0"
+    hawk "^6.0.2"
+    lodash "^4.17.4"
+    promise "^8.0.1"
+    slugid "^1.1.0"
+    superagent "~3.8.1"
+    taskcluster-lib-urls "^10.0.0"
+
+taskcluster-lib-urls@^10.0.0:
+  version "10.1.1"
+  resolved "https://registry.yarnpkg.com/taskcluster-lib-urls/-/taskcluster-lib-urls-10.1.1.tgz#67d5b9449b947e5234eafdd15c46267dde29bf74"
+  integrity sha512-tdrK++rCX73FMXk/cXwS6RLTjA3pX8hJlxg1ECLs3L3llCOPMNhQ4wi6lb6yMgHc/s5on/Edj6AlAH7gkxzgPg==
+
+url-parse@~1.4.3:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
+  integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==
+  dependencies:
+    querystringify "^2.0.0"
+    requires-port "^1.0.0"
+
+util-deprecate@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+  integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+uuid@^2.0.1:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
+  integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-007.html.ini
@@ -0,0 +1,2 @@
+[multicol-span-all-007.html]
+  prefs: [layout.css.column-span.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-dynamic-add-010.html.ini
@@ -0,0 +1,2 @@
+[multicol-span-all-dynamic-add-010.html]
+  prefs: [layout.css.column-span.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-span-all-007-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test Reference: Test column-span:all when the body tag is the multi-column container</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  body {
+    column-count: 1;
+    column-rule: 6px solid;
+    width: 400px;
+    outline: 1px solid black;
+  }
+  h3 {
+    /* column-count: 1 makes this behave like a real spanner. */
+    outline: 1px solid blue;
+  }
+  </style>
+
+  <body>
+    <div>block1</div>
+    <div>
+      <h3>spanner</h3>
+    </div>
+    <div>block2</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-span-all-007.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test: Test column-span:all when the body tag is the multi-column container</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-multicol-1/#column-span">
+  <link rel="match" href="multicol-span-all-007-ref.html">
+  <meta name="assert" content="This test checks a column-span:all element is working if the body tag is the multi-column container.">
+
+  <style>
+  body {
+    column-count: 3;
+    column-rule: 6px solid;
+    width: 400px;
+    outline: 1px solid black;
+  }
+  h3 {
+    column-span: all;
+    outline: 1px solid blue;
+  }
+  </style>
+
+  <body>
+    <div>block1</div>
+    <div>
+      <h3>spanner</h3> <!-- Put spanner in a subtree deliberately -->
+    </div>
+    <div>block2</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-span-all-dynamic-add-010-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test Reference: Append a block containing a spanner kid. The spanner kid should correctly span across all columns.</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  #column {
+    column-count: 3;
+    column-rule: 6px solid;
+    width: 400px;
+    outline: 1px solid black;
+  }
+  h3 {
+    column-span: all;
+    outline: 1px solid blue;
+  }
+  </style>
+
+  <body>
+    <article id="column">
+      <div>block1</div>
+      <div><h3>spanner</h3>block2</div>
+    </article>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-multicol/multicol-span-all-dynamic-add-010.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <meta charset="utf-8">
+  <title>CSS Multi-column Layout Test: Append a block containing a spanner kid. The spanner kid should correctly span across all columns.</title>
+  <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-multicol-1/#column-span">
+  <link rel="match" href="multicol-span-all-dynamic-add-010-ref.html">
+  <meta name="assert" content="This test checks appending a block containing 'column-span' element should be rendered correctly.">
+
+  <script>
+  function runTest() {
+    document.body.offsetHeight;
+
+    // Create a subtree like the following, and append it to columns.
+    // <div>
+    //   <h3>spanner</h3>
+    //   block2
+    // </div>
+    var spanner = document.createElement("h3");
+    var spannerText = document.createTextNode("spanner");
+    spanner.appendChild(spannerText);
+
+    var block2 = document.createElement("div");
+    var block2Text = document.createTextNode("block2");
+    block2.appendChild(spanner);
+    block2.appendChild(block2Text)
+
+    var column = document.getElementById("column");
+    column.appendChild(block2);
+
+    document.documentElement.removeAttribute("class");
+  }
+  </script>
+
+  <style>
+  #column {
+    column-count: 3;
+    column-rule: 6px solid;
+    width: 400px;
+    outline: 1px solid black;
+  }
+  h3 {
+    column-span: all;
+    outline: 1px solid blue;
+  }
+  </style>
+
+  <body onload="runTest();">
+    <article id="column">
+      <div>block1</div>
+    </article>
+  </body>
+</html>
--- a/toolkit/components/browser/nsIWebBrowserChrome.idl
+++ b/toolkit/components/browser/nsIWebBrowserChrome.idl
@@ -60,16 +60,17 @@ interface nsIWebBrowserChrome : nsISuppo
     // window will be non-private.
     //
     // CHROME_PRIVATE_LIFETIME causes the docshell to affect private-browsing
     // session lifetime.  This flag is currently respected only for remote
     // docshells.
     const unsigned long CHROME_PRIVATE_WINDOW         = 0x00010000;
     const unsigned long CHROME_NON_PRIVATE_WINDOW     = 0x00020000;
     const unsigned long CHROME_PRIVATE_LIFETIME       = 0x00040000;
+    const unsigned long CHROME_ALWAYS_ON_TOP          = 0x00080000;
 
     // Whether this window should use remote (out-of-process) tabs.
     const unsigned long CHROME_REMOTE_WINDOW          = 0x00100000;
 
     // Prevents new window animations on MacOS and Windows. Currently
     // ignored for Linux.
     const unsigned long CHROME_SUPPRESS_ANIMATION     = 0x01000000;
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13247,17 +13247,17 @@
   },
   "CONTENT_FRAME_TIME_REASON": {
     "record_in_processes": ["main", "gpu"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "mwoodrow@mozilla.com"],
     "bug_numbers": [1510853],
     "expires_in_version": "73",
     "kind": "categorical",
     "description": "The reason that CONTENT_FRAME_TIME recorded a slow (>200) result, if any.",
-    "labels": ["OnTime", "NoVsync", "MissedComposite", "SlowComposite", "MissedCompositeMid", "MissedCompositeLong", "MissedCompositeLow"]
+    "labels": ["OnTime", "NoVsync", "MissedComposite", "SlowComposite", "MissedCompositeMid", "MissedCompositeLong", "MissedCompositeLow", "NoVsyncNoId"]
   },
   "CONTENT_LARGE_PAINT_PHASE_WEIGHT": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "mwoodrow@mozilla.com"],
     "bug_numbers": [1309442, 1518134],
     "expires_in_version": "72",
     "keyed": true,
     "keys": ["dl", "flb", "fr", "r"],
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -27,37 +27,95 @@ function getLists(prefName) {
   // Splitting an empty string returns [''], we really want an empty array.
   if (!pref) {
     return [];
   }
 
   return pref.split(",").map(value => value.trim());
 }
 
-const tablePreferences = [
-  "urlclassifier.phishTable",
-  "urlclassifier.malwareTable",
-  "urlclassifier.downloadBlockTable",
-  "urlclassifier.downloadAllowTable",
-  "urlclassifier.passwordAllowTable",
-  "urlclassifier.trackingAnnotationTable",
-  "urlclassifier.trackingAnnotationWhitelistTable",
-  "urlclassifier.trackingTable",
-  "urlclassifier.trackingWhitelistTable",
-  "urlclassifier.blockedTable",
-  "urlclassifier.flashAllowTable",
-  "urlclassifier.flashAllowExceptTable",
-  "urlclassifier.flashTable",
-  "urlclassifier.flashExceptTable",
-  "urlclassifier.flashSubDocTable",
-  "urlclassifier.flashSubDocExceptTable",
+const FEATURES = [
+  { name: "phishing",
+    list: ["urlclassifier.phishTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
+    },
+  },
+  { name: "malware",
+    list: ["urlclassifier.malwareTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
+    },
+  },
+  { name: "blockedURIs",
+    list: ["urlclassifier.blockedTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
+    },
+  },
+  { name: "passwords",
+    list: ["urlclassifier.passwordAllowTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled");
+    },
+  },
+  { name: "downloads",
+    list: ["urlclassifier.downloadBlockTable",
+           "urlclassifier.downloadAllowTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled") &&
+             Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
+    },
+  },
+  { name: "trackingAnnotation",
+    list: ["urlclassifier.trackingAnnotationTable",
+           "urlclassifier.trackingAnnotationWhitelistTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("privacy.trackingprotection.annotate_channels");
+    },
+  },
+  { name: "tracking",
+    list: ["urlclassifier.trackingTable",
+           "urlclassifier.trackingWhitelistTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("toolkit.telemetry.isGeckoViewMode", false) ||
+             Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
+             Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
+    },
+  },
+  { name: "flashBlock",
+    list: ["urlclassifier.flashAllowTable",
+           "urlclassifier.flashAllowExceptTable",
+           "urlclassifier.flashTable",
+           "urlclassifier.flashExceptTable",
+           "urlclassifier.flashSubDocTable",
+           "urlclassifier.flashSubDocExceptTable"],
+    enabled() {
+      return Services.prefs.getBoolPref("plugins.flashBlock.enabled");
+    },
+  },
+  { name: "fingerprinting",
+    list: ["urlclassifier.features.fingerprinting.blacklistTables",
+           "urlclassifier.features.fingerprinting.whitelistTables"],
+    enabled() {
+      return Services.prefs.getBoolPref("privacy.trackingprotection.fingerprinting.enabled", false);
+    },
+  },
+  { name: "cryptomining",
+    list: ["urlclassifier.features.cryptomining.blacklistTables",
+           "urlclassifier.features.cryptomining.whitelistTables"],
+    enabled() {
+      return Services.prefs.getBoolPref("privacy.trackingprotection.cryptomining.enabled", false);
+    },
+  },
 ];
 
 var SafeBrowsing = {
 
+
   init() {
     if (this.initialized) {
       log("Already initialized");
       return;
     }
 
     Services.prefs.addObserver("browser.safebrowsing", this);
     Services.prefs.addObserver("privacy.trackingprotection", this);
@@ -90,88 +148,41 @@ var SafeBrowsing = {
       log("Invalid update url " + listname);
       return;
     }
 
     listManager.registerTable(listname, providerName, provider.updateURL, provider.gethashURL);
   },
 
   registerTables() {
-    for (let i = 0; i < this.phishingLists.length; ++i) {
-      this.registerTableWithURLs(this.phishingLists[i]);
-    }
-    for (let i = 0; i < this.malwareLists.length; ++i) {
-      this.registerTableWithURLs(this.malwareLists[i]);
-    }
-    for (let i = 0; i < this.downloadBlockLists.length; ++i) {
-      this.registerTableWithURLs(this.downloadBlockLists[i]);
-    }
-    for (let i = 0; i < this.downloadAllowLists.length; ++i) {
-      this.registerTableWithURLs(this.downloadAllowLists[i]);
-    }
-    for (let i = 0; i < this.passwordAllowLists.length; ++i) {
-      this.registerTableWithURLs(this.passwordAllowLists[i]);
-    }
-    for (let i = 0; i < this.trackingAnnotationLists.length; ++i) {
-      this.registerTableWithURLs(this.trackingAnnotationLists[i]);
-    }
-    for (let i = 0; i < this.trackingAnnotationWhitelists.length; ++i) {
-      this.registerTableWithURLs(this.trackingAnnotationWhitelists[i]);
-    }
-    for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
-      this.registerTableWithURLs(this.trackingProtectionLists[i]);
-    }
-    for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
-      this.registerTableWithURLs(this.trackingProtectionWhitelists[i]);
-    }
-    for (let i = 0; i < this.blockedLists.length; ++i) {
-      this.registerTableWithURLs(this.blockedLists[i]);
-    }
-    for (let i = 0; i < this.flashLists.length; ++i) {
-      this.registerTableWithURLs(this.flashLists[i]);
-    }
+    this.features.forEach(feature => {
+      feature.list.forEach(table => {
+        this.registerTableWithURLs(table);
+      });
+    });
   },
 
   unregisterTables(obsoleteLists) {
     let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
       getService(Ci.nsIUrlListManager);
 
-    for (let i = 0; i < obsoleteLists.length; ++i) {
-      for (let j = 0; j < obsoleteLists[i].length; ++j) {
-        listManager.unregisterTable(obsoleteLists[i][j]);
-      }
-    }
+    obsoleteLists.forEach(list => {
+      list.forEach(table => {
+        listManager.unregisterTable(table);
+      });
+    });
   },
 
 
   initialized:          false,
-  phishingEnabled:      false,
-  malwareEnabled:       false,
-  downloadsEnabled:     false,
-  passwordsEnabled:     false,
-  trackingEnabled:      false,
-  blockedEnabled:       false,
-  trackingAnnotations:  false,
-  flashBlockEnabled:    false,
 
-  phishingLists:                [],
-  malwareLists:                 [],
-  downloadBlockLists:           [],
-  downloadAllowLists:           [],
-  passwordAllowLists:           [],
-  trackingAnnotationLists:      [],
-  trackingAnnotationWhiteLists: [],
-  trackingProtectionLists:      [],
-  trackingProtectionWhitelists: [],
-  blockedLists:                 [],
-  flashLists:                   [],
+  features:                [],
 
   updateURL:             null,
   gethashURL:            null,
-
   reportURL:             null,
 
   getReportURL(kind, info) {
     let pref;
     switch (kind) {
       case "Phish":
         pref = "browser.safebrowsing.reportPhishURL";
         break;
@@ -218,87 +229,38 @@ var SafeBrowsing = {
 
     this.readPrefs();
   },
 
   readPrefs() {
     loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
     log("reading prefs");
 
-    this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
-    this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
-    this.downloadsEnabled = Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled");
-    this.passwordsEnabled = Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled");
-    this.trackingEnabled = Services.prefs.getBoolPref("toolkit.telemetry.isGeckoViewMode", false) ||
-                           Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
-                           Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
-    this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
-    this.trackingAnnotations = Services.prefs.getBoolPref("privacy.trackingprotection.annotate_channels");
-    this.flashBlockEnabled = Services.prefs.getBoolPref("plugins.flashBlock.enabled");
 
-    let flashAllowTable, flashAllowExceptTable, flashTable,
-        flashExceptTable, flashSubDocTable,
-        flashSubDocExceptTable;
-
-    let obsoleteLists;
+    let obsoleteLists = [];
     // Make a copy of the original lists before we re-read the prefs.
     if (this.initialized) {
-      obsoleteLists = [this.phishingLists,
-                       this.malwareLists,
-                       this.downloadBlockLists,
-                       this.downloadAllowLists,
-                       this.passwordAllowLists,
-                       this.trackingAnnotationLists,
-                       this.trackingAnnotationWhitelists,
-                       this.trackingProtectionLists,
-                       this.trackingProtectionWhitelists,
-                       this.blockedLists,
-                       this.flashLists];
+      obsoleteLists = this.features.map(feature => {
+        return feature.list;
+      });
     }
 
-    [this.phishingLists,
-     this.malwareLists,
-     this.downloadBlockLists,
-     this.downloadAllowLists,
-     this.passwordAllowLists,
-     this.trackingAnnotationLists,
-     this.trackingAnnotationWhitelists,
-     this.trackingProtectionLists,
-     this.trackingProtectionWhitelists,
-     this.blockedLists,
-     flashAllowTable,
-     flashAllowExceptTable,
-     flashTable,
-     flashExceptTable,
-     flashSubDocTable,
-     flashSubDocExceptTable] = tablePreferences.map(getLists);
+    this.features = [];
+    for (let i = 0; i < FEATURES.length; ++i) {
+      this.features[i] = { name: FEATURES[i].name,
+                           list: [],
+                           enabled: FEATURES[i].enabled() };
+      FEATURES[i].list.forEach(pref => {
+        this.features[i].list.push(...getLists(pref));
+      });
+    }
 
-    this.flashLists = flashAllowTable.concat(flashAllowExceptTable,
-                                             flashTable,
-                                             flashExceptTable,
-                                             flashSubDocTable,
-                                             flashSubDocExceptTable);
-
-    if (obsoleteLists) {
-      let newLists = [this.phishingLists,
-                      this.malwareLists,
-                      this.downloadBlockLists,
-                      this.downloadAllowLists,
-                      this.passwordAllowLists,
-                      this.trackingAnnotationLists,
-                      this.trackingAnnotationWhitelists,
-                      this.trackingProtectionLists,
-                      this.trackingProtectionWhitelists,
-                      this.blockedLists,
-                      this.flashLists];
-
-      for (let i = 0; i < obsoleteLists.length; ++i) {
-        obsoleteLists[i] = obsoleteLists[i]
-          .filter(list => !newLists[i].includes(list));
-      }
+    for (let i = 0; i < obsoleteLists.length; ++i) {
+      obsoleteLists[i] = obsoleteLists[i]
+        .filter(list => !this.features[i].list.includes(list));
     }
 
     this.updateProviderURLs();
     this.registerTables();
     if (obsoleteLists) {
       this.unregisterTables(obsoleteLists);
     }
 
@@ -380,85 +342,37 @@ var SafeBrowsing = {
         }, this);
       } else {
         log("Update URL given but no lists managed for provider: " + provider);
       }
     }, this);
   },
 
   controlUpdateChecking() {
-    log("phishingEnabled:", this.phishingEnabled,
-        "malwareEnabled:", this.malwareEnabled,
-        "downloadsEnabled:", this.downloadsEnabled,
-        "passwordsEnabled:", this.passwordsEnabled,
-        "trackingEnabled:", this.trackingEnabled,
-        "blockedEnabled:", this.blockedEnabled,
-        "trackingAnnotations", this.trackingAnnotations,
-        "flashBlockEnabled", this.flashBlockEnabled);
+    if (loggingEnabled) {
+      this.features.forEach(feature => {
+        log("feature " + feature.name + ":");
+        log("  enabled:" + feature.enabled);
+        log("  tables:" + feature.list);
+      });
+    }
 
     let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                       getService(Ci.nsIUrlListManager);
 
     listManager.disableAllUpdates();
 
-    for (let i = 0; i < this.phishingLists.length; ++i) {
-      if (this.phishingEnabled) {
-        listManager.enableUpdate(this.phishingLists[i]);
-      }
-    }
-    for (let i = 0; i < this.malwareLists.length; ++i) {
-      if (this.malwareEnabled) {
-        listManager.enableUpdate(this.malwareLists[i]);
-      }
-    }
-    for (let i = 0; i < this.downloadBlockLists.length; ++i) {
-      if (this.malwareEnabled && this.downloadsEnabled) {
-        listManager.enableUpdate(this.downloadBlockLists[i]);
-      }
-    }
-    for (let i = 0; i < this.downloadAllowLists.length; ++i) {
-      if (this.malwareEnabled && this.downloadsEnabled) {
-        listManager.enableUpdate(this.downloadAllowLists[i]);
-      }
-    }
-    for (let i = 0; i < this.passwordAllowLists.length; ++i) {
-      if (this.passwordsEnabled) {
-        listManager.enableUpdate(this.passwordAllowLists[i]);
+    this.features.forEach(feature => {
+      if (feature.enabled) {
+        feature.list.forEach(table => {
+          listManager.enableUpdate(table);
+        });
       }
-    }
-    for (let i = 0; i < this.trackingAnnotationLists.length; ++i) {
-      if (this.trackingAnnotations) {
-        listManager.enableUpdate(this.trackingAnnotationLists[i]);
-      }
-    }
-    for (let i = 0; i < this.trackingAnnotationWhitelists.length; ++i) {
-      if (this.trackingAnnotations) {
-        listManager.enableUpdate(this.trackingAnnotationWhitelists[i]);
-      }
-    }
-    for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
-      if (this.trackingEnabled) {
-        listManager.enableUpdate(this.trackingProtectionLists[i]);
-      }
-    }
-    for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
-      if (this.trackingEnabled) {
-        listManager.enableUpdate(this.trackingProtectionWhitelists[i]);
-      }
-    }
-    for (let i = 0; i < this.blockedLists.length; ++i) {
-      if (this.blockedEnabled) {
-        listManager.enableUpdate(this.blockedLists[i]);
-      }
-    }
-    for (let i = 0; i < this.flashLists.length; ++i) {
-      if (this.flashBlockEnabled) {
-        listManager.enableUpdate(this.flashLists[i]);
-      }
-    }
+    });
+
     listManager.maybeToggleUpdateChecking();
   },
 
 
   addMozEntries() {
     // Add test entries to the DB.
     // XXX bug 779008 - this could be done by DB itself?
     const phishURL    = "itisatrap.org/firefox/its-a-trap.html";
@@ -534,9 +448,10 @@ var SafeBrowsing = {
       Services.obs.removeObserver(finished, "mozentries-update-finished");
       if (data == "error") {
         Cu.reportError("addMozEntries failed to update the db!");
       }
       resolve();
     };
     Services.obs.addObserver(finished, "mozentries-update-finished");
   }),
+
 };
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -1769,17 +1769,19 @@ uint32_t nsWindowWatcher::CalculateChrom
     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED;
   } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) {
     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED;
   }
 
   chromeFlags |= WinHasOption(aFeatures, "suppressanimation", 0, nullptr)
                      ? nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION
                      : 0;
-
+  chromeFlags |= WinHasOption(aFeatures, "alwaysontop", 0, nullptr)
+                     ? nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP
+                     : 0;
   chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr)
                      ? nsIWebBrowserChrome::CHROME_OPENAS_CHROME
                      : 0;
   chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr)
                      ? nsIWebBrowserChrome::CHROME_EXTRA
                      : 0;
   chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr)
                      ? nsIWebBrowserChrome::CHROME_CENTER_SCREEN
--- a/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
+++ b/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
@@ -83,16 +83,20 @@ const DISALLOWED = {
   "z-lock": {
     flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered
     defaults_to: false,
   },
   "alwaysRaised": {
     flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED,
     defaults_to: false,
   },
+  "alwaysOnTop": {
+    flag: Ci.nsIWebBrowserChrome.CHROME_ALWAYS_ON_TOP,
+    defaults_to: false,
+  },
   "suppressanimation": {
     flag: Ci.nsIWebBrowserChrome.CHROME_SUPPRESS_ANIMATION,
     defaults_to: false,
   },
   "extrachrome": {
     flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA,
     defaults_to: false,
   },
--- a/toolkit/components/windowwatcher/test/chrome.ini
+++ b/toolkit/components/windowwatcher/test/chrome.ini
@@ -1,7 +1,9 @@
 [DEFAULT]
 tags = openwindow
 
+[test_alwaysOnTop_windows.html]
+skip-if = os != "win"
 [test_dialog_arguments.html]
 support-files =
   file_test_dialog.html
 [test_modal_windows.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_alwaysOnTop_windows.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests the alwaysOnTop window feature for the Windows OS.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test an alwaysOnTop window on Windows</title>
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+  <script type="application/javascript">
+
+  ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+  ChromeUtils.import("resource://gre/modules/ctypes.jsm");
+
+  function assertAlwaysOnTop(win, expected) {
+    let docShell = win.docShell;
+    let chromeFlags = docShell.treeOwner
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIXULWindow)
+                              .chromeFlags;
+    let hasFlag = !!(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALWAYS_ON_TOP);
+    is(hasFlag, expected, "Window should have CHROME_ALWAYS_ON_TOP flag.");
+
+    const hWND = Number(win.docShell.treeOwner.nsIBaseWindow.nativeHandle);
+    const WS_EX_TOPMOST = 0x00000008;
+    const GWL_EXSTYLE = -20;
+
+    let lib = ctypes.open("user32.dll");
+    // On 32-bit systems, the function we need to call is GetWindowLongW. On
+    // 64-bit systems, the function is GetWindowLongPtrW. Interestingly,
+    // the MSDN page[1] for GetWindowLongPtrW claims that calling it should work
+    // on both 32-bit and 64-bit, but that didn't appear to be the case here
+    // with local testing, hence the conditional.
+    //
+    // [1]: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowlongptrw
+    let GetWindowFuncSymbol = Services.appinfo.is64Bit ? "GetWindowLongPtrW"
+                                                       : "GetWindowLongW";
+    let GetWindowFunc = lib.declare(GetWindowFuncSymbol, ctypes.winapi_abi,
+                                    ctypes.intptr_t, ctypes.uintptr_t,
+                                    ctypes.int);
+
+    let styles = GetWindowFunc(hWND, GWL_EXSTYLE);
+    let isAlwaysOnTop = !!(styles & WS_EX_TOPMOST);
+    is(isAlwaysOnTop, expected, "Window should be always on top.");
+    lib.close();
+  }
+
+  if (Services.appinfo.OS != "WINNT") {
+    ok(false, "This test is only designed to run on Windows.");
+  } else {
+    add_task(async function() {
+      let normalWinOpened = BrowserTestUtils.domWindowOpened();
+      window.openDialog("data:text/html,<p>This is a normal window. </p>",
+                        "_blank", "chrome,width=100,height=100", null);
+      let normalWin = await normalWinOpened;
+      ok(normalWin, "Normal window opened");
+      assertAlwaysOnTop(normalWin, false);
+      await BrowserTestUtils.closeWindow(normalWin);
+
+      let alwaysOnTopWinOpened = BrowserTestUtils.domWindowOpened();
+      window.openDialog("data:text/html,<p>This is an alwaysOnTop window. </p>",
+                        "_blank", "chrome,width=100,height=100,alwaysOnTop", null);
+      let alwaysOnTopWin = await alwaysOnTopWinOpened;
+      ok(alwaysOnTopWin, "AlwaysOnTop window opened");
+      assertAlwaysOnTop(alwaysOnTopWin, true);
+      await BrowserTestUtils.closeWindow(alwaysOnTopWin);
+    });
+  }
+
+  </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/toolkit/modules/ActorManagerChild.jsm
+++ b/toolkit/modules/ActorManagerChild.jsm
@@ -239,17 +239,21 @@ class SingletonDispatcher extends Dispat
     } else if (event.type == "pagehide") {
       this.hidden = true;
       this.cleanup();
     }
   }
 
   handleActorEvent(actor, event) {
     if (event.target.ownerGlobal == this.window) {
-      this.getActor(actor).handleEvent(event);
+      const inst = this.getActor(actor);
+      if (typeof inst.handleEvent != "function") {
+        throw new Error(`Unhandled event for ${actor}: ${event.type}: missing handleEvent`);
+      }
+      inst.handleEvent(event);
     }
   }
 
   addEventListener(event, actor, options) {
     let listener = this.handleActorEvent.bind(this, actor);
     this.listeners.push([event, listener, options]);
     this.mm.addEventListener(event, listener, options);
   }
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -1,14 +1,49 @@
 # -*- 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/.
 
+# Set the MOZ_CONFIGURE_OPTIONS variable with all the options that
+# were passed somehow (environment, command line, mozconfig)
+@dependable
+@imports(_from='mozbuild.shellutil', _import='quote')
+@imports('__sandbox__')
+def all_configure_options():
+    result = []
+    previous = None
+    for option in __sandbox__._options.itervalues():
+        # __sandbox__._options contains items for both option.name and
+        # option.env. But it's also an OrderedDict, meaning both are
+        # consecutive.
+        # Also ignore OLD_CONFIGURE and MOZCONFIG because they're not
+        # interesting.
+        if option == previous or option.env in ('OLD_CONFIGURE', 'MOZCONFIG'):
+            continue
+        previous = option
+        value = __sandbox__._value_for(option)
+        # We only want options that were explicitly given on the command
+        # line, the environment, or mozconfig, and that differ from the
+        # defaults.
+        if (value is not None and value.origin not in ('default', 'implied') and
+                value != option.default):
+            result.append(__sandbox__._raw_options[option])
+        # We however always include options that are sent to old configure
+        # because we don't know their actual defaults. (Keep the conditions
+        # separate for ease of understanding and ease of removal)
+        elif (option.help == 'Help missing for old configure options' and
+                option in __sandbox__._raw_options):
+            result.append(__sandbox__._raw_options[option])
+
+    return quote(*result)
+
+
+set_config('MOZ_CONFIGURE_OPTIONS', all_configure_options)
 
 # Profiling
 # ==============================================================
 # Some of the options here imply an option from js/moz.configure,
 # so, need to be declared before the include.
 
 option('--enable-jprof', env='MOZ_JPROF',
        help='Enable jprof profiling tool (needs mozilla/tools/jprof)')
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -2608,16 +2608,17 @@ var gListView = {
 var gDetailView = {
   node: null,
   _addon: null,
   _loadingTimer: null,
   _autoUpdate: null,
 
   initialize() {
     this.node = document.getElementById("detail-view");
+    this.headingImage = this.node.querySelector(".card-heading-image");
 
     this._autoUpdate = document.getElementById("detail-autoUpdate");
 
     this._autoUpdate.addEventListener("command", () => {
       this._addon.applyBackgroundUpdates = this._autoUpdate.value;
     }, true);
   },
 
@@ -2628,20 +2629,21 @@ var gDetailView = {
   onUpdateModeChanged() {
     this.onPropertyChanged(["applyBackgroundUpdates"]);
   },
 
   _updateView(aAddon, aIsRemote, aScrollToPreferences) {
     setSearchLabel(aAddon.type);
 
     // Set the preview image for themes, if available.
+    this.headingImage.src = "";
     if (aAddon.type == "theme") {
       let previewURL = aAddon.screenshots && aAddon.screenshots[0] && aAddon.screenshots[0].url;
       if (previewURL) {
-        this.node.querySelector(".card-heading-image").src = previewURL;
+        this.headingImage.src = previewURL;
       }
     }
 
     AddonManager.addManagerListener(this);
     this.clearLoading();
 
     this._addon = aAddon;
     gEventManager.registerAddonListener(this, aAddon.id);
--- a/toolkit/mozapps/extensions/content/shortcuts.js
+++ b/toolkit/mozapps/extensions/content/shortcuts.js
@@ -8,16 +8,18 @@
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
 });
 
+const FALLBACK_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+
 let templatesLoaded = false;
 const templates = {};
 
 function loadTemplates() {
   if (templatesLoaded) return;
   templatesLoaded = true;
 
   templates.card = document.getElementById("card-template");
@@ -261,17 +263,17 @@ async function renderAddons(addons) {
 
     // Skip this extension if it isn't a webextension.
     if (!extension) continue;
 
     let card = document.importNode(
       templates.card.content, true).firstElementChild;
     let icon = AddonManager.getPreferredIconURL(addon, 24, window);
     card.setAttribute("addon-id", addon.id);
-    card.querySelector(".addon-icon").src = icon;
+    card.querySelector(".addon-icon").src = icon || FALLBACK_ICON;
     card.querySelector(".addon-name").textContent = addon.name;
 
     if (extension.shortcuts) {
       let commands = await extension.shortcuts.allCommands();
 
       for (let command of commands) {
         let row = document.importNode(templates.row.content, true);
         let label = row.querySelector(".shortcut-label");
--- a/toolkit/mozapps/extensions/test/browser/browser_theme_previews.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_theme_previews.js
@@ -43,40 +43,69 @@ async function init(startPage) {
   gCategoryUtilities = new CategoryUtilities(gManagerWindow);
   return gCategoryUtilities.openType(startPage);
 }
 
 add_task(async function testThemePreviewShown() {
   await init("theme");
 
   await AddonTestUtils.promiseInstallXPI(getThemeData());
-  let addon = await AddonManager.getAddonByID(id);
+  let theme = await AddonManager.getAddonByID(id);
 
-  ok(addon.screenshots[0].url, "The add-on has a preview URL");
-  let previewURL = addon.screenshots[0].url;
+  ok(theme.screenshots[0].url, "The add-on has a preview URL");
+  let previewURL = theme.screenshots[0].url;
 
   let doc = gManagerWindow.document;
   let item = doc.querySelector(`richlistitem[value="${id}"]`);
 
   await BrowserTestUtils.waitForCondition(
-    () => item.getAttribute("status") == "installed",
+    () => item.getAttribute("status") == "installed" && item.getAttribute("previewURL"),
     "Wait for the item to update to installed");
 
   is(item.getAttribute("previewURL"), previewURL, "The previewURL is set on the item");
   let image = doc.getAnonymousElementByAttribute(item, "anonid", "theme-screenshot");
   is(image.src, previewURL, "The previewURL is set on the image src");
 
   item.click();
   await wait_for_view_load(gManagerWindow);
 
   image = doc.querySelector(".theme-screenshot");
   is(image.src, previewURL, "The previewURL is set on the detail image src");
 
+  // Now check that an add-on doesn't have a preview (bug 1519616).
+  let extensionId = "extension@mochi.test";
+  await AddonTestUtils.promiseInstallXPI({
+    "manifest.json": {
+      applications: {
+        gecko: {id: extensionId},
+      },
+      manifest_version: 2,
+      name: "anextension",
+      description: "wow. such extension.",
+      author: "Woof",
+      version: "1",
+    },
+  });
+
+  await gCategoryUtilities.openType("extension");
+
+  // Go to the detail page.
+  item = doc.querySelector(`richlistitem[value="${extensionId}"]`);
+  item.click();
+  await wait_for_view_load(gManagerWindow);
+
+  // Check that the image has no src attribute.
+  image = doc.querySelector(".theme-screenshot");
+  ok(!image.src, "There is no preview for extensions");
+
   await close_manager(gManagerWindow);
-  await addon.uninstall();
+  await theme.uninstall();
+
+  let extension = await AddonManager.getAddonByID(extensionId);
+  await extension.uninstall();
 });
 
 add_task(async function testThemeOrdering() {
   // Install themes before loading the manager, if it's open they'll sort by install date.
   let themeId = id => id + "@mochi.test";
   let themeIds = [themeId(5), themeId(6), themeId(7), themeId(8)];
   await AddonTestUtils.promiseInstallXPI(getThemeData(themeId(6), {name: "BBB"}));
   await AddonTestUtils.promiseInstallXPI(getThemeData(themeId(7), {name: "CCC"}));
--- a/toolkit/xre/CmdLineAndEnvUtils.h
+++ b/toolkit/xre/CmdLineAndEnvUtils.h
@@ -354,47 +354,56 @@ inline UniquePtr<wchar_t[]> MakeCommandL
     }
   }
 
   *c = '\0';
 
   return s;
 }
 
-inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
-  if (!aArgv) {
-    return false;
-  }
-
+inline UniquePtr<wchar_t[]> GetFullBinaryPath() {
   DWORD bufLen = MAX_PATH;
   mozilla::UniquePtr<wchar_t[]> buf;
   DWORD retLen;
 
   while (true) {
     buf = mozilla::MakeUnique<wchar_t[]>(bufLen);
     retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
     if (!retLen) {
-      return false;
+      return nullptr;
     }
 
     if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
       bufLen *= 2;
       continue;
     }
 
     break;
   }
 
   // Upon success, retLen *excludes* the null character
   ++retLen;
 
   // Since we're likely to have a bunch of unused space in buf, let's reallocate
   // a string to the actual size of the file name.
-  auto newArgv_0 = mozilla::MakeUnique<wchar_t[]>(retLen);
-  if (wcscpy_s(newArgv_0.get(), retLen, buf.get())) {
+  auto result = mozilla::MakeUnique<wchar_t[]>(retLen);
+  if (wcscpy_s(result.get(), retLen, buf.get())) {
+    return nullptr;
+  }
+
+  return result;
+}
+
+inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
+  if (!aArgv) {
+    return false;
+  }
+
+  UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath());
+  if (!newArgv_0) {
     return false;
   }
 
   // We intentionally leak newArgv_0 into argv[0]
   aArgv[0] = newArgv_0.release();
   MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]);
   return true;
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "LauncherRegistryInfo.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/NativeNt.h"
+
+#include <string>
+
+#define EXPAND_STRING_MACRO2(t) t
+#define EXPAND_STRING_MACRO(t) EXPAND_STRING_MACRO2(t)
+
+namespace mozilla {
+
+const wchar_t LauncherRegistryInfo::kLauncherSubKeyPath[] =
+    L"SOFTWARE\\" EXPAND_STRING_MACRO(MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(
+        MOZ_APP_BASENAME) L"\\Launcher";
+const wchar_t LauncherRegistryInfo::kLauncherSuffix[] = L"|Launcher";
+const wchar_t LauncherRegistryInfo::kBrowserSuffix[] = L"|Browser";
+const wchar_t LauncherRegistryInfo::kImageTimestampSuffix[] = L"|Image";
+
+LauncherResult<LauncherRegistryInfo::Disposition> LauncherRegistryInfo::Open() {
+  if (!!mRegKey) {
+    return Disposition::OpenedExisting;
+  }
+
+  DWORD disposition;
+  HKEY rawKey;
+  LSTATUS result = ::RegCreateKeyExW(
+      HKEY_CURRENT_USER, kLauncherSubKeyPath, 0, nullptr,
+      REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, &disposition);
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  mRegKey.own(rawKey);
+
+  switch (disposition) {
+    case REG_CREATED_NEW_KEY:
+      return Disposition::CreatedNew;
+    case REG_OPENED_EXISTING_KEY:
+      return Disposition::OpenedExisting;
+    default:
+      break;
+  }
+
+  MOZ_ASSERT_UNREACHABLE("Invalid disposition from RegCreateKeyExW");
+  return LAUNCHER_ERROR_GENERIC();
+}
+
+LauncherVoidResult LauncherRegistryInfo::ReflectPrefToRegistry(
+    const bool aEnable) {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  // Always delete the launcher timestamp
+  LauncherResult<bool> clearedLauncherTimestamp =
+      ClearStartTimestamp(ProcessType::Launcher);
+  MOZ_ASSERT(clearedLauncherTimestamp.isOk());
+  if (clearedLauncherTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedLauncherTimestamp);
+  }
+
+  if (!aEnable) {
+    // Set the browser timestamp to 0 to indicate force-disabled
+    return WriteStartTimestamp(ProcessType::Browser, Some(0ULL));
+  }
+
+  // Otherwise we delete the browser timestamp to start over fresh
+  LauncherResult<bool> clearedBrowserTimestamp =
+      ClearStartTimestamp(ProcessType::Browser);
+  MOZ_ASSERT(clearedBrowserTimestamp.isOk());
+  if (clearedBrowserTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedBrowserTimestamp);
+  }
+
+  return Ok();
+}
+
+LauncherResult<LauncherRegistryInfo::ProcessType> LauncherRegistryInfo::Check(
+    const ProcessType aDesiredType) {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  LauncherResult<DWORD> ourImageTimestamp = GetCurrentImageTimestamp();
+  if (ourImageTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(ourImageTimestamp);
+  }
+
+  LauncherResult<DWORD> savedImageTimestamp = GetSavedImageTimestamp();
+  if (savedImageTimestamp.isErr() &&
+      savedImageTimestamp.unwrapErr() !=
+          WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return LAUNCHER_ERROR_FROM_RESULT(savedImageTimestamp);
+  }
+
+  // If we don't have a saved timestamp, or we do but it doesn't match with
+  // our current timestamp, clear previous values.
+  if (savedImageTimestamp.isErr() ||
+      savedImageTimestamp.unwrap() != ourImageTimestamp.unwrap()) {
+    LauncherVoidResult clearResult = ClearStartTimestamps();
+    if (clearResult.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(clearResult);
+    }
+
+    LauncherVoidResult writeResult =
+        WriteImageTimestamp(ourImageTimestamp.unwrap());
+    if (writeResult.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(writeResult);
+    }
+  }
+
+  ProcessType typeToRunAs = aDesiredType;
+
+  if (disposition.unwrap() == Disposition::CreatedNew ||
+      aDesiredType == ProcessType::Browser) {
+    // No existing values to check, or we're going to be running as the browser
+    // process: just write our timestamp and return
+    LauncherVoidResult wroteTimestamp = WriteStartTimestamp(typeToRunAs);
+    if (wroteTimestamp.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(wroteTimestamp);
+    }
+
+    return typeToRunAs;
+  }
+
+  LauncherResult<uint64_t> lastLauncherTimestamp =
+      GetStartTimestamp(ProcessType::Launcher);
+  bool haveLauncherTs = lastLauncherTimestamp.isOk();
+
+  LauncherResult<uint64_t> lastBrowserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+  bool haveBrowserTs = lastBrowserTimestamp.isOk();
+
+  if (haveLauncherTs != haveBrowserTs) {
+    // If we have a launcher timestamp but no browser timestamp (or vice versa),
+    // that's bad because it is indicating that the browser can't run with
+    // the launcher process.
+    typeToRunAs = ProcessType::Browser;
+  } else if (haveLauncherTs) {
+    // if we have both timestamps, we want to ensure that the launcher timestamp
+    // is earlier than the browser timestamp.
+    if (aDesiredType == ProcessType::Launcher) {
+      bool areTimestampsOk =
+          lastLauncherTimestamp.unwrap() < lastBrowserTimestamp.unwrap();
+      if (!areTimestampsOk) {
+        typeToRunAs = ProcessType::Browser;
+      }
+    }
+  } else {
+    // If we have neither timestamp, then we should try running as suggested
+    // by |aDesiredType|.
+    // We shouldn't really have this scenario unless we're going to be running
+    // as the launcher process.
+    MOZ_ASSERT(typeToRunAs == ProcessType::Launcher);
+    // No change to typeToRunAs
+  }
+
+  LauncherVoidResult wroteTimestamp = Ok();
+
+  if (typeToRunAs == ProcessType::Browser && aDesiredType != typeToRunAs) {
+    // We were hoping to run as the launcher, but some failure has caused us
+    // to run as the browser. Set the browser timestamp to zero as an indicator.
+    wroteTimestamp = WriteStartTimestamp(typeToRunAs, Some(0ULL));
+  } else {
+    wroteTimestamp = WriteStartTimestamp(typeToRunAs);
+  }
+
+  if (wroteTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(wroteTimestamp);
+  }
+
+  return typeToRunAs;
+}
+
+LauncherVoidResult LauncherRegistryInfo::DisableDueToFailure() {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  return WriteStartTimestamp(ProcessType::Browser, Some(0ULL));
+}
+
+LauncherResult<LauncherRegistryInfo::EnabledState>
+LauncherRegistryInfo::IsEnabled() {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  LauncherResult<uint64_t> launcherTimestamp =
+      GetStartTimestamp(ProcessType::Launcher);
+
+  LauncherResult<uint64_t> browserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+
+  // In this function, we'll explictly search for the ForceDisabled and
+  // Enabled conditions. Everything else is FailDisabled.
+
+  bool isBrowserTimestampZero =
+      browserTimestamp.isOk() && browserTimestamp.unwrap() == 0ULL;
+
+  if (isBrowserTimestampZero && launcherTimestamp.isErr()) {
+    return EnabledState::ForceDisabled;
+  }
+
+  if (launcherTimestamp.isOk() && browserTimestamp.isOk() &&
+      launcherTimestamp.unwrap() < browserTimestamp.unwrap()) {
+    return EnabledState::Enabled;
+  }
+
+  if (launcherTimestamp.isErr() && browserTimestamp.isErr()) {
+    return EnabledState::Enabled;
+  }
+
+  return EnabledState::FailDisabled;
+}
+
+LauncherResult<std::wstring> LauncherRegistryInfo::ResolveValueName(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  if (aProcessType == ProcessType::Launcher) {
+    if (mLauncherValueName.empty()) {
+      mLauncherValueName.assign(mBinPath);
+      mLauncherValueName.append(kLauncherSuffix,
+                                ArrayLength(kLauncherSuffix) - 1);
+    }
+
+    return mLauncherValueName;
+  } else if (aProcessType == ProcessType::Browser) {
+    if (mBrowserValueName.empty()) {
+      mBrowserValueName.assign(mBinPath);
+      mBrowserValueName.append(kBrowserSuffix, ArrayLength(kBrowserSuffix) - 1);
+    }
+
+    return mBrowserValueName;
+  }
+
+  return LAUNCHER_ERROR_GENERIC();
+}
+
+std::wstring LauncherRegistryInfo::ResolveImageTimestampValueName() {
+  if (mImageValueName.empty()) {
+    mImageValueName.assign(mBinPath);
+    mImageValueName.append(kImageTimestampSuffix,
+                           ArrayLength(kImageTimestampSuffix) - 1);
+  }
+
+  return mImageValueName;
+}
+
+LauncherVoidResult LauncherRegistryInfo::WriteStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType, const Maybe<uint64_t>& aValue) {
+  LauncherResult<std::wstring> name = ResolveValueName(aProcessType);
+  if (name.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(name);
+  }
+
+  ULARGE_INTEGER timestamp;
+  if (aValue.isSome()) {
+    timestamp.QuadPart = aValue.value();
+  } else {
+    // We need to use QPC here because millisecond granularity is too coarse
+    // to properly measure the times involved.
+    if (!::QueryPerformanceCounter(
+            reinterpret_cast<LARGE_INTEGER*>(&timestamp))) {
+      return LAUNCHER_ERROR_FROM_LAST();
+    }
+  }
+
+  DWORD len = sizeof(timestamp);
+  LSTATUS result =
+      ::RegSetValueExW(mRegKey.get(), name.unwrap().c_str(), 0, REG_QWORD,
+                       reinterpret_cast<PBYTE>(&timestamp), len);
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  return Ok();
+}
+
+LauncherResult<DWORD> LauncherRegistryInfo::GetCurrentImageTimestamp() {
+  nt::PEHeaders headers(::GetModuleHandleW(nullptr));
+  if (!headers) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+  }
+
+  DWORD timestamp;
+  if (!headers.GetTimeStamp(timestamp)) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
+  }
+
+  return timestamp;
+}
+
+LauncherVoidResult LauncherRegistryInfo::WriteImageTimestamp(DWORD aTimestamp) {
+  std::wstring imageTimestampValueName = ResolveImageTimestampValueName();
+
+  DWORD len = sizeof(aTimestamp);
+  LSTATUS result =
+      ::RegSetValueExW(mRegKey.get(), imageTimestampValueName.c_str(), 0,
+                       REG_DWORD, reinterpret_cast<PBYTE>(&aTimestamp), len);
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  return Ok();
+}
+
+LauncherResult<bool> LauncherRegistryInfo::ClearStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  LauncherResult<std::wstring> timestampName = ResolveValueName(aProcessType);
+  if (timestampName.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(timestampName);
+  }
+
+  LSTATUS result =
+      ::RegDeleteValueW(mRegKey.get(), timestampName.unwrap().c_str());
+  if (result == ERROR_SUCCESS) {
+    return true;
+  }
+
+  if (result == ERROR_FILE_NOT_FOUND) {
+    return false;
+  }
+
+  return LAUNCHER_ERROR_FROM_WIN32(result);
+}
+
+LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() {
+  LauncherResult<uint64_t> lastBrowserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+  if (lastBrowserTimestamp.isOk() && lastBrowserTimestamp.unwrap() == 0ULL) {
+    // Only proceed when the browser timestamp is non-zero
+    return Ok();
+  }
+
+  LauncherResult<bool> clearedLauncherTimestamp =
+      ClearStartTimestamp(ProcessType::Launcher);
+  if (clearedLauncherTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedLauncherTimestamp);
+  }
+
+  LauncherResult<bool> clearedBrowserTimestamp =
+      ClearStartTimestamp(ProcessType::Browser);
+  if (clearedBrowserTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedBrowserTimestamp);
+  }
+
+  return Ok();
+}
+
+LauncherResult<DWORD> LauncherRegistryInfo::GetSavedImageTimestamp() {
+  std::wstring imageTimestampValueName = ResolveImageTimestampValueName();
+
+  DWORD value;
+  DWORD valueLen = sizeof(value);
+  DWORD type;
+  LSTATUS result = ::RegQueryValueExW(
+      mRegKey.get(), imageTimestampValueName.c_str(), nullptr, &type,
+      reinterpret_cast<PBYTE>(&value), &valueLen);
+  // NB: If the value does not exist, result == ERROR_FILE_NOT_FOUND
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  if (type != REG_DWORD) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+  }
+
+  return value;
+}
+
+LauncherResult<uint64_t> LauncherRegistryInfo::GetStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  LauncherResult<std::wstring> name = ResolveValueName(aProcessType);
+  if (name.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(name);
+  }
+
+  uint64_t value;
+  DWORD valueLen = sizeof(value);
+  DWORD type;
+  LSTATUS result =
+      ::RegQueryValueExW(mRegKey.get(), name.unwrap().c_str(), nullptr, &type,
+                         reinterpret_cast<PBYTE>(&value), &valueLen);
+  // NB: If the value does not exist, result == ERROR_FILE_NOT_FOUND
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  if (type != REG_QWORD) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+  }
+
+  return value;
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LauncherRegistryInfo_h
+#define mozilla_LauncherRegistryInfo_h
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/LauncherResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsWindowsHelpers.h"
+
+#include <string>
+
+/**
+ * We use std::wstring here because this code must be usable within both the
+ * launcher process and Gecko itself.
+ */
+
+namespace mozilla {
+
+class LauncherRegistryInfo final {
+ public:
+  enum class ProcessType { Launcher, Browser };
+
+  enum class EnabledState {
+    Enabled,
+    FailDisabled,
+    ForceDisabled,
+  };
+
+  LauncherRegistryInfo() : mBinPath(GetFullBinaryPath().get()) {}
+
+  LauncherVoidResult ReflectPrefToRegistry(const bool aEnable);
+  LauncherResult<EnabledState> IsEnabled();
+  LauncherResult<ProcessType> Check(const ProcessType aDesiredType);
+  LauncherVoidResult DisableDueToFailure();
+
+ private:
+  enum class Disposition { CreatedNew, OpenedExisting };
+
+ private:
+  LauncherResult<Disposition> Open();
+  LauncherVoidResult WriteStartTimestamp(ProcessType aProcessType,
+                                         const Maybe<uint64_t>& aValue = Nothing());
+  LauncherResult<DWORD> GetCurrentImageTimestamp();
+  LauncherVoidResult WriteImageTimestamp(DWORD aTimestamp);
+  LauncherResult<bool> ClearStartTimestamp(ProcessType aProcessType);
+  LauncherVoidResult ClearStartTimestamps();
+  LauncherResult<DWORD> GetSavedImageTimestamp();
+  LauncherResult<uint64_t> GetStartTimestamp(ProcessType aProcessType);
+
+  LauncherResult<std::wstring> ResolveValueName(ProcessType aProcessType);
+  std::wstring ResolveImageTimestampValueName();
+
+ private:
+  nsAutoRegKey mRegKey;
+  std::wstring mBinPath;
+  std::wstring mImageValueName;
+  std::wstring mBrowserValueName;
+  std::wstring mLauncherValueName;
+
+  static const wchar_t kLauncherSubKeyPath[];
+  static const wchar_t kLauncherSuffix[];
+  static const wchar_t kBrowserSuffix[];
+  static const wchar_t kImageTimestampSuffix[];
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_LauncherRegistryInfo_h
rename from browser/app/winlauncher/LauncherResult.h
rename to toolkit/xre/LauncherResult.h
--- a/browser/app/winlauncher/LauncherResult.h
+++ b/toolkit/xre/LauncherResult.h
@@ -7,54 +7,95 @@
 #ifndef mozilla_LauncherResult_h
 #define mozilla_LauncherResult_h
 
 #include "mozilla/Result.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 
 namespace mozilla {
 
+#if defined(MOZILLA_INTERNAL_API)
+
+template <typename T>
+using LauncherResult = WindowsErrorResult<T>;
+
+#else
+
 struct LauncherError {
   LauncherError(const char* aFile, int aLine, WindowsError aWin32Error)
       : mFile(aFile), mLine(aLine), mError(aWin32Error) {}
 
   const char* mFile;
   int mLine;
   WindowsError mError;
+
+  bool operator==(const LauncherError& aOther) const {
+    return mError == aOther.mError;
+  }
+
+  bool operator!=(const LauncherError& aOther) const {
+    return mError != aOther.mError;
+  }
+
+  bool operator==(const WindowsError& aOther) const { return mError == aOther; }
+
+  bool operator!=(const WindowsError& aOther) const { return mError != aOther; }
 };
 
 template <typename T>
 using LauncherResult = Result<T, LauncherError>;
 
-using LauncherVoidResult = Result<Ok, LauncherError>;
+#endif  // defined(MOZILLA_INTERNAL_API)
+
+using LauncherVoidResult = LauncherResult<Ok>;
 
 }  // namespace mozilla
 
+#if defined(MOZILLA_INTERNAL_API)
+
+#define LAUNCHER_ERROR_GENERIC() \
+  ::mozilla::Err(::mozilla::WindowsError::CreateGeneric())
+
+#define LAUNCHER_ERROR_FROM_WIN32(err) \
+  ::mozilla::Err(::mozilla::WindowsError::FromWin32Error(err))
+
+#define LAUNCHER_ERROR_FROM_LAST() \
+  ::mozilla::Err(::mozilla::WindowsError::FromLastError())
+
+#define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+  ::mozilla::Err(::mozilla::WindowsError::FromNtStatus(ntstatus))
+
+#define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+  ::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult))
+
+#else
+
 #define LAUNCHER_ERROR_GENERIC()           \
   ::mozilla::Err(::mozilla::LauncherError( \
       __FILE__, __LINE__, ::mozilla::WindowsError::CreateGeneric()))
 
 #define LAUNCHER_ERROR_FROM_WIN32(err)     \
   ::mozilla::Err(::mozilla::LauncherError( \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromWin32Error(err)))
 
 #define LAUNCHER_ERROR_FROM_LAST()         \
   ::mozilla::Err(::mozilla::LauncherError( \
-      __FILE__, __LINE__,                  \
-      ::mozilla::WindowsError::FromWin32Error(::GetLastError())))
+      __FILE__, __LINE__, ::mozilla::WindowsError::FromLastError()))
 
 #define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
   ::mozilla::Err(::mozilla::LauncherError(     \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromNtStatus(ntstatus)))
 
 #define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
   ::mozilla::Err(::mozilla::LauncherError(   \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromHResult(hresult)))
 
+// This macro wraps the supplied WindowsError with a LauncherError
+#define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+
+#endif  // defined(MOZILLA_INTERNAL_API)
+
 // This macro enables moving of a mozilla::LauncherError from a
 // mozilla::LauncherResult<Foo> into a mozilla::LauncherResult<Bar>
 #define LAUNCHER_ERROR_FROM_RESULT(result) ::mozilla::Err(result.unwrapErr())
 
-// This macro wraps the supplied WindowsError with a LauncherError
-#define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
-  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
-
 #endif  // mozilla_LauncherResult_h
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -54,16 +54,24 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
         'ModuleVersionInfo_windows.cpp',
         'nsNativeAppSupportWin.cpp',
         'WinDllServices.cpp',
     ]
     DEFINES['PROXY_PRINTING'] = 1
     LOCAL_INCLUDES += [
         '../components/printingui',
     ]
+    if CONFIG['MOZ_LAUNCHER_PROCESS']:
+        EXPORTS.mozilla += [
+          'LauncherRegistryInfo.h',
+          'LauncherResult.h',
+        ]
+        UNIFIED_SOURCES += [
+          'LauncherRegistryInfo.cpp',
+        ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     UNIFIED_SOURCES += [
         'MacApplicationDelegate.mm',
         'MacAutoreleasePool.mm',
         'MacLaunchHelper.mm',
         'nsCommandLineServiceMac.cpp',
         'nsNativeAppSupportCocoa.mm',
         'updaterfileutils_osx.mm',
@@ -151,17 +159,18 @@ if CONFIG['MOZ_PDF_PRINTING']:
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_X11']:
     DEFINES['USE_GLX_TEST'] = True
 
 for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_DISPLAYNAME',
-            'MOZ_APP_VERSION', 'OS_TARGET', 'MOZ_WIDGET_TOOLKIT'):
+            'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'OS_TARGET',
+            'MOZ_WIDGET_TOOLKIT'):
     DEFINES[var] = '"%s"' % CONFIG[var]
 
 if CONFIG['MOZ_UPDATER'] and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     DEFINES['MOZ_UPDATER'] = True
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     DEFINES['WIN32_LEAN_AND_MEAN'] = True
     DEFINES['UNICODE'] = True
@@ -185,16 +194,17 @@ for var in ('APP_VERSION', 'APP_ID'):
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     DEFINES['MOZ_BUILD_APP_IS_BROWSER'] = True
 
 LOCAL_INCLUDES += [
     '../../other-licenses/nsis/Contrib/CityHash/cityhash',
     '../components/find',
     '../components/printingui/ipc',
     '../components/windowwatcher',
+    '../mozapps/update/common',
     '../profile',
     '/config',
     '/dom/base',
     '/dom/commandhandler',
     '/dom/ipc',
     '/dom/webbrowserpersist',
     '/testing/gtest/mozilla',
     '/toolkit/crashreporter',
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -105,16 +105,20 @@
 #include <intrin.h>
 #include <math.h>
 #include "cairo/cairo-features.h"
 #include "mozilla/WindowsDllBlocklist.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "mozilla/mscom/MainThreadRuntime.h"
 #include "mozilla/widget/AudioSession.h"
 
+#if defined(MOZ_LAUNCHER_PROCESS)
+#include "mozilla/LauncherRegistryInfo.h"
+#endif
+
 #ifndef PROCESS_DEP_ENABLE
 #define PROCESS_DEP_ENABLE 0x1
 #endif
 #endif
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/SandboxSettings.h"
 #if (defined(XP_WIN) || defined(XP_MACOSX))
@@ -1623,16 +1627,47 @@ static void RegisterApplicationRestartCh
       // should be restarted if terminated by an update or restart.
       ::RegisterApplicationRestart(restartCommandLine.get(),
                                    RESTART_NO_CRASH | RESTART_NO_HANG);
     }
   } else if (wasRegistered) {
     ::UnregisterApplicationRestart();
   }
 }
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+
+static void OnLauncherPrefChanged(const char* aPref, void* aData) {
+  bool prefVal = Preferences::GetBool(PREF_WIN_LAUNCHER_PROCESS_ENABLED, false);
+
+  mozilla::LauncherRegistryInfo launcherRegInfo;
+  mozilla::LauncherVoidResult reflectResult =
+      launcherRegInfo.ReflectPrefToRegistry(prefVal);
+  MOZ_ASSERT(reflectResult.isOk());
+}
+
+static void SetupLauncherProcessPref() {
+  mozilla::LauncherRegistryInfo launcherRegInfo;
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState>
+      enabledState = launcherRegInfo.IsEnabled();
+
+  if (enabledState.isOk()) {
+    Preferences::SetBool(
+        PREF_WIN_LAUNCHER_PROCESS_ENABLED,
+        enabledState.unwrap() !=
+            mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled);
+  }
+
+  Preferences::RegisterCallback(&OnLauncherPrefChanged,
+                                PREF_WIN_LAUNCHER_PROCESS_ENABLED);
+}
+
+#endif  // defined(MOZ_LAUNCHER_PROCESS)
+
 #endif  // XP_WIN
 
 // If aBlankCommandLine is true, then the application will be launched with a
 // blank command line instead of being launched with the same command line that
 // it was initially started with.
 static nsresult LaunchChild(nsINativeAppSupport* aNative,
                             bool aBlankCommandLine = false) {
   aNative->Quit();  // release DDE mutex, if we're holding it
@@ -4225,16 +4260,19 @@ nsresult XREMain::XRE_mainRun() {
 
   if (!mShuttingDown) {
     rv = appStartup->CreateHiddenWindow();
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
 #ifdef XP_WIN
     Preferences::RegisterCallbackAndCall(RegisterApplicationRestartChanged,
                                          PREF_WIN_REGISTER_APPLICATION_RESTART);
+#if defined(MOZ_LAUNCHER_PROCESS)
+    SetupLauncherProcessPref();
+#endif  // defined(MOZ_LAUNCHER_PROCESS)
 #endif
 
 #if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK)
     nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
     if (toolkit && !mDesktopStartupID.IsEmpty()) {
       toolkit->SetDesktopStartupID(mDesktopStartupID);
     }
     // Clear the environment variable so it won't be inherited by
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -97,16 +97,20 @@ void MozExpectedExit();
 #ifdef XP_WIN
 void UseParentConsole();
 
 BOOL WinLaunchChild(const wchar_t* exePath, int argc, char** argv,
                     HANDLE userToken = nullptr, HANDLE* hProcess = nullptr);
 
 #define PREF_WIN_REGISTER_APPLICATION_RESTART \
   "toolkit.winRegisterApplicationRestart"
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+#define PREF_WIN_LAUNCHER_PROCESS_ENABLED "browser.launcherProcess.enabled"
+#endif  // defined(MOZ_LAUNCHER_PROCESS)
 #endif
 
 namespace mozilla {
 namespace startup {
 Result<nsCOMPtr<nsIFile>, nsresult> GetIncompleteStartupFile(nsIFile* aProfLD);
 
 extern GeckoProcessType sChildProcessType;
 }  // namespace startup
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -33,16 +33,19 @@
 #include "nsAutoRef.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsExceptionHandler.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsJSUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsXREDirProvider.h"
+#ifdef MOZ_ASAN_REPORTER
+#include "CmdLineAndEnvUtils.h"
+#endif
 #include "ThreadAnnotation.h"
 
 #include "mozilla/Omnijar.h"
 #if defined(XP_MACOSX)
 #include "nsVersionComparator.h"
 #include "chrome/common/mach_ipc_mac.h"
 #endif
 #include "nsX11ErrorHandler.h"
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
@@ -0,0 +1,694 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/LauncherRegistryInfo.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "CmdLineAndEnvUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include "LauncherRegistryInfo.cpp"
+
+#include <string>
+
+const char kFailFmt[] = "TEST-FAILED | LauncherRegistryInfo | %s | %S\n";
+
+static const wchar_t kRegKeyPath[] = L"SOFTWARE\\" EXPAND_STRING_MACRO(
+    MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(MOZ_APP_BASENAME) L"\\Launcher";
+static const wchar_t kBrowserSuffix[] = L"|Browser";
+static const wchar_t kLauncherSuffix[] = L"|Launcher";
+static const wchar_t kImageSuffix[] = L"|Image";
+
+static std::wstring gBrowserValue;
+static std::wstring gLauncherValue;
+static std::wstring gImageValue;
+
+static DWORD gMyImageTimestamp;
+
+using QWordResult = mozilla::Result<DWORD64, mozilla::WindowsError>;
+using DWordResult = mozilla::Result<DWORD, mozilla::WindowsError>;
+using VoidResult = mozilla::Result<mozilla::Ok, mozilla::WindowsError>;
+
+#define RUN_TEST(fn)                                        \
+  if ((vr = fn()).isErr()) {                                \
+    printf(kFailFmt, #fn, vr.unwrapErr().AsString().get()); \
+    return 1;                                               \
+  }
+
+static QWordResult GetBrowserTimestamp() {
+  DWORD64 qword;
+  DWORD size = sizeof(qword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gBrowserValue.c_str(),
+                     RRF_RT_QWORD, nullptr, &qword, &size);
+  if (status == ERROR_SUCCESS) {
+    return qword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetBrowserTimestamp(DWORD64 aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gBrowserValue.c_str(),
+                        REG_QWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteBrowserTimestamp() {
+  LSTATUS status = ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
+                                        gBrowserValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static QWordResult GetLauncherTimestamp() {
+  DWORD64 qword;
+  DWORD size = sizeof(qword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gLauncherValue.c_str(),
+                     RRF_RT_QWORD, nullptr, &qword, &size);
+  if (status == ERROR_SUCCESS) {
+    return qword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetLauncherTimestamp(DWORD64 aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gLauncherValue.c_str(),
+                        REG_QWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteLauncherTimestamp() {
+  LSTATUS status = ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
+                                        gLauncherValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static DWordResult GetImageTimestamp() {
+  DWORD dword;
+  DWORD size = sizeof(dword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str(),
+                     RRF_RT_DWORD, nullptr, &dword, &size);
+  if (status == ERROR_SUCCESS) {
+    return dword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetImageTimestamp(DWORD aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str(),
+                        REG_DWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteImageTimestamp() {
+  LSTATUS status =
+      ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteAllTimestamps() {
+  VoidResult vr = DeleteBrowserTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  return DeleteImageTimestamp();
+}
+
+static VoidResult SetupEnabledScenario() {
+  // Reset the registry state to an enabled state. First, we delete all existing
+  // timestamps (if any).
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  // Now we run Check(Launcher)...
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Launcher) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // ...and Check(Browser)
+  result = info.Check(mozilla::LauncherRegistryInfo::ProcessType::Browser);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  // By this point we are considered to be fully enabled.
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestEmptyRegistry() {
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Launcher) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // LauncherRegistryInfo should have created Launcher and Image values
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isErr()) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  DWordResult imageTs = GetImageTimestamp();
+  if (imageTs.isErr()) {
+    return mozilla::Err(imageTs.unwrapErr());
+  }
+
+  if (imageTs.unwrap() != gMyImageTimestamp) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (browserTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  return mozilla::Ok();
+}
+
+// This test depends on the side effects from having just run TestEmptyRegistry
+static VoidResult TestNormal() {
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Browser);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isErr()) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isErr()) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  if (browserTs.unwrap() < launcherTs.unwrap()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::Enabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+// This test depends on the side effects from having just run TestNormal
+static VoidResult TestBrowserNoLauncher() {
+  VoidResult vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // Verify that we still don't have a launcher timestamp
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  if (launcherTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  // Verify that the browser timestamp is now zero
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isErr()) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  if (browserTs.unwrap() != 0ULL) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestLauncherNoBrowser() {
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetLauncherTimestamp(0x77777777);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetImageTimestamp(gMyImageTimestamp);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::FailDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestBrowserLessThanLauncher() {
+  VoidResult vr = SetLauncherTimestamp(0x77777777);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetBrowserTimestamp(0ULL);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  moz