Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 26 Feb 2019 18:58:45 +0200
changeset 519068 3c172c9d894541142342901bcaecb73715099d0e
parent 519067 805264e071d9071f73fead2d45e75538d9922f2a (current diff)
parent 519026 d326a9d5f77bdbb3f43581b2ba3d268881698cdc (diff)
child 519069 6e4eb864eeb65002d6e7f8632dc65b40be838859
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
browser/app/profile/firefox.js
browser/components/sessionstore/test/browser_preopened_apptabs.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1000,21 +1000,17 @@ pref("dom.ipc.plugins.sandbox-level.flas
 #endif
 
 #if defined(MOZ_CONTENT_SANDBOX)
 // This controls the strength of the Windows content process sandbox for testing
 // purposes. This will require a restart.
 // On windows these levels are:
 // See - security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
 // SetSecurityLevelForContentProcess() for what the different settings mean.
-#if defined(_ARM64_)
-pref("security.sandbox.content.level", 2);
-#else
 pref("security.sandbox.content.level", 5);
-#endif
 
 // This controls the depth of stack trace that is logged when Windows sandbox
 // logging is turned on.  This is only currently available for the content
 // process because the only other sandbox (for GMP) has too strict a policy to
 // allow stack tracing.  This does not require a restart to take effect.
 pref("security.sandbox.windows.log.stackTraceDepth", 0);
 #endif
 
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -17,21 +17,16 @@
 .tab-throbber:not([busy]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
 .tab-icon-sound[pinned],
 .tab-sharing-icon-overlay,
 .tab-icon-overlay {
   display: none;
 }
 
-.tab-icon-pending[preopened],
-.tab-icon-image[preopened] {
-  visibility: hidden;
-}
-
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
 .tab-icon-overlay[activemedia-blocked][pinned],
 .tab-icon-overlay[crashed] {
   display: -moz-box;
 }
 
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -27,17 +27,16 @@ window._gBrowser = {
     Services.els.addSystemEventListener(document, "keydown", this, false);
     if (AppConstants.platform == "macosx") {
       Services.els.addSystemEventListener(document, "keypress", this, false);
     }
     window.addEventListener("sizemodechange", this);
     window.addEventListener("occlusionstatechange", this);
 
     this._setupInitialBrowserAndTab();
-    this._preopenPinnedTabs();
 
     if (Services.prefs.getBoolPref("browser.display.use_system_colors")) {
       this.tabpanels.style.backgroundColor = "-moz-default-background-color";
     } else if (Services.prefs.getIntPref("browser.display.document_color_use") == 2) {
       this.tabpanels.style.backgroundColor =
         Services.prefs.getCharPref("browser.display.background_color");
     }
 
@@ -379,39 +378,16 @@ window._gBrowser = {
     let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                   .createInstance(Ci.nsIWebProgress);
     filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
     this._tabListeners.set(tab, tabListener);
     this._tabFilters.set(tab, filter);
     browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
   },
 
-  _preopenPinnedTabs() {
-    let numPinnedTabs = 0;
-    let windows = browserWindows();
-    windows.getNext();
-    let isOnlyWindow = !windows.hasMoreElements();
-    if (isOnlyWindow) {
-      numPinnedTabs = Services.prefs.getIntPref("browser.tabs.firstWindowRestore.numPinnedTabs", 0);
-    }
-
-    for (let i = 0; i < numPinnedTabs; i++) {
-      let tab = this.addTrustedTab("about:blank", {
-        skipAnimation: true,
-        noInitialLabel: true,
-        skipBackgroundNotify: true,
-        createLazyBrowser: true,
-        pinned: true,
-        isForFirstWindowRestore: true,
-      });
-
-      tab.setAttribute("preopened", "true");
-    }
-  },
-
   /**
    * BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
    * MAKE SURE TO ADD IT HERE AS WELL.
    */
   get canGoBack() {
     return this.selectedBrowser.canGoBack;
   },
 
@@ -612,66 +588,46 @@ window._gBrowser = {
   },
 
   _updateTabBarForPinnedTabs() {
     this.tabContainer._unlockTabSizing();
     this.tabContainer._positionPinnedTabs();
     this.tabContainer._updateCloseButtons();
   },
 
-  _sendPinnedTabContentMessage(aTab) {
+  _notifyPinnedStatus(aTab) {
     this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: aTab.pinned });
-  },
-
-  _notifyPinnedStatus(aTab, aDeferContentMessage = false) {
-    if (!aDeferContentMessage) {
-      this._sendPinnedTabContentMessage(aTab);
-    }
 
     let event = document.createEvent("Events");
     event.initEvent(aTab.pinned ? "TabPinned" : "TabUnpinned", true, false);
     aTab.dispatchEvent(event);
   },
 
-  _maybeUpdateNumPinnedTabsPref() {
-    if (BrowserWindowTracker.getTopWindow() == window) {
-      Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
-                                this._numPinnedTabs);
-    }
-  },
-
-  activatePreopenedPinnedTab(aTab) {
-    this._insertBrowser(aTab);
-    this._sendPinnedTabContentMessage(aTab);
-  },
-
   pinTab(aTab) {
     if (aTab.pinned)
       return;
 
     if (aTab.hidden)
       this.showTab(aTab);
 
     this.moveTabTo(aTab, this._numPinnedTabs);
     aTab.setAttribute("pinned", "true");
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
-    this._maybeUpdateNumPinnedTabsPref();
   },
 
   unpinTab(aTab) {
     if (!aTab.pinned)
       return;
 
     this.moveTabTo(aTab, this._numPinnedTabs - 1);
     aTab.removeAttribute("pinned");
     aTab.style.marginInlineStart = "";
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
-    this._maybeUpdateNumPinnedTabsPref();
   },
 
   previewTab(aTab, aCallback) {
     let currentTab = this.selectedTab;
     try {
       // Suppress focus, ownership and selected tab changes
       this._previewMode = true;
       this.selectedTab = aTab;
@@ -2335,17 +2291,16 @@ window._gBrowser = {
     bulkOrderedOpen,
     charset,
     createLazyBrowser,
     eventDetail,
     focusUrlBar,
     forceNotRemote,
     fromExternal,
     index,
-    isForFirstWindowRestore,
     lazyTabTitle,
     name,
     nextTabParentId,
     noInitialLabel,
     noReferrer,
     opener,
     openerBrowser,
     originPrincipal,
@@ -2519,19 +2474,16 @@ window._gBrowser = {
       if (tabAfter) {
         this._updateTabsAfterInsert();
       } else {
         t._tPos = index;
       }
 
       if (pinned) {
         this._updateTabBarForPinnedTabs();
-        if (!isForFirstWindowRestore) {
-          this._maybeUpdateNumPinnedTabsPref();
-        }
       }
       this.tabContainer._setPositionalAttributes();
 
       TabBarVisibility.update();
 
       // If we don't have a preferred remote type, and we have a remote
       // opener, use the opener's remote type.
       if (!preferredRemoteType && openerBrowser) {
@@ -2601,25 +2553,23 @@ window._gBrowser = {
 
         if (lazyBrowserURI) {
           // Lazy browser must be explicitly registered so tab will appear as
           // a switch-to-tab candidate in autocomplete.
           this.UrlbarProviderOpenTabs.registerOpenTab(lazyBrowserURI.spec,
                                                       userContextId);
           b.registeredOpenURI = lazyBrowserURI;
         }
-        if (!isForFirstWindowRestore) {
-          SessionStore.setTabState(t, {
-            entries: [{
-              url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
-              title: lazyTabTitle,
-              triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
-            }],
-          });
-        }
+        SessionStore.setTabState(t, {
+          entries: [{
+            url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
+            title: lazyTabTitle,
+            triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
+          }],
+        });
       } else {
         this._insertBrowser(t, true);
       }
     } catch (e) {
       Cu.reportError("Failed to create tab");
       Cu.reportError(e);
       t.remove();
       if (t.linkedBrowser) {
@@ -2647,19 +2597,17 @@ window._gBrowser = {
       if (!aURIObject ||
           (doGetProtocolFlags(aURIObject) & URI_INHERITS_SECURITY_CONTEXT)) {
         b.createAboutBlankContentViewer(originPrincipal);
       }
     }
 
     // If we didn't swap docShells with a preloaded browser
     // then let's just continue loading the page normally.
-    if (!usingPreloadedContent &&
-        !createLazyBrowser &&
-        (!uriIsAboutBlank || !allowInheritPrincipal)) {
+    if (!usingPreloadedContent && (!uriIsAboutBlank || !allowInheritPrincipal)) {
       // pretend the user typed this so it'll be available till
       // the document successfully loads
       if (aURI && !gInitialPages.includes(aURI)) {
         b.userTypedValue = aURI;
       }
 
       let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       if (allowThirdPartyFixup) {
@@ -2702,17 +2650,17 @@ window._gBrowser = {
       requestAnimationFrame(function() {
         // kick the animation off
         t.setAttribute("fadein", "true");
       });
     }
 
     // Additionally send pinned tab events
     if (pinned) {
-      this._notifyPinnedStatus(t, isForFirstWindowRestore);
+      this._notifyPinnedStatus(t);
     }
 
     return t;
   },
 
   moveTabsToStart(contextTab) {
     let tabs = contextTab.multiselected ?
       this.selectedTabs :
@@ -3200,20 +3148,18 @@ window._gBrowser = {
     if (aTab.hidden)
       this.tabContainer._updateHiddenTabsStatus();
 
     // ... and fix up the _tPos properties immediately.
     for (let i = aTab._tPos; i < this.tabs.length; i++)
       this.tabs[i]._tPos = i;
 
     if (!this._windowIsClosing) {
-      if (wasPinned) {
+      if (wasPinned)
         this.tabContainer._positionPinnedTabs();
-        this._maybeUpdateNumPinnedTabsPref();
-      }
 
       // update tab close buttons state
       this.tabContainer._updateCloseButtons();
 
       setTimeout(function(tabs) {
         tabs._lastTabClosedByMouse = false;
       }, 0, this.tabContainer);
     }
@@ -3888,16 +3834,17 @@ window._gBrowser = {
     let createLazyBrowser = !aTab.linkedPanel;
     let params = {
       eventDetail: { adoptedTab: aTab },
       preferredRemoteType: linkedBrowser.remoteType,
       sameProcessAsFrameLoader: linkedBrowser.frameLoader,
       skipAnimation: true,
       index: aIndex,
       createLazyBrowser,
+      allowInheritPrincipal: createLazyBrowser,
     };
 
     let numPinned = this._numPinnedTabs;
     if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
       params.pinned = true;
     }
 
     if (aTab.hasAttribute("usercontextid")) {
@@ -5064,17 +5011,16 @@ class TabProgressListener {
           // remoteness or is a new tab/window).
           this.mBrowser.urlbarChangeTracker.startedLoad();
         }
         delete this.mBrowser.initialPageLoadedFromUserAction;
         // If the browser is loading it must not be crashed anymore
         this.mTab.removeAttribute("crashed");
       }
 
-      this.mTab.removeAttribute("preopened");
       if (this._shouldShowProgress(aRequest)) {
         if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
             aWebProgress && aWebProgress.isTopLevel) {
           this.mTab.setAttribute("busy", "true");
           gBrowser._tabAttrModified(this.mTab, ["busy"]);
           this.mTab._notselectedsinceload = !this.mTab.selected;
           gBrowser.syncThrobberAnimations(this.mTab);
         }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1932,20 +1932,20 @@
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
                     anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
-          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon,preopened"
+          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon"
                     anonid="tab-icon-pending"
                     class="tab-icon-pending"/>
-          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing,preopened"
+          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
           <xul:image xbl:inherits="sharing,selected=visuallyselected,pinned"
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -244,18 +244,18 @@ var SessionStore = {
   setBrowserState: function ss_setBrowserState(aState) {
     SessionStoreInternal.setBrowserState(aState);
   },
 
   getWindowState: function ss_getWindowState(aWindow) {
     return SessionStoreInternal.getWindowState(aWindow);
   },
 
-  setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite, aFirstWindow) {
-    SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite, aFirstWindow);
+  setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
+    SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
   },
 
   getTabState: function ss_getTabState(aTab) {
     return SessionStoreInternal.getTabState(aTab);
   },
 
   setTabState: function ss_setTabState(aTab, aState) {
     SessionStoreInternal.setTabState(aTab, aState);
@@ -726,17 +726,16 @@ var SessionStoreInternal = {
 
     this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
 
     this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
     this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
 
     this._restore_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
-    this._restore_pinned_tabs_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
     this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
 
     gResistFingerprintingEnabled = Services.prefs.getBoolPref("privacy.resistFingerprinting");
     Services.prefs.addObserver("privacy.resistFingerprinting", this);
   },
 
   /**
    * Called on application shutdown, after notifications:
@@ -2495,25 +2494,22 @@ var SessionStoreInternal = {
     if (DyingWindowCache.has(aWindow)) {
       let data = DyingWindowCache.get(aWindow);
       return JSON.stringify({ windows: [data] });
     }
 
     throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
   },
 
-  setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite, aFirstWindow) {
+  setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
     if (!aWindow.__SSi) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    this.restoreWindows(aWindow, aState, {
-      overwriteTabs: aOverwrite,
-      firstWindow: aFirstWindow,
-    });
+    this.restoreWindows(aWindow, aState, {overwriteTabs: aOverwrite});
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
   },
 
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab || !aTab.ownerGlobal) {
       throw Components.Exception("Need a valid tab", Cr.NS_ERROR_INVALID_ARG);
@@ -3202,17 +3198,16 @@ var SessionStoreInternal = {
     } catch (e) {}
 
     // Start the throbber to pretend we're doing something while actually
     // waiting for data from the frame script. This throbber is disabled
     // if the URI is a local about: URI.
     if (!uriObj || (uriObj && !window.gBrowser.isLocalAboutURI(uriObj))) {
       tab.setAttribute("busy", "true");
     }
-    tab.removeAttribute("preopened");
 
     // Hack to ensure that the about:home, about:newtab, and about:welcome
     // favicon is loaded instantaneously, to avoid flickering and improve
     // perceived performance.
     window.gBrowser.setDefaultIcon(tab, uriObj);
 
     TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
 
@@ -3650,29 +3645,26 @@ var SessionStoreInternal = {
 
     // disable smooth scrolling while adding, moving, removing and selecting tabs
     let arrowScrollbox = tabbrowser.tabContainer.arrowScrollbox;
     let smoothScroll = arrowScrollbox.smoothScroll;
     arrowScrollbox.smoothScroll = false;
 
     // We need to keep track of the initially open tabs so that they
     // can be moved to the end of the restored tabs.
-    let initialTabs = [];
+    let initialTabs;
     if (!overwriteTabs && firstWindow) {
       initialTabs = Array.slice(tabbrowser.tabs);
     }
 
     // Get rid of tabs that aren't needed anymore.
     if (overwriteTabs) {
       for (let i = tabbrowser.browsers.length - 1; i >= 0; i--) {
-        if (!tabbrowser.tabs[i].selected &&
-            !tabbrowser.tabs[i].hasAttribute("preopened")) {
+        if (!tabbrowser.tabs[i].selected) {
           tabbrowser.removeTab(tabbrowser.tabs[i]);
-        } else if (tabbrowser.tabs[i].hasAttribute("preopened")) {
-          initialTabs.push(tabbrowser.tabs[i]);
         }
       }
     }
 
     let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") && this._restore_on_demand;
 
     for (var t = 0; t < newTabCount; t++) {
       let tabData = winData.tabs[t];
@@ -3681,34 +3673,23 @@ var SessionStoreInternal = {
       let select = t == selectTab - 1;
       let tab;
 
       // Re-use existing selected tab if possible to avoid the overhead of
       // selecting a new tab.
       if (select &&
           tabbrowser.selectedTab.userContextId == userContextId) {
         tab = tabbrowser.selectedTab;
-        if (tab.pinned && !tabData.pinned) {
+        if (!tabData.pinned) {
           tabbrowser.unpinTab(tab);
-        } else if (!tab.pinned && tabData.pinned) {
-          tabbrowser.removeTab(tabbrowser.tabs[t]);
-          tabbrowser.pinTab(tab);
-          tabbrowser.moveTabTo(tab, t);
         }
-
         tabbrowser.moveTabToEnd();
         if (aWindow.gMultiProcessBrowser && !tab.linkedBrowser.isRemoteBrowser) {
           tabbrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
         }
-      } else if (tabData.pinned &&
-          tabbrowser.tabs[t] &&
-          tabbrowser.tabs[t].pinned &&
-          !tabbrowser.tabs[t].linkedPanel) {
-        tab = tabbrowser.tabs[t];
-        tabbrowser.activatePreopenedPinnedTab(tab);
       }
 
       // Add a new tab if needed.
       if (!tab) {
         let createLazyBrowser = restoreTabsLazily && !select && !tabData.pinned;
 
         let url = "about:blank";
         if (createLazyBrowser && tabData.entries && tabData.entries.length) {
@@ -3747,24 +3728,21 @@ var SessionStoreInternal = {
       }
 
       if (tabData.pinned) {
         tabbrowser.pinTab(tab);
       }
     }
 
     // Move the originally open tabs to the end.
-    let endPosition = tabbrowser.tabs.length - 1;
-    for (let tab of initialTabs) {
-      if (tab.hasAttribute("preopened") &&
-          !tab.linkedPanel) {
-        tabbrowser.removeTab(tab);
-      } else if (!tab.hasAttribute("preopened")) {
-        tabbrowser.unpinTab(tab);
-        tabbrowser.moveTabTo(tab, endPosition);
+    if (initialTabs) {
+      let endPosition = tabbrowser.tabs.length - 1;
+      for (let i = 0; i < initialTabs.length; i++) {
+        tabbrowser.unpinTab(initialTabs[i]);
+        tabbrowser.moveTabTo(initialTabs[i], endPosition);
       }
     }
 
     // We want to correlate the window with data from the last session, so
     // assign another id if we have one. Otherwise clear so we don't do
     // anything with it.
     delete aWindow.__SS_lastSessionWindowID;
     if (winData.__lastSessionWindowID)
@@ -4015,19 +3993,16 @@ var SessionStoreInternal = {
     if (selectedIndex > -1) {
       this.restoreTab(tabbrowser.selectedTab, aTabData[selectedIndex]);
     }
 
     // Restore all tabs.
     for (let t = 0; t < aTabs.length; t++) {
       if (t != selectedIndex) {
         this.restoreTab(aTabs[t], aTabData[t]);
-        if (this._restore_pinned_tabs_on_demand) {
-          aTabs[t].removeAttribute("preopened");
-        }
       }
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
     let browser = tab.linkedBrowser;
 
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -108,17 +108,16 @@ support-files = file_formdata_password.h
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_global_store.js]
 [browser_history_persist.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_old_favicon.js]
 [browser_page_title.js]
 [browser_pending_tabs.js]
-[browser_preopened_apptabs.js]
 [browser_privatetabs.js]
 [browser_purge_shistory.js]
 skip-if = e10s # Bug 1271024
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_restore_cookies_noOriginAttributes.js]
 [browser_scrollPositions.js]
 [browser_scrollPositionsReaderMode.js]
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_preopened_apptabs.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
-const PREF_NUM_PINNED_TABS = "browser.tabs.firstWindowRestore.numPinnedTabs";
-
-function muffleMainWindowType() {
-  let oldWinType = document.documentElement.getAttribute("windowtype");
-  // Check if we've already done this to allow calling multiple times:
-  if (oldWinType != "navigator:testrunner") {
-    // Make the main test window not count as a browser window any longer
-    document.documentElement.setAttribute("windowtype", "navigator:testrunner");
-
-    registerCleanupFunction(() => {
-      document.documentElement.setAttribute("windowtype", oldWinType);
-    });
-  }
-}
-
-registerCleanupFunction(function() {
-  Services.prefs.clearUserPref(PREF_NUM_PINNED_TABS);
-});
-
-add_task(async function testPrefSynced() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 3);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 3 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
-
-add_task(async function testPrefSyncedSelected() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 3);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 1 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
-
-add_task(async function testPrefTooHigh() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 5);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 3 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 5);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
-
-add_task(async function testPrefTooLow() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 1);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 3 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 1);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
-
-add_task(async function testPrefTooHighSelected() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 5);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 1 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 5);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
-
-add_task(async function testPrefTooLowSelected() {
-  Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, 1);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true },
-    { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true },
-  ], selected: 1 }] };
-
-  muffleMainWindowType();
-  let win = await BrowserTestUtils.openNewBrowserWindow();
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 1);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-  await setWindowState(win, state, false, true);
-  Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-  Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 4);
-  await BrowserTestUtils.closeWindow(win);
-});
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -185,19 +185,18 @@ function promiseWindowRestored(win) {
   return new Promise(resolve => win.addEventListener("SSWindowRestored", resolve, {once: true}));
 }
 
 async function setBrowserState(state, win = window) {
   ss.setBrowserState(typeof state != "string" ? JSON.stringify(state) : state);
   await promiseWindowRestored(win);
 }
 
-async function setWindowState(win, state, overwrite = false, firstWindow = false) {
-  ss.setWindowState(win, typeof state != "string" ? JSON.stringify(state) : state,
-                    overwrite, firstWindow);
+async function setWindowState(win, state, overwrite = false) {
+  ss.setWindowState(win, typeof state != "string" ? JSON.stringify(state) : state, overwrite);
   await promiseWindowRestored(win);
 }
 
 /**
  * Wait for a content -> chrome message.
  */
 function promiseContentMessage(browser, name) {
   let mm = browser.messageManager;
--- a/browser/modules/BrowserWindowTracker.jsm
+++ b/browser/modules/BrowserWindowTracker.jsm
@@ -91,21 +91,16 @@ function _trackWindowOrder(window) {
 }
 
 function _untrackWindowOrder(window) {
   let idx = _trackedWindows.indexOf(window);
   if (idx >= 0)
     _trackedWindows.splice(idx, 1);
 }
 
-function _trackPinnedTabs(window) {
-  Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
-                            window.gBrowser._numPinnedTabs);
-}
-
 // Methods that impact a window. Put into single object for organization.
 var WindowHelper = {
   addWindow(window) {
     // Add event listeners
     TAB_EVENTS.forEach(function(event) {
       window.gBrowser.tabContainer.addEventListener(event, _handleEvent);
     });
     WINDOW_EVENTS.forEach(function(event) {
@@ -138,17 +133,16 @@ var WindowHelper = {
 
   onActivate(window) {
     // If this window was the last focused window, we don't need to do anything
     if (window == _trackedWindows[0])
       return;
 
     _untrackWindowOrder(window);
     _trackWindowOrder(window);
-    _trackPinnedTabs(window);
 
     _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
   },
 };
 
 this.BrowserWindowTracker = {
   /**
    * Get the most recent browser window.
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 126
+Version 127
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-125...release-126
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-126...release-127
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.2
 - babel-preset-react @6.24.1
 - react @16.4.1
 - react-dom @16.4.1
 - webpack @3.12.0
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -1370,25 +1370,25 @@ html[dir="ltr"] .toggle-button.end:not(.
   min-height: 24px;
   width: 100%;
   background-color: var(--theme-toolbar-background);
 }
 
 .search-field .img.search {
   position: absolute;
   z-index: 1;
-  left: 6px;
+  inset-inline-start: 6px;
   top: calc(50% - 8px);
   mask-size: 12px;
   background-color: var(--theme-icon-dimmed-color);
   pointer-events: none;
 }
 
 .search-field.big .img.search {
-  left: 12px;
+  inset-inline-start: 12px;
   mask-size: 16px;
 }
 
 .search-field .img.loader {
   width: 24px;
   height: 24px;
   margin-inline-end: 6px;
   animation: search-loader-rotate 0.5s linear infinite;
@@ -1509,17 +1509,17 @@ html[dir="ltr"] .toggle-button.end:not(.
   margin: 0 2px 0 2px;
   border-radius: 2px;
 }
 
 .project-text-search .result .line-number {
   padding-left: 5px;
   min-width: 33px;
   width: 100%;
-  text-align: right;
+  text-align: end;
   grid-column: 1/2;
 }
 
 .project-text-search .result .line-value {
   grid-column: 2/3;
   padding-left: 5px;
   text-overflow: ellipsis;
   overflow-x: hidden;
@@ -2781,17 +2781,17 @@ menuseparator {
 }
 
 .theme-light {
   --theme-conditional-breakpoint-color: var(--theme-body-color);
 }
 
 /**
  * There's a known codemirror flex issue with chrome that this addresses.
- * BUG https://github.com/firefox-devtools/debugger.html/issues/63
+ * BUG https://github.com/firefox-devtools/debugger/issues/63
  */
 .editor-wrapper {
   position: absolute;
   height: calc(100% - var(--editor-header-height));
   width: calc(100% - 1px);
   top: var(--editor-header-height);
   left: 0px;
 }
@@ -3183,21 +3183,17 @@ html .breakpoints-list .breakpoint.pause
 .breakpoints-list .breakpoint:hover {
   background-color: var(--search-overlays-semitransparent);
 }
 
 .breakpoints-list .breakpoint .breakpoint-line {
   font-size: 11px;
   color: var(--theme-comment);
   min-width: 16px;
-  text-align: right;
-}
-
-html[dir="rtl"] .breakpoints-list .breakpoint .breakpoint-line {
-  text-align: left;
+  text-align: end;
 }
 
 .breakpoints-list .breakpoint:hover .breakpoint-line {
   color: transparent;
 }
 
 .breakpoints-list .breakpoint.paused:hover {
   border-color: var(--breakpoint-active-color-hover);
@@ -3235,19 +3231,17 @@ html[dir="rtl"] .breakpoints-list .break
 }
 
 .breakpoints-list .pause-indicator {
   flex: 0 1 content;
   order: 3;
 }
 
 .breakpoint .close-btn {
-  offset-inline-end: 15px;
   inset-inline-end: 15px;
-  offset-inline-start: auto;
   inset-inline-start: auto;
   position: absolute;
   top: 8px;
   display: none;
 }
 
 .breakpoint:hover .close-btn {
   display: flex;
@@ -3387,17 +3381,16 @@ html[dir="rtl"] .breakpoints-list .break
 }
 
 .tree .tree-node:not(.focused):hover {
   background-color: transparent;
 }
 
 .expression-container__close-btn {
   position: absolute;
-  offset-inline-end: 0px;
   inset-inline-end: 0px;
   top: 1px;
 }
 
 .expression-content {
   position: relative;
 }
 
@@ -3803,20 +3796,16 @@ html[dir="rtl"] .command-bar {
   padding-top: 8px;
   margin-inline-end: 4px;
 }
 
 .command-bar .replay-active {
   color: var(--theme-highlight-blue);
 }
 
-.command-bar .subSettings {
-  float: right;
-}
-
 .command-bar .active .disable-pausing {
   background-color: var(--theme-icon-checked-color);
 }
 
 .bottom {
   border-bottom: none;
   background-color: var(--theme-body-background);
   border-top: 1px solid var(--theme-splitter-color);
@@ -3948,18 +3937,18 @@ html[dir="rtl"] .command-bar {
   flex-grow: 1;
   display: flex;
   padding-inline-end: 36px;
   align-items: center;
   overflow-x: hidden;
 }
 
 .xhr-container__close-btn {
-  offset-inline-end: 12px;
-  offset-inline-start: auto;
+  inset-inline-end: 12px;
+  inset-inline-start: auto;
   position: absolute;
   top: 8px;
 }
 /* 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/>. */
 
 .event-listeners-content {
@@ -4114,17 +4103,17 @@ html[dir="rtl"] .object-node {
   --breakpoint-expression-height: 2.4em;
 }
 
 /*
   We apply overflow to the container with the commandbar.
   This allows the commandbar to remain fixed when scrolling
   until the content completely ends. Not just the height of
   the wrapper.
-  Ref: https://github.com/firefox-devtools/debugger.html/issues/3426
+  Ref: https://github.com/firefox-devtools/debugger/issues/3426
 */
 
 .secondary-panes-wrapper {
   height: 100%;
   width: 100%;
   display: flex;
   flex-direction: column;
 }
@@ -4179,19 +4168,17 @@ html[dir="rtl"] .object-node {
 
 .theme-dark .welcomebox {
   background-color: var(--theme-body-background);
 }
 
 .welcomebox .command-bar-button {
   position: absolute;
   top: auto;
-  offset-inline-end: 0;
   inset-inline-end: 0;
-  offset-inline-start: auto;
   inset-inline-start: auto;
   bottom: 0;
 }
 
 .alignlabel {
   display: flex;
   white-space: nowrap;
   font-size: 1.25em;
@@ -4211,26 +4198,32 @@ html[dir="rtl"] .object-node {
 
 .welcomebox__searchSources:hover,
 .welcomebox__searchProject:hover,
 .welcomebox__allShortcuts:hover {
   color: var(--theme-body-color);
 }
 
 .shortcutKey {
-  text-align: right;
+  text-align: end;
   padding-right: 10px;
   font-family: var(--monospace-font-family);
   font-size: 14px;
   line-height: 18px;
   color: var(--theme-body-color);
 }
 
+:root[platform="mac"] .welcomebox .shortcutKey,
+.launchpad-root[platform="mac"] .welcomebox .shortcutKey {
+  font-family: system-ui, -apple-system, sans-serif;
+  font-weight: 500;
+}
+
 .shortcutLabel {
-  text-align: left;
+  text-align: start;
   padding-left: 10px;
   font-size: 14px;
   line-height: 18px;
 }
 
 .shortcutFunction {
   margin: 0 auto;
   color: var(--theme-comment);
@@ -4372,17 +4365,17 @@ html .welcomebox .toggle-button-end.coll
 
 .dropdown {
   background: var(--theme-body-background);
   border: 1px solid var(--theme-splitter-color);
   border-radius: 4px;
   box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
   max-height: 300px;
   position: absolute;
-  offset-inline-end: 2px;
+  inset-inline-end: 2px;
   top: 24px;
   width: 150px;
   z-index: 1000;
   overflow: auto;
 }
 
 .dropdown-block {
   position: relative;
--- a/devtools/client/debugger/new/packages/devtools-reps/README.md
+++ b/devtools/client/debugger/new/packages/devtools-reps/README.md
@@ -40,17 +40,17 @@ Supported types:
 
 `Grip` is a client representation of a remote JS object and is used as an input object for this rep component.
 
 ## Getting started
 
 You need to clone the debugger.html repository, then install dependencies, for which you'll need the [Yarn](https://yarnpkg.com/en/) tool:
 
 ```
-git clone https://github.com/firefox-devtools/debugger.html.git
+git clone https://github.com/firefox-devtools/debugger.git
 cd debugger.html
 yarn install
 ```
 
 Once everything is installed, you can start the development server with:
 
 ```bash
 cd packages/devtools-reps/
--- a/devtools/client/debugger/new/packages/devtools-source-map/package.json
+++ b/devtools/client/debugger/new/packages/devtools-source-map/package.json
@@ -1,18 +1,18 @@
 {
   "name": "devtools-source-map",
   "version": "0.16.0",
   "description": "DevTools utilities for working with source maps",
   "license": "MPL-2.0",
   "author": "Jason Laster <jlaster@mozilla.com>",
-  "repository": "github:devtools-html/debugger.html",
-  "bugs": "https://github.com/firefox-devtools/debugger.html/issues",
+  "repository": "github:devtools-html/debugger",
+  "bugs": "https://github.com/firefox-devtools/debugger/issues",
   "homepage":
-    "https://github.com/firefox-devtools/debugger.html/tree/master/packages/source-maps#readme",
+    "https://github.com/firefox-devtools/debugger/tree/master/packages/source-maps#readme",
   "main": "src/index.js",
   "browser": {
     "./src/utils/wasmAsset.js": "./src/utils/wasmAssetBrowser.js"
   },
   "scripts": {
     "license-check": "devtools-license-check",
     "test": "jest --projects jest.config.js"
   },
--- a/devtools/client/debugger/new/packages/devtools-source-map/src/index.js
+++ b/devtools/client/debugger/new/packages/devtools-source-map/src/index.js
@@ -20,16 +20,20 @@ const _getGeneratedRanges = dispatcher.t
 
 const _getGeneratedLocation = dispatcher.task("getGeneratedLocation", {
   queue: true
 });
 const _getAllGeneratedLocations = dispatcher.task("getAllGeneratedLocations", {
   queue: true
 });
 
+const _getOriginalLocation = dispatcher.task("getOriginalLocation", {
+  queue: true
+});
+
 export const setAssetRootURL = async (assetRoot: string): Promise<void> =>
   dispatcher.invoke("setAssetRootURL", assetRoot);
 
 export const getOriginalURLs = async (
   generatedSource: Source
 ): Promise<SourceMapConsumer> =>
   dispatcher.invoke("getOriginalURLs", generatedSource);
 
@@ -66,18 +70,17 @@ export const getAllGeneratedLocations = 
   location: SourceLocation,
   originalSource: Source
 ): Promise<Array<SourceLocation>> =>
   _getAllGeneratedLocations(location, originalSource);
 
 export const getOriginalLocation = async (
   location: SourceLocation,
   options: locationOptions = {}
-): Promise<SourceLocation> =>
-  dispatcher.invoke("getOriginalLocation", location, options);
+): Promise<SourceLocation> => _getOriginalLocation(location, options);
 
 export const getFileGeneratedRange = async (
   originalSource: Source
 ): Promise<?{ start: any, end: any }> =>
   dispatcher.invoke("getFileGeneratedRange", originalSource);
 
 export const getLocationScopes = dispatcher.task("getLocationScopes");
 
--- a/devtools/client/debugger/new/src/actions/README.md
+++ b/devtools/client/debugger/new/src/actions/README.md
@@ -17,10 +17,10 @@ If you just want to cancel subsequent ca
 state in the store. [ex][state]
 
 The advantage of adding the pending state to the store is that we can use that
 in the UI:
 
 * disable/hide the pretty print button
 * show a progress ui
 
-[req]: https://github.com/firefox-devtools/debugger.html/blob/master/src/actions/sources/loadSourceText.js
-[state]: https://github.com/firefox-devtools/debugger.html/blob/master/src/reducers/sources.js
+[req]: https://github.com/firefox-devtools/debugger/blob/master/src/actions/sources/loadSourceText.js
+[state]: https://github.com/firefox-devtools/debugger/blob/master/src/reducers/sources.js
--- a/devtools/client/debugger/new/src/actions/file-search.js
+++ b/devtools/client/debugger/new/src/actions/file-search.js
@@ -168,19 +168,18 @@ export function traverseResults(rev: boo
     const { matches } = getFileSearchResults(getState());
 
     if (query === "") {
       dispatch(setActiveSearch("file"));
     }
 
     if (modifiers) {
       const matchedLocations = matches || [];
-      const results = rev
-        ? findPrev(ctx, query, true, modifiers.toJS())
-        : findNext(ctx, query, true, modifiers.toJS());
+      const findArgs = [ctx, query, true, modifiers.toJS()];
+      const results = rev ? findPrev(...findArgs) : findNext(...findArgs);
 
       if (!results) {
         return;
       }
       const { ch, line } = results;
       dispatch(updateSearchResults(ch, line, matchedLocations));
     }
   };
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -161,37 +161,42 @@ function removeXHRBreakpoint(path: strin
 
 // Get the string key to use for a breakpoint location.
 // See also duplicate code in breakpoint-actor-map.js :(
 function locationKey(location) {
   const { sourceUrl, sourceId, line, column } = location;
   return `${(sourceUrl: any)}:${(sourceId: any)}:${line}:${(column: any)}`;
 }
 
-function* getAllThreadClients() {
-  yield threadClient;
-  for (const { thread } of (Object.values(workerClients): any)) {
-    yield thread;
-  }
-}
-
 async function setBreakpoint(
   location: BreakpointLocation,
   options: BreakpointOptions
 ) {
   breakpoints[locationKey(location)] = { location, options };
-  for (const thread of getAllThreadClients()) {
-    await thread.setBreakpoint(location, options);
+  await threadClient.setBreakpoint(location, options);
+
+  // Set breakpoints in other threads as well, but do not wait for the requests
+  // to complete, so that we don't get hung up if one of the threads stops
+  // responding. We don't strictly need to wait for the main thread to finish
+  // setting its breakpoint, but this leads to more consistent behavior if the
+  // user sets a breakpoint and immediately starts interacting with the page.
+  // If the main thread stops responding then we're toast regardless.
+  for (const { thread } of (Object.values(workerClients): any)) {
+    thread.setBreakpoint(location, options);
   }
 }
 
 async function removeBreakpoint(location: BreakpointLocation) {
   delete breakpoints[locationKey(location)];
-  for (const thread of getAllThreadClients()) {
-    await thread.removeBreakpoint(location);
+  await threadClient.removeBreakpoint(location);
+
+  // Remove breakpoints without waiting for the thread to respond, for the same
+  // reason as in setBreakpoint.
+  for (const { thread } of (Object.values(workerClients): any)) {
+    thread.removeBreakpoint(location);
   }
 }
 
 async function evaluateInFrame(script: Script, options: EvaluateParam) {
   return evaluate(script, options);
 }
 
 async function evaluateExpressions(scripts: Script[], options: EvaluateParam) {
--- a/devtools/client/debugger/new/src/components/Editor/Editor.css
+++ b/devtools/client/debugger/new/src/components/Editor/Editor.css
@@ -35,17 +35,17 @@
 }
 
 .theme-light {
   --theme-conditional-breakpoint-color: var(--theme-body-color);
 }
 
 /**
  * There's a known codemirror flex issue with chrome that this addresses.
- * BUG https://github.com/firefox-devtools/debugger.html/issues/63
+ * BUG https://github.com/firefox-devtools/debugger/issues/63
  */
 .editor-wrapper {
   position: absolute;
   height: calc(100% - var(--editor-header-height));
   width: calc(100% - 1px);
   top: var(--editor-header-height);
   left: 0px;
 }
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/OutlineFilter.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/OutlineFilter.js
@@ -32,17 +32,17 @@ export default class OutlineFilter exten
   onKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
     if (e.key === "Escape" && this.props.filter !== "") {
       // use preventDefault to override toggling the split-console which is
       // also bound to the ESC key
       e.preventDefault();
       this.props.updateFilter("");
     } else if (e.key === "Enter") {
       // We must prevent the form submission from taking any action
-      // https://github.com/firefox-devtools/debugger.html/pull/7308
+      // https://github.com/firefox-devtools/debugger/pull/7308
       e.preventDefault();
     }
   };
 
   render() {
     const { focused } = this.state;
     return (
       <div className="outline-filter">
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js
@@ -159,16 +159,20 @@ class SourcesTree extends Component<Prop
       this.props.selectSource(item.contents.id);
     }
   };
 
   onFocus = (item: TreeNode) => {
     this.props.focusItem({ thread: this.props.thread, item });
   };
 
+  onActivate = (item: TreeNode) => {
+    this.selectItem(item);
+  };
+
   // NOTE: we get the source from sources because item.contents is cached
   getSource(item: TreeNode): ?Source {
     const source = getSourceFromNode(item);
     if (source) {
       return this.props.sources[source.id];
     }
 
     return null;
@@ -189,23 +193,16 @@ class SourcesTree extends Component<Prop
   onExpand = (item: Item, expandedState: Set<string>) => {
     this.props.setExpandedState(this.props.thread, expandedState);
   };
 
   onCollapse = (item: Item, expandedState: Set<string>) => {
     this.props.setExpandedState(this.props.thread, expandedState);
   };
 
-  onKeyDown = (e: KeyboardEvent) => {
-    const { focused } = this.props;
-    if (e.keyCode === 13 && focused) {
-      this.selectItem(focused);
-    }
-  };
-
   isEmpty() {
     const { sourceTree } = this.state;
     return sourceTree.contents.length === 0;
   }
 
   renderEmptyElement(message) {
     return (
       <div key="empty" className="no-sources-message">
@@ -275,16 +272,17 @@ class SourcesTree extends Component<Prop
       getRoots: this.getRoots,
       highlightItems,
       itemHeight: 21,
       key: this.isEmpty() ? "empty" : "full",
       listItems,
       onCollapse: this.onCollapse,
       onExpand: this.onExpand,
       onFocus: this.onFocus,
+      onActivate: this.onActivate,
       renderItem: this.renderItem,
       preventBlur: true
     };
 
     return <ManagedTree {...treeProps} />;
   }
 
   renderPane(...children) {
@@ -331,17 +329,17 @@ class SourcesTree extends Component<Prop
     const { worker } = this.props;
 
     if (!features.windowlessWorkers && worker) {
       return null;
     }
 
     return this.renderPane(
       this.renderThreadHeader(),
-      <div key="tree" className="sources-list" onKeyDown={this.onKeyDown}>
+      <div key="tree" className="sources-list">
         {this.renderTree()}
       </div>
     );
   }
 }
 
 function getSourceForTree(
   state: AppState,
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTreeItem.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTreeItem.js
@@ -210,17 +210,17 @@ class SourceTreeItem extends Component<P
     const { item } = this.props;
 
     switch (item.name) {
       case "ng://":
         return "Angular";
       case "webpack://":
         return "Webpack";
       default:
-        return `${item.name}`;
+        return `${unescape(item.name)}`;
     }
   }
 
   render() {
     const {
       item,
       depth,
       source,
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTree.spec.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTree.spec.js
@@ -172,36 +172,27 @@ describe("SourcesTree", () => {
           ...props,
           selectedSource: mockSource
         });
         expect(component).toMatchSnapshot();
       });
     });
   });
 
-  describe("focusItem", () => {
-    it("update the focused item", async () => {
+  describe("activateItem", () => {
+    it("select activated item", async () => {
+      const { instance, props } = render();
       const item = createMockItem();
-      const { component, props } = render({ focused: item });
-
-      await component
-        .find(".sources-list")
-        .simulate("keydown", { keyCode: 13 });
+      const spy = jest.spyOn(instance, "selectItem");
 
-      expect(props.selectSource).toHaveBeenCalledWith(item.contents.id);
-    });
-
-    it("allows focus on the (index)", async () => {
-      const item = createMockItem("https://davidwalsh.name/", "(index)");
-
-      const { component, props } = render({ focused: item });
-      await component
-        .find(".sources-list")
-        .simulate("keydown", { keyCode: 13 });
-      expect(props.selectSource).toHaveBeenCalledWith(item.contents.id);
+      instance.onActivate(item);
+      expect(spy).toHaveBeenCalledWith(item);
+      expect(props.selectSource).toHaveBeenCalledWith(
+        "server1.conn13.child1/39"
+      );
     });
   });
 
   describe("selectItem", () => {
     it("should select item with no children", async () => {
       const { instance, props } = render();
       instance.selectItem(createMockItem());
       expect(props.selectSource).toHaveBeenCalledWith(
@@ -209,24 +200,16 @@ describe("SourcesTree", () => {
       );
     });
 
     it("should not select item with children", async () => {
       const { props, instance } = render();
       instance.selectItem(createMockDirectory());
       expect(props.selectSource).not.toHaveBeenCalled();
     });
-
-    it("does not select if no item is focused on", async () => {
-      const { component, props } = render();
-      await component
-        .find(".sources-list")
-        .simulate("keydown", { keyCode: 13 });
-      expect(props.selectSource).not.toHaveBeenCalled();
-    });
   });
 
   describe("handles items", () => {
     it("getChildren from directory", async () => {
       const { component } = render();
       const item = createMockDirectory("http://mdn.com/views", "views", [
         "a",
         "b"
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTreeItem.spec.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTreeItem.spec.js
@@ -332,16 +332,27 @@ describe("SourceTreeItem", () => {
         focused: true,
         expanded: false,
         selectItem
       });
 
       component.simulate("click", { event: "click" });
       expect(props.selectItem).not.toHaveBeenCalled();
     });
+
+    it("should unescape escaped source URLs", async () => {
+      const item = createMockItem({
+        path: "mdn.com/external%20file",
+        name: "external%20file"
+      });
+
+      const node = render({ item });
+
+      expect(node).toMatchSnapshot();
+    });
   });
 });
 
 function generateDefaults(overrides) {
   const source = makeMockSource(
     "http://mdn.com/one.js",
     "server1.conn13.child1/39"
   );
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
@@ -15,17 +15,16 @@ exports[`SourcesTree After changing expa
       className="label"
     >
       Main Thread
     </span>
   </div>
   <div
     className="sources-list"
     key="tree"
-    onKeyDown={[Function]}
   >
     <ManagedTree
       autoExpandAll={false}
       autoExpandDepth={0}
       expanded={
         Array [
           "four.js",
           "five.js",
@@ -33,16 +32,17 @@ exports[`SourcesTree After changing expa
         ]
       }
       getChildren={[Function]}
       getParent={[Function]}
       getPath={[Function]}
       getRoots={[Function]}
       itemHeight={21}
       key="full"
+      onActivate={[Function]}
       onCollapse={[Function]}
       onExpand={[Function]}
       onFocus={[Function]}
       preventBlur={true}
       renderItem={[Function]}
     />
   </div>
 </div>
@@ -63,27 +63,27 @@ exports[`SourcesTree Should show the tre
       className="label"
     >
       Main Thread
     </span>
   </div>
   <div
     className="sources-list"
     key="tree"
-    onKeyDown={[Function]}
   >
     <ManagedTree
       autoExpandAll={false}
       autoExpandDepth={1}
       getChildren={[Function]}
       getParent={[Function]}
       getPath={[Function]}
       getRoots={[Function]}
       itemHeight={21}
       key="full"
+      onActivate={[Function]}
       onCollapse={[Function]}
       onExpand={[Function]}
       onFocus={[Function]}
       preventBlur={true}
       renderItem={[Function]}
     />
   </div>
 </div>
@@ -104,17 +104,16 @@ exports[`SourcesTree When loading initia
       className="label"
     >
       Main Thread
     </span>
   </div>
   <div
     className="sources-list"
     key="tree"
-    onKeyDown={[Function]}
   >
     <ManagedTree
       autoExpandAll={false}
       autoExpandDepth={0}
       expanded={
         Array [
           "one.js",
           "two.js",
@@ -122,16 +121,17 @@ exports[`SourcesTree When loading initia
         ]
       }
       getChildren={[Function]}
       getParent={[Function]}
       getPath={[Function]}
       getRoots={[Function]}
       itemHeight={21}
       key="full"
+      onActivate={[Function]}
       onCollapse={[Function]}
       onExpand={[Function]}
       onFocus={[Function]}
       preventBlur={true}
       renderItem={[Function]}
     />
   </div>
 </div>
@@ -152,17 +152,16 @@ exports[`SourcesTree on receiving new pr
       className="label"
     >
       Main Thread
     </span>
   </div>
   <div
     className="sources-list"
     key="tree"
-    onKeyDown={[Function]}
   >
     <ManagedTree
       autoExpandAll={false}
       autoExpandDepth={1}
       getChildren={[Function]}
       getParent={[Function]}
       getPath={[Function]}
       getRoots={[Function]}
@@ -294,16 +293,17 @@ exports[`SourcesTree on receiving new pr
             "name": "mdn.com",
             "path": "mdn.com",
             "type": "directory",
           },
         ]
       }
       itemHeight={21}
       key="full"
+      onActivate={[Function]}
       onCollapse={[Function]}
       onExpand={[Function]}
       onFocus={[Function]}
       preventBlur={true}
       renderItem={[Function]}
     />
   </div>
 </div>
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTreeItem.spec.js.snap
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTreeItem.spec.js.snap
@@ -2319,8 +2319,238 @@ Object {
       "relativeUrl": "http://mdn.com/one.js",
       "text": "",
       "url": "http://mdn.com/one.js",
     },
     "toggleBlackBox": [MockFunction],
   },
 }
 `;
+
+exports[`SourceTreeItem renderItem should unescape escaped source URLs 1`] = `
+Object {
+  "component": <div
+    className="node"
+    key="mdn.com/external%20file"
+    onClick={[Function]}
+    onContextMenu={[Function]}
+  >
+    <span
+      className="img no-arrow"
+    />
+    <Connect(SourceIcon)
+      source={
+        Object {
+          "actors": Array [],
+          "contentType": "text/javascript",
+          "id": "server1.conn13.child1/39",
+          "introductionUrl": null,
+          "isBlackBoxed": false,
+          "isExtension": false,
+          "isPrettyPrinted": false,
+          "isWasm": false,
+          "loadedState": "unloaded",
+          "relativeUrl": "http://mdn.com/one.js",
+          "text": "",
+          "url": "http://mdn.com/one.js",
+        }
+      }
+    />
+    <span
+      className="label"
+    >
+       
+      external file
+       
+    </span>
+  </div>,
+  "defaultState": null,
+  "instance": SourceTreeItem {
+    "addCollapseExpandAllOptions": [Function],
+    "context": Object {},
+    "onClick": [Function],
+    "onContextMenu": [Function],
+    "props": Object {
+      "clearProjectDirectoryRoot": [MockFunction],
+      "debuggeeUrl": "http://mdn.com",
+      "expanded": false,
+      "focusItem": [MockFunction],
+      "item": Object {
+        "contents": Object {
+          "actors": Array [],
+          "contentType": "text/javascript",
+          "id": "server1.conn13.child1/39",
+          "introductionUrl": null,
+          "isBlackBoxed": false,
+          "isExtension": false,
+          "isPrettyPrinted": false,
+          "isWasm": false,
+          "loadedState": "unloaded",
+          "relativeUrl": "url",
+          "text": "",
+          "url": "url",
+        },
+        "name": "external%20file",
+        "path": "mdn.com/external%20file",
+        "type": "source",
+      },
+      "projectRoot": "",
+      "selectItem": [MockFunction],
+      "setExpanded": [MockFunction],
+      "setProjectDirectoryRoot": [MockFunction],
+      "source": Object {
+        "actors": Array [],
+        "contentType": "text/javascript",
+        "id": "server1.conn13.child1/39",
+        "introductionUrl": null,
+        "isBlackBoxed": false,
+        "isExtension": false,
+        "isPrettyPrinted": false,
+        "isWasm": false,
+        "loadedState": "unloaded",
+        "relativeUrl": "http://mdn.com/one.js",
+        "text": "",
+        "url": "http://mdn.com/one.js",
+      },
+      "toggleBlackBox": [MockFunction],
+    },
+    "refs": Object {},
+    "state": null,
+    "updater": Updater {
+      "_callbacks": Array [],
+      "_renderer": ReactShallowRenderer {
+        "_context": Object {},
+        "_element": <SourceTreeItem
+          clearProjectDirectoryRoot={[MockFunction]}
+          debuggeeUrl="http://mdn.com"
+          expanded={false}
+          focusItem={[MockFunction]}
+          item={
+            Object {
+              "contents": Object {
+                "actors": Array [],
+                "contentType": "text/javascript",
+                "id": "server1.conn13.child1/39",
+                "introductionUrl": null,
+                "isBlackBoxed": false,
+                "isExtension": false,
+                "isPrettyPrinted": false,
+                "isWasm": false,
+                "loadedState": "unloaded",
+                "relativeUrl": "url",
+                "text": "",
+                "url": "url",
+              },
+              "name": "external%20file",
+              "path": "mdn.com/external%20file",
+              "type": "source",
+            }
+          }
+          projectRoot=""
+          selectItem={[MockFunction]}
+          setExpanded={[MockFunction]}
+          setProjectDirectoryRoot={[MockFunction]}
+          source={
+            Object {
+              "actors": Array [],
+              "contentType": "text/javascript",
+              "id": "server1.conn13.child1/39",
+              "introductionUrl": null,
+              "isBlackBoxed": false,
+              "isExtension": false,
+              "isPrettyPrinted": false,
+              "isWasm": false,
+              "loadedState": "unloaded",
+              "relativeUrl": "http://mdn.com/one.js",
+              "text": "",
+              "url": "http://mdn.com/one.js",
+            }
+          }
+          toggleBlackBox={[MockFunction]}
+        />,
+        "_forcedUpdate": false,
+        "_instance": [Circular],
+        "_newState": null,
+        "_rendered": <div
+          className="node"
+          onClick={[Function]}
+          onContextMenu={[Function]}
+        >
+          <span
+            className="img no-arrow"
+          />
+          <Connect(SourceIcon)
+            source={
+              Object {
+                "actors": Array [],
+                "contentType": "text/javascript",
+                "id": "server1.conn13.child1/39",
+                "introductionUrl": null,
+                "isBlackBoxed": false,
+                "isExtension": false,
+                "isPrettyPrinted": false,
+                "isWasm": false,
+                "loadedState": "unloaded",
+                "relativeUrl": "http://mdn.com/one.js",
+                "text": "",
+                "url": "http://mdn.com/one.js",
+              }
+            }
+          />
+          <span
+            className="label"
+          >
+             
+            external file
+             
+          </span>
+        </div>,
+        "_rendering": false,
+        "_updater": [Circular],
+      },
+    },
+  },
+  "props": Object {
+    "clearProjectDirectoryRoot": [MockFunction],
+    "debuggeeUrl": "http://mdn.com",
+    "expanded": false,
+    "focusItem": [MockFunction],
+    "item": Object {
+      "contents": Object {
+        "actors": Array [],
+        "contentType": "text/javascript",
+        "id": "server1.conn13.child1/39",
+        "introductionUrl": null,
+        "isBlackBoxed": false,
+        "isExtension": false,
+        "isPrettyPrinted": false,
+        "isWasm": false,
+        "loadedState": "unloaded",
+        "relativeUrl": "url",
+        "text": "",
+        "url": "url",
+      },
+      "name": "external%20file",
+      "path": "mdn.com/external%20file",
+      "type": "source",
+    },
+    "projectRoot": "",
+    "selectItem": [MockFunction],
+    "setExpanded": [MockFunction],
+    "setProjectDirectoryRoot": [MockFunction],
+    "source": Object {
+      "actors": Array [],
+      "contentType": "text/javascript",
+      "id": "server1.conn13.child1/39",
+      "introductionUrl": null,
+      "isBlackBoxed": false,
+      "isExtension": false,
+      "isPrettyPrinted": false,
+      "isWasm": false,
+      "loadedState": "unloaded",
+      "relativeUrl": "http://mdn.com/one.js",
+      "text": "",
+      "url": "http://mdn.com/one.js",
+    },
+    "toggleBlackBox": [MockFunction],
+  },
+}
+`;
--- a/devtools/client/debugger/new/src/components/ProjectSearch.css
+++ b/devtools/client/debugger/new/src/components/ProjectSearch.css
@@ -47,17 +47,17 @@
   margin: 0 2px 0 2px;
   border-radius: 2px;
 }
 
 .project-text-search .result .line-number {
   padding-left: 5px;
   min-width: 33px;
   width: 100%;
-  text-align: right;
+  text-align: end;
   grid-column: 1/2;
 }
 
 .project-text-search .result .line-value {
   grid-column: 2/3;
   padding-left: 5px;
   text-overflow: ellipsis;
   overflow-x: hidden;
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
@@ -126,21 +126,17 @@ html .breakpoints-list .breakpoint.pause
 .breakpoints-list .breakpoint:hover {
   background-color: var(--search-overlays-semitransparent);
 }
 
 .breakpoints-list .breakpoint .breakpoint-line {
   font-size: 11px;
   color: var(--theme-comment);
   min-width: 16px;
-  text-align: right;
-}
-
-html[dir="rtl"] .breakpoints-list .breakpoint .breakpoint-line {
-  text-align: left;
+  text-align: end;
 }
 
 .breakpoints-list .breakpoint:hover .breakpoint-line {
   color: transparent;
 }
 
 .breakpoints-list .breakpoint.paused:hover {
   border-color: var(--breakpoint-active-color-hover);
@@ -178,19 +174,17 @@ html[dir="rtl"] .breakpoints-list .break
 }
 
 .breakpoints-list .pause-indicator {
   flex: 0 1 content;
   order: 3;
 }
 
 .breakpoint .close-btn {
-  offset-inline-end: 15px;
   inset-inline-end: 15px;
-  offset-inline-start: auto;
   inset-inline-start: auto;
   position: absolute;
   top: 8px;
   display: none;
 }
 
 .breakpoint:hover .close-btn {
   display: flex;
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.css
@@ -32,20 +32,16 @@ html[dir="rtl"] .command-bar {
   padding-top: 8px;
   margin-inline-end: 4px;
 }
 
 .command-bar .replay-active {
   color: var(--theme-highlight-blue);
 }
 
-.command-bar .subSettings {
-  float: right;
-}
-
 .command-bar .active .disable-pausing {
   background-color: var(--theme-icon-checked-color);
 }
 
 .bottom {
   border-bottom: none;
   background-color: var(--theme-body-background);
   border-top: 1px solid var(--theme-splitter-color);
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.css
@@ -89,17 +89,16 @@
 }
 
 .tree .tree-node:not(.focused):hover {
   background-color: transparent;
 }
 
 .expression-container__close-btn {
   position: absolute;
-  offset-inline-end: 0px;
   inset-inline-end: 0px;
   top: 1px;
 }
 
 .expression-content {
   position: relative;
 }
 
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/SecondaryPanes.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/SecondaryPanes.css
@@ -12,17 +12,17 @@
   --breakpoint-expression-height: 2.4em;
 }
 
 /*
   We apply overflow to the container with the commandbar.
   This allows the commandbar to remain fixed when scrolling
   until the content completely ends. Not just the height of
   the wrapper.
-  Ref: https://github.com/firefox-devtools/debugger.html/issues/3426
+  Ref: https://github.com/firefox-devtools/debugger/issues/3426
 */
 
 .secondary-panes-wrapper {
   height: 100%;
   width: 100%;
   display: flex;
   flex-direction: column;
 }
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/XHRBreakpoints.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/XHRBreakpoints.css
@@ -104,13 +104,13 @@
   flex-grow: 1;
   display: flex;
   padding-inline-end: 36px;
   align-items: center;
   overflow-x: hidden;
 }
 
 .xhr-container__close-btn {
-  offset-inline-end: 12px;
-  offset-inline-start: auto;
+  inset-inline-end: 12px;
+  inset-inline-start: auto;
   position: absolute;
   top: 8px;
 }
--- a/devtools/client/debugger/new/src/components/WelcomeBox.css
+++ b/devtools/client/debugger/new/src/components/WelcomeBox.css
@@ -18,19 +18,17 @@
 
 .theme-dark .welcomebox {
   background-color: var(--theme-body-background);
 }
 
 .welcomebox .command-bar-button {
   position: absolute;
   top: auto;
-  offset-inline-end: 0;
   inset-inline-end: 0;
-  offset-inline-start: auto;
   inset-inline-start: auto;
   bottom: 0;
 }
 
 .alignlabel {
   display: flex;
   white-space: nowrap;
   font-size: 1.25em;
@@ -50,26 +48,32 @@
 
 .welcomebox__searchSources:hover,
 .welcomebox__searchProject:hover,
 .welcomebox__allShortcuts:hover {
   color: var(--theme-body-color);
 }
 
 .shortcutKey {
-  text-align: right;
+  text-align: end;
   padding-right: 10px;
   font-family: var(--monospace-font-family);
   font-size: 14px;
   line-height: 18px;
   color: var(--theme-body-color);
 }
 
+:root[platform="mac"] .welcomebox .shortcutKey,
+.launchpad-root[platform="mac"] .welcomebox .shortcutKey {
+  font-family: system-ui, -apple-system, sans-serif;
+  font-weight: 500;
+}
+
 .shortcutLabel {
-  text-align: left;
+  text-align: start;
   padding-left: 10px;
   font-size: 14px;
   line-height: 18px;
 }
 
 .shortcutFunction {
   margin: 0 auto;
   color: var(--theme-comment);
--- a/devtools/client/debugger/new/src/components/shared/Dropdown.css
+++ b/devtools/client/debugger/new/src/components/shared/Dropdown.css
@@ -4,17 +4,17 @@
 
 .dropdown {
   background: var(--theme-body-background);
   border: 1px solid var(--theme-splitter-color);
   border-radius: 4px;
   box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
   max-height: 300px;
   position: absolute;
-  offset-inline-end: 2px;
+  inset-inline-end: 2px;
   top: 24px;
   width: 150px;
   z-index: 1000;
   overflow: auto;
 }
 
 .dropdown-block {
   position: relative;
--- a/devtools/client/debugger/new/src/components/shared/SearchInput.css
+++ b/devtools/client/debugger/new/src/components/shared/SearchInput.css
@@ -29,25 +29,25 @@
   min-height: 24px;
   width: 100%;
   background-color: var(--theme-toolbar-background);
 }
 
 .search-field .img.search {
   position: absolute;
   z-index: 1;
-  left: 6px;
+  inset-inline-start: 6px;
   top: calc(50% - 8px);
   mask-size: 12px;
   background-color: var(--theme-icon-dimmed-color);
   pointer-events: none;
 }
 
 .search-field.big .img.search {
-  left: 12px;
+  inset-inline-start: 12px;
   mask-size: 16px;
 }
 
 .search-field .img.loader {
   width: 24px;
   height: 24px;
   margin-inline-end: 6px;
   animation: search-loader-rotate 0.5s linear infinite;
--- a/devtools/client/debugger/new/src/reducers/ast.js
+++ b/devtools/client/debugger/new/src/reducers/ast.js
@@ -127,17 +127,17 @@ function update(
 
     default: {
       return state;
     }
   }
 }
 
 // NOTE: we'd like to have the app state fully typed
-// https://github.com/firefox-devtools/debugger.html/blob/master/src/reducers/sources.js#L179-L185
+// https://github.com/firefox-devtools/debugger/blob/master/src/reducers/sources.js#L179-L185
 type OuterState = { ast: Record<ASTState> };
 
 export function getSymbols(state: OuterState, source: ?Source): ?Symbols {
   if (!source) {
     return null;
   }
 
   return state.ast.symbols.get(source.id) || null;
--- a/devtools/client/debugger/new/src/reducers/file-search.js
+++ b/devtools/client/debugger/new/src/reducers/file-search.js
@@ -93,17 +93,17 @@ function update(
 
     default: {
       return state;
     }
   }
 }
 
 // NOTE: we'd like to have the app state fully typed
-// https://github.com/firefox-devtools/debugger.html/blob/master/src/reducers/sources.js#L179-L185
+// https://github.com/firefox-devtools/debugger/blob/master/src/reducers/sources.js#L179-L185
 type OuterState = { fileSearch: Record<FileSearchState> };
 
 export function getFileSearchQuery(state: OuterState): string {
   return state.fileSearch.query;
 }
 
 export function getFileSearchModifiers(state: OuterState): Modifiers {
   return state.fileSearch.modifiers;
--- a/devtools/client/debugger/new/src/reducers/sources.js
+++ b/devtools/client/debugger/new/src/reducers/sources.js
@@ -181,36 +181,33 @@ function update(
 
 /*
  * Update a source when its state changes
  * e.g. the text was loaded, it was blackboxed
  */
 function updateSource(state: SourcesState, source: Object) {
   const existingSource = state.sources[source.id];
   return {
-    ...state, 
+    ...state,
     sources: {
-      ...state.sources, 
+      ...state.sources,
       [source.id]: { ...existingSource, ...source }
     }
   };
 }
 
 /*
  * Update all of the sources when an event occurs.
  * e.g. workers are updated, project directory root changes
  */
-function updateAllSources(
-  state: SourcesState,
-  callback: any
-) {
+function updateAllSources(state: SourcesState, callback: any) {
   const updatedSources = Object.values(state.sources).map(source => ({
     ...source,
     ...callback(source)
-  }))
+  }));
 
   return addSources({ ...state, ...emptySources }, updatedSources);
 }
 
 /*
  * Add sources to the sources store
  * - Add the source to the sources store
  * - Add the source URL to the urls map
@@ -229,36 +226,38 @@ function addSources(state: SourcesState,
     let updatedSource = existingSource || source;
 
     // Merge the source actor list
     if (existingSource && source.actors) {
       const actors = uniqBy(
         [...existingSource.actors, ...source.actors],
         ({ actor }) => actor
       );
+
       updatedSource = (({ ...updatedSource, actors }: any): Source);
     }
 
     // 1. Add the source to the sources map
     state.sources[source.id] = updatedSource;
 
     // 2. Update the source url map
     const existing = state.urls[source.url] || [];
     if (!existing.includes(source.id)) {
       state.urls[source.url] = [...existing, source.id];
     }
 
     // 3. Update the displayed actor map
     if (
       underRoot(source, state.projectDirectoryRoot) &&
-      (!source.isExtension || getChromeAndExtenstionsEnabled({ sources: state }))
+      (!source.isExtension ||
+        getChromeAndExtenstionsEnabled({ sources: state }))
     ) {
       for (const actor of getSourceActors(state, source)) {
         if (!state.displayed[actor.thread]) {
-          state.displayed[actor.thread] = {}
+          state.displayed[actor.thread] = {};
         }
         state.displayed[actor.thread][source.id] = true;
       }
     }
   }
 
   return state;
 }
@@ -280,38 +279,34 @@ function updateWorkers(state: SourcesSta
 }
 
 /*
  * Update sources when the project directory root changes
  */
 function updateProjectDirectoryRoot(state: SourcesState, root: string) {
   prefs.projectDirectoryRoot = root;
 
-  return updateAllSources(
-    { ...state, projectDirectoryRoot: root },
-    source => ({ relativeUrl: getRelativeUrl(source, root) })
-  );
+  return updateAllSources({ ...state, projectDirectoryRoot: root }, source => ({
+    relativeUrl: getRelativeUrl(source, root)
+  }));
 }
 
 /*
  * Update a source's loaded state fields
  * i.e. loadedState, text, error
  */
 function updateLoadedState(state, action: LoadSourceAction): SourcesState {
   const { sourceId } = action;
   let source;
 
   if (action.status === "start") {
     source = { id: sourceId, loadedState: "loading" };
-
   } else if (action.status === "error") {
     source = { id: sourceId, error: action.error, loadedState: "loaded" };
-
   } else {
-
     if (!action.value) {
       return state;
     }
 
     source = {
       id: sourceId,
       text: action.value.text,
       contentType: action.value.contentType,
@@ -373,17 +368,20 @@ export function getSource(state: OuterSt
 export function getSourceFromId(state: OuterState, id: string): Source {
   const source = getSource(state, id);
   if (!source) {
     throw new Error(`source ${id} does not exist`);
   }
   return source;
 }
 
-export function getSourceByActorId(state: OuterState, actorId: string): ?Source {
+export function getSourceByActorId(
+  state: OuterState,
+  actorId: string
+): ?Source {
   // We don't index the sources by actor IDs, so this method should be used
   // sparingly.
   for (const source of getSourceList(state)) {
     if (source.actors.some(({ actor }) => actor == actorId)) {
       return source;
     }
   }
   return null;
--- a/devtools/client/debugger/new/src/reducers/ui.js
+++ b/devtools/client/debugger/new/src/reducers/ui.js
@@ -131,17 +131,17 @@ function update(
 
     default: {
       return state;
     }
   }
 }
 
 // NOTE: we'd like to have the app state fully typed
-// https://github.com/firefox-devtools/debugger.html/blob/master/src/reducers/sources.js#L179-L185
+// https://github.com/firefox-devtools/debugger/blob/master/src/reducers/sources.js#L179-L185
 type OuterState = { ui: Record<UIState> };
 
 export function getSelectedPrimaryPaneTab(
   state: OuterState
 ): SelectedPrimaryPaneTabType {
   return state.ui.get("selectedPrimaryPaneTab");
 }
 
--- a/devtools/client/debugger/new/src/utils/dbg.js
+++ b/devtools/client/debugger/new/src/utils/dbg.js
@@ -79,15 +79,15 @@ export function setupHelper(obj: Object)
       events: {}
     }
   };
 
   window.dbg = dbg;
 
   if (isDevelopment() && !isTesting()) {
     console.group("Development Notes");
-    const baseUrl = "https://firefox-devtools.github.io/debugger.html";
+    const baseUrl = "https://firefox-devtools.github.io/debugger";
     const localDevelopmentUrl = `${baseUrl}/docs/dbg.html`;
     console.log("Debugging Tips", localDevelopmentUrl);
     console.log("dbg", window.dbg);
     console.groupEnd();
   }
 }
--- a/devtools/client/debugger/new/src/utils/pause/frames/getLibraryFromUrl.js
+++ b/devtools/client/debugger/new/src/utils/pause/frames/getLibraryFromUrl.js
@@ -73,17 +73,17 @@ const libraryMap = [
     pattern: /vue(?:\.[a-z]+)*\.js/i
   },
   {
     label: "RxJS",
     pattern: /rxjs/i
   },
   {
     label: "Angular",
-    pattern: /angular/i,
+    pattern: /angular(?!.*\/app\/)/i,
     contextPattern: /(zone\.js)/
   },
   {
     label: "Redux",
     pattern: /redux/i
   },
   {
     label: "Dojo",
--- a/devtools/client/debugger/new/src/utils/pause/frames/tests/annotateFrames.spec.js
+++ b/devtools/client/debugger/new/src/utils/pause/frames/tests/annotateFrames.spec.js
@@ -6,17 +6,17 @@
 
 import { annotateFrames } from "../annotateFrames";
 import { makeMockFrameWithURL } from "../../../test-mockup";
 
 describe("annotateFrames", () => {
   it("should return Angular", () => {
     const callstack = [
       makeMockFrameWithURL(
-        "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
+        "https://stackblitz.io/turbo_modules/@angular/core@7.2.4/bundles/core.umd.js"
       ),
       makeMockFrameWithURL("/node_modules/zone/zone.js"),
       makeMockFrameWithURL(
         "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
       )
     ];
     const frames = annotateFrames(callstack);
     expect(frames).toEqual(callstack.map(f => ({ ...f, library: "Angular" })));
--- a/devtools/client/debugger/new/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js
+++ b/devtools/client/debugger/new/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js
@@ -64,30 +64,53 @@ describe("getLibraryFromUrl", () => {
       ];
       reactUrlList.forEach(reactUrl => {
         const frame = makeMockFrameWithURL(reactUrl);
         expect(getLibraryFromUrl(frame)).toEqual("React");
       });
     });
   });
 
+  describe("When Angular is in the URL", () => {
+    it("should return Angular for AngularJS (1.x)", () => {
+      const frame = makeMockFrameWithURL(
+        "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
+      );
+      expect(getLibraryFromUrl(frame)).toEqual("Angular");
+    });
+
+    it("should return Angular for Angular (2.x)", () => {
+      const frame = makeMockFrameWithURL(
+        "https://stackblitz.io/turbo_modules/@angular/core@7.2.4/bundles/core.umd.js"
+      );
+      expect(getLibraryFromUrl(frame)).toEqual("Angular");
+    });
+
+    it("should not return Angular for Angular components", () => {
+      const frame = makeMockFrameWithURL(
+        "https://firefox-devtools-angular-log.stackblitz.io/~/src/app/hello.component.ts"
+      );
+      expect(getLibraryFromUrl(frame)).toBeNull();
+    });
+  });
+
   describe("When zone.js is on the frame", () => {
     it("should not return Angular when no callstack", () => {
       const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
       expect(getLibraryFromUrl(frame)).toEqual(null);
     });
 
     it("should not return Angular when stack without Angular frames", () => {
       const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
       const callstack = [frame];
 
       expect(getLibraryFromUrl(frame, callstack)).toEqual(null);
     });
 
-    it("should return Angular when stack with Angular frames", () => {
+    it("should return Angular when stack with AngularJS (1.x) frames", () => {
       const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
       const callstack = [
         frame,
         makeMockFrameWithURL(
           "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
         )
       ];
 
--- a/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
@@ -14,17 +14,17 @@ export type ParsedURL = {
   filename: string
 };
 
 export function getFilenameFromPath(pathname?: string) {
   let filename = "";
   if (pathname) {
     filename = pathname.substring(pathname.lastIndexOf("/") + 1);
     // This file does not have a name. Default should be (index).
-    if (filename == "" || !filename.includes(".")) {
+    if (filename == "") {
       filename = "(index)";
     }
   }
   return filename;
 }
 
 const NoDomain = "(no domain)";
 const def = { path: "", group: "", filename: "" };
--- a/devtools/client/debugger/new/src/utils/sources-tree/tests/getUrl.spec.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/tests/getUrl.spec.js
@@ -54,23 +54,33 @@ describe("getUrl", () => {
     const urlObject = getURL(
       createMockSource({
         url: "https://a/b.js#specialSection"
       })
     );
     expect(urlObject.filename).toBe("b.js");
   });
 
-  it("handles url with no filename for filename", function() {
+  it("handles url with no file extension for filename", function() {
     const urlObject = getURL(
       createMockSource({
         url: "https://a/c",
         id: "c"
       })
     );
+    expect(urlObject.filename).toBe("c");
+  });
+
+  it("handles url with no name for filename", function() {
+    const urlObject = getURL(
+      createMockSource({
+        url: "https://a/",
+        id: "c"
+      })
+    );
     expect(urlObject.filename).toBe("(index)");
   });
 
   it("separates resources by protocol and host", () => {
     const urlObject = getURL(
       createMockSource({
         url: "moz-extension://xyz/123",
         id: "c2"
--- a/devtools/client/debugger/new/src/utils/text.js
+++ b/devtools/client/debugger/new/src/utils/text.js
@@ -24,20 +24,20 @@ const isMacOS = appinfo.OS === "Darwin";
  * For Win/Lin this replaces CommandOrControl or CmdOrCtrl with Ctrl
  *
  * @memberof utils/text
  * @static
  */
 export function formatKeyShortcut(shortcut: string): string {
   if (isMacOS) {
     return shortcut
-      .replace(/Shift\+/g, "\u21E7 ")
-      .replace(/Command\+|Cmd\+/g, "\u2318 ")
-      .replace(/CommandOrControl\+|CmdOrCtrl\+/g, "\u2318 ")
-      .replace(/Alt\+/g, "\u2325 ");
+      .replace(/Shift\+/g, "\u21E7")
+      .replace(/Command\+|Cmd\+/g, "\u2318")
+      .replace(/CommandOrControl\+|CmdOrCtrl\+/g, "\u2318")
+      .replace(/Alt\+/g, "\u2325");
   }
   return shortcut
     .replace(/CommandOrControl\+|CmdOrCtrl\+/g, `${L10N.getStr("ctrl")}+`)
     .replace(/Shift\+/g, "Shift+");
 }
 
 /**
  * Truncates the received text to the maxLength in the format:
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -1326,17 +1326,17 @@ async function hoverAtPos(dbg, { line, c
   cm.scrollIntoView({ line: line - 1, ch }, 0);
   await waitForScrolling(cm);
 
   const coords = getCoordsFromPosition(cm, { line: line - 1, ch });
 
   const { left, top } = coords;
 
   // Adds a vertical offset due to increased line height
-  // https://github.com/firefox-devtools/debugger.html/pull/7934
+  // https://github.com/firefox-devtools/debugger/pull/7934
   const lineHeightOffset = 3;
 
   const tokenEl = dbg.win.document.elementFromPoint(
     left,
     top + lineHeightOffset
   );
 
   if (!tokenEl) {
--- a/devtools/client/shared/source-map/index.js
+++ b/devtools/client/shared/source-map/index.js
@@ -135,30 +135,34 @@ const _getGeneratedRanges = dispatcher.t
 
 const _getGeneratedLocation = dispatcher.task("getGeneratedLocation", {
   queue: true
 });
 const _getAllGeneratedLocations = dispatcher.task("getAllGeneratedLocations", {
   queue: true
 });
 
+const _getOriginalLocation = dispatcher.task("getOriginalLocation", {
+  queue: true
+});
+
 const setAssetRootURL = exports.setAssetRootURL = async assetRoot => dispatcher.invoke("setAssetRootURL", assetRoot);
 
 const getOriginalURLs = exports.getOriginalURLs = async generatedSource => dispatcher.invoke("getOriginalURLs", generatedSource);
 
 const hasOriginalURL = exports.hasOriginalURL = async url => dispatcher.invoke("hasOriginalURL", url);
 
 const getOriginalRanges = exports.getOriginalRanges = async (sourceId, url) => dispatcher.invoke("getOriginalRanges", sourceId, url);
 const getGeneratedRanges = exports.getGeneratedRanges = async (location, originalSource) => _getGeneratedRanges(location, originalSource);
 
 const getGeneratedLocation = exports.getGeneratedLocation = async (location, originalSource) => _getGeneratedLocation(location, originalSource);
 
 const getAllGeneratedLocations = exports.getAllGeneratedLocations = async (location, originalSource) => _getAllGeneratedLocations(location, originalSource);
 
-const getOriginalLocation = exports.getOriginalLocation = async (location, options = {}) => dispatcher.invoke("getOriginalLocation", location, options);
+const getOriginalLocation = exports.getOriginalLocation = async (location, options = {}) => _getOriginalLocation(location, options);
 
 const getFileGeneratedRange = exports.getFileGeneratedRange = async originalSource => dispatcher.invoke("getFileGeneratedRange", originalSource);
 
 const getLocationScopes = exports.getLocationScopes = dispatcher.task("getLocationScopes");
 
 const getOriginalSourceText = exports.getOriginalSourceText = async originalSource => dispatcher.invoke("getOriginalSourceText", originalSource);
 
 const applySourceMap = exports.applySourceMap = async (generatedId, url, code, mappings) => dispatcher.invoke("applySourceMap", generatedId, url, code, mappings);
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -84,17 +84,17 @@ class Timeout final : public LinkedListE
 
   // Returned as value of setTimeout()
   uint32_t mTimeoutId;
 
   // Identifies which firing level this Timeout is being processed in
   // when sync loops trigger nested firing.
   uint32_t mFiringId;
 
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
   int64_t mFiringIndex;
 #endif
 
   // The popup state at timeout creation time if not created from
   // another timeout
   PopupBlocker::PopupControlState mPopupState;
 
   // Used to allow several reasons for setting a timeout, where each
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -449,17 +449,17 @@ int32_t gDisableOpenClickDelay;
 TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow,
                                uint32_t aMaxIdleDeferMS)
     : mWindow(aWindow),
       mExecutor(new TimeoutExecutor(this, false, 0)),
       mIdleExecutor(new TimeoutExecutor(this, true, aMaxIdleDeferMS)),
       mTimeouts(*this),
       mTimeoutIdCounter(1),
       mNextFiringId(InvalidFiringId + 1),
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
       mFiringIndex(0),
       mLastFiringIndex(-1),
 #endif
       mRunningTimeout(nullptr),
       mIdleTimeouts(*this),
       mIdleCallbackTimeoutCounter(1),
       mLastBudgetUpdate(TimeStamp::Now()),
       mExecutionBudget(GetMaxBudget(mWindow.IsBackgroundInternal())),
@@ -558,17 +558,17 @@ nsresult TimeoutManager::SetTimeout(nsIT
   // code can handle. (Note: we already forced |interval| to be non-negative,
   // so the uint32_t cast (to avoid compiler warnings) is ok.)
   uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE);
   if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
     interval = maxTimeoutMs;
   }
 
   RefPtr<Timeout> timeout = new Timeout();
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
   timeout->mFiringIndex = -1;
 #endif
   timeout->mWindow = &mWindow;
   timeout->mIsInterval = aIsInterval;
   timeout->mInterval = TimeDuration::FromMilliseconds(interval);
   timeout->mScriptHandler = aHandler;
   timeout->mReason = aReason;
 
@@ -862,28 +862,28 @@ void TimeoutManager::RunTimeout(const Ti
         // If the FiringId does not match, but is still valid, then this is
         // a Timeout for another RunTimeout() on the call stack (such as in
         // the case of nested event loops, for alert() or more likely XHR).
         // Just skip it.
         if (IsValidFiringId(timeout->mFiringId)) {
           MOZ_LOG(gTimeoutLog, LogLevel::Debug,
                   ("Skipping Run%s(TimeoutManager=%p, timeout=%p) since "
                    "firingId %d is valid (processing firingId %d)"
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
                    " - FiringIndex %" PRId64 " (mLastFiringIndex %" PRId64 ")"
 #endif
                    ,
                    timeout->mIsInterval ? "Interval" : "Timeout", this,
                    timeout.get(), timeout->mFiringId, firingId
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
                    ,
                    timeout->mFiringIndex, mFiringIndex
 #endif
                    ));
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
           // The old FiringIndex assumed no recursion; recursion can cause
           // other timers to get fired "in the middle" of a sequence we've
           // already assigned firingindexes to.  Since we're not going to
           // run this timeout now, remove any FiringIndex that was already
           // set.
 
           // Since all timers that have FiringIndexes set *must* be ready
           // to run and have valid FiringIds, all of them will be 'skipped'
@@ -917,17 +917,17 @@ void TimeoutManager::RunTimeout(const Ti
       // retain compliance with the spec language
       // (https://html.spec.whatwg.org/#dom-settimeout) specifically items
       // 15 ("If method context is a Window object, wait until the Document
       // associated with method context has been fully active for a further
       // timeout milliseconds (not necessarily consecutively)") and item 16
       // ("Wait until any invocations of this algorithm that had the same
       // method context, that started before this one, and whose timeout is
       // equal to or less than this one's, have completed.").
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
       if (timeout->mFiringIndex == -1) {
         timeout->mFiringIndex = mFiringIndex++;
       }
 #endif
 
       if (mIsLoading && !aProcessIdle) {
         // Any timeouts that would fire during a load will be deferred
         // until the load event occurs, but if there's an idle time,
@@ -957,28 +957,28 @@ void TimeoutManager::RunTimeout(const Ti
         if (!scx) {
           // No context means this window was closed or never properly
           // initialized for this language.  This timer will never fire
           // so just remove it.
           timeout->remove();
           continue;
         }
 
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
         if (timeout->mFiringIndex <= mLastFiringIndex) {
           MOZ_LOG(gTimeoutLog, LogLevel::Debug,
                   ("Incorrect firing index for Run%s(TimeoutManager=%p, "
                    "timeout=%p) with "
                    "firingId %d - FiringIndex %" PRId64
                    " (mLastFiringIndex %" PRId64 ")",
                    timeout->mIsInterval ? "Interval" : "Timeout", this,
                    timeout.get(), timeout->mFiringId, timeout->mFiringIndex,
                    mFiringIndex));
         }
-        MOZ_DIAGNOSTIC_ASSERT(timeout->mFiringIndex > mLastFiringIndex);
+        MOZ_ASSERT(timeout->mFiringIndex > mLastFiringIndex);
         mLastFiringIndex = timeout->mFiringIndex;
 #endif
         // This timeout is good to run
         bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);
 #if MOZ_GECKO_PROFILER
         if (profiler_is_active()) {
           TimeDuration elapsed = now - timeout->SubmitTime();
           TimeDuration target = timeout->When() - timeout->SubmitTime();
@@ -1096,17 +1096,17 @@ bool TimeoutManager::RescheduleTimeout(T
 
   // Compute time to next timeout for interval timer.
   // Make sure nextInterval is at least CalculateDelay().
   TimeDuration nextInterval = CalculateDelay(aTimeout);
 
   TimeStamp firingTime = aLastCallbackTime + nextInterval;
   TimeDuration delay = firingTime - aCurrentNow;
 
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
   aTimeout->mFiringIndex = -1;
 #endif
   // And make sure delay is nonnegative; that might happen if the timer
   // thread is firing our timers somewhat early or if they're taking a long
   // time to run the callback.
   if (delay < TimeDuration(0)) {
     delay = TimeDuration(0);
   }
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -198,17 +198,17 @@ class TimeoutManager final {
   // it must be a separate ref-counted object.
   RefPtr<TimeoutExecutor> mExecutor;
   // For timeouts run off the idle queue
   RefPtr<TimeoutExecutor> mIdleExecutor;
   // The list of timeouts coming from non-tracking scripts.
   Timeouts mTimeouts;
   uint32_t mTimeoutIdCounter;
   uint32_t mNextFiringId;
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+#ifdef DEBUG
   int64_t mFiringIndex;
   int64_t mLastFiringIndex;
 #endif
   AutoTArray<uint32_t, 2> mFiringIdStack;
   mozilla::dom::Timeout* mRunningTimeout;
 
   // Timeouts that would have fired but are being deferred until MainThread
   // is idle (because we're loading)
--- a/dom/plugins/base/nsPluginStreamListenerPeer.cpp
+++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp
@@ -261,60 +261,16 @@ nsresult nsPluginStreamListenerPeer::Get
   return NS_OK;
 }
 
 nsresult nsPluginStreamListenerPeer::GetURL(const char** result) {
   *result = mURLSpec.get();
   return NS_OK;
 }
 
-// XXX: Converting the channel within nsPluginStreamListenerPeer
-// to use asyncOpen() and do not want to touch the fragile logic
-// of byte range requests. Hence we just introduce this lightweight
-// wrapper to proxy the context.
-class PluginContextProxy final : public nsIStreamListener {
- public:
-  NS_DECL_ISUPPORTS
-
-  PluginContextProxy(nsIStreamListener* aListener, nsISupports* aContext)
-      : mListener(aListener), mContext(aContext) {
-    MOZ_ASSERT(aListener);
-    MOZ_ASSERT(aContext);
-  }
-
-  NS_IMETHOD
-  OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
-                  nsIInputStream* aIStream, uint64_t aSourceOffset,
-                  uint32_t aLength) override {
-    // Proxy OnDataAvailable using the internal context
-    return mListener->OnDataAvailable(aRequest, mContext, aIStream,
-                                      aSourceOffset, aLength);
-  }
-
-  NS_IMETHOD
-  OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override {
-    // Proxy OnStartRequest using the internal context
-    return mListener->OnStartRequest(aRequest, mContext);
-  }
-
-  NS_IMETHOD
-  OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
-                nsresult aStatusCode) override {
-    // Proxy OnStopRequest using the inernal context
-    return mListener->OnStopRequest(aRequest, mContext, aStatusCode);
-  }
-
- private:
-  ~PluginContextProxy() {}
-  nsCOMPtr<nsIStreamListener> mListener;
-  nsCOMPtr<nsISupports> mContext;
-};
-
-NS_IMPL_ISUPPORTS(PluginContextProxy, nsIStreamListener)
-
 nsresult nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result) {
   *result = mStreamOffset;
   return NS_OK;
 }
 
 nsresult nsPluginStreamListenerPeer::SetStreamOffset(int32_t value) {
   mStreamOffset = value;
   return NS_OK;
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -7,16 +7,17 @@
 #ifndef GFX_STACKINGCONTEXTHELPER_H
 #define GFX_STACKINGCONTEXTHELPER_H
 
 #include "mozilla/Attributes.h"
 #include "mozilla/gfx/MatrixFwd.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "Units.h"
+#include "nsSVGIntegrationUtils.h" // for WrFiltersHolder
 
 class nsDisplayTransform;
 
 namespace mozilla {
 
 struct ActiveScrolledRoot;
 
 namespace layers {
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1420,17 +1420,17 @@ void WebRenderCommandBuilder::EmptyTrans
 bool WebRenderCommandBuilder::NeedsEmptyTransaction() {
   return !mLastCanvasDatas.IsEmpty();
 }
 
 void WebRenderCommandBuilder::BuildWebRenderCommands(
     wr::DisplayListBuilder& aBuilder,
     wr::IpcResourceUpdateQueue& aResourceUpdates, nsDisplayList* aDisplayList,
     nsDisplayListBuilder* aDisplayListBuilder, WebRenderScrollData& aScrollData,
-    wr::LayoutSize& aContentSize, nsTArray<wr::FilterOp>&& aFilters) {
+    wr::LayoutSize& aContentSize, WrFiltersHolder&& aFilters) {
   AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_WRDisplayList);
 
   StackingContextHelper sc;
   aScrollData = WebRenderScrollData(mManager);
   MOZ_ASSERT(mLayerScrollData.empty());
   mLastCanvasDatas.Clear();
   mLastAsr = nullptr;
   mBuilderDumpIndex = 0;
@@ -1446,17 +1446,18 @@ void WebRenderCommandBuilder::BuildWebRe
     }
 
     nsPresContext* presContext =
         aDisplayListBuilder->RootReferenceFrame()->PresContext();
     bool isTopLevelContent =
         presContext->Document()->IsTopLevelContentDocument();
 
     wr::StackingContextParams params;
-    params.mFilters = std::move(aFilters);
+    params.mFilters = std::move(aFilters.filters);
+    params.mFilterDatas = std::move(aFilters.filter_datas);
     params.animation = mZoomProp.ptrOr(nullptr);
     params.cache_tiles = isTopLevelContent;
     params.clip =
         wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
 
     StackingContextHelper pageRootSc(sc, nullptr, nullptr, nullptr, aBuilder,
                                      params);
     if (ShouldDumpDisplayList(aDisplayListBuilder)) {
--- a/gfx/layers/wr/WebRenderCommandBuilder.h
+++ b/gfx/layers/wr/WebRenderCommandBuilder.h
@@ -52,17 +52,17 @@ class WebRenderCommandBuilder {
   bool NeedsEmptyTransaction();
 
   void BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder,
                               wr::IpcResourceUpdateQueue& aResourceUpdates,
                               nsDisplayList* aDisplayList,
                               nsDisplayListBuilder* aDisplayListBuilder,
                               WebRenderScrollData& aScrollData,
                               wr::LayoutSize& aContentSize,
-                              nsTArray<wr::FilterOp>&& aFilters);
+                              WrFiltersHolder&& aFilters);
 
   void PushOverrideForASR(const ActiveScrolledRoot* aASR,
                           const wr::WrSpatialId& aSpatialId);
   void PopOverrideForASR(const ActiveScrolledRoot* aASR);
 
   Maybe<wr::ImageKey> CreateImageKey(
       nsDisplayItem* aItem, ImageContainer* aContainer,
       mozilla::wr::DisplayListBuilder& aBuilder,
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -241,17 +241,17 @@ void WebRenderLayerManager::EndTransacti
                                            EndTransactionFlags aFlags) {
   // This should never get called, all callers should use
   // EndTransactionWithoutLayer instead.
   MOZ_ASSERT(false);
 }
 
 void WebRenderLayerManager::EndTransactionWithoutLayer(
     nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder,
-    nsTArray<wr::FilterOp>&& aFilters, WebRenderBackgroundData* aBackground) {
+    WrFiltersHolder&& aFilters, WebRenderBackgroundData* aBackground) {
   AUTO_PROFILER_TRACING("Paint", "RenderLayers", GRAPHICS);
 
   // Since we don't do repeat transactions right now, just set the time
   mAnimationReadyTime = TimeStamp::Now();
 
   WrBridge()->BeginTransaction();
 
   LayoutDeviceIntSize size = mWidget->GetClientSize();
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -74,17 +74,17 @@ class WebRenderLayerManager final : publ
 
   virtual bool BeginTransactionWithTarget(gfxContext* aTarget,
                                           const nsCString& aURL) override;
   virtual bool BeginTransaction(const nsCString& aURL) override;
   virtual bool EndEmptyTransaction(
       EndTransactionFlags aFlags = END_DEFAULT) override;
   void EndTransactionWithoutLayer(
       nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder,
-      nsTArray<wr::FilterOp>&& aFilters = nsTArray<wr::FilterOp>(),
+      WrFiltersHolder&& aFilters = WrFiltersHolder(),
       WebRenderBackgroundData* aBackground = nullptr);
   virtual void EndTransaction(
       DrawPaintedLayerCallback aCallback, void* aCallbackData,
       EndTransactionFlags aFlags = END_DEFAULT) override;
 
   virtual LayersBackend GetBackendType() override {
     return LayersBackend::LAYERS_WR;
   }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1739,17 +1739,30 @@ already_AddRefed<DrawTarget> gfxPlatform
   return CreateDrawTargetForBackend(mFallbackCanvasBackend, aSize, aFormat);
 #endif
 }
 
 already_AddRefed<DrawTarget> gfxPlatform::CreateOffscreenContentDrawTarget(
     const IntSize& aSize, SurfaceFormat aFormat, bool aFallback) {
   BackendType backend = (aFallback) ? mSoftwareBackend : mContentBackend;
   NS_ASSERTION(backend != BackendType::NONE, "No backend.");
-  return CreateDrawTargetForBackend(backend, aSize, aFormat);
+  RefPtr<DrawTarget> dt = CreateDrawTargetForBackend(backend, aSize, aFormat);
+
+  if (!dt) {
+    return nullptr;
+  }
+
+  // We'd prefer this to take proper care and return a CaptureDT, but for the
+  // moment since we can't and this means we're going to be drawing on the main
+  // thread force it's initialization. See bug 1526045 and bug 1521368.
+  dt->ClearRect(gfx::Rect());
+  if (!dt->IsValid()) {
+
+  }
+  return dt.forget();
 }
 
 already_AddRefed<DrawTarget> gfxPlatform::CreateSimilarSoftwareDrawTarget(
     DrawTarget* aDT, const IntSize& aSize, SurfaceFormat aFormat) {
   RefPtr<DrawTarget> dt;
 
   if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) {
     dt = aDT->CreateSimilarDrawTarget(aSize, aFormat);
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -685,17 +685,17 @@ Maybe<wr::WrSpatialId> DisplayListBuilde
   const wr::LayoutTransform* maybeTransform = transform ? &matrix : nullptr;
   WRDL_LOG("PushStackingContext b=%s t=%s\n", mWrState,
            Stringify(aBounds).c_str(),
            transform ? Stringify(*transform).c_str() : "none");
 
   auto spatialId = wr_dp_push_stacking_context(
       mWrState, aBounds, mCurrentSpaceAndClipChain.space, &aParams,
       maybeTransform, aParams.mFilters.Elements(), aParams.mFilters.Length(),
-      aRasterSpace);
+      aParams.mFilterDatas.Elements(), aParams.mFilterDatas.Length(), aRasterSpace);
 
   return spatialId.id != 0 ? Some(spatialId) : Nothing();
 }
 
 void DisplayListBuilder::PopStackingContext(bool aIsReferenceFrame) {
   WRDL_LOG("PopStackingContext\n", mWrState);
   wr_dp_pop_stacking_context(mWrState, aIsReferenceFrame);
 }
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -324,16 +324,17 @@ struct MOZ_STACK_CLASS StackingContextPa
                                 wr::MixBlendMode::Normal} {}
 
   void SetPreserve3D(bool aPreserve) {
     transform_style =
         aPreserve ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat;
   }
 
   nsTArray<wr::FilterOp> mFilters;
+  nsTArray<wr::WrFilterData> mFilterDatas;
   wr::LayoutRect mBounds = wr::ToLayoutRect(LayoutDeviceRect());
   const gfx::Matrix4x4* mBoundTransform = nullptr;
   const gfx::Matrix4x4* mTransformPtr = nullptr;
   Maybe<nsDisplayTransform*> mDeferredTransformItem;
   // Whether the stacking context is possibly animated. This alters how
   // coordinates are transformed/snapped to invalidate less when transforms
   // change frequently.
   bool mAnimated = false;
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -8,17 +8,17 @@ use std::os::unix::ffi::OsStringExt;
 use std::io::Cursor;
 use std::{mem, slice, ptr, env};
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::cell::RefCell;
 use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::ops::Range;
-use std::os::raw::{c_void, c_char};
+use std::os::raw::{c_void, c_char, c_float};
 #[cfg(target_os = "android")]
 use std::os::raw::{c_int};
 use gleam::gl;
 
 use webrender::api::*;
 use webrender::{ReadPixelsFormat, Renderer, RendererOptions, RendererStats, ThreadListener};
 use webrender::{ExternalImage, ExternalImageHandler, ExternalImageSource};
 use webrender::DebugFlags;
@@ -484,16 +484,34 @@ impl ExternalImageHandler for WrExternal
               id: ExternalImageId,
               channel_index: u8) {
         unsafe {
             (self.unlock_func)(self.external_image_obj, id.into(), channel_index);
         }
     }
 }
 
+#[repr(C)]
+#[derive(Clone, Copy)]
+// Used for ComponentTransfer only
+pub struct WrFilterData {
+    funcR_type: ComponentTransferFuncType,
+    R_values: *mut c_float,
+    R_values_count: usize,
+    funcG_type: ComponentTransferFuncType,
+    G_values: *mut c_float,
+    G_values_count: usize,
+    funcB_type: ComponentTransferFuncType,
+    B_values: *mut c_float,
+    B_values_count: usize,
+    funcA_type: ComponentTransferFuncType,
+    A_values: *mut c_float,
+    A_values_count: usize,
+}
+
 #[repr(u32)]
 pub enum WrAnimationType {
     Transform = 0,
     Opacity = 1,
 }
 
 #[repr(C)]
 pub struct WrAnimationProperty {
@@ -1947,25 +1965,41 @@ pub struct WrStackingContextParams {
 pub extern "C" fn wr_dp_push_stacking_context(
     state: &mut WrState,
     mut bounds: LayoutRect,
     spatial_id: WrSpatialId,
     params: &WrStackingContextParams,
     transform: *const LayoutTransform,
     filters: *const FilterOp,
     filter_count: usize,
+    filter_datas: *const WrFilterData,
+    filter_datas_count: usize,
     glyph_raster_space: RasterSpace,
 ) -> WrSpatialId {
     debug_assert!(unsafe { !is_in_render_thread() });
 
     let c_filters = make_slice(filters, filter_count);
     let mut filters : Vec<FilterOp> = c_filters.iter().map(|c_filter| {
                                                            *c_filter
     }).collect();
 
+    let c_filter_datas = make_slice(filter_datas, filter_datas_count);
+    let r_filter_datas : Vec<FilterData> = c_filter_datas.iter().map(|c_filter_data| {
+        FilterData {
+            func_r_type: c_filter_data.funcR_type,
+            r_values: make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec(),
+            func_g_type: c_filter_data.funcG_type,
+            g_values: make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec(),
+            func_b_type: c_filter_data.funcB_type,
+            b_values: make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec(),
+            func_a_type: c_filter_data.funcA_type,
+            a_values: make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec(),
+        }
+    }).collect();
+
     let transform_ref = unsafe { transform.as_ref() };
     let mut transform_binding = match transform_ref {
         Some(t) => Some(PropertyBinding::Value(t.clone())),
         None => None,
     };
 
     let opacity_ref = unsafe { params.opacity.as_ref() };
     let mut has_opacity_animation = false;
@@ -2043,16 +2077,17 @@ pub extern "C" fn wr_dp_push_stacking_co
     state.frame_builder
          .dl_builder
          .push_stacking_context(&prim_info,
                                 wr_spatial_id,
                                 wr_clip_id,
                                 params.transform_style,
                                 params.mix_blend_mode,
                                 &filters,
+                                &r_filter_datas,
                                 glyph_raster_space,
                                 params.cache_tiles);
 
     result
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState,
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -42,17 +42,19 @@ void gecko_profiler_end_marker(const cha
   macro(normal_border);                    \
   macro(image_border);                     \
   macro(image);                            \
   macro(yuv_image);                        \
   macro(line_decoration);                  \
   macro(linear_grad);                      \
   macro(radial_grad);                      \
   macro(picture);                          \
-  macro(text_run);
+  macro(text_run);                         \
+  macro(filterdata);
+
 
 // Prelude of types necessary before including webrender_ffi_generated.h
 namespace mozilla {
 namespace wr {
 
 // Because this struct is macro-generated on the Rust side, cbindgen can't see
 // it. Work around that by re-declaring it here.
 #define DECLARE_MEMBER(id) uintptr_t id;
--- a/gfx/wr/examples/animation.rs
+++ b/gfx/wr/examples/animation.rs
@@ -62,16 +62,17 @@ impl App {
             PropertyBinding::Binding(property_key, LayoutTransform::identity()),
             ReferenceFrameKind::Transform,
         );
 
         builder.push_simple_stacking_context_with_filters(
             &LayoutPrimitiveInfo::new(LayoutRect::zero()),
             spatial_id,
             &filters,
+            &[],
         );
 
         let space_and_clip = SpaceAndClipInfo {
             spatial_id,
             clip_id: ClipId::root(pipeline_id),
         };
         let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         let complex_clip = ComplexClipRegion {
--- a/gfx/wr/webrender/res/brush_blend.glsl
+++ b/gfx/wr/webrender/res/brush_blend.glsl
@@ -1,26 +1,34 @@
 /* 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/. */
 
 #define VECS_PER_SPECIFIC_BRUSH 3
 
+#define COMPONENT_TRANSFER_IDENTITY 0
+#define COMPONENT_TRANSFER_TABLE 1
+#define COMPONENT_TRANSFER_DISCRETE 2
+#define COMPONENT_TRANSFER_LINEAR 3
+#define COMPONENT_TRANSFER_GAMMA 4
+
 #include shared,prim_shared,brush
 
 // Interpolated UV coordinates to sample.
 varying vec2 vUv;
 
 // X = layer index to sample, Y = flag to allow perspective interpolation of UV.
 flat varying vec2 vLayerAndPerspective;
 flat varying float vAmount;
 flat varying int vOp;
 flat varying mat3 vColorMat;
 flat varying vec3 vColorOffset;
 flat varying vec4 vUvClipBounds;
+flat varying int vTableAddress;
+flat varying int vFuncs[4];
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
@@ -50,19 +58,31 @@ void brush_vs(
     float lumB = 0.0722;
     float oneMinusLumR = 1.0 - lumR;
     float oneMinusLumG = 1.0 - lumG;
     float oneMinusLumB = 1.0 - lumB;
 
     float amount = float(user_data.z) / 65536.0;
     float invAmount = 1.0 - amount;
 
-    vOp = user_data.y;
+    vOp = user_data.y & 0xffff;
     vAmount = amount;
 
+    // This assignment is only used for component transfer filters but this
+    // assignment has to be done here and not in the component transfer case
+    // below because it doesn't get executed on Windows because of a suspected
+    // miscompile of this shader on Windows. See
+    // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
+    // default: just to satisfy angle_shader_validation.rs which needs one
+    // default: for every switch, even in comments.
+    vFuncs[0] = (user_data.y >> 28) & 0xf; // R
+    vFuncs[1] = (user_data.y >> 24) & 0xf; // G
+    vFuncs[2] = (user_data.y >> 20) & 0xf; // B
+    vFuncs[3] = (user_data.y >> 16) & 0xf; // A
+
     switch (vOp) {
         case 2: {
             // Grayscale
             vColorMat = mat3(
                 vec3(lumR + oneMinusLumR * invAmount, lumR - lumR * invAmount, lumR - lumR * invAmount),
                 vec3(lumG - lumG * invAmount, lumG + oneMinusLumG * invAmount, lumG - lumG * invAmount),
                 vec3(lumB - lumB * invAmount, lumB - lumB * invAmount, lumB + oneMinusLumB * invAmount)
             );
@@ -104,16 +124,21 @@ void brush_vs(
         case 10: {
             // Color Matrix
             vec4 mat_data[3] = fetch_from_gpu_cache_3(user_data.z);
             vec4 offset_data = fetch_from_gpu_cache_1(user_data.z + 4);
             vColorMat = mat3(mat_data[0].xyz, mat_data[1].xyz, mat_data[2].xyz);
             vColorOffset = offset_data.rgb;
             break;
         }
+        case 13: {
+            // Component Transfer
+            vTableAddress = user_data.z;
+            break;
+        }
         default: break;
     }
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec3 Contrast(vec3 Cs, float amount) {
     return Cs.rgb * amount - 0.5 * amount + 0.5;
@@ -172,16 +197,68 @@ Fragment brush_fs() {
             alpha *= vAmount;
             break;
         case 11:
             color = SrgbToLinear(color);
             break;
         case 12:
             color = LinearToSrgb(color);
             break;
+        case 13: // Component Transfer
+            int offset = 0;
+            vec4 texel;
+            int k;
+
+            // We push a different amount of data to the gpu cache depending
+            // on the function type.
+            // Identity => 0 blocks
+            // Table/Discrete => 64 blocks (256 values)
+            // Linear => 1 block (2 values)
+            // Gamma => 1 block (3 values)
+            // We loop through the color components and increment the offset
+            // (for the next color component) into the gpu cache based on how
+            // many blocks that function type put into the gpu cache.
+            // Table/Discrete use a 256 entry look up table.
+            // Linear/Gamma are a simple calculation.
+            vec4 colora = alpha != 0.0 ? Cs / alpha : Cs;
+            for (int i = 0; i < 4; i++) {
+                switch (vFuncs[i]) {
+                    case COMPONENT_TRANSFER_IDENTITY:
+                        break;
+                    case COMPONENT_TRANSFER_TABLE:
+                    case COMPONENT_TRANSFER_DISCRETE:
+                        // fetch value from lookup table
+                        k = int(floor(colora[i]*255.0));
+                        texel = fetch_from_gpu_cache_1(vTableAddress + offset + k/4);
+                        colora[i] = clamp(texel[k % 4], 0.0, 1.0);
+                        // offset plus 256/4 blocks
+                        offset = offset + 64;
+                        break;
+                    case COMPONENT_TRANSFER_LINEAR:
+                        // fetch the two values for use in the linear equation
+                        texel = fetch_from_gpu_cache_1(vTableAddress + offset);
+                        colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
+                        // offset plus 1 block
+                        offset = offset + 1;
+                        break;
+                    case COMPONENT_TRANSFER_GAMMA:
+                        // fetch the three values for use in the gamma equation
+                        texel = fetch_from_gpu_cache_1(vTableAddress + offset);
+                        colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
+                        // offset plus 1 block
+                        offset = offset + 1;
+                        break;
+                    default:
+                        // shouldn't happen
+                        break;
+                }
+            }
+            color = colora.rgb;
+            alpha = colora.a;
+            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(uv, vUvClipBounds.xy, vUvClipBounds.zw);
 
--- a/gfx/wr/webrender/res/gpu_cache.glsl
+++ b/gfx/wr/webrender/res/gpu_cache.glsl
@@ -28,16 +28,25 @@ vec4[2] fetch_from_gpu_cache_2_direct(iv
 vec4[2] fetch_from_gpu_cache_2(int address) {
     ivec2 uv = get_gpu_cache_uv(address);
     return vec4[2](
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0))
     );
 }
 
+vec4 fetch_from_gpu_cache_1_direct(ivec2 address) {
+    return texelFetch(sGpuCache, address, 0);
+}
+
+vec4 fetch_from_gpu_cache_1(int address) {
+    ivec2 uv = get_gpu_cache_uv(address);
+    return texelFetch(sGpuCache, uv, 0);
+}
+
 #ifdef WR_VERTEX_SHADER
 
 vec4[8] fetch_from_gpu_cache_8(int address) {
     ivec2 uv = get_gpu_cache_uv(address);
     return vec4[8](
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
@@ -80,25 +89,16 @@ vec4[4] fetch_from_gpu_cache_4(int addre
     return vec4[4](
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)),
         TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0))
     );
 }
 
-vec4 fetch_from_gpu_cache_1_direct(ivec2 address) {
-    return texelFetch(sGpuCache, address, 0);
-}
-
-vec4 fetch_from_gpu_cache_1(int address) {
-    ivec2 uv = get_gpu_cache_uv(address);
-    return texelFetch(sGpuCache, uv, 0);
-}
-
 //TODO: image resource is too specific for this module
 
 struct ImageResource {
     RectWithEndpoint uv_rect;
     float layer;
     vec3 user_data;
 };
 
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1383,16 +1383,17 @@ impl AlphaBatchBuilder {
                                             FilterOp::Saturate(..) => 5,
                                             FilterOp::Sepia(..) => 6,
                                             FilterOp::Brightness(..) => 7,
                                             FilterOp::Opacity(..) => 8,
                                             FilterOp::DropShadow(..) => 9,
                                             FilterOp::ColorMatrix(..) => 10,
                                             FilterOp::SrgbToLinear => 11,
                                             FilterOp::LinearToSrgb => 12,
+                                            FilterOp::ComponentTransfer => unreachable!(),
                                         };
 
                                         let user_data = match filter {
                                             FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
                                             FilterOp::Contrast(amount) |
                                             FilterOp::Grayscale(amount) |
                                             FilterOp::Invert(amount) |
                                             FilterOp::Saturate(amount) |
@@ -1408,16 +1409,17 @@ impl AlphaBatchBuilder {
                                             // Go through different paths
                                             FilterOp::Blur(..) |
                                             FilterOp::DropShadow(..) => {
                                                 unreachable!();
                                             }
                                             FilterOp::ColorMatrix(_) => {
                                                 picture.extra_gpu_data_handle.as_int(gpu_cache)
                                             }
+                                            FilterOp::ComponentTransfer => unreachable!(),
                                         };
 
                                         let (uv_rect_address, textures) = surface
                                             .resolve(
                                                 render_tasks,
                                                 ctx.resource_cache,
                                                 gpu_cache,
                                             );
@@ -1447,16 +1449,70 @@ impl AlphaBatchBuilder {
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                 }
                             }
+                            PictureCompositeMode::ComponentTransferFilter(handle) => {
+                                // This is basically the same as the general filter case above
+                                // except we store a little more data in the filter mode and
+                                // a gpu cache handle in the user data.
+                                let surface = ctx.surfaces[raster_config.surface_index.0]
+                                    .surface
+                                    .as_ref()
+                                    .expect("bug: surface must be allocated by now");
+
+
+                                let filter_data = &ctx.data_stores.filterdata[handle];
+                                let filter_mode : i32 = 13 |
+                                    ((filter_data.data.r_func.to_int() << 28 |
+                                      filter_data.data.g_func.to_int() << 24 |
+                                      filter_data.data.b_func.to_int() << 20 |
+                                      filter_data.data.a_func.to_int() << 16) as i32);
+
+                                let user_data = filter_data.gpu_cache_handle.as_int(gpu_cache);
+
+                                let (uv_rect_address, textures) = surface
+                                    .resolve(
+                                        render_tasks,
+                                        ctx.resource_cache,
+                                        gpu_cache,
+                                    );
+
+                                let key = BatchKey::new(
+                                    BatchKind::Brush(BrushBatchKind::Blend),
+                                    BlendMode::PremultipliedAlpha,
+                                    textures,
+                                );
+
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                    uv_rect_address.as_int(),
+                                    filter_mode,
+                                    user_data,
+                                ]);
+
+                                let instance = BrushInstance {
+                                    prim_header_index,
+                                    clip_task_address,
+                                    segment_index: INVALID_SEGMENT_INDEX,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags,
+                                    user_data: 0,
+                                };
+
+                                self.current_batch_list().push_single_instance(
+                                    key,
+                                    bounding_rect,
+                                    z_id,
+                                    PrimitiveInstanceData::from(instance),
+                                );
+                            }
                             PictureCompositeMode::Puppet { master: Some(source) } if ctx.is_picture_surface_visible(source) => return,
                             PictureCompositeMode::MixBlend { mode, backdrop } if ctx.is_picture_surface_visible(backdrop) => {
                                 let backdrop_picture = &ctx.prim_store.pictures[backdrop.0];
 
                                 let source_id = ctx
                                     .surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -6,17 +6,17 @@ use api::{AlphaType, BorderDetails, Bord
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DisplayItemRef, ExtendMode, ExternalScrollId, AuHelpers};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpaceAndClipInfo, SpatialId, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
-use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
+use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData, TempFilterData};
 use app_units::Au;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use intern::{Handle, Internable, InternDebug};
@@ -38,16 +38,17 @@ use resource_cache::{FontInstanceMap, Im
 use scene::{Scene, StackingContextHelpers};
 use scene_builder::{InternerMut, Interners};
 use spatial_node::{StickyFrameInfo, ScrollFrameKind, SpatialNodeType};
 use std::{f32, mem, usize};
 use std::collections::vec_deque::VecDeque;
 use std::sync::Arc;
 use tiling::{CompositeOps};
 use util::{MaxRect, VecHelper};
+use ::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
 
 impl ClipNode {
@@ -666,30 +667,32 @@ impl<'a> DisplayListFlattener<'a> {
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         stacking_context: &StackingContext,
         spatial_node_index: SpatialNodeIndex,
         origin: LayoutPoint,
         filters: ItemRange<FilterOp>,
+        filter_datas: &[TempFilterData],
         is_backface_visible: bool,
         apply_pipeline_clip: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             // TODO(optimization?): self.traversal.display_list()
             let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, filters),
+                stacking_context.filter_datas_for_compositing(display_list, filter_datas),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         let clip_chain_id = match stacking_context.clip_id {
             Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
             None => ClipChainId::NONE,
         };
@@ -947,16 +950,17 @@ impl<'a> DisplayListFlattener<'a> {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     &info.stacking_context,
                     clip_and_scroll.spatial_node_index,
                     item.rect().origin,
                     item.filters(),
+                    item.filter_datas(),
                     prim_info.is_backface_visible,
                     apply_pipeline_clip,
                 );
                 return Some(subtraversal);
             }
             SpecificDisplayItem::PushReferenceFrame(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_reference_frame(
@@ -1069,16 +1073,18 @@ impl<'a> DisplayListFlattener<'a> {
                     &item,
                     info,
                     clip_and_scroll.spatial_node_index,
                 );
             }
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => {}
+            SpecificDisplayItem::SetFilterOps => {}
+            SpecificDisplayItem::SetFilterData => {}
 
             SpecificDisplayItem::PopReferenceFrame |
             SpecificDisplayItem::PopStackingContext => {
                 unreachable!("Should have returned in parent method.")
             }
             SpecificDisplayItem::PushShadow(shadow) => {
                 self.push_shadow(shadow, clip_and_scroll);
             }
@@ -1480,17 +1486,17 @@ impl<'a> DisplayListFlattener<'a> {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             Picture3DContext::In { ancestor_index, .. } => {
-                assert_ne!(leaf_composite_mode, None);
+                assert!(!leaf_composite_mode.is_none());
                 Picture3DContext::In { root_data: None, ancestor_index }
             }
             Picture3DContext::Out => Picture3DContext::Out,
         };
 
         let leaf_prim_list = PrimitiveList::new(
             stacking_context.primitives,
             &self.interners,
@@ -1566,19 +1572,51 @@ impl<'a> DisplayListFlattener<'a> {
                 stacking_context.is_backface_visible,
                 ClipChainId::NONE,
                 stacking_context.spatial_node_index,
                 &mut self.interners,
             );
         }
 
         // For each filter, create a new image with that composite mode.
+        let mut current_filter_data_index = 0;
         for filter in &stacking_context.composite_ops.filters {
             let filter = filter.sanitize();
-            let composite_mode = Some(PictureCompositeMode::Filter(filter));
+
+            let composite_mode = Some(match filter {
+                FilterOp::ComponentTransfer => {
+                    let filter_data =
+                        &stacking_context.composite_ops.filter_datas[current_filter_data_index];
+                    let filter_data = filter_data.sanitize();
+                    current_filter_data_index = current_filter_data_index + 1;
+                    if filter_data.is_identity() {
+                        continue
+                    } else {
+                        let filter_data_key = SFilterDataKey {
+                            data:
+                                SFilterData {
+                                    r_func: SFilterDataComponent::from_functype_values(
+                                        filter_data.func_r_type, &filter_data.r_values),
+                                    g_func: SFilterDataComponent::from_functype_values(
+                                        filter_data.func_g_type, &filter_data.g_values),
+                                    b_func: SFilterDataComponent::from_functype_values(
+                                        filter_data.func_b_type, &filter_data.b_values),
+                                    a_func: SFilterDataComponent::from_functype_values(
+                                        filter_data.func_a_type, &filter_data.a_values),
+                                },
+                        };
+
+                        let handle = self.interners
+                            .filterdata
+                            .intern(&filter_data_key, || ());
+                        PictureCompositeMode::ComponentTransferFilter(handle)
+                    }
+                }
+                _ => PictureCompositeMode::Filter(filter),
+            });
 
             let filter_pic_index = PictureIndex(self.prim_store.pictures
                 .alloc()
                 .init(PicturePrimitive::new_image(
                     composite_mode,
                     Picture3DContext::Out,
                     stacking_context.pipeline_id,
                     None,
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/src/filterdata.rs
@@ -0,0 +1,198 @@
+/* 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 std::{hash};
+use gpu_cache::{GpuCacheHandle};
+use frame_builder::FrameBuildingState;
+use gpu_cache::GpuDataRequest;
+use intern;
+use api::{ComponentTransferFuncType};
+
+
+pub use intern_types::filterdata::Handle as FilterDataHandle;
+
+#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum SFilterDataComponent {
+    Identity,
+    Table(Vec<f32>),
+    Discrete(Vec<f32>),
+    Linear(f32, f32),
+    Gamma(f32, f32, f32),
+}
+
+impl Eq for SFilterDataComponent {}
+
+impl hash::Hash for SFilterDataComponent {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        match self {
+            SFilterDataComponent::Identity => {
+                0.hash(state);
+            }
+            SFilterDataComponent::Table(values) => {
+                1.hash(state);
+                values.len().hash(state);
+                for val in values {
+                    val.to_bits().hash(state);
+                }
+            }
+            SFilterDataComponent::Discrete(values) => {
+                2.hash(state);
+                values.len().hash(state);
+                for val in values {
+                    val.to_bits().hash(state);
+                }
+            }
+            SFilterDataComponent::Linear(a, b) => {
+                3.hash(state);
+                a.to_bits().hash(state);
+                b.to_bits().hash(state);
+            }
+            SFilterDataComponent::Gamma(a, b, c) => {
+                4.hash(state);
+                a.to_bits().hash(state);
+                b.to_bits().hash(state);
+                c.to_bits().hash(state);
+            }
+        }
+    }
+}
+
+impl SFilterDataComponent {
+    pub fn to_int(&self) -> u32 {
+        match self {
+            SFilterDataComponent::Identity => 0,
+            SFilterDataComponent::Table(_) => 1,
+            SFilterDataComponent::Discrete(_) => 2,
+            SFilterDataComponent::Linear(_, _) => 3,
+            SFilterDataComponent::Gamma(_, _, _) => 4,
+        }
+    }
+
+    pub fn from_functype_values(
+        func_type: ComponentTransferFuncType,
+        values: &[f32],
+    ) -> SFilterDataComponent {
+        match func_type {
+            ComponentTransferFuncType::Identity => SFilterDataComponent::Identity,
+            ComponentTransferFuncType::Table => SFilterDataComponent::Table(values.to_vec()),
+            ComponentTransferFuncType::Discrete => SFilterDataComponent::Discrete(values.to_vec()),
+            ComponentTransferFuncType::Linear => SFilterDataComponent::Linear(values[0], values[1]),
+            ComponentTransferFuncType::Gamma => SFilterDataComponent::Gamma(values[0], values[1], values[2]),
+        }
+    }
+}
+
+#[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SFilterData {
+    pub r_func: SFilterDataComponent,
+    pub g_func: SFilterDataComponent,
+    pub b_func: SFilterDataComponent,
+    pub a_func: SFilterDataComponent,
+}
+
+#[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SFilterDataKey {
+    pub data: SFilterData,
+}
+
+impl intern::InternDebug for SFilterDataKey {}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+pub struct SFilterDataTemplate {
+    pub data: SFilterData,
+    pub gpu_cache_handle: GpuCacheHandle,
+}
+
+impl From<SFilterDataKey> for SFilterDataTemplate {
+    fn from(item: SFilterDataKey) -> Self {
+        SFilterDataTemplate {
+            data: item.data,
+            gpu_cache_handle: GpuCacheHandle::new(),
+        }
+    }
+}
+
+impl SFilterDataTemplate {
+    /// Update the GPU cache for a given filter data template. This may be called multiple
+    /// times per frame, by each primitive reference that refers to this interned
+    /// template. The initial request call to the GPU cache ensures that work is only
+    /// done if the cache entry is invalid (due to first use or eviction).
+    pub fn update(
+        &mut self,
+        frame_state: &mut FrameBuildingState,
+    ) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+            push_component_transfer_data(&self.data.r_func, &mut request);
+            push_component_transfer_data(&self.data.g_func, &mut request);
+            push_component_transfer_data(&self.data.b_func, &mut request);
+            push_component_transfer_data(&self.data.a_func, &mut request);
+            assert!(self.data.r_func != SFilterDataComponent::Identity
+                 || self.data.g_func != SFilterDataComponent::Identity
+                 || self.data.b_func != SFilterDataComponent::Identity
+                 || self.data.a_func != SFilterDataComponent::Identity);
+        }
+    }
+}
+
+fn push_component_transfer_data(
+    func_comp: &SFilterDataComponent,
+    request: &mut GpuDataRequest,
+) {
+    match func_comp {
+        SFilterDataComponent::Identity => { return; }
+        SFilterDataComponent::Table(values) |
+        SFilterDataComponent::Discrete(values) => {
+            // Push a 256 entry lookup table.
+            assert!(values.len() > 0);
+            for i in 0 .. 64 {
+                let mut arr = [0.0 ; 4];
+                for j in 0 .. 4 {
+                    if (values.len() == 1) || (i == 63 && j == 3) {
+                        arr[j] = values[values.len()-1];
+                    } else {
+                        let c = ((4*i + j) as f32)/255.0;
+                        match func_comp {
+                            SFilterDataComponent::Table(_) => {
+                                let n = (values.len()-1) as f32;
+                                let k = (n * c).floor() as u32;
+                                let ku = k as usize;
+                                assert!(ku < values.len()-1);
+                                arr[j] = values[ku] + (c*n - (k as f32)) * (values[ku+1] - values[ku]);
+                            }
+                            SFilterDataComponent::Discrete(_) => {
+                                let n = values.len() as f32;
+                                let k = (n * c).floor() as usize;
+                                assert!(k < values.len());
+                                arr[j] = values[k];
+                            }
+                            SFilterDataComponent::Identity |
+                            SFilterDataComponent::Linear(_,_) |
+                            SFilterDataComponent::Gamma(_,_,_) => {
+                                unreachable!();
+                            }
+                        }
+
+                    }
+                }
+
+                request.push(arr);
+            }
+        }
+        SFilterDataComponent::Linear(a, b) => {
+            request.push([*a, *b, 0.0, 0.0]);
+        }
+        SFilterDataComponent::Gamma(a, b, c) => {
+            request.push([*a, *b, *c, 0.0]);
+        }
+    }
+}
--- a/gfx/wr/webrender/src/intern_types.rs
+++ b/gfx/wr/webrender/src/intern_types.rs
@@ -98,10 +98,17 @@ pub type Interner = intern::Interner<Pic
 pub mod text_run {
 common!();
 use ::prim_store::text_run::{TextRunKey, TextRunTemplate};
 pub type Store = intern::DataStore<TextRunKey, TextRunTemplate, Marker>;
 pub type UpdateList = intern::UpdateList<TextRunKey>;
 pub type Interner = intern::Interner<TextRunKey, PrimitiveSceneData, Marker>;
 }
 
+pub mod filterdata {
+common!();
+use ::filterdata::{SFilterDataKey, SFilterDataTemplate};
+pub type Store = intern::DataStore<SFilterDataKey, SFilterDataTemplate, Marker>;
+pub type UpdateList = intern::UpdateList<SFilterDataKey>;
+pub type Interner = intern::Interner<SFilterDataKey, (), Marker>;
+}
 
 
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -85,16 +85,17 @@ mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
 mod device;
 mod display_list_flattener;
 mod ellipse;
+mod filterdata;
 mod frame_builder;
 mod freelist;
 #[cfg(any(target_os = "macos", target_os = "windows"))]
 mod gamma_lut;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
 #[cfg(feature = "pathfinder")]
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -32,16 +32,17 @@ use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::Interners;
 use smallvec::SmallVec;
 use std::{mem, u16};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use texture_cache::TextureCacheHandle;
 use tiling::RenderTargetKind;
 use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect, scale_factors};
+use ::filterdata::{FilterDataHandle};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
    picture into its parent.
  * A configuration describing how to draw the primitives on
@@ -1877,35 +1878,37 @@ bitflags! {
         /// Preserve-3D requires a surface for plane-splitting.
         const PRESERVE3D = 2;
     }
 }
 
 /// Specifies how this Picture should be composited
 /// onto the target it belongs to.
 #[allow(dead_code)]
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum PictureCompositeMode {
     /// Don't composite this picture in a standard way,
     /// can be used for pictures that need to be isolated but used
     /// manually, e.g. for the backdrop of mix-blend pictures.
     Puppet {
         /// The master picture that actually handles compositing
         /// of this one. If that picture turns out to be invisible,
         /// the puppet mode becomes a regular blit.
         master: Option<PictureIndex>,
     },
     /// Apply CSS mix-blend-mode effect.
     MixBlend {
         mode: MixBlendMode,
         backdrop: PictureIndex,
     },
-    /// Apply a CSS filter.
+    /// Apply a CSS filter (except component transfer).
     Filter(FilterOp),
+    /// Apply a component transfer filter.
+    ComponentTransferFilter(FilterDataHandle),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
     TileCache {
         clear_color: ColorF,
     },
 }
@@ -2861,16 +2864,17 @@ impl PicturePrimitive {
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
         clipped_prim_bounding_rect: WorldRect,
         surface_index: SurfaceIndex,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
+        data_stores: &mut DataStores,
     ) -> bool {
         let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
 
         if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
             self.resolve_split_planes(splitter, frame_state);
         }
 
         let raster_config = match self.raster_config {
@@ -2883,17 +2887,16 @@ impl PicturePrimitive {
         let (raster_spatial_node_index, child_tasks, device_pixel_scale) = {
             let surface_info = &mut frame_state.surfaces[raster_config.surface_index.0];
             (
                 surface_info.raster_spatial_node_index,
                 surface_info.take_render_tasks(),
                 surface_info.device_pixel_scale,
             )
         };
-        let surfaces = &mut frame_state.surfaces;
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             prim_instance.spatial_node_index,
             raster_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
@@ -2917,29 +2920,29 @@ impl PicturePrimitive {
         //           probably worth tidying this code up to be a bit more consistent.
         //           Perhaps store the color matrix after the common data, even though
         //           it's not used by that shader.
 
         let surface = match raster_config.composite_mode {
             PictureCompositeMode::TileCache { .. } => {
                 // For a picture surface, just push any child tasks and tile
                 // blits up to the parent surface.
-                let surface = &mut surfaces[surface_index.0];
+                let surface = &mut frame_state.surfaces[surface_index.0];
                 surface.tasks.extend(child_tasks);
 
                 return true;
             }
             PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
                 let blur_std_deviation = blur_radius * device_pixel_scale.0;
                 let scale_factors = scale_factors(&transform);
                 let blur_std_deviation = DeviceSize::new(
                     blur_std_deviation * scale_factors.0,
                     blur_std_deviation * scale_factors.1
                 );
-                let inflation_factor = surfaces[raster_config.surface_index.0].inflation_factor;
+                let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor;
                 let inflation_factor = (inflation_factor * device_pixel_scale.0).ceil() as i32;
 
                 // The clipped field is the part of the picture that is visible
                 // on screen. The unclipped field is the screen-space rect of
                 // the complete picture, if no screen / clip-chain was applied
                 // (this includes the extra space for blur region). To ensure
                 // that we draw a large enough part of the picture to get correct
                 // blur results, inflate that clipped area by the blur range, and
@@ -2983,17 +2986,17 @@ impl PicturePrimitive {
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
 
-                surfaces[surface_index.0].tasks.push(render_task_id);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
 
                 PictureSurface::RenderTask(render_task_id)
             }
             PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
                 let blur_std_deviation = blur_radius * device_pixel_scale.0;
                 let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
                 let rounded_std_dev = blur_std_deviation.round();
                 let rounded_std_dev = DeviceSize::new(rounded_std_dev, rounded_std_dev);
@@ -3041,17 +3044,17 @@ impl PicturePrimitive {
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
                 self.secondary_render_task_id = Some(picture_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                surfaces[surface_index.0].tasks.push(render_task_id);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
 
                 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                     // TODO(gw): This is very hacky code below! It stores an extra
                     //           brush primitive below for the special case of a
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
@@ -3102,17 +3105,44 @@ impl PicturePrimitive {
                     clipped.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     device_pixel_scale,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
-                surfaces[surface_index.0].tasks.push(render_task_id);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
+                PictureSurface::RenderTask(render_task_id)
+            }
+            PictureCompositeMode::ComponentTransferFilter(handle) => {
+                let filter_data = &mut data_stores.filterdata[handle];
+                filter_data.update(frame_state);
+
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    device_pixel_scale,
+                    true,
+                );
+
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    child_tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                    device_pixel_scale,
+                );
+
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
             PictureCompositeMode::Puppet { .. } |
             PictureCompositeMode::MixBlend { .. } |
             PictureCompositeMode::Blit(_) => {
                 // The SplitComposite shader used for 3d contexts doesn't snap
                 // to pixels, so we shouldn't snap our uv coordinates either.
                 let supports_snapping = match self.context_3d {
@@ -3135,22 +3165,22 @@ impl PicturePrimitive {
                     clipped.origin,
                     child_tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                     device_pixel_scale,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
-                surfaces[surface_index.0].tasks.push(render_task_id);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
         };
 
-        surfaces[raster_config.surface_index.0].surface = Some(surface);
+        frame_state.surfaces[raster_config.surface_index.0].surface = Some(surface);
 
         true
     }
 }
 
 // Calculate a single homogeneous screen-space UV for a picture.
 fn calculate_screen_uv(
     local_pos: &PicturePoint,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -2299,16 +2299,17 @@ impl PrimitiveStore {
                 let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
                 if pic.prepare_for_render(
                     pic_index,
                     prim_instance,
                     prim_info.clipped_world_rect,
                     pic_context.surface_index,
                     frame_context,
                     frame_state,
+                    data_stores,
                 ) {
                     if let Some(ref mut splitter) = pic_state.plane_splitter {
                         PicturePrimitive::add_split_plane(
                             splitter,
                             frame_state.transforms,
                             prim_instance,
                             pic.local_rect,
                             &prim_info.combined_local_clip_rect,
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{
     ColorU, FilterOp, LayoutSize, LayoutPrimitiveInfo, MixBlendMode,
     PropertyBinding, PropertyBindingId, LayoutVector2D,
 };
+use intern::ItemUid;
 use app_units::Au;
 use display_list_flattener::{AsInstanceKind, IsVisible};
 use intern::{Internable, InternDebug};
 use intern_types;
 use picture::PictureCompositeMode;
 use prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
     PrimitiveInstanceKind, PrimitiveSceneData, PrimitiveStore, VectorKey,
@@ -35,16 +36,17 @@ pub enum PictureCompositeKey {
     Opacity(Au),
     OpacityBinding(PropertyBindingId, Au),
     Saturate(Au),
     Sepia(Au),
     DropShadow(VectorKey, Au, ColorU),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
+    ComponentTransfer(ItemUid),
 
     // MixBlendMode
     Multiply,
     Screen,
     Overlay,
     Darken,
     Lighten,
     ColorDodge,
@@ -110,18 +112,22 @@ impl From<Option<PictureCompositeMode>> 
                     }
                     FilterOp::ColorMatrix(values) => {
                         let mut quantized_values: [Au; 20] = [Au(0); 20];
                         for (value, result) in values.iter().zip(quantized_values.iter_mut()) {
                             *result = Au::from_f32_px(*value);
                         }
                         PictureCompositeKey::ColorMatrix(quantized_values)
                     }
+                    FilterOp::ComponentTransfer => unreachable!(),
                 }
             }
+            Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
+                PictureCompositeKey::ComponentTransfer(handle.uid())
+            }
             Some(PictureCompositeMode::Puppet { .. }) |
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
@@ -220,12 +226,12 @@ impl IsVisible for Picture {
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<Picture>(), 84, "Picture size changed");
+    assert_eq!(mem::size_of::<Picture>(), 88, "Picture size changed");
     assert_eq!(mem::size_of::<PictureTemplate>(), 20, "PictureTemplate size changed");
-    assert_eq!(mem::size_of::<PictureKey>(), 96, "PictureKey size changed");
+    assert_eq!(mem::size_of::<PictureKey>(), 104, "PictureKey size changed");
 }
--- a/gfx/wr/webrender/src/profiler.rs
+++ b/gfx/wr/webrender/src/profiler.rs
@@ -529,16 +529,17 @@ impl BackendProfileCounters {
                 line_decoration: ResourceProfileCounter::new("Interned line decorations"),
                 linear_grad: ResourceProfileCounter::new("Interned linear gradients"),
                 normal_border: ResourceProfileCounter::new("Interned normal borders"),
                 picture: ResourceProfileCounter::new("Interned pictures"),
                 radial_grad: ResourceProfileCounter::new("Interned radial gradients"),
                 text_run: ResourceProfileCounter::new("Interned text runs"),
                 yuv_image: ResourceProfileCounter::new("Interned YUV images"),
                 clip: ResourceProfileCounter::new("Interned clips"),
+                filterdata: ResourceProfileCounter::new("Interned filterdata"),
             },
         }
     }
 
     pub fn reset(&mut self) {
         self.total_time.reset();
         self.ipc.total_time.reset();
         self.ipc.build_time.reset();
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1588,16 +1588,18 @@ impl ToDebugString for SpecificDisplayIt
             SpecificDisplayItem::Image(..) => String::from("image"),
             SpecificDisplayItem::Line(..) => String::from("line"),
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
             SpecificDisplayItem::PopReferenceFrame => String::from("pop_reference_frame"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
             SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
             SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
+            SpecificDisplayItem::SetFilterOps => String::from("set_filter_ops"),
+            SpecificDisplayItem::SetFilterData => String::from("set_filter_data"),
             SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
             SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
             SpecificDisplayItem::ScrollFrame(..) => String::from("scroll_frame"),
             SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
             SpecificDisplayItem::StickyFrame(..) => String::from("sticky_frame"),
             SpecificDisplayItem::Text(..) => String::from("text"),
             SpecificDisplayItem::YuvImage(..) => String::from("yuv_image"),
             SpecificDisplayItem::PushCacheMarker(..) => String::from("push_cache_marker"),
--- a/gfx/wr/webrender/src/scene.rs
+++ b/gfx/wr/webrender/src/scene.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch, LayoutSize};
-use api::{FilterOp, LayoutTransform, PipelineId, PropertyBinding, PropertyBindingId};
-use api::{ItemRange, MixBlendMode, StackingContext};
+use api::{FilterOp, TempFilterData, FilterData, ComponentTransferFuncType, LayoutTransform};
+use api::{PipelineId, PropertyBinding, PropertyBindingId, ItemRange, MixBlendMode, StackingContext};
 use internal_types::FastHashMap;
 use std::sync::Arc;
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -220,17 +220,18 @@ impl FilterOpHelpers for FilterOp {
             FilterOp::Grayscale(..) |
             FilterOp::HueRotate(..) |
             FilterOp::Invert(..) |
             FilterOp::Saturate(..) |
             FilterOp::Sepia(..) |
             FilterOp::DropShadow(..) |
             FilterOp::ColorMatrix(..) |
             FilterOp::SrgbToLinear |
-            FilterOp::LinearToSrgb  => true,
+            FilterOp::LinearToSrgb |
+            FilterOp::ComponentTransfer  => true,
             FilterOp::Opacity(_, amount) => {
                 amount > OPACITY_EPSILON
             }
         }
     }
 
     fn is_noop(&self) -> bool {
         match *self {
@@ -249,28 +250,35 @@ impl FilterOpHelpers for FilterOp {
             },
             FilterOp::ColorMatrix(matrix) => {
                 matrix == [1.0, 0.0, 0.0, 0.0,
                            0.0, 1.0, 0.0, 0.0,
                            0.0, 0.0, 1.0, 0.0,
                            0.0, 0.0, 0.0, 1.0,
                            0.0, 0.0, 0.0, 0.0]
             }
-            FilterOp::SrgbToLinear | FilterOp::LinearToSrgb => false,
+            FilterOp::SrgbToLinear |
+            FilterOp::LinearToSrgb |
+            FilterOp::ComponentTransfer => false,
         }
     }
 }
 
 pub trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filters: ItemRange<FilterOp>,
     ) -> Vec<FilterOp>;
+    fn filter_datas_for_compositing(
+        &self,
+        display_list: &BuiltDisplayList,
+        input_filter_datas: &[TempFilterData],
+    ) -> Vec<FilterData>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
@@ -285,9 +293,35 @@ impl StackingContextHelpers for Stacking
         //           we could probably make it a bit
         //           more efficient than cloning these here.
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
             filters.push(filter);
         }
         filters
     }
+
+    fn filter_datas_for_compositing(
+        &self,
+        display_list: &BuiltDisplayList,
+        input_filter_datas: &[TempFilterData],
+    ) -> Vec<FilterData> {
+        // TODO(gw): Now that we resolve these later on,
+        //           we could probably make it a bit
+        //           more efficient than cloning these here.
+        let mut filter_datas = vec![];
+        for temp_filter_data in input_filter_datas {
+            let func_types : Vec<ComponentTransferFuncType> = display_list.get(temp_filter_data.func_types).collect();
+            debug_assert!(func_types.len() == 4);
+            filter_datas.push( FilterData {
+                func_r_type: func_types[0],
+                r_values: display_list.get(temp_filter_data.r_values).collect(),
+                func_g_type: func_types[1],
+                g_values: display_list.get(temp_filter_data.g_values).collect(),
+                func_b_type: func_types[2],
+                b_values: display_list.get(temp_filter_data.b_values).collect(),
+                func_a_type: func_types[3],
+                a_values: display_list.get(temp_filter_data.a_values).collect(),
+            });
+        }
+        filter_datas
+    }
 }
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
-use api::{DocumentLayer, FilterOp, ImageFormat, DevicePoint};
+use api::{DocumentLayer, FilterOp, FilterData, ImageFormat, DevicePoint};
 use api::{MixBlendMode, PipelineId, DeviceRect, LayoutSize, WorldRect};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use debug_render::DebugItem;
 use device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
@@ -1100,31 +1100,35 @@ impl RenderPass {
         }
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<FilterOp>,
+    pub filter_datas: Vec<FilterData>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> Self {
+    pub fn new(filters: Vec<FilterOp>,
+               filter_datas: Vec<FilterData>,
+               mix_blend_mode: Option<MixBlendMode>) -> Self {
         CompositeOps {
             filters,
+            filter_datas,
             mix_blend_mode,
         }
     }
 
     pub fn is_empty(&self) -> bool {
-        self.filters.is_empty() && self.mix_blend_mode.is_none()
+        self.filters.is_empty() && self.filter_datas.is_empty() && self.mix_blend_mode.is_none()
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -832,16 +832,17 @@ macro_rules! enumerate_interners {
             image_border,
             image,
             yuv_image,
             line_decoration,
             linear_grad,
             radial_grad,
             picture,
             text_run,
+            filterdata,
         }
     }
 }
 
 macro_rules! declare_interning_memory_report {
     ( $( $name: ident, )+ ) => {
         #[repr(C)]
         #[derive(AddAssign, Clone, Debug, Default, Deserialize, Serialize)]
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -122,16 +122,18 @@ pub enum SpecificDisplayItem {
     PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
     PushCacheMarker(CacheMarkerDisplayItem),
     PopCacheMarker,
+    SetFilterOps,
+    SetFilterData,
 }
 
 /// This is a "complete" version of the DI specifics,
 /// containing the auxiliary data within the corresponding
 /// enumeration variants, to be used for debug serialization.
 #[cfg(any(feature = "serialize", feature = "deserialize"))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
@@ -148,23 +150,25 @@ pub enum CompletelySpecificDisplayItem {
     YuvImage(YuvImageDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
     PushReferenceFrame(ReferenceFrameDisplayListItem),
     PopReferenceFrame,
-    PushStackingContext(PushStackingContextDisplayItem, Vec<FilterOp>),
+    PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops(Vec<GradientStop>),
     PushShadow(Shadow),
     PopAllShadows,
     PushCacheMarker(CacheMarkerDisplayItem),
     PopCacheMarker,
+    SetFilterOps(Vec<FilterOp>),
+    SetFilterData(FilterData),
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub image_mask: Option<ImageMask>,
 }
 
@@ -557,18 +561,17 @@ pub struct PushStackingContextDisplayIte
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
     pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
     /// True if picture caching should be used on this stacking context.
     pub cache_tiles: bool,
-} // IMPLICIT: filters: Vec<FilterOp>
-
+} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
@@ -636,16 +639,17 @@ pub enum FilterOp {
     Invert(f32),
     Opacity(PropertyBinding<f32>, f32),
     Saturate(f32),
     Sepia(f32),
     DropShadow(LayoutVector2D, f32, ColorF),
     ColorMatrix([f32; 20]),
     SrgbToLinear,
     LinearToSrgb,
+    ComponentTransfer,
 }
 
 impl FilterOp {
     /// Ensure that the parameters for a filter operation
     /// are sensible.
     pub fn sanitize(self) -> FilterOp {
         match self {
             FilterOp::Blur(radius) => {
@@ -656,16 +660,113 @@ impl FilterOp {
                 let radius = radius.min(MAX_BLUR_RADIUS);
                 FilterOp::DropShadow(offset, radius, color)
             }
             filter => filter,
         }
     }
 }
 
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
+pub enum ComponentTransferFuncType {
+  Identity = 0,
+  Table = 1,
+  Discrete = 2,
+  Linear = 3,
+  Gamma = 4,
+}
+
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct FilterData {
+    pub func_r_type: ComponentTransferFuncType,
+    pub r_values: Vec<f32>,
+    pub func_g_type: ComponentTransferFuncType,
+    pub g_values: Vec<f32>,
+    pub func_b_type: ComponentTransferFuncType,
+    pub b_values: Vec<f32>,
+    pub func_a_type: ComponentTransferFuncType,
+    pub a_values: Vec<f32>,
+}
+
+fn sanitize_func_type(
+    func_type: ComponentTransferFuncType,
+    values: &[f32],
+) -> ComponentTransferFuncType {
+    if values.is_empty() {
+        return ComponentTransferFuncType::Identity;
+    }
+    if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+        return ComponentTransferFuncType::Identity;
+    }
+    if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+        return ComponentTransferFuncType::Identity;
+    }
+    func_type
+}
+
+fn sanitize_values(
+    func_type: ComponentTransferFuncType,
+    values: &[f32],
+) -> bool {
+    if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+        return false;
+    }
+    if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+        return false;
+    }
+    true
+}
+
+impl FilterData {
+    /// Ensure that the number of values matches up with the function type.
+    pub fn sanitize(&self) -> FilterData {
+        FilterData {
+            func_r_type: sanitize_func_type(self.func_r_type, &self.r_values),
+            r_values:
+                    if sanitize_values(self.func_r_type, &self.r_values) {
+                        self.r_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+            func_g_type: sanitize_func_type(self.func_g_type, &self.g_values),
+            g_values:
+                    if sanitize_values(self.func_g_type, &self.g_values) {
+                        self.g_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+            func_b_type: sanitize_func_type(self.func_b_type, &self.b_values),
+            b_values:
+                    if sanitize_values(self.func_b_type, &self.b_values) {
+                        self.b_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+            func_a_type: sanitize_func_type(self.func_a_type, &self.a_values),
+            a_values:
+                    if sanitize_values(self.func_a_type, &self.a_values) {
+                        self.a_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+        }
+    }
+
+    pub fn is_identity(&self) -> bool {
+        self.func_r_type == ComponentTransferFuncType::Identity &&
+        self.func_g_type == ComponentTransferFuncType::Identity &&
+        self.func_b_type == ComponentTransferFuncType::Identity &&
+        self.func_a_type == ComponentTransferFuncType::Identity
+    }
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
     pub pipeline_id: PipelineId,
     pub ignore_missing_pipeline: bool,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -56,16 +56,24 @@ impl<T> Default for ItemRange<T> {
 
 impl<T> ItemRange<T> {
     pub fn is_empty(&self) -> bool {
         // Nothing more than space for a length (0).
         self.length <= mem::size_of::<u64>()
     }
 }
 
+pub struct TempFilterData {
+    pub func_types: ItemRange<di::ComponentTransferFuncType>,
+    pub r_values: ItemRange<f32>,
+    pub g_values: ItemRange<f32>,
+    pub b_values: ItemRange<f32>,
+    pub a_values: ItemRange<f32>,
+}
+
 /// A display list.
 #[derive(Clone, Default)]
 pub struct BuiltDisplayList {
     /// Serde encoded bytes. Mostly DisplayItems, but some mixed in slices.
     data: Vec<u8>,
     descriptor: BuiltDisplayListDescriptor,
 }
 
@@ -90,16 +98,17 @@ pub struct BuiltDisplayListDescriptor {
 
 pub struct BuiltDisplayListIter<'a> {
     list: &'a BuiltDisplayList,
     data: &'a [u8],
     cur_item: di::DisplayItem,
     cur_stops: ItemRange<di::GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
     cur_filters: ItemRange<di::FilterOp>,
+    cur_filter_data: Vec<TempFilterData>,
     cur_clip_chain_items: ItemRange<di::ClipId>,
     cur_complex_clip: (ItemRange<di::ComplexClipRegion>, usize),
     peeking: Peek,
 }
 
 pub struct DisplayItemRef<'a: 'b, 'b> {
     iter: &'b BuiltDisplayListIter<'a>,
 }
@@ -210,16 +219,17 @@ impl<'a> BuiltDisplayListIter<'a> {
                 // Dummy data, will be overwritten by `next`
                 item: di::SpecificDisplayItem::PopStackingContext,
                 layout: di::LayoutPrimitiveInfo::new(LayoutRect::zero()),
                 space_and_clip: di::SpaceAndClipInfo::root_scroll(PipelineId::dummy())
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
+            cur_filter_data: Vec::new(),
             cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
         }
     }
 
     pub fn display_list(&self) -> &'a BuiltDisplayList {
         self.list
@@ -245,16 +255,25 @@ impl<'a> BuiltDisplayListIter<'a> {
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
             self.next_raw()?;
             if let SetGradientStops = self.cur_item.item {
                 // SetGradientStops is a dummy item that most consumers should ignore
                 continue;
             }
+            if let SetFilterOps = self.cur_item.item {
+                // SetFilterOps is a dummy item that most consumers should ignore
+                continue;
+            }
+            if let SetFilterData = self.cur_item.item {
+                // SetFilterData is a dummy item that most consumers should ignore
+                continue;
+            }
+
             break;
         }
 
         Some(self.as_ref())
     }
 
     /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking
     /// and may leave irrelevant ranges live (so a Clip may have GradientStops if
@@ -271,24 +290,35 @@ impl<'a> BuiltDisplayListIter<'a> {
             bincode::deserialize_in_place(reader, &mut self.cur_item)
                 .expect("MEH: malicious process?");
         }
 
         match self.cur_item.item {
             SetGradientStops => {
                 self.cur_stops = skip_slice::<di::GradientStop>(self.list, &mut self.data).0;
             }
+            SetFilterOps => {
+                self.cur_filters = skip_slice::<di::FilterOp>(self.list, &mut self.data).0;
+            }
+            SetFilterData => {
+                self.cur_filter_data.push(TempFilterData {
+                    func_types: skip_slice::<di::ComponentTransferFuncType>(self.list, &mut self.data).0,
+                    r_values: skip_slice::<f32>(self.list, &mut self.data).0,
+                    g_values: skip_slice::<f32>(self.list, &mut self.data).0,
+                    b_values: skip_slice::<f32>(self.list, &mut self.data).0,
+                    a_values: skip_slice::<f32>(self.list, &mut self.data).0,
+                });
+            }
             ClipChain(_) => {
                 self.cur_clip_chain_items = skip_slice::<di::ClipId>(self.list, &mut self.data).0;
             }
             Clip(_) | ScrollFrame(_) => {
                 self.cur_complex_clip = self.skip_slice::<di::ComplexClipRegion>()
             }
             Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
-            PushStackingContext(_) => self.cur_filters = self.skip_slice::<di::FilterOp>().0,
             _ => { /* do nothing */ }
         }
 
         Some(self.as_ref())
     }
 
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
         skip_slice::<T>(self.list, &mut self.data)
@@ -384,16 +414,20 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn glyphs(&self) -> ItemRange<GlyphInstance> {
         self.iter.cur_glyphs
     }
 
     pub fn filters(&self) -> ItemRange<di::FilterOp> {
         self.iter.cur_filters
     }
 
+    pub fn filter_datas(&self) -> &Vec<TempFilterData> {
+        &self.iter.cur_filter_data
+    }
+
     pub fn clip_chain_items(&self) -> ItemRange<di::ClipId> {
         self.iter.cur_clip_chain_items
     }
 
     pub fn display_list(&self) -> &BuiltDisplayList {
         self.iter.display_list()
     }
 
@@ -482,21 +516,39 @@ impl Serialize for BuiltDisplayList {
                     di::SpecificDisplayItem::YuvImage(v) => YuvImage(v),
                     di::SpecificDisplayItem::Border(v) => Border(v),
                     di::SpecificDisplayItem::BoxShadow(v) => BoxShadow(v),
                     di::SpecificDisplayItem::Gradient(v) => Gradient(v),
                     di::SpecificDisplayItem::RadialGradient(v) => RadialGradient(v),
                     di::SpecificDisplayItem::Iframe(v) => Iframe(v),
                     di::SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
                     di::SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
-                    di::SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(
-                        v,
+                    di::SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(v),
+                    di::SpecificDisplayItem::PopStackingContext => PopStackingContext,
+                    di::SpecificDisplayItem::SetFilterOps => SetFilterOps(
                         item.iter.list.get(item.iter.cur_filters).collect()
                     ),
-                    di::SpecificDisplayItem::PopStackingContext => PopStackingContext,
+                    di::SpecificDisplayItem::SetFilterData => {
+                        debug_assert!(!item.iter.cur_filter_data.is_empty());
+                        let temp_filter_data = &item.iter.cur_filter_data[item.iter.cur_filter_data.len()-1];
+
+                        let func_types: Vec<di::ComponentTransferFuncType> =
+                            item.iter.list.get(temp_filter_data.func_types).collect();
+                        debug_assert!(func_types.len() == 4);
+                        SetFilterData(di::FilterData {
+                            func_r_type: func_types[0],
+                            r_values: item.iter.list.get(temp_filter_data.r_values).collect(),
+                            func_g_type: func_types[1],
+                            g_values: item.iter.list.get(temp_filter_data.g_values).collect(),
+                            func_b_type: func_types[2],
+                            b_values: item.iter.list.get(temp_filter_data.b_values).collect(),
+                            func_a_type: func_types[3],
+                            a_values: item.iter.list.get(temp_filter_data.a_values).collect(),
+                        })
+                    },
                     di::SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
                     di::SpecificDisplayItem::PushShadow(v) => PushShadow(v),
                     di::SpecificDisplayItem::PopAllShadows => PopAllShadows,
                     di::SpecificDisplayItem::PushCacheMarker(m) => PushCacheMarker(m),
                     di::SpecificDisplayItem::PopCacheMarker => PopCacheMarker,
                 },
@@ -569,19 +621,35 @@ impl<'de> Deserialize<'de> for BuiltDisp
                         total_clip_nodes += 1;
                         di::SpecificDisplayItem::Iframe(specific_item)
                     }
                     PushReferenceFrame(v) => {
                         total_spatial_nodes += 1;
                         di::SpecificDisplayItem::PushReferenceFrame(v)
                     }
                     PopReferenceFrame => di::SpecificDisplayItem::PopReferenceFrame,
-                    PushStackingContext(specific_item, filters) => {
+                    PushStackingContext(specific_item) => {
+                        di::SpecificDisplayItem::PushStackingContext(specific_item)
+                    },
+                    SetFilterOps(filters) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, filters);
-                        di::SpecificDisplayItem::PushStackingContext(specific_item)
+                        di::SpecificDisplayItem::SetFilterOps
+                    },
+                    SetFilterData(filter_data) => {
+                        let func_types: Vec<di::ComponentTransferFuncType> =
+                            [filter_data.func_r_type,
+                             filter_data.func_g_type,
+                             filter_data.func_b_type,
+                             filter_data.func_a_type].to_vec();
+                        DisplayListBuilder::push_iter_impl(&mut temp, func_types);
+                        DisplayListBuilder::push_iter_impl(&mut temp, filter_data.r_values);
+                        DisplayListBuilder::push_iter_impl(&mut temp, filter_data.g_values);
+                        DisplayListBuilder::push_iter_impl(&mut temp, filter_data.b_values);
+                        DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values);
+                        di::SpecificDisplayItem::SetFilterData
                     },
                     PopStackingContext => di::SpecificDisplayItem::PopStackingContext,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         di::SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => di::SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => di::SpecificDisplayItem::PopAllShadows,
@@ -1294,59 +1362,76 @@ impl DisplayListBuilder {
     pub fn push_stacking_context(
         &mut self,
         layout: &di::LayoutPrimitiveInfo,
         spatial_id: di::SpatialId,
         clip_id: Option<di::ClipId>,
         transform_style: di::TransformStyle,
         mix_blend_mode: di::MixBlendMode,
         filters: &[di::FilterOp],
+        filter_datas: &[di::FilterData],
         raster_space: di::RasterSpace,
         cache_tiles: bool,
     ) {
+        self.push_new_empty_item(&di::SpecificDisplayItem::SetFilterOps);
+        self.push_iter(filters);
+
+        for filter_data in filter_datas {
+            let func_types = [
+                filter_data.func_r_type, filter_data.func_g_type,
+                filter_data.func_b_type, filter_data.func_a_type];
+            self.push_new_empty_item(&di::SpecificDisplayItem::SetFilterData);
+            self.push_iter(&func_types);
+            self.push_iter(&filter_data.r_values);
+            self.push_iter(&filter_data.g_values);
+            self.push_iter(&filter_data.b_values);
+            self.push_iter(&filter_data.a_values);
+        }
+
         let item = di::SpecificDisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
             stacking_context: di::StackingContext {
                 transform_style,
                 mix_blend_mode,
                 clip_id,
                 raster_space,
                 cache_tiles,
             },
         });
 
         self.push_item(&item, layout, &di::SpaceAndClipInfo {
             spatial_id,
             clip_id: di::ClipId::invalid(),
         });
-        self.push_iter(filters);
     }
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context(
         &mut self,
         layout: &di::LayoutPrimitiveInfo,
         spatial_id: di::SpatialId,
     ) {
-        self.push_simple_stacking_context_with_filters(layout, spatial_id, &[]);
+        self.push_simple_stacking_context_with_filters(layout, spatial_id, &[], &[]);
     }
 
     /// Helper for examples/ code.
     pub fn push_simple_stacking_context_with_filters(
         &mut self,
         layout: &di::LayoutPrimitiveInfo,
         spatial_id: di::SpatialId,
         filters: &[di::FilterOp],
+        filter_datas: &[di::FilterData],
     ) {
         self.push_stacking_context(
             layout,
             spatial_id,
             None,
             di::TransformStyle::Flat,
             di::MixBlendMode::Normal,
             filters,
+            filter_datas,
             di::RasterSpace::Screen,
             /* cache_tiles = */ false,
         );
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(&di::SpecificDisplayItem::PopStackingContext);
     }
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/filter-component-transfer-ref.yaml
@@ -0,0 +1,21 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      items:
+        - type: rect
+          bounds: [0, 0, 50, 50]
+          color: [255, 0, 255, 1]
+        - type: rect
+          bounds: [0, 50, 50, 50]
+          color: [0, 255, 141, 1]
+        - type: rect
+          bounds: [0, 100, 50, 50]
+          color: [255, 255, 0, 1]
+        - type: rect
+          bounds: [0, 150, 50, 50]
+          color: [191, 128, 128, 1]
+        - type: rect
+          bounds: [0, 200, 50, 50]
+          color: [0, 255, 24, 1]
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/filters/filter-component-transfer.yaml
@@ -0,0 +1,118 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 50, 250]
+      items:
+        - type: stacking-context
+          bounds: [0, 0, 50, 50]
+          filters:
+            - component-transfer
+          filter-datas:
+            - - - Identity
+                - Identity
+                - Identity
+                - Identity
+              - []
+              - []
+              - []
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [255, 0, 255, 1]
+        - type: stacking-context
+          bounds: [0, 50, 50, 50]
+          filters:
+            - component-transfer
+          filter-datas:
+            - - - Table
+                - Table
+                - Table
+                - Identity
+              - - "1"
+                - "1"
+                - "0"
+                - "0"
+              - - "0"
+                - "0"
+                - "1"
+                - "1"
+              - - "0"
+                - "1"
+                - "1"
+                - "0"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [173, 255, 47, 1]
+        - type: stacking-context
+          bounds: [0, 100, 50, 50]
+          filters:
+            - component-transfer
+          filter-datas:
+            - - - Discrete
+                - Discrete
+                - Discrete
+                - Identity
+              - - "1"
+                - "1"
+                - "0"
+                - "0"
+              - - "0"
+                - "0"
+                - "1"
+                - "1"
+              - - "0"
+                - "1"
+                - "1"
+                - "0"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [0, 255, 255, 1]
+        - type: stacking-context
+          bounds: [0, 150, 50, 50]
+          filters:
+            - component-transfer
+          filter-datas:
+            - - - Linear
+                - Linear
+                - Linear
+                - Identity
+              - - "0.5"
+                - "0.25"
+              - - "0.5"
+                - "0"
+              - - "0.5"
+                - "0.5"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [255, 255, 0, 1]
+        - type: stacking-context
+          bounds: [0, 200, 50, 50]
+          filters:
+            - component-transfer
+          filter-datas:
+            - - - Gamma
+                - Gamma
+                - Gamma
+                - Identity
+              - - "2"
+                - "5"
+                - "-1"
+              - - "2"
+                - "3"
+                - "0"
+              - - "2"
+                - "1"
+                - "-1.75"
+              - []
+          items:
+            - type: rect
+              bounds: [0, 0, 50, 50]
+              color: [135, 206, 235, 1]
--- a/gfx/wr/wrench/reftests/filters/reftest.list
+++ b/gfx/wr/wrench/reftests/filters/reftest.list
@@ -4,16 +4,17 @@ platform(linux,mac) == draw_calls(6) col
 == invisible.yaml invisible-ref.yaml
 color_targets(1) alpha_targets(0) == opacity.yaml opacity-ref.yaml
 color_targets(1) alpha_targets(0) == opacity-combined.yaml opacity-combined-ref.yaml
 == opacity-overlap.yaml opacity-overlap-ref.yaml
 == filter-brightness.yaml filter-brightness-ref.yaml
 == filter-brightness-2.yaml filter-brightness-2-ref.yaml
 == filter-brightness-3.yaml filter-brightness-3-ref.yaml
 == filter-brightness-4.yaml filter-brightness-4-ref.yaml
+== filter-component-transfer.yaml filter-component-transfer-ref.yaml
 == filter-color-matrix.yaml filter-color-matrix-ref.yaml
 == filter-contrast-gray-alpha-1.yaml filter-contrast-gray-alpha-1-ref.yaml
 == filter-invert.yaml filter-invert-ref.yaml
 == filter-invert-2.yaml filter-invert-2-ref.yaml
 platform(linux,mac) fuzzy(1,133) == filter-large-blur-radius.yaml filter-large-blur-radius.png
 == draw_calls(4) color_targets(4) alpha_targets(0) filter-small-blur-radius.yaml filter-small-blur-radius.png
 == filter-saturate-red-1.yaml filter-saturate-red-1-ref.yaml
 == filter-saturate-red-2.yaml filter-saturate-red-2-ref.yaml
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -1820,27 +1820,29 @@ impl YamlFrameReader {
         if is_root {
             if let Some(size) = yaml["scroll-offset"].as_point() {
                 let external_id = ExternalScrollId(0, dl.pipeline_id);
                 self.scroll_offsets.insert(external_id, LayoutPoint::new(size.x, size.y));
             }
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
+        let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or(vec![]);
 
         info.rect = bounds;
         info.clip_rect = bounds;
 
         dl.push_stacking_context(
             &info,
             *self.spatial_id_stack.last().unwrap(),
             clip_node_id,
             transform_style,
             mix_blend_mode,
             &filters,
+            &filter_datas,
             raster_space,
             cache_tiles,
         );
 
         if !yaml["items"].is_badvalue() {
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
         }
 
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -212,16 +212,18 @@ fn write_reference_frame(
     usize_node(parent, "id", clip_id_mapper.add_spatial_id(reference_frame.id));
 }
 
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
+    filter_data_iter: &[TempFilterData],
+    display_list: &BuiltDisplayList,
 ) {
     enum_node(parent, "transform-style", sc.transform_style);
 
     let raster_space = match sc.raster_space {
         RasterSpace::Local(scale) => {
             format!("local({})", scale)
         }
         RasterSpace::Screen => {
@@ -261,20 +263,60 @@ fn write_stacking_context(
                 filters.push(Yaml::String(format!("color-matrix({:?})", matrix)))
             }
             FilterOp::SrgbToLinear => {
                 filters.push(Yaml::String("srgb-to-linear".to_string()))
             }
             FilterOp::LinearToSrgb => {
                 filters.push(Yaml::String("linear-to-srgb".to_string()))
             }
+            FilterOp::ComponentTransfer => {
+                filters.push(Yaml::String("component-transfer".to_string()))
+            }
         }
     }
 
     yaml_node(parent, "filters", Yaml::Array(filters));
+
+    // filter datas
+    let mut filter_datas = vec![];
+    for filter_data in filter_data_iter {
+        let func_types = display_list.get(filter_data.func_types).map(|func_type| {
+            match func_type {
+                ComponentTransferFuncType::Identity => { Yaml::String("Identity".to_string()) }
+                ComponentTransferFuncType::Table => { Yaml::String("Table".to_string()) }
+                ComponentTransferFuncType::Discrete => { Yaml::String("Discrete".to_string()) }
+                ComponentTransferFuncType::Linear => { Yaml::String("Linear".to_string()) }
+                ComponentTransferFuncType::Gamma => { Yaml::String("Gamma".to_string()) }
+            }
+        }).collect();
+        let r_values = display_list.get(filter_data.r_values).map(|value| {
+            Yaml::String(format!("{}", value))
+        }).collect();
+        let g_values = display_list.get(filter_data.g_values).map(|value| {
+            Yaml::String(format!("{}", value))
+        }).collect();
+        let b_values = display_list.get(filter_data.b_values).map(|value| {
+            Yaml::String(format!("{}", value))
+        }).collect();
+        let a_values = display_list.get(filter_data.a_values).map(|value| {
+            Yaml::String(format!("{}", value))
+        }).collect();
+
+        let avec: Vec<Yaml> = [
+            Yaml::Array(func_types),
+            Yaml::Array(r_values),
+            Yaml::Array(g_values),
+            Yaml::Array(b_values),
+            Yaml::Array(a_values),
+        ].to_vec();
+        filter_datas.push(Yaml::Array(avec));
+    }
+
+    yaml_node(parent, "filter-datas", Yaml::Array(filter_datas));
 }
 
 #[cfg(target_os = "macos")]
 fn native_font_handle_to_yaml(
     rsrc: &mut ResourceGenerator,
     handle: &NativeFontHandle,
     parent: &mut yaml_rust::yaml::Hash,
     path_opt: &mut Option<PathBuf>,
@@ -1029,16 +1071,18 @@ impl YamlFrameWriter {
                 Sdi::PushStackingContext(item) => {
                     str_node(&mut v, "type", "stacking-context");
                     let filters = display_list.get(base.filters());
                     write_stacking_context(
                         &mut v,
                         &item.stacking_context,
                         &scene.properties,
                         filters,
+                        base.filter_datas(),
+                        display_list,
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
                 Sdi::PushReferenceFrame(item) => {
                     str_node(&mut v, "type", "reference-frame");
@@ -1147,16 +1191,18 @@ impl YamlFrameWriter {
                 Sdi::PopStackingContext => return,
 
                 Sdi::PopCacheMarker => return,
                 Sdi::PushCacheMarker(_) => {
                     str_node(&mut v, "type", "cache-marker");
                 }
 
                 Sdi::SetGradientStops => panic!("dummy item yielded?"),
+                Sdi::SetFilterOps => panic!("dummy item yielded?"),
+                Sdi::SetFilterData => panic!("dummy item yielded?"),
                 Sdi::PushShadow(shadow) => {
                     str_node(&mut v, "type", "shadow");
                     vector_node(&mut v, "offset", &shadow.offset);
                     color_node(&mut v, "color", shadow.color);
                     f32_node(&mut v, "blur-radius", shadow.blur_radius);
                 }
                 Sdi::PopAllShadows => {
                     str_node(&mut v, "type", "pop-all-shadows");
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -31,16 +31,18 @@ pub trait YamlHelper {
     fn as_border_radius_component(&self) -> LayoutSize;
     fn as_border_radius(&self) -> Option<BorderRadius>;
     fn as_transform_style(&self) -> Option<TransformStyle>;
     fn as_raster_space(&self) -> Option<RasterSpace>;
     fn as_clip_mode(&self) -> Option<ClipMode>;
     fn as_mix_blend_mode(&self) -> Option<MixBlendMode>;
     fn as_filter_op(&self) -> Option<FilterOp>;
     fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>;
+    fn as_filter_data(&self) -> Option<FilterData>;
+    fn as_vec_filter_data(&self) -> Option<Vec<FilterData>>;
 }
 
 fn string_to_color(color: &str) -> Option<ColorF> {
     match color {
         "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
         "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
         "blue" => Some(ColorF::new(0.0, 0.0, 1.0, 1.0)),
         "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
@@ -134,16 +136,27 @@ define_string_enum!(
         Dotted = "dotted",
         Dashed = "dashed",
         Wavy = "wavy"
     ]
 );
 
 define_string_enum!(ClipMode, [Clip = "clip", ClipOut = "clip-out"]);
 
+define_string_enum!(
+    ComponentTransferFuncType,
+    [
+        Identity = "Identity",
+        Table = "Table",
+        Discrete = "Discrete",
+        Linear = "Linear",
+        Gamma = "Gamma"
+    ]
+);
+
 // Rotate around `axis` by `degrees` angle
 fn make_rotation(
     origin: &LayoutPoint,
     degrees: f32,
     axis_x: f32,
     axis_y: f32,
     axis_z: f32,
 ) -> LayoutTransform {
@@ -543,16 +556,19 @@ impl YamlHelper for Yaml {
     }
 
     fn as_filter_op(&self) -> Option<FilterOp> {
         if let Some(s) = self.as_str() {
             match parse_function(s) {
                 ("identity", _, _) => {
                     Some(FilterOp::Identity)
                 }
+                ("component-transfer", _, _) => {
+                    Some(FilterOp::ComponentTransfer)
+                }
                 ("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()))
@@ -601,9 +617,57 @@ impl YamlHelper for Yaml {
 
     fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>> {
         if let Some(v) = self.as_vec() {
             Some(v.iter().map(|x| x.as_filter_op().unwrap()).collect())
         } else {
             self.as_filter_op().map(|op| vec![op])
         }
     }
+
+    fn as_filter_data(&self) -> Option<FilterData> {
+        // Parse an array with five entries. First entry is an array of func types (4).
+        // The remaining entries are arrays of floats.
+        if let Yaml::Array(ref array) = *self {
+            if array.len() != 5 {
+                panic!("Invalid filter data specified, base array doesn't have five entries: {:?}", self);
+            }
+            if let Some(func_types_p) = array[0].as_vec_string() {
+                if func_types_p.len() != 4 {
+                    panic!("Invalid filter data specified, func type array doesn't have five entries: {:?}", self);
+                }
+                let func_types: Vec<ComponentTransferFuncType> =
+                    func_types_p.into_iter().map(|x| { match StringEnum::from_str(&x) {
+                        Some(y) => y,
+                        None => panic!("Invalid filter data specified, invalid func type name: {:?}", self),
+                    }}).collect();
+                if let Some(r_values_p) = array[1].as_vec_f32() {
+                    if let Some(g_values_p) = array[2].as_vec_f32() {
+                        if let Some(b_values_p) = array[3].as_vec_f32() {
+                            if let Some(a_values_p) = array[4].as_vec_f32() {
+                                let filter_data = FilterData {
+                                    func_r_type: func_types[0],
+                                    r_values: r_values_p,
+                                    func_g_type: func_types[1],
+                                    g_values: g_values_p,
+                                    func_b_type: func_types[2],
+                                    b_values: b_values_p,
+                                    func_a_type: func_types[3],
+                                    a_values: a_values_p,
+                                };
+                                return Some(filter_data)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        None
+    }
+
+    fn as_vec_filter_data(&self) -> Option<Vec<FilterData>> {
+        if let Some(v) = self.as_vec() {
+            Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect())
+        } else {
+            self.as_filter_data().map(|data| vec![data])
+        }
+    }
 }
--- a/js/src/gc/Memory.cpp
+++ b/js/src/gc/Memory.cpp
@@ -267,95 +267,86 @@ static inline uint64_t GetNumberInRange(
     } while (!result);
     rndNum = result.value() / binSize;
   } while (rndNum > maxNum);
 
   return minNum + rndNum;
 }
 
 #  ifndef XP_WIN
+static inline uint64_t FindAddressLimitInner(size_t highBit, size_t tries);
+
 /*
  * The address range available to applications depends on both hardware and
  * kernel configuration. For example, AArch64 on Linux uses addresses with
  * 39 significant bits by default, but can be configured to use addresses with
  * 48 significant bits by enabling a 4th translation table. Unfortunately,
  * there appears to be no standard way to query the limit at runtime
  * (Windows exposes this via GetSystemInfo()).
  *
  * This function tries to find the address limit by performing a binary search
  * on the index of the most significant set bit in the addresses it attempts to
  * allocate. As the requested address is often treated as a hint by the
  * operating system, we use the actual returned addresses to narrow the range.
  * We return the number of bits of an address that may be set.
  */
 static size_t FindAddressLimit() {
+  // Use 32 bits as a lower bound in case we keep getting nullptr.
+  uint64_t low = 31;
+  uint64_t highestSeen = (UINT64_C(1) << 32) - allocGranularity - 1;
+
+  // Exclude 48-bit and 47-bit addresses first.
+  uint64_t high = 47;
+  for (; high >= std::max(low, UINT64_C(46)); --high) {
+    highestSeen = std::max(FindAddressLimitInner(high, 4), highestSeen);
+    low = mozilla::FloorLog2(highestSeen);
+  }
+  // If those didn't work, perform a modified binary search.
+  while (high - 1 > low) {
+    uint64_t middle = low + (high - low) / 2;
+    highestSeen = std::max(FindAddressLimitInner(middle, 4), highestSeen);
+    low = mozilla::FloorLog2(highestSeen);
+    if (highestSeen < (UINT64_C(1) << middle)) {
+      high = middle;
+    }
+  }
+  // We can be sure of the lower bound, but check the upper bound again.
+  do {
+    high = low + 1;
+    highestSeen = std::max(FindAddressLimitInner(high, 8), highestSeen);
+    low = mozilla::FloorLog2(highestSeen);
+  } while (low >= high);
+
+  // `low` is the highest set bit, so `low + 1` is the number of bits.
+  return low + 1;
+}
+
+static inline uint64_t FindAddressLimitInner(size_t highBit, size_t tries) {
   const size_t length = allocGranularity;  // Used as both length and alignment.
 
-  void* address;
-  uint64_t startRaw, endRaw, start, end, desired, actual;
-
-  // Use 32 bits as a lower bound in case we keep getting nullptr.
-  size_t low = 31;
-  uint64_t highestSeen = (UINT64_C(1) << 32) - length - 1;
-
-  // Start with addresses that have bit 47 set.
-  size_t high = 47;
-  startRaw = UINT64_C(1) << high;
-  endRaw = 2 * startRaw - length - 1;
-  start = (startRaw + length - 1) / length;
-  end = (endRaw - (length - 1)) / length;
-
-  for (size_t tries = 0; tries < 4; ++tries) {
-    desired = length * GetNumberInRange(start, end);
-    address = MapMemoryAtFuzzy(reinterpret_cast<void*>(desired), length);
-    actual = uint64_t(address);
+  uint64_t highestSeen = 0;
+  uint64_t startRaw = UINT64_C(1) << highBit;
+  uint64_t endRaw = 2 * startRaw - length - 1;
+  uint64_t start = (startRaw + length - 1) / length;
+  uint64_t end = (endRaw - (length - 1)) / length;
+  for (size_t i = 0; i < tries; ++i) {
+    uint64_t desired = length * GetNumberInRange(start, end);
+    void* address = MapMemoryAtFuzzy(reinterpret_cast<void*>(desired), length);
+    uint64_t actual = uint64_t(address);
     if (address) {
       UnmapInternal(address, length);
     }
-    if (actual >= startRaw) {
-      return high + 1;  // Return early and skip the binary search.
-    }
     if (actual > highestSeen) {
       highestSeen = actual;
-      low = mozilla::FloorLog2(highestSeen);
-    }
-  }
-
-  // Those didn't work, so perform a binary search.
-  while (high - 1 > low) {
-    size_t middle = low + (high - low) / 2;
-    startRaw = UINT64_C(1) << middle;
-    endRaw = 2 * startRaw - length - 1;
-    start = (startRaw + length - 1) / length;
-    end = (endRaw - (length - 1)) / length;
-
-    for (size_t tries = 0; tries < 4; ++tries) {
-      desired = length * GetNumberInRange(start, end);
-      address = MapMemoryAtFuzzy(reinterpret_cast<void*>(desired), length);
-      actual = uint64_t(address);
-      if (address) {
-        UnmapInternal(address, length);
-      }
-      if (actual > highestSeen) {
-        highestSeen = actual;
-        low = mozilla::FloorLog2(highestSeen);
-      }
       if (actual >= startRaw) {
         break;
       }
     }
-
-    // Low was already updated above, so just check if we need to update high.
-    if (actual < startRaw) {
-      high = middle;
-    }
   }
-
-  // High was excluded, so use low (but sanity check it).
-  return std::min(low + 1, size_t(47));
+  return highestSeen;
 }
 #  endif  // !defined(XP_WIN)
 
 #endif  // defined(JS_64BIT)
 
 void InitMemorySubsystem() {
   if (pageSize == 0) {
 #ifdef XP_WIN
--- a/js/src/jit-test/tests/wasm/gc/TypedObject.js
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // We can read the object fields from JS, and write them if they are mutable.
 
 {
     let ins = wasmEvalText(`(module
                              (gc_feature_opt_in 3)
 
                              (type $p (struct (field f64) (field (mut i32))))
--- a/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // Moving a JS value through a wasm anyref is a pair of boxing/unboxing
 // conversions that leaves the value unchanged.  There are many cases,
 // along these axes:
 //
 //  - global variables, see anyref-boxing.js
 //  - tables, see anyref-boxing.js
 //  - function parameters and returns, see anyref-boxing.js
--- a/js/src/jit-test/tests/wasm/gc/ref-global.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-global.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // Basic private-to-module functionality.  At the moment all we have is null
 // pointers, not very exciting.
 
 {
     let bin = wasmTextToBinary(
         `(module
           (gc_feature_opt_in 3)
--- a/js/src/jit-test/tests/wasm/gc/ref-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // We'll be running some binary-format tests shortly.
 
 load(libdir + "wasm-binary.js");
 
 const v2vSigSection = sigSection([{args:[], ret:VoidCode}]);
 
 function checkInvalid(body, errorMessage) {
--- a/js/src/jit-test/tests/wasm/gc/structs.js
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 var conf = getBuildConfiguration();
 
 var bin = wasmTextToBinary(
     `(module
       (gc_feature_opt_in 3)
 
       (table 2 anyfunc)
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGeneralizedTables() || !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGeneralizedTables() || !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // table.set in bounds with i32 x anyref - works, no value generated
 // table.set with (ref T) - works
 // table.set with null - works
 // table.set out of bounds - fails
 
 {
     let ins = wasmEvalText(
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5701,17 +5701,19 @@ void CodeGenerator::emitAssertGCThingRes
 
     masm.assumeUnreachable(
         "MIR instruction returned object with unexpected type");
 
     masm.bind(&ok);
   }
 
   // Check that we have a valid GC pointer.
-  if (JitOptions.fullDebugChecks) {
+  // Disable for wasm because we don't have a context on wasm compilation threads
+  // and this needs a context.
+  if (JitOptions.fullDebugChecks && !IsCompilingWasm()) {
     saveVolatile();
     masm.setupUnalignedABICall(temp);
     masm.loadJSContext(temp);
     masm.passABIArg(temp);
     masm.passABIArg(input);
 
     void* callee;
     switch (type) {
@@ -7386,16 +7388,20 @@ void CodeGenerator::visitWasmStoreSlot(L
   }
 }
 
 void CodeGenerator::visitWasmDerivedPointer(LWasmDerivedPointer* ins) {
   masm.movePtr(ToRegister(ins->base()), ToRegister(ins->output()));
   masm.addPtr(Imm32(int32_t(ins->offset())), ToRegister(ins->output()));
 }
 
+void CodeGenerator::visitWasmLoadRef(LWasmLoadRef* lir) {
+  masm.loadPtr(Address(ToRegister(lir->ptr()), 0), ToRegister(lir->output()));
+}
+
 void CodeGenerator::visitWasmStoreRef(LWasmStoreRef* ins) {
   Register tls = ToRegister(ins->tls());
   Register valueAddr = ToRegister(ins->valueAddr());
   Register value = ToRegister(ins->value());
   Register temp = ToRegister(ins->temp());
 
   Label skipPreBarrier;
   wasm::EmitWasmPreBarrierGuard(masm, tls, temp, valueAddr, &skipPreBarrier);
@@ -13681,13 +13687,22 @@ void CodeGenerator::emitIonToWasmCallBas
 
 void CodeGenerator::visitIonToWasmCall(LIonToWasmCall* lir) {
   emitIonToWasmCallBase(lir);
 }
 void CodeGenerator::visitIonToWasmCallV(LIonToWasmCallV* lir) {
   emitIonToWasmCallBase(lir);
 }
 
+void CodeGenerator::visitWasmNullConstant(LWasmNullConstant* lir) {
+  masm.xorPtr(ToRegister(lir->output()), ToRegister(lir->output()));
+}
+
+void CodeGenerator::visitIsNullPointer(LIsNullPointer* lir) {
+  masm.cmpPtrSet(Assembler::Equal, ToRegister(lir->value()), ImmWord(0),
+                 ToRegister(lir->output()));
+}
+
 static_assert(!std::is_polymorphic<CodeGenerator>::value,
               "CodeGenerator should not have any virtual methods");
 
 }  // namespace jit
 }  // namespace js
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -523,16 +523,17 @@ class LDefinition {
         static_assert(sizeof(bool) <= sizeof(int32_t),
                       "bool doesn't fit in an int32 slot");
         return LDefinition::INT32;
       case MIRType::String:
       case MIRType::Symbol:
       case MIRType::BigInt:
       case MIRType::Object:
       case MIRType::ObjectOrNull:
+      case MIRType::RefOrNull:
         return LDefinition::OBJECT;
       case MIRType::Double:
         return LDefinition::DOUBLE;
       case MIRType::Float32:
         return LDefinition::FLOAT32;
 #if defined(JS_PUNBOX64)
       case MIRType::Value:
         return LDefinition::BOX;
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1007,21 +1007,22 @@ void LIRGenerator::visitCompare(MCompare
     MOZ_ASSERT(right->type() == MIRType::Boolean);
 
     LCompareB* lir =
         new (alloc()) LCompareB(useBox(left), useRegisterOrConstant(right));
     define(lir, comp);
     return;
   }
 
-  // Compare Int32, Symbol or Object pointers.
+  // Compare Int32, Symbol, Object or Wasm pointers.
   if (comp->isInt32Comparison() ||
       comp->compareType() == MCompare::Compare_UInt32 ||
       comp->compareType() == MCompare::Compare_Object ||
-      comp->compareType() == MCompare::Compare_Symbol) {
+      comp->compareType() == MCompare::Compare_Symbol ||
+      comp->compareType() == MCompare::Compare_RefOrNull) {
     JSOp op = ReorderComparison(comp->jsop(), &left, &right);
     LAllocation lhs = useRegister(left);
     LAllocation rhs;
     if (comp->isInt32Comparison() ||
         comp->compareType() == MCompare::Compare_UInt32) {
       rhs = useAnyOrConstant(right);
     } else {
       rhs = useRegister(right);
@@ -4380,16 +4381,21 @@ void LIRGenerator::visitWasmStoreGlobalC
   }
 }
 
 void LIRGenerator::visitWasmDerivedPointer(MWasmDerivedPointer* ins) {
   LAllocation base = useRegisterAtStart(ins->base());
   define(new (alloc()) LWasmDerivedPointer(base), ins);
 }
 
+void LIRGenerator::visitWasmLoadRef(MWasmLoadRef* ins) {
+  define(new (alloc()) LWasmLoadRef(useRegisterAtStart(ins->getOperand(0))),
+         ins);
+}
+
 void LIRGenerator::visitWasmStoreRef(MWasmStoreRef* ins) {
   LAllocation tls = useRegister(ins->tls());
   LAllocation valueAddr = useFixed(ins->valueAddr(), PreBarrierReg);
   LAllocation value = useRegister(ins->value());
   add(new (alloc()) LWasmStoreRef(tls, valueAddr, value, temp()), ins);
 }
 
 void LIRGenerator::visitWasmParameter(MWasmParameter* ins) {
@@ -4414,17 +4420,17 @@ void LIRGenerator::visitWasmParameter(MW
 #if defined(JS_NUNBOX32)
         LInt64Allocation(LArgument(abi.offsetFromArgBase() + INT64HIGH_OFFSET),
                          LArgument(abi.offsetFromArgBase() + INT64LOW_OFFSET))
 #else
         LInt64Allocation(LArgument(abi.offsetFromArgBase()))
 #endif
     );
   } else {
-    MOZ_ASSERT(IsNumberType(ins->type()));
+    MOZ_ASSERT(IsNumberType(ins->type()) || ins->type() == MIRType::RefOrNull);
     defineFixed(new (alloc()) LWasmParameter, ins,
                 LArgument(abi.offsetFromArgBase()));
   }
 }
 
 void LIRGenerator::visitWasmReturn(MWasmReturn* ins) {
   MDefinition* rval = ins->getOperand(0);
 
@@ -4433,17 +4439,18 @@ void LIRGenerator::visitWasmReturn(MWasm
     return;
   }
 
   LWasmReturn* lir = new (alloc()) LWasmReturn;
   if (rval->type() == MIRType::Float32) {
     lir->setOperand(0, useFixed(rval, ReturnFloat32Reg));
   } else if (rval->type() == MIRType::Double) {
     lir->setOperand(0, useFixed(rval, ReturnDoubleReg));
-  } else if (rval->type() == MIRType::Int32) {
+  } else if (rval->type() == MIRType::Int32 ||
+             rval->type() == MIRType::RefOrNull) {
     lir->setOperand(0, useFixed(rval, ReturnReg));
   } else {
     MOZ_CRASH("Unexpected wasm return type");
   }
 
   add(lir);
 }
 
@@ -4740,16 +4747,25 @@ void LIRGenerator::visitConstant(MConsta
       break;
     default:
       // Constants of special types (undefined, null) should never flow into
       // here directly. Operations blindly consuming them require a Box.
       MOZ_CRASH("unexpected constant type");
   }
 }
 
+void LIRGenerator::visitWasmNullConstant(MWasmNullConstant* ins) {
+  define(new (alloc()) LWasmNullConstant(), ins);
+}
+
+void LIRGenerator::visitIsNullPointer(MIsNullPointer* ins) {
+  define(new (alloc()) LIsNullPointer(
+           useRegisterAtStart(ins->getOperand(0))), ins);
+}
+
 void LIRGenerator::visitWasmFloatConstant(MWasmFloatConstant* ins) {
   switch (ins->type()) {
     case MIRType::Double:
       define(new (alloc()) LDouble(ins->toDouble()), ins);
       break;
     case MIRType::Float32:
       define(new (alloc()) LFloat32(ins->toFloat32()), ins);
       break;
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1240,16 +1240,20 @@ HashNumber MWasmFloatConstant::valueHash
   return ConstantValueHash(type(), u.bits_);
 }
 
 bool MWasmFloatConstant::congruentTo(const MDefinition* ins) const {
   return ins->isWasmFloatConstant() && type() == ins->type() &&
          u.bits_ == ins->toWasmFloatConstant()->u.bits_;
 }
 
+HashNumber MWasmNullConstant::valueHash() const {
+  return ConstantValueHash(MIRType::Pointer, 0);
+}
+
 #ifdef JS_JITSPEW
 void MControlInstruction::printOpcode(GenericPrinter& out) const {
   MDefinition::printOpcode(out);
   for (size_t j = 0; j < numSuccessors(); j++) {
     if (getSuccessor(j)) {
       out.printf(" block%u", getSuccessor(j)->id());
     } else {
       out.printf(" (null-to-be-patched)");
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -344,20 +344,21 @@ class AliasSet {
     DOMProperty = 1 << 5,     // A DOM property
     FrameArgument = 1 << 6,   // An argument kept on the stack frame
     WasmGlobalVar = 1 << 7,   // An asm.js/wasm private global var
     WasmHeap = 1 << 8,        // An asm.js/wasm heap load
     WasmHeapMeta = 1 << 9,    // The asm.js/wasm heap base pointer and
                               // bounds check limit, in Tls.
     TypedArrayLengthOrOffset = 1 << 10,  // A typed array's length or byteOffset
     WasmGlobalCell = 1 << 11,            // A wasm global cell
-    Last = WasmGlobalCell,
+    WasmTableElement = 1 << 12,          // An element of a wasm table
+    Last = WasmTableElement,
     Any = Last | (Last - 1),
 
-    NumCategories = 12,
+    NumCategories = 13,
 
     // Indicates load or store.
     Store_ = 1 << 31
   };
 
   static_assert((1 << NumCategories) - 1 == Any,
                 "NumCategories must include all flags present in Any");
 
@@ -1543,16 +1544,60 @@ class MConstant : public MNullaryInstruc
   // Convert this constant to a js::Value. Float32 constants will be stored
   // as DoubleValue and NaNs are canonicalized. Callers must be careful: not
   // all constants can be represented by js::Value (wasm supports int64).
   Value toJSValue() const;
 
   bool appendRoots(MRootList& roots) const override;
 };
 
+class MWasmNullConstant : public MNullaryInstruction {
+  explicit MWasmNullConstant() : MNullaryInstruction(classOpcode) {
+    setResultType(MIRType::RefOrNull);
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(WasmNullConstant)
+
+  static MWasmNullConstant* New(TempAllocator& alloc) {
+    return new (alloc) MWasmNullConstant();
+  }
+
+  HashNumber valueHash() const override;
+  bool congruentTo(const MDefinition* ins) const override {
+    return ins->isWasmNullConstant();
+  }
+  AliasSet getAliasSet() const override { return AliasSet::None(); }
+
+  ALLOW_CLONE(MWasmNullConstant)
+};
+
+class MIsNullPointer : public MUnaryInstruction, public NoTypePolicy::Data {
+  explicit MIsNullPointer(MDefinition* value) : MUnaryInstruction(classOpcode,
+                                                                  value) {
+    MOZ_ASSERT(value->type() == MIRType::Pointer);
+    setResultType(MIRType::Boolean);
+    setMovable();
+  }
+ public:
+  INSTRUCTION_HEADER(IsNullPointer);
+
+  static MIsNullPointer* New(TempAllocator& alloc, MDefinition* value) {
+    return new (alloc) MIsNullPointer(value);
+  }
+
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  AliasSet getAliasSet() const override { return AliasSet::None(); }
+
+  ALLOW_CLONE(MIsNullPointer)
+};
+
 // Floating-point value as created by wasm. Just a constant value, used to
 // effectively inhibite all the MIR optimizations. This uses the same LIR nodes
 // as a MConstant of the same type would.
 class MWasmFloatConstant : public MNullaryInstruction {
   union {
     float f32_;
     double f64_;
     uint64_t bits_;
@@ -3131,16 +3176,19 @@ class MCompare : public MBinaryInstructi
     // Double    compared to String
     // Object    compared to String
     // Value     compared to String
     Compare_StrictString,
 
     // Object compared to Object
     Compare_Object,
 
+    // Wasm Ref/AnyRef/NullRef compared to Ref/AnyRef/NullRef
+    Compare_RefOrNull,
+
     // Compare 2 values bitwise
     Compare_Bitwise,
 
     // All other possible compares
     Compare_Unknown
   };
 
  private:
@@ -3165,17 +3213,18 @@ class MCompare : public MBinaryInstructi
     setMovable();
   }
 
   MCompare(MDefinition* left, MDefinition* right, JSOp jsop,
            CompareType compareType)
       : MCompare(left, right, jsop) {
     MOZ_ASSERT(compareType == Compare_Int32 || compareType == Compare_UInt32 ||
                compareType == Compare_Int64 || compareType == Compare_UInt64 ||
-               compareType == Compare_Double || compareType == Compare_Float32);
+               compareType == Compare_Double || compareType == Compare_Float32 ||
+               compareType == Compare_RefOrNull);
     compareType_ = compareType;
     operandMightEmulateUndefined_ = false;
     setResultType(MIRType::Int32);
   }
 
  public:
   INSTRUCTION_HEADER(Compare)
   TRIVIAL_NEW_WRAPPERS
@@ -11780,17 +11829,17 @@ class MWasmAtomicBinopHeap : public MVar
 
 class MWasmLoadGlobalVar : public MUnaryInstruction, public NoTypePolicy::Data {
   MWasmLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant,
                      MDefinition* tlsPtr)
       : MUnaryInstruction(classOpcode, tlsPtr),
         globalDataOffset_(globalDataOffset),
         isConstant_(isConstant) {
     MOZ_ASSERT(IsNumberType(type) || IsSimdType(type) ||
-               type == MIRType::Pointer);
+               type == MIRType::Pointer || type == MIRType::RefOrNull);
     setResultType(type);
     setMovable();
   }
 
   unsigned globalDataOffset_;
   bool isConstant_;
 
  public:
@@ -11899,23 +11948,48 @@ class MWasmDerivedPointer : public MUnar
 
   bool congruentTo(const MDefinition* ins) const override {
     return congruentIfOperandsEqual(ins);
   }
 
   ALLOW_CLONE(MWasmDerivedPointer)
 };
 
+class MWasmLoadRef : public MUnaryInstruction,
+                     public NoTypePolicy::Data {
+  AliasSet::Flag aliasSet_;
+
+  explicit MWasmLoadRef(MDefinition* valueAddr, AliasSet::Flag aliasSet)
+      : MUnaryInstruction(classOpcode, valueAddr) {
+    MOZ_ASSERT(valueAddr->type() == MIRType::Pointer);
+    setResultType(MIRType::RefOrNull);
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(WasmLoadRef)
+  TRIVIAL_NEW_WRAPPERS
+
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  AliasSet getAliasSet() const override { return AliasSet::Load(aliasSet_); }
+
+  ALLOW_CLONE(MWasmLoadRef)
+};
+
 class MWasmStoreRef : public MAryInstruction<3>,
                       public NoTypePolicy::Data {
   AliasSet::Flag aliasSet_;
 
   MWasmStoreRef(MDefinition* tls, MDefinition* valueAddr, MDefinition* value,
                 AliasSet::Flag aliasSet)
     : MAryInstruction<3>(classOpcode), aliasSet_(aliasSet) {
+    MOZ_ASSERT(valueAddr->type() == MIRType::Pointer);
+    MOZ_ASSERT(value->type() == MIRType::RefOrNull);
     initOperand(0, tls);
     initOperand(1, valueAddr);
     initOperand(2, value);
   }
 
  public:
   INSTRUCTION_HEADER(WasmStoreRef)
   TRIVIAL_NEW_WRAPPERS
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -484,17 +484,17 @@ MBasicBlock::MBasicBlock(MIRGraph& graph
       loopDepth_(0),
       kind_(kind),
       mark_(false),
       immediatelyDominated_(graph.alloc()),
       immediateDominator_(nullptr),
       trackedSite_(site),
       hitCount_(0),
       hitState_(HitState::NotDefined)
-#if defined(JS_ION_PERF)
+#if defined(JS_ION_PERF) || defined(DEBUG)
       ,
       lineno_(0u),
       columnIndex_(0u)
 #endif
 {
 }
 
 bool MBasicBlock::init() { return slots_.init(graph_.alloc(), info_.nslots()); }
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1777,17 +1777,17 @@ void CodeGenerator::visitAtomicTypedArra
 }
 
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   masm.as_cmp(cond, Imm8(0));
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register falseExpr = ToRegister(ins->falseExpr());
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
     masm.ma_mov(falseExpr, out, LeaveCC, Assembler::Zero);
     return;
   }
 
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -2188,17 +2188,17 @@ void CodeGenerator::visitWasmStackArgI64
 }
 
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   const LAllocation* falseExpr = ins->falseExpr();
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
     masm.as_movz(out, ToRegister(falseExpr), cond);
     return;
   }
 
   FloatRegister out = ToFloatRegister(ins->output());
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6722,16 +6722,27 @@ class LWasmDerivedPointer : public LInst
   explicit LWasmDerivedPointer(const LAllocation& base)
     : LInstructionHelper(classOpcode) {
     setOperand(0, base);
   }
   const LAllocation* base() { return getOperand(0); }
   size_t offset() { return mirRaw()->toWasmDerivedPointer()->offset(); }
 };
 
+class LWasmLoadRef : public LInstructionHelper<1, 1, 0> {
+ public:
+  LIR_HEADER(WasmLoadRef);
+  explicit LWasmLoadRef(const LAllocation& ptr)
+    : LInstructionHelper(classOpcode) {
+    setOperand(0, ptr);
+  }
+  MWasmLoadRef* mir() const { return mirRaw()->toWasmLoadRef(); }
+  const LAllocation* ptr() { return getOperand(0); }
+};
+
 class LWasmStoreRef : public LInstructionHelper<0, 3, 1> {
  public:
   LIR_HEADER(WasmStoreRef);
   LWasmStoreRef(const LAllocation& tls, const LAllocation& valueAddr,
                 const LAllocation& value, const LDefinition& temp)
     : LInstructionHelper(classOpcode) {
     setOperand(0, tls);
     setOperand(1, valueAddr);
@@ -6805,16 +6816,35 @@ class LWasmStackArgI64 : public LInstruc
   const LInt64Allocation arg() { return getInt64Operand(0); }
 };
 
 inline bool IsWasmCall(LNode::Opcode op) {
   return (op == LNode::Opcode::WasmCall || op == LNode::Opcode::WasmCallVoid ||
           op == LNode::Opcode::WasmCallI64);
 }
 
+class LWasmNullConstant : public LInstructionHelper<1, 0, 0> {
+ public:
+  LIR_HEADER(WasmNullConstant);
+  explicit LWasmNullConstant()
+      : LInstructionHelper(classOpcode) {
+  }
+};
+
+class LIsNullPointer : public LInstructionHelper<1, 1, 0> {
+ public:
+  LIR_HEADER(IsNullPointer);
+  explicit LIsNullPointer(const LAllocation& value)
+    : LInstructionHelper(classOpcode) {
+    setOperand(0, value);
+  }
+  MIsNullPointer* mir() const { return mirRaw()->toIsNullPointer(); }
+  const LAllocation* value() { return getOperand(0); }
+};
+
 template <size_t Defs>
 class LWasmCallBase : public LVariadicInstruction<Defs, 0> {
   using Base = LVariadicInstruction<Defs, 0>;
 
   bool needsBoundsCheck_;
 
  public:
   LWasmCallBase(LNode::Opcode opcode, uint32_t numOperands,
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -955,16 +955,19 @@ class Assembler : public AssemblerX86Sha
         masm.leaq_mr(src.disp(), src.base(), src.index(), src.scale(),
                      dest.encoding());
         break;
       default:
         MOZ_CRASH("unexepcted operand kind");
     }
   }
 
+  void cmovz32(const Operand& src, Register dest) { return cmovzl(src, dest); }
+  void cmovzPtr(const Operand& src, Register dest) { return cmovzq(src, dest); }
+
   CodeOffset loadRipRelativeInt32(Register dest) {
     return CodeOffset(masm.movl_ripr(dest.encoding()).offset());
   }
   CodeOffset loadRipRelativeInt64(Register dest) {
     return CodeOffset(masm.movq_ripr(dest.encoding()).offset());
   }
   CodeOffset loadRipRelativeDouble(FloatRegister dest) {
     return CodeOffset(masm.vmovsd_ripr(dest.encoding()).offset());
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -302,21 +302,25 @@ void CodeGenerator::visitWasmStackArgI64
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   Operand falseExpr = ToOperand(ins->falseExpr());
 
   masm.test32(cond, cond);
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
-    masm.cmovzl(falseExpr, out);
+    if (mirType == MIRType::Int32) {
+      masm.cmovz32(falseExpr, out);
+    } else {
+      masm.cmovzPtr(falseExpr, out);
+    }
     return;
   }
 
   FloatRegister out = ToFloatRegister(ins->output());
   MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out,
              "true expr input is reused for output");
 
   Label done;
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -347,16 +347,18 @@ class Assembler : public AssemblerX86Sha
   void mov(CodeLabel* label, Register dest) {
     // Put a placeholder value in the instruction stream.
     masm.movl_i32r(0, dest.encoding());
     label->patchAt()->bind(masm.size());
   }
   void mov(Register src, Register dest) { movl(src, dest); }
   void xchg(Register src, Register dest) { xchgl(src, dest); }
   void lea(const Operand& src, Register dest) { return leal(src, dest); }
+  void cmovz32(const Operand& src, Register dest) { return cmovzl(src, dest); }
+  void cmovzPtr(const Operand& src, Register dest) { return cmovzl(src, dest); }
 
   void fstp32(const Operand& src) {
     switch (src.kind()) {
       case Operand::MEM_REG_DISP:
         masm.fstp32_m(src.disp(), src.base());
         break;
       default:
         MOZ_CRASH("unexpected operand kind");
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -842,16 +842,21 @@ Instance::tableInit(Instance* instance, 
     }
   }
 
   JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                             JSMSG_WASM_OUT_OF_BOUNDS);
   return -1;
 }
 
+// The return convention for tableGet() is awkward but avoids a situation where
+// Ion code has to hold a value that may or may not be a pointer to GC'd
+// storage, or where Ion has to pass in a pointer to storage where a return
+// value can be written.
+
 /* static */ void* /* nullptr to signal trap; pointer to table location
                       otherwise */
 Instance::tableGet(Instance* instance, uint32_t index, uint32_t tableIndex) {
   const Table& table = *instance->tables()[tableIndex];
   MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
   if (index >= table.length()) {
     JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                               JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -142,16 +142,17 @@ class FunctionCompiler {
     if (!mirGen_.ensureBallast()) {
       return false;
     }
     if (!newBlock(/* prev */ nullptr, &curBlock_)) {
       return false;
     }
 
     for (ABIArgIter<ValTypeVector> i(args); !i.done(); i++) {
+      MOZ_ASSERT(i.mirType() != MIRType::Pointer);
       MWasmParameter* ins = MWasmParameter::New(alloc(), *i, i.mirType());
       curBlock_->add(ins);
       curBlock_->initSlot(info().localSlot(i.index()), ins);
       if (!mirGen_.ensureBallast()) {
         return false;
       }
     }
 
@@ -175,17 +176,17 @@ class FunctionCompiler {
         case ValType::F32:
           ins = MConstant::New(alloc(), Float32Value(0.f), MIRType::Float32);
           break;
         case ValType::F64:
           ins = MConstant::New(alloc(), DoubleValue(0.0), MIRType::Double);
           break;
         case ValType::Ref:
         case ValType::AnyRef:
-          MOZ_CRASH("ion support for ref/anyref value NYI");
+          ins = MWasmNullConstant::New(alloc());
           break;
         case ValType::NullRef:
           MOZ_CRASH("NullRef not expressible");
       }
 
       curBlock_->add(ins);
       curBlock_->initSlot(info().localSlot(i), ins);
       if (!mirGen_.ensureBallast()) {
@@ -259,16 +260,26 @@ class FunctionCompiler {
     if (inDeadCode()) {
       return nullptr;
     }
     MConstant* constant = MConstant::NewInt64(alloc(), i);
     curBlock_->add(constant);
     return constant;
   }
 
+  MDefinition* nullRefConstant() {
+    if (inDeadCode()) {
+      return nullptr;
+    }
+    // MConstant has a lot of baggage so we don't use that here.
+    MWasmNullConstant* constant = MWasmNullConstant::New(alloc());
+    curBlock_->add(constant);
+    return constant;
+  }
+
   template <class T>
   MDefinition* unary(MDefinition* op) {
     if (inDeadCode()) {
       return nullptr;
     }
     T* ins = T::New(alloc(), op);
     curBlock_->add(ins);
     return ins;
@@ -684,16 +695,48 @@ class FunctionCompiler {
 
     curBlock_->end(MTest::New(alloc(), cond, failBlock, okBlock));
     failBlock->end(
         MWasmTrap::New(alloc(), wasm::Trap::ThrowReported, bytecodeOffset()));
     curBlock_ = okBlock;
     return true;
   }
 
+  bool checkPointerNullMeansFailedResult(MDefinition* value) {
+    if (inDeadCode()) {
+      return true;
+    }
+
+    auto* cond = MIsNullPointer::New(alloc(), value);
+    curBlock_->add(cond);
+
+    MBasicBlock* failBlock;
+    if (!newBlock(curBlock_, &failBlock)) {
+      return false;
+    }
+
+    MBasicBlock* okBlock;
+    if (!newBlock(curBlock_, &okBlock)) {
+      return false;
+    }
+
+    curBlock_->end(MTest::New(alloc(), cond, failBlock, okBlock));
+    failBlock->end(
+        MWasmTrap::New(alloc(), wasm::Trap::ThrowReported, bytecodeOffset()));
+    curBlock_ = okBlock;
+    return true;
+  }
+
+  MDefinition* derefTableElementPointer(MDefinition* base) {
+    MWasmLoadRef* load =
+      MWasmLoadRef::New(alloc(), base, AliasSet::WasmTableElement);
+    curBlock_->add(load);
+    return load;
+  }
+
   MDefinition* load(MDefinition* base, MemoryAccessDesc* access,
                     ValType result) {
     if (inDeadCode()) {
       return nullptr;
     }
 
     MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
     MInstruction* load = nullptr;
@@ -1101,36 +1144,43 @@ class FunctionCompiler {
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
   bool builtinInstanceMethodCall(SymbolicAddress builtin,
                                  uint32_t lineOrBytecode,
-                                 const CallCompileState& call, ValType ret,
+                                 const CallCompileState& call, MIRType ret,
                                  MDefinition** def) {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
     auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(
-        alloc(), desc, builtin, call.instanceArg_, call.regArgs_,
-        ToMIRType(ret));
+        alloc(), desc, builtin, call.instanceArg_, call.regArgs_, ret);
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
+  bool builtinInstanceMethodCall(SymbolicAddress builtin,
+                                 uint32_t lineOrBytecode,
+                                 const CallCompileState& call, ValType ret,
+                                 MDefinition** def) {
+    return builtinInstanceMethodCall(builtin, lineOrBytecode, call,
+                                     ToMIRType(ret), def);
+  }
+
   /*********************************************** Control flow generation */
 
   inline bool inDeadCode() const { return curBlock_ == nullptr; }
 
   void returnExpr(MDefinition* operand) {
     if (inDeadCode()) {
       return;
     }
@@ -2111,16 +2161,20 @@ static bool EmitGetGlobal(FunctionCompil
       result = f.constant(int64_t(value.i64()));
       break;
     case ValType::F32:
       result = f.constant(value.f32());
       break;
     case ValType::F64:
       result = f.constant(value.f64());
       break;
+    case ValType::AnyRef:
+      MOZ_ASSERT(value.anyref().isNull());
+      result = f.nullRefConstant();
+      break;
     default:
       MOZ_CRASH("unexpected type in EmitGetGlobal");
   }
 
   f.iter().setResult(result);
   return true;
 }
 
@@ -3058,53 +3112,173 @@ static bool EmitMemOrTableInit(FunctionC
     return false;
   }
 
   return true;
 }
 #endif  // ENABLE_WASM_BULKMEM_OPS
 
 #ifdef ENABLE_WASM_GENERALIZED_TABLES
-// About these implementations: table.{get,grow,set} on table(anyfunc) is
-// rejected by the verifier, while table.{get,grow,set} on table(anyref)
-// requires gc_feature_opt_in and will always be handled by the baseline
-// compiler; we should never get here in that case.
-//
-// table.size must however be handled properly here.
+// Note, table.{get,grow,set} on table(anyfunc) are currently rejected by the
+// verifier.
 
 static bool EmitTableGet(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* index;
   if (!f.iter().readTableGet(&tableIndex, &index)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(index, ValType::I32, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  // The return value here is either null, denoting an error, or a pointer to an
+  // unmovable location containing a possibly-null ref.
+  MDefinition* result;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGet, lineOrBytecode,
+                                   args, MIRType::Pointer, &result)) {
+    return false;
+  }
+  if (!f.checkPointerNullMeansFailedResult(result)) {
+    return false;
+  }
+
+  MDefinition* ret = f.derefTableElementPointer(result);
+  if (!ret) {
+    return false;
+  }
+
+  f.iter().setResult(ret);
+  return true;
 }
 
 static bool EmitTableGrow(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* delta;
   MDefinition* initValue;
   if (!f.iter().readTableGrow(&tableIndex, &delta, &initValue)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(delta, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.passArg(initValue, ValType::AnyRef, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  MDefinition* ret;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGrow, lineOrBytecode,
+                                   args, ValType::I32, &ret)) {
+    return false;
+  }
+
+  f.iter().setResult(ret);
+  return true;
 }
 
 static bool EmitTableSet(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* index;
   MDefinition* value;
   if (!f.iter().readTableSet(&tableIndex, &index, &value)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(index, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.passArg(value, ValType::AnyRef, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  MDefinition* ret;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSet, lineOrBytecode,
+                                   args, ValType::I32, &ret)) {
+    return false;
+  }
+  if (!f.checkI32NegativeMeansFailedResult(ret)) {
+    return false;
+  }
+  return true;
 }
 
 static bool EmitTableSize(FunctionCompiler& f) {
   uint32_t tableIndex;
   if (!f.iter().readTableSize(&tableIndex)) {
     return false;
   }
 
@@ -3138,16 +3312,54 @@ static bool EmitTableSize(FunctionCompil
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 #endif  // ENABLE_WASM_GENERALIZED_TABLES
 
+#ifdef ENABLE_WASM_REFTYPES
+static bool EmitRefNull(FunctionCompiler& f) {
+  if (!f.iter().readRefNull()) {
+    return false;
+  }
+
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  MDefinition* nullVal = f.nullRefConstant();
+  if (!nullVal) {
+    return false;
+  }
+  f.iter().setResult(nullVal);
+  return true;
+}
+
+static bool EmitRefIsNull(FunctionCompiler& f) {
+  MDefinition* input;
+  if (!f.iter().readConversion(ValType::AnyRef, ValType::I32, &input)) {
+    return false;
+  }
+
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  MDefinition* nullVal = f.nullRefConstant();
+  if (!nullVal) {
+    return false;
+  }
+  f.iter().setResult(f.compare(input, nullVal, JSOP_EQ,
+                               MCompare::Compare_RefOrNull));
+  return true;
+}
+#endif
+
 static bool EmitBodyExprs(FunctionCompiler& f) {
   if (!f.iter().readFunctionStart(f.funcType().ret())) {
     return false;
   }
 
 #define CHECK(c)          \
   if (!(c)) return false; \
   break
@@ -3578,22 +3790,33 @@ static bool EmitBodyExprs(FunctionCompil
         CHECK(EmitReinterpret(f, ValType::I64, ValType::F64, MIRType::Int64));
       case uint16_t(Op::F32ReinterpretI32):
         CHECK(EmitReinterpret(f, ValType::F32, ValType::I32, MIRType::Float32));
       case uint16_t(Op::F64ReinterpretI64):
         CHECK(EmitReinterpret(f, ValType::F64, ValType::I64, MIRType::Double));
 
 #ifdef ENABLE_WASM_GC
       case uint16_t(Op::RefEq):
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitComparison(f, ValType::AnyRef, JSOP_EQ,
+                             MCompare::Compare_RefOrNull));
 #endif
 #ifdef ENABLE_WASM_REFTYPES
       case uint16_t(Op::RefNull):
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitRefNull(f));
       case uint16_t(Op::RefIsNull):
-        // Not yet supported
-        return f.iter().unrecognizedOpcode(&op);
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitRefIsNull(f));
 #endif
 
       // Sign extensions
       case uint16_t(Op::I32Extend8S):
         CHECK(EmitSignExtend(f, 1, 4));
       case uint16_t(Op::I32Extend16S):
         CHECK(EmitSignExtend(f, 2, 4));
       case uint16_t(Op::I64Extend8S):
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6069,17 +6069,17 @@ void PresShell::Paint(nsView* aViewToPai
 
   if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
     nsPresContext* pc = GetPresContext();
     LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
         pc->GetVisibleArea(), pc->AppUnitsPerDevPixel());
     bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
     WebRenderBackgroundData data(wr::ToLayoutRect(bounds),
                                  wr::ToColorF(ToDeviceColor(bgcolor)));
-    nsTArray<wr::FilterOp> wrFilters;
+    WrFiltersHolder wrFilters;
 
     MaybeSetupTransactionIdAllocator(layerManager, presContext);
     layerManager->AsWebRenderLayerManager()->EndTransactionWithoutLayer(
         nullptr, nullptr, std::move(wrFilters), &data);
     return;
   }
 
   RefPtr<ColorLayer> root = layerManager->CreateColorLayer();
--- a/layout/forms/nsHTMLButtonControlFrame.cpp
+++ b/layout/forms/nsHTMLButtonControlFrame.cpp
@@ -74,30 +74,29 @@ nsresult nsHTMLButtonControlFrame::Handl
 }
 
 bool nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox() {
   return IsInput() || StyleDisplay()->mOverflowX != StyleOverflow::Visible;
 }
 
 void nsHTMLButtonControlFrame::BuildDisplayList(
     nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
-  // Clip to our border area for event hit testing.
-  Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
-  const bool isForEventDelivery = aBuilder->IsForEventDelivery();
-  if (isForEventDelivery) {
-    eventClipState.emplace(aBuilder);
-    nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
-    nscoord radii[8];
-    bool hasRadii = GetBorderRadii(radii);
-    eventClipState->ClipContainingBlockDescendants(rect,
-                                                   hasRadii ? radii : nullptr);
-  }
-
   nsDisplayList onTop;
   if (IsVisibleForPainting()) {
+    // Clip the button itself to its border area for event hit testing.
+    Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
+    if (aBuilder->IsForEventDelivery()) {
+      eventClipState.emplace(aBuilder);
+      nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
+      nscoord radii[8];
+      bool hasRadii = GetBorderRadii(radii);
+      eventClipState->ClipContainingBlockDescendants(rect,
+          hasRadii ? radii : nullptr);
+    }
+
     mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
   }
 
   nsDisplayListCollection set(aBuilder);
 
   {
     DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
--- a/layout/forms/test/mochitest.ini
+++ b/layout/forms/test/mochitest.ini
@@ -60,10 +60,11 @@ skip-if = toolkit == 'android'
 [test_listcontrol_search.html]
 skip-if = toolkit == 'android' #select elements don't use an in-page popup on Android
 [test_select_prevent_default.html]
 [test_select_vertical.html]
 skip-if = e10s || toolkit == 'android' # Bug 1170129 - vertical <select> popup not implemented for e10s # <select> elements don't use an in-page popup on Android
 [test_textarea_resize.html]
 skip-if = toolkit == 'android'
 [test_bug1327129.html]
+[test_bug1529036.html]
 [test_readonly.html]
 [test_select_key_navigation_bug1498769.html]
new file mode 100644
--- /dev/null
+++ b/layout/forms/test/test_bug1529036.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1529036
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1529036</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+  </style>
+  <script type="application/javascript">
+
+  /** Test for Bug 1529036 **/
+
+  function doTest() {
+    SimpleTest.waitForExplicitFinish();
+
+    var clicks = 0;
+    var elms = document.querySelectorAll('.click');
+    for (var i = 0; i < elms.length; ++i) {
+      var e = elms[i];
+      e.addEventListener('click', function(event) {
+        ++clicks;
+      });
+    }
+
+    var elms = document.querySelectorAll('.click.hit');
+    for (var i = 0; i < elms.length; ++i) {
+      var e = elms[i];
+      let r = e.getBoundingClientRect();
+      synthesizeMouse(e, 50, 50, {});
+    }
+    is(clicks, elms.length, "click events on overflow");
+
+    clicks = 0;
+    elms = document.querySelectorAll('.click.nohit');
+    for (var i = 0; i < elms.length; ++i) {
+      var e = elms[i];
+      let r = e.getBoundingClientRect();
+      synthesizeMouse(e, 50, 50, {});
+    }
+    is(clicks, 0, "click events on clipped overflow");
+
+    SimpleTest.finish();
+  }
+  </script>
+</head>
+<body onload="SimpleTest.waitForFocus(doTest, window)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1529036">Mozilla Bug 1529036</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<div style="border: 1px solid">
+<button class="click hit" id="t1" style="width:10px; height:10px; padding:20px; border-radius:50%"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+<button class="click hit" id="t2" style="width:10px; height:10px; padding:20px; border:1px solid"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+<button class="click hit" id="t3" style="width:10px; height:10px; border-width:20px; border-radius:50%"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+<button class="click hit" id="t4" style="width:10px; height:10px; border:20px solid"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+<button class="click nohit" id="t5" style="width:10px; height:10px; padding:20px; overflow:hidden; border-radius:50%"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+<button class="click nohit" id="t6" style="width:10px; height:10px; padding:20px; overflow:hidden; border:1px solid"><div>button<br>button<br>button<br>button<br>button<br></div></button>
+</div>
+
+</body>
+</html>
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2599,21 +2599,21 @@ already_AddRefed<LayerManager> nsDisplay
         }
         // This must be called even if PluginGeometryUpdates were not computed.
         rootPresContext->CollectPluginGeometryUpdates(layerManager);
       }
 
       auto* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());
 
       nsIDocShell* docShell = presContext->GetDocShell();
-      nsTArray<wr::FilterOp> wrFilters;
+      WrFiltersHolder wrFilters;
       gfx::Matrix5x4* colorMatrix =
           nsDocShell::Cast(docShell)->GetColorMatrix();
       if (colorMatrix) {
-        wrFilters.AppendElement(
+        wrFilters.filters.AppendElement(
             wr::FilterOp::ColorMatrix(colorMatrix->components));
       }
 
       wrManager->EndTransactionWithoutLayer(this, aBuilder,
                                             std::move(wrFilters));
     }
 
     // For layers-free mode, we check the invalidation state bits in the
@@ -9316,70 +9316,70 @@ void nsDisplayFilters::PaintAsLayer(nsDi
 }
 
 static float ClampStdDeviation(float aStdDeviation) {
   // Cap software blur radius for performance reasons.
   return std::min(std::max(0.0f, aStdDeviation), 100.0f);
 }
 
 bool nsDisplayFilters::CreateWebRenderCSSFilters(
-    nsTArray<mozilla::wr::FilterOp>& wrFilters) {
+    WrFiltersHolder& wrFilters) {
   // All CSS filters are supported by WebRender. SVG filters are not fully
   // supported, those use NS_STYLE_FILTER_URL and are handled separately.
   const nsTArray<nsStyleFilter>& filters = mFrame->StyleEffects()->mFilters;
 
   // If there are too many filters to render, then just pretend that we
   // succeeded, and don't render any of them.
   if (filters.Length() > gfxPrefs::WebRenderMaxFilterOpsPerChain()) {
     return true;
   }
-  wrFilters.SetCapacity(filters.Length());
+  wrFilters.filters.SetCapacity(filters.Length());
 
   for (const nsStyleFilter& filter : filters) {
     switch (filter.GetType()) {
       case NS_STYLE_FILTER_BRIGHTNESS:
-        wrFilters.AppendElement(wr::FilterOp::Brightness(
+        wrFilters.filters.AppendElement(wr::FilterOp::Brightness(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       case NS_STYLE_FILTER_CONTRAST:
-        wrFilters.AppendElement(wr::FilterOp::Contrast(
+        wrFilters.filters.AppendElement(wr::FilterOp::Contrast(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       case NS_STYLE_FILTER_GRAYSCALE:
-        wrFilters.AppendElement(wr::FilterOp::Grayscale(
+        wrFilters.filters.AppendElement(wr::FilterOp::Grayscale(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       case NS_STYLE_FILTER_INVERT:
-        wrFilters.AppendElement(wr::FilterOp::Invert(
+        wrFilters.filters.AppendElement(wr::FilterOp::Invert(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       case NS_STYLE_FILTER_OPACITY: {
         float opacity = filter.GetFilterParameter().GetFactorOrPercentValue();
-        wrFilters.AppendElement(wr::FilterOp::Opacity(
+        wrFilters.filters.AppendElement(wr::FilterOp::Opacity(
             wr::PropertyBinding<float>::Value(opacity), opacity));
         break;
       }
       case NS_STYLE_FILTER_SATURATE:
-        wrFilters.AppendElement(wr::FilterOp::Saturate(
+        wrFilters.filters.AppendElement(wr::FilterOp::Saturate(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       case NS_STYLE_FILTER_SEPIA: {
-        wrFilters.AppendElement(wr::FilterOp::Sepia(
+        wrFilters.filters.AppendElement(wr::FilterOp::Sepia(
             filter.GetFilterParameter().GetFactorOrPercentValue()));
         break;
       }
       case NS_STYLE_FILTER_HUE_ROTATE: {
-        wrFilters.AppendElement(wr::FilterOp::HueRotate(
+        wrFilters.filters.AppendElement(wr::FilterOp::HueRotate(
             (float)filter.GetFilterParameter().GetAngleValueInDegrees()));
         break;
       }
       case NS_STYLE_FILTER_BLUR: {
         float appUnitsPerDevPixel =
             mFrame->PresContext()->AppUnitsPerDevPixel();
-        wrFilters.AppendElement(mozilla::wr::FilterOp::Blur(ClampStdDeviation(
+        wrFilters.filters.AppendElement(mozilla::wr::FilterOp::Blur(ClampStdDeviation(
             NSAppUnitsToFloatPixels(filter.GetFilterParameter().GetCoordValue(),
                                     appUnitsPerDevPixel))));
         break;
       }
       case NS_STYLE_FILTER_DROP_SHADOW: {
         float appUnitsPerDevPixel =
             mFrame->PresContext()->AppUnitsPerDevPixel();
         nsCSSShadowArray* shadows = filter.GetDropShadow();
@@ -9401,30 +9401,30 @@ bool nsDisplayFilters::CreateWebRenderCS
             NSAppUnitsToFloatPixels(shadow->mRadius, appUnitsPerDevPixel),
             {
                 NS_GET_R(color) / 255.0f,
                 NS_GET_G(color) / 255.0f,
                 NS_GET_B(color) / 255.0f,
                 NS_GET_A(color) / 255.0f,
             });
 
-        wrFilters.AppendElement(filterOp);
+        wrFilters.filters.AppendElement(filterOp);
         break;
       }
       default:
         return false;
     }
   }
 
   return true;
 }
 
 bool nsDisplayFilters::CanCreateWebRenderCommands(
     nsDisplayListBuilder* aBuilder) {
-  nsTArray<mozilla::wr::FilterOp> wrFilters;
+  WrFiltersHolder wrFilters;
   Maybe<nsRect> filterClip;
   if (!CreateWebRenderCSSFilters(wrFilters) &&
       !nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, wrFilters,
                                                     filterClip)) {
     return false;
   }
   return true;
 }
@@ -9432,17 +9432,17 @@ bool nsDisplayFilters::CanCreateWebRende
 bool nsDisplayFilters::CreateWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc,
     mozilla::layers::RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
   float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
 
-  nsTArray<mozilla::wr::FilterOp> wrFilters;
+  WrFiltersHolder wrFilters;
   Maybe<nsRect> filterClip;
   if (!CreateWebRenderCSSFilters(wrFilters) &&
       !nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, wrFilters,
                                                     filterClip)) {
     return false;
   }
 
   wr::WrStackingContextClip clip;
@@ -9453,17 +9453,18 @@ bool nsDisplayFilters::CreateWebRenderCo
         aBuilder.DefineClip(Nothing(), wr::ToRoundedLayoutRect(devPxRect));
     clip = wr::WrStackingContextClip::ClipId(clipId);
   } else {
     clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
   }
 
   float opacity = mFrame->StyleEffects()->mOpacity;
   wr::StackingContextParams params;
-  params.mFilters = std::move(wrFilters);
+  params.mFilters = std::move(wrFilters.filters);
+  params.mFilterDatas = std::move(wrFilters.filter_datas);
   params.opacity = opacity != 1.0f && mHandleOpacity ? &opacity : nullptr;
   params.clip = clip;
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params);
 
   nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc,
                                                 aManager, aDisplayListBuilder);
 
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -59,16 +59,17 @@ class nsIContent;
 class nsDisplayList;
 class nsDisplayTableItem;
 class nsIScrollableFrame;
 class nsSubDocumentFrame;
 class nsDisplayCompositorHitTestInfo;
 class nsDisplayScrollInfoLayer;
 class nsCaret;
 enum class nsDisplayOwnLayerFlags;
+struct WrFiltersHolder;
 
 namespace mozilla {
 class FrameLayerBuilder;
 struct MotionPathData;
 namespace layers {
 struct FrameMetrics;
 class RenderRootStateManager;
 class Layer;
@@ -6384,17 +6385,17 @@ class nsDisplayFilters : public nsDispla
   bool CreateWebRenderCommands(
       mozilla::wr::DisplayListBuilder& aBuilder,
       mozilla::wr::IpcResourceUpdateQueue& aResources,
       const StackingContextHelper& aSc,
       mozilla::layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
   bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder);
 
-  bool CreateWebRenderCSSFilters(nsTArray<mozilla::wr::FilterOp>& wrFilters);
+  bool CreateWebRenderCSSFilters(WrFiltersHolder& wrFilters);
 
  private:
   // relative to mFrame
   nsRect mEffectsBounds;
 };
 
 /* A display item that applies a transformation to all of its descendant
  * elements.  This wrapper should only be used if there is a transform applied
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -90,20 +90,42 @@ void nsFilterInstance::PaintFilteredFram
                             *metrics, filterChain, /* InputIsTainted */ true,
                             aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
                             nullptr, nullptr, nullptr);
   if (instance.IsInitialized()) {
     instance.Render(aCtx, aImgParams, aOpacity);
   }
 }
 
+static mozilla::wr::ComponentTransferFuncType
+FuncTypeToWr(uint8_t aFuncType) {
+  switch (aFuncType) {
+    case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
+      return mozilla::wr::ComponentTransferFuncType::Identity;
+    case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
+      return mozilla::wr::ComponentTransferFuncType::Table;
+    case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
+      return mozilla::wr::ComponentTransferFuncType::Discrete;
+    case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
+      return mozilla::wr::ComponentTransferFuncType::Linear;
+    case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
+      return mozilla::wr::ComponentTransferFuncType::Gamma;
+    default:
+      MOZ_ASSERT(false, "unknown func type?");
+  }
+  MOZ_ASSERT(false, "unknown func type?");
+  return mozilla::wr::ComponentTransferFuncType::Identity;
+}
+
 bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
-                                             nsTArray<wr::FilterOp>& aWrFilters,
+                                             WrFiltersHolder& aWrFilters,
                                              Maybe<nsRect>& aPostFilterClip) {
-  aWrFilters.Clear();
+  aWrFilters.filters.Clear();
+  aWrFilters.filter_datas.Clear();
+  aWrFilters.values.Clear();
 
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics =
       UserSpaceMetricsForFrame(aFilteredFrame);
 
   // TODO: simply using an identity matrix here, was pulling the scale from a
   // gfx context for the non-wr path.
   gfxMatrix scaleMatrix;
@@ -157,29 +179,29 @@ bool nsFilterInstance::BuildWebRenderFil
       }
     } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
       return false;
     }
 
     bool previousSrgb = srgb;
     bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
     if (srgb && !primNeedsSrgb) {
-      aWrFilters.AppendElement(wr::FilterOp::SrgbToLinear());
+      aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
     } else if (!srgb && primNeedsSrgb) {
-      aWrFilters.AppendElement(wr::FilterOp::LinearToSrgb());
+      aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
     }
     srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
 
     const PrimitiveAttributes& attr = primitive.Attributes();
 
     bool filterIsNoop = false;
 
     if (attr.is<OpacityAttributes>()) {
       float opacity = attr.as<OpacityAttributes>().mOpacity;
-      aWrFilters.AppendElement(wr::FilterOp::Opacity(
+      aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
           wr::PropertyBinding<float>::Value(opacity), opacity));
     } else if (attr.is<ColorMatrixAttributes>()) {
       const ColorMatrixAttributes& attributes =
           attr.as<ColorMatrixAttributes>();
 
       float transposed[20];
       if (!gfx::ComputeColorMatrix(attributes, transposed)) {
         filterIsNoop = true;
@@ -202,17 +224,17 @@ bool nsFilterInstance::BuildWebRenderFil
 
       float matrix[20] = {
           transposed[0], transposed[5], transposed[10], transposed[15],
           transposed[1], transposed[6], transposed[11], transposed[16],
           transposed[2], transposed[7], transposed[12], transposed[17],
           transposed[3], transposed[8], transposed[13], transposed[18],
           transposed[4], transposed[9], transposed[14], transposed[19]};
 
-      aWrFilters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
+      aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
     } else if (attr.is<GaussianBlurAttributes>()) {
       if (finalClip) {
         // There's a clip that needs to apply before the blur filter, but
         // WebRender only lets us apply the clip at the end of the filter
         // chain. Clipping after a blur is not equivalent to clipping before
         // a blur, so bail out.
         return false;
       }
@@ -221,17 +243,17 @@ bool nsFilterInstance::BuildWebRenderFil
 
       const Size& stdDev = blur.mStdDeviation;
       if (stdDev.width != stdDev.height) {
         return false;
       }
 
       float radius = stdDev.width;
       if (radius != 0.0) {
-        aWrFilters.AppendElement(wr::FilterOp::Blur(radius));
+        aWrFilters.filters.AppendElement(wr::FilterOp::Blur(radius));
       } else {
         filterIsNoop = true;
       }
     } else if (attr.is<DropShadowAttributes>()) {
       if (finalClip) {
         // We have to bail out for the same reason we would with a blur filter.
         return false;
       }
@@ -250,47 +272,93 @@ bool nsFilterInstance::BuildWebRenderFil
       if (!primNeedsSrgb) {
         color = Color(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
                       gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
                       gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
       }
       wr::FilterOp filterOp = wr::FilterOp::DropShadow(
           offset, radius, wr::ToColorF(ToDeviceColor(color)));
 
-      aWrFilters.AppendElement(filterOp);
+      aWrFilters.filters.AppendElement(filterOp);
+    } else if (attr.is<ComponentTransferAttributes>()) {
+      const ComponentTransferAttributes& attributes =
+          attr.as<ComponentTransferAttributes>();
+
+      size_t numValues = attributes.mValues[0].Length() +
+          attributes.mValues[1].Length() + attributes.mValues[2].Length() +
+          attributes.mValues[3].Length();
+      if (numValues > 1024) {
+        // Depending on how the wr shaders are implemented we may need to
+        // limit the total number of values.
+        return false;
+      }
+
+      wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
+      wr::WrFilterData filterData;
+      aWrFilters.values.AppendElement(nsTArray<float>());
+      nsTArray<float>* values = &aWrFilters.values[aWrFilters.values.Length()-1];
+      values->SetCapacity(numValues);
+
+      filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
+      size_t R_startindex = values->Length();
+      values->AppendElements(attributes.mValues[0]);
+      filterData.R_values_count = attributes.mValues[0].Length();
+
+      filterData.funcG_type = FuncTypeToWr(attributes.mTypes[1]);
+      size_t G_startindex = values->Length();
+      values->AppendElements(attributes.mValues[1]);
+      filterData.G_values_count = attributes.mValues[1].Length();
+
+      filterData.funcB_type = FuncTypeToWr(attributes.mTypes[2]);
+      size_t B_startindex = values->Length();
+      values->AppendElements(attributes.mValues[2]);
+      filterData.B_values_count = attributes.mValues[2].Length();
+
+      filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
+      size_t A_startindex = values->Length();
+      values->AppendElements(attributes.mValues[3]);
+      filterData.A_values_count = attributes.mValues[3].Length();
+
+      filterData.R_values = filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
+      filterData.G_values = filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
+      filterData.B_values = filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
+      filterData.A_values = filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
+
+      aWrFilters.filters.AppendElement(filterOp);
+      aWrFilters.filter_datas.AppendElement(filterData);
     } else {
       return false;
     }
 
-    if (filterIsNoop && aWrFilters.Length() > 0 &&
-        (aWrFilters.LastElement().tag == wr::FilterOp::Tag::SrgbToLinear ||
-         aWrFilters.LastElement().tag == wr::FilterOp::Tag::LinearToSrgb)) {
+    if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
+        (aWrFilters.filters.LastElement().tag == wr::FilterOp::Tag::SrgbToLinear ||
+         aWrFilters.filters.LastElement().tag == wr::FilterOp::Tag::LinearToSrgb)) {
       // We pushed a color space conversion filter in prevision of applying
       // another filter which turned out to be a no-op, so the conversion is
       // unnecessary. Remove it from the filter list.
       // This is both an optimization and a way to pass the wptest
       // css/filter-effects/filter-scale-001.html for which the needless
       // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
       // cannot add fuzziness to the test.
-      Unused << aWrFilters.PopLastElement();
+      Unused << aWrFilters.filters.PopLastElement();
       srgb = previousSrgb;
     }
 
     if (!filterIsNoop) {
       if (finalClip.isNothing()) {
         finalClip = Some(primitive.PrimitiveSubregion());
       } else {
         finalClip =
             Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
       }
     }
   }
 
   if (!srgb) {
-    aWrFilters.AppendElement(wr::FilterOp::LinearToSrgb());
+    aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
   }
 
   if (finalClip) {
     aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
   }
   return true;
 }
 
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -118,17 +118,17 @@ class nsFilterInstance {
                                     const gfxRect* aOverrideBBox = nullptr,
                                     const nsRect* aPreFilterBounds = nullptr);
 
   /**
    * Try to build WebRender filters for a frame if the filters applied to it are
    * supported.
    */
   static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
-                                    nsTArray<mozilla::wr::FilterOp>& aWrFilters,
+                                    WrFiltersHolder& aWrFilters,
                                     mozilla::Maybe<nsRect>& aPostFilterClip);
 
  private:
   /**
    * @param aTargetFrame The frame of the filtered element under consideration,
    *   may be null.
    * @param aTargetContent The filtered element itself.
    * @param aMetrics The metrics to resolve SVG lengths against.
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -1081,17 +1081,17 @@ void nsSVGIntegrationUtils::PaintFilter(
                                      offsets.offsetToUserSpaceInDevPx);
   nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
 
   nsFilterInstance::PaintFilteredFrame(frame, &context, &callback, &dirtyRegion,
                                        aParams.imgParams, opacity);
 }
 
 bool nsSVGIntegrationUtils::BuildWebRenderFilters(
-    nsIFrame* aFilteredFrame, nsTArray<mozilla::wr::FilterOp>& aWrFilters,
+    nsIFrame* aFilteredFrame, WrFiltersHolder& aWrFilters,
     Maybe<nsRect>& aPostFilterClip) {
   return nsFilterInstance::BuildWebRenderFilters(aFilteredFrame, aWrFilters,
                                                  aPostFilterClip);
 }
 
 class PaintFrameCallback : public gfxDrawingCallback {
  public:
   PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -29,16 +29,23 @@ class DrawTarget;
 namespace layers {
 class LayerManager;
 }  // namespace layers
 }  // namespace mozilla
 
 struct nsPoint;
 struct nsSize;
 
+struct WrFiltersHolder {
+  nsTArray<mozilla::wr::FilterOp> filters;
+  nsTArray<mozilla::wr::WrFilterData> filter_datas;
+  // This exists just to own the values long enough for them to be copied into rust.
+  nsTArray<nsTArray<float>> values;
+};
+
 /**
  * Integration of SVG effects (clipPath clipping, masking and filters) into
  * regular display list based painting and hit-testing.
  */
 class nsSVGIntegrationUtils final {
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::IntRect IntRect;
   typedef mozilla::image::imgDrawingParams imgDrawingParams;
@@ -191,17 +198,17 @@ class nsSVGIntegrationUtils final {
    */
   static void PaintFilter(const PaintFramesParams& aParams);
 
   /**
    * Try to build WebRender filters for a frame if the filters applied to it are
    * supported.
    */
   static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
-                                    nsTArray<mozilla::wr::FilterOp>& aWrFilters,
+                                    WrFiltersHolder& aWrFilters,
                                     mozilla::Maybe<nsRect>& aPostFilterClip);
 
   /**
    * @param aRenderingContext the target rendering context in which the paint
    * server will be rendered
    * @param aTarget the target frame onto which the paint server will be
    * rendered
    * @param aPaintServer a first-continuation frame to use as the source
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-1f04eea8834a
+f7be0a534e89
--- a/security/nss/cmd/atob/atob.c
+++ b/security/nss/cmd/atob/atob.c
@@ -100,75 +100,89 @@ Usage(char *progName)
 {
     fprintf(stderr,
             "Usage: %s [-i input] [-o output]\n",
             progName);
     fprintf(stderr, "%-20s Define an input file to use (default is stdin)\n",
             "-i input");
     fprintf(stderr, "%-20s Define an output file to use (default is stdout)\n",
             "-o output");
-    exit(-1);
 }
 
 int
 main(int argc, char **argv)
 {
     char *progName;
     SECStatus rv;
-    FILE *inFile, *outFile;
-    PLOptState *optstate;
+    FILE *inFile = NULL, *outFile = NULL;
+    PRBool closeIn = PR_TRUE, closeOut = PR_TRUE;
+    PLOptState *optstate = NULL;
     PLOptStatus status;
+    int exitCode = -1;
 
-    inFile = 0;
-    outFile = 0;
     progName = strrchr(argv[0], '/');
     progName = progName ? progName + 1 : argv[0];
 
     /* Parse command line arguments */
     optstate = PL_CreateOptState(argc, argv, "?hi:o:");
     while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
         switch (optstate->option) {
             case '?':
             case 'h':
                 Usage(progName);
+                goto loser;
                 break;
 
             case 'i':
                 inFile = fopen(optstate->value, "r");
                 if (!inFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
                             progName, optstate->value);
-                    return -1;
+                    goto loser;
                 }
                 break;
 
             case 'o':
                 outFile = fopen(optstate->value, "wb");
                 if (!outFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
                             progName, optstate->value);
-                    return -1;
+                    goto loser;
                 }
                 break;
         }
     }
-    if (!inFile)
+    if (!inFile) {
         inFile = stdin;
+        closeIn = PR_FALSE;
+    }
     if (!outFile) {
 #if defined(WIN32)
         int smrv = _setmode(_fileno(stdout), _O_BINARY);
         if (smrv == -1) {
             fprintf(stderr,
                     "%s: Cannot change stdout to binary mode. Use -o option instead.\n",
                     progName);
-            return smrv;
+            goto loser;
         }
 #endif
         outFile = stdout;
+        closeOut = PR_FALSE;
     }
     rv = decode_file(outFile, inFile);
     if (rv != SECSuccess) {
         fprintf(stderr, "%s: lossage: error=%d errno=%d\n",
                 progName, PORT_GetError(), errno);
-        return -1;
+        goto loser;
+    }
+    exitCode = 0;
+loser:
+    if (optstate) {
+        PL_DestroyOptState(optstate);
     }
-    return 0;
+    if (inFile && closeIn) {
+        fclose(inFile);
+    }
+    if (outFile && closeOut) {
+        fclose(outFile);
+    }
+    return exitCode;
 }
--- a/security/nss/cmd/btoa/btoa.c
+++ b/security/nss/cmd/btoa/btoa.c
@@ -94,59 +94,59 @@ Usage(char *progName)
     fprintf(stderr, "%-20s Define an input file to use (default is stdin)\n",
             "-i input");
     fprintf(stderr, "%-20s Define an output file to use (default is stdout)\n",
             "-o output");
     fprintf(stderr, "%-20s Wrap output in BEGIN/END lines and the given suffix\n",
             "-w suffix");
     fprintf(stderr, "%-20s (use \"c\" as a shortcut for suffix CERTIFICATE)\n",
             "");
-    exit(-1);
 }
 
 int
 main(int argc, char **argv)
 {
     char *progName;
     SECStatus rv;
-    FILE *inFile, *outFile;
-    PLOptState *optstate;
+    FILE *inFile = NULL, *outFile = NULL;
+    PRBool closeIn = PR_TRUE, closeOut = PR_TRUE;
+    PLOptState *optstate = NULL;
     PLOptStatus status;
     char *suffix = NULL;
+    int exitCode = -1;
 
-    inFile = 0;
-    outFile = 0;
     progName = strrchr(argv[0], '/');
     if (!progName)
         progName = strrchr(argv[0], '\\');
     progName = progName ? progName + 1 : argv[0];
 
     /* Parse command line arguments */
     optstate = PL_CreateOptState(argc, argv, "i:o:w:");
     while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
         switch (optstate->option) {
             default:
                 Usage(progName);
+                goto loser;
                 break;
 
             case 'i':
                 inFile = fopen(optstate->value, "rb");
                 if (!inFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
                             progName, optstate->value);
-                    return -1;
+                    goto loser;
                 }
                 break;
 
             case 'o':
                 outFile = fopen(optstate->value, "wb");
                 if (!outFile) {
                     fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
                             progName, optstate->value);
-                    return -1;
+                    goto loser;
                 }
                 break;
 
             case 'w':
                 if (!strcmp(optstate->value, "c"))
                     suffix = strdup("CERTIFICATE");
                 else
                     suffix = strdup(optstate->value);
@@ -161,43 +161,56 @@ main(int argc, char **argv)
         ** into O_BINARY mode or else incoming \r\n's will become \n's.
         */
 
         int smrv = _setmode(_fileno(stdin), _O_BINARY);
         if (smrv == -1) {
             fprintf(stderr,
                     "%s: Cannot change stdin to binary mode. Use -i option instead.\n",
                     progName);
-            return smrv;
+            goto loser;
         }
 #endif
         inFile = stdin;
+        closeIn = PR_FALSE;
     }
     if (!outFile) {
 #if defined(WIN32)
         /* We're going to write binary data to stdout. We must put stdout
         ** into O_BINARY mode or else outgoing \r\n's will become \r\r\n's.
         */
 
         int smrv = _setmode(_fileno(stdout), _O_BINARY);
         if (smrv == -1) {
             fprintf(stderr,
                     "%s: Cannot change stdout to binary mode. Use -o option instead.\n",
                     progName);
-            return smrv;
+            goto loser;
         }
 #endif
         outFile = stdout;
+        closeOut = PR_FALSE;
     }
     if (suffix) {
         fprintf(outFile, "-----BEGIN %s-----\n", suffix);
     }
     rv = encode_file(outFile, inFile);
     if (rv != SECSuccess) {
         fprintf(stderr, "%s: lossage: error=%d errno=%d\n",
                 progName, PORT_GetError(), errno);
-        return -1;
+        goto loser;
     }
     if (suffix) {
         fprintf(outFile, "-----END %s-----\n", suffix);
     }
-    return 0;
+    exitCode = 0;
+loser:
+    if (optstate) {
+        PL_DestroyOptState(optstate);
+    }
+    if (inFile && closeIn) {
+        fclose(inFile);
+    }
+    if (outFile && closeOut) {
+        fclose(outFile);
+    }
+    return exitCode;
 }
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/cpputil/scoped_ptrs_ssl.h
+++ b/security/nss/cpputil/scoped_ptrs_ssl.h
@@ -9,27 +9,29 @@
 
 #include <memory>
 #include "sslexp.h"
 
 struct ScopedDeleteSSL {
   void operator()(SSLResumptionTokenInfo* token) {
     SSL_DestroyResumptionTokenInfo(token);
   }
+  void operator()(SSLAeadContext* ctx) { SSL_DestroyAead(ctx); }
 };
 
 template <class T>
 struct ScopedMaybeDeleteSSL {
   void operator()(T* ptr) {
     if (ptr) {
       ScopedDeleteSSL del;
       del(ptr);
     }
   }
 };
 
 #define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDeleteSSL<x> > Scoped##x
 
 SCOPED(SSLResumptionTokenInfo);
+SCOPED(SSLAeadContext);
 
 #undef SCOPED
 
 #endif  // scoped_ptrs_ssl_h__
--- a/security/nss/gtests/ssl_gtest/manifest.mn
+++ b/security/nss/gtests/ssl_gtest/manifest.mn
@@ -30,16 +30,17 @@ CPPSRCS = \
       ssl_fuzz_unittest.cc \
       ssl_gather_unittest.cc \
       ssl_gtest.cc \
       ssl_hrr_unittest.cc \
       ssl_keylog_unittest.cc \
       ssl_keyupdate_unittest.cc \
       ssl_loopback_unittest.cc \
       ssl_misc_unittest.cc \
+      ssl_primitive_unittest.cc \
       ssl_record_unittest.cc \
       ssl_recordsep_unittest.cc \
       ssl_recordsize_unittest.cc \
       ssl_resumption_unittest.cc \
       ssl_renegotiation_unittest.cc \
       ssl_skip_unittest.cc \
       ssl_staticrsa_unittest.cc \
       ssl_tls13compat_unittest.cc \
--- a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
@@ -542,16 +542,66 @@ TEST_P(TlsExtensionTest12, SignatureAlgo
        i < PR_ARRAY_SIZE(schemes) && cursor < ext.len(); ++i) {
     uint32_t v = 0;
     EXPECT_TRUE(ext.Read(cursor, 2, &v));
     cursor += 2;
     EXPECT_EQ(schemes[i], static_cast<SSLSignatureScheme>(v));
   }
 }
 
+// This only works on TLS 1.2, since it relies on DSA.
+TEST_P(TlsExtensionTest12, SignatureAlgorithmDisableDSA) {
+  const std::vector<SSLSignatureScheme> schemes = {
+      ssl_sig_dsa_sha1, ssl_sig_dsa_sha256, ssl_sig_dsa_sha384,
+      ssl_sig_dsa_sha512, ssl_sig_rsa_pss_rsae_sha256};
+
+  // Connect with DSA enabled by policy.
+  SECStatus rv = NSS_SetAlgorithmPolicy(SEC_OID_ANSIX9_DSA_SIGNATURE,
+                                        NSS_USE_ALG_IN_SSL_KX, 0);
+  ASSERT_EQ(SECSuccess, rv);
+  rv = NSS_SetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY, NSS_USE_POLICY_IN_SSL,
+                              0);
+  ASSERT_EQ(SECSuccess, rv);
+
+  Reset(TlsAgent::kServerDsa);
+  auto capture1 =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_signature_algorithms_xtn);
+  client_->SetSignatureSchemes(schemes.data(), schemes.size());
+  Connect();
+
+  // Check if all the signature algorithms are advertised.
+  EXPECT_TRUE(capture1->captured());
+  const DataBuffer& ext1 = capture1->extension();
+  EXPECT_EQ(2U + 2U * schemes.size(), ext1.len());
+
+  // Connect with DSA disabled by policy.
+  rv = NSS_SetAlgorithmPolicy(SEC_OID_ANSIX9_DSA_SIGNATURE, 0,
+                              NSS_USE_ALG_IN_SSL_KX);
+  ASSERT_EQ(SECSuccess, rv);
+  rv = NSS_SetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY, NSS_USE_POLICY_IN_SSL,
+                              0);
+  ASSERT_EQ(SECSuccess, rv);
+
+  Reset(TlsAgent::kServerDsa);
+  auto capture2 =
+      MakeTlsFilter<TlsExtensionCapture>(client_, ssl_signature_algorithms_xtn);
+  client_->SetSignatureSchemes(schemes.data(), schemes.size());
+  ConnectExpectAlert(server_, kTlsAlertHandshakeFailure);
+  server_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM);
+  client_->CheckErrorCode(SSL_ERROR_NO_CYPHER_OVERLAP);
+
+  // Check if no DSA algorithms are advertised.
+  EXPECT_TRUE(capture2->captured());
+  const DataBuffer& ext2 = capture2->extension();
+  EXPECT_EQ(2U + 2U, ext2.len());
+  uint32_t v = 0;
+  EXPECT_TRUE(ext2.Read(2, 2, &v));
+  EXPECT_EQ(ssl_sig_rsa_pss_rsae_sha256, v);
+}
+
 // Temporary test to verify that we choke on an empty ClientKeyShare.
 // This test will fail when we implement HelloRetryRequest.
 TEST_P(TlsExtensionTest13, EmptyClientKeyShare) {
   ClientHelloErrorTest(std::make_shared<TlsExtensionTruncator>(
                            client_, ssl_tls13_key_share_xtn, 2),
                        kTlsAlertHandshakeFailure);
 }
 
@@ -1116,16 +1166,20 @@ INSTANTIATE_TEST_CASE_P(
                        TlsConnectTestBase::kTlsVAll));
 INSTANTIATE_TEST_CASE_P(
     ExtensionDatagram, TlsExtensionTestGeneric,
     ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
                        TlsConnectTestBase::kTlsV11Plus));
 INSTANTIATE_TEST_CASE_P(ExtensionDatagramOnly, TlsExtensionTestDtls,
                         TlsConnectTestBase::kTlsV11Plus);
 
+INSTANTIATE_TEST_CASE_P(ExtensionTls12, TlsExtensionTest12,
+                        ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
+                                           TlsConnectTestBase::kTlsV12));
+
 INSTANTIATE_TEST_CASE_P(ExtensionTls12Plus, TlsExtensionTest12Plus,
                         ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV12Plus));
 
 INSTANTIATE_TEST_CASE_P(
     ExtensionPre13Stream, TlsExtensionTestPre13,
     ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
                        TlsConnectTestBase::kTlsV10ToV12));
--- a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
@@ -31,16 +31,17 @@
         'ssl_fragment_unittest.cc',
         'ssl_gather_unittest.cc',
         'ssl_gtest.cc',
         'ssl_hrr_unittest.cc',
         'ssl_keylog_unittest.cc',
         'ssl_keyupdate_unittest.cc',
         'ssl_loopback_unittest.cc',
         'ssl_misc_unittest.cc',
+        'ssl_primitive_unittest.cc',
         'ssl_record_unittest.cc',
         'ssl_recordsep_unittest.cc',
         'ssl_recordsize_unittest.cc',
         'ssl_resumption_unittest.cc',
         'ssl_renegotiation_unittest.cc',
         'ssl_skip_unittest.cc',
         'ssl_staticrsa_unittest.cc',
         'ssl_tls13compat_unittest.cc',
new file mode 100644
--- /dev/null
+++ b/security/nss/gtests/ssl_gtest/ssl_primitive_unittest.cc
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <memory>
+
+#include "keyhi.h"
+#include "pk11pub.h"
+#include "secerr.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+#include "gtest_utils.h"
+#include "nss_scoped_ptrs.h"
+#include "scoped_ptrs_ssl.h"
+#include "tls_connect.h"
+
+namespace nss_test {
+
+// From tls_hkdf_unittest.cc:
+extern size_t GetHashLength(SSLHashType ht);
+
+class AeadTest : public ::testing::Test {
+ public:
+  AeadTest() : slot_(PK11_GetInternalSlot()) {}
+
+  void InitSecret(SSLHashType hash_type) {
+    static const uint8_t kData[64] = {'s', 'e', 'c', 'r', 'e', 't'};
+    SECItem key_item = {siBuffer, const_cast<uint8_t *>(kData),
+                        static_cast<unsigned int>(GetHashLength(hash_type))};
+    PK11SymKey *s =
+        PK11_ImportSymKey(slot_.get(), CKM_SSL3_MASTER_KEY_DERIVE,
+                          PK11_OriginUnwrap, CKA_DERIVE, &key_item, NULL);
+    ASSERT_NE(nullptr, s);
+    secret_.reset(s);
+  }
+
+  void SetUp() override {
+    InitSecret(ssl_hash_sha256);
+    PORT_SetError(0);
+  }
+
+ protected:
+  static void EncryptDecrypt(const ScopedSSLAeadContext &ctx,
+                             const uint8_t *ciphertext, size_t ciphertext_len) {
+    static const uint8_t kAad[] = {'a', 'a', 'd'};
+    static const uint8_t kPlaintext[] = {'t', 'e', 'x', 't'};
+    static const size_t kMaxSize = 32;
+
+    ASSERT_GE(kMaxSize, ciphertext_len);
+
+    uint8_t output[kMaxSize];
+    unsigned int output_len = 0;
+    EXPECT_EQ(SECSuccess, SSL_AeadEncrypt(ctx.get(), 0, kAad, sizeof(kAad),
+                                          kPlaintext, sizeof(kPlaintext),
+                                          output, &output_len, sizeof(output)));
+    ASSERT_EQ(ciphertext_len, static_cast<size_t>(output_len));
+    EXPECT_EQ(0, memcmp(ciphertext, output, ciphertext_len));
+
+    memset(output, 0, sizeof(output));
+    EXPECT_EQ(SECSuccess, SSL_AeadDecrypt(ctx.get(), 0, kAad, sizeof(kAad),
+                                          ciphertext, ciphertext_len, output,
+                                          &output_len, sizeof(output)));
+    ASSERT_EQ(sizeof(kPlaintext), static_cast<size_t>(output_len));
+    EXPECT_EQ(0, memcmp(kPlaintext, output, sizeof(kPlaintext)));
+
+    // Now for some tests of decryption failure.
+    // Truncate the input.
+    EXPECT_EQ(SECFailure, SSL_AeadDecrypt(ctx.get(), 0, kAad, sizeof(kAad),
+                                          ciphertext, ciphertext_len - 1,
+                                          output, &output_len, sizeof(output)));
+    EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
+
+    // Skip the first byte of the AAD.
+    EXPECT_EQ(
+        SECFailure,
+        SSL_AeadDecrypt(ctx.get(), 0, kAad + 1, sizeof(kAad) - 1, ciphertext,
+                        ciphertext_len, output, &output_len, sizeof(output)));
+    EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
+
+    uint8_t input[kMaxSize] = {0};
+    // Toggle a byte of the input.
+    memcpy(input, ciphertext, ciphertext_len);
+    input[0] ^= 9;
+    EXPECT_EQ(SECFailure, SSL_AeadDecrypt(ctx.get(), 0, kAad, sizeof(kAad),
+                                          input, ciphertext_len, output,
+                                          &output_len, sizeof(output)));
+    EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
+
+    // Toggle the last byte (the auth tag).
+    memcpy(input, ciphertext, ciphertext_len);
+    input[ciphertext_len - 1] ^= 77;
+    EXPECT_EQ(SECFailure, SSL_AeadDecrypt(ctx.get(), 0, kAad, sizeof(kAad),
+                                          input, ciphertext_len, output,
+                                          &output_len, sizeof(output)));
+    EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
+
+    // Toggle some of the AAD.
+    memcpy(input, kAad, sizeof(kAad));
+    input[1] ^= 23;
+    EXPECT_EQ(SECFailure, SSL_AeadDecrypt(ctx.get(), 0, input, sizeof(kAad),
+                                          ciphertext, ciphertext_len, output,
+                                          &output_len, sizeof(output)));
+    EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
+  }
+
+ protected:
+  ScopedPK11SymKey secret_;
+
+ private:
+  ScopedPK11SlotInfo slot_;
+};
+
+// These tests all use fixed inputs: a fixed secret, a fixed label, and fixed
+// inputs.  So they have fixed outputs.
+static const char *kLabel = "test ";
+static const uint8_t kCiphertextAes128Gcm[] = {
+    0x11, 0x14, 0xfc, 0x58, 0x4f, 0x44, 0xff, 0x8c, 0xb6, 0xd8,
+    0x20, 0xb3, 0xfb, 0x50, 0xd9, 0x3b, 0xd4, 0xc6, 0xe1, 0x14};
+static const uint8_t kCiphertextAes256Gcm[] = {
+    0xf7, 0x27, 0x35, 0x80, 0x88, 0xaf, 0x99, 0x85, 0xf2, 0x83,
+    0xca, 0xbb, 0x95, 0x42, 0x09, 0x3f, 0x9c, 0xf3, 0x29, 0xf0};
+static const uint8_t kCiphertextChaCha20Poly1305[] = {
+    0x4e, 0x89, 0x2c, 0xfa, 0xfc, 0x8c, 0x40, 0x55, 0x6d, 0x7e,
+    0x99, 0xac, 0x8e, 0x54, 0x58, 0xb1, 0x18, 0xd2, 0x66, 0x22};
+
+TEST_F(AeadTest, AeadBadVersion) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(SECFailure,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_2, TLS_AES_128_GCM_SHA256,
+                         secret_.get(), kLabel, strlen(kLabel), &ctx));
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadUnsupportedCipher) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(SECFailure,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_RSA_WITH_NULL_MD5,
+                         secret_.get(), kLabel, strlen(kLabel), &ctx));
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadOlderCipher) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(
+      SECFailure,
+      SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_RSA_WITH_AES_128_CBC_SHA,
+                   secret_.get(), kLabel, strlen(kLabel), &ctx));
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadNoLabel) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(SECFailure,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_128_GCM_SHA256,
+                         secret_.get(), nullptr, 12, &ctx));
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadLongLabel) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(SECFailure,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_128_GCM_SHA256,
+                         secret_.get(), "", 254, &ctx));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadNoPointer) {
+  SSLAeadContext *ctx = nullptr;
+  ASSERT_EQ(SECFailure,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_128_GCM_SHA256,
+                         secret_.get(), kLabel, strlen(kLabel), nullptr));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+  EXPECT_EQ(nullptr, ctx);
+}
+
+TEST_F(AeadTest, AeadAes128Gcm) {
+  SSLAeadContext *ctxInit;
+  ASSERT_EQ(SECSuccess,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_128_GCM_SHA256,
+                         secret_.get(), kLabel, strlen(kLabel), &ctxInit));
+  ScopedSSLAeadContext ctx(ctxInit);
+  EXPECT_NE(nullptr, ctx);
+
+  EncryptDecrypt(ctx, kCiphertextAes128Gcm, sizeof(kCiphertextAes128Gcm));
+}
+
+TEST_F(AeadTest, AeadAes256Gcm) {
+  SSLAeadContext *ctxInit;
+  ASSERT_EQ(SECSuccess,
+            SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_256_GCM_SHA384,
+                         secret_.get(), kLabel, strlen(kLabel), &ctxInit));
+  ScopedSSLAeadContext ctx(ctxInit);
+  EXPECT_NE(nullptr, ctx);
+
+  EncryptDecrypt(ctx, kCiphertextAes256Gcm, sizeof(kCiphertextAes256Gcm));
+}
+
+TEST_F(AeadTest, AeadChaCha20Poly1305) {
+  SSLAeadContext *ctxInit;
+  ASSERT_EQ(
+      SECSuccess,
+      SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_CHACHA20_POLY1305_SHA256,
+                   secret_.get(), kLabel, strlen(kLabel), &ctxInit));
+  ScopedSSLAeadContext ctx(ctxInit);
+  EXPECT_NE(nullptr, ctx);
+
+  EncryptDecrypt(ctx, kCiphertextChaCha20Poly1305,
+                 sizeof(kCiphertextChaCha20Poly1305));
+}
+
+}  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc
@@ -219,18 +219,36 @@ class StagedRecords {
     }
     records_.clear();
   }
 
   // This forwards all saved data and checks the resulting state.
   void ForwardAll(std::shared_ptr<TlsAgent>& peer,
                   TlsAgent::State expected_state) {
     ForwardAll(peer);
-    peer->Handshake();
-    EXPECT_EQ(expected_state, peer->state());
+    switch (expected_state) {
+      case TlsAgent::STATE_CONNECTED:
+        // The handshake callback should have been called, so check that before
+        // checking that SSL_ForceHandshake succeeds.
+        EXPECT_EQ(expected_state, peer->state());
+        EXPECT_EQ(SECSuccess, SSL_ForceHandshake(peer->ssl_fd()));
+        break;
+
+      case TlsAgent::STATE_CONNECTING:
+        // Check that SSL_ForceHandshake() blocks.
+        EXPECT_EQ(SECFailure, SSL_ForceHandshake(peer->ssl_fd()));
+        EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
+        // Update and check the state.
+        peer->Handshake();
+        EXPECT_EQ(TlsAgent::STATE_CONNECTING, peer->state());
+        break;
+
+      default:
+        ADD_FAILURE() << "No idea how to handle this state";
+    }
   }
 
   void ForwardPartial(std::shared_ptr<TlsAgent>& peer) {
     if (records_.empty()) {
       ADD_FAILURE() << "No records to slice";
       return;
     }
     auto& last = records_.back();
@@ -255,22 +273,20 @@ class StagedRecords {
 
     // This forwards staged data to the identified agent.
     void Forward(std::shared_ptr<TlsAgent>& peer) {
       // Now there should be staged data.
       EXPECT_FALSE(data_.empty());
       if (g_ssl_gtest_verbose) {
         std::cerr << role_ << ": forward " << data_ << std::endl;
       }
-      SECStatus rv = SSL_RecordLayerData(
-          peer->ssl_fd(), epoch_, content_type_, data_.data(),
-          static_cast<unsigned int>(data_.len()));
-      if (rv != SECSuccess) {
-        EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
-      }
+      EXPECT_EQ(SECSuccess,
+                SSL_RecordLayerData(peer->ssl_fd(), epoch_, content_type_,
+                                    data_.data(),
+                                    static_cast<unsigned int>(data_.len())));
     }
 
     // Slices the tail off this record and returns it.
     StagedRecord SliceTail() {
       size_t slice = 1;
       if (data_.len() <= slice) {
         ADD_FAILURE() << "record too small to slice in two";
         slice = 0;
--- a/security/nss/gtests/ssl_gtest/ssl_versionpolicy_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_versionpolicy_unittest.cc
@@ -209,45 +209,35 @@ class TestPolicyVersionRange
       rv = NSS_OptionSet(NSS_TLS_VERSION_MIN_POLICY, saved_min_tls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionSet(NSS_TLS_VERSION_MAX_POLICY, saved_max_tls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionSet(NSS_DTLS_VERSION_MIN_POLICY, saved_min_dtls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionSet(NSS_DTLS_VERSION_MAX_POLICY, saved_max_dtls_);
       ASSERT_EQ(SECSuccess, rv);
-      // If it wasn't set initially, clear the bit that we set.
-      if (!(saved_algorithm_policy_ & NSS_USE_POLICY_IN_SSL)) {
-        rv = NSS_SetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY, 0,
-                                    NSS_USE_POLICY_IN_SSL);
-        ASSERT_EQ(SECSuccess, rv);
-      }
     }
 
    private:
     void SaveOriginalPolicy() {
       SECStatus rv;
       rv = NSS_OptionGet(NSS_TLS_VERSION_MIN_POLICY, &saved_min_tls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionGet(NSS_TLS_VERSION_MAX_POLICY, &saved_max_tls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionGet(NSS_DTLS_VERSION_MIN_POLICY, &saved_min_dtls_);
       ASSERT_EQ(SECSuccess, rv);
       rv = NSS_OptionGet(NSS_DTLS_VERSION_MAX_POLICY, &saved_max_dtls_);
       ASSERT_EQ(SECSuccess, rv);
-      rv = NSS_GetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY,
-                                  &saved_algorithm_policy_);
-      ASSERT_EQ(SECSuccess, rv);
     }
 
     int32_t saved_min_tls_;
     int32_t saved_max_tls_;
     int32_t saved_min_dtls_;
     int32_t saved_max_dtls_;
-    uint32_t saved_algorithm_policy_;
   };
 
   VersionPolicy saved_version_policy_;
 
   SSLProtocolVariant variant_;
   const VersionRangeWithLabel policy_;
   const VersionRangeWithLabel input_;
   const VersionRangeWithLabel library_;
--- a/security/nss/gtests/ssl_gtest/tls_connect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_connect.cc
@@ -178,32 +178,54 @@ void TlsConnectTestBase::ClearStats() {
 }
 
 void TlsConnectTestBase::ClearServerCache() {
   SSL_ShutdownServerSessionIDCache();
   SSLInt_ClearSelfEncryptKey();
   SSL_ConfigServerSessionIDCache(1024, 0, 0, g_working_dir_path.c_str());
 }
 
+void TlsConnectTestBase::SaveAlgorithmPolicy() {
+  saved_policies_.clear();
+  for (auto it = algorithms_.begin(); it != algorithms_.end(); ++it) {
+    uint32_t policy;
+    SECStatus rv = NSS_GetAlgorithmPolicy(*it, &policy);
+    ASSERT_EQ(SECSuccess, rv);
+    saved_policies_.push_back(std::make_tuple(*it, policy));
+  }
+}
+
+void TlsConnectTestBase::RestoreAlgorithmPolicy() {
+  for (auto it = saved_policies_.begin(); it != saved_policies_.end(); ++it) {
+    auto algorithm = std::get<0>(*it);
+    auto policy = std::get<1>(*it);
+    SECStatus rv = NSS_SetAlgorithmPolicy(
+        algorithm, policy, NSS_USE_POLICY_IN_SSL | NSS_USE_ALG_IN_SSL_KX);
+    ASSERT_EQ(SECSuccess, rv);
+  }
+}
+
 void TlsConnectTestBase::SetUp() {
   SSL_ConfigServerSessionIDCache(1024, 0, 0, g_working_dir_path.c_str());
   SSLInt_ClearSelfEncryptKey();
   SSLInt_SetTicketLifetime(30);
   SSL_SetupAntiReplay(1 * PR_USEC_PER_SEC, 1, 3);
   ClearStats();
+  SaveAlgorithmPolicy();
   Init();
 }
 
 void TlsConnectTestBase::TearDown() {
   client_ = nullptr;
   server_ = nullptr;
 
   SSL_ClearSessionCache();
   SSLInt_ClearSelfEncryptKey();
   SSL_ShutdownServerSessionIDCache();
+  RestoreAlgorithmPolicy();
 }
 
 void TlsConnectTestBase::Init() {
   client_->SetPeer(server_);
   server_->SetPeer(client_);
 
   if (version_) {
     ConfigureVersion(version_);
--- a/security/nss/gtests/ssl_gtest/tls_connect.h
+++ b/security/nss/gtests/ssl_gtest/tls_connect.h
@@ -127,16 +127,19 @@ class TlsConnectTestBase : public ::test
   void ExpectExtendedMasterSecret(bool expected);
   void ExpectEarlyDataAccepted(bool expected);
   void DisableECDHEServerKeyReuse();
   void SkipVersionChecks();
 
   // Move the DTLS timers for both endpoints to pop the next timer.
   void ShiftDtlsTimers();
 
+  void SaveAlgorithmPolicy();
+  void RestoreAlgorithmPolicy();
+
  protected:
   SSLProtocolVariant variant_;
   std::shared_ptr<TlsAgent> client_;
   std::shared_ptr<TlsAgent> server_;
   std::unique_ptr<TlsAgent> client_model_;
   std::unique_ptr<TlsAgent> server_model_;
   uint16_t version_;
   SessionResumptionMode expected_resumption_mode_;
@@ -144,16 +147,23 @@ class TlsConnectTestBase : public ::test
   std::vector<std::vector<uint8_t>> session_ids_;
 
   // A simple value of "a", "b".  Note that the preferred value of "a" is placed
   // at the end, because the NSS API follows the now defunct NPN specification,
   // which places the preferred (and default) entry at the end of the list.
   // NSS will move this final entry to the front when used with ALPN.
   const uint8_t alpn_dummy_val_[4] = {0x01, 0x62, 0x01, 0x61};
 
+  // A list of algorithm IDs whose policies need to be preserved
+  // around test cases.  In particular, DSA is checked in
+  // ssl_extension_unittest.cc.
+  const std::vector<SECOidTag> algorithms_ = {SEC_OID_APPLY_SSL_POLICY,
+                                              SEC_OID_ANSIX9_DSA_SIGNATURE};
+  std::vector<std::tuple<SECOidTag, uint32_t>> saved_policies_;
+
  private:
   void CheckResumption(SessionResumptionMode expected);
   void CheckExtendedMasterSecret();
   void CheckEarlyDataAccepted();
 
   bool expect_extended_master_secret_;
   bool expect_early_data_accepted_;
   bool skip_version_checks_;
--- a/security/nss/gtests/ssl_gtest/tls_hkdf_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/tls_hkdf_unittest.cc
@@ -2,16 +2,19 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <memory>
 #include "nss.h"
 #include "pk11pub.h"
+#include "secerr.h"
+#include "sslproto.h"
+#include "sslexp.h"
 #include "tls13hkdf.h"
 
 #include "databuffer.h"
 #include "gtest_utils.h"
 #include "nss_scoped_ptrs.h"
 
 namespace nss_test {
 
@@ -51,25 +54,46 @@ const size_t kHashLength[] = {
     16, /* ssl_hash_md5    */
     20, /* ssl_hash_sha1   */
     28, /* ssl_hash_sha224 */
     32, /* ssl_hash_sha256 */
     48, /* ssl_hash_sha384 */
     64, /* ssl_hash_sha512 */
 };
 
+size_t GetHashLength(SSLHashType hash) {
+  size_t i = static_cast<size_t>(hash);
+  if (i < PR_ARRAY_SIZE(kHashLength)) {
+    return kHashLength[i];
+  }
+  ADD_FAILURE() << "Unknown hash: " << hash;
+  return 0;
+}
+
+PRUint16 GetSomeCipherSuiteForHash(SSLHashType hash) {
+  switch (hash) {
+    case ssl_hash_sha256:
+      return TLS_AES_128_GCM_SHA256;
+    case ssl_hash_sha384:
+      return TLS_AES_256_GCM_SHA384;
+    default:
+      ADD_FAILURE() << "Unknown hash: " << hash;
+  }
+  return 0;
+}
+
 const std::string kHashName[] = {"None",    "MD5",     "SHA-1",  "SHA-224",
                                  "SHA-256", "SHA-384", "SHA-512"};
 
 static void ImportKey(ScopedPK11SymKey* to, const DataBuffer& key,
                       SSLHashType hash_type, PK11SlotInfo* slot) {
   ASSERT_LT(hash_type, sizeof(kHashLength));
   ASSERT_LE(kHashLength[hash_type], key.len());
   SECItem key_item = {siBuffer, const_cast<uint8_t*>(key.data()),
-                      static_cast<unsigned int>(kHashLength[hash_type])};
+                      static_cast<unsigned int>(GetHashLength(hash_type))};
 
   PK11SymKey* inner =
       PK11_ImportSymKey(slot, CKM_SSL3_MASTER_KEY_DERIVE, PK11_OriginUnwrap,
                         CKA_DERIVE, &key_item, NULL);
   ASSERT_NE(nullptr, inner);
   to->reset(inner);
 }
 
@@ -129,32 +153,53 @@ class TlsHkdfTest : public ::testing::Te
 
     PK11SymKey* prk = nullptr;
     SECStatus rv = tls13_HkdfExtract(ikmk1.get(), ikmk2.get(), base_hash, &prk);
     ASSERT_EQ(SECSuccess, rv);
     ScopedPK11SymKey prkk(prk);
 
     DumpKey("Output", prkk);
     VerifyKey(prkk, expected);
+
+    // Now test the public wrapper.
+    PRUint16 cs = GetSomeCipherSuiteForHash(base_hash);
+    rv = SSL_HkdfExtract(SSL_LIBRARY_VERSION_TLS_1_3, cs, ikmk1.get(),
+                         ikmk2.get(), &prk);
+    ASSERT_EQ(SECSuccess, rv);
+    ASSERT_NE(nullptr, prk);
+    VerifyKey(ScopedPK11SymKey(prk), expected);
   }
 
   void HkdfExpandLabel(ScopedPK11SymKey* prk, SSLHashType base_hash,
                        const uint8_t* session_hash, size_t session_hash_len,
                        const char* label, size_t label_len,
                        const DataBuffer& expected) {
     std::cerr << "Hash = " << kHashName[base_hash] << std::endl;
 
     std::vector<uint8_t> output(expected.len());
 
     SECStatus rv = tls13_HkdfExpandLabelRaw(prk->get(), base_hash, session_hash,
                                             session_hash_len, label, label_len,
                                             &output[0], output.size());
     ASSERT_EQ(SECSuccess, rv);
     DumpData("Output", &output[0], output.size());
     EXPECT_EQ(0, memcmp(expected.data(), &output[0], expected.len()));
+
+    if (session_hash_len > 0) {
+      return;
+    }
+
+    // Verify that the public API produces the same result.
+    PRUint16 cs = GetSomeCipherSuiteForHash(base_hash);
+    PK11SymKey* secret;
+    rv = SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3, cs, prk->get(),
+                              label, label_len, &secret);
+    EXPECT_EQ(SECSuccess, rv);
+    ASSERT_NE(nullptr, prk);
+    VerifyKey(ScopedPK11SymKey(secret), expected);
   }
 
  protected:
   ScopedPK11SymKey k1_;
   ScopedPK11SymKey k2_;
   SSLHashType hash_type_;
 
  private:
@@ -170,17 +215,17 @@ TEST_P(TlsHkdfTest, HkdfNullNull) {
       {0x33, 0xad, 0x0a, 0x1c, 0x60, 0x7e, 0xc0, 0x3b, 0x09, 0xe6, 0xcd,
        0x98, 0x93, 0x68, 0x0c, 0xe2, 0x10, 0xad, 0xf3, 0x00, 0xaa, 0x1f,
        0x26, 0x60, 0xe1, 0xb2, 0x2e, 0x10, 0xf1, 0x70, 0xf9, 0x2a},
       {0x7e, 0xe8, 0x20, 0x6f, 0x55, 0x70, 0x02, 0x3e, 0x6d, 0xc7, 0x51, 0x9e,
        0xb1, 0x07, 0x3b, 0xc4, 0xe7, 0x91, 0xad, 0x37, 0xb5, 0xc3, 0x82, 0xaa,
        0x10, 0xba, 0x18, 0xe2, 0x35, 0x7e, 0x71, 0x69, 0x71, 0xf9, 0x36, 0x2f,
        0x2c, 0x2f, 0xe2, 0xa7, 0x6b, 0xfd, 0x78, 0xdf, 0xec, 0x4e, 0xa9, 0xb5}};
 
-  const DataBuffer expected_data(tv[hash_type_], kHashLength[hash_type_]);
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
   HkdfExtract(nullptr, nullptr, hash_type_, expected_data);
 }
 
 TEST_P(TlsHkdfTest, HkdfKey1Only) {
   const uint8_t tv[][48] = {
       {/* ssl_hash_none   */},
       {/* ssl_hash_md5    */},
       {/* ssl_hash_sha1   */},
@@ -188,17 +233,17 @@ TEST_P(TlsHkdfTest, HkdfKey1Only) {
       {0x41, 0x6c, 0x53, 0x92, 0xb9, 0xf3, 0x6d, 0xf1, 0x88, 0xe9, 0x0e,
        0xb1, 0x4d, 0x17, 0xbf, 0x0d, 0xa1, 0x90, 0xbf, 0xdb, 0x7f, 0x1f,
        0x49, 0x56, 0xe6, 0xe5, 0x66, 0xa5, 0x69, 0xc8, 0xb1, 0x5c},
       {0x51, 0xb1, 0xd5, 0xb4, 0x59, 0x79, 0x79, 0x08, 0x4a, 0x15, 0xb2, 0xdb,
        0x84, 0xd3, 0xd6, 0xbc, 0xfc, 0x93, 0x45, 0xd9, 0xdc, 0x74, 0xda, 0x1a,
        0x57, 0xc2, 0x76, 0x9f, 0x3f, 0x83, 0x45, 0x2f, 0xf6, 0xf3, 0x56, 0x1f,
        0x58, 0x63, 0xdb, 0x88, 0xda, 0x40, 0xce, 0x63, 0x7d, 0x24, 0x37, 0xf3}};
 
-  const DataBuffer expected_data(tv[hash_type_], kHashLength[hash_type_]);
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
   HkdfExtract(k1_, nullptr, hash_type_, expected_data);
 }
 
 TEST_P(TlsHkdfTest, HkdfKey2Only) {
   const uint8_t tv[][48] = {
       {/* ssl_hash_none   */},
       {/* ssl_hash_md5    */},
       {/* ssl_hash_sha1   */},
@@ -206,17 +251,17 @@ TEST_P(TlsHkdfTest, HkdfKey2Only) {
       {0x16, 0xaf, 0x00, 0x54, 0x3a, 0x56, 0xc8, 0x26, 0xa2, 0xa7, 0xfc,
        0xb6, 0x34, 0x66, 0x8a, 0xfd, 0x36, 0xdc, 0x8e, 0xce, 0xc4, 0xd2,
        0x6c, 0x7a, 0xdc, 0xe3, 0x70, 0x36, 0x3d, 0x60, 0xfa, 0x0b},
       {0x7b, 0x40, 0xf9, 0xef, 0x91, 0xff, 0xc9, 0xd1, 0x29, 0x24, 0x5c, 0xbf,
        0xf8, 0x82, 0x76, 0x68, 0xae, 0x4b, 0x63, 0xe8, 0x03, 0xdd, 0x39, 0xa8,
        0xd4, 0x6a, 0xf6, 0xe5, 0xec, 0xea, 0xf8, 0x7d, 0x91, 0x71, 0x81, 0xf1,
        0xdb, 0x3b, 0xaf, 0xbf, 0xde, 0x71, 0x61, 0x15, 0xeb, 0xb5, 0x5f, 0x68}};
 
-  const DataBuffer expected_data(tv[hash_type_], kHashLength[hash_type_]);
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
   HkdfExtract(nullptr, k2_, hash_type_, expected_data);
 }
 
 TEST_P(TlsHkdfTest, HkdfKey1Key2) {
   const uint8_t tv[][48] = {
       {/* ssl_hash_none   */},
       {/* ssl_hash_md5    */},
       {/* ssl_hash_sha1   */},
@@ -224,17 +269,17 @@ TEST_P(TlsHkdfTest, HkdfKey1Key2) {
       {0xa5, 0x68, 0x02, 0x5a, 0x95, 0xc9, 0x7f, 0x55, 0x38, 0xbc, 0xf7,
        0x97, 0xcc, 0x0f, 0xd5, 0xf6, 0xa8, 0x8d, 0x15, 0xbc, 0x0e, 0x85,
        0x74, 0x70, 0x3c, 0xa3, 0x65, 0xbd, 0x76, 0xcf, 0x9f, 0xd3},
       {0x01, 0x93, 0xc0, 0x07, 0x3f, 0x6a, 0x83, 0x0e, 0x2e, 0x4f, 0xb2, 0x58,
        0xe4, 0x00, 0x08, 0x5c, 0x68, 0x9c, 0x37, 0x32, 0x00, 0x37, 0xff, 0xc3,
        0x1c, 0x5b, 0x98, 0x0b, 0x02, 0x92, 0x3f, 0xfd, 0x73, 0x5a, 0x6f, 0x2a,
        0x95, 0xa3, 0xee, 0xf6, 0xd6, 0x8e, 0x6f, 0x86, 0xea, 0x63, 0xf8, 0x33}};
 
-  const DataBuffer expected_data(tv[hash_type_], kHashLength[hash_type_]);
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
   HkdfExtract(k1_, k2_, hash_type_, expected_data);
 }
 
 TEST_P(TlsHkdfTest, HkdfExpandLabel) {
   const uint8_t tv[][48] = {
       {/* ssl_hash_none   */},
       {/* ssl_hash_md5    */},
       {/* ssl_hash_sha1   */},
@@ -242,19 +287,118 @@ TEST_P(TlsHkdfTest, HkdfExpandLabel) {
       {0x3e, 0x4e, 0x6e, 0xd0, 0xbc, 0xc4, 0xf4, 0xff, 0xf0, 0xf5, 0x69,
        0xd0, 0x6c, 0x1e, 0x0e, 0x10, 0x32, 0xaa, 0xd7, 0xa3, 0xef, 0xf6,
        0xa8, 0x65, 0x8e, 0xbe, 0xee, 0xc7, 0x1f, 0x01, 0x6d, 0x3c},
       {0x41, 0xea, 0x77, 0x09, 0x8c, 0x90, 0x04, 0x10, 0xec, 0xbc, 0x37, 0xd8,
        0x5b, 0x54, 0xcd, 0x7b, 0x08, 0x15, 0x13, 0x20, 0xed, 0x1e, 0x3f, 0x54,
        0x74, 0xf7, 0x8b, 0x06, 0x38, 0x28, 0x06, 0x37, 0x75, 0x23, 0xa2, 0xb7,
        0x34, 0xb1, 0x72, 0x2e, 0x59, 0x6d, 0x5a, 0x31, 0xf5, 0x53, 0xab, 0x99}};
 
-  const DataBuffer expected_data(tv[hash_type_], kHashLength[hash_type_]);
-  HkdfExpandLabel(&k1_, hash_type_, kSessionHash, kHashLength[hash_type_],
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
+  HkdfExpandLabel(&k1_, hash_type_, kSessionHash, GetHashLength(hash_type_),
                   kLabelMasterSecret, strlen(kLabelMasterSecret),
                   expected_data);
 }
 
+TEST_P(TlsHkdfTest, HkdfExpandLabelNoHash) {
+  const uint8_t tv[][48] = {
+      {/* ssl_hash_none   */},
+      {/* ssl_hash_md5    */},
+      {/* ssl_hash_sha1   */},
+      {/* ssl_hash_sha224 */},
+      {0xb7, 0x08, 0x00, 0xe3, 0x8e, 0x48, 0x68, 0x91, 0xb1, 0x0f, 0x5e,
+       0x6f, 0x22, 0x53, 0x6b, 0x84, 0x69, 0x75, 0xaa, 0xa3, 0x2a, 0xe7,
+       0xde, 0xaa, 0xc3, 0xd1, 0xb4, 0x05, 0x22, 0x5c, 0x68, 0xf5},
+      {0x13, 0xd3, 0x36, 0x9f, 0x3c, 0x78, 0xa0, 0x32, 0x40, 0xee, 0x16, 0xe9,
+       0x11, 0x12, 0x66, 0xc7, 0x51, 0xad, 0xd8, 0x3c, 0xa1, 0xa3, 0x97, 0x74,
+       0xd7, 0x45, 0xff, 0xa7, 0x88, 0x9e, 0x52, 0x17, 0x2e, 0xaa, 0x3a, 0xd2,
+       0x35, 0xd8, 0xd5, 0x35, 0xfd, 0x65, 0x70, 0x9f, 0xa9, 0xf9, 0xfa, 0x23}};
+
+  const DataBuffer expected_data(tv[hash_type_], GetHashLength(hash_type_));
+  HkdfExpandLabel(&k1_, hash_type_, nullptr, 0, kLabelMasterSecret,
+                  strlen(kLabelMasterSecret), expected_data);
+}
+
+TEST_P(TlsHkdfTest, BadExtractWrapperInput) {
+  PK11SymKey* key = nullptr;
+
+  // Bad version.
+  EXPECT_EQ(SECFailure,
+            SSL_HkdfExtract(SSL_LIBRARY_VERSION_TLS_1_2, TLS_AES_128_GCM_SHA256,
+                            k1_.get(), k2_.get(), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Bad ciphersuite.
+  EXPECT_EQ(SECFailure,
+            SSL_HkdfExtract(SSL_LIBRARY_VERSION_TLS_1_3, TLS_RSA_WITH_NULL_SHA,
+                            k1_.get(), k2_.get(), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Old ciphersuite.
+  EXPECT_EQ(SECFailure, SSL_HkdfExtract(SSL_LIBRARY_VERSION_TLS_1_3,
+                                        TLS_RSA_WITH_AES_128_CBC_SHA, k1_.get(),
+                                        k2_.get(), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // NULL outparam..
+  EXPECT_EQ(SECFailure, SSL_HkdfExtract(SSL_LIBRARY_VERSION_TLS_1_3,
+                                        TLS_RSA_WITH_AES_128_CBC_SHA, k1_.get(),
+                                        k2_.get(), nullptr));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  EXPECT_EQ(nullptr, key);
+}
+
+TEST_P(TlsHkdfTest, BadDeriveSecretWrapperInput) {
+  PK11SymKey* key = nullptr;
+  static const char* kLabel = "label";
+
+  // Bad version.
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_2,
+                                             TLS_AES_128_GCM_SHA256, k1_.get(),
+                                             kLabel, strlen(kLabel), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Bad ciphersuite.
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3,
+                                             TLS_RSA_WITH_NULL_MD5, k1_.get(),
+                                             kLabel, strlen(kLabel), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Old ciphersuite.
+  EXPECT_EQ(SECFailure,
+            SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3,
+                                 TLS_RSA_WITH_AES_128_CBC_SHA, k1_.get(),
+                                 kLabel, strlen(kLabel), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Null PRK.
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_2,
+                                             TLS_AES_128_GCM_SHA256, nullptr,
+                                             kLabel, strlen(kLabel), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Null, non-zero-length label.
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3,
+                                             TLS_AES_128_GCM_SHA256, k1_.get(),
+                                             nullptr, strlen(kLabel), &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Null, empty label.
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3,
+                                             TLS_AES_128_GCM_SHA256, k1_.get(),
+                                             nullptr, 0, &key));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Null key pointer..
+  EXPECT_EQ(SECFailure, SSL_HkdfDeriveSecret(SSL_LIBRARY_VERSION_TLS_1_3,
+                                             TLS_AES_128_GCM_SHA256, k1_.get(),
+                                             kLabel, strlen(kLabel), nullptr));
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  EXPECT_EQ(nullptr, key);
+}
+
 static const SSLHashType kHashTypes[] = {ssl_hash_sha256, ssl_hash_sha384};
 INSTANTIATE_TEST_CASE_P(AllHashFuncs, TlsHkdfTest,
                         ::testing::ValuesIn(kHashTypes));
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/tls_protect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_protect.cc
@@ -1,217 +1,103 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "tls_protect.h"
+#include "sslproto.h"
 #include "tls_filter.h"
 
-// Do this to avoid having to re-implement HKDF.
-#include "tls13hkdf.h"
-
 namespace nss_test {
 
-AeadCipher::~AeadCipher() {
-  if (key_) {
-    PK11_FreeSymKey(key_);
-  }
-}
-
-bool AeadCipher::Init(PK11SymKey* key, const uint8_t* iv) {
-  key_ = key;
-  if (!key_) return false;
-
-  memcpy(iv_, iv, sizeof(iv_));
-  if (g_ssl_gtest_verbose) {
-    EXPECT_EQ(SECSuccess, PK11_ExtractKeyValue(key_));
-    SECItem* raw_key = PK11_GetKeyData(key_);
-    std::cerr << "key: " << DataBuffer(raw_key->data, raw_key->len)
-              << std::endl;
-    std::cerr << "iv: " << DataBuffer(iv_, 12) << std::endl;
-  }
-  return true;
-}
-
-void AeadCipher::FormatNonce(uint64_t seq, uint8_t* nonce) {
-  memcpy(nonce, iv_, 12);
-
-  for (size_t i = 0; i < 8; ++i) {
-    nonce[12 - (i + 1)] ^= seq & 0xff;
-    seq >>= 8;
-  }
-}
-
-bool AeadCipher::AeadInner(bool decrypt, void* params, size_t param_length,
-                           const uint8_t* in, size_t inlen, uint8_t* out,
-                           size_t* outlen, size_t maxlen) {
-  SECStatus rv;
-  unsigned int uoutlen = 0;
-  SECItem param = {
-      siBuffer, static_cast<unsigned char*>(params),
-      static_cast<unsigned int>(param_length),
-  };
-
-  if (decrypt) {
-    rv = PK11_Decrypt(key_, mech_, &param, out, &uoutlen, maxlen, in, inlen);
-  } else {
-    rv = PK11_Encrypt(key_, mech_, &param, out, &uoutlen, maxlen, in, inlen);
-  }
-  *outlen = (int)uoutlen;
-
-  return rv == SECSuccess;
-}
-
-bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
-                            uint64_t seq, const uint8_t* in, size_t inlen,
-                            uint8_t* out, size_t* outlen, size_t maxlen) {
-  CK_GCM_PARAMS aeadParams;
-  unsigned char nonce[12];
-
-  memset(&aeadParams, 0, sizeof(aeadParams));
-  aeadParams.pIv = nonce;
-  aeadParams.ulIvLen = sizeof(nonce);
-  aeadParams.pAAD = const_cast<uint8_t*>(hdr);
-  aeadParams.ulAADLen = hdr_len;
-  aeadParams.ulTagBits = 128;
-
-  FormatNonce(seq, nonce);
-  return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
-                   inlen, out, outlen, maxlen);
-}
-
-bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t* hdr,
-                                      size_t hdr_len, uint64_t seq,
-                                      const uint8_t* in, size_t inlen,
-                                      uint8_t* out, size_t* outlen,
-                                      size_t maxlen) {
-  CK_NSS_AEAD_PARAMS aeadParams;
-  unsigned char nonce[12];
-
-  memset(&aeadParams, 0, sizeof(aeadParams));
-  aeadParams.pNonce = nonce;
-  aeadParams.ulNonceLen = sizeof(nonce);
-  aeadParams.pAAD = const_cast<uint8_t*>(hdr);
-  aeadParams.ulAADLen = hdr_len;
-  aeadParams.ulTagLen = 16;
-
-  FormatNonce(seq, nonce);
-  return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
-                   inlen, out, outlen, maxlen);
-}
-
 static uint64_t FirstSeqno(bool dtls, uint16_t epoc) {
   if (dtls) {
     return static_cast<uint64_t>(epoc) << 48;
   }
   return 0;
 }
 
 TlsCipherSpec::TlsCipherSpec(bool dtls, uint16_t epoc)
     : dtls_(dtls),
       epoch_(epoc),
       in_seqno_(FirstSeqno(dtls, epoc)),
       out_seqno_(FirstSeqno(dtls, epoc)) {}
 
 bool TlsCipherSpec::SetKeys(SSLCipherSuiteInfo* cipherinfo,
                             PK11SymKey* secret) {
-  CK_MECHANISM_TYPE mech;
-  switch (cipherinfo->symCipher) {
-    case ssl_calg_aes_gcm:
-      aead_.reset(new AeadCipherAesGcm());
-      mech = CKM_AES_GCM;
-      break;
-    case ssl_calg_chacha20:
-      aead_.reset(new AeadCipherChacha20Poly1305());
-      mech = CKM_NSS_CHACHA20_POLY1305;
-      break;
-    default:
-      return false;
-  }
-
-  PK11SymKey* key;
-  const std::string kPurposeKey = "key";
-  SECStatus rv = tls13_HkdfExpandLabel(
-      secret, cipherinfo->kdfHash, NULL, 0, kPurposeKey.c_str(),
-      kPurposeKey.length(), mech, cipherinfo->symKeyBits / 8, &key);
+  SSLAeadContext* ctx;
+  SECStatus rv = SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3,
+                              cipherinfo->cipherSuite, secret, "",
+                              0,  // Use the default labels.
+                              &ctx);
   if (rv != SECSuccess) {
-    ADD_FAILURE() << "unable to derive key for epoch " << epoch_;
     return false;
   }
-
-  // No constant for IV length, but everything we know of uses 12.
-  uint8_t iv[12];
-  const std::string kPurposeIv = "iv";
-  rv = tls13_HkdfExpandLabelRaw(secret, cipherinfo->kdfHash, NULL, 0,
-                                kPurposeIv.c_str(), kPurposeIv.length(), iv,
-                                sizeof(iv));
-  if (rv != SECSuccess) {
-    ADD_FAILURE() << "unable to derive IV for epoch " << epoch_;
-    return false;
-  }
-
-  return aead_->Init(key, iv);
+  aead_.reset(ctx);
+  return true;
 }
 
 bool TlsCipherSpec::Unprotect(const TlsRecordHeader& header,
                               const DataBuffer& ciphertext,
                               DataBuffer* plaintext) {
   if (aead_ == nullptr) {
     return false;
   }
   // Make space.
   plaintext->Allocate(ciphertext.len());
 
   auto header_bytes = header.header();
-  size_t len;
+  unsigned int len;
   uint64_t seqno;
   if (dtls_) {
     seqno = header.sequence_number();
   } else {
     seqno = in_seqno_;
   }
-  bool ret = aead_->Aead(true, header_bytes.data(), header_bytes.len(), seqno,
-                         ciphertext.data(), ciphertext.len(), plaintext->data(),
-                         &len, plaintext->len());
-  if (!ret) {
+  SECStatus rv =
+      SSL_AeadDecrypt(aead_.get(), seqno, header_bytes.data(),
+                      header_bytes.len(), ciphertext.data(), ciphertext.len(),
+                      plaintext->data(), &len, plaintext->len());
+  if (rv != SECSuccess) {
     return false;
   }
 
   RecordUnprotected(seqno);
-  plaintext->Truncate(len);
+  plaintext->Truncate(static_cast<size_t>(len));
 
   return true;
 }
 
 bool TlsCipherSpec::Protect(const TlsRecordHeader& header,
                             const DataBuffer& plaintext,
                             DataBuffer* ciphertext) {
   if (aead_ == nullptr) {
     return false;
   }
   // Make a padded buffer.
   ciphertext->Allocate(plaintext.len() +
                        32);  // Room for any plausible auth tag
-  size_t len;
+  unsigned int len;
 
   DataBuffer header_bytes;
   (void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16);
   uint64_t seqno;
   if (dtls_) {
     seqno = header.sequence_number();
   } else {
     seqno = out_seqno_;
   }
 
-  bool ret = aead_->Aead(false, header_bytes.data(), header_bytes.len(), seqno,
-                         plaintext.data(), plaintext.len(), ciphertext->data(),
-                         &len, ciphertext->len());
-  if (!ret) {
+  SECStatus rv =
+      SSL_AeadEncrypt(aead_.get(), seqno, header_bytes.data(),
+                      header_bytes.len(), plaintext.data(), plaintext.len(),
+                      ciphertext->data(), &len, ciphertext->len());
+  if (rv != SECSuccess) {
     return false;
   }
 
   RecordProtected();
   ciphertext->Truncate(len);
 
   return true;
 }
--- a/security/nss/gtests/ssl_gtest/tls_protect.h
+++ b/security/nss/gtests/ssl_gtest/tls_protect.h
@@ -5,64 +5,26 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef tls_protection_h_
 #define tls_protection_h_
 
 #include <cstdint>
 #include <memory>
 
-#include "databuffer.h"
 #include "pk11pub.h"
 #include "sslt.h"
+#include "sslexp.h"
+
+#include "databuffer.h"
+#include "scoped_ptrs_ssl.h"
 
 namespace nss_test {
 class TlsRecordHeader;
 
-class AeadCipher {
- public:
-  AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {}
-  virtual ~AeadCipher();
-
-  bool Init(PK11SymKey* key, const uint8_t* iv);
-  virtual bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
-                    uint64_t seq, const uint8_t* in, size_t inlen, uint8_t* out,
-                    size_t* outlen, size_t maxlen) = 0;
-
- protected:
-  void FormatNonce(uint64_t seq, uint8_t* nonce);
-  bool AeadInner(bool decrypt, void* params, size_t param_length,
-                 const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
-                 size_t maxlen);
-
-  CK_MECHANISM_TYPE mech_;
-  PK11SymKey* key_;
-  uint8_t iv_[12];
-};
-
-class AeadCipherChacha20Poly1305 : public AeadCipher {
- public:
-  AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {}
-
- protected:
-  bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq,
-            const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
-            size_t maxlen);
-};
-
-class AeadCipherAesGcm : public AeadCipher {
- public:
-  AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {}
-
- protected:
-  bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq,
-            const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
-            size_t maxlen);
-};
-
 // Our analog of ssl3CipherSpec
 class TlsCipherSpec {
  public:
   TlsCipherSpec(bool dtls, uint16_t epoc);
   bool SetKeys(SSLCipherSuiteInfo* cipherinfo, PK11SymKey* secret);
 
   bool Protect(const TlsRecordHeader& header, const DataBuffer& plaintext,
                DataBuffer* ciphertext);
@@ -84,14 +46,14 @@ class TlsCipherSpec {
   bool is_protected() const { return aead_ != nullptr; }
 
  private:
   bool dtls_;
   uint16_t epoch_;
   uint64_t in_seqno_;
   uint64_t out_seqno_;
   bool record_dropped_ = false;
-  std::unique_ptr<AeadCipher> aead_;
+  ScopedSSLAeadContext aead_;
 };
 
 }  // namespace nss_test
 
 #endif
--- a/security/nss/lib/certhigh/certvfy.c
+++ b/security/nss/lib/certhigh/certvfy.c
@@ -32,26 +32,32 @@
  */
 SECStatus
 CERT_CertTimesValid(CERTCertificate *c)
 {
     SECCertTimeValidity valid = CERT_CheckCertValidTimes(c, PR_Now(), PR_TRUE);
     return (valid == secCertTimeValid) ? SECSuccess : SECFailure;
 }
 
-SECStatus
+static SECStatus
 checkKeyParams(const SECAlgorithmID *sigAlgorithm, const SECKEYPublicKey *key)
 {
     SECStatus rv;
     SECOidTag sigAlg;
     SECOidTag curve;
     PRUint32 policyFlags = 0;
     PRInt32 minLen, len;
 
     sigAlg = SECOID_GetAlgorithmTag(sigAlgorithm);
+    rv = NSS_GetAlgorithmPolicy(sigAlg, &policyFlags);
+    if (rv == SECSuccess &&
+        !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) {
+        PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+        return SECFailure;
+    }
 
     switch (sigAlg) {
         case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE:
         case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE:
         case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE:
         case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE:
         case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE:
             if (key->keyType != ecKey) {
--- a/security/nss/lib/freebl/unix_urandom.c
+++ b/security/nss/lib/freebl/unix_urandom.c
@@ -27,17 +27,17 @@ RNG_SystemInfoForRNG(void)
 size_t
 RNG_SystemRNG(void *dest, size_t maxLen)
 {
     int fd;
     int bytes;
     size_t fileBytes = 0;
     unsigned char *buffer = dest;
 
-#if defined(__OpenBSD__) || (defined(LINUX) && defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 25))))
+#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(LINUX) && defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 25))))
     int result;
 
     while (fileBytes < maxLen) {
         size_t getBytes = maxLen - fileBytes;
         if (getBytes > GETENTROPY_MAX_BYTES) {
             getBytes = GETENTROPY_MAX_BYTES;
         }
         result = getentropy(buffer, getBytes);
--- a/security/nss/lib/pk11wrap/pk11pars.c
+++ b/security/nss/lib/pk11wrap/pk11pars.c
@@ -379,28 +379,36 @@ static const oidValDef kxOptList[] = {
     { CIPHER_NAME("DH-RSA"), SEC_OID_TLS_DH_RSA, NSS_USE_ALG_IN_SSL_KX },
     { CIPHER_NAME("DH-DSS"), SEC_OID_TLS_DH_DSS, NSS_USE_ALG_IN_SSL_KX },
     { CIPHER_NAME("ECDHE-ECDSA"), SEC_OID_TLS_ECDHE_ECDSA, NSS_USE_ALG_IN_SSL_KX },
     { CIPHER_NAME("ECDHE-RSA"), SEC_OID_TLS_ECDHE_RSA, NSS_USE_ALG_IN_SSL_KX },
     { CIPHER_NAME("ECDH-ECDSA"), SEC_OID_TLS_ECDH_ECDSA, NSS_USE_ALG_IN_SSL_KX },
     { CIPHER_NAME("ECDH-RSA"), SEC_OID_TLS_ECDH_RSA, NSS_USE_ALG_IN_SSL_KX },
 };
 
+static const oidValDef signOptList[] = {
+    /* Signatures */
+    { CIPHER_NAME("DSA"), SEC_OID_ANSIX9_DSA_SIGNATURE,
+      NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE },
+};
+
 typedef struct {
     const oidValDef *list;
     PRUint32 entries;
     const char *description;
+    PRBool allowEmpty;
 } algListsDef;
 
 static const algListsDef algOptLists[] = {
-    { curveOptList, PR_ARRAY_SIZE(curveOptList), "ECC" },
-    { hashOptList, PR_ARRAY_SIZE(hashOptList), "HASH" },
-    { macOptList, PR_ARRAY_SIZE(macOptList), "MAC" },
-    { cipherOptList, PR_ARRAY_SIZE(cipherOptList), "CIPHER" },
-    { kxOptList, PR_ARRAY_SIZE(kxOptList), "OTHER-KX" },
+    { curveOptList, PR_ARRAY_SIZE(curveOptList), "ECC", PR_FALSE },
+    { hashOptList, PR_ARRAY_SIZE(hashOptList), "HASH", PR_FALSE },
+    { macOptList, PR_ARRAY_SIZE(macOptList), "MAC", PR_FALSE },
+    { cipherOptList, PR_ARRAY_SIZE(cipherOptList), "CIPHER", PR_FALSE },
+    { kxOptList, PR_ARRAY_SIZE(kxOptList), "OTHER-KX", PR_FALSE },
+    { signOptList, PR_ARRAY_SIZE(signOptList), "OTHER-SIGN", PR_TRUE },
 };
 
 static const optionFreeDef sslOptList[] = {
     /* Versions */
     { CIPHER_NAME("SSL2.0"), 0x002 },
     { CIPHER_NAME("SSL3.0"), 0x300 },
     { CIPHER_NAME("SSL3.1"), 0x301 },
     { CIPHER_NAME("TLS1.0"), 0x301 },
@@ -713,17 +721,17 @@ secmod_sanityCheckCryptoPolicy(void)
     fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-SSL-ALG: %u\n", num_ssl_enabled ? sInfo : sWarn, num_ssl_enabled);
     fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-CERT-SIG: %u\n", num_sig_enabled ? sInfo : sWarn, num_sig_enabled);
     if (!num_kx_enabled || !num_ssl_enabled || !num_sig_enabled) {
         haveWarning = PR_TRUE;
     }
     for (i = 0; i < PR_ARRAY_SIZE(algOptLists); i++) {
         const algListsDef *algOptList = &algOptLists[i];
         fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-%s: %u\n", enabledCount[i] ? sInfo : sWarn, algOptList->description, enabledCount[i]);
-        if (!enabledCount[i]) {
+        if (!enabledCount[i] && !algOptList->allowEmpty) {
             haveWarning = PR_TRUE;
         }
     }
     if (haveWarning) {
         PR_SetEnv("NSS_POLICY_WARN=1");
     }
 }
 
--- a/security/nss/lib/ssl/manifest.mn
+++ b/security/nss/lib/ssl/manifest.mn
@@ -50,16 +50,17 @@ CSRCS = \
         ssl3ecc.c \
         tls13con.c \
         tls13exthandle.c \
         tls13hashstate.c \
         tls13hkdf.c \
         tls13replay.c \
         sslcert.c \
         sslgrp.c \
+        sslprimitive.c \
         tls13esni.c \
         $(NULL)
 
 LIBRARY_NAME = ssl
 LIBRARY_VERSION = 3
 
 # This part of the code, including all sub-dirs, can be optimized for size
 export ALLOW_OPT_CODE_SIZE = 1
--- a/security/nss/lib/ssl/ssl.gyp
+++ b/security/nss/lib/ssl/ssl.gyp
@@ -30,16 +30,17 @@
         'sslenum.c',
         'sslerr.c',
         'sslerrstrs.c',
         'sslgrp.c',
         'sslinfo.c',
         'sslinit.c',
         'sslmutex.c',
         'sslnonce.c',
+        'sslprimitive.c',
         'sslreveal.c',
         'sslsecur.c',
         'sslsnce.c',
         'sslsock.c',
         'sslspec.c',
         'ssltrace.c',
         'sslver.c',
         'tls13con.c',
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -59,16 +59,17 @@ static SECStatus ssl3_HandleServerHelloP
 static SECStatus ssl3_HandlePostHelloHandshakeMessage(sslSocket *ss,
                                                       PRUint8 *b,
                                                       PRUint32 length);
 static SECStatus ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags);
 
 static CK_MECHANISM_TYPE ssl3_GetHashMechanismByHashType(SSLHashType hashType);
 static CK_MECHANISM_TYPE ssl3_GetMgfMechanismByHashType(SSLHashType hash);
 PRBool ssl_IsRsaPssSignatureScheme(SSLSignatureScheme scheme);
+PRBool ssl_IsDsaSignatureScheme(SSLSignatureScheme scheme);
 
 const PRUint8 ssl_hello_retry_random[] = {
     0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11,
     0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
     0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E,
     0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C
 };
 PR_STATIC_ASSERT(PR_ARRAY_SIZE(ssl_hello_retry_random) == SSL3_RANDOM_LENGTH);
@@ -548,20 +549,19 @@ SSL_AtomicIncrementLong(long *x)
         PR_ATOMIC_INCREMENT((PRInt32 *)x);
     } else {
         tooLong *tl = (tooLong *)x;
         if (PR_ATOMIC_INCREMENT(&tl->low) == 0)
             PR_ATOMIC_INCREMENT(&tl->high);
     }
 }
 
-static PRBool
-ssl3_CipherSuiteAllowedForVersionRange(
-    ssl3CipherSuite cipherSuite,
-    const SSLVersionRange *vrange)
+PRBool
+ssl3_CipherSuiteAllowedForVersionRange(ssl3CipherSuite cipherSuite,
+                                       const SSLVersionRange *vrange)
 {
     switch (cipherSuite) {
         case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
         case TLS_RSA_WITH_AES_256_CBC_SHA256:
         case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
         case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
         case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
         case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
@@ -907,18 +907,18 @@ count_cipher_suites(sslSocket *ss, PRUin
     }
     return count;
 }
 
 /*
  * Null compression, mac and encryption functions
  */
 SECStatus
-Null_Cipher(void *ctx, unsigned char *output, int *outputLen, int maxOutputLen,
-            const unsigned char *input, int inputLen)
+Null_Cipher(void *ctx, unsigned char *output, unsigned int *outputLen, unsigned int maxOutputLen,
+            const unsigned char *input, unsigned int inputLen)
 {
     if (inputLen > maxOutputLen) {
         *outputLen = 0; /* Match PK11_CipherOp in setting outputLen */
         PORT_SetError(SEC_ERROR_OUTPUT_LEN);
         return SECFailure;
     }
     *outputLen = inputLen;
     if (inputLen > 0 && input != output) {
@@ -1549,25 +1549,25 @@ ssl3_BuildRecordPseudoHeader(DTLSEpoch e
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     return SECSuccess;
 }
 
 static SECStatus
-ssl3_AESGCM(ssl3KeyMaterial *keys,
+ssl3_AESGCM(const ssl3KeyMaterial *keys,
             PRBool doDecrypt,
             unsigned char *out,
-            int *outlen,
-            int maxout,
+            unsigned int *outlen,
+            unsigned int maxout,
             const unsigned char *in,
-            int inlen,
+            unsigned int inlen,
             const unsigned char *additionalData,
-            int additionalDataLen)
+            unsigned int additionalDataLen)
 {
     SECItem param;
     SECStatus rv = SECFailure;
     unsigned char nonce[12];
     unsigned int uOutLen;
     CK_GCM_PARAMS gcmParams;
 
     const int tagSize = 16;
@@ -1611,21 +1611,21 @@ ssl3_AESGCM(ssl3KeyMaterial *keys,
                           maxout, in, inlen);
     }
     *outlen += (int)uOutLen;
 
     return rv;
 }
 
 static SECStatus
-ssl3_ChaCha20Poly1305(ssl3KeyMaterial *keys, PRBool doDecrypt,
-                      unsigned char *out, int *outlen, int maxout,
-                      const unsigned char *in, int inlen,
+ssl3_ChaCha20Poly1305(const ssl3KeyMaterial *keys, PRBool doDecrypt,
+                      unsigned char *out, unsigned int *outlen, unsigned int maxout,
+                      const unsigned char *in, unsigned int inlen,
                       const unsigned char *additionalData,
-                      int additionalDataLen)
+                      unsigned int additionalDataLen)
 {
     size_t i;
     SECItem param;
     SECStatus rv = SECFailure;
     unsigned int uOutLen;
     unsigned char nonce[12];
     CK_NSS_AEAD_PARAMS aeadParams;
 
@@ -2007,17 +2007,17 @@ ssl3_MACEncryptRecord(ssl3CipherSpec *cw
 {
     SECStatus rv;
     PRUint32 macLen = 0;
     PRUint32 fragLen;
     PRUint32 p1Len, p2Len, oddLen = 0;
     unsigned int ivLen = 0;
     unsigned char pseudoHeaderBuf[13];
     sslBuffer pseudoHeader = SSL_BUFFER(pseudoHeaderBuf);
-    int len;
+    unsigned int len;
 
     if (cwSpec->cipherDef->type == type_block &&
         cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
         /* Prepend the per-record explicit IV using technique 2b from
          * RFC 4346 section 6.2.3.2: The IV is a cryptographically
          * strong random number XORed with the CBC residue from the previous
          * record.
          */
@@ -2125,41 +2125,41 @@ ssl3_MACEncryptRecord(ssl3CipherSpec *cw
         }
         if (oddLen) {
             p2Len += oddLen;
             PORT_Assert((blockSize < 2) ||
                         (p2Len % blockSize) == 0);
             memmove(SSL_BUFFER_NEXT(wrBuf) + p1Len, pIn + p1Len, oddLen);
         }
         if (p1Len > 0) {
-            int cipherBytesPart1 = -1;
+            unsigned int cipherBytesPart1 = 0;
             rv = cwSpec->cipher(cwSpec->cipherContext,
                                 SSL_BUFFER_NEXT(wrBuf), /* output */
                                 &cipherBytesPart1,      /* actual outlen */
                                 p1Len,                  /* max outlen */
                                 pIn,
                                 p1Len); /* input, and inputlen */
-            PORT_Assert(rv == SECSuccess && cipherBytesPart1 == (int)p1Len);
-            if (rv != SECSuccess || cipherBytesPart1 != (int)p1Len) {
+            PORT_Assert(rv == SECSuccess && cipherBytesPart1 == p1Len);
+            if (rv != SECSuccess || cipherBytesPart1 != p1Len) {
                 PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
                 return SECFailure;
             }
             rv = sslBuffer_Skip(wrBuf, p1Len, NULL);
             PORT_Assert(rv == SECSuccess);
         }
         if (p2Len > 0) {
-            int cipherBytesPart2 = -1;
+            unsigned int cipherBytesPart2 = 0;
             rv = cwSpec->cipher(cwSpec->cipherContext,
                                 SSL_BUFFER_NEXT(wrBuf),
                                 &cipherBytesPart2, /* output and actual outLen */
                                 p2Len,             /* max outlen */
                                 SSL_BUFFER_NEXT(wrBuf),
                                 p2Len); /* input and inputLen*/
-            PORT_Assert(rv == SECSuccess && cipherBytesPart2 == (int)p2Len);
-            if (rv != SECSuccess || cipherBytesPart2 != (int)p2Len) {
+            PORT_Assert(rv == SECSuccess && cipherBytesPart2 == p2Len);
+            if (rv != SECSuccess || cipherBytesPart2 != p2Len) {
                 PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
                 return SECFailure;
             }
             rv = sslBuffer_Skip(wrBuf, p2Len, NULL);
             PORT_Assert(rv == SECSuccess);
         }
     }
 
@@ -2236,17 +2236,17 @@ ssl_ProtectRecord(sslSocket *ss, ssl3Cip
         rv = sslBuffer_Skip(wrBuf, 2, &lenOffset);
         if (rv != SECSuccess) {
             return SECFailure;
         }
     }
 
 #ifdef UNSAFE_FUZZER_MODE
     {
-        int len;
+        unsigned int len;
         rv = Null_Cipher(NULL, SSL_BUFFER_NEXT(wrBuf), &len,
                          SSL_BUFFER_SPACE(wrBuf), pIn, contentLen);
         if (rv != SECSuccess) {
             return SECFailure; /* error was set */
         }
         rv = sslBuffer_Skip(wrBuf, len, NULL);
         PORT_Assert(rv == SECSuccess); /* Can't fail. */
     }
@@ -4315,16 +4315,32 @@ ssl_IsRsaPssSignatureScheme(SSLSignature
             return PR_TRUE;
 
         default:
             return PR_FALSE;
     }
     return PR_FALSE;
 }
 
+PRBool
+ssl_IsDsaSignatureScheme(SSLSignatureScheme scheme)
+{
+    switch (scheme) {
+        case ssl_sig_dsa_sha256:
+        case ssl_sig_dsa_sha384:
+        case ssl_sig_dsa_sha512:
+        case ssl_sig_dsa_sha1:
+            return PR_TRUE;
+
+        default:
+            return PR_FALSE;
+    }
+    return PR_FALSE;
+}
+
 SSLAuthType
 ssl_SignatureSchemeToAuthType(SSLSignatureScheme scheme)
 {
     switch (scheme) {
         case ssl_sig_rsa_pkcs1_sha1:
         case ssl_sig_rsa_pkcs1_sha1md5:
         case ssl_sig_rsa_pkcs1_sha256:
         case ssl_sig_rsa_pkcs1_sha384:
@@ -6027,16 +6043,23 @@ ssl_CanUseSignatureScheme(SSLSignatureSc
     unsigned int i;
 
     /* Skip RSA-PSS schemes when the certificate's private key slot does
      * not support this signature mechanism. */
     if (ssl_IsRsaPssSignatureScheme(scheme) && !slotDoesPss) {
         return PR_FALSE;
     }
 
+    if (ssl_IsDsaSignatureScheme(scheme) &&
+        (NSS_GetAlgorithmPolicy(SEC_OID_ANSIX9_DSA_SIGNATURE, &policy) ==
+         SECSuccess) &&
+        !(policy & NSS_USE_ALG_IN_SSL_KX)) {
+        return PR_FALSE;
+    }
+
     hashType = ssl_SignatureSchemeToHashType(scheme);
     if (requireSha1 && (hashType != ssl_hash_sha1)) {
         return PR_FALSE;
     }
     hashOID = ssl3_HashTypeToOID(hashType);
     if ((NSS_GetAlgorithmPolicy(hashOID, &policy) == SECSuccess) &&
         !(policy & NSS_USE_ALG_IN_SSL_KX)) {
         return PR_FALSE;
@@ -9505,16 +9528,24 @@ ssl3_EncodeSigAlgs(const sslSocket *ss, 
         SECOidTag hashOID = ssl3_HashTypeToOID(hashType);
 
         /* Skip RSA-PSS schemes if there are no tokens to verify them. */
         if (ssl_IsRsaPssSignatureScheme(ss->ssl3.signatureSchemes[i]) &&
             !PK11_TokenExists(auth_alg_defs[ssl_auth_rsa_pss])) {
             continue;
         }
 
+        /* Skip DSA scheme if it is disabled by policy. */
+        if (ssl_IsDsaSignatureScheme(ss->ssl3.signatureSchemes[i]) &&
+            (NSS_GetAlgorithmPolicy(SEC_OID_ANSIX9_DSA_SIGNATURE, &policy) ==
+             SECSuccess) &&
+            !(policy & NSS_USE_ALG_IN_SSL_KX)) {
+            continue;
+        }
+
         if ((NSS_GetAlgorithmPolicy(hashOID, &policy) != SECSuccess) ||
             (policy & NSS_USE_ALG_IN_SSL_KX)) {
             rv = sslBuffer_AppendNumber(buf, ss->ssl3.signatureSchemes[i], 2);
             if (rv != SECSuccess) {
                 return SECFailure;
             }
 
             found = PR_TRUE;
@@ -12195,17 +12226,17 @@ ssl3_UnprotectRecord(sslSocket *ss,
         spec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
         /* Consume the per-record explicit IV. RFC 4346 Section 6.2.3.2 states
          * "The receiver decrypts the entire GenericBlockCipher structure and
          * then discards the first cipher block corresponding to the IV
          * component." Instead, we decrypt the first cipher block and then
          * discard it before decrypting the rest.
          */
         PRUint8 iv[MAX_IV_LENGTH];
-        int decoded;
+        unsigned int decoded;
 
         ivLen = cipher_def->iv_size;
         if (ivLen < 8 || ivLen > sizeof(iv)) {
             *alert = internal_error;
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
         }
 
@@ -12243,35 +12274,35 @@ ssl3_UnprotectRecord(sslSocket *ss,
         unsigned int decryptedLen =
             cText->buf->len - cipher_def->explicit_nonce_size -
             cipher_def->tag_size;
         rv = ssl3_BuildRecordPseudoHeader(
             spec->epoch, cText->seqNum,
             rType, isTLS, rVersion, IS_DTLS(ss), decryptedLen, &header);
         PORT_Assert(rv == SECSuccess);
         rv = spec->aead(&spec->keyMaterial,
-                        PR_TRUE,                /* do decrypt */
-                        plaintext->buf,         /* out */
-                        (int *)&plaintext->len, /* outlen */
-                        plaintext->space,       /* maxout */
-                        cText->buf->buf,        /* in */
-                        cText->buf->len,        /* inlen */
+                        PR_TRUE,          /* do decrypt */
+                        plaintext->buf,   /* out */
+                        &plaintext->len,  /* outlen */
+                        plaintext->space, /* maxout */
+                        cText->buf->buf,  /* in */
+                        cText->buf->len,  /* inlen */
                         SSL_BUFFER_BASE(&header), SSL_BUFFER_LEN(&header));
         if (rv != SECSuccess) {
             good = 0;
         }
     } else {
         if (cipher_def->type == type_block &&
             ((cText->buf->len - ivLen) % cipher_def->block_size) != 0) {
             goto decrypt_loser;
         }
 
         /* decrypt from cText buf to plaintext. */
         rv = spec->cipher(
-            spec->cipherContext, plaintext->buf, (int *)&plaintext->len,
+            spec->cipherContext, plaintext->buf, &plaintext->len,
             plaintext->space, cText->buf->buf + ivLen, cText->buf->len - ivLen);
         if (rv != SECSuccess) {
             goto decrypt_loser;
         }
 
         PRINT_BUF(80, (ss, "cleartext:", plaintext->buf, plaintext->len));
 
         originalLen = plaintext->len;
@@ -12543,17 +12574,17 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Cip
      * completes in DTLS 1.3. These can look like valid TLS 1.2 application_data
      * records in epoch 0, which is never valid. Pretend they didn't decrypt. */
     if (spec->epoch == 0 && rType == ssl_ct_application_data) {
         PORT_SetError(SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA);
         alert = unexpected_message;
         rv = SECFailure;
     } else {
 #ifdef UNSAFE_FUZZER_MODE
-        rv = Null_Cipher(NULL, plaintext->buf, (int *)&plaintext->len,
+        rv = Null_Cipher(NULL, plaintext->buf, &plaintext->len,
                          plaintext->space, cText->buf->buf, cText->buf->len);
 #else
         /* IMPORTANT: Unprotect functions MUST NOT send alerts
          * because we still hold the spec read lock. Instead, if they
          * return SECFailure, they set *alert to the alert to be sent. */
         if (spec->version < SSL_LIBRARY_VERSION_TLS_1_3 ||
             spec->epoch == 0) {
             rv = ssl3_UnprotectRecord(ss, spec, cText, plaintext, &alert);
--- a/security/nss/lib/ssl/ssl3gthr.c
+++ b/security/nss/lib/ssl/ssl3gthr.c
@@ -402,17 +402,17 @@ ssl3_GatherCompleteHandshake(sslSocket *
     int rv;
     SSL3Ciphertext cText;
     PRBool keepGoing = PR_TRUE;
 
     if (ss->ssl3.fatalAlertSent) {
         SSL_TRC(3, ("%d: SSL3[%d] Cannot gather data; fatal alert already sent",
                     SSL_GETPID(), ss->fd));
         PORT_SetError(SSL_ERROR_HANDSHAKE_FAILED);
-        return SECFailure;
+        return -1;
     }
 
     SSL_TRC(30, ("%d: SSL3[%d]: ssl3_GatherCompleteHandshake",
                  SSL_GETPID(), ss->fd));
 
     /* ssl3_HandleRecord may end up eventually calling ssl_FinishHandshake,
      * which requires the 1stHandshakeLock, which must be acquired before the
      * RecvBufLock.
@@ -422,31 +422,35 @@ ssl3_GatherCompleteHandshake(sslSocket *
 
     do {
         PRBool processingEarlyData;
 
         ssl_GetSSL3HandshakeLock(ss);
 
         processingEarlyData = ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted;
 
-        /* If we have a detached record layer, don't ever gather. */
-        if (ss->recordWriteCallback) {
-            ssl_ReleaseSSL3HandshakeLock(ss);
-            PORT_SetError(PR_WOULD_BLOCK_ERROR);
-            return (int)SECFailure;
-        }
-
         /* Without this, we may end up wrongly reporting
          * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the
          * peer while we are waiting to be restarted.
          */
         if (ss->ssl3.hs.restartTarget) {
             ssl_ReleaseSSL3HandshakeLock(ss);
             PORT_SetError(PR_WOULD_BLOCK_ERROR);
-            return (int)SECFailure;
+            return -1;
+        }
+
+        /* If we have a detached record layer, don't ever gather. */
+        if (ss->recordWriteCallback) {
+            PRBool done = ss->firstHsDone;
+            ssl_ReleaseSSL3HandshakeLock(ss);
+            if (done) {
+                return 1;
+            }
+            PORT_SetError(PR_WOULD_BLOCK_ERROR);
+            return -1;
         }
 
         ssl_ReleaseSSL3HandshakeLock(ss);
 
         /* State for SSLv2 client hello support. */
         ssl2Gather ssl2gs = { PR_FALSE, 0 };
         ssl2Gather *ssl2gs_ptr = NULL;
 
@@ -658,17 +662,18 @@ SSLExp_RecordLayerData(PRFileDesc *fd, P
     if (rv != SECSuccess) {
         goto loser;
     }
 
     /* ...and process it.  Just saving application data is enough for it to be
      * available to PR_Read(). */
     if (contentType != ssl_ct_application_data) {
         rv = ssl3_HandleNonApplicationData(ss, contentType, 0, 0, &ss->gs.buf);
-        if (rv != SECSuccess) {
+        /* This occasionally blocks, but that's OK here. */
+        if (rv != SECSuccess && PORT_GetError() != PR_WOULD_BLOCK_ERROR) {
             goto loser;
         }
     }
 
     ssl_ReleaseRecvBufLock(ss);
     ssl_Release1stHandshakeLock(ss);
     return SECSuccess;
 
--- a/security/nss/lib/ssl/sslexp.h
+++ b/security/nss/lib/ssl/sslexp.h
@@ -621,14 +621,88 @@ typedef SECStatus(PR_CALLBACK *SSLRecord
  * See SSL_RecordLayerWriteCallback() for details on epochs.
  */
 #define SSL_GetCurrentEpoch(fd, readEpoch, writeEpoch)             \
     SSL_EXPERIMENTAL_API("SSL_GetCurrentEpoch",                    \
                          (PRFileDesc * _fd, PRUint16 * _readEpoch, \
                           PRUint16 * _writeEpoch),                 \
                          (fd, readEpoch, writeEpoch))
 
+/*
+ * The following AEAD functions expose an AEAD primitive that uses a ciphersuite
+ * to set parameters.  The ciphersuite determines the Hash function used by
+ * HKDF, the AEAD function, and the size of key and IV.  This is only supported
+ * for TLS 1.3.
+ *
+ * The key and IV are generated using the TLS KDF with a custom label.  That is
+ * HKDF-Expand-Label(secret, labelPrefix + " key" or " iv", "", L).
+ *
+ * The encrypt and decrypt functions use a nonce construction identical to that
+ * used in TLS.  The lower bits of the IV are XORed with the 64-bit counter to
+ * produce the nonce.  Otherwise, this is an AEAD interface similar to that
+ * described in RFC 5116.
+ */
+typedef struct SSLAeadContextStr SSLAeadContext;
+
+#define SSL_MakeAead(version, cipherSuite, secret,                  \
+                     labelPrefix, labelPrefixLen, ctx)              \
+    SSL_EXPERIMENTAL_API("SSL_MakeAead",                            \
+                         (PRUint16 _version, PRUint16 _cipherSuite, \
+                          PK11SymKey * _secret,                     \
+                          const char *_labelPrefix,                 \
+                          unsigned int _labelPrefixLen,             \
+                          SSLAeadContext **_ctx),                   \
+                         (version, cipherSuite, secret,             \
+                          labelPrefix, labelPrefixLen, ctx))
+
+#define SSL_AeadEncrypt(ctx, counter, aad, aadLen, in, inLen,            \
+                        output, outputLen, maxOutputLen)                 \
+    SSL_EXPERIMENTAL_API("SSL_AeadEncrypt",                              \
+                         (const SSLAeadContext *_ctx, PRUint64 _counter, \
+                          const PRUint8 *_aad, unsigned int _aadLen,     \
+                          const PRUint8 *_in, unsigned int _inLen,       \
+                          PRUint8 *_out, unsigned int *_outLen,          \
+                          unsigned int _maxOut),                         \
+                         (ctx, counter, aad, aadLen, in, inLen,          \
+                          output, outputLen, maxOutputLen))
+
+#define SSL_AeadDecrypt(ctx, counter, aad, aadLen, in, inLen,            \
+                        output, outputLen, maxOutputLen)                 \
+    SSL_EXPERIMENTAL_API("SSL_AeadDecrypt",                              \
+                         (const SSLAeadContext *_ctx, PRUint64 _counter, \
+                          const PRUint8 *_aad, unsigned int _aadLen,     \
+                          const PRUint8 *_in, unsigned int _inLen,       \
+                          PRUint8 *_output, unsigned int *_outLen,       \
+                          unsigned int _maxOut),                         \
+                         (ctx, counter, aad, aadLen, in, inLen,          \
+                          output, outputLen, maxOutputLen))
+
+#define SSL_DestroyAead(ctx)                      \
+    SSL_EXPERIMENTAL_API("SSL_DestroyAead",       \
+                         (SSLAeadContext * _ctx), \
+                         (ctx))
+
+/* SSL_HkdfExtract and SSL_HkdfExpandLabel implement the functions from TLS,
+ * using the version and ciphersuite to set parameters. This allows callers to
+ * use these TLS functions as a KDF. This is only supported for TLS 1.3. */
+#define SSL_HkdfExtract(version, cipherSuite, salt, ikm, keyp)      \
+    SSL_EXPERIMENTAL_API("SSL_HkdfExtract",                         \
+                         (PRUint16 _version, PRUint16 _cipherSuite, \
+                          PK11SymKey * _salt, PK11SymKey * _ikm,    \
+                          PK11SymKey * *_keyp),                     \
+                         (version, cipherSuite, salt, ikm, keyp))
+
+#define SSL_HkdfDeriveSecret(version, cipherSuite, prk,               \
+                             label, labelLen, keyp)                   \
+    SSL_EXPERIMENTAL_API("SSL_HkdfDeriveSecret",                      \
+                         (PRUint16 _version, PRUint16 _cipherSuite,   \
+                          PK11SymKey * _prk,                          \
+                          const char *_label, unsigned int _labelLen, \
+                          PK11SymKey **_keyp),                        \
+                         (version, cipherSuite, prk,                  \
+                          label, labelLen, keyp))
+
 /* Deprecated experimental APIs */
 #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API
 
 SEC_END_PROTOS
 
 #endif /* __sslexp_h_ */
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -1204,19 +1204,19 @@ extern void ssl_FinishHandshake(sslSocke
 
 extern SECStatus ssl_CipherPolicySet(PRInt32 which, PRInt32 policy);
 
 extern SECStatus ssl_CipherPrefSetDefault(PRInt32 which, PRBool enabled);
 
 extern SECStatus ssl3_ConstrainRangeByPolicy(void);
 
 extern SECStatus ssl3_InitState(sslSocket *ss);
-extern SECStatus Null_Cipher(void *ctx, unsigned char *output, int *outputLen,
-                             int maxOutputLen, const unsigned char *input,
-                             int inputLen);
+extern SECStatus Null_Cipher(void *ctx, unsigned char *output, unsigned int *outputLen,
+                             unsigned int maxOutputLen, const unsigned char *input,
+                             unsigned int inputLen);
 extern void ssl3_RestartHandshakeHashes(sslSocket *ss);
 extern SECStatus ssl3_UpdateHandshakeHashes(sslSocket *ss,
                                             const unsigned char *b,
                                             unsigned int l);
 SECStatus
 ssl_HashHandshakeMessageInt(sslSocket *ss, SSLHandshakeType type,
                             PRUint32 dtlsSeq,
                             const PRUint8 *b, PRUint32 length);
@@ -1658,16 +1658,18 @@ PK11SymKey *ssl3_GetWrappingKey(sslSocke
                                 PK11SlotInfo *masterSecretSlot,
                                 CK_MECHANISM_TYPE masterWrapMech,
                                 void *pwArg);
 SECStatus ssl3_FillInCachedSID(sslSocket *ss, sslSessionID *sid,
                                PK11SymKey *secret);
 const ssl3CipherSuiteDef *ssl_LookupCipherSuiteDef(ssl3CipherSuite suite);
 const ssl3CipherSuiteCfg *ssl_LookupCipherSuiteCfg(ssl3CipherSuite suite,
                                                    const ssl3CipherSuiteCfg *suites);
+PRBool ssl3_CipherSuiteAllowedForVersionRange(ssl3CipherSuite cipherSuite,
+                                              const SSLVersionRange *vrange);
 
 SECStatus ssl3_SelectServerCert(sslSocket *ss);
 SECStatus ssl_PickSignatureScheme(sslSocket *ss,
                                   CERTCertificate *cert,
                                   SECKEYPublicKey *pubKey,
                                   SECKEYPrivateKey *privKey,
                                   const SSLSignatureScheme *peerSchemes,
                                   unsigned int peerSchemeCount,
@@ -1753,16 +1755,35 @@ SECStatus SSLExp_RecordLayerWriteCallbac
 SECStatus SSLExp_RecordLayerData(PRFileDesc *fd, PRUint16 epoch,
                                  SSLContentType contentType,
                                  const PRUint8 *data, unsigned int len);
 SECStatus SSLExp_GetCurrentEpoch(PRFileDesc *fd, PRUint16 *readEpoch,
                                  PRUint16 *writeEpoch);
 
 #define SSLResumptionTokenVersion 2
 
+SECStatus SSLExp_MakeAead(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *secret,
+                          const char *labelPrefix, unsigned int labelPrefixLen,
+                          SSLAeadContext **ctx);
+SECStatus SSLExp_DestroyAead(SSLAeadContext *ctx);
+SECStatus SSLExp_AeadEncrypt(const SSLAeadContext *ctx, PRUint64 counter,
+                             const PRUint8 *aad, unsigned int aadLen,
+                             const PRUint8 *plaintext, unsigned int plaintextLen,
+                             PRUint8 *out, unsigned int *outLen, unsigned int maxOut);
+SECStatus SSLExp_AeadDecrypt(const SSLAeadContext *ctx, PRUint64 counter,
+                             const PRUint8 *aad, unsigned int aadLen,
+                             const PRUint8 *plaintext, unsigned int plaintextLen,
+                             PRUint8 *out, unsigned int *outLen, unsigned int maxOut);
+
+SECStatus SSLExp_HkdfExtract(PRUint16 version, PRUint16 cipherSuite,
+                             PK11SymKey *salt, PK11SymKey *ikm, PK11SymKey **keyp);
+SECStatus SSLExp_HkdfDeriveSecret(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+                                  const char *label, unsigned int labelLen,
+                                  PK11SymKey **key);
+
 SEC_END_PROTOS
 
 #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS)
 #define SSL_GETPID getpid
 #elif defined(WIN32)
 extern int __cdecl _getpid(void);
 #define SSL_GETPID _getpid
 #else
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ssl/sslprimitive.c
@@ -0,0 +1,249 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * SSL Primitives: Public HKDF and AEAD Functions
+ *
+ * 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 "keyhi.h"
+#include "pk11pub.h"
+#include "sechash.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include "sslimpl.h"
+#include "tls13con.h"
+#include "tls13hkdf.h"
+
+struct SSLAeadContextStr {
+    CK_MECHANISM_TYPE mech;
+    ssl3KeyMaterial keys;
+};
+
+static SECStatus
+tls13_GetHashAndCipher(PRUint16 version, PRUint16 cipherSuite,
+                       SSLHashType *hash, const ssl3BulkCipherDef **cipher)
+{
+    if (version < SSL_LIBRARY_VERSION_TLS_1_3) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    // Lookup and check the suite.
+    SSLVersionRange vrange = { version, version };
+    if (!ssl3_CipherSuiteAllowedForVersionRange(cipherSuite, &vrange)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    const ssl3CipherSuiteDef *suiteDef = ssl_LookupCipherSuiteDef(cipherSuite);
+    const ssl3BulkCipherDef *cipherDef = ssl_GetBulkCipherDef(suiteDef);
+    if (cipherDef->type != type_aead) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    *hash = suiteDef->prf_hash;
+    *cipher = cipherDef;
+    return SECSuccess;
+}
+
+SECStatus
+SSLExp_MakeAead(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *secret,
+                const char *labelPrefix, unsigned int labelPrefixLen,
+                SSLAeadContext **ctx)
+{
+    SSLAeadContext *out = NULL;
+    char label[255]; // Maximum length label.
+    static const char *const keySuffix = "key";
+    static const char *const ivSuffix = "iv";
+
+    PORT_Assert(strlen(keySuffix) >= strlen(ivSuffix));
+    if (secret == NULL || ctx == NULL ||
+        (labelPrefix == NULL && labelPrefixLen > 0) ||
+        labelPrefixLen + strlen(keySuffix) > sizeof(label)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        goto loser;
+    }
+
+    SSLHashType hash;
+    const ssl3BulkCipherDef *cipher;
+    SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+                                          &hash, &cipher);
+    if (rv != SECSuccess) {
+        goto loser; /* Code already set. */
+    }
+
+    out = PORT_ZNew(SSLAeadContext);
+    if (out == NULL) {
+        goto loser;
+    }
+    out->mech = ssl3_Alg2Mech(cipher->calg);
+
+    memcpy(label, labelPrefix, labelPrefixLen);
+    memcpy(label + labelPrefixLen, ivSuffix, strlen(ivSuffix));
+    unsigned int labelLen = labelPrefixLen + strlen(ivSuffix);
+    unsigned int ivLen = cipher->iv_size + cipher->explicit_nonce_size;
+    rv = tls13_HkdfExpandLabelRaw(secret, hash,
+                                  NULL, 0, // Handshake hash.
+                                  label, labelLen,
+                                  out->keys.iv, ivLen);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    memcpy(label + labelPrefixLen, keySuffix, strlen(keySuffix));
+    labelLen = labelPrefixLen + strlen(keySuffix);
+    rv = tls13_HkdfExpandLabel(secret, hash,
+                               NULL, 0, // Handshake hash.
+                               label, labelLen,
+                               out->mech, cipher->key_size, &out->keys.key);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    *ctx = out;
+    return SECSuccess;
+
+loser:
+    SSLExp_DestroyAead(out);
+    return SECFailure;
+}
+
+SECStatus
+SSLExp_DestroyAead(SSLAeadContext *ctx)
+{
+    if (!ctx) {
+        return SECSuccess;
+    }
+
+    PK11_FreeSymKey(ctx->keys.key);
+    PORT_ZFree(ctx, sizeof(*ctx));
+    return SECSuccess;
+}
+
+/* Bug 1529440 exists to refactor this and the other AEAD uses. */
+static SECStatus
+ssl_AeadInner(const SSLAeadContext *ctx, PRBool decrypt, PRUint64 counter,
+              const PRUint8 *aad, unsigned int aadLen,
+              const PRUint8 *plaintext, unsigned int plaintextLen,
+              PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+    if (ctx == NULL || (aad == NULL && aadLen > 0) || plaintext == NULL ||
+        out == NULL || outLen == NULL) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    // Setup the nonce.
+    PRUint8 nonce[12] = { 0 };
+    sslBuffer nonceBuf = SSL_BUFFER_FIXED(nonce + sizeof(nonce) - sizeof(counter),
+                                          sizeof(counter));
+    SECStatus rv = sslBuffer_AppendNumber(&nonceBuf, counter, sizeof(counter));
+    if (rv != SECSuccess) {
+        PORT_Assert(0);
+        return SECFailure;
+    }
+    for (int i = 0; i < sizeof(nonce); ++i) {
+        nonce[i] ^= ctx->keys.iv[i];
+    }
+
+    // Build AEAD parameters.
+    CK_GCM_PARAMS gcmParams = { 0 };
+    CK_NSS_AEAD_PARAMS aeadParams = { 0 };
+    unsigned char *params;
+    unsigned int paramsLen;
+    switch (ctx->mech) {
+        case CKM_AES_GCM:
+            gcmParams.pIv = nonce;
+            gcmParams.ulIvLen = sizeof(nonce);
+            gcmParams.pAAD = (unsigned char *)aad; // const cast :(
+            gcmParams.ulAADLen = aadLen;
+            gcmParams.ulTagBits = 128; // GCM measures in bits.
+            params = (unsigned char *)&gcmParams;
+            paramsLen = sizeof(gcmParams);
+            break;
+
+        case CKM_NSS_CHACHA20_POLY1305:
+            aeadParams.pNonce = nonce;
+            aeadParams.ulNonceLen = sizeof(nonce);
+            aeadParams.pAAD = (unsigned char *)aad; // const cast :(
+            aeadParams.ulAADLen = aadLen;
+            aeadParams.ulTagLen = 16; // AEAD measures in octets.
+            params = (unsigned char *)&aeadParams;
+            paramsLen = sizeof(aeadParams);
+            break;
+
+        default:
+            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+            return SECFailure;
+    }
+
+    return tls13_AEAD(&ctx->keys, decrypt, out, outLen, maxOut,
+                      plaintext, plaintextLen, ctx->mech, params, paramsLen);
+}
+
+SECStatus
+SSLExp_AeadEncrypt(const SSLAeadContext *ctx, PRUint64 counter,
+                   const PRUint8 *aad, unsigned int aadLen,
+                   const PRUint8 *plaintext, unsigned int plaintextLen,
+                   PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+    // false == encrypt
+    return ssl_AeadInner(ctx, PR_FALSE, counter, aad, aadLen,
+                         plaintext, plaintextLen, out, outLen, maxOut);
+}
+
+SECStatus
+SSLExp_AeadDecrypt(const SSLAeadContext *ctx, PRUint64 counter,
+                   const PRUint8 *aad, unsigned int aadLen,
+                   const PRUint8 *plaintext, unsigned int plaintextLen,
+                   PRUint8 *out, unsigned int *outLen, unsigned int maxOut)
+{
+    // true == decrypt
+    return ssl_AeadInner(ctx, PR_TRUE, counter, aad, aadLen,
+                         plaintext, plaintextLen, out, outLen, maxOut);
+}
+
+SECStatus
+SSLExp_HkdfExtract(PRUint16 version, PRUint16 cipherSuite,
+                   PK11SymKey *salt, PK11SymKey *ikm, PK11SymKey **keyp)
+{
+    if (keyp == NULL) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    SSLHashType hash;
+    const ssl3BulkCipherDef *cipher; /* Unused here. */
+    SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+                                          &hash, &cipher);
+    if (rv != SECSuccess) {
+        return SECFailure; /* Code already set. */
+    }
+    return tls13_HkdfExtract(salt, ikm, hash, keyp);
+}
+
+SECStatus
+SSLExp_HkdfDeriveSecret(PRUint16 version, PRUint16 cipherSuite, PK11SymKey *prk,
+                        const char *label, unsigned int labelLen,
+                        PK11SymKey **keyp)
+{
+    if (prk == NULL || keyp == NULL ||
+        label == NULL || labelLen == 0) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    SSLHashType hash;
+    const ssl3BulkCipherDef *cipher; /* Unused here. */
+    SECStatus rv = tls13_GetHashAndCipher(version, cipherSuite,
+                                          &hash, &cipher);
+    if (rv != SECSuccess) {
+        return SECFailure; /* Code already set. */
+    }
+    return tls13_HkdfExpandLabel(prk, hash, NULL, 0, label, labelLen,
+                                 tls13_GetHkdfMechanismForHash(hash),
+                                 tls13_GetHashSizeForHash(hash), keyp);
+}
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -4036,25 +4036,31 @@ SSL_CanBypass(CERTCertificate *cert, SEC
     {                      \
         "SSL_" #n, SSL_##n \
     }
 struct {
     const char *const name;
     void *function;
 } ssl_experimental_functions[] = {
 #ifndef SSL_DISABLE_EXPERIMENTAL_API
+    EXP(AeadDecrypt),
+    EXP(AeadEncrypt),
+    EXP(DestroyAead),
     EXP(DestroyResumptionTokenInfo),
     EXP(EnableESNI),
     EXP(EncodeESNIKeys),
     EXP(GetCurrentEpoch),
     EXP(GetExtensionSupport),
     EXP(GetResumptionTokenInfo),
     EXP(HelloRetryRequestCallback),
     EXP(InstallExtensionHooks),
+    EXP(HkdfExtract),
+    EXP(HkdfDeriveSecret),
     EXP(KeyUpdate),
+    EXP(MakeAead),
     EXP(RecordLayerData),
     EXP(RecordLayerWriteCallback),
     EXP(SecretCallback),
     EXP(SendCertificateRequest),
     EXP(SendSessionTicket),
     EXP(SetESNIKeyPair),
     EXP(SetMaxEarlyDataSize),
     EXP(SetResumptionTokenCallback),
--- a/security/nss/lib/ssl/sslspec.h
+++ b/security/nss/lib/ssl/sslspec.h
@@ -96,30 +96,30 @@ typedef struct {
     PK11SymKey *key;
     PK11SymKey *macKey;
     PK11Context *macContext;
     PRUint8 iv[MAX_IV_LENGTH];
 } ssl3KeyMaterial;
 
 typedef SECStatus (*SSLCipher)(void *context,
                                unsigned char *out,
-                               int *outlen,
-                               int maxout,
+                               unsigned int *outlen,
+                               unsigned int maxout,
                                const unsigned char *in,
-                               int inlen);
+                               unsigned int inlen);
 typedef SECStatus (*SSLAEADCipher)(
-    ssl3KeyMaterial *keys,
+    const ssl3KeyMaterial *keys,
     PRBool doDecrypt,
     unsigned char *out,
-    int *outlen,
-    int maxout,
+    unsigned int *outlen,
+    unsigned int maxout,
     const unsigned char *in,
-    int inlen,
+    unsigned int inlen,
     const unsigned char *additionalData,
-    int additionalDataLen);
+    unsigned int additionalDataLen);
 
 /* The DTLS anti-replay window in number of packets. Defined here because we
  * need it in the cipher spec. Note that this is a ring buffer but left and
  * right represent the true window, with modular arithmetic used to map them
  * onto the buffer.
  */
 #define DTLS_RECVD_RECORDS_WINDOW 1024
 #define RECORD_SEQ_MASK ((1ULL << 48) - 1)
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -23,28 +23,34 @@
 #include "tls13err.h"
 #include "tls13esni.h"
 #include "tls13exthandle.h"
 #include "tls13hashstate.h"
 
 static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
                                      SSLSecretDirection install,
                                      PRBool deleteSecret);
-static SECStatus tls13_AESGCM(
-    ssl3KeyMaterial *keys,
-    PRBool doDecrypt,
-    unsigned char *out, int *outlen, int maxout,
-    const unsigned char *in, int inlen,
-    const unsigned char *additionalData, int additionalDataLen);
-static SECStatus tls13_ChaCha20Poly1305(
-    ssl3KeyMaterial *keys,
-    PRBool doDecrypt,
-    unsigned char *out, int *outlen, int maxout,
-    const unsigned char *in, int inlen,
-    const unsigned char *additionalData, int additionalDataLen);
+static SECStatus tls13_AESGCM(const ssl3KeyMaterial *keys,
+                              PRBool doDecrypt,
+                              unsigned char *out,
+                              unsigned int *outlen,
+                              unsigned int maxout,
+                              const unsigned char *in,
+                              unsigned int inlen,
+                              const unsigned char *additionalData,
+                              unsigned int additionalDataLen);
+static SECStatus tls13_ChaCha20Poly1305(const ssl3KeyMaterial *keys,
+                                        PRBool doDecrypt,
+                                        unsigned char *out,
+                                        unsigned int *outlen,
+                                        unsigned int maxout,
+                                        const unsigned char *in,
+                                        unsigned int inlen,
+                                        const unsigned char *additionalData,
+                                        unsigned int additionalDataLen);
 static SECStatus tls13_SendServerHelloSequence(sslSocket *ss);
 static SECStatus tls13_SendEncryptedExtensions(sslSocket *ss);
 static void tls13_SetKeyExchangeType(sslSocket *ss, const sslNamedGroupDef *group);
 static SECStatus tls13_HandleClientKeyShare(sslSocket *ss,
                                             TLS13KeyShareEntry *peerShare);
 static SECStatus tls13_SendHelloRetryRequest(
     sslSocket *ss, const sslNamedGroupDef *selectedGroup,
     const PRUint8 *token, unsigned int tokenLen);
@@ -288,17 +294,17 @@ tls13_GetHashSizeForHash(SSLHashType has
 }
 
 unsigned int
 tls13_GetHashSize(const sslSocket *ss)
 {
     return tls13_GetHashSizeForHash(tls13_GetHash(ss));
 }
 
-static CK_MECHANISM_TYPE
+CK_MECHANISM_TYPE
 tls13_GetHkdfMechanismForHash(SSLHashType hash)
 {
     switch (hash) {
         case ssl_hash_sha256:
             return CKM_NSS_HKDF_SHA256;
         case ssl_hash_sha384:
             return CKM_NSS_HKDF_SHA384;
         default:
@@ -3706,17 +3712,17 @@ tls13_DestroyEarlyData(PRCList *list)
  * The resulting quantity (of length iv_length) is used as the per-
  * record nonce.
  *
  * Existing suites have the same nonce size: N_MIN = N_MAX = 12 bytes
  *
  * See RFC 5288 and https://tools.ietf.org/html/draft-ietf-tls-chacha20-poly1305-04#section-2
  */
 static void
-tls13_WriteNonce(ssl3KeyMaterial *keys,
+tls13_WriteNonce(const ssl3KeyMaterial *keys,
                  const unsigned char *seqNumBuf, unsigned int seqNumLen,
                  unsigned char *nonce, unsigned int nonceLen)
 {
     size_t i;
 
     PORT_Assert(nonceLen == 12);
     memcpy(nonce, keys->iv, 12);
 
@@ -3729,51 +3735,45 @@ tls13_WriteNonce(ssl3KeyMaterial *keys,
 }
 
 /* Implement the SSLAEADCipher interface defined in sslimpl.h.
  *
  * That interface takes the additional data (see below) and reinterprets that as
  * a sequence number. In TLS 1.3 there is no additional data so this value is
  * just the encoded sequence number.
  */
-static SECStatus
-tls13_AEAD(ssl3KeyMaterial *keys, PRBool doDecrypt,
-           unsigned char *out, int *outlen, int maxout,
-           const unsigned char *in, int inlen,
+SECStatus
+tls13_AEAD(const ssl3KeyMaterial *keys, PRBool doDecrypt,
+           unsigned char *out, unsigned int *outlen, unsigned int maxout,
+           const unsigned char *in, unsigned int inlen,
            CK_MECHANISM_TYPE mechanism,
            unsigned char *aeadParams, unsigned int aeadParamLength)
 {
-    SECStatus rv;
-    unsigned int uOutLen = 0;
     SECItem param = {
         siBuffer, aeadParams, aeadParamLength
     };
 
     if (doDecrypt) {
-        rv = PK11_Decrypt(keys->key, mechanism, &param,
-                          out, &uOutLen, maxout, in, inlen);
-    } else {
-        rv = PK11_Encrypt(keys->key, mechanism, &param,
-                          out, &uOutLen, maxout, in, inlen);
-    }
-    *outlen = (int)uOutLen;
-
-    return rv;
+        return PK11_Decrypt(keys->key, mechanism, &param,
+                            out, outlen, maxout, in, inlen);
+    }
+    return PK11_Encrypt(keys->key, mechanism, &param,
+                        out, outlen, maxout, in, inlen);
 }
 
 static SECStatus
-tls13_AESGCM(ssl3KeyMaterial *keys,
+tls13_AESGCM(const ssl3KeyMaterial *keys,
              PRBool doDecrypt,
              unsigned char *out,
-             int *outlen,
-             int maxout,
+             unsigned int *outlen,
+             unsigned int maxout,
              const unsigned char *in,
-             int inlen,
+             unsigned int inlen,
              const unsigned char *additionalData,
-             int additionalDataLen)
+             unsigned int additionalDataLen)
 {
     CK_GCM_PARAMS gcmParams;
     unsigned char nonce[12];
 
     PORT_Assert(additionalDataLen >= 8);
     memset(&gcmParams, 0, sizeof(gcmParams));
     gcmParams.pIv = nonce;
     gcmParams.ulIvLen = sizeof(nonce);
@@ -3784,21 +3784,21 @@ tls13_AESGCM(ssl3KeyMaterial *keys,
     tls13_WriteNonce(keys, additionalData, 8,
                      nonce, sizeof(nonce));
     return tls13_AEAD(keys, doDecrypt, out, outlen, maxout, in, inlen,
                       CKM_AES_GCM,
                       (unsigned char *)&gcmParams, sizeof(gcmParams));
 }
 
 static SECStatus
-tls13_ChaCha20Poly1305(ssl3KeyMaterial *keys, PRBool doDecrypt,
-                       unsigned char *out, int *outlen, int maxout,
-                       const unsigned char *in, int inlen,
+tls13_ChaCha20Poly1305(const ssl3KeyMaterial *keys, PRBool doDecrypt,
+                       unsigned char *out, unsigned int *outlen, unsigned int maxout,
+                       const unsigned char *in, unsigned int inlen,
                        const unsigned char *additionalData,
-                       int additionalDataLen)
+                       unsigned int additionalDataLen)
 {
     CK_NSS_AEAD_PARAMS aeadParams;
     unsigned char nonce[12];
 
     PORT_Assert(additionalDataLen > 8);
     memset(&aeadParams, 0, sizeof(aeadParams));
     aeadParams.pNonce = nonce;
     aeadParams.ulNonceLen = sizeof(nonce);
@@ -5140,17 +5140,17 @@ tls13_ProtectRecord(sslSocket *ss,
         rv = sslBuffer_Skip(wrBuf, contentLen, NULL);
         PORT_Assert(rv == SECSuccess);
     } else {
         PRUint8 hdr[13];
         sslBuffer buf = SSL_BUFFER_FIXED(hdr, sizeof(hdr));
         PRBool needsLength;
         PRUint8 aad[21];
         unsigned int aadLen;
-        int len;
+        unsigned int len;
 
         PORT_Assert(cipher_def->type == type_aead);
 
         /* Add the content type at the end. */
         *(SSL_BUFFER_NEXT(wrBuf) + contentLen) = type;
 
         /* Create the header (ugly that we have to do it twice). */
         rv = ssl_InsertRecordHeader(ss, cwSpec, ssl_ct_application_data,
@@ -5261,22 +5261,22 @@ tls13_UnprotectRecord(sslSocket *ss,
     PORT_Assert(cipher_def->type == type_aead);
     rv = tls13_FormatAdditionalData(ss, cText->hdr, cText->hdrLen,
                                     spec->epoch, cText->seqNum,
                                     aad, &aadLen, sizeof(aad));
     if (rv != SECSuccess) {
         return SECFailure;
     }
     rv = spec->aead(&spec->keyMaterial,
-                    PR_TRUE,                /* do decrypt */
-                    plaintext->buf,         /* out */
-                    (int *)&plaintext->len, /* outlen */
-                    plaintext->space,       /* maxout */
-                    cText->buf->buf,        /* in */
-                    cText->buf->len,        /* inlen */
+                    PR_TRUE,          /* do decrypt */
+                    plaintext->buf,   /* out */
+                    &plaintext->len,  /* outlen */
+                    plaintext->space, /* maxout */
+                    cText->buf->buf,  /* in */
+                    cText->buf->len,  /* inlen */
                     aad, aadLen);
     if (rv != SECSuccess) {
         SSL_TRC(3,
                 ("%d: TLS13[%d]: record has bogus MAC",
                  SSL_GETPID(), ss->fd));
         PORT_SetError(SSL_ERROR_BAD_MAC_READ);
         return SECFailure;
     }
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -47,16 +47,17 @@ PRBool tls13_InHsState(sslSocket *ss, ..
 #define TLS13_IN_HS_STATE(ss, ...) \
     tls13_InHsState(ss, __VA_ARGS__, wait_invalid)
 
 SSLHashType tls13_GetHashForCipherSuite(ssl3CipherSuite suite);
 SSLHashType tls13_GetHash(const sslSocket *ss);
 unsigned int tls13_GetHashSizeForHash(SSLHashType hash);
 unsigned int tls13_GetHashSize(const sslSocket *ss);
 CK_MECHANISM_TYPE tls13_GetHkdfMechanism(sslSocket *ss);
+CK_MECHANISM_TYPE tls13_GetHkdfMechanismForHash(SSLHashType hash);
 SECStatus tls13_ComputeHash(sslSocket *ss, SSL3Hashes *hashes,
                             const PRUint8 *buf, unsigned int len);
 SECStatus tls13_ComputeHandshakeHashes(sslSocket *ss,
                                        SSL3Hashes *hashes);
 SECStatus tls13_DeriveSecretNullHash(sslSocket *ss, PK11SymKey *key,
                                      const char *label,
                                      unsigned int labelLen,
                                      PK11SymKey **dest);
@@ -125,16 +126,21 @@ SECStatus SSLExp_SetupAntiReplay(PRTime 
 SECStatus SSLExp_HelloRetryRequestCallback(PRFileDesc *fd,
                                            SSLHelloRetryRequestCallback cb,
                                            void *arg);
 SECStatus tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request,
                               PRBool buffer);
 SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate);
 PRBool tls13_MaybeTls13(sslSocket *ss);
 SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef);
+SECStatus tls13_AEAD(const ssl3KeyMaterial *keys, PRBool doDecrypt,
+                     unsigned char *out, unsigned int *outlen, unsigned int maxout,
+                     const unsigned char *in, unsigned int inlen,
+                     CK_MECHANISM_TYPE mechanism,
+                     unsigned char *aeadParams, unsigned int aeadParamLength);
 void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec);
 SECStatus SSLExp_SendCertificateRequest(PRFileDesc *fd);
 
 /* Use this instead of FATAL_ERROR when no alert shall be sent. */
 #define LOG_ERROR(ss, prError)                                                     \
     do {                                                                           \
         SSL_TRC(3, ("%d: TLS13[%d]: fatal error %d in %s (%s:%d)",                 \
                     SSL_GETPID(), ss->fd, prError, __func__, __FILE__, __LINE__)); \
--- a/security/nss/lib/ssl/tls13esni.c
+++ b/security/nss/lib/ssl/tls13esni.c
@@ -716,18 +716,18 @@ tls13_ServerGetEsniAEAD(const sslSocket 
     }
 
     *suiteDefp = suiteDef;
     *aeadp = aead;
     return SECSuccess;
 }
 
 SECStatus
-tls13_ServerDecryptEsniXtn(const sslSocket *ss, PRUint8 *in, unsigned int inLen,
-                           PRUint8 *out, int *outLen, int maxLen)
+tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen,
+                           PRUint8 *out, unsigned int *outLen, unsigned int maxLen)
 {
     sslReader rdr = SSL_READER(in, inLen);
     PRUint64 suite;
     const ssl3CipherSuiteDef *suiteDef;
     SSLAEADCipher aead = NULL;
     TLSExtension *keyShareExtension;
     TLS13KeyShareEntry *entry = NULL;
     ssl3KeyMaterial keyMat = { NULL };
--- a/security/nss/lib/ssl/tls13esni.h
+++ b/security/nss/lib/ssl/tls13esni.h
@@ -40,12 +40,12 @@ SECStatus tls13_ComputeESNIKeys(const ss
                                 const PRUint8 *esniKeysHash,
                                 const PRUint8 *keyShareBuf,
                                 unsigned int keyShareBufLen,
                                 const PRUint8 *clientRandom,
                                 ssl3KeyMaterial *keyMat);
 SECStatus tls13_FormatEsniAADInput(sslBuffer *aadInput,
                                    PRUint8 *keyShare, unsigned int keyShareLen);
 
-SECStatus tls13_ServerDecryptEsniXtn(const sslSocket *ss, PRUint8 *in, unsigned int inLen,
-                                     PRUint8 *out, int *outLen, int maxLen);
+SECStatus tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen,
+                                     PRUint8 *out, unsigned int *outLen, unsigned int maxLen);
 
 #endif
--- a/security/nss/lib/ssl/tls13exthandle.c
+++ b/security/nss/lib/ssl/tls13exthandle.c
@@ -1135,17 +1135,17 @@ tls13_ClientSendEsniXtn(const sslSocket 
     SECStatus rv;
     PRUint8 sniBuf[1024];
     PRUint8 hash[64];
     sslBuffer sni = SSL_BUFFER(sniBuf);
     const ssl3CipherSuiteDef *suiteDef;
     ssl3KeyMaterial keyMat;
     SSLAEADCipher aead;
     PRUint8 outBuf[1024];
-    int outLen;
+    unsigned int outLen;
     unsigned int sniStart;
     unsigned int sniLen;
     sslBuffer aadInput = SSL_BUFFER_EMPTY;
     unsigned int keyShareBufStart;
     unsigned int keyShareBufLen;
 
     PORT_Memset(&keyMat, 0, sizeof(keyMat));
 
@@ -1289,17 +1289,17 @@ tls13_ServerSendEsniXtn(const sslSocket 
 }
 
 SECStatus
 tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                           SECItem *data)
 {
     sslReadBuffer buf;
     PRUint8 *plainText = NULL;
-    int ptLen;
+    unsigned int ptLen;
     SECStatus rv;
 
     /* If we are doing < TLS 1.3, then ignore this. */
     if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         return SECSuccess;
     }
 
     if (!ss->esniKeys) {
--- a/security/nss/lib/ssl/tls13hkdf.c
+++ b/security/nss/lib/ssl/tls13hkdf.c
@@ -135,24 +135,23 @@ tls13_HkdfExpandLabel(PK11SymKey *prk, S
      */
     PRUint8 info[256];
     sslBuffer infoBuf = SSL_BUFFER(info);
     PK11SymKey *derived;
     SECStatus rv;
     const char *kLabelPrefix = "tls13 ";
     const unsigned int kLabelPrefixLen = strlen(kLabelPrefix);
 
-    if (handshakeHash) {
-        if (handshakeHashLen > 255) {
-            PORT_Assert(0);
-            PORT_SetError(SEC_ERROR_INVALID_ARGS);
-            return SECFailure;
-        }
-    } else {
-        PORT_Assert(!handshakeHashLen);
+    PORT_Assert(prk);
+    PORT_Assert(keyp);
+    if ((handshakeHashLen > 255) ||
+        (handshakeHash == NULL && handshakeHashLen > 0) ||
+        (labelLen + kLabelPrefixLen > 255)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
     }
 
     /*
      *  [draft-ietf-tls-tls13-11] Section 7.1:
      *
      *  HKDF-Expand-Label(Secret, Label, HashValue, Length) =
      *       HKDF-Expand(Secret, HkdfLabel, Length)
      *
--- a/security/nss/tests/smime/alice.txt
+++ b/security/nss/tests/smime/alice.txt
@@ -1,6 +1,1 @@
-Date: Wed, 20 Sep 2000 00:00:01 -0700 (PDT)
-From: alice@bogus.com
-Subject: message Alice --> Bob
-To: bob@bogus.com
-
 This is a test message from Alice to Bob.
--- a/security/nss/tests/smime/smime.sh
+++ b/security/nss/tests/smime/smime.sh
@@ -46,22 +46,24 @@ smime_init()
       Exit 11 "Fatal - S/MIME of cert.sh needs to pass first"
   }
 
   SMIMEDIR=${HOSTDIR}/smime
   R_SMIMEDIR=../smime
   mkdir -p ${SMIMEDIR}
   cd ${SMIMEDIR}
   cp ${QADIR}/smime/alice.txt ${SMIMEDIR}
+
+  mkdir tb
 }
 
-smime_sign()
+cms_sign()
 {
-  HASH_CMD="-H ${HASH}"
-  SIG=sig.${HASH}
+  HASH_CMD="-H SHA${HASH}"
+  SIG=sig.SHA${HASH}
 
   echo "$SCRIPTNAME: Signing Detached Message {$HASH} ------------------"
   echo "cmsutil -S -T -N Alice ${HASH_CMD} -i alice.txt -d ${P_R_ALICEDIR} -p nss -o alice.d${SIG}"
   ${PROFTOOL} ${BINDIR}/cmsutil -S -T -N Alice ${HASH_CMD} -i alice.txt -d ${P_R_ALICEDIR} -p nss -o alice.d${SIG}
   html_msg $? 0 "Create Detached Signature Alice (${HASH})" "."
 
   echo "cmsutil -D -i alice.d${SIG} -c alice.txt -d ${P_R_BOBDIR} "
   ${PROFTOOL} ${BINDIR}/cmsutil -D -i alice.d${SIG} -c alice.txt -d ${P_R_BOBDIR} 
@@ -97,33 +99,176 @@ smime_sign()
 
   echo "cmsutil -D -i alice-ec.${SIG} -d ${P_R_BOBDIR} -o alice-ec.data.${HASH}"
   ${PROFTOOL} ${BINDIR}/cmsutil -D -i alice-ec.${SIG} -d ${P_R_BOBDIR} -o alice-ec.data.${HASH}
   html_msg $? 0 "Decode Alice's Attached Signature (ECDSA w/ ${HASH})" "."
 
   echo "diff alice.txt alice-ec.data.${HASH}"
   diff alice.txt alice-ec.data.${HASH}
   html_msg $? 0 "Compare Attached Signed Data and Original (ECDSA w/ ${HASH})" "."
+}
 
+header_mime_from_to_subject="MIME-Version: 1.0
+From: Alice@bogus.com
+To: Bob@bogus.com
+Subject: "
+
+header_opaque_signed="Content-Type: application/pkcs7-mime; name=smime.p7m;
+    smime-type=signed-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=smime.p7m
+Content-Description: S/MIME Cryptographic Signature
+"
+
+header_enveloped="Content-Type: application/pkcs7-mime; name=smime.p7m;
+    smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=smime.p7m
+Content-Description: S/MIME Encrypted Message
+"
+
+header_clearsigned="Content-Type: text/plain; charset=utf-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Language: en-US
+"
+
+multipart_start="Content-Type: multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=sha-HASHHASH; boundary=\"------------ms030903020902020502030404\"
+
+This is a cryptographically signed message in MIME format.
+
+--------------ms030903020902020502030404"
+
+multipart_middle="
+--------------ms030903020902020502030404
+Content-Type: application/pkcs7-signature; name=smime.p7s
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=smime.p7s
+Content-Description: S/MIME Cryptographic Signature
+"
+
+multipart_end="--------------ms030903020902020502030404--
+"
+
+header_plaintext="Content-Type: text/plain
+"
+
+CR=$(printf '\r')
+
+mime_init()
+{
+  OUT="tb/alice.mime"
+  echo "${header_clearsigned}" >>${OUT}
+  cat alice.txt >>${OUT}
+  sed -i"" "s/\$/${CR}/" ${OUT}
+
+  OUT="tb/alice.textplain"
+  echo "${header_plaintext}" >>${OUT}
+  cat alice.txt >>${OUT}
+  sed -i"" "s/\$/${CR}/" ${OUT}
+}
+
+smime_enveloped()
+{
+  ${PROFTOOL} ${BINDIR}/cmsutil -E -r bob@bogus.com -i tb/alice.mime -d ${P_R_ALICEDIR} -p nss -o tb/alice.mime.env
+
+  OUT="tb/alice.env.eml"
+  echo -n "${header_mime_from_to_subject}" >>${OUT}
+  echo "enveloped ${SIG}" >>${OUT}
+  echo "${header_enveloped}" >>${OUT}
+  cat "tb/alice.mime.env" | ${BINDIR}/btoa | sed 's/\r$//' >>${OUT}
+  echo >>${OUT}
+  sed -i"" "s/\$/${CR}/" ${OUT}
 }
 
+smime_signed_enveloped()
+{
+  SIG=sig.SHA${HASH}
 
+  ${PROFTOOL} ${BINDIR}/cmsutil -S -T -N Alice ${HASH_CMD} -i tb/alice.mime -d ${P_R_ALICEDIR} -p nss -o tb/alice.mime.d${SIG}
+
+  OUT="tb/alice.d${SIG}.multipart"
+  echo "${multipart_start}" | sed "s/HASHHASH/${HASH}/" >>${OUT}
+  cat tb/alice.mime | sed 's/\r$//' >>${OUT}
+  echo "${multipart_middle}" >>${OUT}
+  cat tb/alice.mime.d${SIG} | ${BINDIR}/btoa | sed 's/\r$//' >>${OUT}
+  echo "${multipart_end}" >>${OUT}
+
+  ${PROFTOOL} ${BINDIR}/cmsutil -E -r bob@bogus.com -i ${OUT} -d ${P_R_ALICEDIR} -p nss -o ${OUT}.env
+
+  OUT="tb/alice.d${SIG}.multipart.eml"
+  echo -n "${header_mime_from_to_subject}" >>${OUT}
+  echo "clear-signed ${SIG}" >>${OUT}
+  cat "tb/alice.d${SIG}.multipart" >>${OUT}
+  sed -i"" "s/\$/$CR/" ${OUT}
+
+  OUT="tb/alice.d${SIG}.multipart.env.eml"
+  echo -n "${header_mime_from_to_subject}" >>${OUT}
+  echo "enveloped clear-signed $SIG" >>${OUT}
+  echo "$header_enveloped" >>${OUT}
+  cat "tb/alice.d${SIG}.multipart.env" | ${BINDIR}/btoa | sed 's/\r$//' >>${OUT}
+  echo >>${OUT}
+  sed -i"" "s/\$/$CR/" ${OUT}
+
+  ${PROFTOOL} ${BINDIR}/cmsutil -S -N Alice ${HASH_CMD} -i tb/alice.textplain -d ${P_R_ALICEDIR} -p nss -o tb/alice.textplain.${SIG}