Merge mozilla-central to autoland. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Fri, 31 Aug 2018 00:51:52 +0300
changeset 482495 962a34323e709b36f5e46cc89c5a603ab859b5ff
parent 482494 0cdebe3c53f883266962a601a78baaa08cd62d0f (current diff)
parent 482454 ff62eab1d2ed5abce22e2922bc0103b0a3df9000 (diff)
child 482496 c8d6a8dca44d819667a0cacfed8f0b3f411e4bc5
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersmerge
milestone63.0a1
Merge mozilla-central to autoland. a=merge CLOSED TREE
browser/themes/shared/browser.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7987,17 +7987,17 @@ var ToolbarIconColor = {
       case "toolbarvisibilitychange":
         // toolbar changes dont require reset of the cached color values
         break;
       case "tabsintitlebar":
         this._windowState.tabsintitlebar = reasonValue;
         break;
     }
 
-    let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true])";
+    let toolbarSelector = ".browser-toolbar:not([collapsed=true])";
     if (AppConstants.platform == "macosx")
       toolbarSelector += ":not([type=menubar])";
 
     // The getComputedStyle calls and setting the brighttext are separated in
     // two loops to avoid flushing layout and making it dirty repeatedly.
     let cachedLuminances = this._toolbarLuminanceCache;
     let luminances = new Map();
     for (let toolbar of document.querySelectorAll(toolbarSelector)) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -667,17 +667,17 @@ xmlns="http://www.w3.org/1999/xhtml"
     </hbox>
 #endif
   </hbox>
 </vbox>
 
   <toolbox id="navigator-toolbox">
     <!-- Menu -->
     <toolbar type="menubar" id="toolbar-menubar"
-             class="chromeclass-menubar titlebar-color"
+             class="browser-toolbar chromeclass-menubar titlebar-color"
              customizable="true"
              mode="icons"
 #ifdef MENUBAR_CAN_AUTOHIDE
              toolbarname="&menubarCmd.label;"
              accesskey="&menubarCmd.accesskey;"
              autohide="true"
 #endif
              context="toolbar-context-menu">
@@ -689,17 +689,17 @@ xmlns="http://www.w3.org/1999/xhtml"
 
 #ifndef XP_MACOSX
       <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"
             skipintoolbarset="true"/>
 #endif
     </toolbar>
 
     <toolbar id="TabsToolbar"
-             class="titlebar-color"
+             class="browser-toolbar titlebar-color"
              fullscreentoolbar="true"
              customizable="true"
              mode="icons"
              aria-label="&tabsToolbar.label;"
              context="toolbar-context-menu">
       <hbox class="titlebar-placeholder" type="pre-tabs"
             skipintoolbarset="true"/>
 
@@ -748,16 +748,17 @@ xmlns="http://www.w3.org/1999/xhtml"
 
 #ifdef XP_MACOSX
       <hbox class="titlebar-placeholder" type="fullscreen-button"
             skipintoolbarset="true"/>
 #endif
     </toolbar>
 
     <toolbar id="nav-bar"
+             class="browser-toolbar"
              aria-label="&navbarCmd.label;"
              fullscreentoolbar="true" mode="icons" customizable="true"
              customizationtarget="nav-bar-customization-target"
              overflowable="true"
              overflowbutton="nav-bar-overflow-button"
              overflowtarget="widget-overflow-list"
              overflowpanel="widget-overflow"
              context="toolbar-context-menu">
@@ -1054,17 +1055,17 @@ xmlns="http://www.w3.org/1999/xhtml"
         <toolbarbutton id="close-button"
                        tooltiptext="&fullScreenClose.tooltip;"
                        oncommand="BrowserTryToCloseWindow();"/>
       </hbox>
     </toolbar>
 
     <toolbar id="PersonalToolbar"
              mode="icons"
-             class="chromeclass-directories"
+             class="browser-toolbar chromeclass-directories"
              context="toolbar-context-menu"
              toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
              collapsed="true"
              customizable="true">
       <toolbaritem id="personal-bookmarks"
                    title="&bookmarksToolbarItem.label;"
                    cui-areatype="toolbar"
                    removable="true">
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -13,17 +13,17 @@ prefs =
 support-files =
   head.js
 [browser_appmenu.js]
 skip-if = asan || debug || (os == 'win' && bits == 32) # Bug 1382809, bug 1369959, Win32 because of intermittent OOM failures
 [browser_preferences_usage.js]
 skip-if = !debug
 [browser_startup.js]
 [browser_startup_content.js]
-skip-if = !e10s || verify
+skip-if = !e10s
 [browser_startup_flicker.js]
 run-if = debug || devedition || nightly_build # Requires startupRecorder.js, which isn't shipped everywhere by default
 [browser_tabclose_grow.js]
 [browser_tabclose.js]
 [browser_tabopen.js]
 skip-if = (verify && (os == 'mac'))
 [browser_tabopen_squeeze.js]
 [browser_tabstrip_overflow_underflow.js]
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -44,21 +44,20 @@ support-files =
 [browser_new_file_whitelisted_http_tab.js]
 skip-if = !e10s # Test only relevant for e10s.
 [browser_new_tab_insert_position.js]
 skip-if = (debug && os == 'linux' && bits == 32) #Bug 1455882, disabled on Linux32 for almost permafailing
 support-files = file_new_tab_page.html
 [browser_new_tab_in_privileged_process_pref.js]
 skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_new_web_tab_in_file_process_pref.js]
-skip-if = !e10s || (os == 'mac' && debug) # Pref and test only relevant for e10s. #Bug 1366137
+skip-if = !e10s # Pref and test only relevant for e10s.
 [browser_newwindow_tabstrip_overflow.js]
 [browser_open_newtab_start_observer_notification.js]
 [browser_opened_file_tab_navigated_to_web.js]
-skip-if = (os == 'mac' && debug) || (os == 'linux' && debug) # Bug 1356347
 [browser_overflowScroll.js]
 [browser_pinnedTabs_clickOpen.js]
 [browser_pinnedTabs_closeByKeyboard.js]
 [browser_pinnedTabs.js]
 [browser_positional_attributes.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_preloadedBrowser_zoom.js]
 [browser_reload_deleted_file.js]
--- a/browser/base/content/test/tabs/browser_new_web_tab_in_file_process_pref.js
+++ b/browser/base/content/test/tabs/browser_new_web_tab_in_file_process_pref.js
@@ -49,71 +49,71 @@ add_task(async function() {
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that new http tab opened from file loaded in file content process.");
 
     // Check that reload doesn't break the file content process affinity.
     if (httpTab != gBrowser.selectedTab) {
       httpTab = await BrowserTestUtils.switchTab(gBrowser, httpTab);
       httpBrowser = httpTab.linkedBrowser;
     }
-    let promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    let promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, TEST_HTTP);
     document.getElementById("reload-button").doCommand();
     await promiseLoad;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after reload.");
 
     // Check that same-origin load doesn't break the affinity.
-    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, TEST_HTTP + "foo");
     httpBrowser.loadURI(TEST_HTTP + "foo");
     await promiseLoad;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after same origin load.");
 
     // Check that history back doesn't break the affinity.
     let promiseLocation =
       BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HTTP);
     httpBrowser.goBack();
     await promiseLocation;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after history back.");
 
     // Check that history forward doesn't break the affinity.
     promiseLocation =
       BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HTTP + "foo");
-    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, TEST_HTTP + "foo");
     httpBrowser.goForward();
     await promiseLocation;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after history forward.");
 
     // Check that goto history index doesn't break the affinity.
     promiseLocation = BrowserTestUtils.waitForLocationChange(gBrowser, TEST_HTTP);
     httpBrowser.gotoIndex(0);
     await promiseLocation;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after history gotoIndex.");
 
     // Check that file:// URI load doesn't break the affinity.
-    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, uriString);
     httpBrowser.loadURI(uriString);
     await promiseLoad;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after file:// load.");
 
     // Check that location change doesn't break the affinity.
-    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, TEST_HTTP);
     await ContentTask.spawn(httpBrowser, TEST_HTTP, uri => {
       content.location = uri;
     });
     await promiseLoad;
     await CheckBrowserInPid(httpBrowser, filePid,
       "Check that http tab still in file content process after location change.");
 
     // Check that cross-origin load does break the affinity.
-    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser);
+    promiseLoad = BrowserTestUtils.browserLoaded(httpBrowser, false, TEST_CROSS_ORIGIN);
     httpBrowser.loadURI(TEST_CROSS_ORIGIN);
     await promiseLoad;
     await CheckBrowserNotInPid(httpBrowser, filePid,
       "Check that http tab not in file content process after cross origin load.");
     is(httpBrowser.remoteType, E10SUtils.WEB_REMOTE_TYPE,
       "Check that tab now has web remote type.");
 
     // Check that history back now remains in the web content process.
--- a/browser/base/content/test/tabs/browser_opened_file_tab_navigated_to_web.js
+++ b/browser/base/content/test/tabs/browser_opened_file_tab_navigated_to_web.js
@@ -1,12 +1,13 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 
 const TEST_FILE = "dummy_page.html";
+const WEB_ADDRESS = "http://example.org/";
 
 // Test for bug 1321020.
 add_task(async function() {
   let dir = getChromeDir(getResolvedURI(gTestPath));
   dir.append(TEST_FILE);
 
   // The file can be a symbolic link on local build.  Normalize it to make sure
   // the path matches to the actual URI opened in the new tab.
@@ -30,13 +31,13 @@ add_task(async function() {
   let openedTab = await promiseTabOpened;
   registerCleanupFunction(async function() {
     BrowserTestUtils.removeTab(openedTab);
   });
 
   let openedBrowser = openedTab.linkedBrowser;
 
   // Ensure that new file:// tab can be navigated to web content.
-  openedBrowser.loadURI("http://example.org/");
-  let href = await BrowserTestUtils.browserLoaded(openedBrowser);
-  is(href, "http://example.org/",
+  openedBrowser.loadURI(WEB_ADDRESS);
+  let href = await BrowserTestUtils.browserLoaded(openedBrowser, false, WEB_ADDRESS);
+  is(href, WEB_ADDRESS,
      "Check that new file:// page has navigated successfully to web content");
 });
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -64,21 +64,21 @@
 }
 
 #navigator-toolbox {
   -moz-appearance: none;
   background-color: transparent;
   border-top: none;
 }
 
-#navigator-toolbox > toolbar {
+.browser-toolbar {
   padding: 0;
 }
 
-#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+.browser-toolbar:not(.titlebar-color) {
   background-color: var(--toolbar-bgcolor);
   background-image: var(--toolbar-bgimage);
   color: var(--toolbar-color, inherit);
   -moz-appearance: none;
   border-style: none;
 }
 
 #TabsToolbar:not([collapsed="true"]) + #nav-bar {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -137,17 +137,17 @@
 }
 
 /** End titlebar **/
 
 #main-window[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
   border-top: 1px solid rgba(0,0,0,0.65);
 }
 
-#navigator-toolbox > toolbar:not(#TabsToolbar) {
+.browser-toolbar:not(#TabsToolbar) {
   -moz-appearance: none;
   background: var(--toolbar-bgcolor);
   color: var(--toolbar-color, inherit);
 }
 
 /* Draw the bottom border of the tabs toolbar when it's not using
    -moz-appearance: toolbar. */
 #main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -42,42 +42,42 @@
 }
 
 :root[sessionrestored] #nav-bar:-moz-lwtheme {
   transition: @themeTransition@;
 }
 
 /* Bookmark toolbar */
 
-#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar) {
+#PersonalToolbar {
   overflow: -moz-hidden-unscrollable;
   max-height: 4em;
   padding: 0 6px 2px;
 }
 
-:root[sessionrestored] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar) {
+:root[sessionrestored] #PersonalToolbar {
   transition: min-height 170ms ease-out, max-height 170ms ease-out, @themeTransition@;
 }
 
-#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar)[collapsed=true] {
+#PersonalToolbar[collapsed=true] {
   min-height: 0.1px;
   max-height: 0;
 }
 
-:root[sessionrestored] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar)[collapsed=true] {
+:root[sessionrestored] #PersonalToolbar[collapsed=true] {
   transition: min-height 170ms ease-out, max-height 170ms ease-out, visibility 170ms linear;
 }
 
-#navigator-toolbox > toolbar[customizing]:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar) {
+#PersonalToolbar[customizing] {
   outline: 1px dashed;
   outline-offset: -3px;
   -moz-outline-radius: 2px;
 }
 
-#navigator-toolbox > toolbar[customizing]:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):empty {
+#PersonalToolbar[customizing]:empty {
   /* Avoid the toolbar having no height when there's no items in it */
   min-height: 22px;
 }
 
 /* Required for Library animation */
 
 #navigator-toolbox {
   position: relative;
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -58,17 +58,17 @@ toolbar[brighttext] .toolbarbutton-1 {
 /* Change the base colors for the browser chrome */
 
 #TabsToolbar,
 #navigator-toolbox {
   background-color: var(--chrome-background-color);
   color: var(--chrome-color);
 }
 
-#navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
+.browser-toolbar:not(.titlebar-color),
 .browserContainer > findbar,
 #browser-bottombox {
   background-color: var(--chrome-secondary-background-color) !important;
   background-image: none !important;
   color: var(--chrome-color);
 }
 
 /* URL bar and search bar*/
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -270,18 +270,18 @@
   :root[darkwindowframe="true"] .titlebar-color:not(:-moz-window-inactive):not(:-moz-lwtheme) {
     color: white;
   }
 
   /* Show borders on Win 7 & 8, but not on 10 and later: */
   @media (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     /* Vertical toolbar border */
-    #main-window[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
-    #main-window[sizemode=normal] #navigator-toolbox > toolbar:-moz-lwtheme {
+    #main-window[sizemode=normal] .browser-toolbar:not(.titlebar-color):not(:-moz-lwtheme),
+    #main-window[sizemode=normal] .browser-toolbar:-moz-lwtheme {
       border-left: 1px solid @glassShadowColor@;
       border-right: 1px solid @glassShadowColor@;
       background-clip: padding-box;
     }
 
     #main-window[sizemode=normal] #navigator-toolbox::after {
       box-shadow: 1px 0 0 @glassShadowColor@, -1px 0 0 @glassShadowColor@;
       margin-left: 1px;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -126,21 +126,21 @@
 @media (-moz-os-version: windows-win7) {
   :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
   :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
     padding-top: calc(var(--space-above-tabbar) + 4px);
   }
 }
 
 #navigator-toolbox,
-#navigator-toolbox > toolbar {
+.browser-toolbar {
   -moz-appearance: none;
 }
 
-#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+.browser-toolbar:not(.titlebar-color) {
   background-color: var(--toolbar-bgcolor);
   background-image: var(--toolbar-bgimage);
   background-clip: padding-box;
   color: var(--toolbar-color, inherit);
 }
 
 /*
  * Windows 7 draws the chrome background color as the tab background
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -457,16 +457,19 @@ def valid_mt(path):
 
 
 set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x)))
 
 
 link = check_prog('LINKER', ('lld-link.exe', 'link.exe'),
                   paths=toolchain_search_path)
 
+host_link = check_prog('HOST_LINKER', ('lld-link.exe', 'link.exe'),
+                       paths=toolchain_search_path)
+
 add_old_configure_assignment('LINKER', link)
 
 
 # Normally, we'd just have CC, etc. set to absolute paths, but the build system
 # doesn't currently handle properly the case where the paths contain spaces.
 # Additionally, there's the issue described in toolchain.configure, in
 # valid_compiler().
 @depends(sdk_bin_path)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -587,17 +587,17 @@ ifdef ENABLE_STRIP
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_PROGRAM): $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS) $(call mkdir_deps,$(DEPTH)/dist/host/bin)
 	$(REPORT_BUILD)
 ifeq (_WINNT,$(GNU_CC)_$(HOST_OS_ARCH))
-	$(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $(HOST_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $(HOST_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$(notdir $@).manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$(notdir $@).manifest and $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$(notdir $@).manifest' $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		else \
 			echo 'Embedding manifest from $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
@@ -648,17 +648,17 @@ ifdef ENABLE_STRIP
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_SIMPLE_PROGRAMS): host_%$(HOST_BIN_SUFFIX): host_%.$(OBJ_SUFFIX) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (WINNT_,$(HOST_OS_ARCH)_$(GNU_CC))
-	$(LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 ifneq (,$(HOST_CPPSRCS)$(USE_HOST_CXX))
 	$(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXX_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 	$(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_C_LDFLAGS) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 endif
 ifndef CROSS_COMPILE
@@ -685,18 +685,17 @@ endif
 	$(REPORT_BUILD)
 	$(RM) $@
 	$(HOST_AR) $(HOST_AR_FLAGS) $(HOST_OBJS)
 
 $(HOST_SHARED_LIBRARY): $(HOST_OBJS) Makefile
 	$(REPORT_BUILD)
 	$(RM) $@
 ifdef _MSC_VER
-	# /!\ We assume host and target are using the same compiler
-	$(LINKER) -NOLOGO -DLL -OUT:$@ $(HOST_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -DLL -OUT:$@ $(HOST_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 	$(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 
 # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files,
 # so instead of deleting .o files after repacking them into a dylib, we make
 # symlinks back to the originals. The symlinks are a no-op for stabs debugging,
 # so no need to conditionalize on OS version or debugging format.
--- a/devtools/client/inspector/fonts/components/Font.js
+++ b/devtools/client/inspector/fonts/components/Font.js
@@ -13,18 +13,17 @@ const FontOrigin = createFactory(require
 const FontPreview = createFactory(require("./FontPreview"));
 
 const Types = require("../types");
 
 class Font extends PureComponent {
   static get propTypes() {
     return {
       font: PropTypes.shape(Types.font).isRequired,
-      fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
-      onPreviewFonts: PropTypes.func.isRequired,
+      onPreviewClick: PropTypes.func,
       onToggleFontHighlight: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
@@ -103,23 +102,20 @@ class Font extends PureComponent {
     }
 
     return dom.div({ className: "font-family-name" }, family);
   }
 
   render() {
     const {
       font,
-      fontOptions,
-      onPreviewFonts,
+      onPreviewClick,
       onToggleFontHighlight,
     } = this.props;
 
-    const { previewText } = fontOptions;
-
     const {
       CSSFamilyName,
       previewUrl,
       rule,
       ruleText,
     } = font;
 
     return dom.li(
@@ -127,15 +123,15 @@ class Font extends PureComponent {
         className: "font",
       },
       dom.div(
         {},
         this.renderFontFamilyName(CSSFamilyName),
         FontName({ font, onToggleFontHighlight })
       ),
       FontOrigin({ font }),
-      FontPreview({ previewText, previewUrl, onPreviewFonts }),
+      FontPreview({ onPreviewClick, previewUrl }),
       this.renderFontCSSCode(rule, ruleText)
     );
   }
 }
 
 module.exports = Font;
--- a/devtools/client/inspector/fonts/components/FontList.js
+++ b/devtools/client/inspector/fonts/components/FontList.js
@@ -1,48 +1,87 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const Services = require("Services");
+const {
+  createElement,
+  createFactory,
+  createRef,
+  Fragment,
+  PureComponent,
+} = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const Font = createFactory(require("./Font"));
+const FontPreviewInput = createFactory(require("./FontPreviewInput"));
 
 const Types = require("../types");
 
+const PREF_FONT_EDITOR = "devtools.inspector.fonteditor.enabled";
+
 class FontList extends PureComponent {
   static get propTypes() {
     return {
       fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
       fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired,
-      onPreviewFonts: PropTypes.func.isRequired,
+      onPreviewTextChange: PropTypes.func.isRequired,
       onToggleFontHighlight: PropTypes.func.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.onPreviewClick = this.onPreviewClick.bind(this);
+    this.previewInputRef = createRef();
+  }
+
+  /**
+   * Handler for clicks on the font preview image.
+   * Requests the FontPreviewInput component, if one exists, to focus its input field.
+   */
+  onPreviewClick() {
+    this.previewInputRef.current && this.previewInputRef.current.focus();
+  }
+
   render() {
     const {
       fonts,
       fontOptions,
-      onPreviewFonts,
+      onPreviewTextChange,
       onToggleFontHighlight
     } = this.props;
 
-    return dom.ul(
+    const { previewText } = fontOptions;
+    const { onPreviewClick } = this;
+
+    const list = dom.ul(
       {
         className: "fonts-list"
       },
       fonts.map((font, i) => Font({
         key: i,
         font,
-        fontOptions,
-        onPreviewFonts,
+        onPreviewClick,
         onToggleFontHighlight,
       }))
     );
+
+    // Show the font preview input only when the font editor is enabled.
+    const previewInput = Services.prefs.getBoolPref(PREF_FONT_EDITOR) ?
+      FontPreviewInput({
+        ref: this.previewInputRef,
+        onPreviewTextChange,
+        previewText,
+      })
+      :
+      null;
+
+    return createElement(Fragment, null, previewInput, list);
   }
 }
 
 module.exports = FontList;
--- a/devtools/client/inspector/fonts/components/FontOverview.js
+++ b/devtools/client/inspector/fonts/components/FontOverview.js
@@ -17,58 +17,58 @@ const Types = require("../types");
 
 const PREF_FONT_EDITOR = "devtools.inspector.fonteditor.enabled";
 
 class FontOverview extends PureComponent {
   static get propTypes() {
     return {
       fontData: PropTypes.shape(Types.fontData).isRequired,
       fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
-      onPreviewFonts: PropTypes.func.isRequired,
+      onPreviewTextChange: PropTypes.func.isRequired,
       onToggleFontHighlight: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onToggleFontHighlightGlobal = (font, show) => {
       this.props.onToggleFontHighlight(font, show, false);
     };
   }
 
   renderElementFonts() {
     const {
       fontData,
       fontOptions,
-      onPreviewFonts,
+      onPreviewTextChange,
       onToggleFontHighlight,
     } = this.props;
     const { fonts } = fontData;
 
     return fonts.length ?
       FontList({
         fonts,
         fontOptions,
-        onPreviewFonts,
+        onPreviewTextChange,
         onToggleFontHighlight,
       })
       :
       dom.div(
         {
           className: "devtools-sidepanel-no-result"
         },
         getStr("fontinspector.noFontsOnSelectedElement")
       );
   }
 
   renderFonts() {
     const {
       fontData,
       fontOptions,
-      onPreviewFonts,
+      onPreviewTextChange,
     } = this.props;
 
     const header = Services.prefs.getBoolPref(PREF_FONT_EDITOR)
       ? getStr("fontinspector.allFontsOnPageHeader")
       : getStr("fontinspector.otherFontsInPageHeader");
 
     const fonts = Services.prefs.getBoolPref(PREF_FONT_EDITOR)
       ? fontData.allFonts
@@ -81,17 +81,17 @@ class FontOverview extends PureComponent
     return Accordion({
       items: [
         {
           header,
           component: FontList,
           componentProps: {
             fontOptions,
             fonts,
-            onPreviewFonts,
+            onPreviewTextChange,
             onToggleFontHighlight: this.onToggleFontHighlightGlobal
           },
           opened: false
         }
       ]
     });
   }
 
--- a/devtools/client/inspector/fonts/components/FontPreview.js
+++ b/devtools/client/inspector/fonts/components/FontPreview.js
@@ -4,93 +4,40 @@
 
 "use strict";
 
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const Types = require("../types");
-const { getStr } = require("../utils/l10n");
 
 class FontPreview extends PureComponent {
   static get propTypes() {
     return {
-      previewText: Types.fontOptions.previewText.isRequired,
+      onPreviewClick: PropTypes.func,
       previewUrl: Types.font.previewUrl.isRequired,
-      onPreviewFonts: PropTypes.func.isRequired,
     };
   }
 
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      // Is the text preview input field currently focused?
-      isFocused: false,
+  static get defaultProps() {
+    return {
+      onPreviewClick: () => {},
     };
-
-    this.onBlur = this.onBlur.bind(this);
-    this.onClick = this.onClick.bind(this);
-    this.onChange = this.onChange.bind(this);
-  }
-
-  componentDidUpdate() {
-    if (this.state.isFocused) {
-      const input = this.fontPreviewInput;
-      input.focus();
-      input.selectionStart = input.selectionEnd = input.value.length;
-    }
-  }
-
-  onBlur() {
-    this.setState({ isFocused: false });
-  }
-
-  onClick(event) {
-    this.setState({ isFocused: true });
-    event.stopPropagation();
-  }
-
-  onChange(event) {
-    this.props.onPreviewFonts(event.target.value);
   }
 
   render() {
     const {
-      previewText,
+      onPreviewClick,
       previewUrl,
     } = this.props;
 
-    const { isFocused } = this.state;
-
-    return dom.div(
+    return dom.img(
       {
-        className: "font-preview-container",
-      },
-      isFocused ?
-        dom.input(
-          {
-            type: "search",
-            className: "font-preview-input devtools-searchinput",
-            value: previewText,
-            onBlur: this.onBlur,
-            onChange: this.onChange,
-            ref: input => {
-              this.fontPreviewInput = input;
-            }
-          }
-        )
-        :
-        null,
-      dom.img(
-        {
-          className: "font-preview",
-          src: previewUrl,
-          onClick: this.onClick,
-          title: !isFocused ? getStr("fontinspector.editPreview") : "",
-        }
-      )
+        className: "font-preview",
+        onClick: onPreviewClick,
+        src: previewUrl,
+      }
     );
   }
 }
 
 module.exports = FontPreview;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/FontPreviewInput.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createRef, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const Types = require("../types");
+const { getStr } = require("../utils/l10n");
+
+const PREVIEW_TEXT_MAX_LENGTH = 30;
+
+class FontPreviewInput extends PureComponent {
+  static get propTypes() {
+    return {
+      onPreviewTextChange: PropTypes.func.isRequired,
+      previewText: Types.fontOptions.previewText.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.onChange = this.onChange.bind(this);
+    this.onFocus = this.onFocus.bind(this);
+    this.inputRef = createRef();
+
+    this.state = {
+      value: this.props.previewText
+    };
+  }
+
+  onChange(e) {
+    const value = e.target.value;
+    this.props.onPreviewTextChange(value);
+
+    this.setState((prevState) => {
+      return { ...prevState, value };
+    });
+  }
+
+  onFocus(e) {
+    e.target.select();
+  }
+
+  focus() {
+    this.inputRef.current.focus();
+  }
+
+  render() {
+    return dom.div(
+      {
+        id: "font-preview-input-container",
+      },
+      dom.input({
+        className: "devtools-searchinput",
+        onChange: this.onChange,
+        onFocus: this.onFocus,
+        maxlength: PREVIEW_TEXT_MAX_LENGTH,
+        placeholder: getStr("fontinspector.previewTextPlaceholder"),
+        ref: this.inputRef,
+        type: "text",
+        value: this.state.value,
+      })
+    );
+  }
+}
+
+module.exports = FontPreviewInput;
--- a/devtools/client/inspector/fonts/components/FontsApp.js
+++ b/devtools/client/inspector/fonts/components/FontsApp.js
@@ -17,30 +17,30 @@ const Types = require("../types");
 class FontsApp extends PureComponent {
   static get propTypes() {
     return {
       fontData: PropTypes.shape(Types.fontData).isRequired,
       fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
       fontEditorEnabled: PropTypes.bool.isRequired,
       fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
       onInstanceChange: PropTypes.func.isRequired,
-      onPreviewFonts: PropTypes.func.isRequired,
+      onPreviewTextChange: PropTypes.func.isRequired,
       onPropertyChange: PropTypes.func.isRequired,
       onToggleFontHighlight: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       fontData,
       fontEditor,
       fontEditorEnabled,
       fontOptions,
       onInstanceChange,
-      onPreviewFonts,
+      onPreviewTextChange,
       onPropertyChange,
       onToggleFontHighlight,
     } = this.props;
 
     return dom.div(
       {
         className: "theme-sidebar inspector-tabpanel",
         id: "sidebar-panel-fontinspector"
@@ -49,16 +49,16 @@ class FontsApp extends PureComponent {
         fontEditor,
         onInstanceChange,
         onPropertyChange,
         onToggleFontHighlight,
       }),
       FontOverview({
         fontData,
         fontOptions,
-        onPreviewFonts,
+        onPreviewTextChange,
         onToggleFontHighlight,
       })
     );
   }
 }
 
 module.exports = connect(state => state)(FontsApp);
--- a/devtools/client/inspector/fonts/components/moz.build
+++ b/devtools/client/inspector/fonts/components/moz.build
@@ -7,15 +7,16 @@
 DevToolsModules(
     'Font.js',
     'FontEditor.js',
     'FontList.js',
     'FontName.js',
     'FontOrigin.js',
     'FontOverview.js',
     'FontPreview.js',
+    'FontPreviewInput.js',
     'FontPropertyValue.js',
     'FontsApp.js',
     'FontSize.js',
     'FontStyle.js',
     'FontWeight.js',
     'LineHeight.js',
 )
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -78,17 +78,17 @@ class FontInspector {
     // Values of variable font registered axes may be written to CSS font properties under
     // certain cascade circumstances and platform support. @see `getWriterForAxis(axis)`
     this.writers = new Map();
 
     this.snapshotChanges = debounce(this.snapshotChanges, 100, this);
     this.syncChanges = debounce(this.syncChanges, 100, this);
     this.onInstanceChange = this.onInstanceChange.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
-    this.onPreviewFonts = this.onPreviewFonts.bind(this);
+    this.onPreviewTextChange = debounce(this.onPreviewTextChange, 100, this);
     this.onPropertyChange = this.onPropertyChange.bind(this);
     this.onRulePropertyUpdated = debounce(this.onRulePropertyUpdated, 100, this);
     this.onToggleFontHighlight = this.onToggleFontHighlight.bind(this);
     this.onThemeChanged = this.onThemeChanged.bind(this);
     this.update = this.update.bind(this);
     this.updateFontVariationSettings = this.updateFontVariationSettings.bind(this);
 
     this.init();
@@ -98,17 +98,17 @@ class FontInspector {
     if (!this.inspector) {
       return;
     }
 
     const fontsApp = FontsApp({
       fontEditorEnabled: Services.prefs.getBoolPref(PREF_FONT_EDITOR),
       onInstanceChange: this.onInstanceChange,
       onToggleFontHighlight: this.onToggleFontHighlight,
-      onPreviewFonts: this.onPreviewFonts,
+      onPreviewTextChange: this.onPreviewTextChange,
       onPropertyChange: this.onPropertyChange,
     });
 
     const provider = createElement(Provider, {
       id: "fontinspector",
       key: "fontinspector",
       store: this.store,
       title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
@@ -678,17 +678,17 @@ class FontInspector {
         this.logTelemetryProbesOnNewNode();
       }).catch(e => console.error(e));
     }
   }
 
   /**
    * Handler for change in preview input.
    */
-  onPreviewFonts(value) {
+  onPreviewTextChange(value) {
     this.store.dispatch(updatePreviewText(value));
     this.update();
   }
 
   /**
    * Handler for changes to any CSS font property value or variable font axis value coming
    * from the Font Editor. This handler calls the appropriate method to preview the
    * changes on the page and update the store.
--- a/devtools/client/inspector/fonts/reducers/font-options.js
+++ b/devtools/client/inspector/fonts/reducers/font-options.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {
   UPDATE_PREVIEW_TEXT,
 } = require("../actions/index");
 
 const INITIAL_FONT_OPTIONS = {
-  previewText: "Abc",
+  previewText: "",
 };
 
 const reducers = {
 
   [UPDATE_PREVIEW_TEXT](fontOptions, { previewText }) {
     return Object.assign({}, fontOptions, { previewText });
   },
 
--- a/devtools/client/inspector/fonts/test/head.js
+++ b/devtools/client/inspector/fonts/test/head.js
@@ -56,51 +56,44 @@ var openFontInspectorForURL = async func
     testActor,
     toolbox,
     inspector,
     view: inspector.fontinspector
   };
 };
 
 /**
- * Focus one of the preview inputs, clear it, type new text into it and wait for the
+ * Focus the preview input, clear it, type new text into it and wait for the
  * preview images to be updated.
  *
  * @param {FontInspector} view - The FontInspector instance.
  * @param {String} text - The text to preview.
  */
 async function updatePreviewText(view, text) {
   info(`Changing the preview text to '${text}'`);
 
   const doc = view.document;
-  const previewImg = doc.querySelector("#sidebar-panel-fontinspector .font-preview");
-
-  info("Clicking the font preview element to turn it to edit mode");
-  const onClick = once(doc, "click");
-  previewImg.click();
-  await onClick;
-
-  const input = previewImg.parentNode.querySelector("input");
-  is(doc.activeElement, input, "The input was focused.");
+  const input = doc.querySelector("#font-preview-input-container input");
+  input.focus();
 
   info("Blanking the input field.");
   while (input.value.length) {
     const update = view.inspector.once("fontinspector-updated");
     EventUtils.sendKey("BACK_SPACE", doc.defaultView);
     await update;
   }
 
   if (text) {
-    info("Typing the specified text to the input field.");
-    const update = waitForNEvents(view.inspector, "fontinspector-updated", text.length);
+    info(`Typing "${text}" into the input field.`);
+    const update = view.inspector.once("fontinspector-updated");
     EventUtils.sendString(text, doc.defaultView);
     await update;
   }
 
-  is(input.value, text, "The input now contains the correct text.");
+  is(input.value, text, `The input now contains "${text}".`);
 }
 
 /**
 *  Get all of the <li> elements for the fonts used on the currently selected element.
 *
 *  NOTE: This method is used by tests which check the old Font Inspector. It, along with
 *  the tests should be removed once the Font Editor reaches Firefox Stable.
 *  @see https://bugzilla.mozilla.org/show_bug.cgi?id=1485324
--- a/devtools/client/locales/en-US/font-inspector.properties
+++ b/devtools/client/locales/en-US/font-inspector.properties
@@ -12,21 +12,16 @@ fontinspector.system=system
 # LOCALIZATION NOTE (fontinspector.noFontsOnSelectedElement): This label is shown when
 # no fonts found on the selected element.
 fontinspector.noFontsOnSelectedElement=No fonts were found for the current element.
 
 # LOCALIZATION NOTE (fontinspector.otherFontsInPageHeader): This is the text for the
 # header of a collapsible section containing other fonts used in the page.
 fontinspector.otherFontsInPageHeader=Other fonts in page
 
-# LOCALIZATION NOTE (fontinspector.editPreview): This is the text that appears in a
-# tooltip on hover of a font preview string. Clicking on the string opens a text input
-# where users can type to change the preview text.
-fontinspector.editPreview=Click to edit preview
-
 # LOCALIZATION NOTE (fontinspector.copyURL): This is the text that appears in a tooltip
 # displayed when the user hovers over the copy icon next to the font URL.
 # Clicking the copy icon copies the full font URL to the user's clipboard
 fontinspector.copyURL=Copy URL
 
 # LOCALIZATION NOTE (fontinspector.customInstanceName): Think of instances as presets
 # (groups of settings that apply in bulk to a thing). Instances have names. When the user
 # creates a new instance, it doesn't have a name. This is the text that appears as the
@@ -67,8 +62,12 @@ fontinspector.lineHeightLabel=Line heigh
 
 # LOCALIZATION NOTE (fontinspector.allFontsOnPageHeader): Header for the section listing
 # all the fonts on the current page.
 fontinspector.allFontsOnPageHeader=All fonts on page
 
 # LOCALIZATION NOTE (fontinspector.usedFontsLabel): Label for the Font Editor section
 # which shows the fonts used on the selected element.
 fontinspector.usedFontsLabel=Fonts used
+
+# LOCALIZATION NOTE (fontinspector.previewTextPlaceholder): Placeholder for the input
+# where the user can type text to get a preview of it using a font.
+fontinspector.previewTextPlaceholder=Font preview text
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -4,24 +4,26 @@
 
 /* CSS Variables specific to the font editor that aren't defined by the themes */
 :root {
   --slider-thumb-color: var(--grey-50);
   --slider-track-color: var(--grey-30);
   --input-background-color: white;
   --input-border-color: var(--grey-30);
   --input-text-color: var(--grey-90);
+  --preview-input-background: var(--theme-toolbar-background);
 }
 
 :root.theme-dark {
   --slider-thumb-color: var(--grey-40);
   --slider-track-color: var(--grey-60);
   --input-background-color: var(--grey-70);
   --input-border-color: var(--grey-70);
   --input-text-color: var(--grey-40);
+  --preview-input-background: #222225;
 }
 
 #sidebar-panel-fontinspector {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
@@ -77,54 +79,35 @@
 }
 
 #font-container .theme-twisty {
   display: inline-block;
   cursor: pointer;
   vertical-align: bottom;
 }
 
-.font-preview-container {
-  grid-column: 2;
-  grid-row: 1 / span 2;
-  overflow: hidden;
-  display: grid;
-  place-items: center end;
-  position: relative;
+#font-preview-input-container {
+  background: var(--preview-input-background);
+  border-bottom: 1px solid var(--theme-splitter-color);
+  display: flex;
+  height: 23px;
+}
+
+#font-preview-input-container input {
+  background-image: none;
+  flex: 1;
+  padding-left: 14px;
 }
 
 .font-preview {
+  grid-column: 2;
+  grid-row: 1 / span 2;
+  object-fit: contain;
   height: 50px;
-  display: block;
-}
-
-.font-preview:hover {
-  cursor: text;
-  background-image: linear-gradient(to right,
-    var(--grey-40) 3px, transparent 3px, transparent);
-  background-size: 6px 1px;
-  background-repeat: repeat-x;
-  background-position-y: 45px;
-}
-
-#font-container .font-preview-input {
-  position: absolute;
-  top: 5px;
-  left: 0;
-  width: calc(100% - 5px);
-  height: calc(100% - 10px);
-  background: transparent;
-  color: transparent;
-  border-radius: 0;
-  padding: 0;
-}
-
-.font-preview-input::-moz-selection {
-  background: transparent;
-  color: transparent;
+  width: 100%;
 }
 
 .font-name,
 .font-family-name {
   font-weight: normal;
   white-space: nowrap;
 }
 
--- a/devtools/client/themes/images/command-responsivemode.svg
+++ b/devtools/client/themes/images/command-responsivemode.svg
@@ -1,12 +1,4 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
-  fill="context-fill #0b0b0b">
-  <path d="M3.5 8h9v1h-9z"/>
-  <path d="M4 2v12h8v-12h-8
-           m-1 0c0-.55 .45-1 1-1h8c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1
-           h-8c-.55 0-1-.45-1-1z"/>
-  <circle cx="8" cy="11.5" r="1" fill="none" stroke="context-fill"
-    stroke-width="1"/>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
+  <path d="M1,4H6A1,1,0,0,1,7,5V15a1,1,0,0,1-1,1H1a1,1,0,0,1-1-1V5A1,1,0,0,1,1,4ZM1,6v8H6V6Z"/>
+  <path d="M10,16H9a1,1,0,0,1,0-2h4V2H5V3H3V1A1,1,0,0,1,4,0H14a1,1,0,0,1,1,1V15a1,1,0,0,1-1,1Z"/>
 </svg>
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -278,16 +278,18 @@
 }
 
 #command-button-screenshot::before {
   background-image: var(--command-screenshot-image);
 }
 
 #command-button-responsive::before {
   background-image: var(--command-responsive-image);
+  fill: var(--theme-toolbar-photon-icon-color);
+  -moz-context-properties: fill;
 }
 
 #command-button-scratchpad::before {
   background-image: var(--command-scratchpad-image);
 }
 
 #command-button-pick::before {
   background-image: var(--command-pick-image);
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -537,18 +537,23 @@ Navigator::CookieEnabled()
   doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
 
   if (!codebaseURI) {
     // Not a codebase, so technically can't set cookies, but let's
     // just return the default value.
     return cookieEnabled;
   }
 
-  return AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(mWindow,
-                                                                 codebaseURI);
+  if (AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(mWindow,
+                                                              codebaseURI)) {
+    return true;
+  }
+
+  AntiTrackingCommon::NotifyRejection(mWindow);
+  return false;
 }
 
 bool
 Navigator::OnLine()
 {
   return !NS_IsOffline();
 }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8912,38 +8912,20 @@ bool
 nsContentUtils::StorageDisabledByAntiTracking(nsPIDOMWindowInner* aWindow,
                                               nsIChannel* aChannel,
                                               nsIPrincipal* aPrincipal,
                                               nsIURI* aURI)
 {
   bool disabled =
     StorageDisabledByAntiTrackingInternal(aWindow, aChannel, aPrincipal, aURI);
   if (disabled && sAntiTrackingControlCenterUIEnabled) {
-    nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
-    if (!thirdPartyUtil) {
-      return false;
-    }
-
-    nsCOMPtr<nsPIDOMWindowOuter> pwin;
     if (aWindow) {
-      auto* outer = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
-      if (outer) {
-        pwin = outer->GetTopOuter();
-      }
+      AntiTrackingCommon::NotifyRejection(aWindow);
     } else if (aChannel) {
-      nsCOMPtr<mozIDOMWindowProxy> win;
-      nsresult rv = thirdPartyUtil->GetTopWindowForChannel(aChannel,
-                                                           getter_AddRefs(win));
-      NS_ENSURE_SUCCESS(rv, false);
-      pwin = nsPIDOMWindowOuter::From(win);
-    }
-
-    if (pwin && aChannel) {
-      pwin->NotifyContentBlockingState(
-        nsIWebProgressListener::STATE_BLOCKED_TRACKING_COOKIES, aChannel);
+      AntiTrackingCommon::NotifyRejection(aChannel);
     }
   }
   return disabled;
 }
 
 // static, private
 nsContentUtils::StorageAccess
 nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
--- a/extensions/pref/autoconfig/src/nsReadConfig.cpp
+++ b/extensions/pref/autoconfig/src/nsReadConfig.cpp
@@ -96,17 +96,17 @@ NS_IMETHODIMP nsReadConfig::Observe(nsIS
 
     if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) {
         rv = readConfigFile();
         // Don't show error alerts if the sandbox is enabled, just show
         // sandbox warning.
         if (NS_FAILED(rv)) {
             if (sandboxEnabled) {
                 nsContentUtils::ReportToConsoleNonLocalized(
-                NS_LITERAL_STRING("Autoconfig is sandboxed by default. See https://www.mozilla.org/firefox/enterprise/releasenotes/ for more information."),
+                NS_LITERAL_STRING("Autoconfig is sandboxed by default. See https://support.mozilla.org/products/firefox-enterprise for more information."),
                 nsIScriptError::warningFlag,
                 NS_LITERAL_CSTRING("autoconfig"),
                 nullptr);
             } else {
                 rv = DisplayError();
                 if (NS_FAILED(rv)) {
                     nsCOMPtr<nsIAppStartup> appStartup =
                         do_GetService(NS_APPSTARTUP_CONTRACTID);
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -27,17 +27,20 @@ void brush_vs(
     int brush_flags,
     vec4 unused
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = snap_device_pos(vi) +
         src_task.common_data.task_rect.p0 -
         src_task.content_origin;
-    vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
+    vUv = vec3(
+        uv * gl_Position.w / texture_size, // multiply by W to compensate for perspective interpolation
+        src_task.common_data.texture_layer_index
+    );
 
     vec2 uv0 = src_task.common_data.task_rect.p0;
     vec2 uv1 = uv0 + src_task.common_data.task_rect.size;
     vUvClipBounds = vec4(uv0, uv1) / texture_size.xyxy;
 
     float lumR = 0.2126;
     float lumG = 0.7152;
     float lumB = 0.0722;
@@ -119,17 +122,18 @@ vec3 Invert(vec3 Cs, float amount) {
 vec3 Brightness(vec3 Cs, float amount) {
     // Apply the brightness factor.
     // Resulting color needs to be clamped to output range
     // since we are pre-multiplying alpha in the shader.
     return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
 }
 
 Fragment brush_fs() {
-    vec4 Cs = texture(sColor0, vUv);
+    vec2 base_uv = vUv.xy * gl_FragCoord.w;
+    vec4 Cs = texture(sColor0, vec3(base_uv, vUv.z));
 
     if (Cs.a == 0.0) {
         return Fragment(vec4(0.0)); // could also `discard`
     }
 
     // Un-premultiply the input.
     float alpha = Cs.a;
     vec3 color = Cs.rgb / Cs.a;
@@ -150,14 +154,14 @@ Fragment brush_fs() {
             alpha *= vAmount;
             break;
         default:
             color = vColorMat * color + vColorOffset;
     }
 
     // Fail-safe to ensure that we don't sample outside the rendered
     // portion of a blend source.
-    alpha *= point_inside_rect(vUv.xy, vUvClipBounds.xy, vUvClipBounds.zw);
+    alpha *= point_inside_rect(base_uv, vUvClipBounds.xy, vUvClipBounds.zw);
 
     // Pre-multiply the alpha into the output value.
     return Fragment(alpha * vec4(color, 1.0));
 }
 #endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -824,30 +824,32 @@ impl AlphaBatchBuilder {
                                             Some(ref surface) => {
                                                 let key = BatchKey::new(
                                                     BatchKind::Brush(BrushBatchKind::Blend),
                                                     BlendMode::PremultipliedAlpha,
                                                     BatchTextures::render_target_cache(),
                                                 );
 
                                                 let filter_mode = match filter {
+                                                    FilterOp::Identity => 1, // matches `Contrast(1)`
                                                     FilterOp::Blur(..) => 0,
                                                     FilterOp::Contrast(..) => 1,
                                                     FilterOp::Grayscale(..) => 2,
                                                     FilterOp::HueRotate(..) => 3,
                                                     FilterOp::Invert(..) => 4,
                                                     FilterOp::Saturate(..) => 5,
                                                     FilterOp::Sepia(..) => 6,
                                                     FilterOp::Brightness(..) => 7,
                                                     FilterOp::Opacity(..) => 8,
                                                     FilterOp::DropShadow(..) => 9,
                                                     FilterOp::ColorMatrix(..) => 10,
                                                 };
 
                                                 let user_data = match filter {
+                                                    FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
                                                     FilterOp::Contrast(amount) |
                                                     FilterOp::Grayscale(amount) |
                                                     FilterOp::Invert(amount) |
                                                     FilterOp::Saturate(amount) |
                                                     FilterOp::Sepia(amount) |
                                                     FilterOp::Brightness(amount) |
                                                     FilterOp::Opacity(_, amount) => {
                                                         (amount * 65536.0) as i32
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -319,16 +319,17 @@ pub struct ClipStore {
 
 // A clip chain instance is what gets built for a given clip
 // chain id + local primitive region + positioning node.
 #[derive(Debug)]
 pub struct ClipChainInstance {
     pub clips_range: ClipNodeRange,
     pub local_clip_rect: LayoutRect,
     pub has_non_root_coord_system: bool,
+    pub has_non_local_clips: bool,
     pub world_clip_rect: WorldRect,
 }
 
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_nodes: Vec::new(),
             clip_chain_nodes: Vec::new(),
@@ -526,30 +527,33 @@ impl ClipStore {
 
         // Now, we've collected all the clip nodes that *potentially* affect this
         // primitive region, and reduced the size of the prim region as much as possible.
 
         // Run through the clip nodes, and see which ones affect this prim region.
 
         let first_clip_node_index = self.clip_node_indices.len() as u32;
         let mut has_non_root_coord_system = false;
+        let mut has_non_local_clips = false;
 
         // For each potential clip node
         for node_info in self.clip_node_info.drain(..) {
             let node = &mut self.clip_nodes[node_info.node_index.0 as usize];
 
             // See how this clip affects the prim region.
             let clip_result = match node_info.conversion {
                 ClipSpaceConversion::Local => {
                     node.item.get_clip_result(&local_bounding_rect)
                 }
                 ClipSpaceConversion::Offset(offset) => {
+                    has_non_local_clips = true;
                     node.item.get_clip_result(&local_bounding_rect.translate(&-offset))
                 }
                 ClipSpaceConversion::Transform(ref transform) => {
+                    has_non_local_clips = true;
                     node.item.get_clip_result_complex(
                         transform,
                         &world_bounding_rect,
                     )
                 }
             };
 
             match clip_result {
@@ -598,16 +602,17 @@ impl ClipStore {
             first: first_clip_node_index,
             count: self.clip_node_indices.len() as u32 - first_clip_node_index,
         };
 
         // Return a valid clip chain instance
         Some(ClipChainInstance {
             clips_range,
             has_non_root_coord_system,
+            has_non_local_clips,
             local_clip_rect,
             world_clip_rect,
         })
     }
 }
 
 #[derive(Debug)]
 pub struct LineDecorationClipSource {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2206,17 +2206,18 @@ impl Primitive {
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                 );
 
             match segment_clip_chain {
                 Some(segment_clip_chain) => {
-                    if segment_clip_chain.clips_range.count == 0 {
+                    if segment_clip_chain.clips_range.count == 0 ||
+                       (!segment.may_need_clip_mask && !segment_clip_chain.has_non_local_clips) {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                         continue;
                     }
 
                     let bounds = (segment_clip_chain.world_clip_rect * frame_context.device_pixel_scale)
                         .round_out()
                         .to_i32();
 
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -196,16 +196,17 @@ pub const OPACITY_EPSILON: f32 = 0.001;
 pub trait FilterOpHelpers {
     fn is_visible(&self) -> bool;
     fn is_noop(&self) -> bool;
 }
 
 impl FilterOpHelpers for FilterOp {
     fn is_visible(&self) -> bool {
         match *self {
+            FilterOp::Identity |
             FilterOp::Blur(..) |
             FilterOp::Brightness(..) |
             FilterOp::Contrast(..) |
             FilterOp::Grayscale(..) |
             FilterOp::HueRotate(..) |
             FilterOp::Invert(..) |
             FilterOp::Saturate(..) |
             FilterOp::Sepia(..) |
@@ -214,16 +215,17 @@ impl FilterOpHelpers for FilterOp {
             FilterOp::Opacity(_, amount) => {
                 amount > OPACITY_EPSILON
             }
         }
     }
 
     fn is_noop(&self) -> bool {
         match *self {
+            FilterOp::Identity => false, // this is intentional
             FilterOp::Blur(length) => length == 0.0,
             FilterOp::Brightness(amount) => amount == 1.0,
             FilterOp::Contrast(amount) => amount == 1.0,
             FilterOp::Grayscale(amount) => amount == 0.0,
             FilterOp::HueRotate(amount) => amount == 0.0,
             FilterOp::Invert(amount) => amount == 0.0,
             FilterOp::Opacity(_, amount) => amount >= 1.0,
             FilterOp::Saturate(amount) => amount == 1.0,
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -538,16 +538,19 @@ pub enum MixBlendMode {
     Hue = 12,
     Saturation = 13,
     Color = 14,
     Luminosity = 15,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
+    /// Filter that does no transformation of the colors, needed for
+    /// debug purposes only.
+    Identity,
     Blur(f32),
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>, f32),
     Saturate(f32),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -11,17 +11,17 @@ use serde::ser::{Serializer, SerializeSe
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::marker::PhantomData;
 use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
 use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
 use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
-use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient};
+use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient, GradientBuilder};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform};
 use {LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode, PipelineId};
 use {PropertyBinding, PushReferenceFrameDisplayListItem, PushStackingContextDisplayItem};
 use {RadialGradient, RadialGradientDisplayItem, RectangleDisplayItem, ReferenceFrame};
 use {ScrollFrameDisplayItem, ScrollSensitivity, Shadow, SpecificDisplayItem, StackingContext};
 use {StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace};
 use {YuvData, YuvImageDisplayItem};
@@ -942,17 +942,22 @@ impl DisplayListBuilder {
                 index += 1;
             }
         }
 
         self.data = temp.data;
         index
     }
 
-    fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
+    /// Add an item to the display list.
+    ///
+    /// NOTE: It is usually preferable to use the specialized methods to push
+    /// display items. Pushing unexpected or invalid items here may
+    /// result in WebRender panicking or behaving in unexpected ways.
+    pub fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
         serialize_fast(
             &mut self.data,
             &DisplayItem {
                 item,
                 clip_and_scroll: *self.clip_stack.last().unwrap(),
                 info: *info,
             },
         )
@@ -1013,17 +1018,21 @@ impl DisplayListBuilder {
         bincode::serialize_into(
             &mut &mut data[byte_size_offset..],
             &byte_size,
         ).unwrap();
 
         debug_assert_eq!(len, count);
     }
 
-    fn push_iter<I>(&mut self, iter: I)
+    /// Push items from an iterator to the display list.
+    ///
+    /// NOTE: Pushing unexpected or invalid items to the display list
+    /// may result in panic and confusion.
+    pub fn push_iter<I>(&mut self, iter: I)
     where
         I: IntoIterator,
         I::IntoIter: ExactSizeIterator + Clone,
         I::Item: Serialize,
     {
         Self::push_iter_impl(&mut self.data, iter);
     }
 
@@ -1107,139 +1116,44 @@ impl DisplayListBuilder {
         });
 
         for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
             self.push_item(item, info);
             self.push_iter(split_glyphs);
         }
     }
 
-    // Gradients can be defined with stops outside the range of [0, 1]
-    // when this happens the gradient needs to be normalized by adjusting
-    // the gradient stops and gradient line into an equivalent gradient
-    // with stops in the range [0, 1]. this is done by moving the beginning
-    // of the gradient line to where stop[0] and the end of the gradient line
-    // to stop[n-1]. this function adjusts the stops in place, and returns
-    // the amount to adjust the gradient line start and stop
-    fn normalize_stops(stops: &mut Vec<GradientStop>, extend_mode: ExtendMode) -> (f32, f32) {
-        assert!(stops.len() >= 2);
-
-        let first = *stops.first().unwrap();
-        let last = *stops.last().unwrap();
-
-        assert!(first.offset <= last.offset);
-
-        let stops_delta = last.offset - first.offset;
-
-        if stops_delta > 0.000001 {
-            for stop in stops {
-                stop.offset = (stop.offset - first.offset) / stops_delta;
-            }
-
-            (first.offset, last.offset)
-        } else {
-            // We have a degenerate gradient and can't accurately transform the stops
-            // what happens here depends on the repeat behavior, but in any case
-            // we reconstruct the gradient stops to something simpler and equivalent
-            stops.clear();
-
-            match extend_mode {
-                ExtendMode::Clamp => {
-                    // This gradient is two colors split at the offset of the stops,
-                    // so create a gradient with two colors split at 0.5 and adjust
-                    // the gradient line so 0.5 is at the offset of the stops
-                    stops.push(GradientStop { color: first.color, offset: 0.0, });
-                    stops.push(GradientStop { color: first.color, offset: 0.5, });
-                    stops.push(GradientStop { color: last.color, offset: 0.5, });
-                    stops.push(GradientStop { color: last.color, offset: 1.0, });
-
-                    let offset = last.offset;
-
-                    (offset - 0.5, offset + 0.5)
-                }
-                ExtendMode::Repeat => {
-                    // A repeating gradient with stops that are all in the same
-                    // position should just display the last color. I believe the
-                    // spec says that it should be the average color of the gradient,
-                    // but this matches what Gecko and Blink does
-                    stops.push(GradientStop { color: last.color, offset: 0.0, });
-                    stops.push(GradientStop { color: last.color, offset: 1.0, });
-
-                    (0.0, 1.0)
-                }
-            }
-        }
-    }
-
-    // NOTE: gradients must be pushed in the order they're created
-    // because create_gradient stores the stops in anticipation
+    /// NOTE: gradients must be pushed in the order they're created
+    /// because create_gradient stores the stops in anticipation.
     pub fn create_gradient(
         &mut self,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
-        mut stops: Vec<GradientStop>,
+        stops: Vec<GradientStop>,
         extend_mode: ExtendMode,
     ) -> Gradient {
-        let (start_offset, end_offset) =
-            DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
-
-        let start_to_end = end_point - start_point;
-
-        self.push_stops(&stops);
-
-        Gradient {
-            start_point: start_point + start_to_end * start_offset,
-            end_point: start_point + start_to_end * end_offset,
-            extend_mode,
-        }
+        let mut builder = GradientBuilder::with_stops(stops);
+        let gradient = builder.gradient(start_point, end_point, extend_mode);
+        self.push_stops(builder.stops());
+        gradient
     }
 
-    // NOTE: gradients must be pushed in the order they're created
-    // because create_gradient stores the stops in anticipation
+    /// NOTE: gradients must be pushed in the order they're created
+    /// because create_gradient stores the stops in anticipation.
     pub fn create_radial_gradient(
         &mut self,
         center: LayoutPoint,
         radius: LayoutSize,
-        mut stops: Vec<GradientStop>,
+        stops: Vec<GradientStop>,
         extend_mode: ExtendMode,
     ) -> RadialGradient {
-        if radius.width <= 0.0 || radius.height <= 0.0 {
-            // The shader cannot handle a non positive radius. So
-            // reuse the stops vector and construct an equivalent
-            // gradient.
-            let last_color = stops.last().unwrap().color;
-
-            let stops = [
-                GradientStop { offset: 0.0, color: last_color, },
-                GradientStop { offset: 1.0, color: last_color, },
-            ];
-
-            self.push_stops(&stops);
-
-            return RadialGradient {
-                center,
-                radius: LayoutSize::new(1.0, 1.0),
-                start_offset: 0.0,
-                end_offset: 1.0,
-                extend_mode,
-            };
-        }
-
-        let (start_offset, end_offset) =
-            DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
-
-        self.push_stops(&stops);
-
-        RadialGradient {
-            center,
-            radius,
-            start_offset,
-            end_offset,
-            extend_mode,
-        }
+        let mut builder = GradientBuilder::with_stops(stops);
+        let gradient = builder.radial_gradient(center, radius, extend_mode);
+        self.push_stops(builder.stops());
+        gradient
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         widths: BorderWidths,
         details: BorderDetails,
     ) {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_api/src/gradient_builder.rs
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use {ExtendMode, Gradient, GradientStop, LayoutPoint, LayoutSize, RadialGradient};
+
+
+/// Construct a gradient to be used in display lists.
+///
+/// Each gradient needs at least two stops.
+pub struct GradientBuilder {
+    stops: Vec<GradientStop>,
+}
+
+impl GradientBuilder {
+    /// Create a new gradient builder.
+    pub fn new() -> GradientBuilder {
+        GradientBuilder {
+            stops: Vec::new(),
+        }
+    }
+
+    /// Create a gradient builder with a list of stops.
+    pub fn with_stops(stops: Vec<GradientStop>) -> GradientBuilder {
+        GradientBuilder { stops }
+    }
+
+    /// Push an additional stop for the gradient.
+    pub fn push(&mut self, stop: GradientStop) {
+        self.stops.push(stop);
+    }
+
+    /// Get a reference to the list of stops.
+    pub fn stops(&self) -> &[GradientStop] {
+        self.stops.as_ref()
+    }
+
+    /// Produce a linear gradient, normalize the stops.
+    pub fn gradient(
+        &mut self,
+        start_point: LayoutPoint,
+        end_point: LayoutPoint,
+        extend_mode: ExtendMode,
+    ) -> Gradient {
+        let (start_offset, end_offset) = self.normalize(extend_mode);
+        let start_to_end = end_point - start_point;
+
+        Gradient {
+            start_point: start_point + start_to_end * start_offset,
+            end_point: start_point + start_to_end * end_offset,
+            extend_mode,
+        }
+    }
+
+    /// Produce a radial gradient, normalize the stops.
+    ///
+    /// Will replace the gradient with a single color
+    /// if the radius negative.
+    pub fn radial_gradient(
+        &mut self,
+        center: LayoutPoint,
+        radius: LayoutSize,
+        extend_mode: ExtendMode,
+    ) -> RadialGradient {
+        if radius.width <= 0.0 || radius.height <= 0.0 {
+            // The shader cannot handle a non positive radius. So
+            // reuse the stops vector and construct an equivalent
+            // gradient.
+            let last_color = self.stops.last().unwrap().color;
+
+            self.stops.clear();
+            self.stops.push(GradientStop { offset: 0.0, color: last_color, });
+            self.stops.push(GradientStop { offset: 1.0, color: last_color, });
+
+            return RadialGradient {
+                center,
+                radius: LayoutSize::new(1.0, 1.0),
+                start_offset: 0.0,
+                end_offset: 1.0,
+                extend_mode,
+            };
+        }
+
+        let (start_offset, end_offset) =
+            self.normalize(extend_mode);
+
+        RadialGradient {
+            center,
+            radius,
+            start_offset,
+            end_offset,
+            extend_mode,
+        }
+    }
+
+    /// Gradients can be defined with stops outside the range of [0, 1]
+    /// when this happens the gradient needs to be normalized by adjusting
+    /// the gradient stops and gradient line into an equivalent gradient
+    /// with stops in the range [0, 1]. this is done by moving the beginning
+    /// of the gradient line to where stop[0] and the end of the gradient line
+    /// to stop[n-1]. this function adjusts the stops in place, and returns
+    /// the amount to adjust the gradient line start and stop.
+    fn normalize(&mut self, extend_mode: ExtendMode) -> (f32, f32) {
+        let stops = &mut self.stops;
+        assert!(stops.len() >= 2);
+
+        let first = *stops.first().unwrap();
+        let last = *stops.last().unwrap();
+
+        assert!(first.offset <= last.offset);
+
+        let stops_delta = last.offset - first.offset;
+
+        if stops_delta > 0.000001 {
+            for stop in stops {
+                stop.offset = (stop.offset - first.offset) / stops_delta;
+            }
+
+            (first.offset, last.offset)
+        } else {
+            // We have a degenerate gradient and can't accurately transform the stops
+            // what happens here depends on the repeat behavior, but in any case
+            // we reconstruct the gradient stops to something simpler and equivalent
+            stops.clear();
+
+            match extend_mode {
+                ExtendMode::Clamp => {
+                    // This gradient is two colors split at the offset of the stops,
+                    // so create a gradient with two colors split at 0.5 and adjust
+                    // the gradient line so 0.5 is at the offset of the stops
+                    stops.push(GradientStop { color: first.color, offset: 0.0, });
+                    stops.push(GradientStop { color: first.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 0.5, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
+
+                    let offset = last.offset;
+
+                    (offset - 0.5, offset + 0.5)
+                }
+                ExtendMode::Repeat => {
+                    // A repeating gradient with stops that are all in the same
+                    // position should just display the last color. I believe the
+                    // spec says that it should be the average color of the gradient,
+                    // but this matches what Gecko and Blink does
+                    stops.push(GradientStop { color: last.color, offset: 0.0, });
+                    stops.push(GradientStop { color: last.color, offset: 1.0, });
+
+                    (0.0, 1.0)
+                }
+            }
+        }
+    }
+}
--- a/gfx/webrender_api/src/lib.rs
+++ b/gfx/webrender_api/src/lib.rs
@@ -28,18 +28,20 @@ extern crate time;
 
 
 mod api;
 pub mod channel;
 mod color;
 mod display_item;
 mod display_list;
 mod font;
+mod gradient_builder;
 mod image;
 mod units;
 
 pub use api::*;
 pub use color::*;
 pub use display_item::*;
 pub use display_list::*;
 pub use font::*;
+pub use gradient_builder::*;
 pub use image::*;
 pub use units::*;
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-816ff14c1805c145ccd60d0227d82b1541fc24eb
+5fa5c46e167ca834d8fec3bf662bf420418698f4
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -223,16 +223,17 @@ fn write_stacking_context(
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
     }
     // filters
     let mut filters = vec![];
     for filter in filter_iter {
         match filter {
+            FilterOp::Identity => { filters.push(Yaml::String("identity".into())) }
             FilterOp::Blur(x) => { filters.push(Yaml::String(format!("blur({})", x))) }
             FilterOp::Brightness(x) => { filters.push(Yaml::String(format!("brightness({})", x))) }
             FilterOp::Contrast(x) => { filters.push(Yaml::String(format!("contrast({})", x))) }
             FilterOp::Grayscale(x) => { filters.push(Yaml::String(format!("grayscale({})", x))) }
             FilterOp::HueRotate(x) => { filters.push(Yaml::String(format!("hue-rotate({})", x))) }
             FilterOp::Invert(x) => { filters.push(Yaml::String(format!("invert({})", x))) }
             FilterOp::Opacity(x, _) => {
                 filters.push(Yaml::String(format!("opacity({})",
--- a/gfx/wrench/src/yaml_helper.rs
+++ b/gfx/wrench/src/yaml_helper.rs
@@ -539,16 +539,19 @@ impl YamlHelper for Yaml {
 
     fn as_clip_mode(&self) -> Option<ClipMode> {
         self.as_str().and_then(|x| StringEnum::from_str(x))
     }
 
     fn as_filter_op(&self) -> Option<FilterOp> {
         if let Some(s) = self.as_str() {
             match parse_function(s) {
+                ("identity", _, _) => {
+                    Some(FilterOp::Identity)
+                }
                 ("blur", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Blur(args[0].parse().unwrap()))
                 }
                 ("brightness", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Brightness(args[0].parse().unwrap()))
                 }
                 ("contrast", ref args, _) if args.len() == 1 => {
                     Some(FilterOp::Contrast(args[0].parse().unwrap()))
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -33,17 +33,16 @@
 
 #include "jsexn.h"
 #include "jsnum.h"
 
 #include "builtin/TypedObject.h"
 #include "ctypes/Library.h"
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
-#include "gc/Zone.h"
 #include "jit/AtomicOperations.h"
 #include "js/AutoByteString.h"
 #include "js/StableStringChars.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "util/Windows.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
--- a/js/src/frontend/BinSource-macros.h
+++ b/js/src/frontend/BinSource-macros.h
@@ -2,27 +2,27 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 frontend_BinSource_macros_h
 #define frontend_BinSource_macros_h
 
+#include "vm/JSContext.h"
 
 // Evaluate an expression EXPR, checking that the result is not falsy.
 //
 // Throw `cx->alreadyReportedError()` if it returns 0/nullptr.
 #define BINJS_TRY(EXPR) \
     do { \
         if (!EXPR) \
             return cx_->alreadyReportedError(); \
     } while(false)
 
-
 // Evaluate an expression EXPR, checking that the result is not falsy.
 // In case of success, assign the result to VAR.
 //
 // Throw `cx->alreadyReportedError()` if it returns 0/nullptr.
 #define BINJS_TRY_VAR(VAR, EXPR) \
     do { \
         VAR = EXPR; \
         if (!VAR) \
--- a/js/src/frontend/BinSource.h
+++ b/js/src/frontend/BinSource.h
@@ -70,26 +70,25 @@ class BinASTParserBase: private JS::Auto
   protected:
     LifoAlloc& alloc_;
     ObjectBox* traceListHead_;
     UsedNameTracker& usedNames_;
   private:
     LifoAlloc::Mark tempPoolMark_;
     ParseNodeAllocator nodeAlloc_;
 
+    // ---- Parsing-related stuff
+  protected:
     // Root atoms and objects allocated for the parse tree.
     AutoKeepAtoms keepAtoms_;
 
-    // ---- Parsing-related stuff
-  protected:
     ParseContext* parseContext_;
     FullParseHandler factory_;
 
     friend class BinParseContext;
-
 };
 
 /**
  * The parser for a Binary AST.
  *
  * By design, this parser never needs to backtrack or look ahead. Errors are not
  * recoverable.
  */
--- a/js/src/frontend/BinSourceRuntimeSupport.h
+++ b/js/src/frontend/BinSourceRuntimeSupport.h
@@ -3,32 +3,30 @@
  * 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 frontend_BinSourceSupport_h
 #define frontend_BinSourceSupport_h
 
 #include "mozilla/HashFunctions.h"
-#include "mozilla/Maybe.h"
-
-#include "jsapi.h"
 
 #include "frontend/BinToken.h"
 
+#include "js/AllocPolicy.h"
 #include "js/HashTable.h"
 #include "js/Result.h"
 
 namespace js {
 
 // Support for parsing JS Binary ASTs.
 struct BinaryASTSupport {
-    using BinVariant  = js::frontend::BinVariant;
+    using BinVariant = js::frontend::BinVariant;
     using BinField = js::frontend::BinField;
-    using BinKind  = js::frontend::BinKind;
+    using BinKind = js::frontend::BinKind;
 
     // A structure designed to perform fast char* + length lookup
     // without copies.
     struct CharSlice {
         const char* start_;
         uint32_t byteLen_;
         CharSlice(const CharSlice& other)
             : start_(other.start_)
--- a/js/src/frontend/BinToken.cpp
+++ b/js/src/frontend/BinToken.cpp
@@ -5,22 +5,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "frontend/BinToken.h"
 
 #include "mozilla/Maybe.h"
 
 #include <sys/types.h>
 
-#include "jsapi.h"
-
 #include "frontend/BinSourceRuntimeSupport.h"
 #include "frontend/TokenStream.h"
-#include "gc/Zone.h"
 #include "js/Result.h"
+#include "vm/Runtime.h"
 
 namespace js {
 namespace frontend {
 
 const BinaryASTSupport::CharSlice BINKIND_DESCRIPTIONS[] = {
 #define WITH_VARIANT(_, SPEC_NAME) BinaryASTSupport::CharSlice(SPEC_NAME, sizeof(SPEC_NAME) - 1),
     FOR_EACH_BIN_KIND(WITH_VARIANT)
 #undef WITH_VARIANT
--- a/js/src/frontend/BinTokenReaderBase.cpp
+++ b/js/src/frontend/BinTokenReaderBase.cpp
@@ -2,17 +2,16 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "frontend/BinTokenReaderBase.h"
 
 #include "frontend/BinSource-macros.h"
-#include "gc/Zone.h"
 #include "js/Result.h"
 
 namespace js {
 namespace frontend {
 
 template<typename T> using ErrorResult = mozilla::GenericErrorResult<T>;
 
 // We use signalling NaN (which doesn't exist in the JS syntax)
@@ -59,16 +58,23 @@ ErrorResult<JS::Error&>
 BinTokenReaderBase::raiseInvalidField(const char* kind, const BinField field)
 {
     Sprinter out(cx_);
     BINJS_TRY(out.init());
     BINJS_TRY(out.printf("In %s, invalid field '%s'", kind, describeBinField(field)));
     return raiseError(out.string());
 }
 
+#ifdef DEBUG
+bool
+BinTokenReaderBase::hasRaisedError() const
+{
+    return cx_->isExceptionPending();
+}
+#endif
 
 size_t
 BinTokenReaderBase::offset() const
 {
     return current_ - start_;
 }
 
 TokenPos
--- a/js/src/frontend/BinTokenReaderBase.h
+++ b/js/src/frontend/BinTokenReaderBase.h
@@ -119,33 +119,37 @@ class MOZ_STACK_CLASS BinTokenReaderBase
      * @return true if `value` represents the next few chars in the
      * internal buffer, false otherwise. If `true`, the chars are consumed,
      * otherwise there is no side-effect.
      */
     template <size_t N>
     MOZ_MUST_USE bool matchConst(const char (&value)[N], bool expectNul) {
         MOZ_ASSERT(N > 0);
         MOZ_ASSERT(value[N - 1] == 0);
-        MOZ_ASSERT(!cx_->isExceptionPending());
+        MOZ_ASSERT(!hasRaisedError());
 
         if (current_ + N - 1 > stop_)
             return false;
 
         // Perform lookup, without side-effects.
         if (!std::equal(current_, current_ + N + (expectNul ? 0 : -1)/*implicit NUL*/, value))
             return false;
 
         // Looks like we have a match. Now perform side-effects
         current_ += N + (expectNul ? 0 : -1);
         updateLatestKnownGood();
         return true;
     }
 
     void updateLatestKnownGood();
 
+#ifdef DEBUG
+    bool hasRaisedError() const;
+#endif
+
     JSContext* cx_;
 
     // `true` if we have encountered an error. Errors are non recoverable.
     // Attempting to read from a poisoned tokenizer will cause assertion errors.
     bool poisoned_;
 
     // The first byte of the buffer. Not owned.
     const uint8_t* start_;
--- a/js/src/frontend/BinTokenReaderMultipart.cpp
+++ b/js/src/frontend/BinTokenReaderMultipart.cpp
@@ -11,17 +11,16 @@
 #include "mozilla/EndianUtils.h"
 #include "mozilla/Maybe.h"
 
 #include <utility>
 
 #include "frontend/BinSource-macros.h"
 #include "frontend/BinSourceRuntimeSupport.h"
 
-#include "gc/Zone.h"
 #include "js/Result.h"
 
 namespace js {
 namespace frontend {
 
 // The magic header, at the start of every binjs file.
 const char MAGIC_HEADER[] = "BINJS";
 // The latest format version understood by this tokenizer.
--- a/js/src/frontend/BinTokenReaderMultipart.h
+++ b/js/src/frontend/BinTokenReaderMultipart.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_BinTokenReaderMultipart_h
 #define frontend_BinTokenReaderMultipart_h
 
 #include "mozilla/Maybe.h"
 
+#include "frontend/BinSourceRuntimeSupport.h"
 #include "frontend/BinToken.h"
 #include "frontend/BinTokenReaderBase.h"
 
 #include "js/Result.h"
 
 namespace js {
 namespace frontend {
 
--- a/js/src/frontend/BinTokenReaderTester.cpp
+++ b/js/src/frontend/BinTokenReaderTester.cpp
@@ -8,17 +8,16 @@
 #include "frontend/BinTokenReaderTester.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Casting.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/PodOperations.h"
 
 #include "frontend/BinSource-macros.h"
-#include "gc/Zone.h"
 #include "js/Result.h"
 
 namespace js {
 namespace frontend {
 
 using BinFields = BinTokenReaderTester::BinFields;
 using AutoList = BinTokenReaderTester::AutoList;
 using AutoTaggedTuple = BinTokenReaderTester::AutoTaggedTuple;
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -21,18 +21,17 @@
 #include "vm/GlobalObject.h"
 #include "vm/JSContext.h"
 #include "vm/JSScript.h"
 #include "vm/TraceLogging.h"
 #include "wasm/AsmJS.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/GeckoProfiler-inl.h"
-#include "vm/JSObject-inl.h"
-#include "vm/JSScript-inl.h"
+#include "vm/JSContext-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::Maybe;
 using mozilla::Nothing;
 
 using JS::CompileOptions;
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -8,17 +8,16 @@
 #define frontend_BytecodeCompiler_h
 
 #include "mozilla/Maybe.h"
 
 #include "NamespaceImports.h"
 
 #include "js/CompileOptions.h"
 #include "vm/Scope.h"
-#include "vm/StringType.h"
 #include "vm/TraceLogging.h"
 
 class JSLinearString;
 
 namespace js {
 
 class LazyScript;
 class LifoAlloc;
--- a/js/src/frontend/BytecodeControlStructures.h
+++ b/js/src/frontend/BytecodeControlStructures.h
@@ -14,17 +14,16 @@
 #include <stdint.h>
 
 #include "ds/Nestable.h"
 #include "frontend/JumpList.h"
 #include "frontend/SharedContext.h"
 #include "frontend/TDZCheckCache.h"
 #include "gc/Rooting.h"
 #include "vm/BytecodeUtil.h"
-#include "vm/StringType.h"
 
 namespace js {
 namespace frontend {
 
 struct BytecodeEmitter;
 class EmitterScope;
 
 class NestableControl : public Nestable<NestableControl>
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 
 #include <string.h>
 
-#include "jsapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.h"
 #include "frontend/CForEmitter.h"
 #include "frontend/DoWhileEmitter.h"
@@ -44,20 +43,17 @@
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/Stack.h"
 #include "wasm/AsmJS.h"
 
 #include "frontend/ParseNode-inl.h"
-#include "vm/EnvironmentObject-inl.h"
-#include "vm/JSAtom-inl.h"
-#include "vm/JSScript-inl.h"
-#include "vm/NativeObject-inl.h"
+#include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::frontend;
 
 using mozilla::AssertedCast;
 using mozilla::DebugOnly;
 using mozilla::Maybe;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -16,18 +16,16 @@
 #include "frontend/EitherParser.h"
 #include "frontend/JumpList.h"
 #include "frontend/NameFunctions.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SourceNotes.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
-#include "vm/JSContext.h"
-#include "vm/JSScript.h"
 
 namespace js {
 namespace frontend {
 
 class CGConstList {
     Vector<Value> list;
   public:
     explicit CGConstList(JSContext* cx) : list(cx) {}
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "frontend/EmitterScope.h"
 
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "js/AutoByteString.h"
 
+#include "vm/GlobalObject.h"
+
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
--- a/js/src/frontend/EmitterScope.h
+++ b/js/src/frontend/EmitterScope.h
@@ -13,19 +13,21 @@
 #include <stdint.h>
 
 #include "ds/Nestable.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "frontend/NameCollections.h"
 #include "frontend/ParseContext.h"
 #include "frontend/SharedContext.h"
 #include "js/TypeDecls.h"
-#include "vm/Scope.h"
 
 namespace js {
+
+class Scope;
+
 namespace frontend {
 
 // A scope that introduces bindings.
 class EmitterScope : public Nestable<EmitterScope>
 {
     // The cache of bound names that may be looked up in the
     // scope. Initially populated as the set of names this scope binds. As
     // names are looked up in enclosing scopes, they are cached on the
--- a/js/src/frontend/ErrorReporter.h
+++ b/js/src/frontend/ErrorReporter.h
@@ -6,17 +6,20 @@
 
 #ifndef frontend_ErrorReporter_h
 #define frontend_ErrorReporter_h
 
 #include <stdarg.h> // for va_list
 #include <stddef.h> // for size_t
 #include <stdint.h> // for uint32_t
 
-#include "jsapi.h" // for JS::ReadOnlyCompileOptions
+#include "js/CompileOptions.h"
+#include "js/UniquePtr.h"
+
+class JSErrorNotes;
 
 namespace js {
 namespace frontend {
 
 class ErrorReporter
 {
   public:
     virtual const JS::ReadOnlyCompileOptions& options() const = 0;
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -4,25 +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 "frontend/FoldConstants.h"
 
 #include "mozilla/FloatingPoint.h"
 
 #include "jslibmath.h"
+#include "jsnum.h"
 
 #include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "js/Conversions.h"
 #include "vm/StringType.h"
 
-#include "vm/JSContext-inl.h"
-#include "vm/JSObject-inl.h"
-
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::IsNaN;
 using mozilla::IsNegative;
 using mozilla::NegativeInfinity;
 using mozilla::PositiveInfinity;
 using JS::GenericNaN;
--- a/js/src/frontend/ForOfLoopControl.h
+++ b/js/src/frontend/ForOfLoopControl.h
@@ -7,18 +7,16 @@
 #ifndef frontend_ForOfLoopControl_h
 #define frontend_ForOfLoopControl_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 
 #include <stdint.h>
 
-#include "jsapi.h"
-
 #include "frontend/BytecodeControlStructures.h"
 #include "frontend/TryEmitter.h"
 #include "vm/Iteration.h"
 
 namespace js {
 namespace frontend {
 
 struct BytecodeEmitter;
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/PodOperations.h"
 
 #include <string.h>
 
 #include "frontend/ParseNode.h"
 #include "frontend/SharedContext.h"
+#include "vm/JSContext.h"
 
 namespace js {
 
 class RegExpObject;
 
 namespace frontend {
 
 enum class SourceKind {
--- a/js/src/frontend/NameCollections.h
+++ b/js/src/frontend/NameCollections.h
@@ -5,21 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_NameCollections_h
 #define frontend_NameCollections_h
 
 #include "ds/InlineTable.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "js/Vector.h"
-#include "vm/Stack.h"
 
 namespace js {
 namespace frontend {
 
+class FunctionBox;
+
 // A pool of recyclable containers for use in the frontend. The Parser and
 // BytecodeEmitter create many maps for name analysis that are short-lived
 // (i.e., for the duration of parsing or emitting a lexical scope). Making
 // them recyclable cuts down significantly on allocator churn.
 template <typename RepresentativeCollection, typename ConcreteCollectionPool>
 class CollectionPool
 {
     using RecyclableCollections = Vector<void*, 32, SystemAllocPolicy>;
@@ -157,17 +158,16 @@ struct NameMapHasher : public DefaultHas
 template <typename MapValue>
 using RecyclableNameMap = InlineMap<JSAtom*,
                                     RecyclableAtomMapValueWrapper<MapValue>,
                                     24,
                                     NameMapHasher,
                                     SystemAllocPolicy>;
 
 using DeclaredNameMap = RecyclableNameMap<DeclaredNameInfo>;
-using CheckTDZMap = RecyclableNameMap<MaybeCheckTDZ>;
 using NameLocationMap = RecyclableNameMap<NameLocation>;
 using AtomIndexMap = RecyclableNameMap<uint32_t>;
 
 template <typename RepresentativeTable>
 class InlineTablePool
   : public CollectionPool<RepresentativeTable, InlineTablePool<RepresentativeTable>>
 {
   public:
@@ -330,17 +330,14 @@ class PooledVectorPtr
 
 #undef POOLED_COLLECTION_PTR_METHODS
 
 } // namespace frontend
 } // namespace js
 
 namespace mozilla {
 
-template <>
-struct IsPod<js::MaybeCheckTDZ> : TrueType {};
-
 template <typename T>
 struct IsPod<js::frontend::RecyclableAtomMapValueWrapper<T>> : IsPod<T> {};
 
 } // namespace mozilla
 
 #endif // frontend_NameCollections_h
--- a/js/src/frontend/ParseContext-inl.h
+++ b/js/src/frontend/ParseContext-inl.h
@@ -1,8 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 frontend_ParseContext_inl_h
 #define frontend_ParseContext_inl_h
 
 #include "frontend/ParseContext.h"
 
 namespace js {
 namespace frontend {
 
@@ -15,17 +21,16 @@ ParseContext::Statement::is<ParseContext
 
 template <>
 inline bool
 ParseContext::Statement::is<ParseContext::ClassStatement>() const
 {
     return kind_ == StatementKind::Class;
 }
 
-
 inline JS::Result<Ok, ParseContext::BreakStatementError>
 ParseContext::checkBreakStatement(PropertyName* label)
 {
     // Labeled 'break' statements target the nearest labeled statements (could
     // be any kind) with the same label. Unlabeled 'break' statements target
     // the innermost loop or switch statement.
     if (label) {
         auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -481,45 +481,19 @@ class ParseContext : public Nestable<Par
     // the Function or Generator constructor.
     bool isStandaloneFunctionBody_;
 
     // Set when encountering a super.property inside a method. We need to mark
     // the nearest super scope as needing a home object.
     bool superScopeNeedsHomeObject_;
 
   public:
-    inline ParseContext(JSContext* cx, ParseContext*& parent, SharedContext* sc, ErrorReporter& errorReporter,
-        class UsedNameTracker& usedNames, Directives* newDirectives, bool isFull)
-      : Nestable<ParseContext>(&parent),
-        traceLog_(sc->context,
-                  isFull
-                  ? TraceLogger_ParsingFull
-                  : TraceLogger_ParsingSyntax,
-                  errorReporter),
-        sc_(sc),
-        errorReporter_(errorReporter),
-        innermostStatement_(nullptr),
-        innermostScope_(nullptr),
-        varScope_(nullptr),
-        positionalFormalParameterNames_(cx->frontendCollectionPool()),
-        closedOverBindingsForLazy_(cx->frontendCollectionPool()),
-        innerFunctionsForLazy(cx, GCVector<JSFunction*, 8>(cx)),
-        newDirectives(newDirectives),
-        lastYieldOffset(NoYieldOffset),
-        lastAwaitOffset(NoAwaitOffset),
-        scriptId_(usedNames.nextScriptId()),
-        isStandaloneFunctionBody_(false),
-        superScopeNeedsHomeObject_(false)
-    {
-        if (isFunctionBox()) {
-            if (functionBox()->function()->isNamedLambda())
-                namedLambdaScope_.emplace(cx, parent, usedNames);
-            functionScope_.emplace(cx, parent, usedNames);
-        }
-    }
+    ParseContext(JSContext* cx, ParseContext*& parent, SharedContext* sc,
+                 ErrorReporter& errorReporter, UsedNameTracker& usedNames,
+                 Directives* newDirectives, bool isFull);
 
     MOZ_MUST_USE bool init();
 
     SharedContext* sc() {
         return sc_;
     }
 
     // `true` if we are in the body of a function definition.
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -3,16 +3,18 @@
  * 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 "frontend/ParseNode-inl.h"
 
 #include "mozilla/FloatingPoint.h"
 
+#include "jsnum.h"
+
 #include "frontend/Parser.h"
 
 #include "vm/JSContext-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::IsFinite;
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -4,19 +4,20 @@
  * 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 frontend_ParseNode_h
 #define frontend_ParseNode_h
 
 #include "mozilla/Attributes.h"
 
-#include "builtin/ModuleObject.h"
 #include "frontend/TokenStream.h"
+#include "vm/BytecodeUtil.h"
 #include "vm/Printer.h"
+#include "vm/Scope.h"
 
 // A few notes on lifetime of ParseNode trees:
 //
 // - All the `ParseNode` instances MUST BE explicitly allocated in the context's `LifoAlloc`.
 //   This is typically implemented by the `FullParseHandler` or it can be reimplemented with
 //   a custom `new_`.
 //
 // - The tree is bulk-deallocated when the parser is deallocated. Consequently, references
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -23,17 +23,17 @@
 #include "mozilla/Sprintf.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Utf8.h"
 
 #include <memory>
 #include <new>
 
-#include "jsapi.h"
+#include "jsnum.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FoldConstants.h"
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpParser.h"
@@ -45,18 +45,16 @@
 #include "vm/JSScript.h"
 #include "vm/RegExpObject.h"
 #include "vm/StringType.h"
 #include "wasm/AsmJS.h"
 
 #include "frontend/ParseContext-inl.h"
 #include "frontend/ParseNode-inl.h"
 #include "vm/EnvironmentObject-inl.h"
-#include "vm/JSAtom-inl.h"
-#include "vm/JSScript-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::PodCopy;
 using mozilla::PodZero;
@@ -375,16 +373,47 @@ EvalSharedContext::EvalSharedContext(JSC
                 break;
             }
 
             env = env->enclosingEnvironment();
         }
     }
 }
 
+ParseContext::ParseContext(JSContext* cx, ParseContext*& parent, SharedContext* sc,
+                           ErrorReporter& errorReporter, class UsedNameTracker& usedNames,
+                           Directives* newDirectives, bool isFull)
+  : Nestable<ParseContext>(&parent),
+    traceLog_(sc->context,
+              isFull
+              ? TraceLogger_ParsingFull
+              : TraceLogger_ParsingSyntax,
+              errorReporter),
+    sc_(sc),
+    errorReporter_(errorReporter),
+    innermostStatement_(nullptr),
+    innermostScope_(nullptr),
+    varScope_(nullptr),
+    positionalFormalParameterNames_(cx->frontendCollectionPool()),
+    closedOverBindingsForLazy_(cx->frontendCollectionPool()),
+    innerFunctionsForLazy(cx, GCVector<JSFunction*, 8>(cx)),
+    newDirectives(newDirectives),
+    lastYieldOffset(NoYieldOffset),
+    lastAwaitOffset(NoAwaitOffset),
+    scriptId_(usedNames.nextScriptId()),
+    isStandaloneFunctionBody_(false),
+    superScopeNeedsHomeObject_(false)
+{
+    if (isFunctionBox()) {
+        if (functionBox()->function()->isNamedLambda())
+            namedLambdaScope_.emplace(cx, parent, usedNames);
+        functionScope_.emplace(cx, parent, usedNames);
+    }
+}
+
 bool
 ParseContext::init()
 {
     if (scriptId_ == UINT32_MAX) {
         errorReporter_.reportErrorNoOffset(JSMSG_NEED_DIET, js_script_str);
         return false;
     }
 
@@ -457,16 +486,24 @@ UsedNameTracker::rewind(RewindToken toke
 {
     scriptCounter_ = token.scriptId;
     scopeCounter_ = token.scopeId;
 
     for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront())
         r.front().value().resetToScope(token.scriptId, token.scopeId);
 }
 
+#ifdef DEBUG
+bool
+FunctionBox::atomsAreKept()
+{
+    return context->zone()->hasKeptAtoms();
+}
+#endif
+
 FunctionBox::FunctionBox(JSContext* cx, ObjectBox* traceListHead,
                          JSFunction* fun, uint32_t toStringStart,
                          Directives directives, bool extraWarnings,
                          GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, Kind::FunctionBox, directives, extraWarnings),
     enclosingScope_(nullptr),
     namedLambdaBindings_(nullptr),
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -176,18 +176,16 @@
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "frontend/NameCollections.h"
 #include "frontend/ParseContext.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SyntaxParseHandler.h"
 #include "frontend/TokenStream.h"
-#include "js/CompileOptions.h"
-#include "vm/Iteration.h"
 
 namespace js {
 
 class ModuleObject;
 
 namespace frontend {
 
 class ParserBase;
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -9,20 +9,18 @@
 
 #include "jspubtd.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "ds/InlineTable.h"
 #include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
-#include "gc/Zone.h"
 #include "vm/BytecodeUtil.h"
-#include "vm/EnvironmentObject.h"
-#include "vm/JSAtom.h"
+#include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 
 namespace js {
 namespace frontend {
 
 class ParseContext;
 class ParseNode;
 
@@ -405,28 +403,32 @@ class FunctionBox : public ObjectBox, pu
 
     // Whether this function has nested functions.
     bool hasInnerFunctions_:1;
 
     FunctionBox(JSContext* cx, ObjectBox* traceListHead, JSFunction* fun,
                 uint32_t toStringStart, Directives directives, bool extraWarnings,
                 GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
 
+#ifdef DEBUG
+    bool atomsAreKept();
+#endif
+
     MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
-        MOZ_ASSERT(context->zone()->hasKeptAtoms());
+        MOZ_ASSERT(atomsAreKept());
         return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
     }
 
     MutableHandle<FunctionScope::Data*> functionScopeBindings() {
-        MOZ_ASSERT(context->zone()->hasKeptAtoms());
+        MOZ_ASSERT(atomsAreKept());
         return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(&functionScopeBindings_);
     }
 
     MutableHandle<VarScope::Data*> extraVarScopeBindings() {
-        MOZ_ASSERT(context->zone()->hasKeptAtoms());
+        MOZ_ASSERT(atomsAreKept());
         return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_);
     }
 
     void initFromLazyFunction();
     void initStandaloneFunction(Scope* enclosingScope);
     void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
 
     inline bool isLazyFunctionWithoutEnclosingScope() const {
--- a/js/src/frontend/TDZCheckCache.h
+++ b/js/src/frontend/TDZCheckCache.h
@@ -15,16 +15,18 @@
 #include "js/TypeDecls.h"
 #include "vm/Stack.h"
 
 namespace js {
 namespace frontend {
 
 struct BytecodeEmitter;
 
+using CheckTDZMap = RecyclableNameMap<MaybeCheckTDZ>;
+
 // A cache that tracks Temporal Dead Zone (TDZ) checks, so that any use of a
 // lexical variable that's dominated by an earlier use, or by evaluation of its
 // declaration (which will initialize it, perhaps to |undefined|), doesn't have
 // to redundantly check that the lexical variable has been initialized
 //
 // Each basic block should have a TDZCheckCache in scope. Some NestableControl
 // subclasses contain a TDZCheckCache.
 //
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -205,23 +205,27 @@
 #include "frontend/ErrorReporter.h"
 #include "frontend/TokenKind.h"
 #include "js/CompileOptions.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "util/Text.h"
 #include "util/Unicode.h"
 #include "vm/ErrorReporting.h"
-#include "vm/JSContext.h"
-#include "vm/RegExpShared.h"
+#include "vm/JSAtom.h"
+#include "vm/RegExpConstants.h"
 #include "vm/StringType.h"
 
+struct JSContext;
 struct KeywordInfo;
 
 namespace js {
+
+class AutoKeepAtoms;
+
 namespace frontend {
 
 struct TokenPos {
     uint32_t    begin;  // Offset of the token's first code unit.
     uint32_t    end;    // Offset of 1 past the token's last code unit.
 
     TokenPos()
       : begin(0),
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -2527,17 +2527,17 @@ Zone::prepareForCompacting()
 {
     FreeOp* fop = runtimeFromMainThread()->defaultFreeOp();
     discardJitCode(fop);
 }
 
 void
 GCRuntime::sweepTypesAfterCompacting(Zone* zone)
 {
-    zone->beginSweepTypes(rt->gc.releaseObservedTypes && !zone->isPreservingCode());
+    zone->beginSweepTypes(releaseObservedTypes && !zone->isPreservingCode());
 
     AutoClearTypeInferenceStateOnOOM oom(zone);
 
     for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
         AutoSweepTypeScript sweep(script, &oom);
     for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next())
         AutoSweepObjectGroup sweep(group, &oom);
 
@@ -2943,17 +2943,17 @@ GCRuntime::updateZonePointersToRelocated
     // Mark roots to update them.
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
 
         WeakMapBase::traceZone(zone, &trc);
     }
 
     // Sweep everything to fix up weak pointers.
-    rt->gc.sweepZoneAfterCompacting(zone);
+    sweepZoneAfterCompacting(zone);
 
     // Call callbacks to get the rest of the system to fixup other untraced pointers.
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         callWeakPointerCompartmentCallbacks(comp);
 }
 
 /*
  * Update runtime-wide pointers to relocated cells.
@@ -3637,17 +3637,17 @@ GCRuntime::sweepBackgroundThings(ZoneLis
         Arena* next;
         for (Arena* arena = emptyArenas; arena; arena = next) {
             next = arena->next;
 
             // We already calculated the zone's GC trigger after foreground
             // sweeping finished. Now we must update this value.
             arena->zone->threshold.updateForRemovedArena(tunables);
 
-            rt->gc.releaseArena(arena, lock);
+            releaseArena(arena, lock);
             releaseCount++;
             if (releaseCount % LockReleasePeriod == 0) {
                 lock.unlock();
                 lock.lock();
             }
         }
     }
 }
@@ -3758,43 +3758,49 @@ BackgroundSweepTask::run()
 {
     AutoTraceLog logSweeping(TraceLoggerForCurrentThread(), TraceLogger_GCSweeping);
 
     AutoLockHelperThreadState lock;
     AutoSetThreadIsSweeping threadIsSweeping;
 
     MOZ_ASSERT(!done);
 
-    JSRuntime* rt = runtime();
-
-    // The main thread may call queueZonesForBackgroundSweep() while this is
-    // running so we must check there is no more work after releasing the lock.
-    do {
-        ZoneList zones;
-        zones.transferFrom(rt->gc.backgroundSweepZones.ref());
-        LifoAlloc freeLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
-        freeLifoAlloc.transferFrom(&rt->gc.blocksToFreeAfterSweeping.ref());
-
-        AutoUnlockHelperThreadState unlock(lock);
-        rt->gc.sweepBackgroundThings(zones, freeLifoAlloc);
-    } while (!rt->gc.backgroundSweepZones.ref().isEmpty());
+    runtime()->gc.sweepFromBackgroundThread(lock);
 
     // Signal to the main thread that we're finished, because we release the
     // lock again before GCParallelTask's state is changed to finished.
     done = true;
 }
 
 void
+GCRuntime::sweepFromBackgroundThread(AutoLockHelperThreadState& lock)
+{
+    do {
+        ZoneList zones;
+        zones.transferFrom(backgroundSweepZones.ref());
+        LifoAlloc freeLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+        freeLifoAlloc.transferFrom(&blocksToFreeAfterSweeping.ref());
+
+        AutoUnlockHelperThreadState unlock(lock);
+        sweepBackgroundThings(zones, freeLifoAlloc);
+
+        // The main thread may call queueZonesForBackgroundSweep() while this is
+        // running so we must check there is no more work after releasing the
+        // lock.
+    } while (!backgroundSweepZones.ref().isEmpty());
+}
+
+void
 GCRuntime::waitBackgroundSweepEnd()
 {
     sweepTask.join();
 
     // TODO: Improve assertion to work in incremental GC?
-    if (!rt->gc.isIncrementalGCInProgress())
-        rt->gc.assertBackgroundSweepingFinished();
+    if (!isIncrementalGCInProgress())
+        assertBackgroundSweepingFinished();
 }
 
 bool
 GCRuntime::shouldReleaseObservedTypes()
 {
     bool releaseTypes = false;
 
 #ifdef JS_GC_ZEAL
@@ -3949,17 +3955,17 @@ GCRuntime::deleteEmptyZone(Zone* zone)
 }
 
 void
 GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
 {
     MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
     MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
 
-    if (rt->gc.numActiveZoneIters)
+    if (numActiveZoneIters)
         return;
 
     assertBackgroundSweepingFinished();
 
     Zone** read = zones().begin();
     Zone** end = zones().end();
     Zone** write = read;
 
@@ -5157,17 +5163,17 @@ GCRuntime::getNextSweepGroup()
  * extra slot of the cross compartment wrapper.
  *
  * The list is created during gray marking when one of the
  * MarkCrossCompartmentXXX functions is called for a pointer that leaves the
  * current compartent group.  This calls DelayCrossCompartmentGrayMarking to
  * push the referring object onto the list.
  *
  * The list is traversed and then unlinked in
- * MarkIncomingCrossCompartmentPointers.
+ * GCRuntime::markIncomingCrossCompartmentPointers.
  */
 
 static bool
 IsGrayListObject(JSObject* obj)
 {
     MOZ_ASSERT(obj);
     return obj->is<CrossCompartmentWrapperObject>() && !IsDeadProxyObject(obj);
 }
@@ -5251,26 +5257,26 @@ js::gc::DelayCrossCompartmentGrayMarking
         if (obj == src)
             found = true;
         obj = NextIncomingCrossCompartmentPointer(obj, false);
     }
     MOZ_ASSERT(found);
 #endif
 }
 
-static void
-MarkIncomingCrossCompartmentPointers(JSRuntime* rt, MarkColor color)
+void
+GCRuntime::markIncomingCrossCompartmentPointers(MarkColor color)
 {
     MOZ_ASSERT(color == MarkColor::Black || color == MarkColor::Gray);
 
     static const gcstats::PhaseKind statsPhases[] = {
         gcstats::PhaseKind::SWEEP_MARK_INCOMING_BLACK,
         gcstats::PhaseKind::SWEEP_MARK_INCOMING_GRAY
     };
-    gcstats::AutoPhase ap1(rt->gc.stats(), statsPhases[unsigned(color)]);
+    gcstats::AutoPhase ap1(stats(), statsPhases[unsigned(color)]);
 
     bool unlinkList = color == MarkColor::Gray;
 
     for (SweepGroupCompartmentsIter c(rt); !c.done(); c.next()) {
         MOZ_ASSERT_IF(color == MarkColor::Gray, c->zone()->isGCMarkingGray());
         MOZ_ASSERT_IF(color == MarkColor::Black, c->zone()->isGCMarkingBlack());
         MOZ_ASSERT_IF(c->gcIncomingGrayPointers, IsGrayListObject(c->gcIncomingGrayPointers));
 
@@ -5278,31 +5284,31 @@ MarkIncomingCrossCompartmentPointers(JSR
              src;
              src = NextIncomingCrossCompartmentPointer(src, unlinkList))
         {
             JSObject* dst = CrossCompartmentPointerReferent(src);
             MOZ_ASSERT(dst->compartment() == c);
 
             if (color == MarkColor::Gray) {
                 if (IsMarkedUnbarriered(rt, &src) && src->asTenured().isMarkedGray())
-                    TraceManuallyBarrieredEdge(&rt->gc.marker, &dst,
+                    TraceManuallyBarrieredEdge(&marker, &dst,
                                                "cross-compartment gray pointer");
             } else {
                 if (IsMarkedUnbarriered(rt, &src) && !src->asTenured().isMarkedGray())
-                    TraceManuallyBarrieredEdge(&rt->gc.marker, &dst,
+                    TraceManuallyBarrieredEdge(&marker, &dst,
                                                "cross-compartment black pointer");
             }
         }
 
         if (unlinkList)
             c->gcIncomingGrayPointers = nullptr;
     }
 
     auto unlimited = SliceBudget::unlimited();
-    MOZ_RELEASE_ASSERT(rt->gc.marker.drainMarkStack(unlimited));
+    MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited));
 }
 
 static bool
 RemoveFromGrayList(JSObject* wrapper)
 {
     AutoTouchingGrayThings tgt;
 
     if (!IsGrayListObject(wrapper))
@@ -5389,31 +5395,31 @@ GCRuntime::endMarkingSweepGroup(FreeOp* 
 {
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_MARK);
 
     /*
      * Mark any incoming black pointers from previously swept compartments
      * whose referents are not marked. This can occur when gray cells become
      * black by the action of UnmarkGray.
      */
-    MarkIncomingCrossCompartmentPointers(rt, MarkColor::Black);
+    markIncomingCrossCompartmentPointers(MarkColor::Black);
     markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_WEAK);
 
     /*
      * Change state of current group to MarkGray to restrict marking to this
      * group.  Note that there may be pointers to the atoms zone, and
      * these will be marked through, as they are not marked with
      * TraceCrossCompartmentEdge.
      */
     for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next())
         zone->changeGCState(Zone::Mark, Zone::MarkGray);
     marker.setMarkColorGray();
 
     /* Mark incoming gray pointers from previously swept compartments. */
-    MarkIncomingCrossCompartmentPointers(rt, MarkColor::Gray);
+    markIncomingCrossCompartmentPointers(MarkColor::Gray);
 
     /* Mark gray roots and mark transitively inside the current compartment group. */
     markGrayReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY);
     markWeakReferencesInCurrentGroup(gcstats::PhaseKind::SWEEP_MARK_GRAY_WEAK);
 
     /* Restore marking state. */
     for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next())
         zone->changeGCState(Zone::MarkGray, Zone::Mark);
@@ -6955,25 +6961,25 @@ GCRuntime::resetIncrementalGC(gc::AbortR
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
 
         isCompacting = wasCompacting;
 
         {
             gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
-            rt->gc.waitBackgroundSweepOrAllocEnd();
+            waitBackgroundSweepOrAllocEnd();
         }
         break;
       }
 
       case State::Finalize: {
         {
             gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
-            rt->gc.waitBackgroundSweepOrAllocEnd();
+            waitBackgroundSweepOrAllocEnd();
         }
 
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
 
@@ -7427,31 +7433,31 @@ GCRuntime::budgetIncrementalGC(bool noni
 
 namespace {
 
 class AutoScheduleZonesForGC
 {
     JSRuntime* rt_;
 
   public:
-    explicit AutoScheduleZonesForGC(JSRuntime* rt) : rt_(rt) {
-        for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
+    explicit AutoScheduleZonesForGC(GCRuntime* gc) : rt_(gc->rt) {
+        for (ZonesIter zone(rt_, WithAtoms); !zone.done(); zone.next()) {
             if (!zone->canCollect())
                 continue;
 
-            if (rt->gc.gcMode() == JSGC_MODE_GLOBAL)
+            if (gc->gcMode() == JSGC_MODE_GLOBAL)
                 zone->scheduleGC();
 
             // To avoid resets, continue to collect any zones that were being
             // collected in a previous slice.
-            if (rt->gc.isIncrementalGCInProgress() && zone->wasGCStarted())
+            if (gc->isIncrementalGCInProgress() && zone->wasGCStarted())
                 zone->scheduleGC();
 
             // This is a heuristic to reduce the total number of collections.
-            bool inHighFrequencyMode = rt->gc.schedulingState.inHighFrequencyGCMode();
+            bool inHighFrequencyMode = gc->schedulingState.inHighFrequencyGCMode();
             if (zone->usage.gcBytes() >= zone->threshold.eagerAllocTrigger(inHighFrequencyMode))
                 zone->scheduleGC();
 
             // This ensures we collect zones that have reached the malloc limit.
             if (zone->shouldTriggerGCForTooMuchMalloc())
                 zone->scheduleGC();
         }
     }
@@ -7734,17 +7740,17 @@ GCRuntime::collect(bool nonincrementalBy
         return;
 
     stats().writeLogMessage("GC starting in state %s",
         StateName(incrementalState));
 
     AutoTraceLog logGC(TraceLoggerForCurrentThread(), TraceLogger_GC);
     AutoStopVerifyingBarriers av(rt, IsShutdownGC(reason));
     AutoEnqueuePendingParseTasksAfterGC aept(*this);
-    AutoScheduleZonesForGC asz(rt);
+    AutoScheduleZonesForGC asz(this);
 
     bool repeat;
     do {
         IncrementalResult cycleResult = gcCycle(nonincrementalByAPI, budget, reason);
 
         if (reason == JS::gcreason::ABORT_GC) {
             MOZ_ASSERT(!isIncrementalGCInProgress());
             stats().writeLogMessage("GC aborted by request");
@@ -7777,21 +7783,21 @@ GCRuntime::collect(bool nonincrementalBy
             repeat = true;
         }
     } while (repeat);
 
     if (reason == JS::gcreason::COMPARTMENT_REVIVED)
         maybeDoCycleCollection();
 
 #ifdef JS_GC_ZEAL
-    if (rt->hasZealMode(ZealMode::CheckHeapAfterGC)) {
-        gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
+    if (hasZealMode(ZealMode::CheckHeapAfterGC)) {
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::TRACE_HEAP);
         CheckHeapAfterGC(rt);
     }
-    if (rt->hasZealMode(ZealMode::CheckGrayMarking) && !isIncrementalGCInProgress()) {
+    if (hasZealMode(ZealMode::CheckGrayMarking) && !isIncrementalGCInProgress()) {
         MOZ_RELEASE_ASSERT(CheckGrayMarkingState(rt));
     }
 #endif
     stats().writeLogMessage("GC ending");
 }
 
 js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
 {
@@ -7954,30 +7960,30 @@ GCRuntime::minorGC(JS::gcreason::Reason 
         return;
 
     // Note that we aren't collecting the updated alloc counts from any helper
     // threads.  We should be but I'm not sure where to add that
     // synchronisation.
     uint32_t numAllocs = rt->mainContextFromOwnThread()->getAndResetAllocsThisZoneSinceMinorGC();
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
         numAllocs += zone->getAndResetTenuredAllocsSinceMinorGC();
-    rt->gc.stats().setAllocsSinceMinorGCTenured(numAllocs);
-
-    gcstats::AutoPhase ap(rt->gc.stats(), phase);
+    stats().setAllocsSinceMinorGCTenured(numAllocs);
+
+    gcstats::AutoPhase ap(stats(), phase);
 
     nursery().clearMinorGCRequest();
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC);
     nursery().collect(reason);
     MOZ_ASSERT(nursery().isEmpty());
 
     blocksToFreeAfterMinorGC.ref().freeAll();
 
 #ifdef JS_GC_ZEAL
-    if (rt->hasZealMode(ZealMode::CheckHeapAfterGC))
+    if (hasZealMode(ZealMode::CheckHeapAfterGC))
         CheckHeapAfterGC(rt);
 #endif
 
     {
         AutoLockGC lock(rt);
         for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
             maybeAllocTriggerZoneGC(zone, lock);
     }
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -11,17 +11,16 @@
 #ifndef gc_GCInternals_h
 #define gc_GCInternals_h
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Maybe.h"
 
 #include "gc/GC.h"
 #include "gc/RelocationOverlay.h"
-#include "gc/Zone.h"
 #include "vm/HelperThreads.h"
 #include "vm/Runtime.h"
 
 namespace js {
 namespace gc {
 
 class MOZ_RAII AutoCheckCanAccessAtomsDuringGC
 {
@@ -316,14 +315,14 @@ DelayCrossCompartmentGrayMarking(JSObjec
 inline bool
 IsOOMReason(JS::gcreason::Reason reason)
 {
     return reason == JS::gcreason::LAST_DITCH ||
            reason == JS::gcreason::MEM_PRESSURE;
 }
 
 TenuredCell*
-AllocateCellInGC(Zone* zone, AllocKind thingKind);
+AllocateCellInGC(JS::Zone* zone, AllocKind thingKind);
 
 } /* namespace gc */
 } /* namespace js */
 
 #endif /* gc_GCInternals_h */
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -619,16 +619,17 @@ class GCRuntime
     void markAllWeakReferences(gcstats::PhaseKind phase);
     void markAllGrayReferences(gcstats::PhaseKind phase);
 
     void beginSweepPhase(JS::gcreason::Reason reason, AutoGCSession& session);
     void groupZonesForSweeping(JS::gcreason::Reason reason);
     MOZ_MUST_USE bool findInterZoneEdges();
     void getNextSweepGroup();
     IncrementalProgress endMarkingSweepGroup(FreeOp* fop, SliceBudget& budget);
+    void markIncomingCrossCompartmentPointers(MarkColor color);
     IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
 #ifdef JS_GC_ZEAL
     IncrementalProgress maybeYieldForSweepingZeal(FreeOp* fop, SliceBudget& budget);
 #endif
     bool shouldReleaseObservedTypes();
     void sweepDebuggerOnMainThread(FreeOp* fop);
     void sweepJitDataOnMainThread(FreeOp* fop);
     IncrementalProgress endSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
@@ -643,16 +644,17 @@ class GCRuntime
     IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget, Zone* zone);
     void endSweepPhase(bool lastGC);
     bool allCCVisibleZonesWereCollected() const;
     void sweepZones(FreeOp* fop, bool destroyingRuntime);
     void decommitAllWithoutUnlocking(const AutoLockGC& lock);
     void startDecommit();
     void queueZonesForBackgroundSweep(ZoneList& zones);
     void maybeStartBackgroundSweep(AutoLockHelperThreadState& lock);
+    void sweepFromBackgroundThread(AutoLockHelperThreadState& lock);
     void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
     void assertBackgroundSweepingFinished();
     bool shouldCompact();
     void beginCompactPhase();
     IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
                                      AutoGCSession& session);
     void endCompactPhase();
     void sweepTypesAfterCompacting(Zone* zone);
--- a/js/src/gc/Rooting.h
+++ b/js/src/gc/Rooting.h
@@ -25,16 +25,17 @@ class ScriptSourceObject;
 class SavedFrame;
 class Shape;
 class ObjectGroup;
 class DebuggerArguments;
 class DebuggerEnvironment;
 class DebuggerFrame;
 class DebuggerObject;
 class Scope;
+class ModuleObject;
 
 // These are internal counterparts to the public types such as HandleObject.
 
 typedef JS::Handle<NativeObject*>           HandleNativeObject;
 typedef JS::Handle<Shape*>                  HandleShape;
 typedef JS::Handle<ObjectGroup*>            HandleObjectGroup;
 typedef JS::Handle<JSAtom*>                 HandleAtom;
 typedef JS::Handle<JSLinearString*>         HandleLinearString;
@@ -43,27 +44,29 @@ typedef JS::Handle<ArrayObject*>        
 typedef JS::Handle<PlainObject*>            HandlePlainObject;
 typedef JS::Handle<SavedFrame*>             HandleSavedFrame;
 typedef JS::Handle<ScriptSourceObject*>     HandleScriptSourceObject;
 typedef JS::Handle<DebuggerArguments*>      HandleDebuggerArguments;
 typedef JS::Handle<DebuggerEnvironment*>    HandleDebuggerEnvironment;
 typedef JS::Handle<DebuggerFrame*>          HandleDebuggerFrame;
 typedef JS::Handle<DebuggerObject*>         HandleDebuggerObject;
 typedef JS::Handle<Scope*>                  HandleScope;
+typedef JS::Handle<ModuleObject*>           HandleModuleObject;
 
 typedef JS::MutableHandle<Shape*>               MutableHandleShape;
 typedef JS::MutableHandle<JSAtom*>              MutableHandleAtom;
 typedef JS::MutableHandle<NativeObject*>        MutableHandleNativeObject;
 typedef JS::MutableHandle<PlainObject*>         MutableHandlePlainObject;
 typedef JS::MutableHandle<SavedFrame*>          MutableHandleSavedFrame;
 typedef JS::MutableHandle<DebuggerArguments*>   MutableHandleDebuggerArguments;
 typedef JS::MutableHandle<DebuggerEnvironment*> MutableHandleDebuggerEnvironment;
 typedef JS::MutableHandle<DebuggerFrame*>       MutableHandleDebuggerFrame;
 typedef JS::MutableHandle<DebuggerObject*>      MutableHandleDebuggerObject;
 typedef JS::MutableHandle<Scope*>               MutableHandleScope;
+typedef JS::MutableHandle<ModuleObject*>        MutableHandleModuleObject;
 
 typedef JS::Rooted<NativeObject*>           RootedNativeObject;
 typedef JS::Rooted<Shape*>                  RootedShape;
 typedef JS::Rooted<ObjectGroup*>            RootedObjectGroup;
 typedef JS::Rooted<JSAtom*>                 RootedAtom;
 typedef JS::Rooted<JSLinearString*>         RootedLinearString;
 typedef JS::Rooted<PropertyName*>           RootedPropertyName;
 typedef JS::Rooted<ArrayObject*>            RootedArrayObject;
@@ -71,16 +74,17 @@ typedef JS::Rooted<GlobalObject*>       
 typedef JS::Rooted<PlainObject*>            RootedPlainObject;
 typedef JS::Rooted<SavedFrame*>             RootedSavedFrame;
 typedef JS::Rooted<ScriptSourceObject*>     RootedScriptSourceObject;
 typedef JS::Rooted<DebuggerArguments*>      RootedDebuggerArguments;
 typedef JS::Rooted<DebuggerEnvironment*>    RootedDebuggerEnvironment;
 typedef JS::Rooted<DebuggerFrame*>          RootedDebuggerFrame;
 typedef JS::Rooted<DebuggerObject*>         RootedDebuggerObject;
 typedef JS::Rooted<Scope*>                  RootedScope;
+typedef JS::Rooted<ModuleObject*>           RootedModuleObject;
 
 typedef JS::GCVector<JSFunction*>   FunctionVector;
 typedef JS::GCVector<PropertyName*> PropertyNameVector;
 typedef JS::GCVector<Shape*>        ShapeVector;
 typedef JS::GCVector<JSString*>     StringVector;
 
 /** Interface substitute for Rooted<T> which does not root the variable's memory. */
 template <typename T>
--- a/js/src/gc/WeakMap-inl.h
+++ b/js/src/gc/WeakMap-inl.h
@@ -4,17 +4,16 @@
  * 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 gc_WeakMap_inl_h
 #define gc_WeakMap_inl_h
 
 #include "gc/WeakMap.h"
 
-#include "gc/Zone.h"
 #include "vm/JSContext.h"
 
 namespace js {
 
 template <typename T>
 static T extractUnbarriered(const WriteBarrieredBase<T>& v)
 {
     return v.get();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug1485698.js
@@ -0,0 +1,9 @@
+let m = parseModule(`
+  function f(x,y,z) {
+    delete arguments[2];
+    import.meta[2]
+  }
+  f(1,2,3)
+`);
+instantiateModule(m);
+evaluateModule(m);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -13164,20 +13164,30 @@ IonBuilder::jsop_implicitthis(PropertyNa
     current->push(implicitThis);
 
     return resumeAfter(implicitThis);
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_importmeta()
 {
+    if (info().analysisMode() == Analysis_ArgumentsUsage) {
+        // The meta object may not have been created yet. Just push a dummy
+        // value, it does not affect the arguments analysis.
+        MUnknownValue* unknown = MUnknownValue::New(alloc());
+        current->add(unknown);
+        current->push(unknown);
+        return Ok();
+    }
+
     ModuleObject* module = GetModuleObjectForScript(script());
     MOZ_ASSERT(module);
 
-    // The object must have been created already when we compiled for baseline.
+    // If we get there then the meta object must already have been created, at
+    // the latest when we compiled for baseline.
     JSObject* metaObject = module->metaObject();
     MOZ_ASSERT(metaObject);
 
     pushConstant(ObjectValue(*metaObject));
 
     return Ok();
 }
 
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -22,17 +22,16 @@
 #include "mozilla/Maybe.h"
 
 #include "jsapi.h"
 
 #include "frontend/BinSource.h"
 #include "frontend/FullParseHandler.h"
 #include "frontend/ParseContext.h"
 #include "frontend/Parser.h"
-#include "gc/Zone.h"
 #include "js/Vector.h"
 
 #include "jsapi-tests/tests.h"
 
 #include "vm/Interpreter.h"
 
 using UsedNameTracker = js::frontend::UsedNameTracker;
 using namespace JS;
--- a/js/src/jsapi-tests/testBinTokenReaderTester.cpp
+++ b/js/src/jsapi-tests/testBinTokenReaderTester.cpp
@@ -13,17 +13,16 @@
 #elif defined(XP_UNIX)
 #include <fcntl.h>
 #include <unistd.h>
 #endif // defined (XP_WIN) || defined (XP_UNIX)
 
 #include "mozilla/Maybe.h"
 
 #include "frontend/BinTokenReaderTester.h"
-#include "gc/Zone.h"
 
 #include "js/Vector.h"
 
 #include "jsapi-tests/tests.h"
 
 using mozilla::Maybe;
 
 using Tokenizer = js::frontend::BinTokenReaderTester;
--- a/js/src/jsapi-tests/testErrorInterceptor.cpp
+++ b/js/src/jsapi-tests/testErrorInterceptor.cpp
@@ -3,18 +3,16 @@
 #include "jsapi-tests/tests.h"
 
 #include "util/StringBuffer.h"
 
 // Tests for JS_GetErrorInterceptorCallback and JS_SetErrorInterceptorCallback.
 
 
 namespace {
-const double EXN_VALUE = 3.14;
-
 static JS::PersistentRootedString gLatestMessage;
 
 // An interceptor that stores the error in `gLatestMessage`.
 struct SimpleInterceptor: JSErrorInterceptor {
     virtual void interceptError(JSContext* cx, const JS::Value& val) override {
         js::StringBuffer buffer(cx);
         if (!ValueToStringBuffer(cx, val, buffer))
             MOZ_CRASH("Could not convert to string buffer");
--- a/js/src/jsapi-tests/testGCUniqueId.cpp
+++ b/js/src/jsapi-tests/testGCUniqueId.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* 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 "gc/GCInternals.h"
-#include "gc/Zone.h"
 #include "js/GCVector.h"
 
 #include "jsapi-tests/tests.h"
 
 #include "gc/Zone-inl.h"
 
 static void
 MinimizeHeap(JSContext* cx)
--- a/js/src/jsapi-tests/testGCWeakRef.cpp
+++ b/js/src/jsapi-tests/testGCWeakRef.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* 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 "gc/Barrier.h"
-#include "gc/Zone.h"
 #include "js/RootingAPI.h"
 
 #include "jsapi-tests/tests.h"
 
 struct MyHeap
 {
     explicit MyHeap(JSObject* obj) : weak(obj) {}
     js::WeakRef<JSObject*> weak;
--- a/js/src/jsapi-tests/testIsInsideNursery.cpp
+++ b/js/src/jsapi-tests/testIsInsideNursery.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* 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 "gc/Zone.h"
 #include "jsapi-tests/tests.h"
 
 #include "vm/JSContext-inl.h"
 
 BEGIN_TEST(testIsInsideNursery)
 {
     /* Non-GC things are never inside the nursery. */
     CHECK(!cx->nursery().isInside(cx));
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -214,17 +214,17 @@ js::GetScriptRealm(JSScript* script)
 
 JS_FRIEND_API(bool)
 JS_ScriptHasMutedErrors(JSScript* script)
 {
     return script->mutedErrors();
 }
 
 JS_FRIEND_API(bool)
-JS_WrapPropertyDescriptor(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc)
+JS_WrapPropertyDescriptor(JSContext* cx, JS::MutableHandle<JS::PropertyDescriptor> desc)
 {
     return cx->compartment()->wrap(cx, desc);
 }
 
 JS_FRIEND_API(void)
 JS_TraceShapeCycleCollectorChildren(JS::CallbackTracer* trc, JS::GCCellPtr shape)
 {
     MOZ_ASSERT(shape.is<Shape>());
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -66,17 +66,16 @@
 #include "builtin/ModuleObject.h"
 #include "builtin/RegExp.h"
 #include "builtin/TestingFunctions.h"
 #if defined(JS_BUILD_BINAST)
 # include "frontend/BinSource.h"
 #endif // defined(JS_BUILD_BINAST)
 #include "frontend/Parser.h"
 #include "gc/PublicIterators.h"
-#include "gc/Zone.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitRealm.h"
 #include "jit/shared/CodeGenerator-shared.h"
 #include "js/AutoByteString.h"
 #include "js/CompilationAndEvaluation.h"
@@ -714,31 +713,31 @@ GetLine(FILE* file, const char * prompt)
             }
         }
 
         len += strlen(current);
         char* t = buffer + len - 1;
         if (*t == '\n') {
             /* Line was read. We remove '\n' and exit. */
             *t = '\0';
-            return buffer;
+            break;
         }
 
         if (len + 1 == size) {
             size = size * 2;
             char* tmp = static_cast<char*>(realloc(buffer, size));
             if (!tmp) {
                 free(buffer);
                 return nullptr;
             }
             buffer = tmp;
         }
         current = buffer + len;
     } while (true);
-    return nullptr;
+    return buffer;
 }
 
 static bool
 ShellInterruptCallback(JSContext* cx)
 {
     ShellContext* sc = GetShellContext(cx);
     if (!sc->serviceInterrupt)
         return true;
@@ -4305,17 +4304,17 @@ EnsureModuleLoaderScriptObjectMap(JSCont
 {
     auto priv = EnsureShellCompartmentPrivate(cx);
     if (!priv)
         return nullptr;
 
     if (priv->moduleLoaderScriptObjectMap)
         return priv->moduleLoaderScriptObjectMap.get();
 
-    Zone* zone = cx->zone();
+    JS::Zone* zone = cx->zone();
     auto* map = cx->new_<ScriptObjectMap>(zone);
     if (!map)
         return nullptr;
 
     priv->moduleLoaderScriptObjectMap.reset(map);
     return map;
 }
 
@@ -7217,17 +7216,19 @@ WasmLoop(JSContext* cx, unsigned argc, V
         Rooted<TypedArrayObject*> typedArray(cx, &ret->as<TypedArrayObject>());
         RootedWasmInstanceObject instanceObj(cx);
         if (!wasm::Eval(cx, typedArray, importObj, &instanceObj)) {
             // Clear any pending exceptions, we don't care about them
             cx->clearPendingException();
         }
     }
 
-    return true;
+#ifdef __AFL_HAVE_MANUAL_CONTROL  // to silence unreachable code warning
+    return true;
+#endif
 }
 
 static const JSFunctionSpecWithHelp shell_functions[] = {
     JS_FN_HELP("clone", Clone, 1, 0,
 "clone(fun[, scope])",
 "  Clone function object."),
 
     JS_FN_HELP("options", Options, 0, 0,
--- a/js/src/vm/Compartment.cpp
+++ b/js/src/vm/Compartment.cpp
@@ -343,17 +343,17 @@ Compartment::rewrap(JSContext* cx, Mutab
     // not need to create or return an existing wrapper.
     if (obj->compartment() == this)
         return true;
 
     return getOrCreateWrapper(cx, existing, obj);
 }
 
 bool
-Compartment::wrap(JSContext* cx, MutableHandle<PropertyDescriptor> desc)
+Compartment::wrap(JSContext* cx, MutableHandle<JS::PropertyDescriptor> desc)
 {
     if (!wrap(cx, desc.object()))
         return false;
 
     if (desc.hasGetterObject()) {
         if (!wrap(cx, desc.getterObject()))
             return false;
     }
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -12,23 +12,24 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/Variant.h"
 
 #include <stddef.h>
 
 #include "gc/Barrier.h"
 #include "gc/NurseryAwareHashMap.h"
-#include "gc/Zone.h"
 #include "js/UniquePtr.h"
+#include "vm/JSObject.h"
+#include "vm/JSScript.h"
 
 namespace js {
 
 namespace gc {
-template <typename Node, typename Derived> class ComponentFinder;
+struct ZoneComponentFinder;
 } // namespace gc
 
 class CrossCompartmentKey
 {
   public:
     enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject,
                                         DebuggerWasmScript, DebuggerWasmSource };
     using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
@@ -441,17 +442,17 @@ class JS::Compartment
 
     MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
 
     MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
 #ifdef ENABLE_BIGINT
     MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi);
 #endif
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj);
-    MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc);
+    MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::PropertyDescriptor> desc);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec);
     MOZ_MUST_USE bool rewrap(JSContext* cx, JS::MutableHandleObject obj, JS::HandleObject existing);
 
     MOZ_MUST_USE bool putWrapper(JSContext* cx, const js::CrossCompartmentKey& wrapped,
                                  const js::Value& wrapper);
 
     js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) const {
         return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(wrapped));
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -12,24 +12,24 @@
 
 #include "builtin/Array.h"
 #include "builtin/Boolean.h"
 #include "js/Vector.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/ErrorObject.h"
 #include "vm/JSFunction.h"
 #include "vm/Realm.h"
-#include "vm/RegExpStatics.h"
 #include "vm/Runtime.h"
 
 namespace js {
 
 class Debugger;
 class TypedObjectModuleObject;
 class LexicalEnvironmentObject;
+class RegExpStatics;
 
 /*
  * Global object slots are reserved as follows:
  *
  * [0, APPLICATION_SLOTS)
  *   Pre-reserved slots in all global objects set aside for the embedding's
  *   use. As with all reserved slots these start out as UndefinedValue() and
  *   are traced for GC purposes. Apart from that the engine never touches
--- a/js/src/vm/Iteration.h
+++ b/js/src/vm/Iteration.h
@@ -10,17 +10,16 @@
 /*
  * JavaScript iterators.
  */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "gc/Barrier.h"
-#include "vm/JSContext.h"
 #include "vm/ReceiverGuard.h"
 #include "vm/Stack.h"
 
 namespace js {
 
 class PropertyIteratorObject;
 
 struct NativeIterator
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -11,17 +11,16 @@
 
 #include "jsexn.h"
 
 #include "builtin/DataViewObject.h"
 #include "gc/FreeOp.h"
 #include "gc/HashUtil.h"
 #include "gc/Policy.h"
 #include "gc/StoreBuffer.h"
-#include "gc/Zone.h"
 #include "js/CharacterEncoding.h"
 #include "js/UniquePtr.h"
 #include "vm/ArrayObject.h"
 #include "vm/JSObject.h"
 #include "vm/RegExpObject.h"
 #include "vm/Shape.h"
 #include "vm/TaggedProto.h"
 
--- a/js/src/vm/Realm.h
+++ b/js/src/vm/Realm.h
@@ -15,17 +15,16 @@
 #include "mozilla/Tuple.h"
 #include "mozilla/Variant.h"
 #include "mozilla/XorShift128PlusRNG.h"
 
 #include <stddef.h>
 
 #include "builtin/Array.h"
 #include "gc/Barrier.h"
-#include "gc/Zone.h"
 #include "js/UniquePtr.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/Compartment.h"
 #include "vm/ReceiverGuard.h"
 #include "vm/RegExpShared.h"
 #include "vm/SavedStacks.h"
 #include "vm/Time.h"
 #include "wasm/WasmRealm.h"
new file mode 100644
--- /dev/null
+++ b/js/src/vm/RegExpConstants.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 vm_RegExpConstants_h
+#define vm_RegExpConstants_h
+
+#include "builtin/SelfHostingDefines.h"
+
+namespace js {
+
+enum RegExpFlag : uint8_t
+{
+    IgnoreCaseFlag  = 0x01,
+    GlobalFlag      = 0x02,
+    MultilineFlag   = 0x04,
+    StickyFlag      = 0x08,
+    UnicodeFlag     = 0x10,
+
+    NoFlags         = 0x00,
+    AllFlags        = 0x1f
+};
+
+static_assert(IgnoreCaseFlag == REGEXP_IGNORECASE_FLAG &&
+              GlobalFlag == REGEXP_GLOBAL_FLAG &&
+              MultilineFlag == REGEXP_MULTILINE_FLAG &&
+              StickyFlag == REGEXP_STICKY_FLAG &&
+              UnicodeFlag == REGEXP_UNICODE_FLAG,
+              "Flag values should be in sync with self-hosted JS");
+
+enum RegExpRunStatus
+{
+    RegExpRunStatus_Error,
+    RegExpRunStatus_Success,
+    RegExpRunStatus_Success_NotFound
+};
+
+} /* namespace js */
+
+#endif /* vm_RegExpConstants_h */
--- a/js/src/vm/RegExpShared.h
+++ b/js/src/vm/RegExpShared.h
@@ -10,65 +10,39 @@
  */
 
 #ifndef vm_RegExpShared_h
 #define vm_RegExpShared_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/MemoryReporting.h"
 
-#include "builtin/SelfHostingDefines.h"
 #include "gc/Barrier.h"
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "gc/Zone.h"
 #include "js/AllocPolicy.h"
 #include "js/UbiNode.h"
 #include "js/Vector.h"
 #include "vm/ArrayObject.h"
 #include "vm/JSAtom.h"
+#include "vm/RegExpConstants.h"
 
 namespace js {
 
 class ArrayObject;
 class RegExpRealm;
 class RegExpShared;
 class RegExpStatics;
 class VectorMatchPairs;
 
 using RootedRegExpShared = JS::Rooted<RegExpShared*>;
 using HandleRegExpShared = JS::Handle<RegExpShared*>;
 using MutableHandleRegExpShared = JS::MutableHandle<RegExpShared*>;
 
-enum RegExpFlag : uint8_t
-{
-    IgnoreCaseFlag  = 0x01,
-    GlobalFlag      = 0x02,
-    MultilineFlag   = 0x04,
-    StickyFlag      = 0x08,
-    UnicodeFlag     = 0x10,
-
-    NoFlags         = 0x00,
-    AllFlags        = 0x1f
-};
-
-static_assert(IgnoreCaseFlag == REGEXP_IGNORECASE_FLAG &&
-              GlobalFlag == REGEXP_GLOBAL_FLAG &&
-              MultilineFlag == REGEXP_MULTILINE_FLAG &&
-              StickyFlag == REGEXP_STICKY_FLAG &&
-              UnicodeFlag == REGEXP_UNICODE_FLAG,
-              "Flag values should be in sync with self-hosted JS");
-
-enum RegExpRunStatus
-{
-    RegExpRunStatus_Error,
-    RegExpRunStatus_Success,
-    RegExpRunStatus_Success_NotFound
-};
-
 /*
  * A RegExpShared is the compiled representation of a regexp. A RegExpShared is
  * potentially pointed to by multiple RegExpObjects. Additionally, C++ code may
  * have pointers to RegExpShareds on the stack. The RegExpShareds are kept in a
  * table so that they can be reused when compiling the same regex string.
  *
  * To save memory, a RegExpShared is not created for a RegExpObject until it is
  * needed for execution. When a RegExpShared needs to be created, it is looked
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -98,18 +98,27 @@ class Instance;
 // the activation's "current regs", which contains the stack pointer 'sp'. In
 // the interpreter, sp is adjusted as individual values are pushed and popped
 // from the stack and the InterpreterRegs struct (pointed to by the
 // InterpreterActivation) is a local var of js::Interpret.
 
 enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false };
 enum MaybeCheckTDZ { CheckTDZ = true, DontCheckTDZ = false };
 
+} // namespace js
+
+namespace mozilla {
+template <>
+struct IsPod<js::MaybeCheckTDZ> : TrueType {};
+} // namespace mozilla
+
 /*****************************************************************************/
 
+namespace js {
+
 namespace jit {
     class BaselineFrame;
     class RematerializedFrame;
 } // namespace jit
 
 /**
  * Pointer to a live JS or WASM stack frame.
  */
@@ -2424,9 +2433,10 @@ FrameIter::isPhysicalJitFrame() const
 inline jit::CommonFrameLayout*
 FrameIter::physicalJitFrame() const
 {
     MOZ_ASSERT(isPhysicalJitFrame());
     return jsJitFrame().current();
 }
 
 }  /* namespace js */
+
 #endif /* vm_Stack_h */
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -3,17 +3,16 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_UnboxedObject_h
 #define vm_UnboxedObject_h
 
 #include "gc/DeletePolicy.h"
-#include "gc/Zone.h"
 #include "vm/JSObject.h"
 #include "vm/Runtime.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 // Memory required for an unboxed value of a given type. Returns zero for types
 // which can't be used for unboxed objects.
@@ -54,17 +53,17 @@ class UnboxedLayout : public mozilla::Li
         Property()
           : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
         {}
     };
 
     typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
 
   private:
-    Zone* zone_;
+    JS::Zone* zone_;
 
     // If objects in this group have ever been converted to native objects,
     // these store the corresponding native group and initial shape for such
     // objects. Type information for this object is reflected in nativeGroup.
     GCPtrObjectGroup nativeGroup_;
     GCPtrShape nativeShape_;
 
     // Any script/pc which the associated group is created for.
@@ -94,23 +93,23 @@ class UnboxedLayout : public mozilla::Li
     int32_t* traceList_;
 
     // If this layout has been used to construct script or JSON constant
     // objects, this code might be filled in to more quickly fill in objects
     // from an array of values.
     GCPtrJitCode constructorCode_;
 
   public:
-    explicit UnboxedLayout(Zone* zone)
+    explicit UnboxedLayout(JS::Zone* zone)
       : zone_(zone), nativeGroup_(nullptr), nativeShape_(nullptr),
         allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
         size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr)
     {}
 
-    Zone* zone() const { return zone_; }
+    JS::Zone* zone() const { return zone_; }
 
     bool initProperties(const PropertyVector& properties, size_t size) {
         size_ = size;
         return properties_.appendAll(properties);
     }
 
     ~UnboxedLayout() {
         if (newScript_)
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -761,18 +761,18 @@ nsImageFrame::OnFrameUpdate(imgIRequest*
   InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
   return NS_OK;
 }
 
 void
 nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
                              const nsRect* aFrameInvalidRect)
 {
-  // XXX: Do we really want to check whether we have a
-  // WebRenderUserDataProperty?
+  // Check if WebRender has interacted with this frame. If it has
+  // we need to let it know that things have changed.
   if (HasProperty(WebRenderUserDataProperty::Key())) {
     RefPtr<WebRenderFallbackData> data = GetWebRenderUserData<WebRenderFallbackData>(this, static_cast<uint32_t>(DisplayItemType::TYPE_IMAGE));
     if (data) {
       data->SetInvalid(true);
     }
     SchedulePaint();
     return;
   }
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -897,16 +897,29 @@ nsImageBoxFrame::OnImageIsAnimated(imgIR
 
 nsresult
 nsImageBoxFrame::OnFrameUpdate(imgIRequest* aRequest)
 {
   if ((0 == mRect.width) || (0 == mRect.height)) {
     return NS_OK;
   }
 
+  // Check if WebRender has interacted with this frame. If it has
+  // we need to let it know that things have changed.
+  if (HasProperty(WebRenderUserDataProperty::Key())) {
+    uint32_t key = static_cast<uint32_t>(DisplayItemType::TYPE_XUL_IMAGE);
+    RefPtr<WebRenderFallbackData> data =
+      GetWebRenderUserData<WebRenderFallbackData>(this, key);
+    if (data) {
+      data->SetInvalid(true);
+    }
+    SchedulePaint();
+    return NS_OK;
+  }
+
   InvalidateLayer(DisplayItemType::TYPE_XUL_IMAGE);
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsImageBoxListener, imgINotificationObserver)
 
 nsImageBoxListener::nsImageBoxListener(nsImageBoxFrame *frame)
new file mode 100644
--- /dev/null
+++ b/media/libvorbis/aarch64-win.patch
@@ -0,0 +1,13 @@
+diff --git a/lib/os.h b/lib/os.h
+index 416a401..44d54fe 100644
+--- a/lib/os.h
++++ b/lib/os.h
+@@ -147,7 +147,7 @@ static __inline void vorbis_fpu_restore(vorbis_fpu_control fpu){
+ 
+ /* Optimized code path for x86_64 builds. Uses SSE2 intrinsics. This can be
+    done safely because all x86_64 CPUs supports SSE2. */
+-#if (defined(_MSC_VER) && defined(_WIN64)) || (defined(__GNUC__) && defined (__x86_64__))
++#if (defined(_MSC_VER) && defined(_M_X64)) || (defined(__GNUC__) && defined (__x86_64__))
+ #  define VORBIS_FPU_CONTROL
+ 
+ typedef ogg_int16_t vorbis_fpu_control;
--- a/media/libvorbis/lib/os.h
+++ b/media/libvorbis/lib/os.h
@@ -142,17 +142,17 @@ static __inline void vorbis_fpu_restore(
   (void)fpu;
 }
 
 #endif /* Special MSVC 32 bit implementation */
 
 
 /* Optimized code path for x86_64 builds. Uses SSE2 intrinsics. This can be
    done safely because all x86_64 CPUs supports SSE2. */
-#if (defined(_MSC_VER) && defined(_WIN64)) || (defined(__GNUC__) && defined (__x86_64__))
+#if (defined(_MSC_VER) && defined(_M_X64)) || (defined(__GNUC__) && defined (__x86_64__))
 #  define VORBIS_FPU_CONTROL
 
 typedef ogg_int16_t vorbis_fpu_control;
 
 #include <emmintrin.h>
 static __inline int vorbis_ftoi(double f){
         return _mm_cvtsd_si32(_mm_load_sd(&f));
 }
--- a/media/libvorbis/update.sh
+++ b/media/libvorbis/update.sh
@@ -79,9 +79,9 @@ mkdir -p ./lib/books/coupled
 mkdir -p ./lib/books/floor
 mkdir -p ./lib/books/uncoupled
 cp $1/lib/books/coupled/res_books_stereo.h ./lib/books/coupled/
 cp $1/lib/books/coupled/res_books_51.h ./lib/books/coupled/
 cp $1/lib/books/floor/floor_books.h ./lib/books/floor/
 cp $1/lib/books/uncoupled/res_books_uncoupled.h ./lib/books/uncoupled/
 
 # Add any patches against upstream here.
-# ...nothing to apply...
+patch -p1 < ./aarch64-win.patch
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -196,16 +196,47 @@ class NavigationDelegateTest : BaseSessi
                 object : Callbacks.TrackingProtectionDelegate {
             @AssertCalled(false)
             override fun onTrackerBlocked(session: GeckoSession, uri: String,
                                           categories: Int) {
             }
         })
     }
 
+    @Test fun redirectLoad() {
+        val redirectUri = if (sessionRule.env.isAutomation) {
+            "http://example.org/tests/robocop/robocop_blank_02.html"
+        } else {
+            "http://jigsaw.w3.org/HTTP/300/Overview.html"
+        }
+        val uri = if (sessionRule.env.isAutomation) {
+            "http://example.org/tests/robocop/simple_redirect.sjs?$redirectUri"
+        } else {
+            "http://jigsaw.w3.org/HTTP/300/301.html"
+        }
+
+        sessionRule.session.loadUri(uri)
+        sessionRule.waitForPageStop()
+
+        sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
+            @AssertCalled(count = 2, order = [1, 2])
+            override fun onLoadRequest(session: GeckoSession, uri: String,
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
+                assertThat("Session should not be null", session, notNullValue())
+                assertThat("URI should not be null", uri, notNullValue())
+                assertThat("URL should match", uri,
+                        equalTo(forEachCall(uri, redirectUri)))
+                assertThat("Where should not be null", where, notNullValue())
+                assertThat("Where should match", where,
+                        equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
+                return null
+            }
+        })
+    }
+
     @WithDevToolsAPI
     @Test fun desktopMode() {
         sessionRule.session.loadUri("https://example.com")
         sessionRule.waitForPageStop()
 
         val userAgentJs = "window.navigator.userAgent"
         val mobileSubStr = "Mobile"
         val desktopSubStr = "X11"
--- a/netwerk/base/SimpleChannelParent.cpp
+++ b/netwerk/base/SimpleChannelParent.cpp
@@ -34,16 +34,23 @@ SimpleChannelParent::SetParentListener(H
 NS_IMETHODIMP
 SimpleChannelParent::NotifyTrackingProtectionDisabled()
 {
   // Nothing to do.
   return NS_OK;
 }
 
 NS_IMETHODIMP
+SimpleChannelParent::NotifyTrackingCookieBlocked()
+{
+  // Nothing to do.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 SimpleChannelParent::NotifyTrackingResource(bool aIsThirdParty)
 {
   // Nothing to do.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
--- a/netwerk/base/nsIParentChannel.idl
+++ b/netwerk/base/nsIParentChannel.idl
@@ -29,16 +29,22 @@ interface nsIParentChannel : nsIStreamLi
   [noscript] void setParentListener(in HttpChannelParentListener listener);
 
   /**
    * Called to notify the HttpChannelChild that tracking protection was
    * disabled for this load.
    */
   [noscript] void notifyTrackingProtectionDisabled();
 
+  /**
+   * Called to notify the HttpChannelChild that cookie has been blocked for
+   * this load.
+   */
+  [noscript] void notifyTrackingCookieBlocked();
+
    /**
    * Called to set matched information when URL matches SafeBrowsing list.
    * @param aList
    *        Name of the list that matched
    * @param aProvider
    *        Name of provider that matched
    * @param aFullHash
    *        String represents full hash that matched
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2229,17 +2229,17 @@ nsCookieService::SetCookieStringInternal
                                          aFirstPartyStorageAccessGranted,
                                          aCookieHeader.get(), priorCookieCount,
                                          aOriginAttrs);
 
   // fire a notification if third party or if cookie was rejected
   // (but not if there was an error)
   switch (cookieStatus) {
   case STATUS_REJECTED:
-    NotifyRejected(aHostURI);
+    NotifyRejected(aHostURI, aChannel);
     if (aIsForeign) {
       NotifyThirdParty(aHostURI, false, aChannel);
     }
     return; // Stop here
   case STATUS_REJECTED_WITH_ERROR:
     return;
   case STATUS_ACCEPTED: // Fallthrough
   case STATUS_ACCEPT_SESSION:
@@ -2259,22 +2259,24 @@ nsCookieService::SetCookieStringInternal
     // document.cookie can only set one cookie at a time
     if (!aFromHttp)
       break;
   }
 }
 
 // notify observers that a cookie was rejected due to the users' prefs.
 void
-nsCookieService::NotifyRejected(nsIURI *aHostURI)
+nsCookieService::NotifyRejected(nsIURI *aHostURI, nsIChannel* aChannel)
 {
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
     os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
   }
+
+  AntiTrackingCommon::NotifyRejection(aChannel);
 }
 
 // notify observers that a third-party cookie was accepted/rejected
 // if the cookie issuer is unknown, it defaults to "?"
 void
 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
 {
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
@@ -3603,17 +3605,17 @@ nsCookieService::SetCookieInternal(nsIUR
     mPermissionService->CanSetCookie(aHostURI,
                                      aChannel,
                                      static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
                                      &cookieAttributes.isSession,
                                      &cookieAttributes.expiryTime,
                                      &permission);
     if (!permission) {
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
-      NotifyRejected(aHostURI);
+      NotifyRejected(aHostURI, aChannel);
       return newCookie;
     }
 
     // update isSession and expiry attributes, in case they changed
     cookie->SetIsSession(cookieAttributes.isSession);
     cookie->SetExpiry(cookieAttributes.expiryTime);
   }
 
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -314,17 +314,17 @@ class nsCookieService final : public nsI
     static bool                   CheckPrefixes(nsCookieAttributes &aCookie, bool aSecureRequest);
     static bool                   GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
     void                          RemoveAllFromMemory();
     already_AddRefed<nsIArray>    PurgeCookies(int64_t aCurrentTimeInUsec);
     bool                          FindCookie(const nsCookieKey& aKey, const nsCString& aHost, const nsCString& aName, const nsCString& aPath, nsListIter &aIter);
     bool                          FindSecureCookie(const nsCookieKey& aKey, nsCookie* aCookie);
     int64_t                       FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, const mozilla::Maybe<bool> &aIsSecure, nsListIter &aIter);
     void                          TelemetryForEvictingStaleCookie(nsCookie* aEvicted, int64_t oldestCookieTime);
-    void                          NotifyRejected(nsIURI *aHostURI);
+    void                          NotifyRejected(nsIURI *aHostURI, nsIChannel* aChannel);
     void                          NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
     void                          NotifyChanged(nsISupports *aSubject, const char16_t *aData, bool aOldCookieIsSession = false, bool aFromHttp = false);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
     void                          UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
 
     nsresult                      GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, nsISimpleEnumerator **aEnumerator);
     nsresult                      RemoveCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain);
--- a/netwerk/protocol/data/DataChannelParent.cpp
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -34,16 +34,23 @@ DataChannelParent::SetParentListener(Htt
 NS_IMETHODIMP
 DataChannelParent::NotifyTrackingProtectionDisabled()
 {
     // Nothing to do.
     return NS_OK;
 }
 
 NS_IMETHODIMP
+DataChannelParent::NotifyTrackingCookieBlocked()
+{
+    // Nothing to do.
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 DataChannelParent::NotifyTrackingResource(bool aIsThirdParty)
 {
     // Nothing to do.
     return NS_OK;
 }
 
 NS_IMETHODIMP
 DataChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
--- a/netwerk/protocol/file/FileChannelParent.cpp
+++ b/netwerk/protocol/file/FileChannelParent.cpp
@@ -34,16 +34,23 @@ FileChannelParent::SetParentListener(Htt
 NS_IMETHODIMP
 FileChannelParent::NotifyTrackingProtectionDisabled()
 {
   // Nothing to do.
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FileChannelParent::NotifyTrackingCookieBlocked()
+{
+  // Nothing to do.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FileChannelParent::NotifyTrackingResource(bool aIsThirdParty)
 {
   // Nothing to do.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FileChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -572,16 +572,23 @@ FTPChannelParent::SetParentListener(Http
 NS_IMETHODIMP
 FTPChannelParent::NotifyTrackingProtectionDisabled()
 {
   // One day, this should probably be filled in.
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FTPChannelParent::NotifyTrackingCookieBlocked()
+{
+  // One day, this should probably be filled in.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FTPChannelParent::NotifyTrackingResource(bool aIsThirdParty)
 {
   // One day, this should probably be filled in.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FTPChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -343,16 +343,31 @@ HttpBackgroundChannelChild::RecvNotifyTr
   // NotifyTrackingProtectionDisabled has no order dependency to OnStartRequest.
   // It this be handled as soon as possible
   mChannelChild->ProcessNotifyTrackingProtectionDisabled();
 
   return IPC_OK();
 }
 
 IPCResult
+HttpBackgroundChannelChild::RecvNotifyTrackingCookieBlocked()
+{
+  LOG(("HttpBackgroundChannelChild::RecvNotifyTrackingCookieBlocked [this=%p]\n", this));
+  MOZ_ASSERT(OnSocketThread());
+
+  if (NS_WARN_IF(!mChannelChild)) {
+    return IPC_OK();
+  }
+
+  mChannelChild->ProcessNotifyTrackingCookieBlocked();
+
+  return IPC_OK();
+}
+
+IPCResult
 HttpBackgroundChannelChild::RecvNotifyTrackingResource(const bool& aIsThirdParty)
 {
   LOG(("HttpBackgroundChannelChild::RecvNotifyTrackingResource thirdparty=%d "
        "[this=%p]\n", static_cast<int>(aIsThirdParty), this));
   MOZ_ASSERT(OnSocketThread());
 
   if (NS_WARN_IF(!mChannelChild)) {
     return IPC_OK();
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.h
@@ -60,16 +60,18 @@ protected:
   IPCResult RecvFlushedForDiversion() override;
 
   IPCResult RecvDivertMessages() override;
 
   IPCResult RecvOnStartRequestSent() override;
 
   IPCResult RecvNotifyTrackingProtectionDisabled() override;
 
+  IPCResult RecvNotifyTrackingCookieBlocked() override;
+
   IPCResult RecvNotifyTrackingResource(const bool& aIsThirdParty) override;
 
   IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& info) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
 private:
   virtual ~HttpBackgroundChannelChild();
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -375,16 +375,43 @@ HttpBackgroundChannelParent::OnNotifyTra
 
     return NS_SUCCEEDED(rv);
   }
 
   return SendNotifyTrackingProtectionDisabled();
 }
 
 bool
+HttpBackgroundChannelParent::OnNotifyTrackingCookieBlocked()
+{
+  LOG(("HttpBackgroundChannelParent::OnNotifyTrackingCookieBlocked [this=%p]\n", this));
+  AssertIsInMainProcess();
+
+  if (NS_WARN_IF(!mIPCOpened)) {
+    return false;
+  }
+
+  if (!IsOnBackgroundThread()) {
+    MutexAutoLock lock(mBgThreadMutex);
+    nsresult rv = mBackgroundThread->Dispatch(
+      NewRunnableMethod(
+        "net::HttpBackgroundChannelParent::OnNotifyTrackingCookieBlocked",
+        this,
+        &HttpBackgroundChannelParent::OnNotifyTrackingCookieBlocked),
+      NS_DISPATCH_NORMAL);
+
+    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+    return NS_SUCCEEDED(rv);
+  }
+
+  return SendNotifyTrackingCookieBlocked();
+}
+
+bool
 HttpBackgroundChannelParent::OnNotifyTrackingResource(bool aIsThirdParty)
 {
   LOG(("HttpBackgroundChannelParent::OnNotifyTrackingResource thirdparty=%d "
        "[this=%p]\n", static_cast<int>(aIsThirdParty), this));
   AssertIsInMainProcess();
 
   if (NS_WARN_IF(!mIPCOpened)) {
     return false;
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -64,16 +64,19 @@ public:
 
   // To send FlushedForDiversion and DivertMessages messages
   // over background channel.
   bool OnDiversion();
 
   // To send NotifyTrackingProtectionDisabled message over background channel.
   bool OnNotifyTrackingProtectionDisabled();
 
+  // To send NotifyTrackingCookieBlocked message over background channel.
+  bool OnNotifyTrackingCookieBlocked();
+
   // To send NotifyTrackingResource message over background channel.
   bool OnNotifyTrackingResource(bool aIsThirdParty);
 
   // To send SetClassifierMatchedInfo message over background channel.
   bool OnSetClassifierMatchedInfo(const nsACString& aList,
                                   const nsACString& aProvider,
                                   const nsACString& aFullHash);
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -5,16 +5,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "nsHttp.h"
 #include "nsICacheEntry.h"
+#include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/extensions/StreamFilterParent.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
@@ -2035,16 +2036,33 @@ HttpChannelChild::ProcessNotifyTrackingP
       "nsChannelClassifier::NotifyTrackingProtectionDisabled",
       [self]() {
         nsChannelClassifier::NotifyTrackingProtectionDisabled(self);
       }),
     NS_DISPATCH_NORMAL);
 }
 
 void
+HttpChannelChild::ProcessNotifyTrackingCookieBlocked()
+{
+  LOG(("HttpChannelChild::ProcessNotifyTrackingCookieBlocked [this=%p]\n", this));
+  MOZ_ASSERT(OnSocketThread());
+
+  RefPtr<HttpChannelChild> self = this;
+  nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+  neckoTarget->Dispatch(
+    NS_NewRunnableFunction(
+      "nsChannelClassifier::NotifyTrackingCookieBlocked",
+      [self]() {
+        AntiTrackingCommon::NotifyRejection(self);
+      }),
+    NS_DISPATCH_NORMAL);
+}
+
+void
 HttpChannelChild::ProcessNotifyTrackingResource(bool aIsThirdParty)
 {
   LOG(("HttpChannelChild::ProcessNotifyTrackingResource thirdparty=%d "
        "[this=%p]\n", static_cast<int>(aIsThirdParty), this));
   MOZ_ASSERT(OnSocketThread());
 
   SetIsTrackingResource(aIsThirdParty);
 }
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -248,16 +248,17 @@ private:
   void ProcessOnStopRequest(const nsresult& aStatusCode,
                             const ResourceTimingStruct& aTiming,
                             const nsHttpHeaderArray& aResponseTrailers);
   void ProcessOnProgress(const int64_t& aProgress, const int64_t& aProgressMax);
   void ProcessOnStatus(const nsresult& aStatus);
   void ProcessFlushedForDiversion();
   void ProcessDivertMessages();
   void ProcessNotifyTrackingProtectionDisabled();
+  void ProcessNotifyTrackingCookieBlocked();
   void ProcessNotifyTrackingResource(bool aIsThirdParty);
   void ProcessSetClassifierMatchedInfo(const nsCString& aList,
                                        const nsCString& aProvider,
                                        const nsCString& aFullHash);
 
   // Return true if we need to tell the parent the size of unreported received
   // data
   bool NeedToReportBytesRead();
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1800,17 +1800,28 @@ HttpChannelParent::SetParentListener(Htt
 }
 
 NS_IMETHODIMP
 HttpChannelParent::NotifyTrackingProtectionDisabled()
 {
   LOG(("HttpChannelParent::NotifyTrackingProtectionDisabled [this=%p]\n", this));
   if (!mIPCClosed) {
     MOZ_ASSERT(mBgParent);
-    Unused << mBgParent->OnNotifyTrackingProtectionDisabled();
+    Unused << NS_WARN_IF(!mBgParent->OnNotifyTrackingProtectionDisabled());
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::NotifyTrackingCookieBlocked()
+{
+  LOG(("HttpChannelParent::NotifyTrackingCookieBlocked [this=%p]\n", this));
+  if (!mIPCClosed) {
+    MOZ_ASSERT(mBgParent);
+    Unused << NS_WARN_IF(!mBgParent->OnNotifyTrackingCookieBlocked());
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
                                             const nsACString& aProvider,
                                             const nsACString& aFullHash)
--- a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
+++ b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
@@ -51,16 +51,19 @@ child:
 
   // Child should resume processing the ChannelEventQueue, i.e. diverting any
   // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
   async DivertMessages();
 
   // Tell the child that tracking protection was disabled for this load.
   async NotifyTrackingProtectionDisabled();
 
+  // Tell the child that tracking cookies are blocked for this load.
+  async NotifyTrackingCookieBlocked();
+
   // Tell the child that the resource being loaded is on the tracking
   // protection list.
   async NotifyTrackingResource(bool aIsThirdParty);
 
   // Tell the child information of matched URL againts SafeBrowsing list
   async SetClassifierMatchedInfo(ClassifierInfo info);
 
   async __delete__();
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -3138,17 +3138,17 @@ GeckoDriver.prototype.fullscreenWindow =
  * no modal is displayed.
  */
 GeckoDriver.prototype.dismissDialog = async function() {
   let win = assert.open(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   await new Promise(resolve => {
     win.addEventListener("DOMModalDialogClosed", whenIdle(win, () => {
-      this.dialog = null;
+      this.dialog = modal.findModalDialogs(this.curBrowser);
       resolve();
     }), {once: true});
 
     let {button0, button1} = this.dialog.ui;
     (button1 ? button1 : button0).click();
   });
 };
 
@@ -3157,17 +3157,17 @@ GeckoDriver.prototype.dismissDialog = as
  * no modal is displayed.
  */
 GeckoDriver.prototype.acceptDialog = async function() {
   let win = assert.open(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   await new Promise(resolve => {
     win.addEventListener("DOMModalDialogClosed", whenIdle(win, () => {
-      this.dialog = null;
+      this.dialog = modal.findModalDialogs(this.curBrowser);
       resolve();
     }), {once: true});
 
     let {button0} = this.dialog.ui;
     button0.click();
   });
 };
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
@@ -1,230 +1,193 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
 from __future__ import absolute_import
 
 from marionette_driver.by import By
 from marionette_driver.expected import element_present
 from marionette_driver import errors
 from marionette_driver.marionette import Alert
 from marionette_driver.wait import Wait
 
-from marionette_harness import MarionetteTestCase, WindowManagerMixin
+from marionette_harness import MarionetteTestCase, parameterized, WindowManagerMixin
 
 
 class BaseAlertTestCase(WindowManagerMixin, MarionetteTestCase):
 
     @property
     def alert_present(self):
         try:
             Alert(self.marionette).text
             return True
         except errors.NoAlertPresentException:
             return False
 
     def wait_for_alert(self, timeout=None):
         Wait(self.marionette, timeout=timeout).until(
             lambda _: self.alert_present)
 
-    def wait_for_alert_closed(self, timeout=None):
-        Wait(self.marionette, timeout=timeout).until(
-            lambda _: not self.alert_present)
-
 
 class TestTabModalAlerts(BaseAlertTestCase):
 
     def setUp(self):
         super(TestTabModalAlerts, self).setUp()
         self.assertTrue(self.marionette.get_pref("prompts.tab_modal.enabled",
                         "Tab modal alerts should be enabled by default."))
 
         self.test_page = self.marionette.absolute_url("test_tab_modal_dialogs.html")
         self.marionette.navigate(self.test_page)
 
     def tearDown(self):
-        # Ensure to close a possible remaining tab modal dialog
+        # Ensure to close all possible remaining tab modal dialogs
         try:
-            alert = self.marionette.switch_to_alert()
-            alert.dismiss()
-
-            self.wait_for_alert_closed()
-        except:
+            while True:
+                alert = self.marionette.switch_to_alert()
+                alert.dismiss()
+        except errors.NoAlertPresentException:
             pass
 
         super(TestTabModalAlerts, self).tearDown()
 
     def test_no_alert_raises(self):
         with self.assertRaises(errors.NoAlertPresentException):
             Alert(self.marionette).accept()
         with self.assertRaises(errors.NoAlertPresentException):
             Alert(self.marionette).dismiss()
 
-    def test_alert_accept(self):
-        self.marionette.find_element(By.ID, "tab-modal-alert").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.accept()
-
-    def test_alert_dismiss(self):
-        self.marionette.find_element(By.ID, "tab-modal-alert").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.dismiss()
-
-    def test_confirm_accept(self):
-        self.marionette.find_element(By.ID, "tab-modal-confirm").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.accept()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "confirm-result").text == "true")
-
-    def test_confirm_dismiss(self):
-        self.marionette.find_element(By.ID, "tab-modal-confirm").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.dismiss()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "confirm-result").text == "false")
-
-    def test_prompt_accept(self):
-        self.marionette.find_element(By.ID, "tab-modal-prompt").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.accept()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "prompt-result").text == "")
-
-    def test_prompt_dismiss(self):
-        self.marionette.find_element(By.ID, "tab-modal-prompt").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        alert.dismiss()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "prompt-result").text == "null")
-
     def test_alert_opened_before_session_starts(self):
         self.marionette.find_element(By.ID, "tab-modal-alert").click()
         self.wait_for_alert()
 
         # Restart the session to ensure we still find the formerly left-open dialog.
         self.marionette.delete_session()
         self.marionette.start_session()
 
         alert = self.marionette.switch_to_alert()
         alert.dismiss()
 
-    def test_alert_text(self):
-        with self.assertRaises(errors.NoAlertPresentException):
-            alert = self.marionette.switch_to_alert()
-            alert.text
-        self.marionette.find_element(By.ID, "tab-modal-alert").click()
+    @parameterized("alert", "alert", "undefined")
+    @parameterized("confirm", "confirm", "true")
+    @parameterized("prompt", "prompt", "")
+    def test_accept(self, value, result):
+        self.marionette.find_element(By.ID, "tab-modal-{}".format(value)).click()
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
-        self.assertEqual(alert.text, "Marionette alert")
         alert.accept()
+        self.assertEqual(self.marionette.find_element(By.ID, "text").text, result)
 
-    def test_prompt_text(self):
+    @parameterized("alert", "alert", "undefined")
+    @parameterized("confirm", "confirm", "false")
+    @parameterized("prompt", "prompt", "null")
+    def test_dismiss(self, value, result):
+        self.marionette.find_element(By.ID, "tab-modal-{}".format(value)).click()
+        self.wait_for_alert()
+        alert = self.marionette.switch_to_alert()
+        alert.dismiss()
+        self.assertEqual(self.marionette.find_element(By.ID, "text").text, result)
+
+    @parameterized("alert", "alert", "Marionette alert")
+    @parameterized("confirm", "confirm", "Marionette confirm")
+    @parameterized("prompt", "prompt", "Marionette prompt")
+    def test_text(self, value, text):
         with self.assertRaises(errors.NoAlertPresentException):
             alert = self.marionette.switch_to_alert()
             alert.text
-        self.marionette.find_element(By.ID, "tab-modal-prompt").click()
+        self.marionette.find_element(By.ID, "tab-modal-{}".format(value)).click()
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
-        self.assertEqual(alert.text, "Marionette prompt")
+        self.assertEqual(alert.text, text)
         alert.accept()
 
-    def test_confirm_text(self):
-        with self.assertRaises(errors.NoAlertPresentException):
-            alert = self.marionette.switch_to_alert()
-            alert.text
-        self.marionette.find_element(By.ID, "tab-modal-confirm").click()
-        self.wait_for_alert()
-        alert = self.marionette.switch_to_alert()
-        self.assertEqual(alert.text, "Marionette confirm")
-        alert.accept()
-
-    def test_set_text_throws(self):
+    @parameterized("alert", "alert")
+    @parameterized("confirm", "confirm")
+    def test_set_text_throws(self, value):
         with self.assertRaises(errors.NoAlertPresentException):
             Alert(self.marionette).send_keys("Foo")
-        self.marionette.find_element(By.ID, "tab-modal-alert").click()
+        self.marionette.find_element(By.ID, "tab-modal-{}".format(value)).click()
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
         with self.assertRaises(errors.ElementNotInteractableException):
             alert.send_keys("Foo")
         alert.accept()
 
     def test_set_text_accept(self):
         self.marionette.find_element(By.ID, "tab-modal-prompt").click()
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
-        alert.send_keys("Some text!")
+        alert.send_keys("Foo bar")
         alert.accept()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "prompt-result").text == "Some text!")
+        self.assertEqual(self.marionette.find_element(By.ID, "text").text, "Foo bar")
 
     def test_set_text_dismiss(self):
         self.marionette.find_element(By.ID, "tab-modal-prompt").click()
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
         alert.send_keys("Some text!")
         alert.dismiss()
-        self.wait_for_condition(
-            lambda mn: mn.find_element(By.ID, "prompt-result").text == "null")
+        self.assertEqual(self.marionette.find_element(By.ID, "text").text, "null")
 
     def test_unrelated_command_when_alert_present(self):
         self.marionette.find_element(By.ID, "tab-modal-alert").click()
         self.wait_for_alert()
         with self.assertRaises(errors.UnexpectedAlertOpen):
-            self.marionette.find_element(By.ID, "click-result")
+            self.marionette.find_element(By.ID, "text")
 
     def test_modal_is_dismissed_after_unexpected_alert(self):
         self.marionette.find_element(By.ID, "tab-modal-alert").click()
         self.wait_for_alert()
         with self.assertRaises(errors.UnexpectedAlertOpen):
-            self.marionette.find_element(By.ID, "click-result")
+            self.marionette.find_element(By.ID, "text")
 
         assert not self.alert_present
 
+    def test_handle_two_modal_dialogs(self):
+        self.marionette.find_element(By.ID, "open-two-dialogs").click()
+
+        self.wait_for_alert()
+        alert1 = self.marionette.switch_to_alert()
+        alert1.send_keys("foo")
+        alert1.accept()
+
+        alert2 = self.marionette.switch_to_alert()
+        alert2.send_keys("bar")
+        alert2.accept()
+
+        self.assertEqual(self.marionette.find_element(By.ID, "text1").text, "foo")
+        self.assertEqual(self.marionette.find_element(By.ID, "text2").text, "bar")
+
 
 class TestModalAlerts(BaseAlertTestCase):
 
     def setUp(self):
         super(TestModalAlerts, self).setUp()
-        self.marionette.set_pref("network.auth.non-web-content-triggered-resources-http-auth-allow",
-                                 True)
+        self.marionette.set_pref(
+            "network.auth.non-web-content-triggered-resources-http-auth-allow",
+            True)
 
     def tearDown(self):
         # Ensure to close a possible remaining modal dialog
         self.close_all_windows()
-        self.marionette.clear_pref("network.auth.non-web-content-triggered-resources-http-auth-allow")
+        self.marionette.clear_pref(
+            "network.auth.non-web-content-triggered-resources-http-auth-allow")
 
         super(TestModalAlerts, self).tearDown()
 
     def test_http_auth_dismiss(self):
         self.marionette.navigate(self.marionette.absolute_url("http_auth"))
         self.wait_for_alert(timeout=self.marionette.timeout.page_load)
 
         alert = self.marionette.switch_to_alert()
         alert.dismiss()
 
-        self.wait_for_alert_closed()
-
         status = Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
             element_present(By.ID, "status")
         )
         self.assertEqual(status.text, "restricted")
 
     def test_alert_opened_before_session_starts(self):
         self.marionette.navigate(self.marionette.absolute_url("http_auth"))
         self.wait_for_alert(timeout=self.marionette.timeout.page_load)
 
         # Restart the session to ensure we still find the formerly left-open dialog.
         self.marionette.delete_session()
         self.marionette.start_session()
 
         alert = self.marionette.switch_to_alert()
         alert.dismiss()
-
-        self.wait_for_alert_closed()
--- a/testing/marionette/harness/marionette_harness/www/test_tab_modal_dialogs.html
+++ b/testing/marionette/harness/marionette_harness/www/test_tab_modal_dialogs.html
@@ -2,33 +2,42 @@
    - 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/. -->
 
 <!DOCTYPE html>
 <html>
 <head>
   <title>Dialog Test</title>
   <script type="text/javascript">
+    function setInnerText(id, value) {
+      document.getElementById(id).innerHTML = "<p>" + value + "</p>";
+    }
+
     function handleAlert () {
-      window.alert('Marionette alert');
+      setInnerText("text", alert("Marionette alert"));
     }
 
     function handleConfirm () {
-      var alertAccepted = window.confirm('Marionette confirm');
-      document.getElementById('confirm-result').innerHTML = alertAccepted;
+      setInnerText("text", confirm("Marionette confirm"));
     }
 
     function handlePrompt () {
-      var promptText = window.prompt('Marionette prompt');
-      document.getElementById('prompt-result').innerHTML = promptText === null ? 'null' : promptText;
+      setInnerText("text", prompt("Marionette prompt"));
+    }
+
+    function handleTwoDialogs() {
+      setInnerText("text1", prompt("First"));
+      setInnerText("text2", prompt("Second"));
     }
   </script>
 </head>
 <body>
-   <a href="#" id="tab-modal-alert" onclick="handleAlert()">Open an alert dialog.</a>
-   <a href="#" id="tab-modal-confirm" onclick="handleConfirm()">Open a confirm dialog.</a>
-   <a href="#" id="tab-modal-prompt" onclick="handlePrompt()">Open a prompt dialog.</a>
-   <a href="#" id="click-handler" onclick="document.getElementById('click-result').innerHTML='result';">Make text appear.</a>
-   <div id="confirm-result"></div>
-   <div id="prompt-result"></div>
-   <div id="click-result"></div>
+  <a href="#" id="tab-modal-alert" onclick="handleAlert()">Open an alert dialog.</a>
+  <a href="#" id="tab-modal-confirm" onclick="handleConfirm()">Open a confirm dialog.</a>
+  <a href="#" id="tab-modal-prompt" onclick="handlePrompt()">Open a prompt dialog.</a>
+  <a href="#" id="open-two-dialogs" onclick="handleTwoDialogs()">Open two prompts.</a>
+  <a href="#" id="click-handler" onclick="document.getElementById('text').innerHTML='result';">Make text appear.</a>
+
+  <div id="text"></div>
+  <div id="text1"></div>
+  <div id="text2"></div>
 </body>
 </html>
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -14,20 +14,23 @@
 #include "mozilla/StaticPrefs.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsICookiePermission.h"
 #include "nsICookieService.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIIOService.h"
+#include "nsIParentChannel.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptSecurityManager.h"
 #include "prtime.h"
 
 #define ANTITRACKING_PERM_KEY "3rdPartyStorage"
 
 using namespace mozilla;
 using mozilla::dom::ContentChild;
@@ -828,8 +831,73 @@ AntiTrackingCommon::IsOnContentBlockingA
   }
 
   if (!aIsAllowListed) {
     LOG(("No user override found"));
   }
 
   return NS_OK;
 }
+
+/* static */ void
+AntiTrackingCommon::NotifyRejection(nsIChannel* aChannel)
+{
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+  if (!httpChannel) {
+    return;
+  }
+
+  // Can be called in EITHER the parent or child process.
+  nsCOMPtr<nsIParentChannel> parentChannel;
+  NS_QueryNotificationCallbacks(aChannel, parentChannel);
+  if (parentChannel) {
+    // This channel is a parent-process proxy for a child process request.
+    // Tell the child process channel to do this instead.
+    parentChannel->NotifyTrackingCookieBlocked();
+    return;
+  }
+
+  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
+  if (!thirdPartyUtil) {
+    return;
+  }
+
+  nsCOMPtr<mozIDOMWindowProxy> win;
+  nsresult rv = thirdPartyUtil->GetTopWindowForChannel(httpChannel,
+                                                       getter_AddRefs(win));
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(win);
+  if (!pwin) {
+    return;
+  }
+
+  pwin->NotifyContentBlockingState(
+    nsIWebProgressListener::STATE_BLOCKED_TRACKING_COOKIES, httpChannel);
+}
+
+/* static */ void
+AntiTrackingCommon::NotifyRejection(nsPIDOMWindowInner* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+
+  nsIDocument* document = aWindow->GetExtantDoc();
+  if (!document) {
+    return;
+  }
+
+  nsCOMPtr<nsIHttpChannel> httpChannel =
+    do_QueryInterface(document->GetChannel());
+  if (!httpChannel) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> pwin;
+  auto* outer = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
+  if (outer) {
+    pwin = outer->GetTopOuter();
+  }
+
+  if (pwin) {
+    pwin->NotifyContentBlockingState(
+      nsIWebProgressListener::STATE_BLOCKED_TRACKING_COOKIES, httpChannel);
+  }
+}
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_antitrackingservice_h
 #define mozilla_antitrackingservice_h
 
 #include "nsString.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/RefPtr.h"
 
+class nsIChannel;
 class nsIHttpChannel;
 class nsIPrincipal;
 class nsIURI;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 
 class AntiTrackingCommon final
@@ -83,13 +84,21 @@ public:
                                                              const nsCString& aGrantedOrigin,
                                                              FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
 
 
   // Check whether a top window URI is on the content blocking allow list.
   static nsresult
   IsOnContentBlockingAllowList(nsIURI* aTopWinURI, bool& aIsAllowListed);
 
+  // This method can be called on the parent process or on the content process.
+  // The notification is propagated to the child channel if aChannel is a parent
+  // channel proxy.
+  static void
+  NotifyRejection(nsIChannel* aChannel);
+
+  static void
+  NotifyRejection(nsPIDOMWindowInner* aWindow);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_antitrackingservice_h
--- a/toolkit/components/antitracking/test/browser/browser_blockingStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingStorage.js
@@ -26,9 +26,13 @@ AntiTracking.runTest("sessionStorage",
   async _ => {
     sessionStorage.foo = 42;
     ok(true, "SessionStorage is always allowed");
   },
   async _ => {
     await new Promise(resolve => {
       Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
     });
-  });
+  },
+  [],
+  true,
+  true,
+  false);
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -16,19 +16,27 @@ const BEHAVIOR_ACCEPT         = Ci.nsICo
 const BEHAVIOR_REJECT_FOREIGN = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
 const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
 
 var gFeatures = undefined;
 
 let {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
 
 this.AntiTracking = {
-  runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs, windowOpenTest = true, userInteractionTest = true) {
+  runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs, windowOpenTest = true, userInteractionTest = true, expectedBlockingNotifications = true) {
     // Here we want to test that a 3rd party context is simply blocked.
-    this._createTask(name, true, true, false, callbackTracking, extraPrefs);
+    this._createTask({
+      name,
+      cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+      blockingByContentBlocking: true,
+      allowList: false,
+      callback: callbackTracking,
+      extraPrefs,
+      expectedBlockingNotifications,
+    });
     this._createCleanupTask(cleanupFunction);
 
     if (callbackNonTracking) {
       let runExtraTests = true;
       let options = {};
       if (typeof callbackNonTracking == "object") {
         callbackNonTracking = callbackNonTracking.callback;
         runExtraTests = callbackNonTracking.runExtraTests;
@@ -54,45 +62,114 @@ this.AntiTracking = {
       // Phase 1: Here we want to test that a 3rd party context is not blocked if pref is off.
       if (runExtraTests) {
         // There are four ways in which the third-party context may not be blocked:
         //   * If the cookieBehavior pref causes it to not be blocked.
         //   * If the contentBlocking pref causes it to not be blocked.
         //   * If both of these prefs cause it to not be blocked.
         //   * If the top-level page is on the content blocking allow list.
         // All of these cases are tested here.
-        this._createTask(name, BEHAVIOR_ACCEPT, true, false, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_ACCEPT,
+          blockingByContentBlocking: true,
+          allowList: false,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_REJECT_FOREIGN, false, false, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+          blockingByContentBlocking: false,
+          allowList: false,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_REJECT_TRACKER, false, false, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+          blockingByContentBlocking: false,
+          allowList: false,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_REJECT_FOREIGN, false, true, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+          blockingByContentBlocking: false,
+          allowList: true,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_REJECT_TRACKER, false, true, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+          blockingByContentBlocking: false,
+          allowList: true,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_ACCEPT, false, false, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_ACCEPT,
+          blockingByContentBlocking: false,
+          allowList: false,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
         // Try testing using the allow list with both reject foreign and reject tracker cookie behaviors
-        this._createTask(name, BEHAVIOR_REJECT_FOREIGN, true, true, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+          blockingByContentBlocking: true,
+          allowList: true,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
 
-        this._createTask(name, BEHAVIOR_REJECT_TRACKER, true, true, callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+          blockingByContentBlocking: true,
+          allowList: true,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
       } else {
-        this._createTask(name, options.cookieBehavior,
-                         options.blockingByContentBlocking,
-                         options.blockingByAllowList,
-                         callbackNonTracking);
+        this._createTask({
+          name,
+          cookieBehavior: options.cookieBehavior,
+          blockingByContentBlocking: options.blockingByContentBlocking,
+          allowList: options.blockingByAllowList,
+          callback: callbackNonTracking,
+          extraPrefs: [],
+          expectedBlockingNotifications: false,
+        });
         this._createCleanupTask(cleanupFunction);
       }
 
       // Phase 2: Here we want to test that a third-party context doesn't
       // get blocked with when the same origin is opened through window.open().
       if (windowOpenTest) {
         this._createWindowOpenTask(name, callbackTracking, callbackNonTracking, extraPrefs);
         this._createCleanupTask(cleanupFunction);
@@ -120,44 +197,57 @@ this.AntiTracking = {
 
     if (extraPrefs && Array.isArray(extraPrefs) && extraPrefs.length) {
       await SpecialPowers.pushPrefEnv({"set": extraPrefs });
     }
 
     await UrlClassifierTestUtils.addTestTrackers();
   },
 
-  _createTask(name, cookieBehavior, blockingByContentBlocking,
-              allowList, callback, extraPrefs) {
+  _createTask(options) {
     add_task(async function() {
-      info("Starting " + (cookieBehavior != BEHAVIOR_ACCEPT ? "blocking" : "non-blocking") + " cookieBehavior (" + cookieBehavior + ") and " +
-                         (blockingByContentBlocking ? "blocking" : "non-blocking") + " contentBlocking with" +
-                         (allowList ? "" : "out") + " allow list test " + name);
+      info("Starting " + (options.cookieBehavior != BEHAVIOR_ACCEPT ? "blocking" : "non-blocking") + " cookieBehavior (" + options.cookieBehavior + ") and " +
+                         (options.blockingByContentBlocking ? "blocking" : "non-blocking") + " contentBlocking with" +
+                         (options.allowList ? "" : "out") + " allow list test " + options.name);
+
+     requestLongerTimeout(2);
+
+      await AntiTracking._setupTest(options.cookieBehavior,
+                                    options.blockingByContentBlocking,
+                                    options.extraPrefs);
 
-      await AntiTracking._setupTest(cookieBehavior, blockingByContentBlocking, extraPrefs);
+      let cookieBlocked = 0;
+      let listener = {
+        onSecurityChange(webProgress, request, stateFlags, status) {
+          if (stateFlags & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_COOKIES) {
+            ++cookieBlocked;
+          }
+        }
+      };
+      gBrowser.addProgressListener(listener);
 
       info("Creating a new tab");
       let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
       gBrowser.selectedTab = tab;
 
       let browser = gBrowser.getBrowserForTab(tab);
       await BrowserTestUtils.browserLoaded(browser);
 
-      if (allowList) {
+      if (options.allowList) {
         info("Disabling content blocking for this page");
         ContentBlocking.disableForCurrentPage();
 
         // The previous function reloads the browser, so wait for it to load again!
         await BrowserTestUtils.browserLoaded(browser);
       }
 
       info("Creating a 3rd party content");
       await ContentTask.spawn(browser,
                               { page: TEST_3RD_PARTY_PAGE,
-                                callback: callback.toString() },
+                                callback: options.callback.toString() },
                               async function(obj) {
         await new content.Promise(resolve => {
           let ifr = content.document.createElement("iframe");
           ifr.onload = function() {
             info("Sending code to the 3rd party content");
             ifr.contentWindow.postMessage(obj.callback, "*");
           };
 
@@ -181,24 +271,28 @@ this.AntiTracking = {
             ok(false, "Unknown message");
           });
 
           content.document.body.appendChild(ifr);
           ifr.src = obj.page;
         });
       });
 
-      if (allowList) {
+      if (options.allowList) {
         info("Enabling content blocking for this page");
         ContentBlocking.enableForCurrentPage();
 
         // The previous function reloads the browser, so wait for it to load again!
         await BrowserTestUtils.browserLoaded(browser);
       }
 
+      gBrowser.removeProgressListener(listener);
+
+      is(!!cookieBlocked, options.expectedBlockingNotifications, "Checking cookie blocking notifications");
+
       info("Removing the tab");
       BrowserTestUtils.removeTab(tab);
     });
   },
 
   _createCleanupTask(cleanupFunction) {
     add_task(async function() {
       info("Cleaning up.");
@@ -206,16 +300,19 @@ this.AntiTracking = {
         await cleanupFunction();
       }
     });
   },
 
   _createWindowOpenTask(name, blockingCallback, nonBlockingCallback, extraPrefs) {
     add_task(async function() {
       info("Starting window-open test " + name);
+
+      requestLongerTimeout(2);
+
       await AntiTracking._setupTest(BEHAVIOR_REJECT_TRACKER, true, extraPrefs);
 
       info("Creating a new tab");
       let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
       gBrowser.selectedTab = tab;
 
       let browser = gBrowser.getBrowserForTab(tab);
       await BrowserTestUtils.browserLoaded(browser);
@@ -267,16 +364,19 @@ this.AntiTracking = {
       info("Removing the tab");
       BrowserTestUtils.removeTab(tab);
     });
   },
 
   _createUserInteractionTask(name, blockingCallback, nonBlockingCallback, extraPrefs) {
     add_task(async function() {
       info("Starting user-interaction test " + name);
+
+      requestLongerTimeout(2);
+
       await AntiTracking._setupTest(BEHAVIOR_REJECT_TRACKER, true, extraPrefs);
 
       info("Creating a new tab");
       let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
       gBrowser.selectedTab = tab;
 
       let browser = gBrowser.getBrowserForTab(tab);
       await BrowserTestUtils.browserLoaded(browser);
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -417,16 +417,22 @@ NS_IMETHODIMP nsExtProtocolChannel::SetP
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingProtectionDisabled()
 {
   // nothing to do
   return NS_OK;
 }
 
+NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingCookieBlocked()
+{
+  // nothing to do
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsExtProtocolChannel::SetClassifierMatchedInfo(const nsACString& aList,
                                                              const nsACString& aProvider,
                                                              const nsACString& aFullHash)
 {
   // nothing to do
   return NS_OK;
 }