Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Sat, 11 Aug 2018 06:46:31 +0300
changeset 486162 1ceb97675d5671a58ce33e9fb9be2129e24ea4a5
parent 486161 09fe744984bc6f548ae4b9f2e09ae44773215923 (current diff)
parent 486142 7ed5ed3d48146ef939a6ca4cba221430128ba5f5 (diff)
child 486163 8dadac023d15cce16284e5ebb533b5b267cb552e
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.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 autoland. a=merge CLOSED TREE
browser/base/content/browser-trackingprotection.js
browser/base/content/nsContextMenu.js
browser/base/content/test/general/benignPage.html
browser/base/content/test/general/browser_trackingUI_3.js
browser/base/content/test/general/browser_trackingUI_4.js
browser/base/content/test/general/browser_trackingUI_5.js
browser/base/content/test/general/browser_trackingUI_6.js
browser/base/content/test/general/browser_trackingUI_telemetry.js
browser/base/content/test/general/browser_utilityOverlay.js
browser/base/content/test/general/file_trackingUI_6.html
browser/base/content/test/general/file_trackingUI_6.js
browser/base/content/test/general/file_trackingUI_6.js^headers^
browser/base/content/test/general/trackingPage.html
browser/base/content/test/trackingUI/browser_trackingUI_reload_hint.js
browser/themes/shared/controlcenter/tracking-protection-disabled.svg
browser/themes/shared/controlcenter/warning-gray.svg
browser/themes/shared/controlcenter/warning-yellow.svg
modules/libpref/init/all.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1435409 - move Android to a non-NDK clang
+Bug 1433158 - libvpx library update requires a clobber
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -12,26 +12,27 @@ ChromeUtils.defineModuleGetter(this, "Re
                                "resource://gre/modules/Geometry.jsm");
 
 if (Utils.MozBuildApp === "mobile/android") {
   ChromeUtils.import("resource://gre/modules/Messaging.jsm");
 }
 
 const GECKOVIEW_MESSAGE = {
   ACTIVATE: "GeckoView:AccessibilityActivate",
-  VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
+  BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
+  CLIPBOARD: "GeckoView:AccessibilityClipboard",
+  EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
   LONG_PRESS: "GeckoView:AccessibilityLongPress",
-  BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
   NEXT: "GeckoView:AccessibilityNext",
   PREVIOUS: "GeckoView:AccessibilityPrevious",
   SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
   SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
-  EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
+  SELECT: "GeckoView:AccessibilitySelect",
   SET_SELECTION: "GeckoView:AccessibilitySetSelection",
-  CLIPBOARD: "GeckoView:AccessibilityClipboard",
+  VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
 };
 
 const ACCESSFU_MESSAGE = {
   PRESENT: "AccessFu:Present",
   DOSCROLL: "AccessFu:DoScroll",
 };
 
 const FRAME_SCRIPT = "chrome://global/content/accessibility/content-script.js";
@@ -233,16 +234,19 @@ var AccessFu = {
         this.Input.moveToPoint("Simple", ...data.coordinates);
         break;
       case GECKOVIEW_MESSAGE.SET_SELECTION:
         this.Input.setSelection(data);
         break;
       case GECKOVIEW_MESSAGE.CLIPBOARD:
         this.Input.clipboard(data);
         break;
+      case GECKOVIEW_MESSAGE.SELECT:
+        this.Input.selectCurrent(data);
+        break;
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "domwindowopened": {
         let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
         win.addEventListener("load", () => {
@@ -343,16 +347,21 @@ var Input = {
     mm.sendAsyncMessage("AccessFu:Clipboard", aDetails);
   },
 
   activateCurrent: function activateCurrent(aData) {
     let mm = Utils.getMessageManager();
     mm.sendAsyncMessage("AccessFu:Activate", { offset: 0 });
   },
 
+  selectCurrent: function selectCurrent(aData) {
+    let mm = Utils.getMessageManager();
+    mm.sendAsyncMessage("AccessFu:Select", aData);
+  },
+
   doScroll: function doScroll(aDetails, aBrowser) {
     let horizontal = aDetails.horizontal;
     let page = aDetails.page;
     let win = aBrowser.ownerGlobal;
     let winUtils = win.windowUtils;
     let p = AccessFu.screenToClientBounds(aDetails.bounds, win).center();
     winUtils.sendWheelEvent(p.x, p.y,
       horizontal ? page : 0, horizontal ? 0 : page, 0,
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -28,25 +28,26 @@ const CLIPBOARD_PASTE = 0x8000;
 const CLIPBOARD_CUT = 0x10000;
 
 function ContentControl(aContentScope) {
   this._contentScope = Cu.getWeakReference(aContentScope);
   this._childMessageSenders = new WeakMap();
 }
 
 this.ContentControl.prototype = {
-  messagesOfInterest: ["AccessFu:MoveCursor",
-                       "AccessFu:ClearCursor",
-                       "AccessFu:MoveToPoint",
+  messagesOfInterest: ["AccessFu:Activate",
+                       "AccessFu:AndroidScroll",
                        "AccessFu:AutoMove",
-                       "AccessFu:Activate",
+                       "AccessFu:ClearCursor",
+                       "AccessFu:Clipboard",
                        "AccessFu:MoveByGranularity",
-                       "AccessFu:AndroidScroll",
-                       "AccessFu:SetSelection",
-                       "AccessFu:Clipboard"],
+                       "AccessFu:MoveCursor",
+                       "AccessFu:MoveToPoint",
+                       "AccessFu:Select",
+                       "AccessFu:SetSelection"],
 
   start: function cc_start() {
     let cs = this._contentScope.get();
     for (let message of this.messagesOfInterest) {
       cs.addMessageListener(message, this);
     }
   },
 
@@ -175,16 +176,26 @@ this.ContentControl.prototype = {
     }
     this.document.activeElement.blur();
   },
 
   handleAutoMove: function cc_handleAutoMove(aMessage) {
     this.autoMove(null, aMessage.json);
   },
 
+  handleSelect: function cc_handleSelect(aMessage) {
+    const vc = this.vc;
+    if (!this.sendToChild(vc, aMessage, null, true)) {
+      const acc = vc.position;
+      if (Utils.getState(acc).contains(States.SELECTABLE)) {
+        this.handleActivate(aMessage);
+      }
+    }
+  },
+
   handleActivate: function cc_handleActivate(aMessage) {
     let activateAccessible = (aAccessible) => {
       Logger.debug(() => {
         return ["activateAccessible", Logger.accessibleToString(aAccessible)];
       });
 
       if (aAccessible.actionCount > 0) {
         aAccessible.doAction(0);
@@ -217,17 +228,17 @@ this.ContentControl.prototype = {
           node.dispatchEvent(evt);
         }
       }
 
       // Action invoked will be presented on checked/selected state change.
       if (!Utils.getState(aAccessible).contains(States.CHECKABLE) &&
           !Utils.getState(aAccessible).contains(States.SELECTABLE)) {
         this._contentScope.get().sendAsyncMessage("AccessFu:Present",
-          Presentation.actionInvoked(aAccessible, "click"));
+          Presentation.actionInvoked());
       }
     };
 
     let focusedAcc = Utils.AccService.getAccessibleFor(
       this.document.activeElement);
     if (focusedAcc && this.vc.position === focusedAcc
         && focusedAcc.role === Roles.ENTRY) {
       let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -88,35 +88,36 @@ this.EventManager.prototype = {
     return this.contentScope._jsat_contentControl;
   },
 
   handleEvent: function handleEvent(aEvent) {
     Logger.debug(() => {
       return ["DOMEvent", aEvent.type];
     });
 
+    // The target could be an element, document or window
+    const win = aEvent.target.ownerGlobal;
     try {
       switch (aEvent.type) {
-      case "wheel":
-      {
-        let delta = aEvent.deltaX || aEvent.deltaY;
-        this.contentControl.autoMove(
-         null,
-         { moveMethod: delta > 0 ? "moveNext" : "movePrevious",
-           onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
-        break;
-      }
-      case "scroll":
-      case "resize":
-      {
-        // the target could be an element, document or window
-        let window = aEvent.target.ownerGlobal;
-        this.present(Presentation.viewportChanged(window));
-        break;
-      }
+        case "wheel":
+        {
+          let delta = aEvent.deltaX || aEvent.deltaY;
+          this.contentControl.autoMove(
+           null,
+           { moveMethod: delta > 0 ? "moveNext" : "movePrevious",
+             onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
+          break;
+        }
+        case "scroll":
+          this.present(Presentation.viewportScrolled(win));
+        case "resize":
+        {
+          this.present(Presentation.viewportChanged(win));
+          break;
+        }
       }
     } catch (x) {
       Logger.logException(x, "Error handling DOM event");
     }
   },
 
   handleAccEvent: function handleAccEvent(aEvent) {
     Logger.debug(() => {
@@ -460,17 +461,19 @@ this.EventManager.prototype = {
     if (this._liveEventQueue.has(domNode)) {
       this._liveEventQueue.get(domNode).push(eventHandler);
     } else {
       this._liveEventQueue.set(domNode, [eventHandler]);
     }
   },
 
   present: function present(aPresentationData) {
-    this.sendMsgFunc("AccessFu:Present", aPresentationData);
+    if (aPresentationData && aPresentationData.length > 0) {
+      this.sendMsgFunc("AccessFu:Present", aPresentationData);
+    }
   },
 
   onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
     let tabstate = "";
 
     let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
       Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
--- a/accessible/jsat/Presentation.jsm
+++ b/accessible/jsat/Presentation.jsm
@@ -111,31 +111,26 @@ class AndroidPresentor {
   }
 
   /**
    * An object's select action has been invoked.
    * @param {nsIAccessible} aAccessible the object that has been invoked.
    */
   selected(aAccessible) {
     return [{
-      eventType: AndroidEvents.VIEW_CLICKED,
+      eventType: AndroidEvents.VIEW_SELECTED,
       selected: Utils.getState(aAccessible).contains(States.SELECTED)
     }];
   }
 
   /**
    * An object's action has been invoked.
-   * @param {nsIAccessible} aAccessible the object that has been invoked.
-   * @param {string} aActionName the name of the action.
    */
-  actionInvoked(aAccessible, aActionName) {
-    return [{
-      eventType: AndroidEvents.VIEW_CLICKED,
-      text: Utils.localize(UtteranceGenerator.genForAction(aAccessible, aActionName))
-    }];
+  actionInvoked() {
+    return [{ eventType: AndroidEvents.VIEW_CLICKED }];
   }
 
   /**
    * Text has changed, either by the user or by the system. TODO.
    */
   textChanged(aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
     let androidEvent = {
       eventType: AndroidEvents.VIEW_TEXT_CHANGED,
@@ -221,42 +216,50 @@ class AndroidPresentor {
    *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
    */
   tabStateChanged(aDocObj, aPageState) {
     return this.announce(
       UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
   }
 
   /**
-   * The viewport has changed, either a scroll, pan, zoom, or
-   *    landscape/portrait toggle.
+   * The viewport has changed because of scroll.
+   * @param {Window} aWindow window of viewport that changed.
+   */
+  viewportScrolled(aWindow) {
+    const { windowUtils, devicePixelRatio } = aWindow;
+    const resolution = { value: 1 };
+    windowUtils.getResolution(resolution);
+    const scale = devicePixelRatio * resolution.value;
+    return [{
+      eventType: AndroidEvents.VIEW_SCROLLED,
+      scrollX: aWindow.scrollX * scale,
+      scrollY: aWindow.scrollY * scale,
+      maxScrollX: aWindow.scrollMaxX * scale,
+      maxScrollY: aWindow.scrollMaxY * scale,
+    }];
+  }
+
+  /**
+   * The viewport has changed, either a pan, zoom, or landscape/portrait toggle.
    * @param {Window} aWindow window of viewport that changed.
    */
   viewportChanged(aWindow) {
-    let currentContext = this.displayedAccessibles.get(aWindow);
-
-    let events = [{
-      eventType: AndroidEvents.VIEW_SCROLLED,
-      scrollX: aWindow.scrollX,
-      scrollY: aWindow.scrollY,
-      maxScrollX: aWindow.scrollMaxX,
-      maxScrollY: aWindow.scrollMaxY,
-    }];
-
-    if (currentContext) {
-      let currentAcc = currentContext.accessibleForBounds;
-      if (Utils.isAliveAndVisible(currentAcc)) {
-        events.push({
-          eventType: AndroidEvents.WINDOW_CONTENT_CHANGED,
-          bounds: Utils.getBounds(currentAcc)
-        });
-      }
+    const currentContext = this.displayedAccessibles.get(aWindow);
+    if (!currentContext) {
+      return;
     }
 
-    return events;
+    const currentAcc = currentContext.accessibleForBounds;
+    if (Utils.isAliveAndVisible(currentAcc)) {
+      return [{
+        eventType: AndroidEvents.WINDOW_STATE_CHANGED,
+        bounds: Utils.getBounds(currentAcc)
+      }];
+    }
   }
 
   /**
    * Announce something. Typically an app state change.
    */
   announce(aAnnouncement) {
     let localizedAnnouncement = Utils.localize(aAnnouncement).join(" ");
     return [{
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1486,21 +1486,16 @@ pref("browser.contentblocking.enabled", 
 #ifdef NIGHTLY_BUILD
 pref("browser.contentblocking.ui.enabled", true);
 #else
 pref("browser.contentblocking.ui.enabled", false);
 #endif
 
 pref("privacy.trackingprotection.introCount", 0);
 pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
-#ifdef NIGHTLY_BUILD
-pref("privacy.trackingprotection.appMenuToggle.enabled", true);
-#else
-pref("privacy.trackingprotection.appMenuToggle.enabled", false);
-#endif
 
 // Always enable newtab segregation using containers
 pref("privacy.usercontext.about_newtab_segregation.enabled", true);
 // Enable Contextual Identity Containers
 #ifdef NIGHTLY_BUILD
 pref("privacy.userContext.enabled", true);
 pref("privacy.userContext.ui.enabled", true);
 
--- a/browser/app/winlauncher/NativeNt.h
+++ b/browser/app/winlauncher/NativeNt.h
@@ -120,19 +120,19 @@ FindCharInUnicodeString(const UNICODE_ST
   }
 
   return false;
 }
 
 inline bool
 IsHexDigit(WCHAR aChar)
 {
-  return aChar >= L'0' && aChar <= L'9' ||
-         aChar >= L'A' && aChar <= L'F' ||
-         aChar >= L'a' && aChar <= L'f';
+  return (aChar >= L'0' && aChar <= L'9') ||
+         (aChar >= L'A' && aChar <= L'F') ||
+         (aChar >= L'a' && aChar <= L'f');
 }
 
 inline bool
 MatchUnicodeString(const UNICODE_STRING& aStr, bool (*aPredicate)(WCHAR))
 {
   WCHAR* cur = aStr.Buffer;
   WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)];
   while (cur < end) {
--- a/browser/app/winlauncher/test/TestNativeNt.cpp
+++ b/browser/app/winlauncher/test/TestNativeNt.cpp
@@ -1,17 +1,19 @@
 /* -*- 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/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";
 const wchar_t kHex16[] = L"ABCDEF0123456789.dll";
 const wchar_t kHex17[] = L"ABCDEF0123456789a.dll";
 const wchar_t kHex24[] = L"ABCDEF0123456789cdabef98.dll";
 const wchar_t kHex8[] = L"01234567.dll";
@@ -29,16 +31,17 @@ const char kFailFmt[] = "TEST-FAILED | N
   }
 
 #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;
 
 int main(int argc, char* argv[])
 {
   UNICODE_STRING normal;
   ::RtlInitUnicodeString(&normal, kNormal);
 
   UNICODE_STRING hex12;
@@ -87,11 +90,58 @@ int main(int argc, char* argv[])
   EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex15);
   EXPECT_FAIL(IsFileNameAtLeast16HexDigits, prefixedHex16);
 
   if (RtlGetProcessHeap() != ::GetProcessHeap()) {
     printf("TEST-FAILED | NativeNt | RtlGetProcessHeap() is broken\n");
     return 1;
   }
 
+  const wchar_t kKernel32[] = L"kernel32.dll";
+  DWORD verInfoSize = ::GetFileVersionInfoSizeW(kKernel32, nullptr);
+  if (!verInfoSize) {
+    printf("TEST-FAILED | NativeNt | Call to GetFileVersionInfoSizeW failed with code %lu\n",
+           ::GetLastError());
+    return 1;
+  }
+
+  auto verInfoBuf = MakeUnique<char[]>(verInfoSize);
+
+  if (!::GetFileVersionInfoW(kKernel32, 0, verInfoSize, verInfoBuf.get())) {
+    printf("TEST-FAILED | NativeNt | Call to GetFileVersionInfoW failed with code %lu\n",
+           ::GetLastError());
+    return 1;
+  }
+
+  UINT len;
+  VS_FIXEDFILEINFO* fixedFileInfo = nullptr;
+  if (!::VerQueryValueW(verInfoBuf.get(), L"\\", (LPVOID*)&fixedFileInfo, &len)) {
+    printf("TEST-FAILED | NativeNt | Call to VerQueryValueW failed with code %lu\n",
+           ::GetLastError());
+    return 1;
+  }
+
+  const uint64_t expectedVersion =
+    (static_cast<uint64_t>(fixedFileInfo->dwFileVersionMS) << 32) |
+    static_cast<uint64_t>(fixedFileInfo->dwFileVersionLS);
+
+  PEHeaders k32headers(::GetModuleHandleW(kKernel32));
+  if (!k32headers) {
+    printf("TEST-FAILED | NativeNt | Failed parsing kernel32.dll's PE headers\n");
+    return 1;
+  }
+
+  uint64_t version;
+  if (!k32headers.GetVersionInfo(version)) {
+    printf("TEST-FAILED | NativeNt | Unable to obtain version information from kernel32.dll\n");
+    return 1;
+  }
+
+  if (version != expectedVersion) {
+    printf("TEST-FAILED | NativeNt | kernel32.dll's detected version "
+           "(0x%016llX) does not match expected version (0x%016llX)\n",
+           version, expectedVersion);
+    return 1;
+  }
+
   return 0;
 }
 
--- a/browser/app/winlauncher/test/moz.build
+++ b/browser/app/winlauncher/test/moz.build
@@ -8,13 +8,14 @@ DisableStlWrapping()
 
 CppUnitTests(['TestNativeNt'])
 
 LOCAL_INCLUDES += [
     '/browser/app/winlauncher',
 ]
 
 OS_LIBS += [
+    'mincore',
     'ntdll',
 ]
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
rename from browser/base/content/browser-trackingprotection.js
rename to browser/base/content/browser-contentblocking.js
--- a/browser/base/content/browser-trackingprotection.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -1,167 +1,296 @@
 /* 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/. */
 
+var FastBlock = {
+  PREF_ENABLED: "browser.fastblock.enabled",
+
+  get categoryItem() {
+    delete this.categoryItem;
+    return this.categoryItem = document.getElementById("identity-popup-content-blocking-category-fastblock");
+  },
+
+  init() {
+    XPCOMUtils.defineLazyPreferenceGetter(this, "enabled", this.PREF_ENABLED, false);
+  },
+};
+
 var TrackingProtection = {
-  // If the user ignores the doorhanger, we stop showing it after some time.
-  MAX_INTROS: 20,
   PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
   PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
-  PREF_APP_MENU_TOGGLE: "privacy.trackingprotection.appMenuToggle.enabled",
-  PREF_ANIMATIONS_ENABLED: "toolkit.cosmeticAnimations.enabled",
   enabledGlobally: false,
   enabledInPrivateWindows: false,
-  container: null,
+
+  get categoryItem() {
+    delete this.categoryItem;
+    return this.categoryItem =
+      document.getElementById("identity-popup-content-blocking-category-tracking-protection");
+  },
+
+  strings: {
+    get enableTooltip() {
+      delete this.enableTooltip;
+      return this.enableTooltip =
+        gNavigatorBundle.getString("trackingProtection.toggle.enable.tooltip");
+    },
+
+    get disableTooltip() {
+      delete this.disableTooltip;
+      return this.disableTooltip =
+        gNavigatorBundle.getString("trackingProtection.toggle.disable.tooltip");
+    },
+
+    get disableTooltipPB() {
+      delete this.disableTooltipPB;
+      return this.disableTooltipPB =
+        gNavigatorBundle.getString("trackingProtection.toggle.disable.pbmode.tooltip");
+    },
+
+    get enableTooltipPB() {
+      delete this.enableTooltipPB;
+      return this.enableTooltipPB =
+        gNavigatorBundle.getString("trackingProtection.toggle.enable.pbmode.tooltip");
+    },
+  },
+
+  init() {
+    this.updateEnabled();
+
+    this.enabledHistogramAdd(this.enabledGlobally);
+    this.disabledPBMHistogramAdd(!this.enabledInPrivateWindows);
+
+    Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this);
+    Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
+  },
+
+  uninit() {
+    Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
+    Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
+  },
+
+  observe() {
+    this.updateEnabled();
+  },
+
+  get enabled() {
+    return this.enabledGlobally ||
+           (this.enabledInPrivateWindows &&
+            PrivateBrowsingUtils.isWindowPrivate(window));
+  },
+
+  enabledHistogramAdd(value) {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      return;
+    }
+    Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
+  },
+
+  disabledPBMHistogramAdd(value) {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      return;
+    }
+    Services.telemetry.getHistogramById("TRACKING_PROTECTION_PBM_DISABLED").add(value);
+  },
+
+  onGlobalToggleCommand() {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      Services.prefs.setBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, !this.enabledInPrivateWindows);
+    } else {
+      Services.prefs.setBoolPref(this.PREF_ENABLED_GLOBALLY, !this.enabledGlobally);
+    }
+  },
+
+  updateEnabled() {
+    this.enabledGlobally =
+      Services.prefs.getBoolPref(this.PREF_ENABLED_GLOBALLY);
+    this.enabledInPrivateWindows =
+      Services.prefs.getBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS);
+
+    if (!ContentBlocking.contentBlockingUIEnabled) {
+      ContentBlocking.updateEnabled();
+      let appMenuButton = ContentBlocking.appMenuButton;
+
+      if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+        appMenuButton.setAttribute("tooltiptext", this.enabledInPrivateWindows ?
+          this.strings.disableTooltipPB : this.strings.enableTooltipPB);
+        appMenuButton.setAttribute("enabled", this.enabledInPrivateWindows);
+        appMenuButton.setAttribute("aria-pressed", this.enabledInPrivateWindows);
+      } else {
+        appMenuButton.setAttribute("tooltiptext", this.enabledGlobally ?
+          this.strings.disableTooltip : this.strings.enableTooltip);
+        appMenuButton.setAttribute("enabled", this.enabledGlobally);
+        appMenuButton.setAttribute("aria-pressed", this.enabledGlobally);
+      }
+    }
+  },
+};
+
+
+var ContentBlocking = {
+  // If the user ignores the doorhanger, we stop showing it after some time.
+  MAX_INTROS: 20,
+  PREF_ENABLED: "browser.contentblocking.enabled",
+  PREF_UI_ENABLED: "browser.contentblocking.ui.enabled",
+  PREF_ANIMATIONS_ENABLED: "toolkit.cosmeticAnimations.enabled",
   content: null,
   icon: null,
   activeTooltipText: null,
   disabledTooltipText: null,
 
+  get appMenuLabel() {
+    delete this.appMenuLabel;
+    return this.appMenuLabel = document.getElementById("appMenu-tp-label");
+  },
+
+  get appMenuButton() {
+    delete this.appMenuButton;
+    return this.appMenuButton = document.getElementById("appMenu-tp-toggle");
+  },
+
+  strings: {
+    get enableTooltip() {
+      delete this.enableTooltip;
+      return this.enableTooltip =
+        gNavigatorBundle.getString("contentBlocking.toggle.enable.tooltip");
+    },
+
+    get disableTooltip() {
+      delete this.disableTooltip;
+      return this.disableTooltip =
+        gNavigatorBundle.getString("contentBlocking.toggle.disable.tooltip");
+    },
+
+    get appMenuTitle() {
+      delete this.appMenuTitle;
+      return this.appMenuTitle =
+        gNavigatorBundle.getString("contentBlocking.title");
+    },
+
+    get appMenuTooltip() {
+      delete this.appMenuTooltip;
+      return this.appMenuTooltip =
+        gNavigatorBundle.getString("contentBlocking.tooltip");
+    },
+  },
+
+  // A list of blockers that will be displayed in the categories list
+  // when blockable content is detected. A blocker must be an object
+  // with at least the following two properties:
+  //  - enabled: Whether the blocker is currently turned on.
+  //  - categoryItem: The DOM item that represents the entry in the category list.
+  //
+  // It may also contain an init() and uninit() function, which will be called
+  // on ContentBlocking.init() and ContentBlocking.uninit().
+  blockers: [FastBlock, TrackingProtection],
+
   get _baseURIForChannelClassifier() {
     // Convert document URI into the format used by
     // nsChannelClassifier::ShouldEnableTrackingProtection.
     // Any scheme turned into https is correct.
     try {
       return Services.io.newURI("https://" + gBrowser.selectedBrowser.currentURI.hostPort);
     } catch (e) {
       // Getting the hostPort for about: and file: URIs fails, but TP doesn't work with
       // these URIs anyway, so just return null here.
       return null;
     }
   },
 
   init() {
     let $ = selector => document.querySelector(selector);
-    this.container = $("#tracking-protection-container");
-    this.content = $("#tracking-protection-content");
+    this.content = $("#identity-popup-content-blocking-content");
     this.icon = $("#tracking-protection-icon");
-    this.appMenuContainer = $("#appMenu-tp-container");
-    this.appMenuSeparator = $("#appMenu-tp-separator");
     this.iconBox = $("#tracking-protection-icon-box");
     this.animatedIcon = $("#tracking-protection-icon-animatable-image");
     this.animatedIcon.addEventListener("animationend", () => this.iconBox.removeAttribute("animate"));
 
-    this.appMenuButton = $("#appMenu-tp-toggle");
-
-    this.enableTooltip =
-      gNavigatorBundle.getString("trackingProtection.toggle.enable.tooltip");
-    this.disableTooltip =
-      gNavigatorBundle.getString("trackingProtection.toggle.disable.tooltip");
-    this.enableTooltipPB =
-      gNavigatorBundle.getString("trackingProtection.toggle.enable.pbmode.tooltip");
-    this.disableTooltipPB =
-      gNavigatorBundle.getString("trackingProtection.toggle.disable.pbmode.tooltip");
-
     this.updateAnimationsEnabled = () => {
       this.iconBox.toggleAttribute("animationsenabled",
         Services.prefs.getBoolPref(this.PREF_ANIMATIONS_ENABLED, false));
     };
 
+    for (let blocker of this.blockers) {
+      if (blocker.init) {
+        blocker.init();
+      }
+    }
+
     this.updateAnimationsEnabled();
 
     Services.prefs.addObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
 
-    this.updateEnabled();
+    XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingEnabled", this.PREF_ENABLED, false,
+      this.updateEnabled.bind(this));
+    XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingUIEnabled", this.PREF_UI_ENABLED, false,
+      this.updateUIEnabled.bind(this));
 
-    this.updateAppMenuToggle = () => {
-      if (Services.prefs.getBoolPref(this.PREF_APP_MENU_TOGGLE, false)) {
-        this.appMenuContainer.removeAttribute("hidden");
-        this.appMenuSeparator.removeAttribute("hidden");
-      } else {
-        this.appMenuContainer.setAttribute("hidden", "true");
-        this.appMenuSeparator.setAttribute("hidden", "true");
-      }
-    };
-
-    Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this);
-    Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
-    Services.prefs.addObserver(this.PREF_APP_MENU_TOGGLE, this.updateAppMenuToggle);
-
-    this.updateAppMenuToggle();
+    this.updateEnabled();
+    this.updateUIEnabled();
 
     this.activeTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
     this.disabledTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
-
-    this.enabledHistogramAdd(this.enabledGlobally);
-    this.disabledPBMHistogramAdd(!this.enabledInPrivateWindows);
   },
 
   uninit() {
-    Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
-    Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
-    Services.prefs.removeObserver(this.PREF_APP_MENU_TOGGLE, this.updateAppMenuToggle);
+    for (let blocker of this.blockers) {
+      if (blocker.uninit) {
+        blocker.uninit();
+      }
+    }
+
     Services.prefs.removeObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
   },
 
-  observe() {
+  get enabled() {
+    return this.contentBlockingUIEnabled ? this.contentBlockingEnabled : TrackingProtection.enabled;
+  },
+
+  updateEnabled() {
+    this.content.toggleAttribute("enabled", this.enabled);
+
+    if (this.contentBlockingUIEnabled) {
+      this.appMenuButton.setAttribute("tooltiptext", this.enabled ?
+        this.strings.disableTooltip : this.strings.enableTooltip);
+      this.appMenuButton.setAttribute("enabled", this.enabled);
+      this.appMenuButton.setAttribute("aria-pressed", this.enabled);
+    }
+  },
+
+  updateUIEnabled() {
+    this.content.toggleAttribute("contentBlockingUI", this.contentBlockingUIEnabled);
+
+    if (this.contentBlockingUIEnabled) {
+      this.appMenuLabel.setAttribute("label", this.strings.appMenuTitle);
+      this.appMenuLabel.setAttribute("tooltiptext", this.strings.appMenuTooltip);
+    }
+
     this.updateEnabled();
   },
 
-  get enabled() {
-    return this.enabledGlobally ||
-           (this.enabledInPrivateWindows &&
-            PrivateBrowsingUtils.isWindowPrivate(window));
-  },
-
   onGlobalToggleCommand() {
-    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
-      Services.prefs.setBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, !this.enabledInPrivateWindows);
+    if (this.contentBlockingUIEnabled) {
+      Services.prefs.setBoolPref(this.PREF_ENABLED, !this.enabled);
     } else {
-      Services.prefs.setBoolPref(this.PREF_ENABLED_GLOBALLY, !this.enabledGlobally);
+      TrackingProtection.onGlobalToggleCommand();
     }
   },
 
   hideIdentityPopupAndReload() {
     document.getElementById("identity-popup").hidePopup();
     BrowserReload();
   },
 
   openPreferences(origin) {
     openPreferences("privacy-trackingprotection", { origin });
   },
 
-  updateEnabled() {
-    this.enabledGlobally =
-      Services.prefs.getBoolPref(this.PREF_ENABLED_GLOBALLY);
-    this.enabledInPrivateWindows =
-      Services.prefs.getBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS);
-
-    this.content.setAttribute("enabled", this.enabled);
-
-    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
-      this.appMenuButton.setAttribute("enabled", this.enabledInPrivateWindows);
-      this.appMenuButton.setAttribute("aria-pressed", this.enabledInPrivateWindows);
-      this.appMenuButton.setAttribute("tooltiptext", this.enabledInPrivateWindows ?
-        this.disableTooltipPB : this.enableTooltipPB);
-    } else {
-      this.appMenuButton.setAttribute("enabled", this.enabledGlobally);
-      this.appMenuButton.setAttribute("aria-pressed", this.enabledGlobally);
-      this.appMenuButton.setAttribute("tooltiptext", this.enabledGlobally ?
-        this.disableTooltip : this.enableTooltip);
-    }
-  },
-
-  enabledHistogramAdd(value) {
-    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
-      return;
-    }
-    Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
-  },
-
-  disabledPBMHistogramAdd(value) {
-    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
-      return;
-    }
-    Services.telemetry.getHistogramById("TRACKING_PROTECTION_PBM_DISABLED").add(value);
-  },
-
   eventsHistogramAdd(value) {
     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
     Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
   },
 
   shieldHistogramAdd(value) {
@@ -180,95 +309,82 @@ var TrackingProtection = {
   },
 
   onSecurityChange(state, webProgress, isSimulated) {
     let baseURI = this._baseURIForChannelClassifier;
 
     // Don't deal with about:, file: etc.
     if (!baseURI) {
       this.cancelAnimation();
-      this.iconBox.removeAttribute("state");
+      this.iconBox.removeAttribute("active");
+      this.iconBox.removeAttribute("hasException");
       return;
     }
 
     // The user might have navigated before the shield animation
     // finished. In this case, reset the animation to be able to
     // play it in full again and avoid choppiness.
     if (webProgress.isTopLevel) {
       this.cancelAnimation();
     }
 
     let isBlocking = state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
     let isAllowing = state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT;
+    let detected = isBlocking || isAllowing;
+
+    // We consider the shield state as "active" when any kind of blocking-related
+    // activity occurs on the page (blocking or allowing). Since we have several
+    // content blockers, we need to go through them individually to figure out which
+    // ones are actually turned on or off.
+    // This state will be overriden later if there's an exception set for this site.
+    let active = this.enabled && detected;
+
+    for (let blocker of this.blockers) {
+      blocker.categoryItem.classList.toggle("blocked", this.enabled && blocker.enabled);
+    }
 
     // Check whether the user has added an exception for this site.
     let hasException = false;
     if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
       hasException = PrivateBrowsingUtils.existsInTrackingAllowlist(baseURI);
     } else {
       hasException = Services.perms.testExactPermission(baseURI,
         "trackingprotection") == Services.perms.ALLOW_ACTION;
     }
 
-    if (hasException) {
-      this.iconBox.setAttribute("hasException", "true");
-      this.content.setAttribute("hasException", "true");
-    } else {
-      this.iconBox.removeAttribute("hasException");
-      this.content.removeAttribute("hasException");
-    }
+    this.content.toggleAttribute("detected", detected);
+    this.content.toggleAttribute("hasException", hasException);
 
-    if (isBlocking && this.enabled) {
-      if (isSimulated) {
-        this.cancelAnimation();
-      } else if (webProgress.isTopLevel) {
-        this.iconBox.setAttribute("animate", "true");
-      }
+    this.iconBox.toggleAttribute("active", active);
+    this.iconBox.toggleAttribute("hasException", this.enabled && hasException);
 
-      this.iconBox.setAttribute("tooltiptext", this.activeTooltipText);
-      this.iconBox.setAttribute("state", "blocked-tracking-content");
-      this.content.setAttribute("state", "blocked-tracking-content");
+    if (isSimulated) {
+      this.cancelAnimation();
+    } else if (active && webProgress.isTopLevel) {
+      this.iconBox.setAttribute("animate", "true");
 
       // Open the tracking protection introduction panel, if applicable.
-      if (this.enabledGlobally) {
+      if (TrackingProtection.enabledGlobally) {
         let introCount = Services.prefs.getIntPref("privacy.trackingprotection.introCount");
-        if (introCount < TrackingProtection.MAX_INTROS) {
+        if (introCount < this.MAX_INTROS) {
           Services.prefs.setIntPref("privacy.trackingprotection.introCount", ++introCount);
           Services.prefs.savePrefFile(null);
           this.showIntroPanel();
         }
       }
-
-      this.shieldHistogramAdd(2);
-    } else if (isAllowing) {
-      if (isSimulated) {
-        this.cancelAnimation();
-      } else if (webProgress.isTopLevel) {
-        this.iconBox.setAttribute("animate", "true");
-      }
+    }
 
-      // Only show the shield when TP is enabled for now.
-      if (this.enabled) {
-        this.iconBox.setAttribute("tooltiptext", this.disabledTooltipText);
-        this.iconBox.setAttribute("state", "loaded-tracking-content");
-        this.shieldHistogramAdd(1);
-      } else {
-        this.iconBox.removeAttribute("tooltiptext");
-        this.iconBox.removeAttribute("state");
-        this.shieldHistogramAdd(0);
-      }
-
-      // Warn in the control center even with TP disabled.
-      this.content.setAttribute("state", "loaded-tracking-content");
+    if (hasException) {
+      this.iconBox.setAttribute("tooltiptext", this.disabledTooltipText);
+      this.shieldHistogramAdd(1);
+    } else if (active) {
+      this.iconBox.setAttribute("tooltiptext", this.activeTooltipText);
+      this.shieldHistogramAdd(2);
     } else {
       this.iconBox.removeAttribute("tooltiptext");
-      this.iconBox.removeAttribute("state");
-      this.content.removeAttribute("state");
-
-      // We didn't show the shield
       this.shieldHistogramAdd(0);
     }
 
     // Telemetry for state change.
     this.eventsHistogramAdd(0);
   },
 
   disableForCurrentPage() {
@@ -306,17 +422,17 @@ var TrackingProtection = {
     this.eventsHistogramAdd(2);
 
     this.hideIdentityPopupAndReload();
   },
 
   dontShowIntroPanelAgain() {
     // This function may be called in private windows, but it does not change
     // any preference unless Tracking Protection is enabled globally.
-    if (this.enabledGlobally) {
+    if (TrackingProtection.enabledGlobally) {
       Services.prefs.setIntPref("privacy.trackingprotection.introCount",
                                 this.MAX_INTROS);
       Services.prefs.savePrefFile(null);
     }
   },
 
   async showIntroPanel() {
     let brandBundle = document.getElementById("bundle_brand");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1308,17 +1308,17 @@ var gBrowserInit = {
     // loading the frame script to ensure that we don't miss any
     // message sent between when the frame script is loaded and when
     // the listener is registered.
     DOMEventHandler.init();
     gPageStyleMenu.init();
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
-    TrackingProtection.init();
+    ContentBlocking.init();
     CaptivePortalWatcher.init();
     ZoomUI.init(window);
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://global/content/content-HybridContentTelemetry.js", true);
 
@@ -1869,17 +1869,17 @@ var gBrowserInit = {
     TabletModeUpdater.uninit();
 
     gTabletModePageCounter.finish();
 
     BrowserOnClick.uninit();
 
     FeedHandler.uninit();
 
-    TrackingProtection.uninit();
+    ContentBlocking.uninit();
 
     CaptivePortalWatcher.uninit();
 
     SidebarUI.uninit();
 
     DownloadsButton.uninit();
 
     gAccessibilityServiceIndicator.uninit();
@@ -4859,17 +4859,17 @@ var XULBrowserWindow = {
     // Make sure the "https" part of the URL is striked out or not,
     // depending on the current mixed active content blocking state.
     gURLBar.formatValue();
 
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
     gIdentityHandler.updateIdentity(this._state, uri);
-    TrackingProtection.onSecurityChange(this._state, aWebProgress, aIsSimulated);
+    ContentBlocking.onSecurityChange(this._state, aWebProgress, aIsSimulated);
   },
 
   // simulate all change notifications after switching tabs
   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
     if (FullZoom.updateBackgroundTabs)
       FullZoom.onLocationChange(gBrowser.currentURI, true);
 
     CombinedStopReload.onTabSwitch();
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -10,25 +10,25 @@
 <script type="application/javascript">
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 for (let script of [
   "chrome://browser/content/browser.js",
 
   "chrome://browser/content/browser-captivePortal.js",
   "chrome://browser/content/browser-compacttheme.js",
+  "chrome://browser/content/browser-contentblocking.js",
   "chrome://browser/content/browser-feeds.js",
   "chrome://browser/content/browser-media.js",
   "chrome://browser/content/browser-pageActions.js",
   "chrome://browser/content/browser-places.js",
   "chrome://browser/content/browser-plugins.js",
   "chrome://browser/content/browser-sidebar.js",
   "chrome://browser/content/browser-siteIdentity.js",
   "chrome://browser/content/browser-tabsintitlebar.js",
-  "chrome://browser/content/browser-trackingprotection.js",
 
   "chrome://global/content/globalOverlay.js",
   "chrome://browser/content/utilityOverlay.js",
 #ifdef XP_MACOSX
   "chrome://global/content/macWindowMenu.js",
 #endif
 #ifdef MOZ_DATA_REPORTING
   "chrome://browser/content/browser-data-submission-info-bar.js",
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -435,42 +435,16 @@ support-files =
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_isActive.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_owner.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
 run-if = e10s
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_trackingUI_3.js]
-tags = trackingprotection
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_trackingUI_4.js]
-tags = trackingprotection
-support-files =
-  trackingPage.html
-  benignPage.html
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_trackingUI_5.js]
-tags = trackingprotection
-support-files =
-  trackingPage.html
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_trackingUI_6.js]
-tags = trackingprotection
-support-files =
-  file_trackingUI_6.html
-  file_trackingUI_6.js
-  file_trackingUI_6.js^headers^
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_trackingUI_telemetry.js]
-tags = trackingprotection
-support-files =
-  trackingPage.html
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_typeAheadFind.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_unknownContentType_title.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_unloaddialogs.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_utilityOverlay.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
rename from browser/base/content/test/general/benignPage.html
rename to browser/base/content/test/trackingUI/benignPage.html
--- a/browser/base/content/test/trackingUI/browser.ini
+++ b/browser/base/content/test/trackingUI/browser.ini
@@ -1,13 +1,21 @@
 [DEFAULT]
 tags = trackingprotection
 support-files =
   head.js
-  ../general/benignPage.html
-  ../general/trackingPage.html
+  benignPage.html
+  trackingPage.html
 
+[browser_trackingUI_3.js]
 [browser_trackingUI_animation.js]
+[browser_trackingUI_animation_2.js]
 [browser_trackingUI_appMenu.js]
 [browser_trackingUI_appMenu_toggle.js]
+[browser_trackingUI_fetch.js]
+support-files =
+  file_trackingUI_fetch.html
+  file_trackingUI_fetch.js
+  file_trackingUI_fetch.js^headers^
 [browser_trackingUI_open_preferences.js]
-[browser_trackingUI_reload_hint.js]
+[browser_trackingUI_pbmode_exceptions.js]
 [browser_trackingUI_state.js]
+[browser_trackingUI_telemetry.js]
rename from browser/base/content/test/general/browser_trackingUI_3.js
rename to browser/base/content/test/trackingUI/browser_trackingUI_3.js
--- a/browser/base/content/test/general/browser_trackingUI_3.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_3.js
@@ -27,17 +27,17 @@ add_task(async function testNormalBrowsi
   Services.prefs.setBoolPref(PREF, false);
   Services.prefs.setBoolPref(PB_PREF, false);
   ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)");
   Services.prefs.setBoolPref(PB_PREF, true);
   ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=true)");
 });
 
 add_task(async function testPrivateBrowsing() {
-  let privateWin = await promiseOpenAndLoadWindow({private: true}, true);
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   let TrackingProtection = privateWin.gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
 
   Services.prefs.setBoolPref(PREF, true);
   Services.prefs.setBoolPref(PB_PREF, false);
   ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)");
   Services.prefs.setBoolPref(PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)");
rename from browser/base/content/test/general/browser_trackingUI_4.js
rename to browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
--- a/browser/base/content/test/general/browser_trackingUI_4.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
@@ -1,108 +1,113 @@
 /*
- * Test that the Tracking Protection icon is properly animated in the identity
+ * Test that the Content Blocking icon is properly animated in the identity
  * block when loading tabs and switching between tabs.
  * See also Bug 1175858.
  */
 
-const PREF = "privacy.trackingprotection.enabled";
-const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
-const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
-const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
-var TrackingProtection = null;
-var tabbrowser = null;
-
-var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+const CB_PREF = "browser.contentblocking.enabled";
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 
 registerCleanupFunction(function() {
-  TrackingProtection = tabbrowser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
-  Services.prefs.clearUserPref(PREF);
-  Services.prefs.clearUserPref(PB_PREF);
-  while (gBrowser.tabs.length > 1) {
-    gBrowser.removeCurrentTab();
-  }
+  Services.prefs.clearUserPref(TP_PREF);
+  Services.prefs.clearUserPref(TP_PB_PREF);
+  Services.prefs.clearUserPref(CB_PREF);
 });
 
-function waitForSecurityChange(numChanges = 1) {
+function waitForSecurityChange(tabbrowser, numChanges = 1) {
   return new Promise(resolve => {
     let n = 0;
     let listener = {
       onSecurityChange() {
         n = n + 1;
         info("Received onSecurityChange event " + n + " of " + numChanges);
         if (n >= numChanges) {
           tabbrowser.removeProgressListener(listener);
           resolve();
         }
       }
     };
     tabbrowser.addProgressListener(listener);
   });
 }
 
-async function testTrackingProtectionAnimation() {
+async function testTrackingProtectionAnimation(tabbrowser) {
   info("Load a test page not containing tracking elements");
   let benignTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
+  let ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
 
-  ok(!TrackingProtection.iconBox.hasAttribute("state"), "iconBox: no state");
-  ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "iconBox not active");
+  ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Load a test page containing tracking elements");
   let trackingTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, TRACKING_PAGE);
 
-  ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
-  ok(TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: animate");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
+  ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
 
   info("Switch from tracking -> benign tab");
-  let securityChanged = waitForSecurityChange();
+  let securityChanged = waitForSecurityChange(tabbrowser);
   tabbrowser.selectedTab = benignTab;
   await securityChanged;
 
-  ok(!TrackingProtection.iconBox.hasAttribute("state"), "iconBox: no state");
-  ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "iconBox not active");
+  ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Switch from benign -> tracking tab");
-  securityChanged = waitForSecurityChange();
+  securityChanged = waitForSecurityChange(tabbrowser);
   tabbrowser.selectedTab = trackingTab;
   await securityChanged;
 
-  ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
-  ok(!TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: no animate");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
+  ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Reload tracking tab");
-  securityChanged = waitForSecurityChange(2);
+  securityChanged = waitForSecurityChange(tabbrowser, 2);
   tabbrowser.reload();
   await securityChanged;
 
-  ok(TrackingProtection.iconBox.hasAttribute("state"), "iconBox: state");
-  ok(TrackingProtection.iconBox.hasAttribute("animate"), "iconBox: animate");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "iconBox active");
+  ok(ContentBlocking.iconBox.hasAttribute("animate"), "iconBox animating");
+
+  while (tabbrowser.tabs.length > 1) {
+    tabbrowser.removeCurrentTab();
+  }
 }
 
 add_task(async function testNormalBrowsing() {
   await UrlClassifierTestUtils.addTestTrackers();
 
-  tabbrowser = gBrowser;
-
-  TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+  let ContentBlocking = gBrowser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the browser window");
+  let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
 
-  Services.prefs.setBoolPref(PREF, true);
+  Services.prefs.setBoolPref(TP_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+  Services.prefs.setBoolPref(CB_PREF, true);
+  ok(ContentBlocking.enabled, "CB is enabled after setting the pref");
 
-  await testTrackingProtectionAnimation();
+  await testTrackingProtectionAnimation(gBrowser);
 });
 
 add_task(async function testPrivateBrowsing() {
-  let privateWin = await promiseOpenAndLoadWindow({private: true}, true);
-  tabbrowser = privateWin.gBrowser;
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
+  let tabbrowser = privateWin.gBrowser;
 
-  TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+  let ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the private window");
+  let TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
-  Services.prefs.setBoolPref(PB_PREF, true);
+  Services.prefs.setBoolPref(TP_PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+  Services.prefs.setBoolPref(CB_PREF, true);
+  ok(TrackingProtection.enabled, "CB is enabled after setting the pref");
 
-  await testTrackingProtectionAnimation();
+  await testTrackingProtectionAnimation(tabbrowser);
 
   privateWin.close();
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_appMenu.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_appMenu.js
@@ -2,18 +2,16 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 ChromeUtils.import("resource://testing-common/CustomizableUITestUtils.jsm", this);
 
 // Test that the "Tracking Protection" button in the app menu loads about:preferences
 add_task(async function testPreferencesButton() {
-  await SpecialPowers.pushPrefEnv({set: [["privacy.trackingprotection.appMenuToggle.enabled", true]]});
-
   let cuiTestUtils = new CustomizableUITestUtils(window);
 
   await BrowserTestUtils.withNewTab(gBrowser, async function(browser) {
     await cuiTestUtils.openMainMenu();
 
     let loaded = TestUtils.waitForCondition(() => gBrowser.currentURI.spec == "about:preferences#privacy",
       "Should open about:preferences.");
     document.getElementById("appMenu-tp-label").click();
--- a/browser/base/content/test/trackingUI/browser_trackingUI_appMenu_toggle.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_appMenu_toggle.js
@@ -1,19 +1,54 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+const CB_PREF = "browser.contentblocking.enabled";
+const CB_UI_PREF = "browser.contentblocking.ui.enabled";
+
 ChromeUtils.import("resource://testing-common/CustomizableUITestUtils.jsm", this);
 
 // Test that the app menu toggle correctly flips the TP pref in
 // normal windows and private windows.
 add_task(async function testGlobalToggle() {
-  await SpecialPowers.pushPrefEnv({set: [["privacy.trackingprotection.appMenuToggle.enabled", true]]});
+  await SpecialPowers.pushPrefEnv({set: [[CB_UI_PREF, true]]});
+
+  let panelUIButton = await TestUtils.waitForCondition(() => document.getElementById("PanelUI-menu-button"));
+
+  info("Opening main menu");
+
+  let promiseShown = BrowserTestUtils.waitForEvent(PanelUI.mainView, "ViewShown");
+  panelUIButton.click();
+  await promiseShown;
+
+  info("Opened main menu");
+
+  let toggle = document.getElementById("appMenu-tp-toggle");
+
+  Services.prefs.setBoolPref(CB_PREF, false);
+  await TestUtils.waitForCondition(() => toggle.getAttribute("enabled") == "false");
+
+  Services.prefs.setBoolPref(CB_PREF, true);
+  await TestUtils.waitForCondition(() => toggle.getAttribute("enabled") == "true");
+
+  toggle.click();
+  is(Services.prefs.getBoolPref(CB_PREF), false);
+
+  toggle.click();
+  is(Services.prefs.getBoolPref(CB_PREF), true);
+
+  Services.prefs.clearUserPref(CB_PREF);
+});
+
+// Test that the app menu toggle correctly flips the TP pref in
+// normal windows and private windows.
+add_task(async function testGlobalToggleTP() {
+  await SpecialPowers.pushPrefEnv({set: [[CB_UI_PREF, false]]});
 
   async function runTest(privateWindow) {
     let win = await BrowserTestUtils.openNewBrowserWindow({private: privateWindow});
 
     let panelUIButton = await TestUtils.waitForCondition(() => win.document.getElementById("PanelUI-menu-button"));
 
     let prefName = privateWindow ? "privacy.trackingprotection.pbmode.enabled" :
       "privacy.trackingprotection.enabled";
rename from browser/base/content/test/general/browser_trackingUI_6.js
rename to browser/base/content/test/trackingUI/browser_trackingUI_fetch.js
--- a/browser/base/content/test/general/browser_trackingUI_6.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_fetch.js
@@ -1,9 +1,9 @@
-const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_trackingUI_6.html";
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/trackingUI/file_trackingUI_fetch.html";
 
 function waitForSecurityChange(numChanges = 1) {
   return new Promise(resolve => {
     let n = 0;
     let listener = {
       onSecurityChange() {
         n = n + 1;
         info("Received onSecurityChange event " + n + " of " + numChanges);
@@ -13,31 +13,32 @@ function waitForSecurityChange(numChange
         }
       }
     };
     gBrowser.addProgressListener(listener);
   });
 }
 
 add_task(async function test_fetch() {
-  await SpecialPowers.pushPrefEnv({ set: [["privacy.trackingprotection.enabled", true]] });
+  await SpecialPowers.pushPrefEnv({ set: [
+    ["privacy.trackingprotection.enabled", true],
+    ["browser.contentblocking.enabled", true],
+  ]});
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: URL }, async function(newTabBrowser) {
     let securityChange = waitForSecurityChange();
     await ContentTask.spawn(newTabBrowser, null, async function() {
       await content.wrappedJSObject.test_fetch()
                    .then(response => Assert.ok(false, "should have denied the request"))
                    .catch(e => Assert.ok(true, `Caught exception: ${e}`));
     });
     await securityChange;
 
-    var TrackingProtection = newTabBrowser.ownerGlobal.TrackingProtection;
-    ok(TrackingProtection, "got TP object");
-    ok(TrackingProtection.enabled, "TP is enabled");
+    let ContentBlocking = newTabBrowser.ownerGlobal.ContentBlocking;
+    ok(ContentBlocking, "got CB object");
+    ok(ContentBlocking.enabled, "CB is enabled");
 
-    is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
-        'content: state="blocked-tracking-content"');
-    is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
-        'iconBox: state="blocked-tracking-content"');
-    is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
+    ok(ContentBlocking.content.hasAttribute("detected"), "has detected content blocking");
+    ok(ContentBlocking.iconBox.hasAttribute("active"), "icon box is active");
+    is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
        gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
   });
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_open_preferences.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_open_preferences.js
@@ -1,14 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const PREF = "privacy.trackingprotection.enabled";
+const CB_PREF = "browser.contentblocking.enabled";
+const CB_UI_PREF = "browser.contentblocking.ui.enabled";
+const TP_PREF = "privacy.trackingprotection.enabled";
+const FB_PREF = "browser.fastblock.enabled";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 
 async function waitAndAssertPreferencesShown() {
   await BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
   await TestUtils.waitForCondition(() => gBrowser.currentURI.spec == "about:preferences#privacy",
     "Should open about:preferences.");
 
   await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() {
@@ -39,11 +42,37 @@ add_task(async function testOpenPreferen
     ok(!BrowserTestUtils.is_hidden(preferencesButton), "The enable tracking protection button is shown.");
 
     let shown = waitAndAssertPreferencesShown();
     preferencesButton.click();
     await shown;
   });
 });
 
+// Tests that clicking the contentblocking category items "add blocking" labels
+// links to about:preferences
+add_task(async function testOpenPreferencesFromAddBlockingButtons() {
+  SpecialPowers.pushPrefEnv({set: [
+    [CB_PREF, true],
+    [CB_UI_PREF, true],
+    [FB_PREF, false],
+    [TP_PREF, false],
+  ]});
+
+  await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function() {
+    let addBlockingButtons = document.querySelectorAll(".identity-popup-content-blocking-category-add-blocking");
+    for (let button of addBlockingButtons) {
+      let promisePanelOpen = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+      gIdentityHandler._identityBox.click();
+      await promisePanelOpen;
+
+      ok(BrowserTestUtils.is_visible(button), "Button is shown.");
+      let shown = waitAndAssertPreferencesShown();
+      button.click();
+      await shown;
+    }
+  });
+});
+
+
 add_task(async function cleanup() {
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
rename from browser/base/content/test/general/browser_trackingUI_5.js
rename to browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
--- a/browser/base/content/test/general/browser_trackingUI_5.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
@@ -1,22 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that sites added to the Tracking Protection whitelist in private
 // browsing mode don't persist once the private browsing window closes.
 
-const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
-const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const CB_PREF = "browser.contentblocking.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.enabled";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 var TrackingProtection = null;
+var ContentBlocking = null;
 var browser = null;
-var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
 
 registerCleanupFunction(function() {
-  TrackingProtection = browser = null;
+  Services.prefs.clearUserPref(TP_PB_PREF);
+  Services.prefs.clearUserPref(CB_PREF);
+  ContentBlocking = TrackingProtection = browser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
 
 function hidden(sel) {
   let win = browser.ownerGlobal;
   let el = win.document.querySelector(sel);
   let display = win.getComputedStyle(el).getPropertyValue("display", null);
   return display === "none";
@@ -30,103 +33,130 @@ function identityPopupState() {
 function clickButton(sel) {
   let win = browser.ownerGlobal;
   let el = win.document.querySelector(sel);
   el.doCommand();
 }
 
 function testTrackingPage(window) {
   info("Tracking content must be blocked");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
-     'content: state="blocked-tracking-content"');
-  is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
-     'iconBox: state="blocked-tracking-content"');
+  ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
+  ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
 
-  ok(!hidden("#tracking-protection-icon-box"), "icon box is visible");
+  ok(BrowserTestUtils.is_visible(ContentBlocking.iconBox), "icon box is visible");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+  ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "icon box shows no exception");
+  is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
+     gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
+
   ok(hidden("#tracking-action-block"), "blockButton is hidden");
 
-  ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
-  ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+  if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+    ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+    ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+  } else {
+    ok(!hidden("#tracking-action-unblock"), "unblockButton is visible");
+    ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
+  }
 
-  // Make sure that the blocked tracking elements message appears
-  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
-  ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
-  ok(hidden("#tracking-loaded-exception"), "labelTrackingLoadedException is hidden");
-  ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+  ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
+  ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
+
+  ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"),
+    "TP category item is not showing add blocking");
+  ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"),
+    "TP category item is set to blocked");
 }
 
 function testTrackingPageUnblocked() {
   info("Tracking content must be white-listed and not blocked");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
-     'content: state="loaded-tracking-content"');
-  is(TrackingProtection.iconBox.getAttribute("state"), "loaded-tracking-content",
-     'iconBox: state="loaded-tracking-content"');
+  ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
+  ok(ContentBlocking.content.hasAttribute("hasException"), "content shows exception");
 
-  ok(!hidden("#tracking-protection-icon-box"), "icon box is visible");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+  ok(ContentBlocking.iconBox.hasAttribute("hasException"), "shield shows exception");
+  is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
+     gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
+
+  ok(BrowserTestUtils.is_visible(ContentBlocking.iconBox), "icon box is visible");
   ok(!hidden("#tracking-action-block"), "blockButton is visible");
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+  ok(!hidden("#identity-popup-content-blocking-disabled-label"), "disabled label is visible");
 
-  // Make sure that the blocked tracking elements message appears
-  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
-  ok(!hidden("#tracking-loaded-exception"), "labelTrackingLoadedException is visible");
-  ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+  ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
+  ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
+
+  ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"),
+    "TP category item is not showing add blocking");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"),
+    "TP category item is not set to blocked");
 }
 
 add_task(async function testExceptionAddition() {
   await UrlClassifierTestUtils.addTestTrackers();
-  let privateWin = await promiseOpenAndLoadWindow({private: true}, true);
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   browser = privateWin.gBrowser;
   let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser: browser, waitForLoad: true, waitForStateStop: true });
 
+  ContentBlocking = browser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
-  await pushPrefs([PB_PREF, true]);
+  ok(TrackingProtection, "TP is attached to the private window");
 
+  Services.prefs.setBoolPref(TP_PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+  Services.prefs.setBoolPref(CB_PREF, true);
+  ok(TrackingProtection.enabled, "CB is enabled after setting the pref");
 
   info("Load a test page containing tracking elements");
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
-  is(identityPopupState(), "closed", "foobar");
+  is(identityPopupState(), "closed", "Identity popup is closed");
 
   await tabReloadPromise;
   testTrackingPageUnblocked();
 
   info("Test that the exception is remembered across tabs in the same private window");
   tab = browser.selectedTab = BrowserTestUtils.addTab(browser);
 
   info("Load a test page containing tracking elements");
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   testTrackingPageUnblocked();
 
-  await promiseWindowClosed(privateWin);
+  privateWin.close();
 });
 
 add_task(async function testExceptionPersistence() {
   info("Open another private browsing window");
-  let privateWin = await promiseOpenAndLoadWindow({private: true}, true);
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   browser = privateWin.gBrowser;
   let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser: browser, waitForLoad: true, waitForStateStop: true });
 
+  ContentBlocking = browser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
+  ok(TrackingProtection, "TP is attached to the private window");
+
+  ok(ContentBlocking.enabled, "CB is still enabled");
   ok(TrackingProtection.enabled, "TP is still enabled");
 
   info("Load a test page containing tracking elements");
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
-  is(identityPopupState(), "closed", "foobar");
+  is(identityPopupState(), "closed", "Identity popup is closed");
 
   await tabReloadPromise;
   testTrackingPageUnblocked();
 
   privateWin.close();
 });
deleted file mode 100644
--- a/browser/base/content/test/trackingUI/browser_trackingUI_reload_hint.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const PREF = "privacy.trackingprotection.enabled";
-const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
-
-// TODO: replace this once bug 1428847 is done.
-function hidden(el) {
-  let win = el.ownerGlobal;
-  let display = win.getComputedStyle(el).getPropertyValue("display", null);
-  let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
-  return display === "none" || opacity === "0";
-}
-
-add_task(async function setup() {
-  await UrlClassifierTestUtils.addTestTrackers();
-});
-
-// Tests that we show the reload hint if the user enables TP on
-// a site that has already loaded trackers before and that pressing
-// the reload button reloads the page.
-add_task(async function testReloadHint() {
-  Services.prefs.setBoolPref(PREF, false);
-
-  await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function() {
-    let promisePanelOpen = BrowserTestUtils.waitForEvent(window.gIdentityHandler._identityPopup, "popupshown");
-    window.gIdentityHandler._identityBox.click();
-    await promisePanelOpen;
-
-    let blockButton = document.getElementById("tracking-action-block");
-    let reloadButton = document.getElementById("tracking-action-reload");
-    let trackingLoaded = document.getElementById("tracking-loaded");
-    let reloadHint = document.getElementById("tracking-reload-required");
-    ok(!hidden(trackingLoaded), "The tracking loaded info is shown.");
-    ok(hidden(blockButton), "The enable tracking protection button is not shown.");
-    ok(hidden(reloadButton), "The reload button is not shown.");
-    ok(hidden(reloadHint), "The reload hint is not shown.");
-
-    Services.prefs.setBoolPref(PREF, true);
-
-    ok(hidden(blockButton), "The enable tracking protection button is not shown.");
-    ok(hidden(trackingLoaded), "The tracking loaded info is not shown.");
-    ok(!hidden(reloadButton), "The reload button is shown.");
-    ok(!hidden(reloadHint), "The reload hint is shown.");
-
-    let reloaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, TRACKING_PAGE);
-    reloadButton.click();
-    await reloaded;
-  });
-
-  Services.prefs.clearUserPref(PREF);
-});
-
-// Tests that the reload hint does not appear on a non-tracking page.
-add_task(async function testReloadHint() {
-  Services.prefs.setBoolPref(PREF, false);
-
-  await BrowserTestUtils.withNewTab("https://example.com", async function() {
-    let promisePanelOpen = BrowserTestUtils.waitForEvent(window.gIdentityHandler._identityPopup, "popupshown");
-    window.gIdentityHandler._identityBox.click();
-    await promisePanelOpen;
-
-    let trackingNotDetected = document.getElementById("tracking-not-detected");
-    let reloadButton = document.getElementById("tracking-action-reload");
-    let reloadHint = document.getElementById("tracking-reload-required");
-    ok(!hidden(trackingNotDetected), "The tracking not detected info is shown.");
-    ok(hidden(reloadButton), "The reload button is not shown.");
-    ok(hidden(reloadHint), "The reload hint is not shown.");
-
-    Services.prefs.setBoolPref(PREF, true);
-
-    ok(!hidden(trackingNotDetected), "The tracking not detected info is shown.");
-    ok(hidden(reloadButton), "The reload button is not shown.");
-    ok(hidden(reloadHint), "The reload hint is not shown.");
-  });
-
-  Services.prefs.clearUserPref(PREF);
-});
-
-add_task(async function cleanup() {
-  UrlClassifierTestUtils.cleanupTestTrackers();
-});
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js
@@ -9,31 +9,31 @@
  *     3) A page with tracking elements is loaded and they are not blocked.
  *   With TP disabled
  *     1) A page with no tracking elements is loaded.
  *     2) A page with tracking elements is loaded.
  *
  * See also Bugs 1175327, 1043801, 1178985
  */
 
-const PREF = "privacy.trackingprotection.enabled";
-const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const CB_PREF = "browser.contentblocking.enabled";
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.enabled";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
+var ContentBlocking = null;
 var TrackingProtection = null;
 var tabbrowser = null;
 
 registerCleanupFunction(function() {
-  TrackingProtection = tabbrowser = null;
+  TrackingProtection = ContentBlocking = tabbrowser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
-  Services.prefs.clearUserPref(PREF);
-  Services.prefs.clearUserPref(PB_PREF);
-  while (gBrowser.tabs.length > 1) {
-    gBrowser.removeCurrentTab();
-  }
+  Services.prefs.clearUserPref(TP_PREF);
+  Services.prefs.clearUserPref(TP_PB_PREF);
+  Services.prefs.clearUserPref(CB_PREF);
 });
 
 // This is a special version of "hidden" that doesn't check for item
 // visibility and just asserts the display and opacity attributes.
 // That way we can test elements even when their panel is hidden...
 function hidden(sel) {
   let win = tabbrowser.ownerGlobal;
   let el = win.document.querySelector(sel);
@@ -45,122 +45,140 @@ function hidden(sel) {
 function clickButton(sel) {
   let win = tabbrowser.ownerGlobal;
   let el = win.document.querySelector(sel);
   el.doCommand();
 }
 
 function testBenignPage() {
   info("Non-tracking content must not be blocked");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
-  ok(!TrackingProtection.iconBox.hasAttribute("state"), "icon box: no state");
-  ok(!TrackingProtection.iconBox.hasAttribute("tooltiptext"), "icon box: no tooltip");
+  ok(!ContentBlocking.content.hasAttribute("detected"), "no trackers are detected");
+  ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
 
-  let doc = tabbrowser.ownerGlobal.document;
-  ok(BrowserTestUtils.is_hidden(doc.getElementById("tracking-protection-icon-box")), "icon box is hidden");
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+  ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "icon box shows no exception");
+  ok(!ContentBlocking.iconBox.hasAttribute("tooltiptext"), "icon box has no tooltip");
+
+  ok(BrowserTestUtils.is_hidden(ContentBlocking.iconBox), "icon box is hidden");
   ok(hidden("#tracking-action-block"), "blockButton is hidden");
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
-  ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
+  is(!hidden("#identity-popup-content-blocking-disabled-label"), !ContentBlocking.enabled,
+    "disabled label is visible if CB is off");
 
-  // Make sure that the no tracking elements message appears
-  ok(!hidden("#tracking-not-detected"), "labelNoTracking is visible");
-  ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
-  ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+  ok(!hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is visible");
+  ok(hidden("#identity-popup-content-blocking-detected"), "blocking detected label is hidden");
+
+  ok(hidden("#identity-popup-content-blocking-category-list"), "category list is hidden");
 }
 
 function testBenignPageWithException() {
   info("Non-tracking content must not be blocked");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
-  ok(TrackingProtection.content.hasAttribute("hasException"), "content has exception attribute");
-  ok(TrackingProtection.iconBox.hasAttribute("hasException"), "icon box has exception attribute");
-  ok(!TrackingProtection.iconBox.hasAttribute("state"), "icon box: no state");
-  ok(!TrackingProtection.iconBox.hasAttribute("tooltiptext"), "icon box: no tooltip");
+  ok(!ContentBlocking.content.hasAttribute("detected"), "no trackers are detected");
+  ok(ContentBlocking.content.hasAttribute("hasException"), "content shows exception");
+
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+  is(ContentBlocking.iconBox.hasAttribute("hasException"), ContentBlocking.enabled,
+    "shield shows exception if CB is on");
+  is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
+     gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
 
-  let doc = tabbrowser.ownerGlobal.document;
-  ok(BrowserTestUtils.is_hidden(doc.getElementById("tracking-protection-icon-box")), "icon box is hidden");
-  is(!hidden("#tracking-action-block"), TrackingProtection.enabled,
-     "blockButton is visible if TP is on");
+  is(!BrowserTestUtils.is_hidden(ContentBlocking.iconBox), ContentBlocking.enabled,
+    "icon box is not hidden if CB is on");
+  is(!hidden("#tracking-action-block"), ContentBlocking.enabled,
+     "blockButton is visible if CB is on");
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
-  ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
+  ok(!hidden("#identity-popup-content-blocking-disabled-label"), "disabled label is visible");
 
-  is(!hidden("#tracking-not-detected-exception"), TrackingProtection.enabled,
-     "labelNoTrackingException is visible if TP is on");
-  is(hidden("#tracking-not-detected"), TrackingProtection.enabled,
-     "labelNoTracking is visible if TP is off");
-  ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
-  ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+  ok(!hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is visible");
+  ok(hidden("#identity-popup-content-blocking-detected"), "blocking detected label is hidden");
+
+  ok(hidden("#identity-popup-content-blocking-category-list"), "category list is hidden");
 }
 
 function testTrackingPage(window) {
   info("Tracking content must be blocked");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
-      'content: state="blocked-tracking-content"');
-  is(TrackingProtection.iconBox.getAttribute("state"), "blocked-tracking-content",
-      'icon box: state="blocked-tracking-content"');
-  is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
+  ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
+  ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
+
+  ok(BrowserTestUtils.is_visible(ContentBlocking.iconBox), "icon box is visible");
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+  ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "icon box shows no exception");
+  is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
      gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
-  ok(!TrackingProtection.content.hasAttribute("hasException"), "content has no exception attribute");
-  ok(!TrackingProtection.iconBox.hasAttribute("hasException"), "icon box has no exception attribute");
 
-  let doc = tabbrowser.ownerGlobal.document;
-  ok(BrowserTestUtils.is_visible(doc.getElementById("tracking-protection-icon-box")), "icon box is visible");
   ok(hidden("#tracking-action-block"), "blockButton is hidden");
-  ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
-
 
   if (PrivateBrowsingUtils.isWindowPrivate(window)) {
     ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
     ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
   } else {
     ok(!hidden("#tracking-action-unblock"), "unblockButton is visible");
     ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
   }
 
-  // Make sure that the blocked tracking elements message appears
-  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
-  ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
-  ok(hidden("#tracking-loaded-exception"), "labelTrackingLoadedException is hidden");
-  ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+  ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
+  ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
+
+  ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"),
+    "TP category item is not showing add blocking");
+  ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"),
+    "TP category item is set to blocked");
 }
 
 function testTrackingPageUnblocked() {
   info("Tracking content must be white-listed and not blocked");
-  is(TrackingProtection.content.hasAttribute("hasException"), TrackingProtection.enabled,
-    "content has exception attribute if TP is on");
-  is(TrackingProtection.iconBox.hasAttribute("hasException"), TrackingProtection.enabled,
-    "icon box has exception attribute if TP is on");
-  ok(!TrackingProtection.container.hidden, "The container is visible");
-  is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
-      'content: state="loaded-tracking-content"');
-  if (TrackingProtection.enabled) {
-    is(TrackingProtection.iconBox.getAttribute("state"), "loaded-tracking-content",
-        'icon box: state="loaded-tracking-content"');
-    is(TrackingProtection.iconBox.getAttribute("tooltiptext"),
-       gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
-  }
+  ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
+  ok(ContentBlocking.content.hasAttribute("hasException"), "content shows exception");
+
+  ok(ContentBlocking.iconBox.hasAttribute("active"), "shield is active");
+  ok(ContentBlocking.iconBox.hasAttribute("hasException"), "shield shows exception");
+  is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
+     gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
 
-  let doc = tabbrowser.ownerGlobal.document;
-  is(BrowserTestUtils.is_visible(doc.getElementById("tracking-protection-icon-box")), TrackingProtection.enabled, "icon box is visible if TP is on");
-  is(!hidden("#tracking-action-block"), TrackingProtection.enabled, "blockButton is visible if TP is on");
+  ok(BrowserTestUtils.is_visible(ContentBlocking.iconBox), "icon box is visible");
+  ok(!hidden("#tracking-action-block"), "blockButton is visible");
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
-  ok(!hidden("#tracking-protection-preferences-button"), "preferences button is visible");
+  ok(!hidden("#identity-popup-content-blocking-disabled-label"), "disabled label is visible");
 
-  // Make sure that the blocked tracking elements message appears
-  ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
-  is(hidden("#tracking-loaded"), TrackingProtection.enabled,
-     "labelTrackingLoaded is visible if TP is off");
-  is(!hidden("#tracking-loaded-exception"), TrackingProtection.enabled,
-     "labelTrackingLoadedException is visible if TP is on");
-  ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+  ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
+  ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
+
+  ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"),
+    "TP category item is not showing add blocking");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"),
+    "TP category item is not set to blocked");
 }
 
-async function testTrackingProtectionEnabled(tab) {
+function testTrackingPageWithCBDisabled() {
+  info("Tracking content must be white-listed and not blocked");
+  ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
+  ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
+
+  ok(!ContentBlocking.iconBox.hasAttribute("active"), "shield is not active");
+  ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "shield shows no exception");
+  ok(!ContentBlocking.iconBox.getAttribute("tooltiptext"), "icon box has no tooltip");
+
+  ok(BrowserTestUtils.is_hidden(ContentBlocking.iconBox), "icon box is hidden");
+  ok(hidden("#tracking-action-block"), "blockButton is hidden");
+  ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+  ok(!hidden("#identity-popup-content-blocking-disabled-label"), "disabled label is visible");
+
+  ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
+  ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
+
+  ok(!hidden("#identity-popup-content-blocking-category-list"), "category list is visible");
+  ok(!hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-add-blocking"),
+    "TP category item is showing add blocking");
+  ok(hidden("#identity-popup-content-blocking-category-tracking-protection > .identity-popup-content-blocking-category-state-label"),
+    "TP category item is not set to blocked");
+}
+
+async function testContentBlockingEnabled(tab) {
   info("Testing with Tracking Protection ENABLED.");
 
   info("Load a test page not containing tracking elements");
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPage();
 
   info("Load a test page not containing tracking elements which has an exception.");
   let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(tab.ownerGlobal);
@@ -179,30 +197,30 @@ async function testTrackingProtectionEna
   } else {
     Services.perms.remove(uri, "trackingprotection");
   }
 
   info("Load a test page containing tracking elements");
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   testTrackingPage(tab.ownerGlobal);
 
-  info("Disable TP for the page (which reloads the page)");
+  info("Disable CB for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   await tabReloadPromise;
   testTrackingPageUnblocked();
 
   info("Re-enable TP for the page (which reloads the page)");
   tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-block");
   await tabReloadPromise;
   testTrackingPage(tab.ownerGlobal);
 }
 
-async function testTrackingProtectionDisabled(tab) {
+async function testContentBlockingDisabled(tab) {
   info("Testing with Tracking Protection DISABLED.");
 
   info("Load a test page not containing tracking elements");
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPage();
 
   info("Load a test page not containing tracking elements which has an exception.");
   let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(tab.ownerGlobal);
@@ -219,55 +237,65 @@ async function testTrackingProtectionDis
   if (isPrivateBrowsing) {
     PrivateBrowsingUtils.removeFromTrackingAllowlist(uri);
   } else {
     Services.perms.remove(uri, "trackingprotection");
   }
 
   info("Load a test page containing tracking elements");
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
-  testTrackingPageUnblocked();
+  testTrackingPageWithCBDisabled();
 }
 
 add_task(async function testNormalBrowsing() {
   await UrlClassifierTestUtils.addTestTrackers();
 
   tabbrowser = gBrowser;
   let tab = tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser);
 
+  ContentBlocking = gBrowser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the browser window");
   TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
-  is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF),
+  is(TrackingProtection.enabled, Services.prefs.getBoolPref(TP_PREF),
      "TP.enabled is based on the original pref value");
 
-  Services.prefs.setBoolPref(PREF, true);
+  Services.prefs.setBoolPref(TP_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+  Services.prefs.setBoolPref(CB_PREF, true);
+  ok(ContentBlocking.enabled, "CB is enabled after setting the pref");
 
-  await testTrackingProtectionEnabled(tab);
+  await testContentBlockingEnabled(tab);
 
-  Services.prefs.setBoolPref(PREF, false);
-  ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+  Services.prefs.setBoolPref(CB_PREF, false);
+  ok(!ContentBlocking.enabled, "CB is disabled after setting the pref");
 
-  await testTrackingProtectionDisabled(tab);
+  await testContentBlockingDisabled(tab);
+
+  gBrowser.removeCurrentTab();
 });
 
 add_task(async function testPrivateBrowsing() {
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   tabbrowser = privateWin.gBrowser;
   let tab = tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser);
 
+  ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
+  ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
-  is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF),
+  is(TrackingProtection.enabled, Services.prefs.getBoolPref(TP_PB_PREF),
      "TP.enabled is based on the pb pref value");
 
-  Services.prefs.setBoolPref(PB_PREF, true);
+  Services.prefs.setBoolPref(TP_PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
-
-  await testTrackingProtectionEnabled(tab);
+  Services.prefs.setBoolPref(CB_PREF, true);
+  ok(TrackingProtection.enabled, "CB is enabled after setting the pref");
 
-  Services.prefs.setBoolPref(PB_PREF, false);
-  ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+  await testContentBlockingEnabled(tab);
 
-  await testTrackingProtectionDisabled(tab);
+  Services.prefs.setBoolPref(CB_PREF, false);
+  ok(!ContentBlocking.enabled, "CB is disabled after setting the pref");
+
+  await testContentBlockingDisabled(tab);
 
   privateWin.close();
 });
rename from browser/base/content/test/general/browser_trackingUI_telemetry.js
rename to browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
--- a/browser/base/content/test/general/browser_trackingUI_telemetry.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
@@ -1,16 +1,15 @@
 /*
  * Test telemetry for Tracking Protection
  */
 
 const PREF = "privacy.trackingprotection.enabled";
-const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
-const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
-const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 
 /**
  * Enable local telemetry recording for the duration of the tests.
  */
 var oldCanRecord = Services.telemetry.canRecordExtended;
 Services.telemetry.canRecordExtended = true;
 Services.prefs.setBoolPref(PREF, false);
 Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear();
@@ -47,29 +46,29 @@ function getEventCounts() {
 add_task(async function setup() {
   await UrlClassifierTestUtils.addTestTrackers();
 
   let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
   ok(!TrackingProtection.enabled, "TP is not enabled");
 
   // Open a window with TP disabled to make sure 'enabled' is logged correctly.
-  let newWin = await promiseOpenAndLoadWindow({}, true);
-  await promiseWindowClosed(newWin);
+  let newWin = await BrowserTestUtils.openNewBrowserWindow({});
+  newWin.close();
 
   is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
   is(getEnabledCounts()[1], 0, "TP was not enabled on start up");
 
   // Enable TP so the next browser to open will log 'enabled'
   Services.prefs.setBoolPref(PREF, true);
 });
 
 
 add_task(async function testNewWindow() {
-  let newWin = await promiseOpenAndLoadWindow({}, true);
+  let newWin = await BrowserTestUtils.openNewBrowserWindow({});
   let tab = await BrowserTestUtils.openNewForegroundTab(newWin.gBrowser);
   let TrackingProtection = newWin.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
 
   is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
   is(getEnabledCounts()[1], 1, "TP was enabled once on start up");
 
   // Reset these to make counting easier
@@ -106,26 +105,26 @@ add_task(async function testNewWindow() 
   tabReloadPromise = promiseTabLoadEvent(tab);
   newWin.document.querySelector("#tracking-action-block").doCommand();
   await tabReloadPromise;
   todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
   is(getEventCounts()[1], 1, "Disable actions");
   is(getEventCounts()[2], 1, "Enable actions");
   todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
 
-  await promiseWindowClosed(newWin);
+  newWin.close();
 
   // Reset these to make counting easier for the next test
   getEventsHistogram().clear();
   getShieldHistogram().clear();
   getEnabledHistogram().clear();
 });
 
 add_task(async function testPrivateBrowsing() {
-  let privateWin = await promiseOpenAndLoadWindow({private: true}, true);
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   let tab = await BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser);
   let TrackingProtection = privateWin.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
 
   // Do a bunch of actions and make sure that no telemetry data is gathered
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   let tabReloadPromise = promiseTabLoadEvent(tab);
@@ -135,10 +134,10 @@ add_task(async function testPrivateBrows
   privateWin.document.querySelector("#tracking-action-block").doCommand();
   await tabReloadPromise;
 
   // Sum up all the counts to make sure that nothing got logged
   is(getEnabledCounts().reduce((p, c) => p + c), 0, "Telemetry logging off in PB mode");
   is(getEventCounts().reduce((p, c) => p + c), 0, "Telemetry logging off in PB mode");
   is(getShieldCounts().reduce((p, c) => p + c), 0, "Telemetry logging off in PB mode");
 
-  await promiseWindowClosed(privateWin);
+  privateWin.close();
 });
rename from browser/base/content/test/general/file_trackingUI_6.html
rename to browser/base/content/test/trackingUI/file_trackingUI_fetch.html
--- a/browser/base/content/test/general/file_trackingUI_6.html
+++ b/browser/base/content/test/trackingUI/file_trackingUI_fetch.html
@@ -3,14 +3,14 @@
 <head>
   <meta charset="UTF-8">
   <title>Testing the shield from fetch and XHR</title>
 </head>
 <body>
   <p>Hello there!</p>
   <script type="application/javascript">
     function test_fetch() {
-      let url = "http://trackertest.org/browser/browser/base/content/test/general/file_trackingUI_6.js";
+      let url = "http://trackertest.org/browser/browser/base/content/test/trackingUI/file_trackingUI_fetch.js";
       return fetch(url);
     }
   </script>
 </body>
 </html>
rename from browser/base/content/test/general/file_trackingUI_6.js
rename to browser/base/content/test/trackingUI/file_trackingUI_fetch.js
rename from browser/base/content/test/general/file_trackingUI_6.js^headers^
rename to browser/base/content/test/trackingUI/file_trackingUI_fetch.js^headers^
rename from browser/base/content/test/general/trackingPage.html
rename to browser/base/content/test/trackingUI/trackingPage.html
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -31,16 +31,17 @@ browser.jar:
 *       content/browser/browser.xul                   (content/browser.xul)
         content/browser/browser-addons.js             (content/browser-addons.js)
         content/browser/browser-allTabsMenu.js        (content/browser-allTabsMenu.js)
         content/browser/browser-captivePortal.js      (content/browser-captivePortal.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
         content/browser/browser-compacttheme.js       (content/browser-compacttheme.js)
+        content/browser/browser-contentblocking.js    (content/browser-contentblocking.js)
 #ifndef MOZILLA_OFFICIAL
         content/browser/browser-development-helpers.js (content/browser-development-helpers.js)
 #endif
         content/browser/browser-feeds.js              (content/browser-feeds.js)
         content/browser/browser-fullScreenAndPointerLock.js  (content/browser-fullScreenAndPointerLock.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
         content/browser/browser-media.js              (content/browser-media.js)
@@ -48,17 +49,16 @@ browser.jar:
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-siteIdentity.js       (content/browser-siteIdentity.js)
         content/browser/browser-sync.js               (content/browser-sync.js)
         content/browser/browser-tabsintitlebar.js       (content/browser-tabsintitlebar.js)
         content/browser/browser-thumbnails.js         (content/browser-thumbnails.js)
-        content/browser/browser-trackingprotection.js (content/browser-trackingprotection.js)
         content/browser/browser-webrender.js          (content/browser-webrender.js)
         content/browser/tab-content.js                (content/tab-content.js)
         content/browser/content.js                    (content/content.js)
         content/browser/defaultthemes/1.header.jpg    (content/defaultthemes/1.header.jpg)
         content/browser/defaultthemes/1.icon.jpg      (content/defaultthemes/1.icon.jpg)
         content/browser/defaultthemes/1.preview.jpg   (content/defaultthemes/1.preview.jpg)
         content/browser/defaultthemes/2.header.jpg    (content/defaultthemes/2.header.jpg)
         content/browser/defaultthemes/2.icon.jpg      (content/defaultthemes/2.icon.jpg)
--- a/browser/components/about/AboutPrivateBrowsingHandler.jsm
+++ b/browser/components/about/AboutPrivateBrowsingHandler.jsm
@@ -32,14 +32,14 @@ var AboutPrivateBrowsingHandler = {
     switch (aMessage.name) {
       case "OpenPrivateWindow": {
         let win = aMessage.target.browser.ownerGlobal;
         win.OpenBrowserWindow({private: true});
         break;
       }
       case "DontShowIntroPanelAgain": {
         let win = aMessage.target.browser.ownerGlobal;
-        win.TrackingProtection.dontShowIntroPanelAgain();
+        win.ContentBlocking.dontShowIntroPanelAgain();
         break;
       }
     }
   },
 };
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -46,63 +46,76 @@
                 when-connection="not-secure secure secure-ev secure-cert-user-overridden"
                 oncommand="gIdentityHandler.showSecuritySubView();"/>
       </hbox>
 
       <!-- Tracking Protection Section -->
       <hbox id="tracking-protection-container"
             class="identity-popup-section"
             when-connection="not-secure secure secure-ev secure-cert-user-overridden extension">
-        <vbox id="tracking-protection-content" flex="1">
+        <vbox id="identity-popup-content-blocking-content" flex="1">
           <hbox>
-            <label id="tracking-protection-label-on"
+            <label id="tracking-protection-label"
+                   class="identity-popup-headline"
+                   flex="1">&trackingProtection.title;</label>
+            <label id="content-blocking-label"
                    class="identity-popup-headline"
-                   flex="1">&trackingProtection.on;</label>
-            <label id="tracking-protection-label-off"
-                   class="identity-popup-headline"
-                   flex="1">&trackingProtection.off;</label>
+                   flex="1">&contentBlocking.title;</label>
+            <hbox id="identity-popup-content-blocking-disabled-label">
+              <image/>
+              <label id="identity-popup-content-blocking-disabled-label-exception"
+                     tooltiptext="&contentBlocking.exception.tooltip;">&contentBlocking.disabled.label;</label>
+              <label id="identity-popup-content-blocking-disabled-label-global"
+                     tooltiptext="&contentBlocking.disabled.tooltip;">&contentBlocking.disabled.label;</label>
+            </hbox>
             <toolbarbutton id="tracking-protection-preferences-button"
                            class="identity-popup-preferences-button subviewbutton"
                            tooltiptext="&trackingProtection.tooltip;"
-                           oncommand="TrackingProtection.openPreferences('identityPopup-TP-preferencesButton');" />
+                           oncommand="ContentBlocking.openPreferences('identityPopup-TP-preferencesButton');" />
           </hbox>
 
-          <description id="tracking-blocked"
-                       crop="end">&trackingProtection.detectedBlocked4;</description>
-          <description id="tracking-loaded"
-                       crop="end">&trackingProtection.detectedNotBlocked5;</description>
-          <description id="tracking-not-detected"
-                       crop="end">&trackingProtection.notDetected5;</description>
-          <description id="tracking-loaded-exception"
-                       crop="end">&trackingProtection.detectedException;</description>
-          <description id="tracking-not-detected-exception"
-                       crop="end">&trackingProtection.notDetectedException;</description>
-          <description id="tracking-reload-required"
-                       crop="end">&trackingProtection.reloadRequired2;</description>
+          <description id="identity-popup-content-blocking-detected"
+                       crop="end">&contentBlocking.detected;</description>
+          <description id="identity-popup-content-blocking-not-detected"
+                       crop="end">&contentBlocking.notDetected;</description>
 
-          <button id="tracking-action-reload"
-                  class="tracking-protection-button"
-                  label="&trackingProtection.reload2.label;"
-                  accesskey="&trackingProtection.reload2.accesskey;"
-                  oncommand="TrackingProtection.hideIdentityPopupAndReload();" />
+          <vbox id="identity-popup-content-blocking-category-list">
+            <hbox id="identity-popup-content-blocking-category-fastblock"
+                  class="identity-popup-content-blocking-category" align="center" role="group">
+              <image class="identity-popup-content-blocking-category-icon fastblock-icon"/>
+              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.fastBlock.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.fastBlock.blocked.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
+                     onclick="ContentBlocking.openPreferences('identityPopup-CB-fastblock');">&contentBlocking.fastBlock.add.label;</label>
+            </hbox>
+            <hbox id="identity-popup-content-blocking-category-tracking-protection"
+                  class="identity-popup-content-blocking-category" align="center" role="group">
+              <image class="identity-popup-content-blocking-category-icon tracking-protection-icon"/>
+              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.trackingProtection.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.trackingProtection.blocked.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
+                     onclick="ContentBlocking.openPreferences('identityPopup-CB-tracking-protection');">&contentBlocking.trackingProtection.add.label;</label>
+            </hbox>
+          </vbox>
+
           <button id="tracking-action-unblock"
                   class="tracking-protection-button"
-                  label="&trackingProtection.unblock3.label;"
-                  accesskey="&trackingProtection.unblock3.accesskey;"
-                  oncommand="TrackingProtection.disableForCurrentPage();" />
+                  label="&trackingProtection.unblock4.label;"
+                  accesskey="&trackingProtection.unblock4.accesskey;"
+                  oncommand="ContentBlocking.disableForCurrentPage();" />
           <button id="tracking-action-unblock-private"
                   class="tracking-protection-button"
-                  label="&trackingProtection.unblockPrivate3.label;"
-                  accesskey="&trackingProtection.unblockPrivate3.accesskey;"
-                  oncommand="TrackingProtection.disableForCurrentPage();" />
+                  label="&trackingProtection.unblockPrivate4.label;"
+                  accesskey="&trackingProtection.unblockPrivate4.accesskey;"
+                  oncommand="ContentBlocking.disableForCurrentPage();" />
           <button id="tracking-action-block"
                   class="tracking-protection-button"
-                  label="&trackingProtection.block4.label;"
-                  accesskey="&trackingProtection.block4.accesskey;"
-                  oncommand="TrackingProtection.enableForCurrentPage();" />
+                  label="&trackingProtection.block5.label;"
+                  accesskey="&trackingProtection.block5.accesskey;"
+                  oncommand="ContentBlocking.enableForCurrentPage();" />
         </vbox>
       </hbox>
 
       <!-- Permissions Section -->
       <hbox class="identity-popup-section"
             when-connection="not-secure secure secure-ev secure-cert-user-overridden file extension">
         <vbox id="identity-popup-permissions-content" flex="1" role="group"
               aria-labelledby="identity-popup-permissions-headline">
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -204,26 +204,26 @@
                          oncommand="gSync.doSync();"
                          closemenu="none">
             <observes element="sync-status" attribute="syncstatus"/>
             <observes element="sync-status" attribute="tooltiptext"/>
             <observes element="sync-status" attribute="onmouseover"/>
           </toolbarbutton>
         </toolbaritem>
         <toolbarseparator class="sync-ui-item"/>
-        <toolbaritem id="appMenu-tp-container" hidden="true" closemenu="none">
+        <toolbaritem closemenu="none">
           <toolbarbutton id="appMenu-tp-label"
                          tooltiptext="&trackingProtection.tooltip;"
                          class="subviewbutton subviewbutton-iconic"
-                         oncommand="TrackingProtection.openPreferences('appMenu-trackingprotection'); PanelUI.hide();"
+                         oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection'); PanelUI.hide();"
                          label="&trackingProtection.title;"/>
           <toolbarseparator orient="vertical"/>
           <toolbarbutton id="appMenu-tp-toggle"
                          enabled="false"
-                         oncommand="TrackingProtection.onGlobalToggleCommand();" />
+                         oncommand="ContentBlocking.onGlobalToggleCommand();" />
         </toolbaritem>
         <toolbarseparator id="appMenu-tp-separator" hidden="true" />
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -529,21 +529,17 @@ var Policies = {
       }
       if (param.Locked) {
         setAndLockPref("browser.startup.homepage", homepages);
         setAndLockPref("browser.startup.page", 1);
         setAndLockPref("pref.browser.homepage.disable_button.current_page", true);
         setAndLockPref("pref.browser.homepage.disable_button.bookmark_page", true);
         setAndLockPref("pref.browser.homepage.disable_button.restore_default", true);
       } else {
-        // The default pref for homepage is actually a complex pref. We need to
-        // set it in a special way such that it works properly
-        let homepagePrefVal = "data:text/plain,browser.startup.homepage=" +
-                               homepages;
-        setDefaultPref("browser.startup.homepage", homepagePrefVal);
+        setDefaultPref("browser.startup.homepage", homepages);
         setDefaultPref("browser.startup.page", 1);
         runOncePerModification("setHomepage", homepages, () => {
           Services.prefs.clearUserPref("browser.startup.homepage");
           Services.prefs.clearUserPref("browser.startup.page");
         });
       }
     }
   },
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js
@@ -149,8 +149,19 @@ add_task(async function homepage_test_lo
                        "http://example6.com/"],
         "Locked": true
       }
     }
   });
   await check_homepage({expectedURL: "http://example4.com/|http://example5.com/|http://example6.com/",
                        locked: true});
 });
+
+add_task(async function homepage_test_anchor_link() {
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "Homepage": {
+        "URL": "http://example1.com/#test"
+      }
+    }
+  });
+  await check_homepage({expectedURL: "http://example1.com/#test"});
+});
--- a/browser/components/extensions/extension.css
+++ b/browser/components/extensions/extension.css
@@ -129,148 +129,129 @@ button.browser-style.default.pressed {
   background-color: #005bab;
   border-color: #004480;
 }
 
 button.browser-style.default.focused {
   border-color: #fff;
 }
 
-/* Radio Buttons */
-.browser-style > input[type="radio"] {
-  display: none;
-}
-
-.browser-style > input[type="radio"] + label {
+.browser-style > label {
   -moz-user-select: none;
 }
 
-.browser-style > input[type="radio"] + label::before {
+.browser-style.disabled > label {
+  color: #999;
+  opacity: .5;
+}
+
+/* Radio Buttons */
+.browser-style > input[type="radio"] {
+  -moz-appearance: none;
   background-color: #fff;
   background-position: center;
   border: 1px solid #b1b1b1;
   border-radius: 50%;
   content: "";
   display: inline-block;
   height: 16px;
   margin-right: 6px;
   vertical-align: text-top;
   width: 16px;
 }
 
-.browser-style > input[type="radio"]:hover + label::before,
-.browser-style.hover > input[type="radio"]:not(active) + label::before {
+.browser-style > input[type="radio"]:hover,
+.browser-style.hover > input[type="radio"]:not(:active) {
   background-color: #fbfbfb;
   border-color: #b1b1b1;
 }
 
-.browser-style > input[type="radio"]:hover:active + label::before,
-.browser-style.pressed > input[type="radio"]:not(active) + label::before {
+.browser-style > input[type="radio"]:hover:active,
+.browser-style.pressed > input[type="radio"]:not(:active) {
   background-color: #ebebeb;
   border-color: #858585;
 }
 
-.browser-style > input[type="radio"]:checked + label::before {
+.browser-style > input[type="radio"]:checked {
   background-color: #0996f8;
   background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iNCIgZmlsbD0iI2ZmZiIgLz4KPC9zdmc+Cg==);
   border-color: #0670cc;
 }
 
-.browser-style > input[type="radio"]:checked:hover + label::before,
-.browser-style.hover > input[type="radio"]:checked:not(active) + label::before {
+.browser-style > input[type="radio"]:checked:hover,
+.browser-style.hover > input[type="radio"]:checked:not(:active) {
   background-color: #0670cc;
   border-color: #005bab;
 }
 
-.browser-style > input[type="radio"]:checked:hover:active + label::before,
-.browser-style.pressed > input[type="radio"]:checked:not(active) + label::before {
+.browser-style > input[type="radio"]:checked:hover:active,
+.browser-style.pressed > input[type="radio"]:checked:not(:active) {
   background-color: #005bab;
   border-color: #004480;
 }
 
-.browser-style.disabled > input[type="radio"] + label,
-.browser-style.disabled > input[type="radio"]:hover + label,
-.browser-style.disabled > input[type="radio"]:hover:active + label {
-  color: #999;
-  opacity: .5;
-}
-
-.browser-style.focused > input[type="radio"] + label::before {
+.browser-style.focused > input[type="radio"] {
   border-color: #0996f8;
   box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
 }
 
-.browser-style.focused > input[type="radio"]:checked + label::before {
+.browser-style.focused > input[type="radio"]:checked {
   border-color: #fff;
 }
 
 /* Checkboxes */
 .browser-style > input[type="checkbox"] {
-  display: none;
-}
-
-.browser-style > input[type="checkbox"] + label {
-  -moz-user-select: none;
-}
-
-.browser-style > input[type="checkbox"] + label::before {
+  -moz-appearance: none;
   background-color: #fff;
   background-position: center;
   border: 1px solid #b1b1b1;
   content: "";
   display: inline-block;
   height: 16px;
   margin-right: 6px;
   vertical-align: text-top;
   width: 16px;
 }
 
-.browser-style > input[type="checkbox"]:hover + label::before,
-.browser-style.hover > input[type="checkbox"]:not(active) + label::before {
+.browser-style > input[type="checkbox"]:hover,
+.browser-style.hover > input[type="checkbox"]:not(:active) {
   background-color: #fbfbfb;
   border-color: #b1b1b1;
 }
 
-.browser-style > input[type="checkbox"]:hover:active + label::before,
-.browser-style.pressed > input[type="checkbox"]:not(active) + label::before {
+.browser-style > input[type="checkbox"]:hover:active,
+.browser-style.pressed > input[type="checkbox"]:not(:active) {
   background-color: #ebebeb;
   border-color: #858585;
 }
 
-.browser-style > input[type="checkbox"]:checked + label::before {
+.browser-style > input[type="checkbox"]:checked {
   background-color: #0996f8;
   background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNNy43LDEyLjkgQzcuNCwxMy4zIDYuOCwxMy40IDYuNCwxMyBMMy4yLDkuOCBDMi44LDkuNCAyLjgsOC42IDMuMiw4LjIgQzMuNiw3LjggNC40LDcuOCA0LjgsOC4yIEw2LjksMTAuMyBMMTEuMSw0LjQgQzExLjUsMy45IDEyLjIsMy44IDEyLjcsNC4xIEMxMy4yLDQuNSAxMy4zLDUuMiAxMyw1LjcgTDcuNywxMi45IEw3LjcsMTIuOSBaIiBmaWxsPSIjZmZmIiAvPgo8L3N2Zz4K);
   border-color: #0670cc;
 }
 
-.browser-style > input[type="checkbox"]:checked:hover + label::before,
-.browser-style.hover > input[type="checkbox"]:checked:not(active) + label::before {
+.browser-style > input[type="checkbox"]:checked:hover,
+.browser-style.hover > input[type="checkbox"]:checked:not(:active) {
   background-color: #0670cc;
   border-color: #005bab;
 }
 
-.browser-style > input[type="checkbox"]:checked:hover:active + label::before,
-.browser-style.pressed > input[type="checkbox"]:checked:not(active) + label::before {
+.browser-style > input[type="checkbox"]:checked:hover:active,
+.browser-style.pressed > input[type="checkbox"]:checked:not(:active) {
   background-color: #005bab;
   border-color: #004480;
 }
 
-.browser-style.disabled > input[type="checkbox"] + label,
-.browser-style.disabled > input[type="checkbox"]:hover + label,
-.browser-style.disabled > input[type="checkbox"]:hover:active + label {
-  color: #999;
-  opacity: .5;
-}
-
-.browser-style.focused > input[type="checkbox"] + label::before {
+.browser-style.focused > input[type="checkbox"] {
   border-color: #0996f8;
   box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
 }
 
-.browser-style.focused > input[type="checkbox"]:checked + label::before {
+.browser-style.focused > input[type="checkbox"]:checked {
   border-color: #fff;
 }
 
 /* Expander Button */
 button.browser-style.expander {
   background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==);
   background-position: center;
   background-repeat: no-repeat;
@@ -540,22 +521,22 @@ textarea.browser-style:focus:hover {
 .panel-section-tabs-button:hover {
   background-color: rgba(0, 0, 0, 0.06);
 }
 
 .panel-section-tabs-button:hover:active {
   background-color: rgba(0, 0, 0, 0.1);
 }
 
-.panel-section-tabs-button.select.browser-styleed {
+.panel-section-tabs-button.selected {
   box-shadow: 0 -1px 0 #0670cc inset, 0 -4px 0 #0996f8 inset;
   color: #0996f8;
 }
 
-.panel-section-tabs-button.select.browser-styleed:hover {
+.panel-section-tabs-button.selected:hover {
   color: #0670cc;
 }
 
 .panel-section-tabs-separator {
   background-color: rgba(0, 0, 0, 0.1);
   width: 1px;
   z-index: 99;
 }
--- a/browser/components/extensions/test/browser/browser_ext_optionsPage_browser_style.js
+++ b/browser/components/extensions/test/browser/browser_ext_optionsPage_browser_style.js
@@ -18,43 +18,44 @@ async function testOptionsBrowserStyle(o
         let buttonBackgroundColor = buttonStyle.backgroundColor;
         if (browserStyle && expected.hasBrowserStyleClass) {
           browser.test.assertEq("rgb(9, 150, 248)", buttonBackgroundColor, assertMessage);
         } else {
           browser.test.assertTrue(buttonBackgroundColor !== "rgb(9, 150, 248)", assertMessage);
         }
       }
 
-      function verifyCheckboxOrRadio(type, element, expected) {
+      function verifyCheckboxOrRadio(element, expected) {
         let style = window.getComputedStyle(element);
+        let styledBackground = element.checked ? "rgb(9, 150, 248)" : "rgb(255, 255, 255)";
         if (browserStyle && expected.hasBrowserStyleClass) {
-          browser.test.assertEq("none", style.display, `Expected ${type} item to be hidden`);
+          browser.test.assertEq(styledBackground, style.backgroundColor, assertMessage);
         } else {
-          browser.test.assertTrue(style.display != "none", `Expected ${type} item to be visible`);
+          browser.test.assertTrue(style.backgroundColor != styledBackground, assertMessage);
         }
       }
 
       let normalButton = document.getElementById("normalButton");
       let browserStyleButton = document.getElementById("browserStyleButton");
       verifyButton(normalButton, {hasBrowserStyleClass: false});
       verifyButton(browserStyleButton, {hasBrowserStyleClass: true});
 
       let normalCheckbox1 = document.getElementById("normalCheckbox1");
       let normalCheckbox2 = document.getElementById("normalCheckbox2");
       let browserStyleCheckbox = document.getElementById("browserStyleCheckbox");
-      verifyCheckboxOrRadio("checkbox", normalCheckbox1, {hasBrowserStyleClass: false});
-      verifyCheckboxOrRadio("checkbox", normalCheckbox2, {hasBrowserStyleClass: false});
-      verifyCheckboxOrRadio("checkbox", browserStyleCheckbox, {hasBrowserStyleClass: true});
+      verifyCheckboxOrRadio(normalCheckbox1, {hasBrowserStyleClass: false});
+      verifyCheckboxOrRadio(normalCheckbox2, {hasBrowserStyleClass: false});
+      verifyCheckboxOrRadio(browserStyleCheckbox, {hasBrowserStyleClass: true});
 
       let normalRadio1 = document.getElementById("normalRadio1");
       let normalRadio2 = document.getElementById("normalRadio2");
       let browserStyleRadio = document.getElementById("browserStyleRadio");
-      verifyCheckboxOrRadio("radio", normalRadio1, {hasBrowserStyleClass: false});
-      verifyCheckboxOrRadio("radio", normalRadio2, {hasBrowserStyleClass: false});
-      verifyCheckboxOrRadio("radio", browserStyleRadio, {hasBrowserStyleClass: true});
+      verifyCheckboxOrRadio(normalRadio1, {hasBrowserStyleClass: false});
+      verifyCheckboxOrRadio(normalRadio2, {hasBrowserStyleClass: false});
+      verifyCheckboxOrRadio(browserStyleRadio, {hasBrowserStyleClass: true});
 
       browser.test.notifyPass("options-ui-browser_style");
     });
     browser.test.sendMessage("options-ui-ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -13,18 +13,18 @@ skip-if = debug || asan # updateUI leaks
 [browser_no_tabs.js]
 [browser_openPreferences.js]
 [browser_openSearchPanel.js]
 skip-if = true # Bug 1113038 - Intermittent "Popup was opened"
 [browser_trackingProtection.js]
 skip-if = os == "linux" # Intermittent NS_ERROR_NOT_AVAILABLE [nsIUrlClassifierDBService.beginUpdate]
 tag = trackingprotection
 support-files =
-  !/browser/base/content/test/general/benignPage.html
-  !/browser/base/content/test/general/trackingPage.html
+  !/browser/base/content/test/trackingUI/benignPage.html
+  !/browser/base/content/test/trackingUI/trackingPage.html
 [browser_trackingProtection_tour.js]
 tag = trackingprotection
 [browser_showMenu.js]
 tag = trackingprotection
 [browser_UITour.js]
 skip-if = os == "linux" || verify # Intermittent failures, bug 951965
 [browser_UITour2.js]
 [browser_UITour3.js]
--- a/browser/components/uitour/test/browser_trackingProtection.js
+++ b/browser/components/uitour/test/browser_trackingProtection.js
@@ -1,30 +1,33 @@
 "use strict";
 
 const PREF_INTRO_COUNT = "privacy.trackingprotection.introCount";
+const PREF_CB_ENABLED = "browser.contentblocking.enabled";
 const PREF_TP_ENABLED = "privacy.trackingprotection.enabled";
-const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
-const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const TOOLTIP_PANEL = document.getElementById("UITourTooltip");
 const TOOLTIP_ANCHOR = document.getElementById("tracking-protection-icon-animatable-box");
 
 var {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
 
 registerCleanupFunction(function() {
   UrlClassifierTestUtils.cleanupTestTrackers();
+  Services.prefs.clearUserPref(PREF_CB_ENABLED);
   Services.prefs.clearUserPref(PREF_TP_ENABLED);
   Services.prefs.clearUserPref(PREF_INTRO_COUNT);
 });
 
 function allowOneIntro() {
-  Services.prefs.setIntPref(PREF_INTRO_COUNT, TrackingProtection.MAX_INTROS - 1);
+  Services.prefs.setIntPref(PREF_INTRO_COUNT, window.ContentBlocking.MAX_INTROS - 1);
 }
 
 add_task(async function setup_test() {
+  Services.prefs.setBoolPref(PREF_CB_ENABLED, true);
   Services.prefs.setBoolPref(PREF_TP_ENABLED, true);
   await UrlClassifierTestUtils.addTestTrackers();
 });
 
 add_task(async function test_benignPage() {
   info("Load a test page not containing tracking elements");
   allowOneIntro();
   await BrowserTestUtils.withNewTab({gBrowser, url: BENIGN_PAGE}, async function() {
@@ -43,17 +46,17 @@ add_task(async function test_trackingPag
   info("Load a test page containing tracking elements");
   allowOneIntro();
   await BrowserTestUtils.withNewTab({gBrowser, url: TRACKING_PAGE}, async function() {
     await new Promise((resolve, reject) => {
       waitForPopupAtAnchor(TOOLTIP_PANEL, TOOLTIP_ANCHOR, resolve,
                            "Intro panel should appear");
     });
 
-    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS, "Check intro count increased");
+    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), window.ContentBlocking.MAX_INTROS, "Check intro count increased");
 
     let step2URL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
                    "?step=2&newtab=true";
     let buttons = document.getElementById("UITourTooltipButtons");
 
     info("Click the step text and nothing should happen");
     let tabCount = gBrowser.tabs.length;
     await EventUtils.synthesizeMouseAtCenter(buttons.children[0], {});
@@ -62,17 +65,17 @@ add_task(async function test_trackingPag
     info("Resetting count to test that viewing the tour prevents future panels");
     allowOneIntro();
 
     let panelHiddenPromise = promisePanelElementHidden(window, TOOLTIP_PANEL);
     let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, step2URL);
     info("Clicking the main button");
     EventUtils.synthesizeMouseAtCenter(buttons.children[1], {});
     let tab = await tabPromise;
-    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS,
+    is(Services.prefs.getIntPref(PREF_INTRO_COUNT), window.ContentBlocking.MAX_INTROS,
        "Check intro count is at the max after opening step 2");
     is(gBrowser.tabs.length, tabCount + 1, "Tour step 2 tab opened");
     await panelHiddenPromise;
     ok(true, "Panel hid when the button was clicked");
     BrowserTestUtils.removeTab(tab);
   });
 
   info("Open another tracking page and make sure we don't show the panel again");
--- a/browser/components/uitour/test/browser_trackingProtection_tour.js
+++ b/browser/components/uitour/test/browser_trackingProtection_tour.js
@@ -24,22 +24,22 @@ add_UITour_task(async function test_unbl
   await checkToggleTarget("controlCenter-trackingUnblock");
 });
 
 add_UITour_task(function setup_block_target() {
   // Preparation for test_block_target. These are separate since the reload
   // interferes with UITour as it does a teardown. All we really care about
   // is the permission manager entry but UITour tests shouldn't rely on that
   // implementation detail.
-  TrackingProtection.disableForCurrentPage();
+  window.ContentBlocking.disableForCurrentPage();
 });
 
 add_UITour_task(async function test_block_target() {
   await checkToggleTarget("controlCenter-trackingBlock");
-  TrackingProtection.enableForCurrentPage();
+  window.ContentBlocking.enableForCurrentPage();
 });
 
 
 async function checkToggleTarget(targetID) {
   let popup = document.getElementById("UITourTooltip");
 
   let trackerOpened = new Promise(function(resolve, reject) {
     Services.obs.addObserver(function onopen(subject) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -913,39 +913,64 @@ you can use these alternative items. Oth
 
 <!ENTITY getUserMedia.selectCamera.label "Camera to share:">
 <!ENTITY getUserMedia.selectCamera.accesskey "C">
 <!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
 <!ENTITY getUserMedia.selectMicrophone.accesskey "M">
 <!ENTITY getUserMedia.audioCapture.label "Audio from the tab will be shared.">
 <!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
 
-<!ENTITY trackingProtection.on "Tracking Protection: ON">
-<!ENTITY trackingProtection.off "Tracking Protection: OFF">
+<!ENTITY contentBlocking.title "Content Blocking">
+<!ENTITY contentBlocking.detected "Blockable content detected on this site.">
+<!ENTITY contentBlocking.notDetected "No blockable content detected on this page">
+<!ENTITY contentBlocking.disabled.label "Disabled">
+<!ENTITY contentBlocking.disabled.tooltip "You have disabled Content Blocking.">
+<!ENTITY contentBlocking.exception.tooltip "You have disabled Content Blocking for this site.">
+
+<!ENTITY contentBlocking.fastBlock.label "Slow-Loading Trackers">
+<!-- LOCALIZATION NOTE (contentBlocking.fastBlock.blocked.label):
+     This label signals that this type of content blocking is turned
+     ON and is successfully blocking malicious/slow content, so this is
+     a positive thing. It forms the end of the (imaginary) sentence
+     "Slow-Loading Trackers [are] Blocked"-->
+<!ENTITY contentBlocking.fastBlock.blocked.label "Blocked">
+<!-- LOCALIZATION NOTE (contentBlocking.fastBlock.add.label):
+     This is displayed as a link to preferences, where the user can add
+     this specific type of content blocking. When this text is shown
+     the type of content blocking is currently not enabled. -->
+<!ENTITY contentBlocking.fastBlock.add.label "Add Blocking…">
+
+<!ENTITY contentBlocking.trackingProtection.label "Trackers">
+<!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.blocked.label):
+     This label signals that this type of content blocking is turned
+     ON and is successfully blocking malicious/slow content, so this is
+     a positive thing. It forms the end of the (imaginary) sentence
+     "Trackers [are] Blocked"-->
+<!ENTITY contentBlocking.trackingProtection.blocked.label "Blocked">
+<!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.add.label):
+     This is displayed as a link to preferences, where the user can add
+     this specific type of content blocking. When this text is shown
+     the type of content blocking is currently not enabled. -->
+<!ENTITY contentBlocking.trackingProtection.add.label "Add Blocking…">
+
 <!ENTITY trackingProtection.title "Tracking Protection">
 <!ENTITY trackingProtection.tooltip "Open Tracking Protection Preferences">
-<!ENTITY trackingProtection.detectedBlocked4 "&brandShortName; is blocking parts of this page that may track your browsing.">
-<!ENTITY trackingProtection.detectedNotBlocked5 "&brandShortName; has detected elements that may track your browsing.">
-<!ENTITY trackingProtection.detectedException "&brandShortName; has detected elements that may track your browsing. You have disabled protection for this site.">
-<!ENTITY trackingProtection.notDetected5 "No tracking elements detected on this page.">
-<!ENTITY trackingProtection.notDetectedException "No tracking elements detected on this page. You have disabled protection for this site.">
 
-<!ENTITY trackingProtection.reloadRequired2 "You have enabled Tracking Protection. Reload this page to block all trackers.">
 <!-- LOCALIZATION NOTE (trackingProtection.unblock3.label, trackingProtection.unblock3.accesskey):
      The associated button with this label and accesskey is only shown when opening the control
      center while looking at a site with trackers in NON-private browsing mode. -->
-<!ENTITY trackingProtection.unblock3.label "Disable For This Site">
-<!ENTITY trackingProtection.unblock3.accesskey "D">
+<!ENTITY trackingProtection.unblock4.label "Disable Blocking For This Site">
+<!ENTITY trackingProtection.unblock4.accesskey "D">
 <!-- LOCALIZATION NOTE (trackingProtection.unblockPrivate3.label, trackingProtection.unblockPrivate3.accesskey):
      The associated button with this label and accesskey is only shown when opening the control
      center while looking at a site with trackers in PRIVATE browsing mode. -->
-<!ENTITY trackingProtection.unblockPrivate3.label "Disable For This Session">
-<!ENTITY trackingProtection.unblockPrivate3.accesskey "D">
-<!ENTITY trackingProtection.block4.label "Enable For This Site">
-<!ENTITY trackingProtection.block4.accesskey "E">
+<!ENTITY trackingProtection.unblockPrivate4.label "Disable Blocking Temporarily">
+<!ENTITY trackingProtection.unblockPrivate4.accesskey "D">
+<!ENTITY trackingProtection.block5.label "Enable Blocking For This Site">
+<!ENTITY trackingProtection.block5.accesskey "E">
 <!ENTITY trackingProtection.reload2.label "Reload Page">
 <!ENTITY trackingProtection.reload2.accesskey "R">
 
 <!ENTITY pluginNotification.showAll.label "Show All">
 <!ENTITY pluginNotification.showAll.accesskey "S">
 
 <!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
 <!ENTITY pluginActivateNow.label "Allow Now">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -520,16 +520,21 @@ identity.identified.state_and_country=%S
 # use a synonym for "safe" or "private" if "secure" is too long.
 identity.notSecure.label=Not Secure
 
 identity.icon.tooltip=Show site information
 identity.extension.label=Extension (%S)
 identity.extension.tooltip=Loaded by extension: %S
 identity.showDetails.tooltip=Show connection details
 
+contentBlocking.title=Content Blocking
+contentBlocking.tooltip=Open Content Blocking Preferences
+contentBlocking.toggle.enable.tooltip=Enable Content Blocking
+contentBlocking.toggle.disable.tooltip=Disable Content Blocking
+
 trackingProtection.intro.title=How Tracking Protection works
 # LOCALIZATION NOTE (trackingProtection.intro.description2):
 # %S is brandShortName. This string should match the one from Step 1 of the tour
 # when it starts from the button shown when a new private window is opened.
 trackingProtection.intro.description2=When you see the shield, %S is blocking some parts of the page that could track your browsing activity.
 # LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
 trackingProtection.intro.step1of3=1 of 3
 trackingProtection.intro.nextButton.label=Next
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -65,42 +65,42 @@
   padding: 0;
   /* Set default fill for icons in the identity popup.
      Individual icons can override this. */
   fill: currentColor;
   fill-opacity: .6;
 }
 
 #identity-popup-mainView {
-  min-width: 30em;
-  max-width: 30em;
+  min-width: 33em;
+  max-width: 33em;
 }
 
 .identity-popup-section:not(:first-child) {
   border-top: 1px solid var(--panel-separator-color);
 }
 
 .identity-popup-security-content,
 #identity-popup-permissions-content,
-#tracking-protection-content {
+#identity-popup-content-blocking-content {
   background-repeat: no-repeat;
   background-position: 1em 1em;
   background-size: 24px auto;
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   fill-opacity: .6;
   padding: 0.5em 0 1em;
   /* .identity-popup-host depends on this width */
   padding-inline-start: calc(2em + 24px);
   padding-inline-end: 1em;
 }
 
 .identity-popup-security-content:-moz-locale-dir(rtl),
 #identity-popup-permissions-content:-moz-locale-dir(rtl),
-#tracking-protection-content:-moz-locale-dir(rtl) {
+#identity-popup-content-blocking-content:-moz-locale-dir(rtl) {
   background-position: calc(100% - 1em) 1em;
 }
 
 /* EXPAND BUTTON */
 
 .identity-popup-expander {
   margin: 0;
   padding: 4px 0;
@@ -142,29 +142,33 @@
 }
 
 .identity-popup-preferences-button > .toolbarbutton-text {
   display: none;
 }
 
 /* CONTENT */
 
+#identity-popup-content-blocking-disabled-label,
+.identity-popup-content-blocking-category-label,
+.identity-popup-content-blocking-category-state-label,
+.identity-popup-content-blocking-category-add-blocking,
 .identity-popup-permission-label,
 .identity-popup-permission-state-label,
 .identity-popup-security-content > description,
 #identity-popup-security-descriptions > description,
 #identity-popup-securityView-body > description,
 #identity-popup-permissions-content > description,
-#tracking-protection-content > description {
+#identity-popup-content-blocking-content > description {
   font-size: 110%;
   margin: 0;
 }
 
 #identity-popup-permissions-content > description,
-#tracking-protection-content > description {
+#identity-popup-content-blocking-content > description {
   color: var(--panel-disabled-color);
 }
 
 /* This element needs the pre-wrap because we add newlines to it in the code. */
 #identity-popup-content-supplemental {
   white-space: pre-wrap;
 }
 
@@ -177,22 +181,28 @@
   word-wrap: break-word;
   /* 1em + 2em + 24px is .identity-popup-security-content padding
    * 30em is the panel width */
   max-width: calc(30rem - 3rem - 24px - @identityPopupExpanderWidth@);
 }
 
 .identity-popup-warning-gray {
   padding-inline-start: 24px;
-  background: url(chrome://browser/skin/controlcenter/warning-gray.svg) no-repeat 0 50%;
+  background: url(chrome://browser/skin/controlcenter/warning.svg) no-repeat 0 50%;
+  fill: #808080;
+  stroke: #fff;
+  -moz-context-properties: fill, stroke;
 }
 
 .identity-popup-warning-yellow {
   padding-inline-start: 24px;
-  background: url(chrome://browser/skin/controlcenter/warning-yellow.svg) no-repeat 0 50%;
+  background: url(chrome://browser/skin/controlcenter/warning.svg) no-repeat 0 50%;
+  fill: #ffbf00;
+  stroke: #fff;
+  -moz-context-properties: fill, stroke;
 }
 
 .identity-popup-warning-gray:-moz-locale-dir(rtl),
 .identity-popup-warning-yellow:-moz-locale-dir(rtl) {
   background-position: 100% 50%;
 }
 /* SECURITY */
 .identity-popup-connection-secure {
@@ -295,69 +305,131 @@ description#identity-popup-content-verif
   margin-top: 1em;
 }
 
 #identity-popup-securityView-body > button {
   margin-inline-start: 0;
   margin-inline-end: 0;
 }
 
-/* TRACKING PROTECTION */
+/* CONTENT BLOCKING / TRACKING PROTECTION */
 
-#tracking-protection-content {
+#identity-popup-content-blocking-content {
   background-image: url("chrome://browser/skin/controlcenter/tracking-protection.svg");
 }
 
-#tracking-protection-content[enabled="false"],
-#tracking-protection-content[hasException],
-#tracking-protection-content[state="loaded-tracking-content"] {
-  background-image: url("chrome://browser/skin/controlcenter/tracking-protection-disabled.svg");
+/* We can currently show either the old tracking protection-only UI, which has "Tracking Protection"
+ * as a label, or the new content blocking UI which has a different label and also a list of
+ * categories of blockers. This rule hides elements depending on which UI we want to show */
+#identity-popup-content-blocking-content[contentBlockingUI] #tracking-protection-label,
+#identity-popup-content-blocking-content:not([contentBlockingUI]) #content-blocking-label,
+#identity-popup-content-blocking-content:not([contentBlockingUI]) > #identity-popup-content-blocking-category-list {
+  display: none;
 }
 
-/* Show the "on" label by default, except when TP is disabled or there's a local exception. */
-#tracking-protection-label-off,
-#tracking-protection-content[enabled="false"] #tracking-protection-label-on,
-#tracking-protection-content[hasException] #tracking-protection-label-on,
-#tracking-protection-content[state="loaded-tracking-content"] #tracking-protection-label-on {
+/* Disabled label */
+
+#identity-popup-content-blocking-disabled-label {
+  padding: 0px 5px;
+  border-radius: 3px;
+  margin: 5px;
+  display: none;
+  color: #fff;
+}
+
+#identity-popup-content-blocking-content[hasException] #identity-popup-content-blocking-disabled-label {
+  display: -moz-box;
+  background-color: #d7b600;
+  stroke: #d7b600;
+}
+
+#identity-popup-content-blocking-content[enabled][hasException] #identity-popup-content-blocking-disabled-label-global {
+  display: none;
+}
+
+#identity-popup-content-blocking-content:not([enabled]) #identity-popup-content-blocking-disabled-label {
+  display: -moz-box;
+  background-color: #d70022;
+  stroke: #d70022;
+}
+
+#identity-popup-content-blocking-content:not([enabled]) #identity-popup-content-blocking-disabled-label-exception {
   display: none;
 }
 
-#tracking-protection-label-on,
-#tracking-protection-content[enabled="false"] #tracking-protection-label-off,
-#tracking-protection-content[hasException] #tracking-protection-label-off,
-#tracking-protection-content[state="loaded-tracking-content"] #tracking-protection-label-off {
-  display: -moz-box;
+#identity-popup-content-blocking-disabled-label > label {
+  margin: 0;
+  line-height: 18px;
+}
+
+#identity-popup-content-blocking-disabled-label > image {
+  list-style-image: url(chrome://browser/skin/controlcenter/warning.svg);
+  fill: currentColor;
+  -moz-context-properties: fill, stroke;
+  margin-inline-end: 4px;
+  height: 13px;
+  width: 13px;
+}
+
+/* Content Blocking categories */
+
+#identity-popup-content-blocking-category-list {
+  margin-top: 10px;
+}
+
+/* Don't show the categories when no trackers were detected. */
+#identity-popup-content-blocking-content:not([detected]) > #identity-popup-content-blocking-category-list {
+  display: none;
 }
 
-#tracking-protection-content > description {
+/* Show the "detected"/"not detected" message depending on the content state. */
+#identity-popup-content-blocking-content:not([detected]) > #identity-popup-content-blocking-detected,
+#identity-popup-content-blocking-content[detected] > #identity-popup-content-blocking-not-detected {
+  display: none;
+}
+
+#identity-popup-content-blocking-content[enabled][hasException] .identity-popup-content-blocking-category-state-label,
+.identity-popup-content-blocking-category:not(.blocked) .identity-popup-content-blocking-category-state-label {
+  display: none;
+}
+
+.identity-popup-content-blocking-category.blocked .identity-popup-content-blocking-category-add-blocking {
   display: none;
 }
 
+.fastblock-icon {
+  list-style-image: url(chrome://browser/skin/controlcenter/slowtrackers.svg);
+}
+
+#identity-popup-content-blocking-category-fastblock.blocked > .fastblock-icon {
+  list-style-image: url(chrome://browser/skin/controlcenter/slowtrackers-disabled.svg);
+}
+
+.tracking-protection-icon {
+  list-style-image: url(chrome://browser/skin/controlcenter/trackers.svg);
+}
+
+#identity-popup-content-blocking-category-tracking-protection.blocked > .tracking-protection-icon {
+  list-style-image: url(chrome://browser/skin/controlcenter/trackers-disabled.svg);
+}
+
+/* Content Blocking action button */
+
 .tracking-protection-button {
   margin: 1em 0 0;
   display: none;
 }
 
-/* Show the right tracking descriptions and buttons for the corresponding state. */
-
-/* Default state / Tracking not detected */
-#tracking-protection-content:not([state]):-moz-any([enabled="false"], :not([hasException])) > #tracking-not-detected,
-#tracking-protection-content:not([state])[enabled="true"][hasException] > #tracking-not-detected-exception,
-/* Blocking tracking, offer buttons to unblock (depending on PBM). */
-#tracking-protection-content:not([hasException])[state="blocked-tracking-content"] > #tracking-blocked,
-#main-window:not([privatebrowsingmode]) #tracking-protection-content:not([hasException])[state="blocked-tracking-content"] > #tracking-action-unblock,
-#main-window[privatebrowsingmode] #tracking-protection-content[state="blocked-tracking-content"] > #tracking-action-unblock-private,
-/* Enabled and no exception but trackers loaded, probably needs a reload */
-#tracking-protection-content[enabled="true"]:not([hasException])[state="loaded-tracking-content"] > #tracking-reload-required,
-#tracking-protection-content[enabled="true"]:not([hasException])[state="loaded-tracking-content"] > #tracking-action-reload,
-/* Tracking Loaded */
-#tracking-protection-content[state="loaded-tracking-content"][enabled="false"] > #tracking-loaded,
-#tracking-protection-content[enabled="true"][hasException] > #tracking-loaded-exception,
-/* Has an exception, offer to block the site again. */
-#tracking-protection-content[enabled="true"][hasException] > #tracking-action-block {
+/* Show the right action buttons depending on content state */
+/* Offer to temporarily add an exception in private mode. */
+#main-window:not([privatebrowsingmode]) #identity-popup-content-blocking-content[enabled][detected]:not([hasException]) > #tracking-action-unblock,
+/* Offer to permanently add an exception in normal mode. */
+#main-window[privatebrowsingmode] #identity-popup-content-blocking-content[enabled][detected]:not([hasException]) > #tracking-action-unblock-private,
+/* If there's an exception just offer to remove the exception again. */
+#identity-popup-content-blocking-content[enabled][hasException] > #tracking-action-block {
   display: -moz-box;
 }
 
 /* PERMISSIONS */
 
 #identity-popup-permissions-content {
   background-image: url(chrome://browser/skin/controlcenter/permissions.svg);
   padding-bottom: 1.5em;
@@ -372,63 +444,75 @@ description#identity-popup-content-verif
 }
 
 #identity-popup-permissions-headline {
   /* Make sure the label is as tall as the icon so that the permission list
      which is aligned with the icon doesn't cover it up. */
   min-height: 24px;
 }
 
+#identity-popup-content-blocking-category-list,
 #identity-popup-permission-list {
   /* Offset the padding set on #identity-popup-permissions-content so that it
      shows up just below the section. The permission icons are 16px wide and
      should be right aligned with the section icon. */
   margin-inline-start: calc(-1em - 16px);
 }
 
+.identity-popup-content-blocking-category,
 .identity-popup-permission-item {
   min-height: 24px;
 }
 
 #identity-popup-permission-list:not(:empty) {
   margin-top: 5px;
 }
 
+.identity-popup-content-blocking-category-icon,
 .identity-popup-permission-icon {
   width: 16px;
   height: 16px;
 }
 
 .identity-popup-permission-icon.in-use {
   -moz-context-properties: fill;
   fill: rgb(224, 41, 29);
   animation: 1.5s ease in-use-blink infinite;
 }
 
 @keyframes in-use-blink {
   50% { opacity: 0; }
 }
 
+.identity-popup-content-blocking-category-label,
+.identity-popup-content-blocking-category-state-label,
 .identity-popup-permission-label,
 .identity-popup-permission-state-label {
   /* We need to align the action buttons and permission icons with the text.
      This is tricky because the icon height is defined in pixels, while the
      font height can vary with platform and system settings, and at least on
      Windows the default font metrics reserve more extra space for accents.
      This value is a good compromise for different platforms and font sizes. */
   margin-top: -0.1em;
 }
 
+.identity-popup-content-blocking-category-label,
 .identity-popup-permission-label {
   margin-inline-start: 1em;
 }
 
+.identity-popup-content-blocking-category-state-label,
+.identity-popup-content-blocking-category-add-blocking,
 .identity-popup-permission-state-label {
   margin-inline-end: 5px;
   text-align: end;
+}
+
+.identity-popup-content-blocking-category-state-label,
+.identity-popup-permission-state-label {
   color: var(--panel-disabled-color);
 }
 
 .identity-popup-permission-remove-button {
   -moz-appearance: none;
   margin: 0;
   border-width: 0;
   border-radius: 50%;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/slowtrackers-disabled.svg
@@ -0,0 +1,7 @@
+<!-- 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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path fill="context-fill" d="M14,6c-1,0-1.746,1-2.246,1.992-.047-.193-.115-.38-.176-.57L8,11H9v1.5a.5.5,0,0,0,.5.5h2a.5.5,0,0,0,.5-.5V10h2a2,2,0,0,0,0-4Z"/>
+  <path fill="context-fill" d="M14.707,1.293a1,1,0,0,0-1.414,0L9.944,4.641A5.359,5.359,0,0,0,6,3C2,3,0,6.686,0,10H1v2.5a.5.5,0,0,0,.5.5h.086l-.293.293a1,1,0,1,0,1.414,1.414l12-12A1,1,0,0,0,14.707,1.293Z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/controlcenter/trackers-disabled.svg
@@ -0,0 +1,8 @@
+<!-- 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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path fill="context-fill" d="M9.306,9.694c.071.416.132.8.15,1.1a4.938,4.938,0,0,1-.058,1,18.45,18.45,0,0,0,3.478.193c.016-.167.033-.343.052-.537.2-2.032.885-2.574,1.028-3.707a5.874,5.874,0,0,0-.223-2.475Z"/>
+  <path fill="context-fill" d="M9.236,12.767c-.069.365-.136.737-.177,1.13a1.675,1.675,0,0,0,1.579,2.079c1.235.16,1.779-.976,1.944-1.635a8.594,8.594,0,0,0,.2-1.35c-.2.005-.4.009-.606.009A18.258,18.258,0,0,1,9.236,12.767Z"/>
+  <path fill="context-fill" d="M14.707,1.293a1,1,0,0,0-1.414,0L6.547,8.039c0-.083-.008-.167,0-.249a25.267,25.267,0,0,0,.432-3.949C6.724,1.833,5.853-.177,4.414,0,2.8.2,1.766,2.521,2.045,4.742c.143,1.133.828,1.675,1.028,3.707.019.194.036.37.052.536A20.41,20.41,0,0,0,5.67,8.916L4.614,9.972c-.256.012-.514.028-.76.028-.221,0-.432,0-.636-.01A9.6,9.6,0,0,0,3.387,11.2L1.293,13.293a1,1,0,1,0,1.414,1.414l12-12A1,1,0,0,0,14.707,1.293Z"/>
+</svg>
deleted file mode 100644
--- a/browser/themes/shared/controlcenter/tracking-protection-disabled.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="context-fill" fill-opacity="context-fill-opacity">
-  <path d="M24.852 14.291c-.391 4.287-1.125 6.49-3.021 9.065A9.562 9.562 0 0 1 16 26.989a9.679 9.679 0 0 1-4.958-2.617l-1.415 1.415a11.419 11.419 0 0 0 6.261 3.207l.112.012.112-.012a11.4 11.4 0 0 0 7.33-4.452c2.12-2.879 2.977-5.42 3.4-10.07.121-1.339.151-4.013.155-6.057l-2.013 2.014c-.016 1.471-.053 2.996-.132 3.862z"/>
-  <path d="M16 24.336v-4.922l-2.921 2.921a6.513 6.513 0 0 0 2.919 2z"/>
-  <path d="M28.707 3.293a1 1 0 0 0-1.414 0l-1.576 1.576a2.59 2.59 0 0 0-.944-.377L16 2.985 7.227 4.491A2.69 2.69 0 0 0 5 7.153c-.006 2.031.007 5.681.155 7.319.349 3.823 1.007 6.221 2.4 8.554l-4.262 4.267a1 1 0 1 0 1.414 1.414l24-24a1 1 0 0 0 0-1.414zM10 8.78c.021 2.264.073 3.979.148 4.8a20.908 20.908 0 0 0 1.124 5.73l-2.244 2.248a17.451 17.451 0 0 1-1.88-7.267C7 12.676 7 8.765 7 7.159a.7.7 0 0 1 .563-.7L16 5.015l8.169 1.4L16 14.586V7.75z"/>
-</svg>
deleted file mode 100644
--- a/browser/themes/shared/controlcenter/warning-yellow.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
-     width="16" height="16" viewBox="0 0 16 16">
-  <path fill="#ffbf00" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
-  <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
-</svg>
rename from browser/themes/shared/controlcenter/warning-gray.svg
rename to browser/themes/shared/controlcenter/warning.svg
--- a/browser/themes/shared/controlcenter/warning-gray.svg
+++ b/browser/themes/shared/controlcenter/warning.svg
@@ -1,9 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!-- 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 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
-     width="16" height="16" viewBox="0 0 16 16">
-  <path fill="#808080" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
-  <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+  <path fill="context-stroke" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
 </svg>
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -149,72 +149,70 @@
 
 #tracking-protection-icon-box {
   visibility: collapse;
   overflow: hidden;
   width: 20px;
   margin-inline-end: -20px;
 }
 
-#tracking-protection-icon-box[state] {
+#tracking-protection-icon-box[active],
+#tracking-protection-icon-box[hasException] {
   margin-inline-end: 0px;
   visibility: visible;
 }
 
 #tracking-protection-icon-box[animationsenabled][animate] {
   transition: margin-left 200ms ease-out, margin-right 200ms ease-out;
 }
 
-#tracking-protection-icon-box:not([hasException])[state="blocked-tracking-content"][animationsenabled] > #tracking-protection-icon,
+#tracking-protection-icon-box:not([hasException])[active][animationsenabled] > #tracking-protection-icon,
 #tracking-protection-icon-box:not([animationsenabled]) > #tracking-protection-icon-animatable-box {
   display: none;
 }
 
 #tracking-protection-icon-box > #tracking-protection-icon-animatable-box {
   position: absolute;
   overflow: hidden;
   top: calc(50% - 10px); /* half the height of the sprite */
   margin-inline-start: 4px;
   width: 16px;
   height: 20px;
 }
 
-#tracking-protection-icon-box:not([hasException])[state="blocked-tracking-content"] #tracking-protection-icon-animatable-image {
+#tracking-protection-icon-box:not([hasException])[active] #tracking-protection-icon-animatable-image {
   background-image: url(chrome://browser/skin/tracking-protection-animation.svg);
   transform: translateX(-1232px);
   width: 1248px;
   background-size: auto;
   height: 16px;
   min-height: 20px;
   -moz-context-properties: fill, fill-opacity;
 }
 
-#tracking-protection-icon-box[state="blocked-tracking-content"] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
+#tracking-protection-icon-box[active] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
   transform: scaleX(-1) translateX(-1232px);
 }
 
-#tracking-protection-icon-box[state="blocked-tracking-content"][animate] #tracking-protection-icon-animatable-image {
+#tracking-protection-icon-box[active][animate] #tracking-protection-icon-animatable-image {
   animation-name: tp-icon-animation;
   animation-timing-function: steps(77);
   animation-duration: 3s;
   animation-fill-mode: forwards;
 }
 
-#tracking-protection-icon-box[state="blocked-tracking-content"][animate] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
+#tracking-protection-icon-box[active][animate] #tracking-protection-icon-animatable-image:-moz-locale-dir(rtl) {
   animation-name: tp-icon-animation-rtl;
 }
 
-#tracking-protection-icon-box[state="blocked-tracking-content"] > #tracking-protection-icon {
+#tracking-protection-icon-box[active] > #tracking-protection-icon {
   list-style-image: url(chrome://browser/skin/tracking-protection.svg);
 }
 
-/* Override the blocked tracking content rule for cases when the user has added an exception
- * on a different tab to signify that protection is disabled now */
-#tracking-protection-icon-box[hasException][state="blocked-tracking-content"] > #tracking-protection-icon,
-#tracking-protection-icon-box[state="loaded-tracking-content"] > #tracking-protection-icon {
+#tracking-protection-icon-box[hasException] > #tracking-protection-icon {
   list-style-image: url(chrome://browser/skin/tracking-protection-disabled.svg);
 }
 
 #urlbar[pageproxystate="invalid"] > #identity-box > #extension-icon,
 #urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon-box {
   visibility: collapse;
 }
 
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -23,21 +23,21 @@
   skin/classic/browser/addons/addon-install-installed.svg      (../shared/addons/addon-install-installed.svg)
   skin/classic/browser/addons/addon-install-warning.svg        (../shared/addons/addon-install-warning.svg)
   skin/classic/browser/controlcenter/conn-not-secure.svg       (../shared/controlcenter/conn-not-secure.svg)
   skin/classic/browser/controlcenter/connection.svg            (../shared/controlcenter/connection.svg)
   skin/classic/browser/controlcenter/mcb-disabled.svg          (../shared/controlcenter/mcb-disabled.svg)
   skin/classic/browser/controlcenter/extension.svg             (../shared/controlcenter/extension.svg)
   skin/classic/browser/controlcenter/permissions.svg           (../shared/controlcenter/permissions.svg)
   skin/classic/browser/controlcenter/slowtrackers.svg          (../shared/controlcenter/slowtrackers.svg)
+  skin/classic/browser/controlcenter/slowtrackers-disabled.svg (../shared/controlcenter/slowtrackers-disabled.svg)
   skin/classic/browser/controlcenter/trackers.svg              (../shared/controlcenter/trackers.svg)
+  skin/classic/browser/controlcenter/trackers-disabled.svg     (../shared/controlcenter/trackers-disabled.svg)
   skin/classic/browser/controlcenter/tracking-protection.svg   (../shared/controlcenter/tracking-protection.svg)
-  skin/classic/browser/controlcenter/tracking-protection-disabled.svg   (../shared/controlcenter/tracking-protection-disabled.svg)
-  skin/classic/browser/controlcenter/warning-gray.svg          (../shared/controlcenter/warning-gray.svg)
-  skin/classic/browser/controlcenter/warning-yellow.svg        (../shared/controlcenter/warning-yellow.svg)
+  skin/classic/browser/controlcenter/warning.svg               (../shared/controlcenter/warning.svg)
   skin/classic/browser/customizableui/empty-overflow-panel.png     (../shared/customizableui/empty-overflow-panel.png)
   skin/classic/browser/customizableui/empty-overflow-panel@2x.png  (../shared/customizableui/empty-overflow-panel@2x.png)
   skin/classic/browser/customizableui/density-compact.svg      (../shared/customizableui/density-compact.svg)
   skin/classic/browser/customizableui/density-normal.svg       (../shared/customizableui/density-normal.svg)
   skin/classic/browser/customizableui/density-touch.svg        (../shared/customizableui/density-touch.svg)
   skin/classic/browser/customizableui/menu-arrow.svg           (../shared/customizableui/menu-arrow.svg)
   skin/classic/browser/customizableui/whimsy.png               (../shared/customizableui/whimsy.png)
   skin/classic/browser/downloads/contentAreaDownloadsView.css  (../shared/downloads/contentAreaDownloadsView.css)
--- a/build/build-clang/clang-7-pre-linux64.json
+++ b/build/build-clang/clang-7-pre-linux64.json
@@ -1,20 +1,20 @@
 {
-    "llvm_revision": "336407",
+    "llvm_revision": "338869",
     "stages": "3",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc1",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc1",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/rc1",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/rc1",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/rc1",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/rc1",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "find_symbolizer_linux.patch",
       "rename_gcov_flush.patch"
--- a/build/build-clang/clang-7-pre-mingw.json
+++ b/build/build-clang/clang-7-pre-mingw.json
@@ -1,18 +1,18 @@
 {
-    "llvm_revision": "337146",
+    "llvm_revision": "338869",
     "stages": "3",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/rc1",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/rc1",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/rc1",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/rc1",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/rc1",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/rc1",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc"
 }
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -624,16 +624,17 @@ support-files =
   examples/simple3.js
   examples/frames.js
   examples/pause-points.js
   examples/script-mutate.js
   examples/script-switching-02.js
   examples/script-switching-01.js
   examples/times2.js
   examples/doc_rr_basic.html
+  examples/doc_rr_continuous.html
 
 [browser_dbg-asm.js]
 [browser_dbg-async-stepping.js]
 [browser_dbg-sourcemapped-breakpoint-console.js]
 skip-if = (os == "win" && ccov) # Bug 1453549
 [browser_dbg-sourcemapped-scopes.js]
 skip-if = ccov || (verify && debug && (os == 'linux')) # Bug 1441545
 [browser_dbg-sourcemapped-stepping.js]
@@ -715,8 +716,26 @@ skip-if = os == 'linux' && !asan # bug 1
 skip-if = debug || (verify && (os == 'win')) || (os == "win" && os_version == "6.1")
 [browser_dbg-tabs.js]
 [browser_dbg-tabs-pretty-print.js]
 [browser_dbg-toggling-tools.js]
 [browser_dbg-wasm-sourcemaps.js]
 skip-if = true
 [browser_dbg_rr_breakpoints-01.js]
 skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_breakpoints-02.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_breakpoints-03.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_breakpoints-04.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_breakpoints-05.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_record.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_stepping-01.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_stepping-02.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_stepping-03.js]
+skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_stepping-04.js]
+skip-if = os != "mac" || debug || !nightly_build
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-02.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test unhandled divergence while evaluating at a breakpoint with Web Replay.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_basic.html", 21);
+  await rewindToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 10);
+  await checkEvaluateInTopFrameThrows(client, "window.alert(3)");
+  await checkEvaluateInTopFrame(client, "number", 10);
+  await checkEvaluateInTopFrameThrows(client, "window.alert(3)");
+  await checkEvaluateInTopFrame(client, "number", 10);
+  await checkEvaluateInTopFrame(client, "testStepping2()", undefined);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-03.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test some issues when stepping around after hitting a breakpoint while recording.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_continuous.html", 19);
+  await resumeToLine(client, 19);
+  await reverseStepOverToLine(client, 18);
+  await checkEvaluateInTopFrame(client, "SpecialPowers.Cu.recordReplayDirective(/* AlwaysTakeTemporarySnapshots */ 3)", undefined);
+  await stepInToLine(client, 22);
+  await setBreakpoint(client, "doc_rr_continuous.html", 24);
+  await resumeToLine(client, 24);
+  await setBreakpoint(client, "doc_rr_continuous.html", 22);
+  await rewindToLine(client, 22);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-04.js
@@ -0,0 +1,36 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test navigating back to earlier breakpoints while recording, then resuming
+// recording.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_continuous.html", 14);
+  await resumeToLine(client, 14);
+  let value = await evaluateInTopFrame(client, "number");
+  await resumeToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value + 1);
+  await rewindToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value);
+  await resumeToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value + 1);
+  await resumeToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value + 2);
+  await resumeToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value + 3);
+  await rewindToLine(client, 14);
+  await checkEvaluateInTopFrame(client, "number", value + 2);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-05.js
@@ -0,0 +1,31 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test hitting breakpoints when rewinding past the point where the breakpoint
+// script was created.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+
+  // Rewind to the beginning of the recording.
+  await rewindToLine(client, undefined);
+
+  await setBreakpoint(client, "doc_rr_basic.html", 21);
+  await resumeToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 1);
+  await resumeToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 2);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_record.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test basic recording of a tab without any debugging.
+async function test() {
+  waitForExplicitFinish();
+
+  var recordingTab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = recordingTab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  await gBrowser.removeTab(recordingTab);
+
+  ok(true, "Finished");
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_stepping-01.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test basic step-over/back functionality in web replay.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_basic.html", 21);
+  await rewindToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 10);
+  await reverseStepOverToLine(client, 20);
+  await checkEvaluateInTopFrame(client, "number", 9);
+  await checkEvaluateInTopFrameThrows(client, "window.alert(3)");
+  await stepOverToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 10);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_stepping-02.js
@@ -0,0 +1,30 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test fixes for some simple stepping bugs.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_basic.html", 22);
+  await rewindToLine(client, 22);
+  await stepInToLine(client, 25);
+  await stepOverToLine(client, 26);
+  await stepOverToLine(client, 27);
+  await reverseStepInToLine(client, 33);
+  await reverseStepOverToLine(client, 32);
+  await reverseStepOutToLine(client, 26);
+  await reverseStepOverToLine(client, 25);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_stepping-03.js
@@ -0,0 +1,28 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test stepping back while recording, then resuming recording.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_continuous.html", 13);
+  await resumeToLine(client, 13);
+  let value = await evaluateInTopFrame(client, "number");
+  await reverseStepOverToLine(client, 12);
+  await checkEvaluateInTopFrame(client, "number", value - 1);
+  await resumeToLine(client, 13);
+  await resumeToLine(client, 13);
+  await checkEvaluateInTopFrame(client, "number", value + 1);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_stepping-04.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Stepping past the beginning or end of a frame should act like a step-out.
+async function test() {
+  waitForExplicitFinish();
+
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
+  await once(Services.ppmm, "RecordingFinished");
+
+  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
+  await client.interrupt();
+  await setBreakpoint(client, "doc_rr_basic.html", 21);
+  await rewindToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 10);
+  await reverseStepOverToLine(client, 20);
+  await reverseStepOverToLine(client, 12);
+
+  // After reverse-stepping out of the topmost frame we should rewind to the
+  // last breakpoint hit.
+  await reverseStepOverToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 9);
+
+  await stepOverToLine(client, 22);
+  await stepOverToLine(client, 23);
+  // Line 13 seems like it should be the next stepping point, but the column
+  // numbers reported by the JS engine and required by the pause points do not
+  // match, and we don't stop here.
+  //await stepOverToLine(client, 13);
+  await stepOverToLine(client, 17);
+  await stepOverToLine(client, 18);
+
+  // After forward-stepping out of the topmost frame we should run forward to
+  // the next breakpoint hit.
+  await stepOverToLine(client, 21);
+  await checkEvaluateInTopFrame(client, "number", 10);
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc_rr_continuous.html
@@ -0,0 +1,28 @@
+<html lang="en" dir="ltr">
+<body>
+<div id="maindiv">Hello World!</div>
+</body>
+<script>
+var number = 0;
+function f() {
+  updateNumber();
+  window.setTimeout(f, 1);
+}
+function updateNumber() {
+  number++;
+  document.getElementById("maindiv").innerHTML = "Number: " + number;
+  testStepping();
+}
+function testStepping() {
+  var a = 0;
+  testStepping2();
+  return a;
+}
+function testStepping2() {
+  var c = 0;
+  c++;
+  c--;
+}
+window.setTimeout(f, 100);
+</script>
+</html>
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3202,20 +3202,31 @@ nsFocusManager::FindOwner(nsIContent* aC
 bool
 nsFocusManager::IsHostOrSlot(nsIContent* aContent)
 {
   return aContent->GetShadowRoot() || // shadow host
          aContent->IsHTMLElement(nsGkAtoms::slot); // slot
 }
 
 int32_t
-nsFocusManager::HostOrSlotTabIndexValue(nsIContent* aContent)
+nsFocusManager::HostOrSlotTabIndexValue(nsIContent* aContent,
+                                        bool* aIsFocusable)
 {
   MOZ_ASSERT(IsHostOrSlot(aContent));
 
+  if (aIsFocusable) {
+    *aIsFocusable = false;
+    nsIFrame* frame = aContent->GetPrimaryFrame();
+    if (frame) {
+      int32_t tabIndex;
+      frame->IsFocusable(&tabIndex, 0);
+      *aIsFocusable = tabIndex >= 0;
+    }
+  }
+
   const nsAttrValue* attrVal =
     aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
   if (!attrVal) {
     return 0;
   }
 
   if (attrVal->Type() == nsAttrValue::eInteger) {
     return attrVal->GetIntegerValue();
@@ -3668,18 +3679,22 @@ nsFocusManager::GetNextTabbableContent(n
       // in the obsolete Shadow DOM specification.
       // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
       // "if ELEMENT is focusable, a shadow host, or a slot element,
       //  append ELEMENT to NAVIGATION-ORDER."
       // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
       // hosts and slots are handled before other elements.
       if (currentContent && nsDocument::IsShadowDOMEnabled(currentContent) &&
           IsHostOrSlot(currentContent)) {
-        int32_t tabIndex = HostOrSlotTabIndexValue(currentContent);
-        if (tabIndex >= 0 &&
+        bool focusableHostSlot;
+        int32_t tabIndex = HostOrSlotTabIndexValue(currentContent,
+                                                   &focusableHostSlot);
+        // Host or slot itself isn't focusable, enter its scope.
+        if (!focusableHostSlot &&
+            tabIndex >= 0 &&
             (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
           nsIContent* contentToFocus =
             GetNextTabbableContentInScope(currentContent, currentContent,
                                           aOriginalStartContent, aForward,
                                           aForward ? 1 : 0, aIgnoreTabIndex,
                                           aForDocumentNavigation,
                                           true /* aSkipOwner */);
           if (contentToFocus) {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -448,18 +448,21 @@ protected:
    * Returns true if aContent is a shadow host or slot
    */
   bool IsHostOrSlot(nsIContent* aContent);
 
   /**
    * Host and Slot elements need to be handled as if they had tabindex 0 even
    * when they don't have the attribute. This is a helper method to get the right
    * value for focus navigation.
+   * If aIsFocusable is passed, it is set to true if the element itself is
+   * focusable.
    */
-  int32_t HostOrSlotTabIndexValue(nsIContent* aContent);
+  int32_t HostOrSlotTabIndexValue(nsIContent* aContent,
+                                  bool* aIsFocusable = nullptr);
 
   /**
    * Retrieve the next tabbable element in scope owned by aOwner, using
    * focusability and tabindex to determine the tab order.
    *
    * aOwner is the owner of scope to search in.
    *
    * aStartContent is the starting point for this call of this method.
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -6251,16 +6251,48 @@ nsGlobalWindowInner::GetParentInternal()
   if (!outer) {
     // No outer window available!
     return nullptr;
   }
   return outer->GetParentInternal();
 }
 
 nsIPrincipal*
+nsGlobalWindowInner::GetTopLevelPrincipal()
+{
+  nsPIDOMWindowOuter* outerWindow = GetOuterWindowInternal();
+  if (!outerWindow) {
+    return nullptr;
+  }
+
+  nsPIDOMWindowOuter* topLevelOuterWindow = GetTopInternal();
+  if (!topLevelOuterWindow) {
+    return nullptr;
+  }
+
+  if (topLevelOuterWindow == outerWindow) {
+    return nullptr;
+  }
+
+  nsPIDOMWindowInner* topLevelInnerWindow =
+    topLevelOuterWindow->GetCurrentInnerWindow();
+  if (NS_WARN_IF(!topLevelInnerWindow)) {
+    return nullptr;
+  }
+
+  nsIPrincipal* topLevelPrincipal =
+    nsGlobalWindowInner::Cast(topLevelInnerWindow)->GetPrincipal();
+  if (NS_WARN_IF(!topLevelPrincipal)) {
+    return nullptr;
+  }
+
+  return topLevelPrincipal;
+}
+
+nsIPrincipal*
 nsGlobalWindowInner::GetTopLevelStorageAreaPrincipal()
 {
   nsPIDOMWindowOuter* outerWindow = GetParentInternal();
   if (!outerWindow) {
     // No outer window available!
     return nullptr;
   }
 
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -1207,17 +1207,22 @@ public:
   virtual bool ShouldShowFocusRing() override;
 
   // Inner windows only.
   void UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent);
 
 public:
   virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
 
-  // Get the parent principal, returns null if this is a toplevel window.
+  // Get the toplevel principal, returns null if this is a toplevel window.
+  nsIPrincipal* GetTopLevelPrincipal();
+
+  // Get the parent principal, returns null if this or the parent are not a
+  // toplevel window. This is mainly used to determine the anti-tracking storage
+  // area.
   nsIPrincipal* GetTopLevelStorageAreaPrincipal();
 
 protected:
   static void NotifyDOMWindowDestroyed(nsGlobalWindowInner* aWindow);
   void NotifyWindowIDDestroyed(const char* aTopic);
 
   static void NotifyDOMWindowFrozen(nsGlobalWindowInner* aWindow);
   static void NotifyDOMWindowThawed(nsGlobalWindowInner* aWindow);
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -301,23 +301,74 @@
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(document.activeElement, document.body.firstChild,
                   "body's first child should have focus.");
 
         host.remove();
         p.remove();
       }
 
+      function testFocusableHost() {
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        var host = document.createElement("div");
+        host.id = "host";
+        host.tabIndex = 0;
+        host.onfocus = focusLogger;
+        document.body.appendChild(host);
+
+        var slotted = document.createElement("div");
+        slotted.tabIndex = 0;
+        slotted.onfocus = focusLogger;
+        host.appendChild(slotted);
+
+        var sr0 = host.attachShadow({mode: "open"});
+        sr0.appendChild(document.createElement("slot"));
+
+        var p = document.createElement("p");
+        p.innerHTML = " <a href='#p'>link 1</a> ";
+        var a = p.firstElementChild;
+        a.onfocus = focusLogger;
+        document.body.appendChild(p);
+
+        document.body.offsetLeft;
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, host, "Should have focused host.");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, a, "Should have focused a.");
+
+        // Backwards
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, host, "Should have focused host.");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        host.remove();
+        p.remove();
+      }
+
       function runTest() {
 
         testTabbingThroughShadowDOMWithTabIndexes();
         testTabbingThroughSimpleShadowDOM();
         testTabbingThroughNestedShadowDOM();
         testTabbingThroughDisplayContentsHost();
         testTabbingThroughLightDOMShadowDOMLightDOM();
+        testFocusableHost();
 
         opener.didRunTests();
         window.close();
       }
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }
--- a/dom/canvas/test/crossorigin/mochitest.ini
+++ b/dom/canvas/test/crossorigin/mochitest.ini
@@ -7,9 +7,10 @@ support-files =
   image.png
   video.sjs
 
 [test_canvas2d_crossorigin.html]
 [test_video_crossorigin.html]
 subsuite = gpu
 [test_webgl_crossorigin_textures.html]
 subsuite = gpu
+skip-if = android_version == '24'
 
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/dom/AudioStreamTrack.h"
 #include "mozilla/dom/MediaStreamTrack.h"
 #include "mozilla/dom/VideoStreamTrack.h"
 #include "mozilla/gfx/Point.h" // IntSize
 #include "mozilla/Logging.h"
 #include "mozilla/media/MediaUtils.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Unused.h"
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "OggWriter.h"
 #include "OpusTrackEncoder.h"
 #include "TimeUnits.h"
@@ -1052,17 +1053,17 @@ MediaEncoder::Stop()
     RemoveMediaStreamTrack(mVideoTrack);
   }
 }
 
 #ifdef MOZ_WEBM_ENCODER
 bool
 MediaEncoder::IsWebMEncoderEnabled()
 {
-  return Preferences::GetBool("media.encoder.webm.enabled");
+  return StaticPrefs::MediaEncoderWebMEnabled();
 }
 #endif
 
 void
 MediaEncoder::NotifyInitialized()
 {
   MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
 
--- a/dom/media/mediacapabilities/MediaCapabilities.cpp
+++ b/dom/media/mediacapabilities/MediaCapabilities.cpp
@@ -402,17 +402,17 @@ MediaCapabilities::DecodingInfo(
   RefPtr<StrongWorkerRef> workerRef;
 
   if (NS_IsMainThread()) {
     targetThread = mParent->AbstractMainThreadFor(TaskCategory::Other);
   } else {
     WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(wp, "Must be called from a worker thread");
     targetThread = wp->HybridEventTarget();
-    RefPtr<StrongWorkerRef> strongWorkerRef = StrongWorkerRef::Create(
+    workerRef = StrongWorkerRef::Create(
       wp, "MediaCapabilities", [holder, targetThread]() {
         MOZ_ASSERT(targetThread->IsOnCurrentThread());
         holder->DisconnectIfExists();
       });
     if (NS_WARN_IF(!workerRef)) {
       // The worker is shutting down.
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -268,22 +268,24 @@ subsuite = clipboard
 [test_documentCharacterSet.html]
 [test_dom_input_event_on_htmleditor.html]
 skip-if = toolkit == 'android' # bug 1054087
 [test_dom_input_event_on_texteditor.html]
 [test_dragdrop.html]
 skip-if = os == 'android'
 [test_handle_new_lines.html]
 subsuite = clipboard
+skip-if = android_version == '24'
 [test_inline_style_cache.html]
 [test_inlineTableEditing.html]
 [test_insertParagraph_in_inline_editing_host.html]
 [test_keypress_untrusted_event.html]
 [test_middle_click_paste.html]
 subsuite = clipboard
+skip-if = android_version == '24'
 [test_objectResizing.html]
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_undo_after_spellchecker_replaces_word.html]
 skip-if = toolkit == 'android'
 [test_undo_redo_stack_after_setting_value.html]
new file mode 100644
--- /dev/null
+++ b/gfx/layers/MemoryPressureObserver.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryPressureObserver.h"
+
+namespace mozilla {
+namespace layers {
+
+MemoryPressureObserver::MemoryPressureObserver(MemoryPressureListener* aListener)
+: mListener(aListener)
+{}
+
+MemoryPressureObserver::~MemoryPressureObserver()
+{
+  // If this assertion is hit we probably forgot to unregister the observer.
+  MOZ_ASSERT(!mListener);
+}
+
+already_AddRefed<MemoryPressureObserver>
+MemoryPressureObserver::Create(MemoryPressureListener* aListener)
+{
+  nsCOMPtr<nsIObserverService> service = services::GetObserverService();
+
+  if (!service) {
+    return nullptr;
+  }
+
+  RefPtr<MemoryPressureObserver> observer = new MemoryPressureObserver(aListener);
+
+  bool useWeakRef = false;
+  service->AddObserver(observer, "memory-pressure", useWeakRef);
+
+  return observer.forget();
+}
+
+void
+MemoryPressureObserver::Unregister()
+{
+  if (!mListener) {
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> service = services::GetObserverService();
+  if (service) {
+    service->RemoveObserver(this, "memory-pressure");
+  }
+
+  mListener = nullptr;
+}
+
+NS_IMETHODIMP
+MemoryPressureObserver::Observe(nsISupports* aSubject,
+                                const char* aTopic,
+                                const char16_t* aData)
+{
+  if (mListener && strcmp(aTopic, "memory-pressure")) {
+    MemoryPressureReason reason = MemoryPressureReason::LOW_MEMORY;
+    auto reason_string = nsDependentString(aData);
+    if (StringBeginsWith(reason_string, NS_LITERAL_STRING("low-memory-ongoing"))) {
+        reason = MemoryPressureReason::LOW_MEMORY_ONGOING;
+    } else if (StringBeginsWith(reason_string, NS_LITERAL_STRING("heap-minimize"))) {
+        reason = MemoryPressureReason::HEAP_MINIMIZE;
+    }
+    mListener->OnMemoryPressure(reason);
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver)
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/MemoryPressureObserver.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_LAYERS_MEMORYPRESSUREOBSERVER_H
+#define MOZILLA_LAYERS_MEMORYPRESSUREOBSERVER_H
+
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace layers {
+
+// A simple memory pressure observer implementation born out of the realization
+// that almost all of our memory pressure observers do exactly the same thing.
+//
+// The intended way to use it is to have the class that nees to react on memory
+// pressure inherit the MemoryPressureListener interface and own a strong
+// reference to a MemoryPressureListener object.
+// Call Unregister on the listener in the destructor of your class or whenever
+// you do not which to receive the notification anymore, otherwise the listener
+// will be held alive by the observer service (leak) and keep a dangling pointer
+// to your class.
+
+/// See nsIMemory.idl
+enum class MemoryPressureReason {
+    LOW_MEMORY,
+    LOW_MEMORY_ONGOING,
+    HEAP_MINIMIZE,
+};
+
+class MemoryPressureListener {
+public:
+  virtual void OnMemoryPressure(MemoryPressureReason aWhy) = 0;
+};
+
+class MemoryPressureObserver final : public nsIObserver {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  // Returns null if anything goes wrong.
+  static already_AddRefed<MemoryPressureObserver>
+  Create(MemoryPressureListener* aListener);
+
+  void Unregister();
+
+private:
+  explicit MemoryPressureObserver(MemoryPressureListener* aListener);
+  virtual ~MemoryPressureObserver();
+  MemoryPressureListener* mListener;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
--- a/gfx/layers/ShareableCanvasRenderer.cpp
+++ b/gfx/layers/ShareableCanvasRenderer.cpp
@@ -4,18 +4,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ShareableCanvasRenderer.h"
 
 #include "GLContext.h"                  // for GLContext
 #include "GLScreenBuffer.h"             // for GLScreenBuffer
 #include "SharedSurfaceGL.h"            // for SurfaceFactory_GLTexture, etc
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
 #include "mozilla/layers/AsyncCanvasRenderer.h"
 #include "mozilla/layers/TextureClientSharedSurface.h"
+#include "mozilla/layers/CompositableForwarder.h"
+
+using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
 ShareableCanvasRenderer::ShareableCanvasRenderer()
   : mCanvasClient(nullptr)
   , mFactory(nullptr)
   , mFlags(TextureFlags::NO_FLAGS)
--- a/gfx/layers/ShareableCanvasRenderer.h
+++ b/gfx/layers/ShareableCanvasRenderer.h
@@ -11,16 +11,20 @@
 #include "CopyableCanvasRenderer.h"
 #include "mozilla/layers/CanvasClient.h"
 
 namespace mozilla {
 namespace gl {
 class SurfaceFactory;
 } // namespace gl
 
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+
 namespace layers {
 
 class ShareableCanvasRenderer : public CopyableCanvasRenderer
 {
   typedef CanvasClient::CanvasClientType CanvasClientType;
 public:
   ShareableCanvasRenderer();
   virtual ~ShareableCanvasRenderer();
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -40,62 +40,16 @@
 #include "gfxDWriteFonts.h"
 #endif
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
-void
-ClientLayerManager::MemoryPressureObserver::Destroy()
-{
-  UnregisterMemoryPressureEvent();
-  mClientLayerManager = nullptr;
-}
-
-NS_IMETHODIMP
-ClientLayerManager::MemoryPressureObserver::Observe(nsISupports* aSubject,
-                                                    const char* aTopic,
-                                                    const char16_t* aSomeData)
-{
-  if (!mClientLayerManager || strcmp(aTopic, "memory-pressure")) {
-    return NS_OK;
-  }
-
-  mClientLayerManager->HandleMemoryPressure();
-  return NS_OK;
-}
-
-void
-ClientLayerManager::MemoryPressureObserver::RegisterMemoryPressureEvent()
-{
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-
-  MOZ_ASSERT(observerService);
-
-  if (observerService) {
-    observerService->AddObserver(this, "memory-pressure", false);
-  }
-}
-
-void
-ClientLayerManager::MemoryPressureObserver::UnregisterMemoryPressureEvent()
-{
-  nsCOMPtr<nsIObserverService> observerService =
-      mozilla::services::GetObserverService();
-
-  if (observerService) {
-      observerService->RemoveObserver(this, "memory-pressure");
-  }
-}
-
-NS_IMPL_ISUPPORTS(ClientLayerManager::MemoryPressureObserver, nsIObserver)
-
 ClientLayerManager::ClientLayerManager(nsIWidget* aWidget)
   : mPhase(PHASE_NONE)
   , mWidget(aWidget)
   , mPaintedLayerCallback(nullptr)
   , mPaintedLayerCallbackData(nullptr)
   , mLatestTransactionId{0}
   , mLastPaintTime(TimeDuration::Forever())
   , mTargetRotation(ROTATION_0)
@@ -104,23 +58,23 @@ ClientLayerManager::ClientLayerManager(n
   , mTransactionIncomplete(false)
   , mCompositorMightResample(false)
   , mNeedsComposite(false)
   , mQueuedAsyncPaints(false)
   , mPaintSequenceNumber(0)
   , mForwarder(new ShadowLayerForwarder(this))
 {
   MOZ_COUNT_CTOR(ClientLayerManager);
-  mMemoryPressureObserver = new MemoryPressureObserver(this);
+  mMemoryPressureObserver = MemoryPressureObserver::Create(this);
 }
 
 
 ClientLayerManager::~ClientLayerManager()
 {
-  mMemoryPressureObserver->Destroy();
+  mMemoryPressureObserver->Unregister();
   ClearCachedResources();
   // Stop receiveing AsyncParentMessage at Forwarder.
   // After the call, the message is directly handled by LayerTransactionChild.
   // Basically this function should be called in ShadowLayerForwarder's
   // destructor. But when the destructor is triggered by
   // CompositorBridgeChild::Destroy(), the destructor can not handle it correctly.
   // See Bug 1000525.
   mForwarder->StopReceiveAsyncParentMessge();
@@ -870,17 +824,17 @@ ClientLayerManager::ClearCachedResources
   if (aSubtree) {
     ClearLayer(aSubtree);
   } else if (mRoot) {
     ClearLayer(mRoot);
   }
 }
 
 void
-ClientLayerManager::HandleMemoryPressure()
+ClientLayerManager::OnMemoryPressure(MemoryPressureReason aWhy)
 {
   if (mRoot) {
     HandleMemoryPressureLayer(mRoot);
   }
 
   if (GetCompositorBridgeChild()) {
     GetCompositorBridgeChild()->HandleMemoryPressure();
   }
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -16,16 +16,17 @@
 #include "mozilla/WidgetUtils.h"        // for ScreenRotation
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/FocusTarget.h"  // for FocusTarget
 #include "mozilla/layers/LayersTypes.h"  // for BufferMode, LayersBackend, etc
 #include "mozilla/layers/PaintThread.h" // For PaintThread
 #include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder, etc
 #include "mozilla/layers/APZTestData.h" // for APZTestData
+#include "mozilla/layers/MemoryPressureObserver.h"
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsIObserver.h"                // for nsIObserver
 #include "nsISupportsImpl.h"            // for Layer::Release, etc
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsTArray.h"                   // for nsTArray
 #include "nscore.h"                     // for nsAString
 #include "mozilla/layers/TransactionIdAllocator.h"
 #include "nsIWidget.h"                  // For plugin window configuration information structs
@@ -42,16 +43,17 @@ namespace layers {
 using dom::TabGroup;
 
 class ClientPaintedLayer;
 class CompositorBridgeChild;
 class ImageLayer;
 class FrameUniformityData;
 
 class ClientLayerManager final : public LayerManager
+                               , public MemoryPressureListener
 {
   typedef nsTArray<RefPtr<Layer> > LayerRefArray;
 
 public:
   explicit ClientLayerManager(nsIWidget* aWidget);
 
   virtual void Destroy() override;
 
@@ -144,17 +146,17 @@ public:
    */
   void StorePluginWidgetConfigurations(const nsTArray<nsIWidget::Configuration>&
                                        aConfigurations) override;
 
   // Drop cached resources and ask our shadow manager to do the same,
   // if we have one.
   virtual void ClearCachedResources(Layer* aSubtree = nullptr) override;
 
-  void HandleMemoryPressure();
+  virtual void OnMemoryPressure(MemoryPressureReason aWhy) override;
 
   void SetRepeatTransaction() { mRepeatTransaction = true; }
   bool GetRepeatTransaction() { return mRepeatTransaction; }
 
   bool IsRepeatTransaction() { return mIsRepeatTransaction; }
 
   void SetTransactionIncomplete() { mTransactionIncomplete = true; }
   void SetQueuedAsyncPaints() { mQueuedAsyncPaints = true; }
@@ -262,39 +264,16 @@ public:
 
 protected:
   enum TransactionPhase {
     PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING, PHASE_FORWARD
   };
   TransactionPhase mPhase;
 
 private:
-  // Listen memory-pressure event for ClientLayerManager
-  class MemoryPressureObserver final : public nsIObserver
-  {
-  public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-
-    explicit MemoryPressureObserver(ClientLayerManager* aClientLayerManager)
-      : mClientLayerManager(aClientLayerManager)
-    {
-      RegisterMemoryPressureEvent();
-    }
-
-    void Destroy();
-
-  private:
-    virtual ~MemoryPressureObserver() {}
-    void RegisterMemoryPressureEvent();
-    void UnregisterMemoryPressureEvent();
-
-    ClientLayerManager* mClientLayerManager;
-  };
-
   /**
    * Forward transaction results to the parent context.
    */
   void ForwardTransaction(bool aScheduleComposite);
 
   /**
    * Take a snapshot of the parent context, and copy
    * it into mShadowTarget.
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2349,16 +2349,24 @@ CompositorBridgeParent::IsSameProcess() 
 
 void
 CompositorBridgeParent::NotifyWebRenderError(wr::WebRenderError aError)
 {
   MOZ_ASSERT(CompositorLoop() == MessageLoop::current());
   Unused << SendNotifyWebRenderError(aError);
 }
 
+void
+CompositorBridgeParent::NotifyWebRenderContextPurge()
+{
+  MOZ_ASSERT(CompositorLoop() == MessageLoop::current());
+  RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI();
+  api->ClearAllCaches();
+}
+
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 //#define PLUGINS_LOG(...) printf_stderr("CP [%s]: ", __FUNCTION__);
 //                         printf_stderr(__VA_ARGS__);
 //                         printf_stderr("\n");
 #define PLUGINS_LOG(...)
 
 bool
 CompositorBridgeParent::UpdatePluginWindowState(LayersId aId)
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -269,16 +269,17 @@ public:
                                       const LayersId& aId,
                                       const uint64_t& aSerial,
                                       const wr::MaybeExternalImageId& aExternalImageId) override;
   bool DeallocPTextureParent(PTextureParent* actor) override;
 
   bool IsSameProcess() const override;
 
   void NotifyWebRenderError(wr::WebRenderError aError);
+  void NotifyWebRenderContextPurge();
   void NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                               const wr::Epoch& aEpoch,
                               TimeStamp& aCompositeStart,
                               TimeStamp& aCompositeEnd);
   RefPtr<AsyncImagePipelineManager> GetAsyncImagePipelineManager() const;
 
   PCompositorWidgetParent* AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData) override;
   bool DeallocPCompositorWidgetParent(PCompositorWidgetParent* aActor) override;
--- a/gfx/layers/ipc/SharedSurfacesChild.cpp
+++ b/gfx/layers/ipc/SharedSurfacesChild.cpp
@@ -25,30 +25,49 @@ public:
   ImageKeyData(WebRenderLayerManager* aManager,
                const wr::ImageKey& aImageKey)
     : mManager(aManager)
     , mImageKey(aImageKey)
   { }
 
   ImageKeyData(ImageKeyData&& aOther)
     : mManager(std::move(aOther.mManager))
+    , mDirtyRect(std::move(aOther.mDirtyRect))
     , mImageKey(aOther.mImageKey)
   { }
 
   ImageKeyData& operator=(ImageKeyData&& aOther)
   {
     mManager = std::move(aOther.mManager);
+    mDirtyRect = std::move(aOther.mDirtyRect);
     mImageKey = aOther.mImageKey;
     return *this;
   }
 
+  void MergeDirtyRect(const Maybe<IntRect>& aDirtyRect)
+  {
+    if (mDirtyRect) {
+      if (aDirtyRect) {
+        mDirtyRect->UnionRect(mDirtyRect.ref(), aDirtyRect.ref());
+      }
+    } else {
+      mDirtyRect = aDirtyRect;
+    }
+  }
+
+  Maybe<IntRect> TakeDirtyRect()
+  {
+    return std::move(mDirtyRect);
+  }
+
   ImageKeyData(const ImageKeyData&) = delete;
   ImageKeyData& operator=(const ImageKeyData&) = delete;
 
   RefPtr<WebRenderLayerManager> mManager;
+  Maybe<IntRect> mDirtyRect;
   wr::ImageKey mImageKey;
 };
 
 class SharedSurfacesChild::SharedUserData final
 {
 public:
   explicit SharedUserData(const wr::ExternalImageId& aId)
     : mId(aId)
@@ -138,24 +157,33 @@ public:
         MOZ_ASSERT(wrBridge);
 
         // Even if the manager is the same, its underlying WebRenderBridgeChild
         // can change state. If our namespace differs, then our old key has
         // already been discarded.
         bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace;
         if (!ownsKey) {
           entry.mImageKey = wrBridge->GetNextImageKey();
+          entry.TakeDirtyRect();
           aResources.AddExternalImage(mId, entry.mImageKey);
-        } else if (aDirtyRect) {
-          aResources.UpdateExternalImage(mId, entry.mImageKey,
-                                         ViewAs<ImagePixel>(aDirtyRect.ref()));
+        } else {
+          entry.MergeDirtyRect(aDirtyRect);
+          Maybe<IntRect> dirtyRect = entry.TakeDirtyRect();
+          if (dirtyRect) {
+            aResources.UpdateExternalImage(mId, entry.mImageKey,
+                                           ViewAs<ImagePixel>(dirtyRect.ref()));
+          }
         }
 
         key = entry.mImageKey;
         found = true;
+      } else {
+        // We don't have the resource update queue for this manager, so just
+        // accumulate the dirty rects until it is requested.
+        entry.MergeDirtyRect(aDirtyRect);
       }
     }
 
     if (!found) {
       key = aManager->WrBridge()->GetNextImageKey();
       ImageKeyData data(aManager, key);
       mKeys.AppendElement(std::move(data));
       aResources.AddExternalImage(mId, key);
@@ -311,17 +339,17 @@ SharedSurfacesChild::Share(SourceSurface
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aSurface);
   MOZ_ASSERT(aManager);
 
   // Each time the surface changes, the producers of SourceSurfaceSharedData
   // surfaces promise to increment the invalidation counter each time the
   // surface has changed. We can use this counter to determine whether or not
-  // we should upate our paired ImageKey.
+  // we should update our paired ImageKey.
   Maybe<IntRect> dirtyRect = aSurface->TakeDirtyRect();
   SharedUserData* data = nullptr;
   nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data);
   if (NS_SUCCEEDED(rv)) {
     MOZ_ASSERT(data);
     aKey = data->UpdateKey(aManager, aResources, dirtyRect);
   }
 
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -203,16 +203,17 @@ EXPORTS.mozilla.layers += [
     'ipc/UiCompositorControllerMessageTypes.h',
     'ipc/UiCompositorControllerParent.h',
     'ipc/VideoBridgeChild.h',
     'ipc/VideoBridgeParent.h',
     'LayerAttributes.h',
     'LayerMetricsWrapper.h',
     'LayersHelpers.h',
     'LayersTypes.h',
+    'MemoryPressureObserver.h',
     'mlgpu/LayerManagerMLGPU.h',
     'mlgpu/LayerMLGPU.h',
     'mlgpu/MemoryReportingMLGPU.h',
     'mlgpu/MLGDevice.h',
     'mlgpu/MLGDeviceTypes.h',
     'mlgpu/ShaderDefinitionsMLGPU.h',
     'mlgpu/UtilityMLGPU.h',
     'opengl/CompositingRenderTargetOGL.h',
@@ -435,16 +436,17 @@ UNIFIED_SOURCES += [
     'ipc/VideoBridgeParent.cpp',
     'Layers.cpp',
     'LayerScope.cpp',
     'LayersHelpers.cpp',
     'LayersLogging.cpp',
     'LayerSorter.cpp',
     'LayersTypes.cpp',
     'LayerTreeInvalidation.cpp',
+    'MemoryPressureObserver.cpp',
     'mlgpu/BufferCache.cpp',
     'mlgpu/CanvasLayerMLGPU.cpp',
     'mlgpu/ContainerLayerMLGPU.cpp',
     'mlgpu/FrameBuilder.cpp',
     'mlgpu/ImageLayerMLGPU.cpp',
     'mlgpu/LayerManagerMLGPU.cpp',
     'mlgpu/LayerMLGPU.cpp',
     'mlgpu/MaskOperation.cpp',
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -459,38 +459,23 @@ static const char* kObservedPrefs[] = {
 static void
 FontPrefChanged(const char* aPref, void* aData)
 {
     MOZ_ASSERT(aPref);
     NS_ASSERTION(gfxPlatform::GetPlatform(), "the singleton instance has gone");
     gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref);
 }
 
-class MemoryPressureObserver final : public nsIObserver
+void
+gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy)
 {
-    ~MemoryPressureObserver() = default;
-public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-};
-
-NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver)
-
-NS_IMETHODIMP
-MemoryPressureObserver::Observe(nsISupports *aSubject,
-                                const char *aTopic,
-                                const char16_t *someData)
-{
-    NS_ASSERTION(strcmp(aTopic, "memory-pressure") == 0, "unexpected event topic");
     Factory::PurgeAllCaches();
     gfxGradientCache::PurgeAllCaches();
-
-    gfxPlatform::PurgeSkiaFontCache();
-    gfxPlatform::GetPlatform()->PurgeSkiaGPUCache();
-    return NS_OK;
+    PurgeSkiaFontCache();
+    PurgeSkiaGPUCache();
 }
 
 gfxPlatform::gfxPlatform()
   : mHasVariationFontSupport(false)
   , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
   , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
   , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
   , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
@@ -814,21 +799,17 @@ gfxPlatform::Init()
 
     GLContext::PlatformStartup();
 
     Preferences::RegisterCallbackAndCall(RecordingPrefChanged, "gfx.2d.recording");
 
     CreateCMSOutputProfile();
 
     // Listen to memory pressure event so we can purge DrawTarget caches
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-        gPlatform->mMemoryPressureObserver = new MemoryPressureObserver();
-        obs->AddObserver(gPlatform->mMemoryPressureObserver, "memory-pressure", false);
-    }
+    gPlatform->mMemoryPressureObserver = layers::MemoryPressureObserver::Create(gPlatform);
 
     // Request the imgITools service, implicitly initializing ImageLib.
     nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
     if (!imgTools) {
       MOZ_CRASH("Could not initialize ImageLib");
     }
 
     RegisterStrongMemoryReporter(new GfxMemoryImageReporter());
@@ -867,16 +848,17 @@ gfxPlatform::Init()
         nsAutoString path;
         profDir->GetPath(path);
         gfxVars::SetProfDirectory(nsString(path));
       }
 
       gfxUtils::RemoveShaderCacheFromDiskIfNecessary();
     }
 
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     if (obs) {
       obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr);
     }
 }
 
 /* static*/ bool
 gfxPlatform::IsDXInterop2Blocked()
 {
@@ -983,22 +965,20 @@ gfxPlatform::Shutdown()
     /* Unregister our CMS Override callback. */
     NS_ASSERTION(gPlatform->mSRGBOverrideObserver, "mSRGBOverrideObserver has alreay gone");
     Preferences::RemoveObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);
     gPlatform->mSRGBOverrideObserver = nullptr;
 
     Preferences::UnregisterPrefixCallbacks(FontPrefChanged, kObservedPrefs);
 
     NS_ASSERTION(gPlatform->mMemoryPressureObserver, "mMemoryPressureObserver has already gone");
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-        obs->RemoveObserver(gPlatform->mMemoryPressureObserver, "memory-pressure");
+    if (gPlatform->mMemoryPressureObserver) {
+      gPlatform->mMemoryPressureObserver->Unregister();
+      gPlatform->mMemoryPressureObserver = nullptr;
     }
-
-    gPlatform->mMemoryPressureObserver = nullptr;
     gPlatform->mSkiaGlue = nullptr;
 
     if (XRE_IsParentProcess()) {
       gPlatform->mVsyncSource->Shutdown();
     }
 
     gPlatform->mVsyncSource = nullptr;
 
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -21,16 +21,17 @@
 #include "nsRect.h"
 
 #include "qcms.h"
 
 #include "mozilla/RefPtr.h"
 #include "GfxInfoCollector.h"
 
 #include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/layers/MemoryPressureObserver.h"
 
 class gfxASurface;
 class gfxFont;
 class gfxFontGroup;
 struct gfxFontStyle;
 class gfxUserFontSet;
 class gfxFontEntry;
 class gfxPlatformFontList;
@@ -150,17 +151,17 @@ enum class ForcedDeviceResetReason
 struct BackendPrefsData
 {
   uint32_t mCanvasBitmask = 0;
   mozilla::gfx::BackendType mCanvasDefault = mozilla::gfx::BackendType::NONE;
   uint32_t mContentBitmask = 0;
   mozilla::gfx::BackendType mContentDefault = mozilla::gfx::BackendType::NONE;
 };
 
-class gfxPlatform {
+class gfxPlatform: public mozilla::layers::MemoryPressureListener {
     friend class SRGBOverrideObserver;
 
 public:
     typedef mozilla::StretchRange StretchRange;
     typedef mozilla::SlantStyleRange SlantStyleRange;
     typedef mozilla::WeightRange WeightRange;
     typedef mozilla::gfx::Color Color;
     typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
@@ -742,16 +743,18 @@ public:
       return mHasNativeColrFontSupport;
     }
 
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderPrefEnabled();
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderEnvvarEnabled();
 
+    virtual void
+    OnMemoryPressure(mozilla::layers::MemoryPressureReason aWhy) override;
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
     virtual void InitAcceleration();
     virtual void InitWebRenderConfig();
 
     /**
@@ -883,17 +886,17 @@ private:
     void InitGPUProcessPrefs();
     void InitOMTPConfig();
 
     static bool IsDXInterop2Blocked();
     static bool IsDXNV12Blocked();
 
     RefPtr<gfxASurface> mScreenReferenceSurface;
     nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
-    nsCOMPtr<nsIObserver> mMemoryPressureObserver;
+    RefPtr<mozilla::layers::MemoryPressureObserver> mMemoryPressureObserver;
 
     // The preferred draw target backend to use for canvas
     mozilla::gfx::BackendType mPreferredCanvasBackend;
     // The fallback draw target backend to use for canvas, if the preferred backend fails
     mozilla::gfx::BackendType mFallbackCanvasBackend;
     // The backend to use for content
     mozilla::gfx::BackendType mContentBackend;
     // The backend to use when we need it not to be accelerated.
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -90,16 +90,22 @@ RendererOGL::Update()
     wr_renderer_set_debug_flags(mRenderer, mDebugFlags);
   }
 
   if (gl()->MakeCurrent()) {
     wr_renderer_update(mRenderer);
   }
 }
 
+static void
+DoNotifyWebRenderContextPurge(layers::CompositorBridgeParent* aBridge)
+{
+  aBridge->NotifyWebRenderContextPurge();
+}
+
 bool
 RendererOGL::UpdateAndRender(bool aReadback)
 {
   uint32_t flags = gfx::gfxVars::WebRenderDebugFlags();
   // Disable debug flags during readback
   if (aReadback) {
     flags = 0;
   }
@@ -145,16 +151,28 @@ RendererOGL::UpdateAndRender(bool aReadb
   if (mFrameStartTime) {
     uint32_t latencyMs = round((TimeStamp::Now() - mFrameStartTime).ToMilliseconds());
     printf_stderr("generate frame latencyMs latencyMs %d\n", latencyMs);
   }
   // Clear frame start time
   mFrameStartTime = TimeStamp();
 #endif
 
+  gl::GLContext* gl = mCompositor->gl();
+  if (gl->IsSupported(gl::GLFeature::robustness)) {
+    GLenum resetStatus = gl->fGetGraphicsResetStatus();
+    if (resetStatus == LOCAL_GL_PURGED_CONTEXT_RESET_NV) {
+      layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
+        "DoNotifyWebRenderContextPurgeRunnable",
+        &DoNotifyWebRenderContextPurge,
+        mBridge
+      ));
+    }
+  }
+
   // TODO: Flush pending actions such as texture deletions/unlocks and
   //       textureHosts recycling.
 
   return true;
 }
 
 void
 RendererOGL::Pause()
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -418,16 +418,22 @@ WebRenderAPI::Readback(const TimeStamp& 
     // read-back event. Then, we could make sure this read-back event gets the
     // latest result.
     RunOnRenderThread(std::move(event));
 
     task.Wait();
 }
 
 void
+WebRenderAPI::ClearAllCaches()
+{
+  wr_api_clear_all_caches(mDocHandle);
+}
+
+void
 WebRenderAPI::Pause()
 {
     class PauseEvent : public RendererEvent
     {
         public:
             explicit PauseEvent(layers::SynchronousTask* aTask)
                 : mTask(aTask)
             {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -193,16 +193,18 @@ public:
   void SendTransaction(TransactionBuilder& aTxn);
 
   void SetFrameStartTime(const TimeStamp& aTime);
 
   void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);
 
   void Readback(const TimeStamp& aStartTime, gfx::IntSize aSize, uint8_t *aBuffer, uint32_t aBufferSize);
 
+  void ClearAllCaches();
+
   void Pause();
   bool Resume();
 
   void WakeSceneBuilder();
   void FlushSceneBuilder();
 
   wr::WrIdNamespace GetNamespace();
   uint32_t GetMaxTextureSize() const { return mMaxTextureSize; }
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1024,16 +1024,22 @@ pub unsafe extern "C" fn wr_api_delete(d
 }
 
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_shut_down(dh: &mut DocumentHandle) {
     dh.api.shut_down();
 }
 
+/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_clear_all_caches(dh: &mut DocumentHandle) {
+    dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
+}
+
 fn make_transaction(do_async: bool) -> Transaction {
     let mut transaction = Transaction::new();
     // Ensure that we either use async scene building or not based on the
     // gecko pref, regardless of what the default is. We can remove this once
     // the scene builder thread is enabled everywhere and working well.
     if do_async {
         transaction.use_scene_builder_thread();
     } else {
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1046,16 +1046,20 @@ WR_FUNC;
 
 WR_INLINE
 void wr_api_capture(DocumentHandle *aDh,
                     const char *aPath,
                     uint32_t aBitsRaw)
 WR_FUNC;
 
 WR_INLINE
+void wr_api_clear_all_caches(DocumentHandle *aDh)
+WR_DESTRUCTOR_SAFE_FUNC;
+
+WR_INLINE
 void wr_api_clone(DocumentHandle *aDh,
                   DocumentHandle **aOutHandle)
 WR_FUNC;
 
 WR_INLINE
 void wr_api_create_document(DocumentHandle *aRootDh,
                             DocumentHandle **aOutHandle,
                             DeviceUintSize aDocSize,
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -334,16 +334,25 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
         getter_AddRefs(sandboxedLoadingPrincipal));
     NS_ENSURE_SUCCESS(rv, rv);
     rv = PrincipalToPrincipalInfo(sandboxedLoadingPrincipal,
                                   &sandboxedLoadingPrincipalInfoTemp);
     NS_ENSURE_SUCCESS(rv, rv);
     sandboxedLoadingPrincipalInfo = sandboxedLoadingPrincipalInfoTemp;
   }
 
+  OptionalPrincipalInfo topLevelPrincipalInfo = mozilla::void_t();
+  if (aLoadInfo->TopLevelPrincipal()) {
+    PrincipalInfo topLevelPrincipalInfoTemp;
+    rv = PrincipalToPrincipalInfo(aLoadInfo->TopLevelPrincipal(),
+                                  &topLevelPrincipalInfoTemp);
+    NS_ENSURE_SUCCESS(rv, rv);
+    topLevelPrincipalInfo = topLevelPrincipalInfoTemp;
+  }
+
   OptionalPrincipalInfo topLevelStorageAreaPrincipalInfo = mozilla::void_t();
   if (aLoadInfo->TopLevelStorageAreaPrincipal()) {
     PrincipalInfo topLevelStorageAreaPrincipalInfoTemp;
     rv = PrincipalToPrincipalInfo(aLoadInfo->TopLevelStorageAreaPrincipal(),
                                   &topLevelStorageAreaPrincipalInfoTemp);
     NS_ENSURE_SUCCESS(rv, rv);
     topLevelStorageAreaPrincipalInfo = topLevelStorageAreaPrincipalInfoTemp;
   }
@@ -403,16 +412,17 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
   }
 
   *aOptionalLoadInfoArgs =
     LoadInfoArgs(
       loadingPrincipalInfo,
       triggeringPrincipalInfo,
       principalToInheritInfo,
       sandboxedLoadingPrincipalInfo,
+      topLevelPrincipalInfo,
       topLevelStorageAreaPrincipalInfo,
       optionalResultPrincipalURI,
       aLoadInfo->GetSecurityFlags(),
       aLoadInfo->InternalContentPolicyType(),
       static_cast<uint32_t>(aLoadInfo->GetTainting()),
       aLoadInfo->GetUpgradeInsecureRequests(),
       aLoadInfo->GetBrowserUpgradeInsecureRequests(),
       aLoadInfo->GetBrowserWouldUpgradeInsecureRequests(),
@@ -483,16 +493,23 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
 
   nsCOMPtr<nsIPrincipal> sandboxedLoadingPrincipal;
   if (loadInfoArgs.sandboxedLoadingPrincipalInfo().type() != OptionalPrincipalInfo::Tvoid_t) {
     sandboxedLoadingPrincipal =
       PrincipalInfoToPrincipal(loadInfoArgs.sandboxedLoadingPrincipalInfo(), &rv);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  nsCOMPtr<nsIPrincipal> topLevelPrincipal;
+  if (loadInfoArgs.topLevelPrincipalInfo().type() != OptionalPrincipalInfo::Tvoid_t) {
+    topLevelPrincipal =
+      PrincipalInfoToPrincipal(loadInfoArgs.topLevelPrincipalInfo(), &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   nsCOMPtr<nsIPrincipal> topLevelStorageAreaPrincipal;
   if (loadInfoArgs.topLevelStorageAreaPrincipalInfo().type() != OptionalPrincipalInfo::Tvoid_t) {
     topLevelStorageAreaPrincipal =
       PrincipalInfoToPrincipal(loadInfoArgs.topLevelStorageAreaPrincipalInfo(), &rv);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIURI> resultPrincipalURI;
@@ -556,16 +573,17 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
       loadInfoArgs.controller().get_IPCServiceWorkerDescriptor()));
   }
 
   nsCOMPtr<nsILoadInfo> loadInfo =
     new mozilla::LoadInfo(loadingPrincipal,
                           triggeringPrincipal,
                           principalToInherit,
                           sandboxedLoadingPrincipal,
+                          topLevelPrincipal,
                           topLevelStorageAreaPrincipal,
                           resultPrincipalURI,
                           clientInfo,
                           reservedClientInfo,
                           initialClientInfo,
                           controller,
                           loadInfoArgs.securityFlags(),
                           loadInfoArgs.contentPolicyType(),
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -3,16 +3,18 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 
+import android.graphics.Rect
+
 import android.os.Build
 import android.os.Bundle
 
 import android.support.test.filters.MediumTest
 import android.support.test.InstrumentationRegistry
 import android.support.test.runner.AndroidJUnit4
 
 import android.view.accessibility.AccessibilityNodeInfo
@@ -25,22 +27,26 @@ import android.view.ViewGroup
 import android.widget.FrameLayout
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.Before
 import org.junit.After
 import org.junit.runner.RunWith
 
+const val DISPLAY_WIDTH = 480
+const val DISPLAY_HEIGHT = 640
+
 @RunWith(AndroidJUnit4::class)
 @MediumTest
-@WithDisplay(width = 480, height = 640)
+@WithDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
 @WithDevToolsAPI
 class AccessibilityTest : BaseSessionTest() {
     lateinit var view: View
+    val screenRect = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
     val provider: AccessibilityNodeProvider get() = view.accessibilityNodeProvider
 
     // Given a child ID, return the virtual descendent ID.
     private fun getVirtualDescendantId(childId: Long): Int {
         try {
             val getVirtualDescendantIdMethod =
                 AccessibilityNodeInfo::class.java.getMethod("getVirtualDescendantId", Long::class.java)
             return getVirtualDescendantIdMethod.invoke(null, childId) as Int
@@ -59,19 +65,22 @@ class AccessibilityTest : BaseSessionTes
             return 0
         }
     }
 
     private interface EventDelegate {
         fun onAccessibilityFocused(event: AccessibilityEvent) { }
         fun onClicked(event: AccessibilityEvent) { }
         fun onFocused(event: AccessibilityEvent) { }
+        fun onSelected(event: AccessibilityEvent) { }
+        fun onScrolled(event: AccessibilityEvent) { }
         fun onTextSelectionChanged(event: AccessibilityEvent) { }
         fun onTextChanged(event: AccessibilityEvent) { }
         fun onTextTraversal(event: AccessibilityEvent) { }
+        fun onWinStateChanged(event: AccessibilityEvent) { }
     }
 
     @Before fun setup() {
         // We initialize a view with a parent and grandparent so that the
         // accessibility events propagate up at least to the parent.
         view = FrameLayout(InstrumentationRegistry.getTargetContext())
         FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view)
         FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view.parent as View)
@@ -85,19 +94,22 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.addExternalDelegateUntilTestEnd(
             EventDelegate::class,
         { newDelegate -> (view.parent as View).setAccessibilityDelegate(object : View.AccessibilityDelegate() {
             override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean {
                 when (event.eventType) {
                     AccessibilityEvent.TYPE_VIEW_FOCUSED -> newDelegate.onFocused(event)
                     AccessibilityEvent.TYPE_VIEW_CLICKED -> newDelegate.onClicked(event)
                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
+                    AccessibilityEvent.TYPE_VIEW_SELECTED -> newDelegate.onSelected(event)
+                    AccessibilityEvent.TYPE_VIEW_SCROLLED -> newDelegate.onScrolled(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> newDelegate.onTextSelectionChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
+                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
                     else -> {}
                 }
                 return false
             }
         }) },
         { (view.parent as View).setAccessibilityDelegate(null) },
         object : EventDelegate { })
     }
@@ -199,31 +211,35 @@ class AccessibilityTest : BaseSessionTes
             @AssertCalled(count = 1)
             override fun onTextTraversal(event: AccessibilityEvent) {
               assertThat("fromIndex matches", event.fromIndex, equalTo(fromIndex))
               assertThat("toIndex matches", event.toIndex, equalTo(toIndex))
             }
         })
     }
 
-    private fun waitUntilClick(checked: Boolean? = null, selected: Boolean? = null) {
+    private fun waitUntilClick(checked: Boolean) {
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onClicked(event: AccessibilityEvent) {
                 var nodeId = getSourceId(event)
                 var node = provider.createAccessibilityNodeInfo(nodeId)
+                assertThat("Event's checked state matches", event.isChecked, equalTo(checked))
+                assertThat("Checkbox node has correct checked state", node.isChecked, equalTo(checked))
+            }
+        })
+    }
 
-                if (checked != null) {
-                    assertThat("Event's checked state matches", event.isChecked, equalTo(checked))
-                    assertThat("Checkbox node has correct checked state", node.isChecked, equalTo(checked))
-                }
-
-                if (selected != null) {
-                    assertThat("Selectable node has correct selected state", node.isSelected, equalTo(selected))
-                }
+    private fun waitUntilSelect(selected: Boolean) {
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onSelected(event: AccessibilityEvent) {
+                var nodeId = getSourceId(event)
+                var node = provider.createAccessibilityNodeInfo(nodeId)
+                assertThat("Selectable node has correct selected state", node.isSelected, equalTo(selected))
             }
         })
     }
 
     private fun setSelectionArguments(start: Int, end: Int): Bundle {
         val arguments = Bundle(2)
         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, start)
         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, end)
@@ -402,20 +418,20 @@ class AccessibilityTest : BaseSessionTes
                 assertThat("Checkbox node is clickable", node.isClickable, equalTo(true))
                 assertThat("Checkbox node is focusable", node.isFocusable, equalTo(true))
                 assertThat("Checkbox node is not checked", node.isChecked, equalTo(false))
                 assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option check button"))
             }
         })
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
-        waitUntilClick(checked = true)
+        waitUntilClick(true)
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
-        waitUntilClick(checked = false)
+        waitUntilClick(false)
     }
 
     @Test fun testSelectable() {
         var nodeId = View.NO_ID
         sessionRule.session.loadString(
                 """<ul style="list-style-type: none;" role="listbox">
                         <li id="li" role="option" onclick="this.setAttribute('aria-selected',
                             this.getAttribute('aria-selected') == 'true' ? 'false' : 'true')">1</li>
@@ -430,14 +446,120 @@ class AccessibilityTest : BaseSessionTes
                 var node = provider.createAccessibilityNodeInfo(nodeId)
                 assertThat("Selectable node is clickable", node.isClickable, equalTo(true))
                 assertThat("Selectable node is not selected", node.isSelected, equalTo(false))
                 assertThat("Selectable node has correct role", node.text.toString(), equalTo("1 option list box"))
             }
         })
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
-        waitUntilClick(selected = true)
+        waitUntilSelect(true)
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
-        waitUntilClick(selected = false)
+        waitUntilSelect(false)
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SELECT, null)
+        waitUntilSelect(true)
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SELECT, null)
+        waitUntilSelect(false)
+    }
+
+    private fun screenContainsNode(nodeId: Int): Boolean {
+        var node = provider.createAccessibilityNodeInfo(nodeId)
+        var nodeBounds = Rect()
+        node.getBoundsInScreen(nodeBounds)
+        return screenRect.contains(nodeBounds)
+    }
+
+    @Test fun testScroll() {
+        var nodeId = View.NO_ID
+        sessionRule.session.loadString(
+                """<body style="margin: 0;">
+                        <div style="height: 100vh;"></div>
+                        <button>Hello</button>
+                        <p style="margin: 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+                            sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+                </body>""",
+                "text/html")
+        sessionRule.waitForPageStop()
+
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                var node = provider.createAccessibilityNodeInfo(nodeId)
+                var nodeBounds = Rect()
+                node.getBoundsInParent(nodeBounds)
+                assertThat("Default root node bounds are correct", nodeBounds, equalTo(screenRect))
+            }
+        })
+
+        provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1, order = [1])
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
+            }
+
+            @AssertCalled(count = 1, order = [2])
+            override fun onScrolled(event: AccessibilityEvent) {
+                assertThat("View is scrolled for focused node to be onscreen", event.scrollY, greaterThan(0))
+                assertThat("View is not scrolled to the end", event.scrollY, lessThan(event.maxScrollY))
+            }
+
+            @AssertCalled(count = 1, order = [3])
+            override fun onWinStateChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
+            }
+        })
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1, order = [1])
+            override fun onScrolled(event: AccessibilityEvent) {
+                assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
+            }
+
+            @AssertCalled(count = 1, order = [2])
+            override fun onWinStateChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is still onscreen", screenContainsNode(nodeId), equalTo(true))
+            }
+        })
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1, order = [1])
+            override fun onScrolled(event: AccessibilityEvent) {
+                assertThat("View is scrolled to the beginning", event.scrollY, equalTo(0))
+            }
+
+            @AssertCalled(count = 1, order = [2])
+            override fun onWinStateChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is offscreen", screenContainsNode(nodeId), equalTo(false))
+            }
+        })
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1, order = [1])
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
+            }
+
+            @AssertCalled(count = 1, order = [2])
+            override fun onScrolled(event: AccessibilityEvent) {
+                assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
+            }
+
+            @AssertCalled(count = 1, order = [3])
+            override fun onWinStateChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
+            }
+        })
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -184,16 +184,19 @@ public class SessionAccessibility {
                             mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
                             return true;
                         case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
                             mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollForward", null);
                             return true;
                         case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
                             mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollBackward", null);
                             return true;
+                        case AccessibilityNodeInfo.ACTION_SELECT:
+                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySelect", null);
+                            return true;
                         case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
                             if (mLastItem) {
                                 return false;
                             }
                             // fall-through
                         case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
                             if (arguments != null) {
                                 data = new GeckoBundle(1);
@@ -478,18 +481,19 @@ public class SessionAccessibility {
             mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
             populateNodeInfoFromJSON(mVirtualContentNode, message);
         }
 
         if (mVirtualContentNode != null) {
             // Bounds for the virtual content can be updated from any event.
             updateBounds(mVirtualContentNode, message);
 
-            // State for the virtual content can be updated when view is clicked.
-            if (eventType == AccessibilityEvent.TYPE_VIEW_CLICKED) {
+            // State for the virtual content can be updated when view is clicked/selected.
+            if (eventType == AccessibilityEvent.TYPE_VIEW_CLICKED ||
+                eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
                 updateState(mVirtualContentNode, message);
             }
         }
 
         final AccessibilityEvent accessibilityEvent = obtainEvent(eventType, eventSource);
         populateEventFromJSON(accessibilityEvent, message);
         ((ViewParent) mView).requestSendAccessibilityEvent(mView, accessibilityEvent);
     }
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -862,16 +862,28 @@ VARCACHE_PREF(
 #endif
 
 VARCACHE_PREF(
   "media.webspeech.recognition.force_enable",
    MediaWebspeechRecognitionForceEnable,
   bool, false
 )
 
+#if defined(MOZ_WEBM_ENCODER)
+# define PREF_VALUE true
+#else
+# define PREF_VALUE false
+#endif
+VARCACHE_PREF(
+  "media.encoder.webm.enabled",
+   MediaEncoderWebMEnabled,
+  RelaxedAtomicBool, true
+)
+#undef PREF_VALUE
+
 #if defined(RELEASE_OR_BETA)
 # define PREF_VALUE 3
 #else
   // Zero tolerance in pre-release builds to detect any decoder regression.
 # define PREF_VALUE 0
 #endif
 VARCACHE_PREF(
   "media.audio-max-decode-error",
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -150,16 +150,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
       mOuterWindowID = contextOuter->WindowID();
       nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent();
       mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID;
       mTopOuterWindowID = FindTopOuterWindowID(contextOuter);
 
       nsGlobalWindowInner* innerWindow =
         nsGlobalWindowInner::Cast(contextOuter->GetCurrentInnerWindow());
       if (innerWindow) {
+        mTopLevelPrincipal = innerWindow->GetTopLevelPrincipal();
         mTopLevelStorageAreaPrincipal =
           innerWindow->GetTopLevelStorageAreaPrincipal();
       }
     }
 
     mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID();
     mAncestorPrincipals = aLoadingContext->OwnerDoc()->AncestorPrincipals();
     mAncestorOuterWindowIDs = aLoadingContext->OwnerDoc()->AncestorOuterWindowIDs();
@@ -341,16 +342,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
   // with the hidden window.
   nsCOMPtr<nsPIDOMWindowOuter> parent = aOuterWindow->GetScriptableParent();
   mParentOuterWindowID = parent ? parent->WindowID() : 0;
   mTopOuterWindowID = FindTopOuterWindowID(aOuterWindow);
 
   nsGlobalWindowInner* innerWindow =
     nsGlobalWindowInner::Cast(aOuterWindow->GetCurrentInnerWindow());
   if (innerWindow) {
+    mTopLevelPrincipal = innerWindow->GetTopLevelPrincipal();
     mTopLevelStorageAreaPrincipal =
       innerWindow->GetTopLevelStorageAreaPrincipal();
   }
 
   // get the docshell from the outerwindow, and then get the originattributes
   nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
   MOZ_ASSERT(docShell);
   mOriginAttributes = nsDocShell::Cast(docShell)->GetOriginAttributes();
@@ -366,16 +368,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
 #endif
 }
 
 LoadInfo::LoadInfo(const LoadInfo& rhs)
   : mLoadingPrincipal(rhs.mLoadingPrincipal)
   , mTriggeringPrincipal(rhs.mTriggeringPrincipal)
   , mPrincipalToInherit(rhs.mPrincipalToInherit)
   , mSandboxedLoadingPrincipal(rhs.mSandboxedLoadingPrincipal)
+  , mTopLevelPrincipal(rhs.mTopLevelPrincipal)
   , mTopLevelStorageAreaPrincipal(rhs.mTopLevelStorageAreaPrincipal)
   , mResultPrincipalURI(rhs.mResultPrincipalURI)
   , mClientInfo(rhs.mClientInfo)
   // mReservedClientSource must be handled specially during redirect
   // mReservedClientInfo must be handled specially during redirect
   // mInitialClientInfo must be handled specially during redirect
   , mController(rhs.mController)
   , mPerformanceStorage(rhs.mPerformanceStorage)
@@ -418,16 +421,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
   , mServiceWorkerTaintingSynthesized(false)
 {
 }
 
 LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
                    nsIPrincipal* aTriggeringPrincipal,
                    nsIPrincipal* aPrincipalToInherit,
                    nsIPrincipal* aSandboxedLoadingPrincipal,
+                   nsIPrincipal* aTopLevelPrincipal,
                    nsIPrincipal* aTopLevelStorageAreaPrincipal,
                    nsIURI* aResultPrincipalURI,
                    const Maybe<ClientInfo>& aClientInfo,
                    const Maybe<ClientInfo>& aReservedClientInfo,
                    const Maybe<ClientInfo>& aInitialClientInfo,
                    const Maybe<ServiceWorkerDescriptor>& aController,
                    nsSecurityFlags aSecurityFlags,
                    nsContentPolicyType aContentPolicyType,
@@ -459,16 +463,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
                    const nsTArray<nsCString>& aCorsUnsafeHeaders,
                    bool aForcePreflight,
                    bool aIsPreflight,
                    bool aLoadTriggeredFromExternal,
                    bool aServiceWorkerTaintingSynthesized)
   : mLoadingPrincipal(aLoadingPrincipal)
   , mTriggeringPrincipal(aTriggeringPrincipal)
   , mPrincipalToInherit(aPrincipalToInherit)
+  , mTopLevelPrincipal(aTopLevelPrincipal)
   , mTopLevelStorageAreaPrincipal(aTopLevelStorageAreaPrincipal)
   , mResultPrincipalURI(aResultPrincipalURI)
   , mClientInfo(aClientInfo)
   , mReservedClientInfo(aReservedClientInfo)
   , mInitialClientInfo(aInitialClientInfo)
   , mController(aController)
   , mSecurityFlags(aSecurityFlags)
   , mInternalContentPolicyType(aContentPolicyType)
@@ -643,16 +648,29 @@ LoadInfo::GetSandboxedLoadingPrincipal(n
   MOZ_ASSERT(mSandboxedLoadingPrincipal);
 
   nsCOMPtr<nsIPrincipal> copy(mSandboxedLoadingPrincipal);
   copy.forget(aPrincipal);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LoadInfo::GetTopLevelPrincipal(nsIPrincipal** aTopLevelPrincipal)
+{
+  NS_IF_ADDREF(*aTopLevelPrincipal = mTopLevelPrincipal);
+  return NS_OK;
+}
+
+nsIPrincipal*
+LoadInfo::TopLevelPrincipal()
+{
+  return mTopLevelPrincipal;
+}
+
+NS_IMETHODIMP
 LoadInfo::GetTopLevelStorageAreaPrincipal(nsIPrincipal** aTopLevelStorageAreaPrincipal)
 {
   NS_IF_ADDREF(*aTopLevelStorageAreaPrincipal = mTopLevelStorageAreaPrincipal);
   return NS_OK;
 }
 
 nsIPrincipal*
 LoadInfo::TopLevelStorageAreaPrincipal()
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -92,16 +92,17 @@ private:
   // private constructor that is only allowed to be called from within
   // HttpChannelParent and FTPChannelParent declared as friends undeneath.
   // In e10s we can not serialize nsINode, hence we store the innerWindowID.
   // Please note that aRedirectChain uses swapElements.
   LoadInfo(nsIPrincipal* aLoadingPrincipal,
            nsIPrincipal* aTriggeringPrincipal,
            nsIPrincipal* aPrincipalToInherit,
            nsIPrincipal* aSandboxedLoadingPrincipal,
+           nsIPrincipal* aTopLevelPrincipal,
            nsIPrincipal* aTopLevelStorageAreaPrincipal,
            nsIURI* aResultPrincipalURI,
            const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
            const Maybe<mozilla::dom::ClientInfo>& aReservedClientInfo,
            const Maybe<mozilla::dom::ClientInfo>& aInitialClientInfo,
            const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
            nsSecurityFlags aSecurityFlags,
            nsContentPolicyType aContentPolicyType,
@@ -156,16 +157,17 @@ private:
   friend class mozilla::dom::XMLHttpRequestMainThread;
 
   // if you add a member, please also update the copy constructor and consider if
   // it should be merged from parent channel through ParentLoadInfoForwarderArgs.
   nsCOMPtr<nsIPrincipal>           mLoadingPrincipal;
   nsCOMPtr<nsIPrincipal>           mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal>           mPrincipalToInherit;
   nsCOMPtr<nsIPrincipal>           mSandboxedLoadingPrincipal;
+  nsCOMPtr<nsIPrincipal>           mTopLevelPrincipal;
   nsCOMPtr<nsIPrincipal>           mTopLevelStorageAreaPrincipal;
   nsCOMPtr<nsIURI>                 mResultPrincipalURI;
 
   Maybe<mozilla::dom::ClientInfo>               mClientInfo;
   UniquePtr<mozilla::dom::ClientSource>         mReservedClientSource;
   Maybe<mozilla::dom::ClientInfo>               mReservedClientInfo;
   Maybe<mozilla::dom::ClientInfo>               mInitialClientInfo;
   Maybe<mozilla::dom::ServiceWorkerDescriptor>  mController;
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -879,16 +879,28 @@ interface nsILoadInfo : nsISupports
    * Returns the null principal of the resulting resource if the SEC_SANDBOXED
    * flag is set.  Otherwise returns null.  This is used by
    * GetChannelResultPrincipal() to ensure that the same null principal object
    * is returned every time.
    */
   [noscript] readonly attribute nsIPrincipal sandboxedLoadingPrincipal;
 
   /**
+   * Return the top-level principal, which is the principal of the top-level
+   * window.
+   */
+  [noscript] readonly attribute nsIPrincipal topLevelPrincipal;
+
+  /**
+   * A C++-friendly version of topLevelPrincipal.
+   */
+  [noscript, notxpcom, nostdcall, binaryname(TopLevelPrincipal)]
+  nsIPrincipal binaryTopLevelPrincipal();
+
+  /**
    * Return the top-level storage area principal, which is the principal of
    * the top-level window if it's not a 3rd party context, non tracking
    * resource.
    */
   [noscript] readonly attribute nsIPrincipal topLevelStorageAreaPrincipal;
 
   /**
    * A C++-friendly version of topLevelStorageAreaPrincipal.
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -37,16 +37,17 @@ struct RedirectHistoryEntryInfo
 };
 
 struct LoadInfoArgs
 {
   OptionalPrincipalInfo       requestingPrincipalInfo;
   PrincipalInfo               triggeringPrincipalInfo;
   OptionalPrincipalInfo       principalToInheritInfo;
   OptionalPrincipalInfo       sandboxedLoadingPrincipalInfo;
+  OptionalPrincipalInfo       topLevelPrincipalInfo;
   OptionalPrincipalInfo       topLevelStorageAreaPrincipalInfo;
   OptionalURIParams           resultPrincipalURI;
   uint32_t                    securityFlags;
   uint32_t                    contentPolicyType;
   uint32_t                    tainting;
   bool                        upgradeInsecureRequests;
   bool                        browserUpgradeInsecureRequests;
   bool                        browserWouldUpgradeInsecureRequests;
--- a/taskcluster/ci/test/misc.yml
+++ b/taskcluster/ci/test/misc.yml
@@ -26,16 +26,24 @@ geckoview-junit:
     description: "Geckoview junit run"
     suite: geckoview-junit
     treeherder-symbol: gv-junit
     instance-size: xlarge
     loopback-video: true
     e10s: true
     target: geckoview-androidTest.apk
     max-run-time: 3600
+    tier:
+        by-test-platform:
+            android-em-7.0-x86/opt: 3
+            default: default
+    run-on-projects:
+        by-test-platform:
+            android-em-7.0-x86/opt: ['mozilla-central']
+            default: built-projects
     chunks:
         by-test-platform:
             android-em-4.3-arm7-api-16-ccov/debug: 4
             android-em-4.3-arm7-api-16/debug: 4
             android-em-4.3-arm7-api-16/opt: 2
             default: 1
     mozharness:
         script: android_emulator_unittest.py
--- a/taskcluster/ci/test/mochitest.yml
+++ b/taskcluster/ci/test/mochitest.yml
@@ -1,13 +1,17 @@
 job-defaults:
     target:
         by-test-platform:
             android-em-7.0-x86/opt: geckoview-androidTest.apk
             default: null
+    tier:
+        by-test-platform:
+            android-em-7.0-x86/opt: 3
+            default: default
     mozharness:
         script:
             by-test-platform:
                 android-em.*: android_emulator_unittest.py
                 android-hw.*: android_hardware_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
@@ -200,16 +204,20 @@ mochitest-clipboard:
     max-run-time:
         by-test-platform:
             windows10-64-ccov/debug: 7200
             default: 3600
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
+    run-on-projects:
+        by-test-platform:
+            android-em-7.0-x86/opt: ['mozilla-central']
+            default: built-projects
     mozharness:
         mochitest-flavor: plain
         extra-options:
             by-test-platform:
                 android-em.*:
                     # note that Android runs fewer suites than other platforms
                     - --test-suite=mochitest-plain-clipboard
                 default:
@@ -244,17 +252,20 @@ mochitest-devtools-chrome:
             default: default
     # Bug 1296086: high number of intermittents observed with software GL and large instances
     allow-software-gl-layers: false
 
 mochitest-gpu:
     description: "Mochitest GPU run"
     suite: mochitest/gpu
     treeherder-symbol: M(gpu)
-    run-on-projects: built-projects
+    run-on-projects:
+        by-test-platform:
+            android-em-7.0-x86/opt: ['mozilla-central']
+            default: built-projects
     loopback-video: true
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: default
     virtualization: virtual-with-gpu
     e10s:
         by-test-platform:
--- a/taskcluster/ci/test/reftest.yml
+++ b/taskcluster/ci/test/reftest.yml
@@ -1,13 +1,17 @@
 job-defaults:
     target:
         by-test-platform:
             android-em-7.0-x86/opt: geckoview-androidTest.apk
             default: null
+    tier:
+        by-test-platform:
+            android-em-7.0-x86/opt: 3
+            default: default
     mozharness:
         script:
             by-test-platform:
                 android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
                 android-em-7.0-x86/opt:
@@ -23,17 +27,20 @@ job-defaults:
                     - unittests/mac_unittest.py
                 windows.*:
                     - unittests/win_taskcluster_unittest.py
 
 crashtest:
     description: "Crashtest run"
     suite: reftest/crashtest
     treeherder-symbol: R(C)
-    run-on-projects: built-projects
+    run-on-projects:
+        by-test-platform:
+            android-em-7.0-x86/opt: ['mozilla-central']
+            default: built-projects
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: default
     virtualization:
         by-test-platform:
             windows10-64-qr/.*: virtual-with-gpu
             default: virtual
@@ -48,17 +55,20 @@ crashtest:
             linux32/debug: both
             default: true
 
 jsreftest:
     description: "JS Reftest run"
     suite: reftest/jsreftest
     schedules-component: jsreftest  # scheduling for this reftest is different from the others..
     treeherder-symbol: R(J)
-    run-on-projects: built-projects
+    run-on-projects:
+        by-test-platform:
+            android-em-7.0-x86/opt: ['mozilla-central']
+            default: built-projects
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: default
     chunks:
         by-test-platform:
             android-em-4.3-arm7-api-16/debug: 100
             android-em-7.0-x86/opt: 3
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -336,21 +336,20 @@ android-em-4.3-arm7-api-16/opt:
         - android-opt-tests
         - android-gradle-tests
 
 android-em-4.2-x86/opt:
     build-platform: android-x86/opt
     test-sets:
         - android-x86-tests
 
-# Coming soon!
-# android-em-7.0-x86/opt:
-#     build-platform: android-x86/opt
-#     test-sets:
-#         - android-x86-kvm-tests
+android-em-7.0-x86/opt:
+    build-platform: android-x86/opt
+    test-sets:
+        - android-x86-kvm-tests
 
 # android-hw test platforms execute on real devices attached to Autophone hosts.
 
 # android-hw-p2-8-0 Google Pixel 2 Android 8.0
 
 android-hw-p2-8-0-arm7-api-16/opt:
     build-platform: android-api-16/opt
     test-sets:
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -385,24 +385,24 @@ android-x86-tests:
     - mochitest-chrome
     - xpcshell
     - geckoview
 
 android-x86-kvm-tests:
     - crashtest
     - geckoview-junit
     - jsreftest
-    - mochitest
+    # - mochitest
     # - mochitest-chrome
     - mochitest-clipboard
     - mochitest-gpu
-    - mochitest-media
+    # - mochitest-media
     # - mochitest-webgl1-core
-    - reftest
-    - test-verify
+    # - reftest
+    # - test-verify
 
 android-ccov-tests:
     - geckoview-junit
 
 devtools-tests:
     - mochitest-devtools-chrome
 
 mochitest-headless:
--- a/testing/mozharness/mozharness/mozilla/testing/per_test_base.py
+++ b/testing/mozharness/mozharness/mozilla/testing/per_test_base.py
@@ -104,16 +104,18 @@ class SingleTestMixin(object):
 
         # for each changed file, determine if it is a test file, and what suite it is in
         for file in changed_files:
             # manifest paths use os.sep (like backslash on Windows) but
             # automation-relevance uses posixpath.sep
             file = file.replace(posixpath.sep, os.sep)
             entry = tests_by_path.get(file)
             if not entry:
+                if os.environ.get('MOZHARNESS_TEST_PATHS', None) is not None:
+                    self.fatal("Per-test run could not find requested test '%s'" % file)
                 continue
 
             if gpu and not self._is_gpu_suite(entry[1]):
                 self.info("Per-test run (gpu) discarded non-gpu test %s (%s)" % (file, entry[1]))
                 continue
             elif not gpu and self._is_gpu_suite(entry[1]):
                 self.info("Per-test run (non-gpu) discarded gpu test %s (%s)" % (file, entry[1]))
                 continue
@@ -165,24 +167,29 @@ class SingleTestMixin(object):
         for (type, path, test) in man:
             if type not in ["testharness", "reftest", "wdspec"]:
                 continue
             repo_path = os.path.join(repo_tests_path, path)
             # manifest paths use os.sep (like backslash on Windows) but
             # automation-relevance uses posixpath.sep
             repo_path = repo_path.replace(os.sep, posixpath.sep)
             if repo_path in changed_files:
-                self.info("found web-platform test file '%s', type %s" % (path, type))
+                self.info("Per-test run found web-platform test '%s', type %s" % (path, type))
                 suite_files = self.suites.get(type)
                 if not suite_files:
                     suite_files = []
                 test_path = os.path.join(tests_path, path)
                 suite_files.append(test_path)
                 self.suites[type] = suite_files
                 self._map_test_path_to_source(test_path, repo_path)
+                changed_files.remove(repo_path)
+        if os.environ.get('MOZHARNESS_TEST_PATHS', None) is not None:
+            for file in changed_files:
+                self.fatal("Per-test run could not find requested web-platform test '%s'" %
+                           file)
 
     def find_modified_tests(self):
         """
            For each file modified on this push, determine if the modified file
            is a test, by searching test manifests. Populate self.suites
            with test files, organized by suite.
 
            This depends on test manifests, so can only run after test zips have
@@ -207,16 +214,18 @@ class SingleTestMixin(object):
         # FIXME(emilio): Need to update test expectations.
         mozinfo.update({'stylo': True})
         mozinfo.update({'verify': True})
         self.info("Per-test run using mozinfo: %s" % str(mozinfo.info))
 
         changed_files = set()
         if os.environ.get('MOZHARNESS_TEST_PATHS', None) is not None:
             changed_files |= set(os.environ['MOZHARNESS_TEST_PATHS'].split(':'))
+            self.info("Per-test run found explicit request in MOZHARNESS_TEST_PATHS:")
+            self.info(str(changed_files))
         else:
             # determine which files were changed on this push
             url = '%s/json-automationrelevance/%s' % (repository.rstrip('/'), revision)
             contents = self.retry(get_automationrelevance, attempts=2, sleeptime=10)
             for c in contents['changesets']:
                 self.info(" {cset} {desc}".format(
                     cset=c['node'][0:12],
                     desc=c['desc'].splitlines()[0].encode('ascii', 'ignore')))
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src-networkState.html.ini
@@ -0,0 +1,3 @@
+[resource-selection-invoke-set-src-networkState.html]
+  disabled:
+    if debug and (os == "linux") and (processor == "x86") and (bits == 32): https://bugzilla.mozilla.org/show_bug.cgi?id=1482405
--- a/testing/web-platform/meta/media-capabilities/idlharness.any.js.ini
+++ b/testing/web-platform/meta/media-capabilities/idlharness.any.js.ini
@@ -29,31 +29,22 @@
   [MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "encodingInfo(MediaEncodingConfiguration)" with the proper type]
     expected: FAIL
 
   [MediaCapabilities must be primary interface of navigatior.mediaCapabilities]
     expected: FAIL
 
 
 [idlharness.any.worker.html]
-  [MediaCapabilitiesInfo interface: encodingInfo must inherit property "smooth" with the proper type]
-    expected: FAIL
-
   [MediaCapabilities interface: calling decodingInfo(MediaDecodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError]
     expected: FAIL
 
   [MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "decodingInfo(MediaDecodingConfiguration)" with the proper type]
     expected: FAIL
 
-  [Stringification of decodingInfo]
-    expected: FAIL
-
-  [MediaCapabilitiesInfo interface: decodingInfo must inherit property "smooth" with the proper type]
-    expected: FAIL
-
   [ScreenLuminance interface: screen.luminance must not have property "max"]
     expected: FAIL
 
   [Stringification of navigatior.mediaCapabilities]
     expected: FAIL
 
   [ScreenLuminance must be primary interface of screen.luminance]
     expected: FAIL
@@ -62,47 +53,26 @@
     expected: FAIL
 
   [ScreenLuminance interface: screen.luminance must not have property "min"]
     expected: FAIL
 
   [Stringification of screen.luminance]
     expected: FAIL
 
-  [MediaCapabilitiesInfo interface: decodingInfo must inherit property "supported" with the proper type]
-    expected: FAIL
-
-  [MediaCapabilitiesInfo interface: decodingInfo must inherit property "powerEfficient" with the proper type]
-    expected: FAIL
-
-  [Stringification of encodingInfo]
-    expected: FAIL
-
-  [MediaCapabilitiesInfo interface: encodingInfo must inherit property "powerEfficient" with the proper type]
-    expected: FAIL
-
   [Screen interface: screen must not have property "onchange"]
     expected: FAIL
 
-  [MediaCapabilitiesInfo must be primary interface of encodingInfo]
-    expected: FAIL
-
   [MediaCapabilities interface: calling encodingInfo(MediaEncodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError]
     expected: FAIL
 
-  [MediaCapabilitiesInfo must be primary interface of decodingInfo]
-    expected: FAIL
-
   [ScreenLuminance interface: screen.luminance must not have property "maxAverage"]
     expected: FAIL
 
   [MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "encodingInfo(MediaEncodingConfiguration)" with the proper type]
     expected: FAIL
 
   [Screen interface: screen must not have property "colorGamut"]
     expected: FAIL
 
   [MediaCapabilities must be primary interface of navigatior.mediaCapabilities]
     expected: FAIL
 
-  [MediaCapabilitiesInfo interface: encodingInfo must inherit property "supported" with the proper type]
-    expected: FAIL
-
--- a/testing/web-platform/meta/notifications/idlharness.any.js.ini
+++ b/testing/web-platform/meta/notifications/idlharness.any.js.ini
@@ -1,8 +1,10 @@
+prefs: [dom.webnotifications.requireinteraction.enabled:true]
+
 [idlharness.any.html]
   [Notification interface: attribute silent]
     expected: FAIL
 
   [Notification interface: notification must inherit property "image" with the proper type]
     expected: FAIL
 
   [Notification interface: attribute actions]
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -232,16 +232,22 @@ AntiTrackingCommon::IsFirstPartyStorageA
 
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
   if (!loadInfo) {
     return true;
   }
 
   nsIPrincipal* parentPrincipal = loadInfo->TopLevelStorageAreaPrincipal();
   if (!parentPrincipal) {
+    // parentPrincipal can be null if the parent window is not the top-level
+    // window.
+    if (loadInfo->TopLevelPrincipal()) {
+      return false;
+    }
+
     parentPrincipal = loadInfo->TriggeringPrincipal();
     if (NS_WARN_IF(!parentPrincipal)) {
       // Why we are here?!?
       return true;
     }
   }
 
   nsCOMPtr<nsIURI> trackingURI;
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -134,21 +134,17 @@ PuppetWidget::InfallibleCreate(nsIWidget
   PuppetWidget* parent = static_cast<PuppetWidget*>(aParent);
   if (parent) {
     parent->SetChild(this);
     mLayerManager = parent->GetLayerManager();
   }
   else {
     Resize(mBounds.X(), mBounds.Y(), mBounds.Width(), mBounds.Height(), false);
   }
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    mMemoryPressureObserver = new MemoryPressureObserver(this);
-    obs->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
-  }
+  mMemoryPressureObserver = MemoryPressureObserver::Create(this);
 }
 
 nsresult
 PuppetWidget::Create(nsIWidget* aParent,
                      nsNativeWidget aNativeParent,
                      const LayoutDeviceIntRect& aRect,
                      nsWidgetInitData* aInitData)
 {
@@ -188,19 +184,19 @@ PuppetWidget::Destroy()
     return;
   }
   mOnDestroyCalled = true;
 
   Base::OnDestroy();
   Base::Destroy();
   mPaintTask.Revoke();
   if (mMemoryPressureObserver) {
-    mMemoryPressureObserver->Remove();
+    mMemoryPressureObserver->Unregister();
+    mMemoryPressureObserver = nullptr;
   }
-  mMemoryPressureObserver = nullptr;
   mChild = nullptr;
   if (mLayerManager) {
     mLayerManager->Destroy();
   }
   mLayerManager = nullptr;
   mTabChild = nullptr;
 }
 
@@ -1132,46 +1128,25 @@ PuppetWidget::PaintTask::Run()
 void
 PuppetWidget::PaintNowIfNeeded()
 {
   if (IsVisible() && mPaintTask.IsPending()) {
     Paint();
   }
 }
 
-NS_IMPL_ISUPPORTS(PuppetWidget::MemoryPressureObserver, nsIObserver)
-
-NS_IMETHODIMP
-PuppetWidget::MemoryPressureObserver::Observe(nsISupports* aSubject,
-                                              const char* aTopic,
-                                              const char16_t* aData)
+void
+PuppetWidget::OnMemoryPressure(layers::MemoryPressureReason aWhy)
 {
-  if (!mWidget) {
-    return NS_OK;
+  if (aWhy != MemoryPressureReason::LOW_MEMORY_ONGOING &&
+      mVisible &&
+      mLayerManager &&
+      XRE_IsContentProcess()) {
+    mLayerManager->ClearCachedResources();
   }
-
-  if (strcmp("memory-pressure", aTopic) == 0 &&
-      !StringBeginsWith(nsDependentString(aData),
-                        NS_LITERAL_STRING("low-memory-ongoing"))) {
-    if (!mWidget->mVisible && mWidget->mLayerManager &&
-        XRE_IsContentProcess()) {
-      mWidget->mLayerManager->ClearCachedResources();
-    }
-  }
-  return NS_OK;
-}
-
-void
-PuppetWidget::MemoryPressureObserver::Remove()
-{
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, "memory-pressure");
-  }
-  mWidget = nullptr;
 }
 
 bool
 PuppetWidget::NeedsPaint()
 {
   // e10s popups are handled by the parent process, so never should be painted here
   if (XRE_IsContentProcess() &&
       gRemoteDesktopBehaviorEnabled &&
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -22,29 +22,31 @@
 #include "nsCOMArray.h"
 #include "nsIKeyEventInPluginCallback.h"
 #include "nsIScreenManager.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentCache.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/layers/MemoryPressureObserver.h"
 
 namespace mozilla {
 
 namespace dom {
 class TabChild;
 } // namespace dom
 
 namespace widget {
 
 struct AutoCacheNativeKeyCommands;
 
 class PuppetWidget : public nsBaseWidget
                    , public TextEventDispatcherListener
+                   , public layers::MemoryPressureListener
 {
   typedef mozilla::CSSRect CSSRect;
   typedef mozilla::dom::TabChild TabChild;
   typedef mozilla::gfx::DrawTarget DrawTarget;
 
   // Avoiding to make compiler confused between mozilla::widget and nsIWidget.
   typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
   typedef mozilla::widget::TextEventDispatcherListener
@@ -317,16 +319,17 @@ public:
   NS_IMETHOD_(void) OnRemovedFrom(
                       TextEventDispatcher* aTextEventDispatcher) override;
   NS_IMETHOD_(void) WillDispatchKeyboardEvent(
                       TextEventDispatcher* aTextEventDispatcher,
                       WidgetKeyboardEvent& aKeyboardEvent,
                       uint32_t aIndexOfKeypress,
                       void* aData) override;
 
+  virtual void OnMemoryPressure(layers::MemoryPressureReason aWhy) override;
 private:
   nsresult Paint();
 
   void SetChild(PuppetWidget* aChild);
 
   nsresult RequestIMEToCommitComposition(bool aCancel);
   nsresult NotifyIMEOfFocusChange(const IMENotification& aIMENotification);
   nsresult NotifyIMEOfSelectionChange(const IMENotification& aIMENotification);
@@ -353,41 +356,29 @@ private:
     NS_DECL_NSIRUNNABLE
     explicit PaintTask(PuppetWidget* widget)
      : Runnable("PuppetWidget::PaintTask"), mWidget(widget) {}
     void Revoke() { mWidget = nullptr; }
   private:
     PuppetWidget* mWidget;
   };
 
-  class MemoryPressureObserver : public nsIObserver {
-  public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-    explicit MemoryPressureObserver(PuppetWidget* aWidget) : mWidget(aWidget) {}
-    void Remove();
-  private:
-    virtual ~MemoryPressureObserver() {}
-    PuppetWidget* mWidget;
-  };
-  friend class MemoryPressureObserver;
-
   // TabChild normally holds a strong reference to this PuppetWidget
   // or its root ancestor, but each PuppetWidget also needs a
   // reference back to TabChild (e.g. to delegate nsIWidget IME calls
   // to chrome) So we hold a weak reference to TabChild here.  Since
   // it's possible for TabChild to outlive the PuppetWidget, we clear
   // this weak reference in Destroy()
   TabChild* mTabChild;
   // The "widget" to which we delegate events if we don't have an
   // event handler.
   RefPtr<PuppetWidget> mChild;
   LayoutDeviceIntRegion mDirtyRegion;
   nsRevocableEventPtr<PaintTask> mPaintTask;
-  RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
+  RefPtr<layers::MemoryPressureObserver> mMemoryPressureObserver;
   // XXX/cjones: keeping this around until we teach LayerManager to do
   // retained-content-only transactions
   RefPtr<DrawTarget> mDrawTarget;
   // IME
   IMENotificationRequests mIMENotificationRequestsOfParent;
   InputContext mInputContext;
   // mNativeIMEContext is initialized when this dispatches every composition
   // event both from parent process's widget and TextEventDispatcher in same
--- a/widget/tests/mochitest.ini
+++ b/widget/tests/mochitest.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 support-files = utils.js
 
 [test_AltGr_key_events_in_web_content_on_windows.html]
 skip-if = toolkit != 'windows' || headless # headless: Bug 1410525
 [test_assign_event_data.html]
 subsuite = clipboard
-skip-if = toolkit == "cocoa" || (toolkit == 'android' && debug) # Mac: Bug 933303, Android bug 1285414
+skip-if = toolkit == "cocoa" || (toolkit == 'android' && debug) || android_version == '24' # Mac: Bug 933303, Android bug 1285414
 [test_keypress_event_with_alt_on_mac.html]
 skip-if = toolkit != "cocoa"
 [test_picker_no_crash.html]
 skip-if = toolkit != "windows" || e10s # Bug 1267491
 support-files = window_picker_no_crash_child.html
 [test_scrollbar_colors.html]
 skip-if = os == 'linux' || os == 'android' # bug 1460109