Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 10 May 2019 05:58:35 +0300
changeset 532255 f71645b9b3e028458b5a4bb1c49807424e5ee924
parent 532254 38895d59d3d007f10a771552e5055b51c4425050 (current diff)
parent 532151 a42caa9f04fc41044437ac56eab6f6086c841d9f (diff)
child 532256 15583c97669a526418146062c36729ccb90c5609
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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 inbound. a=merge CLOSED TREE
dom/tests/browser/browser_test_performance_metrics_off.js
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html.ini
testing/web-platform/meta/webrtc/RTCDataChannel-bufferedAmount.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/cue_func_selector_single_colon.html.ini
testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/type_selector_root.html.ini
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -172,17 +172,17 @@ CustomizeMode.prototype = {
       this.enter();
     }
   },
 
   async _updateThemeButtonIcon() {
     let lwthemeButton = this.$("customization-lwtheme-button");
     let lwthemeIcon = lwthemeButton.icon;
     let theme = (await AddonManager.getAddonsByTypes(["theme"])).find(addon => addon.isActive);
-    lwthemeIcon.style.backgroundImage = theme ? "url(" + theme.iconURL + ")" : "";
+    lwthemeIcon.style.backgroundImage = (theme && theme.iconURL) ? "url(" + theme.iconURL + ")" : "";
   },
 
   setTab(aTab) {
     if (gTab == aTab) {
       return;
     }
 
     if (gTab) {
@@ -1336,17 +1336,17 @@ CustomizeMode.prototype = {
     };
 
     let doc = this.window.document;
 
     function buildToolbarButton(aTheme) {
       let tbb = doc.createXULElement("toolbarbutton");
       tbb.theme = aTheme;
       tbb.setAttribute("label", aTheme.name);
-      tbb.setAttribute("image", aTheme.iconURL);
+      tbb.setAttribute("image", aTheme.iconURL || "chrome://mozapps/skin/extensions/themeGeneric.svg");
       if (aTheme.description)
         tbb.setAttribute("tooltiptext", aTheme.description);
       tbb.setAttribute("tabindex", "0");
       tbb.classList.add("customization-lwtheme-menu-theme");
       let isActive = aTheme.isActive;
       tbb.setAttribute("aria-checked", isActive);
       tbb.setAttribute("role", "menuitemradio");
       if (isActive) {
@@ -1359,18 +1359,24 @@ CustomizeMode.prototype = {
     let themes = await AddonManager.getAddonsByTypes(["theme"]);
     let currentTheme = themes.find(theme => theme.isActive);
 
     // Move the current theme (if any) and the light/dark themes to the start:
     let importantThemes = new Set([DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID]);
     if (currentTheme) {
       importantThemes.add(currentTheme.id);
     }
+    let importantList = [];
+    for (let importantTheme of importantThemes) {
+      importantList.push(...themes.splice(themes.findIndex(theme => theme.id == importantTheme), 1));
+    }
 
-    themes.sort((a, b) => importantThemes.has(a.id) - importantThemes.has(b.id));
+    // Sort the remainder alphabetically:
+    themes.sort((a, b) => a.name.localeCompare(b.name));
+    themes = importantList.concat(themes);
 
     if (themes.length > MAX_THEME_COUNT)
       themes.length = MAX_THEME_COUNT;
 
     let footer = doc.getElementById("customization-lwtheme-menu-footer");
     let panel = footer.parentNode;
     for (let theme of themes) {
       let button = buildToolbarButton(theme);
@@ -1513,16 +1519,20 @@ CustomizeMode.prototype = {
         if (this._canDrawInTitlebar()) {
           this._updateTitlebarCheckbox();
           this._updateDragSpaceCheckbox();
         }
         break;
     }
   },
 
+  async onInstalled(addon) {
+    await this.onEnabled(addon);
+  },
+
   async onEnabled(addon) {
     if (addon.type != "theme") {
       return;
     }
 
     await this._updateThemeButtonIcon();
     if (this._nextThemeChangeUserTriggered) {
       this._onUIChange();
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
@@ -2,16 +2,34 @@
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const DEFAULT_THEME_ID = "default-theme@mozilla.org";
 const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
 const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
+const MAX_THEME_COUNT = 6; // Not exposed from CustomizeMode.jsm
+
+async function installTheme(id) {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {gecko: {id}},
+      manifest_version: 2,
+      name: "Theme " + id,
+      description: "wow. such theme.",
+      author: "Pixel Pusher",
+      version: "1",
+      theme: {},
+    },
+    useAddonManager: "temporary",
+  });
+  await extension.startup();
+  return extension;
+}
 
 add_task(async function() {
   await startCustomizing();
   // Check restore defaults button is disabled.
   ok(document.getElementById("customization-reset-button").disabled,
      "Reset button should start out disabled");
 
   let themesButton = document.getElementById("customization-lwtheme-button");
@@ -76,16 +94,38 @@ add_task(async function() {
   info("Clicked on themes button a third time");
   await popupShownPromise;
 
   let activeThemes = popup.querySelectorAll("toolbarbutton.customization-lwtheme-menu-theme[active]");
   is(activeThemes.length, 1, "Exactly 1 theme should be selected");
   if (activeThemes.length > 0) {
     is(activeThemes[0].theme.id, LIGHT_THEME_ID, "Light theme should be selected");
   }
+  popup.hidePopup();
+
+  // Install 5 themes:
+  let addons = [];
+  for (let n = 1; n <= 5; n++) {
+    addons.push(await installTheme("my-theme-" + n + "@example.com"));
+  }
+  addons = await Promise.all(addons);
+
+  ok(!themesButtonIcon.style.backgroundImage,
+     `Button should show fallback theme thumbnail - was: "${themesButtonIcon.style.backgroundImage}"`);
+
+  popupShownPromise = popupShown(popup);
+  EventUtils.synthesizeMouseAtCenter(themesButton, {});
+  info("Clicked on themes button a fourth time");
+  await popupShownPromise;
+
+  activeThemes = popup.querySelectorAll("toolbarbutton.customization-lwtheme-menu-theme[active]");
+  is(activeThemes.length, 1, "Exactly 1 theme should be selected");
+  if (activeThemes.length > 0) {
+    is(activeThemes[0].theme.id, "my-theme-5@example.com", "Last installed theme should be selected");
+  }
 
   let firstLWTheme = footer.previousElementSibling;
   let firstLWThemeId = firstLWTheme.theme.id;
   themeChangedPromise = promiseObserverNotified("lightweight-theme-styling-update");
   firstLWTheme.doCommand();
   info("Clicked on first theme");
   await themeChangedPromise;
 
@@ -104,51 +144,53 @@ add_task(async function() {
 
   is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
   let themeCount = 0;
   let iterNode = header;
   while (iterNode.nextElementSibling && iterNode.nextElementSibling.theme) {
     themeCount++;
     iterNode = iterNode.nextElementSibling;
   }
-  is(themeCount, 3,
-     "There should be four themes in the 'My Themes' section");
+  is(themeCount, MAX_THEME_COUNT,
+     "There should be the max number of themes in the 'My Themes' section");
 
   let defaultTheme = header.nextElementSibling;
   defaultTheme.doCommand();
   await new Promise(SimpleTest.executeSoon);
 
   // ensure current theme isn't set to "Default"
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a fourth time");
+  info("Clicked on themes button a sixth time");
   await popupShownPromise;
 
   // check that "Restore Defaults" button resets theme
   await gCustomizeMode.reset();
 
   defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID);
   is(defaultTheme.isActive, true, "Current theme reset to default");
 
   await endCustomizing();
   await startCustomizing();
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a fifth time");
+  info("Clicked on themes button a seventh time");
   await popupShownPromise;
   header = document.getElementById("customization-lwtheme-menu-header");
   is(header.hidden, false, "Header should never be hidden");
   let themeNode = header.nextElementSibling;
   is(themeNode.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
   is(themeNode.hidden, false, "The default theme should never be hidden");
 
   themeNode = themeNode.nextElementSibling;
   is(themeNode.theme.id, LIGHT_THEME_ID, "The second theme should be the Light theme");
   is(themeNode.hidden, false, "The light theme should never be hidden");
 
   themeNode = themeNode.nextElementSibling;
   is(themeNode.theme.id, DARK_THEME_ID, "The third theme should be the Dark theme");
   is(themeNode.hidden, false, "The dark theme should never be hidden");
+
+  await Promise.all(addons.map(a => a.unload()));
 });
 
 add_task(async function asyncCleanup() {
   await endCustomizing();
 });
--- a/browser/components/migration/ChromeProfileMigrator.jsm
+++ b/browser/components/migration/ChromeProfileMigrator.jsm
@@ -375,21 +375,28 @@ async function GetWindowsPasswordsResour
             passwordElement: row.getResultByName("password_element"),
             timeCreated: ChromeMigrationUtils.chromeTimeToDate(
               row.getResultByName("date_created") + 0).getTime(),
             timesUsed: row.getResultByName("times_used") + 0,
           };
 
           switch (row.getResultByName("scheme")) {
             case AUTH_TYPE.SCHEME_HTML:
-              let action_url = NetUtil.newURI(row.getResultByName("action_url"));
-              if (!kValidSchemes.has(action_url.scheme)) {
+              let action_url = row.getResultByName("action_url");
+              if (!action_url) {
+                // If there is no action_url, store the wildcard "" value.
+                // See the `formSubmitURL` IDL comments.
+                loginInfo.formSubmitURL = "";
+                break;
+              }
+              let action_uri = NetUtil.newURI(action_url);
+              if (!kValidSchemes.has(action_uri.scheme)) {
                 continue; // This continues the outer for loop.
               }
-              loginInfo.formSubmitURL = action_url.prePath;
+              loginInfo.formSubmitURL = action_uri.prePath;
               break;
             case AUTH_TYPE.SCHEME_BASIC:
             case AUTH_TYPE.SCHEME_DIGEST:
               // signon_realm format is URIrealm, so we need remove URI
               loginInfo.httpRealm = row.getResultByName("signon_realm")
                                        .substring(loginInfo.hostname.length + 1);
               break;
             default:
index 914149c710a8c748b97543c541aaedfee74aa1f6..ac859eff6155306f5af57de81bab15fd424e6cdb
GIT binary patch
literal 24576
zc%1E93v?638lGg+Y##)vQbJm*i(Y|hn_7E>%cG<ZA{1(0v_Ls<<0jcAYc|=iyJ=}T
zqEy8LxAGFf2kPPF3sE>nJ(q`yJPvZNRS*$S<cde?feI)n*i*qfGkG*=nu_8%TyVa2
zUo-#A{Qp0b`Tm{VolsH2D;!fRN^VwRazQ*$sX#u%004%=trzHoCa?ve0>6#nz}Tll
zNB;t7$^vK?y-(9bkJ1<^i-x*{(gXxoH)ape(t5pK)igk1tpX=2tRmxGVo{~Jpvuft
z6%>}38Jxrn7A4-ndn|U*&GH^*VnJom*n&!?w5*CLtu85H$}5ZSEvT$v#+z$08IS1Y
zWQ$Goc@(C&w8}ijT$#z(dD+d&GAne=fDK5B#m>T?dLS)SZAJ#I)#vF|fcMzB8S-=i
z&IcCOr-=C4f>pER;6qYnoK`=`pptQxAVOtji_AMbqQ`%s4OmrNI@&y$i4b7QN&^YZ
zV1J55;#k3*@#rv$)*B3}dG}%$`DOfjO@xzzr2}5_NkWv1G8xvU@M1(}CgYPi$-}xi
z3ny@H4!R{er;ZhTTxYtMmF0R-vPYK)<@ie!mXsAj@A|B6UWpbCw=-H-Sr$+&ygjN*
zGQAR4%SjT~O&01yiw#z(beC%tSer}WWteHPHdv5-(L%D#2^H<8)u3%kgEy3&=@n&}
zhaF=HI!pFjt?=*^C5F_<L@a^^8S9t8s>%%>TbKwOp<aP)u%L=Uj$-Rz87ZREYB@V6
zu~B^~yLhjqk&{G=P2g>=P_+)u6B73dB5TLv)+dJqs!NM2s?FhpCwg3T9S_mtAhJs{
zGVai5^(I5CpH5)>=lG9twJKVllA@ZOjmNp0Q&_aABgQyN8SLU3n3A&6F;Nphz=()>
zz&{Sd#4`q|wfYe$v6MnBSaQ%FF|bAlcWQb+p!d^fuJmL{KtMo1KtOOANK;cIt`k^U
zfwuu}FAR%N_*TbBGK`OMdQntba_}NIk^Twte+!@w!5sks0RaI4!PSQ(Doqt&b}Ee;
zp^6a#sF7%(NF3ZK`UiB{i|(M$`A-Q52nYxW2(C3GAkvRVQ2?z<gnO;_ZlG<{{!4q2
zep7ol?YzS35fBg%5d6PlG@YkQPsvj`6~!wL&(5x|uQzftShrW;jPQ0dJNOtKJwA^m
zqs^mpb$Z<xTnWra2P^L}ijpJSiYn+P{T|lEF_q}idz=}TPSMqLS2{*vH<$0?JRVMQ
zN*u2=A|*6DJx!luFoz6bZTP(z+2{~OM?ldDoK(l#xDxab-|S|2A!uE`16?7Ra`c*j
zy{Agl<7tyVJ;fB^vRc;0Sw+!>CAjdayE-mHk{VBs7?3_FBOxeZ8)oFi?0U|cjo&oj
zJzk#@p(#!&54_6|Sqk$%Z3eWJeuQqJPtj)jjVnF82nYxW2nenU+(^Zag|q{xZeyIV
zSmt)B+c>7XfF+&kA%vh9s^?HfolJF&8qoWHD$Tn9{u2-o5L`u=V^XD#KsQ4VPu685
zBxvK~<NL-xHZoqP)5XWfxIOw~cTMPHr#;Qj{a2N;H@UeHY%SZitPQ0-HGTUV%Ead4
z)ykgPOK+FgBdyd}w?4H1e<Q`f!_JpKv=s=8+*5y?KaBZBHSQVBki;>qL*Gy0@ASRC
z<NM7>zV}1^qDX$ofCtYw<~`ilv2Rbu%7ZTqUA{PZTU~Bid)jrT4L|G~`&}O>Gw{|M
zSH&%##cr5qNdNnecU<|X_`!=y!I!CPCVM{p%Cx$9#gX>qgIj6)gGYAt-BHkTX#KV&
zKb1fB9sl&=q|&m&T%YUk&UJ4;z3t7sb4xx}sDCHVj`R&MHXNV$?9GKpyHBAd=!J2_
z7qvhH5^#eOcys_BctAN|;TZ?>^l*#e;ZUzp0Uss9ts2O%oCND&VT~NL9@WjjGdE1(
zfdFODJt}I&3w%HU=CB<92Wbj7L-E%fA2mW$VVgQET^*1{n#=I79=B`9_I0+^HMg^s
z;Fke^q+Mk9cxWL9TVVoL%dk}kY?p(rFjxZ+rb`iU!W<iLh55X*Wt)?;xvb)hSUQBC
ztGnc5{#SPZ_)kFaSHe8CI&~VS&oMyqFbBv(_4w3C^7yS4!Aq4Z@F~t7R}U|bV78@w
zS@S{7)SR|spN`gM>|z(cJ?_=?bti8c#Xof3)jT~F$%hlnPb*tf+kJ~($l7}-XVikR
zi!M&ncn>5V$aqpNU2xs~(j-JMo6hKNxPI!7)06u@@ca&C?`Qd_j^5s?xoZ>Wl<n<%
z>ZXzP7lzW6AF(I@Id`qyuyEI^dp1;`ef0UYO<7q+-I7taK6iZU;(^CDZag}8{Na5U
z>^tW=?&!%~h;Amkr6Q!O)=v3(qBq<ec)n+W1<v`w*)PaJh;uHr4@|QEA`f|fpiT5B
zyvN7o1&ibU2rG)Nvu_QX=reyuTd2R0A(}vUAzm~?y%o%M3_LpVyf47L5=fQdH4CiJ
zvN~*6e}LwHJ3L3_cxW?^<+30Xu#goLcyz+EtT4t3w%HRW0uY;<D&RH&`&I%C&|(IR
zfmg`u0@l|K&pD{62MmI(F;EKel8CvB2aQ-p!CEO1EN+BXMOd1})*?$icyGhyeb`0@
z9%XEeBcdJ_wrj_w5gVI;|Gl-yNWh<lm(hWL?1;6sG3^if;8J1&ZA6+q<kicG0eWLU
zE3npvZrQ&NZqq+1c(^HnQBZ_?!-Ypeu+`t<|5$ni`JYl91)BBLXm}vF*3ficoFP}I
z)9WD1jfsaWmjG{(N;*ldyGL)A$3+Lf=rwZZ%84(2&I;FWSiW(le$TFv2Zz0S(+PXI
zvV4BuHAp_3i&kxW+%8PnxH{YGR96pMahra6#c`$K!sip_HWwtf?LUcr3cgdtE625e
zX8L*Qi4pHCJn%t2Oe=k&C?V@PreM&~TUtu{9C>qg5}R*5IqtS^PQCYd{KY4>?8{Ik
z9iFr1%=$;3Z?qM?Ua(9otp4?-p_3LnjAxEr-+y+Gi*mr&Xd2rm^+}}cjle&jbO+>?
zCSHSFoPu0jgIuiY(Z!eV;g@tn3deq)w3@kV=9-EQ$?%WYj`E*0UU*=A)j4lYzxqR6
zJzTx|?1$4nDOtwN&s()QdAIfZRjp?)&K%#nVb*(_3K#4@gFIX@D{<?2;oG`3!)Lvv
zWVYRcJUr#>Qf}b0Wnb3}o|t?1{M5<ozEtY_&TRPD_-OXZIj>bVPkLsNv-O!*3XhAQ
zw7w;6ex-WF@h_5`+hy~#3ww@j`8Dp+7B!Kpi)N18qP`Sg&$*XMHC5=-Q=qR+fuJqn
zvC)x1Thz*E%nW>EYV<n2QIB6QuZQNJRz5QADWHe;|3+8dVx95gpmyPgwdXf2`M9CY
zt54b5wTE_3FFyTM`@*_4=Zac>UYoT}X_|j$)v~QAb60GCSAP3QI_jZQlLj(-`_-Io
zX-VzXVcxwE^-!as@<08i+<)7v7gN6Fo2x6HEk9cS)bc`K$Jgz5jQ#%R`@Az&ET}wT
z>yS$t+a@pEIjQu0?cxKoj=bDtSUW|vUYZosF(??DQuAaZQuJWp-XGnEcSc~APz+`O
zopDigMF8B@Q+}uiO!&;gDhlWJDtS5nJ`b)HAinJ6``{`;IgAiQ%$u0|yTk*3_Syid
z&l^Y!*7#?y1Ni;xh*$?m#O+4M0>Ktz@qgF$WAT3vArb)r0RaI4!8HgJTM+&yARr(h
zAoy#+|Nh@0pgDA<H6b7%Ao!Co=Wp87JfOo53i5h-P!Q|GwZGm2fN)O$W9w;8_UbmI
MCt3f$cGv&^2La&%&;S4c
--- a/browser/components/migration/tests/unit/test_Chrome_passwords.js
+++ b/browser/components/migration/tests/unit/test_Chrome_passwords.js
@@ -68,16 +68,29 @@ const TEST_LOGINS = [
     formSubmitURL: null,
     httpRealm: "Fake Realm", // Basic auth.
     usernameField: "",
     passwordField: "",
     timeCreated: 1437787539233,
     timePasswordChanged: 1437787539233,
     timesUsed: 1,
   },
+  {
+    id: 6,
+    username: "username",
+    password: "password6",
+    hostname: "https://www.example.com",
+    formSubmitURL: "", // NULL `action_url`
+    httpRealm: null,
+    usernameField: "",
+    passwordField: "pass",
+    timeCreated: 1557291348878,
+    timePasswordChanged: 1557291348878,
+    timesUsed: 1,
+  },
 ];
 
 var crypto = new OSCrypto();
 var dbConn;
 
 function promiseSetPassword(login) {
   let passwordValue = new Uint8Array(crypto.stringToArray(crypto.encryptData(login.password)));
   return dbConn.execute(`UPDATE logins
--- a/browser/components/newtab/lib/OnboardingMessageProvider.jsm
+++ b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
@@ -213,41 +213,41 @@ const ONBOARDING_MESSAGES = async () => 
     trigger: {id: "firstRun"},
   },
   {
     id: "TRAILHEAD_CARD_1",
     template: "onboarding",
     bundled: 3,
     order: 2,
     content: {
-      title: {string_id: "onboarding-tracking-protection-title"},
-      text: {string_id: "onboarding-tracking-protection-text"},
+      title: {string_id: "onboarding-tracking-protection-title2"},
+      text: {string_id: "onboarding-tracking-protection-text2"},
       icon: "tracking",
       primary_button: {
-        label: {string_id: "onboarding-tracking-protection-button"},
+        label: {string_id: "onboarding-tracking-protection-button2"},
         action: {
           type: "OPEN_PREFERENCES_PAGE",
           data: {category: "privacy-trackingprotection"},
         },
       },
     },
     targeting: "trailheadTriplet == 'privacy'",
     trigger: {id: "showOnboarding"},
   },
   {
     id: "TRAILHEAD_CARD_2",
     template: "onboarding",
     bundled: 3,
     order: 2,
     content: {
       title: {string_id: "onboarding-data-sync-title"},
-      text: {string_id: "onboarding-data-sync-text"},
+      text: {string_id: "onboarding-data-sync-text2"},
       icon: "devices",
       primary_button: {
-        label: {string_id: "onboarding-data-sync-button"},
+        label: {string_id: "onboarding-data-sync-button2"},
         action: {
           type: "OPEN_URL",
           addFlowParams: true,
           data: {args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", where: "tabshifted"},
         },
       },
     },
     targeting: "trailheadTriplet == 'supercharge'",
@@ -292,17 +292,17 @@ const ONBOARDING_MESSAGES = async () => 
   },
   {
     id: "TRAILHEAD_CARD_5",
     template: "onboarding",
     bundled: 3,
     order: 5,
     content: {
       title: {string_id: "onboarding-firefox-send-title"},
-      text: {string_id: "onboarding-firefox-send-text"},
+      text: {string_id: "onboarding-firefox-send-text2"},
       icon: "ffsend",
       primary_button: {
         label: {string_id: "onboarding-firefox-send-button"},
         action: {
           type: "OPEN_URL",
           data: {args: "https://send.firefox.com/", where: "tabshifted"},
         },
       },
@@ -352,17 +352,17 @@ const ONBOARDING_MESSAGES = async () => 
   },
   {
     id: "TRAILHEAD_CARD_8",
     template: "onboarding",
     bundled: 3,
     order: 2,
     content: {
       title: {string_id: "onboarding-pocket-anywhere-title"},
-      text: {string_id: "onboarding-pocket-anywhere-text"},
+      text: {string_id: "onboarding-pocket-anywhere-text2"},
       icon: "pocket",
       primary_button: {
         label: {string_id: "onboarding-pocket-anywhere-button"},
         action: {
           type: "OPEN_URL",
           data: {args: "https://getpocket.com/firefox_learnmore", where: "tabshifted"},
         },
       },
@@ -372,37 +372,37 @@ const ONBOARDING_MESSAGES = async () => 
   },
   {
     id: "TRAILHEAD_CARD_9",
     template: "onboarding",
     bundled: 3,
     order: 3,
     content: {
       title: {string_id: "onboarding-lockwise-passwords-title"},
-      text: {string_id: "onboarding-lockwise-passwords-text"},
+      text: {string_id: "onboarding-lockwise-passwords-text2"},
       icon: "lockwise",
       primary_button: {
-        label: {string_id: "onboarding-lockwise-passwords-button"},
+        label: {string_id: "onboarding-lockwise-passwords-button2"},
         action: {
           type: "OPEN_URL",
           data: {args: "https://lockwise.firefox.com/", where: "tabshifted"},
         },
       },
     },
     targeting: "trailheadTriplet == 'privacy'",
     trigger: {id: "showOnboarding"},
   },
   {
     id: "TRAILHEAD_CARD_10",
     template: "onboarding",
     bundled: 3,
     order: 4,
     content: {
       title: {string_id: "onboarding-facebook-container-title"},
-      text: {string_id: "onboarding-facebook-container-text"},
+      text: {string_id: "onboarding-facebook-container-text2"},
       icon: "fbcont",
       primary_button: {
         label: {string_id: "onboarding-facebook-container-button"},
         action: {
           type: "OPEN_URL",
           data: {args: "https://addons.mozilla.org/firefox/addon/facebook-container/", where: "tabshifted"},
         },
       },
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -55,16 +55,17 @@ class UrlbarInput {
     // the container element.
     let MozXULElement = this.window.MozXULElement;
     // TODO Bug 1535659: urlbarView-body-inner possibly doesn't need the
     // role="combobox" once bug 1513337 is fixed.
     this.document.getElementById("mainPopupSet").appendChild(
       MozXULElement.parseXULToFragment(`
         <panel id="urlbar-results"
                role="group"
+               tooltip="aHTMLTooltip"
                noautofocus="true"
                hidden="true"
                flip="none"
                consumeoutsideclicks="never"
                norolluponanchor="true"
                level="parent">
           <html:div class="urlbarView-body-outer">
             <html:div class="urlbarView-body-inner"
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -436,41 +436,37 @@ function getAcceptableMatchSources(conte
   for (let source of Object.values(UrlbarUtils.RESULT_SOURCE)) {
     // Skip sources that the context doesn't care about.
     if (context.sources && !context.sources.includes(source)) {
       continue;
     }
     // Check prefs and restriction tokens.
     switch (source) {
       case UrlbarUtils.RESULT_SOURCE.BOOKMARKS:
-        if (UrlbarPrefs.get("suggest.bookmark") &&
-            (!restrictTokenType ||
-             restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK ||
-             restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_TAG)) {
+        if (restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK ||
+            restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_TAG ||
+            (!restrictTokenType && UrlbarPrefs.get("suggest.bookmark"))) {
           acceptedSources.push(source);
         }
         break;
       case UrlbarUtils.RESULT_SOURCE.HISTORY:
-        if (UrlbarPrefs.get("suggest.history") &&
-            (!restrictTokenType ||
-             restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_HISTORY)) {
+        if (restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_HISTORY ||
+            (!restrictTokenType && UrlbarPrefs.get("suggest.history"))) {
           acceptedSources.push(source);
         }
         break;
       case UrlbarUtils.RESULT_SOURCE.SEARCH:
-        if (UrlbarPrefs.get("suggest.searches") &&
-            (!restrictTokenType ||
-             restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_SEARCH)) {
+        if (restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_SEARCH ||
+            (!restrictTokenType && UrlbarPrefs.get("suggest.searches"))) {
           acceptedSources.push(source);
         }
         break;
       case UrlbarUtils.RESULT_SOURCE.TABS:
-        if (UrlbarPrefs.get("suggest.openpage") &&
-            (!restrictTokenType ||
-             restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_OPENPAGE)) {
+        if (restrictTokenType === UrlbarTokenizer.TYPE.RESTRICT_OPENPAGE ||
+            (!restrictTokenType && UrlbarPrefs.get("suggest.openpage"))) {
           acceptedSources.push(source);
         }
         break;
       case UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK:
         if (!context.isPrivate && !restrictTokenType) {
           acceptedSources.push(source);
         }
         break;
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -566,16 +566,20 @@ class UrlbarView {
       favicon.src = result.payload.icon || UrlbarUtils.ICON.SEARCH_GLASS;
     } else {
       favicon.src = result.payload.icon || UrlbarUtils.ICON.DEFAULT;
     }
 
     let title = item._elements.get("title");
     this._addTextContentWithHighlights(
       title, result.title, result.titleHighlights);
+    title._tooltip = result.title;
+    if (title.hasAttribute("overflow")) {
+      title.setAttribute("title", title._tooltip);
+    }
 
     let tagsContainer = item._elements.get("tagsContainer");
     tagsContainer.textContent = "";
     if (result.payload.tags && result.payload.tags.length > 0) {
       tagsContainer.append(...result.payload.tags.map((tag, i) => {
         const element = this._createElement("span");
         element.className = "urlbarView-tag";
         this._addTextContentWithHighlights(
@@ -614,18 +618,23 @@ class UrlbarView {
         }
         break;
     }
 
     let url = item._elements.get("url");
     if (setURL) {
       this._addTextContentWithHighlights(url, result.payload.displayUrl,
                                          result.payloadHighlights.displayUrl || []);
+      url._tooltip = result.payload.displayUrl;
     } else {
       url.textContent = "";
+      url._tooltip = "";
+    }
+    if (url.hasAttribute("overflow")) {
+      url.setAttribute("title", url._tooltip);
     }
 
     if (isVisitAction) {
       action = bundle.GetStringFromName("visit");
       title.setAttribute("isurl", "true");
     } else {
       title.removeAttribute("isurl");
     }
@@ -646,17 +655,17 @@ class UrlbarView {
       row = next;
     }
   }
 
   _startRemoveStaleRowsTimer() {
     this._removeStaleRowsTimer = this.window.setTimeout(() => {
       this._removeStaleRowsTimer = null;
       this._removeStaleRows();
-    }, 200);
+    }, 400);
   }
 
   _cancelRemoveStaleRowsTimer() {
     if (this._removeStaleRowsTimer) {
       this.window.clearTimeout(this._removeStaleRowsTimer);
       this._removeStaleRowsTimer = null;
     }
   }
@@ -815,24 +824,26 @@ class UrlbarView {
     this.input.pickResult(event, parseInt(row.getAttribute("resultIndex")));
   }
 
   _on_overflow(event) {
     if (event.detail == 1 &&
         (event.target.classList.contains("urlbarView-url") ||
          event.target.classList.contains("urlbarView-title"))) {
       event.target.toggleAttribute("overflow", true);
+      event.target.setAttribute("title", event.target._tooltip);
     }
   }
 
   _on_underflow(event) {
     if (event.detail == 1 &&
         (event.target.classList.contains("urlbarView-url") ||
          event.target.classList.contains("urlbarView-title"))) {
       event.target.toggleAttribute("overflow", false);
+      event.target.removeAttribute("title");
     }
   }
 
   _on_popupshowing() {
     this.window.addEventListener("resize", this);
   }
 
   _on_popupshown() {
--- a/browser/components/urlbar/tests/unit/test_providersManager_filtering.js
+++ b/browser/components/urlbar/tests/unit/test_providersManager_filtering.js
@@ -245,8 +245,90 @@ add_task(async function test_nofilter_im
   let promise = promiseControllerNotification(controller, "onQueryResults");
   await controller.startQuery(context, controller);
   await promise;
   Services.prefs.clearUserPref("browser.urlbar.suggest.openpage");
   Assert.deepEqual(context.results.length, 1, "Should find only one match");
   Assert.deepEqual(context.results[0].source, UrlbarUtils.RESULT_SOURCE.TABS,
                    "Should find only a tab match");
 });
+
+add_task(async function test_nofilter_restrict() {
+  // Checks that even if a pref is disabled, we still return results on a
+  // restriction token.
+  let controller = new UrlbarController({
+    browserWindow: {
+      location: {
+        href: AppConstants.BROWSER_CHROME_URL,
+      },
+    },
+  });
+
+  let matches = [
+    new UrlbarResult(UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+                     UrlbarUtils.RESULT_SOURCE.TABS,
+                     { url: "http://mozilla.org/foo_tab/" }),
+    new UrlbarResult(UrlbarUtils.RESULT_TYPE.URL,
+                     UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+                     { url: "http://mozilla.org/foo_bookmark/" }),
+    new UrlbarResult(UrlbarUtils.RESULT_TYPE.URL,
+                     UrlbarUtils.RESULT_SOURCE.HISTORY,
+                     { url: "http://mozilla.org/foo_history/" }),
+    new UrlbarResult(UrlbarUtils.RESULT_TYPE.SEARCH,
+                     UrlbarUtils.RESULT_SOURCE.SEARCH,
+                     { engine: "noengine" }),
+  ];
+
+  /**
+   * A test provider.
+   */
+  class TestProvider extends UrlbarProvider {
+    get name() {
+      return "MyProvider";
+    }
+    get type() {
+      return UrlbarUtils.PROVIDER_TYPE.IMMEDIATE;
+    }
+    get sources() {
+      return [
+        UrlbarUtils.RESULT_SOURCE.TABS,
+        UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+        UrlbarUtils.RESULT_SOURCE.HISTORY,
+        UrlbarUtils.RESULT_SOURCE.SEARCH,
+      ];
+    }
+    async startQuery(context, add) {
+      Assert.ok(true, "expected provider was invoked");
+      for (let match of matches) {
+        add(this, match);
+      }
+    }
+    cancelQuery(context) {}
+  }
+  UrlbarProvidersManager.registerProvider(new TestProvider());
+
+  let typeToPropertiesMap = new Map([
+    ["HISTORY", {source: "HISTORY", pref: "history"}],
+    ["BOOKMARK", {source: "BOOKMARKS", pref: "bookmark"}],
+    ["OPENPAGE", {source: "TABS", pref: "openpage"}],
+    ["SEARCH", {source: "SEARCH", pref: "searches"}],
+  ]);
+  for (let [type, token] of Object.entries(UrlbarTokenizer.RESTRICT)) {
+    let properties = typeToPropertiesMap.get(type);
+    if (!properties) {
+      continue;
+    }
+    info("Restricting on " + type);
+    let context = createContext(token + " foo", {
+      providers: ["MyProvider"],
+    });
+    // Disable the corresponding pref.
+    const pref = "browser.urlbar.suggest." + properties.pref;
+    info("Disabling " + pref);
+    Services.prefs.setBoolPref(pref, false);
+    await controller.startQuery(context, controller);
+    Assert.equal(context.results.length, 1, "Should find one result");
+    Assert.equal(context.results[0].source,
+                 UrlbarUtils.RESULT_SOURCE[properties.source],
+                 "Check result source");
+    Services.prefs.clearUserPref(pref);
+  }
+});
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -56,65 +56,59 @@ onboarding-addons-text = Add even more f
 
 onboarding-ghostery-title = Ghostery
 onboarding-ghostery-text = Browse faster, smarter, or safer with extensions like Ghostery, which lets you block annoying ads.
 
 # Note: "Sync" in this case is a generic verb, as in "to synchronize"
 onboarding-fxa-title = Sync
 onboarding-fxa-text = Sign up for a { -fxaccount-brand-name } and sync your bookmarks, passwords, and open tabs everywhere you use { -brand-short-name }.
 
-onboarding-tracking-protection-title = Control How You’re Tracked
-onboarding-tracking-protection-text = Don’t like when ads follow you around? { -brand-short-name } helps you control how advertisers track your activity online.
-# "Update" is a verb, as in "Update the existing settings", not "Options about
-# updates".
-onboarding-tracking-protection-button = { PLATFORM() ->
-  [windows] Update Options
- *[other] Update Preferences
-}
+onboarding-tracking-protection-title2 = Protection From Tracking
+onboarding-tracking-protection-text2 = { -brand-short-name } helps stop websites from tracking you online, making it harder for ads to follow you around the web.
+onboarding-tracking-protection-button2 = How it Works
 
 onboarding-data-sync-title = Take Your Settings with You
 # "Sync" is short for synchronize.
-onboarding-data-sync-text = Sync your bookmarks and passwords everywhere you use { -brand-product-name }.
-onboarding-data-sync-button = Turn on { -sync-brand-short-name }
+onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere you use { -brand-product-name }.
+onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
 
 onboarding-firefox-monitor-title = Stay Alert to Data Breaches
 onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
 onboarding-firefox-monitor-button = Sign up for Alerts
 
 onboarding-browse-privately-title = Browse Privately
 onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
 onboarding-browse-privately-button = Open a Private Window
 
 onboarding-firefox-send-title = Keep Your Shared Files Private
-onboarding-firefox-send-text = { -send-brand-name } protects the files you share with end-to-end encryption and a link that automatically expires.
+onboarding-firefox-send-text2 = Upload your files to { -send-brand-name } to share them with end-to-end encryption and a link that automatically expires.
 onboarding-firefox-send-button = Try { -send-brand-name }
 
 onboarding-mobile-phone-title = Get { -brand-product-name } on Your Phone
 onboarding-mobile-phone-text = Download { -brand-product-name } for iOS or Android and sync your data across devices.
 # "Mobile" is short for mobile/cellular phone, "Browser" is short for web
 # browser.
 onboarding-mobile-phone-button = Download Mobile Browser
 
 onboarding-send-tabs-title = Instantly Send Yourself Tabs
 # "Send Tabs" refers to "Send Tab to Device" feature that appears when opening a
 # tab's context menu.
 onboarding-send-tabs-text = Send Tabs instantly shares pages between your devices without having to copy, paste, or leave the browser.
 onboarding-send-tabs-button = Start Using Send Tabs
 
 onboarding-pocket-anywhere-title = Read and Listen Anywhere
-# "downtime" refers to the user's free/spare time.
-onboarding-pocket-anywhere-text = { -pocket-brand-name } saves your favorite stories so you can read, listen, and watch during your downtime, even if you’re offline.
+onboarding-pocket-anywhere-text2 = Save your favorite content offline with the { -pocket-brand-name } App and read, listen, and watch whenever it’s convenient for you.
 onboarding-pocket-anywhere-button = Try { -pocket-brand-name }
 
 onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
-onboarding-lockwise-passwords-text = { -lockwise-brand-name } saves your passwords in a secure place so you can easily log in to your accounts.
-onboarding-lockwise-passwords-button = Get { -lockwise-brand-name }
+onboarding-lockwise-passwords-text2 = Keep the passwords you save secure and easily log in to your accounts with { -lockwise-brand-name }.
+onboarding-lockwise-passwords-button2 = Get the App
 
 onboarding-facebook-container-title = Set Boundaries with Facebook
-onboarding-facebook-container-text = { -facebook-container-brand-name } keeps your Facebook identity separate from everything else, making it harder to track you across the web.
+onboarding-facebook-container-text2 = { -facebook-container-brand-name } keeps your profile separate from everything else, making it harder for Facebook to target you with ads.
 onboarding-facebook-container-button = Add the Extension
 
 
 ## Message strings belonging to the Return to AMO flow
 return-to-amo-sub-header = Great, you’ve got { -brand-short-name }
 
 # <icon></icon> will be replaced with the icon belonging to the extension
 #
--- a/build.gradle
+++ b/build.gradle
@@ -102,16 +102,22 @@ class TaggedLogOutputStream extends org.
     }
 
     void processLine(String line, int level) {
         logger.lifecycle("${this.tag} ${line}")
     }
 }
 
 ext.geckoBinariesOnlyIf = { task ->
+    // Never when Gradle was invoked within `mach build`.
+    if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) {
+        rootProject.logger.lifecycle("Skipping task ${task.path} because: within `mach build`")
+        return false
+    }
+
     // Never for official builds.
     if (mozconfig.substs.MOZILLA_OFFICIAL) {
         rootProject.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
         return false
     }
 
     // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale.  This
     // causes the
@@ -133,17 +139,29 @@ ext.geckoBinariesOnlyIf = { task ->
         rootProject.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
         return false
     }
 
     rootProject.logger.lifecycle("Executing task ${task.path}")
     return true
 }
 
-task machBuildGeneratedAndroidCodeAndResources(type: Exec) {
+class MachExec extends Exec {
+    def MachExec() {
+        // Bug 1543982: When invoking `mach build` recursively, the outer `mach
+        // build` itself modifies the environment, causing configure to run
+        // again.  This tries to restore the environment that the outer `mach
+        // build` was invoked in.  See the comment in
+        // $topsrcdir/settings.gradle.
+        project.ext.mozconfig.mozconfig.env.unmodified.each { k, v -> environment.remove(k) }
+        environment project.ext.mozconfig.orig_mozconfig.env.unmodified
+    }
+}
+
+task machBuildGeneratedAndroidCodeAndResources(type: MachExec) {
     onlyIf rootProject.ext.geckoBinariesOnlyIf
 
     workingDir "${topsrcdir}"
 
     commandLine mozconfig.substs.PYTHON
     args "${topsrcdir}/mach"
     args 'build'
     args 'mobile/android/base/generated_android_code_and_resources'
@@ -159,17 +177,17 @@ task machBuildGeneratedAndroidCodeAndRes
 }
 
 // Why |mach build mobile/android/base/...| and |mach build faster|?  |mach
 // build faster| generates dependentlibs.list, which in turn depends on compiled
 // code.  That causes a circular dependency between Java compilation/JNI wrapper
 // generation/native code compilation.  So we have the special target for
 // Android-specific generated code, and the |mach build faster| target for all
 // the stuff that goes into the omnijar.
-task machBuildFaster(type: Exec) {
+task machBuildFaster(type: MachExec) {
     onlyIf rootProject.ext.geckoBinariesOnlyIf
 
     workingDir "${topsrcdir}"
 
     commandLine mozconfig.substs.PYTHON
     args "${topsrcdir}/mach"
     args 'build'
     args 'faster'
@@ -180,17 +198,17 @@ task machBuildFaster(type: Exec) {
     }
 
     // `path` is like `:machBuildFaster`.
     standardOutput = new TaggedLogOutputStream("${path}>", logger)
     errorOutput = standardOutput
 }
 
 def createMachStagePackageTask(name) {
-    return task(name, type: Exec) {
+    return task(name, type: MachExec) {
         onlyIf rootProject.ext.geckoBinariesOnlyIf
 
         dependsOn rootProject.machBuildFaster
 
         // We'd prefer to take these from the :omnijar project directly, but
         // it's awkward to reach across projects at evaluation time, so we
         // duplicate the list here.
         inputs.dir "${topsrcdir}/mobile/android/chrome"
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -96,16 +96,17 @@ class MachCommands(MachCommandBase):
                                      locations=locations)
 
             firefox_args = [httpd.get_url()]
 
             env = os.environ.copy()
             env['G_SLICE'] = 'always-malloc'
             env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1'
             env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
+            env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
             env['XPCOM_DEBUG_BREAK'] = 'warn'
 
             env.update(self.extra_environment_variables)
 
             outputHandler = OutputHandler(self.log)
             kp_kwargs = {'processOutputLine': [outputHandler]}
 
             valgrind = 'valgrind'
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -116,18 +116,40 @@ endif
 
 EMBED_MANIFEST_AT=2
 
 endif # MKSHLIB
 endif # FORCE_SHARED_LIB
 
 ifeq ($(OS_ARCH),WINNT)
 
+#
+# This next line captures both the default (non-MOZ_COPY_PDBS)
+# case as well as the MOZ_COPY_PDBS-for-mingwclang case.
+#
+# For the default case, placing the pdb in the build
+# directory is needed.
+#
+# For the MOZ_COPY_PDBS, non-mingwclang case - we need to
+# build the pdb next to the executable (handled in the if
+# statement immediately below.)
+#
+# For the MOZ_COPY_PDBS, mingwclang case - we also need to
+# build the pdb next to the executable, but this macro doesn't
+# work for jsapi-tests which is a little special, so we specify
+# the output directory below with MOZ_PROGRAM_LDFLAGS.
+#
 LINK_PDBFILE ?= $(basename $(@F)).pdb
 
+ifdef MOZ_COPY_PDBS
+ifneq ($(CC_TYPE),clang)
+LINK_PDBFILE = $(basename $@).pdb
+endif
+endif
+
 ifndef GNU_CC
 
 ifdef SIMPLE_PROGRAMS
 COMPILE_PDB_FLAG ?= -Fd$(basename $(@F)).pdb
 COMPILE_CFLAGS += $(COMPILE_PDB_FLAG)
 COMPILE_CXXFLAGS += $(COMPILE_PDB_FLAG)
 endif
 
@@ -780,20 +802,28 @@ endif
 
 ifdef MOZ_AUTOMATION
 ifeq (,$(filter 1,$(MOZ_AUTOMATION_BUILD_SYMBOLS)))
 DUMP_SYMS_TARGETS :=
 endif
 endif
 
 ifdef MOZ_COPY_PDBS
-PDB_FILES = $(addsuffix .pdb,$(basename $(DUMP_SYMS_TARGETS)))
-PDB_DEST ?= $(FINAL_TARGET)
-PDB_TARGET = syms
-INSTALL_TARGETS += PDB
+MAIN_PDB_FILES = $(addsuffix .pdb,$(basename $(DUMP_SYMS_TARGETS)))
+MAIN_PDB_DEST ?= $(FINAL_TARGET)
+MAIN_PDB_TARGET = syms
+INSTALL_TARGETS += MAIN_PDB
+
+ifdef CPP_UNIT_TESTS
+CPP_UNIT_TESTS_PDB_FILES = $(addsuffix .pdb,$(basename $(CPP_UNIT_TESTS)))
+CPP_UNIT_TESTS_PDB_DEST = $(DIST)/cppunittests
+CPP_UNIT_TESTS_PDB_TARGET = syms
+INSTALL_TARGETS += CPP_UNIT_TESTS_PDB
+endif
+
 else ifdef MOZ_CRASHREPORTER
 $(foreach file,$(DUMP_SYMS_TARGETS),$(eval $(call syms_template,$(file),$(notdir $(file))_syms.track)))
 endif
 
 ifneq (,$(RUST_TESTS)$(RUST_LIBRARY_FILE)$(HOST_RUST_LIBRARY_FILE)$(RUST_PROGRAMS)$(HOST_RUST_PROGRAMS))
 include $(MOZILLA_DIR)/config/makefiles/rust.mk
 endif
 
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -55,16 +55,23 @@ button {
 
 .panel {
   max-width: 800px;
   margin-bottom: 35px;
 }
 
 /* Targets */
 
+.target-icon,
+.addons-tip-icon,
+.warning {
+  -moz-context-properties: fill;
+  fill: currentColor;
+}
+
 .target-list {
   margin: 0;
   padding: 0;
 }
 
 .target-container {
   margin-top: 5px;
   min-height: 34px;
@@ -139,21 +146,23 @@ button {
   border-width: 1px;
   border-style: solid;
 
   font-size: 0.6em;
   text-align: center;
 }
 
 .target-status-stopped {
+  color: var(--grey-90);
   border-color: grey;
   background-color: lightgrey;
 }
 
 .target-status-running {
+  color: var(--grey-90);
   border-color: limegreen;
   background-color: palegreen;
 }
 
 .target-name {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -168,48 +177,39 @@ button {
 .service-worker-multi-process {
   padding: 5px 10px;
   margin-top: 5px;
   margin-inline-end: 4px;
 }
 
 .addons-install-error {
   align-items: center;
-  background-color: #f3b0b0;
+  background-color: rgb(222, 33, 33, 0.3);
   display: flex;
   justify-content: space-between;
 }
 
 .service-worker-multi-process {
-  background-color: #ffeebb;
+  background-color: rgb(255, 191, 0, 0.3);
   line-height: 1.5em;
 }
 
 .service-worker-multi-process .update-button {
   margin: 5px 0;
 }
 
 .warning {
   display: inline-block;
   width: 12px;
   height: 12px;
   vertical-align: 0;
   margin-inline-end: 8px;
   background-image: url(chrome://devtools/skin/images/alert.svg);
   background-repeat: no-repeat;
   background-size: contain;
-  -moz-context-properties: fill;
-  fill: #d7b600;
-}
-
-.addons-install-error .warning,
-.service-worker-multi-process .warning {
-  /* The warning icon can be hard to see on red / yellow backgrounds, this turns the icon
-  to a black icon. */
-  fill: #0c0c0d;
 }
 
 .addons-install-error__additional-errors {
   font-family: monospace;
   font-size: 13px;
   margin-block: 8px;
 }
 
@@ -280,17 +280,17 @@ button {
 
 .target-card-heading-icon {
   height: 24px;
   width: 24px;
   margin-inline-end: 16px;
 }
 
 .target-card-actions {
-  border-top: 1px solid rgba(0, 0, 0, 0.2);
+  border-top: 1px solid var(--in-content-border-color);
   padding-top: 16px;
 }
 
 .target-card-action-link {
   background: none;
   border: none;
   /* !important overrides the common.css button color */
   color: var(--in-content-link-color) !important;
--- a/devtools/client/debugger/images/moz.build
+++ b/devtools/client/debugger/images/moz.build
@@ -40,12 +40,13 @@ DevToolsModules(
     'reload.svg',
     'resume.svg',
     'rewind.svg',
     'search.svg',
     'stepIn.svg',
     'stepOut.svg',
     'stepOver.svg',
     'tab.svg',
+    'webconsole-logpoint.svg',
     'whole-word-match.svg',
     'window.svg',
     'worker.svg',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/images/webconsole-logpoint.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
+  <path fill="context-fill" fill-opacity=".2" stroke="context-fill" stroke-linejoin="round" d="M.5 9V3c0-.83.67-1.5 1.5-1.5h5.05a.5.5 0 0 1 .38.17L11.33 6l-3.9 4.33a.5.5 0 0 1-.38.17H2A1.5 1.5 0 0 1 .5 9z"/>
+</svg>
--- a/devtools/client/debugger/src/components/Editor/Footer.css
+++ b/devtools/client/debugger/src/components/Editor/Footer.css
@@ -8,16 +8,17 @@
   position: absolute;
   display: flex;
   bottom: 0;
   left: 0;
   right: 0;
   opacity: 1;
   z-index: 1;
   -moz-user-select: none;
+  width: calc(100% - 1px);
   user-select: none;
   height: var(--editor-footer-height);
   box-sizing: border-box;
 }
 
 .source-footer-start {
   display: flex;
   align-items: center;
--- a/devtools/client/debugger/src/components/Editor/SearchBar.css
+++ b/devtools/client/debugger/src/components/Editor/SearchBar.css
@@ -20,17 +20,17 @@
   right: 0;
   bottom: -1px;
   border: solid 1px var(--blue-50);
   pointer-events: none;
   opacity: 0;
   transition: opacity 150ms ease-out;
 }
 
-.search-bar-focused::before {
+.search-bar:focus-within::before {
   opacity: 1;
 }
 
 .search-bar .search-outline {
   flex-grow: 1;
   border-width: 0;
 }
 
--- a/devtools/client/debugger/src/components/Editor/SearchBar.js
+++ b/devtools/client/debugger/src/components/Editor/SearchBar.js
@@ -47,18 +47,17 @@ function getShortcuts() {
     searchShortcut: searchKey
   };
 }
 
 type State = {
   query: string,
   selectedResultIndex: number,
   count: number,
-  index: number,
-  inputFocused: boolean
+  index: number
 };
 
 type Props = {
   cx: Context,
   editor: SourceEditor,
   selectedSource?: Source,
   selectedContentLoaded: boolean,
   searchOn: boolean,
@@ -77,18 +76,17 @@ type Props = {
 
 class SearchBar extends Component<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
       query: props.query,
       selectedResultIndex: 0,
       count: 0,
-      index: -1,
-      inputFocused: false
+      index: -1
     };
   }
 
   componentWillUnmount() {
     const shortcuts = this.context.shortcuts;
     const {
       searchShortcut,
       searchAgainShortcut,
@@ -143,36 +141,36 @@ class SearchBar extends Component<Props,
   closeSearch = (e: SyntheticEvent<HTMLElement>) => {
     const { cx, closeFileSearch, editor, searchOn } = this.props;
     if (editor && searchOn) {
       this.clearSearch();
       closeFileSearch(cx, editor);
       e.stopPropagation();
       e.preventDefault();
     }
-    this.setState({ query: "", inputFocused: false });
+    this.setState({ query: "" });
   };
 
   toggleSearch = (e: SyntheticKeyboardEvent<HTMLElement>) => {
     e.stopPropagation();
     e.preventDefault();
     const { editor, searchOn, setActiveSearch } = this.props;
 
     if (!searchOn) {
       setActiveSearch("file");
     }
 
     if (searchOn && editor) {
       const query = editor.codeMirror.getSelection() || this.state.query;
 
       if (query !== "") {
-        this.setState({ query, inputFocused: true });
+        this.setState({ query });
         this.doSearch(query);
       } else {
-        this.setState({ query: "", inputFocused: true });
+        this.setState({ query: "" });
       }
     }
   };
 
   doSearch = (query: string) => {
     const { cx, selectedSource, selectedContentLoaded } = this.props;
     if (!selectedSource || !selectedContentLoaded) {
       return;
@@ -195,24 +193,16 @@ class SearchBar extends Component<Props,
   // Handlers
 
   onChange = (e: SyntheticInputEvent<HTMLElement>) => {
     this.setState({ query: e.target.value });
 
     return this.doSearch(e.target.value);
   };
 
-  onFocus = (e: SyntheticFocusEvent<HTMLElement>) => {
-    this.setState({ inputFocused: true });
-  };
-
-  onBlur = (e: SyntheticFocusEvent<HTMLElement>) => {
-    this.setState({ inputFocused: false });
-  };
-
   onKeyDown = (e: any) => {
     if (e.key !== "Enter" && e.key !== "F3") {
       return;
     }
 
     this.traverseResults(e, e.shiftKey);
     e.preventDefault();
     return this.doSearch(e.target.value);
@@ -315,36 +305,31 @@ class SearchBar extends Component<Props,
       searchOn,
       showClose = true,
       size = "big"
     } = this.props;
 
     if (!searchOn) {
       return <div />;
     }
-    const classes = classnames("search-bar", {
-      "search-bar-focused": this.state.inputFocused
-    });
+
     return (
-      <div className={classes}>
+      <div className="search-bar">
         <SearchInput
           query={this.state.query}
           count={count}
           placeholder={L10N.getStr("sourceSearch.search.placeholder2")}
           summaryMsg={this.buildSummaryMsg()}
           isLoading={false}
           onChange={this.onChange}
-          onFocus={this.onFocus}
-          onBlur={this.onBlur}
           showErrorEmoji={this.shouldShowErrorEmoji()}
           onKeyDown={this.onKeyDown}
           onHistoryScroll={this.onHistoryScroll}
           handleNext={e => this.traverseResults(e, false)}
           handlePrev={e => this.traverseResults(e, true)}
-          shouldFocus={this.state.inputFocused}
           showClose={false}
         />
         <div className="search-bottom-bar">
           {this.renderSearchModifiers()}
           {showClose && (
             <React.Fragment>
               <span className="pipe-divider" />
               <CloseButton handleClick={this.closeSearch} buttonClass={size} />
--- a/devtools/client/debugger/src/components/Editor/menus/breakpoints.js
+++ b/devtools/client/debugger/src/components/Editor/menus/breakpoints.js
@@ -85,17 +85,17 @@ export const addLogPointItem = (
 });
 
 export const editLogPointItem = (
   location: SourceLocation,
   breakpointActions: BreakpointItemActions
 ) => ({
   id: "node-menu-edit-log-point",
   label: L10N.getStr("editor.editLogPoint"),
-  accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
+  accesskey: L10N.getStr("editor.editLogPoint.accesskey"),
   disabled: false,
   click: () => breakpointActions.openConditionalPanel(location, true),
   accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
 });
 
 export const logPointItem = (
   breakpoint: Breakpoint,
   breakpointActions: BreakpointItemActions
--- a/devtools/client/debugger/src/components/Editor/tests/__snapshots__/SearchBar.spec.js.snap
+++ b/devtools/client/debugger/src/components/Editor/tests/__snapshots__/SearchBar.spec.js.snap
@@ -5,25 +5,22 @@ exports[`SearchBar should render 1`] = `
   className="search-bar"
 >
   <SearchInput
     expanded={false}
     handleNext={[Function]}
     handlePrev={[Function]}
     hasPrefix={false}
     isLoading={false}
-    onBlur={[Function]}
     onChange={[Function]}
-    onFocus={[Function]}
     onHistoryScroll={[Function]}
     onKeyDown={[Function]}
     placeholder="Find in file…"
     query=""
     selectedItemId=""
-    shouldFocus={false}
     showClose={false}
     showErrorEmoji={false}
     size=""
     summaryMsg=""
   />
   <div
     className="search-bottom-bar"
   >
@@ -78,25 +75,22 @@ exports[`showErrorEmoji false if no quer
 >
   <SearchInput
     count={0}
     expanded={false}
     handleNext={[Function]}
     handlePrev={[Function]}
     hasPrefix={false}
     isLoading={false}
-    onBlur={[Function]}
     onChange={[Function]}
-    onFocus={[Function]}
     onHistoryScroll={[Function]}
     onKeyDown={[Function]}
     placeholder="Find in file…"
     query=""
     selectedItemId=""
-    shouldFocus={false}
     showClose={false}
     showErrorEmoji={false}
     size=""
     summaryMsg=""
   />
   <div
     className="search-bottom-bar"
   >
@@ -149,25 +143,22 @@ exports[`showErrorEmoji false if query +
 >
   <SearchInput
     count={10}
     expanded={false}
     handleNext={[Function]}
     handlePrev={[Function]}
     hasPrefix={false}
     isLoading={false}
-    onBlur={[Function]}
     onChange={[Function]}
-    onFocus={[Function]}
     onHistoryScroll={[Function]}
     onKeyDown={[Function]}
     placeholder="Find in file…"
     query="test"
     selectedItemId=""
-    shouldFocus={false}
     showClose={false}
     showErrorEmoji={false}
     size=""
     summaryMsg="-NaN of 10 results"
   />
   <div
     className="search-bottom-bar"
   >
@@ -220,25 +211,22 @@ exports[`showErrorEmoji true if query + 
 >
   <SearchInput
     count={0}
     expanded={false}
     handleNext={[Function]}
     handlePrev={[Function]}
     hasPrefix={false}
     isLoading={false}
-    onBlur={[Function]}
     onChange={[Function]}
-    onFocus={[Function]}
     onHistoryScroll={[Function]}
     onKeyDown={[Function]}
     placeholder="Find in file…"
     query="test"
     selectedItemId=""
-    shouldFocus={false}
     showClose={false}
     showErrorEmoji={true}
     size=""
     summaryMsg="No results found"
   />
   <div
     className="search-bottom-bar"
   >
--- a/devtools/client/debugger/src/components/ProjectSearch.js
+++ b/devtools/client/debugger/src/components/ProjectSearch.js
@@ -51,17 +51,16 @@ type Result = {
   matches: Array<Match>,
   sourceId: string
 };
 
 type Item = Result | Match;
 
 type State = {
   inputValue: string,
-  inputFocused: boolean,
   focusedItem: ?Item
 };
 
 type Props = {
   cx: Context,
   query: string,
   results: List<Result>,
   status: StatusType,
@@ -85,17 +84,16 @@ function sanitizeQuery(query: string): s
   return query.replace(/\\$/, "");
 }
 
 export class ProjectSearch extends Component<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
       inputValue: this.props.query || "",
-      inputFocused: false,
       focusedItem: null
     };
   }
 
   componentDidMount() {
     const { shortcuts } = this.context;
 
     shortcuts.on(
@@ -174,21 +172,17 @@ export class ProjectSearch extends Compo
     }
   };
 
   onHistoryScroll = (query: string) => {
     this.setState({ inputValue: query });
   };
 
   onEnterPress = () => {
-    if (
-      !this.isProjectSearchEnabled() ||
-      !this.state.focusedItem ||
-      this.state.inputFocused
-    ) {
+    if (!this.isProjectSearchEnabled() || !this.state.focusedItem) {
       return;
     }
     if (this.state.focusedItem.type === "MATCH") {
       this.selectMatchItem(this.state.focusedItem);
     }
   };
 
   onFocus = (item: Item) => {
@@ -296,18 +290,16 @@ export class ProjectSearch extends Compo
         query={this.state.inputValue}
         count={this.getResultCount()}
         placeholder={L10N.getStr("projectTextSearch.placeholder")}
         size="big"
         showErrorEmoji={this.shouldShowErrorEmoji()}
         summaryMsg={this.renderSummary()}
         isLoading={status === statusType.fetching}
         onChange={this.inputOnChange}
-        onFocus={() => this.setState({ inputFocused: true })}
-        onBlur={() => this.setState({ inputFocused: false })}
         onKeyDown={this.onKeyDown}
         onHistoryScroll={this.onHistoryScroll}
         handleClose={
           // TODO - This function doesn't quite match the signature.
           (this.props.closeProjectSearch: any)
         }
         ref="searchInput"
       />
--- a/devtools/client/debugger/src/components/QuickOpenModal.js
+++ b/devtools/client/debugger/src/components/QuickOpenModal.js
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 import React, { Component } from "react";
 import { connect } from "../utils/connect";
 import fuzzyAldrin from "fuzzaldrin-plus";
 import { basename } from "../utils/path";
+import { throttle } from "lodash";
 
 import actions from "../actions";
 import {
   getDisplayedSourcesList,
   getQuickOpenEnabled,
   getQuickOpenQuery,
   getQuickOpenType,
   getSelectedSource,
@@ -41,17 +42,17 @@ import type { Source, Context } from "..
 import type { QuickOpenType } from "../reducers/quick-open";
 import type { Tab } from "../reducers/tabs";
 
 import "./QuickOpenModal.css";
 
 type Props = {
   cx: Context,
   enabled: boolean,
-  sources: Array<Object>,
+  displayedSources: Source[],
   selectedSource?: Source,
   selectedContentLoaded?: boolean,
   query: string,
   searchType: QuickOpenType,
   symbols: FormattedSymbolDeclarations,
   symbolsLoading: boolean,
   tabs: Tab[],
   shortcutsModalEnabled: boolean,
@@ -68,22 +69,26 @@ type State = {
 };
 
 type GotoLocationType = {
   sourceId?: string,
   line: number,
   column?: number
 };
 
+const updateResultsThrottle = 100;
 const maxResults = 100;
 
 function filter(values, query) {
+  const preparedQuery = fuzzyAldrin.prepareQuery(query);
+
   return fuzzyAldrin.filter(values, query, {
     key: "value",
-    maxResults: maxResults
+    maxResults: maxResults,
+    preparedQuery
   });
 }
 
 export class QuickOpenModal extends Component<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = { results: null, selectedIndex: 0 };
   }
@@ -126,17 +131,20 @@ export class QuickOpenModal extends Comp
     this.props.closeQuickOpen();
   };
 
   dropGoto = (query: string) => {
     return query.split(":")[0];
   };
 
   searchSources = (query: string) => {
-    const { sources } = this.props;
+    const { displayedSources, tabs } = this.props;
+    const tabUrls = new Set(tabs.map((tab: Tab) => tab.url));
+    const sources = formatSources(displayedSources, tabUrls);
+
     const results =
       query == "" ? sources : filter(sources, this.dropGoto(query));
     return this.setResults(results);
   };
 
   searchSymbols = (query: string) => {
     const {
       symbols: { functions }
@@ -157,26 +165,34 @@ export class QuickOpenModal extends Comp
     if (query == "?") {
       this.setResults(results);
     } else {
       this.setResults(filter(results, query.slice(1)));
     }
   };
 
   showTopSources = () => {
-    const { tabs, sources } = this.props;
+    const { displayedSources, tabs } = this.props;
+    const tabUrls = new Set(tabs.map((tab: Tab) => tab.url));
+
     if (tabs.length > 0) {
-      const tabUrls = tabs.map((tab: Tab) => tab.url);
-      this.setResults(sources.filter(source => tabUrls.includes(source.url)));
+      this.setResults(
+        formatSources(
+          displayedSources.filter(
+            source => !!source.url && tabUrls.has(source.url)
+          ),
+          tabUrls
+        )
+      );
     } else {
-      this.setResults(sources);
+      this.setResults(formatSources(displayedSources, tabUrls));
     }
   };
 
-  updateResults = (query: string) => {
+  updateResults = throttle((query: string) => {
     if (this.isGotoQuery()) {
       return;
     }
 
     if (query == "" && !this.isShortcutQuery()) {
       return this.showTopSources();
     }
 
@@ -184,17 +200,17 @@ export class QuickOpenModal extends Comp
       return this.searchSymbols(query);
     }
 
     if (this.isShortcutQuery()) {
       return this.searchShortcuts(query);
     }
 
     return this.searchSources(query);
-  };
+  }, updateResultsThrottle);
 
   setModifier = (item: QuickOpenResult) => {
     if (["@", "#", ":"].includes(item.id)) {
       this.props.setQuickOpenQuery(item.id);
     }
   };
 
   selectResultItem = (
@@ -426,30 +442,31 @@ export class QuickOpenModal extends Comp
     );
   }
 }
 
 /* istanbul ignore next: ignoring testing of redux connection stuff */
 function mapStateToProps(state) {
   const selectedSource = getSelectedSource(state);
   const displayedSources = getDisplayedSourcesList(state);
+  const tabs = getTabs(state);
 
   return {
     cx: getContext(state),
     enabled: getQuickOpenEnabled(state),
-    sources: formatSources(displayedSources, getTabs(state)),
+    displayedSources,
     selectedSource,
     selectedContentLoaded: selectedSource
       ? !!getSourceContent(state, selectedSource.id)
       : undefined,
     symbols: formatSymbols(getSymbols(state, selectedSource)),
     symbolsLoading: isSymbolsLoading(state, selectedSource),
     query: getQuickOpenQuery(state),
     searchType: getQuickOpenType(state),
-    tabs: getTabs(state)
+    tabs
   };
 }
 
 /* istanbul ignore next: ignoring testing of redux connection stuff */
 export default connect(
   mapStateToProps,
   {
     selectSpecificLocation: actions.selectSpecificLocation,
--- a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js
@@ -229,17 +229,17 @@ export default function showContextMenu(
     disabled: false,
     click: () => openConditionalPanel(selectedLocation, true),
     accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
   };
 
   const editLogPointItem = {
     id: "node-menu-edit-log-point",
     label: L10N.getStr("editor.editLogPoint"),
-    accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
+    accesskey: L10N.getStr("editor.editLogPoint.accesskey"),
     disabled: false,
     click: () => openConditionalPanel(selectedLocation, true),
     accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
   };
 
   const removeLogPointItem = {
     id: "node-menu-remove-log",
     label: L10N.getStr("editor.removeLogPoint.label"),
--- a/devtools/client/debugger/src/components/SecondaryPanes/index.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/index.js
@@ -424,31 +424,29 @@ class SecondaryPanes extends Component<P
         <WhyPaused delay={renderWhyPauseDelay} />
         <Accordion items={this.getItems()} />
       </div>
     );
   }
 
   renderVerticalLayout() {
     return (
-      <div>
-        <SplitBox
-          initialSize="300px"
-          minSize={10}
-          maxSize="50%"
-          splitterSize={1}
-          startPanel={
-            <div style={{ width: "inherit" }}>
-              <WhyPaused delay={this.props.renderWhyPauseDelay} />
-              <Accordion items={this.getStartItems()} />
-            </div>
-          }
-          endPanel={<Accordion items={this.getEndItems()} />}
-        />
-      </div>
+      <SplitBox
+        initialSize="300px"
+        minSize={10}
+        maxSize="50%"
+        splitterSize={1}
+        startPanel={
+          <div style={{ width: "inherit" }}>
+            <WhyPaused delay={this.props.renderWhyPauseDelay} />
+            <Accordion items={this.getStartItems()} />
+          </div>
+        }
+        endPanel={<Accordion items={this.getEndItems()} />}
+      />
     );
   }
 
   renderUtilsBar() {
     if (!features.shortcuts) {
       return;
     }
 
--- a/devtools/client/debugger/src/components/WelcomeBox.css
+++ b/devtools/client/debugger/src/components/WelcomeBox.css
@@ -6,16 +6,17 @@
   position: absolute;
   top: var(--editor-header-height);
   left: 0;
   bottom: var(--editor-footer-height);
   width: calc(100% - 1px);
   padding: 50px 0 0 0;
   text-align: center;
   background-color: var(--theme-toolbar-background);
+  overflow: hidden;
   font-weight: lighter;
   z-index: 10;
   user-select: none;
 }
 
 .theme-dark .welcomebox {
   background-color: var(--theme-body-background);
 }
--- a/devtools/client/debugger/src/components/shared/Popover.css
+++ b/devtools/client/debugger/src/components/shared/Popover.css
@@ -2,17 +2,31 @@
  * 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/>. */
 
 .popover {
   position: fixed;
   z-index: 100;
 }
 
-.popover .gap {
+.popover {
+  position: fixed;
+  z-index: 100;
+}
+
+.popover.orientation-right {
+  display: flex;
+  flex-direction: row;
+}
+
+.popover.orientation-right .gap {
+  padding-left: 5px;
+}
+
+.popover:not(.orientation-right) .gap {
   height: 5px;
   padding-top: 5px;
 }
 
 .popover:not(.orientation-right) .preview-popup {
   margin-left: -55px;
 }
 
--- a/devtools/client/debugger/src/components/shared/Popover.js
+++ b/devtools/client/debugger/src/components/shared/Popover.js
@@ -69,17 +69,17 @@ class Popover extends Component<Props, S
     editor: ClientRect,
     popover: ClientRect,
     orientation?: Orientation
   ) {
     const estimatedLeft = target.left;
     const estimatedRight = estimatedLeft + popover.width;
     const isOverflowingRight = estimatedRight > editor.right;
     if (orientation === "right") {
-      return target.left + target.width + 10;
+      return target.left + target.width + 5;
     }
     if (isOverflowingRight) {
       const adjustedLeft = editor.right - popover.width - 8;
       return adjustedLeft;
     }
     return estimatedLeft;
   }
 
@@ -216,17 +216,17 @@ class Popover extends Component<Props, S
 
   getPopoverArrow(orientation: Orientation, left: number, top: number) {
     const arrowProps = {};
     if (orientation === "up") {
       Object.assign(arrowProps, { orientation: "down", bottom: 5, left });
     } else if (orientation === "down") {
       Object.assign(arrowProps, { orientation: "up", top: -7, left });
     } else {
-      Object.assign(arrowProps, { orientation: "left", top, left: -14 });
+      Object.assign(arrowProps, { orientation: "left", top, left: -9 });
     }
 
     return <BracketArrow {...arrowProps} />;
   }
 
   renderPopover() {
     const { top, left, orientation, targetMid } = this.state.coords;
     const { onMouseLeave, onKeyDown } = this.props;
--- a/devtools/client/debugger/src/components/shared/SearchInput.css
+++ b/devtools/client/debugger/src/components/shared/SearchInput.css
@@ -12,17 +12,17 @@
 }
 
 .search-outline {
   border: 1px solid var(--theme-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   transition: border-color 200ms ease-in-out;
 }
 
-.search-outline.focused {
+.search-outline:focus-within {
   border-color: var(--blue-50);
 }
 
 .search-field {
   position: relative;
   display: flex;
   align-items: center;
   flex-shrink: 0;
--- a/devtools/client/debugger/src/components/shared/SearchInput.js
+++ b/devtools/client/debugger/src/components/shared/SearchInput.js
@@ -30,35 +30,31 @@ const arrowBtn = (onClick, type, classNa
 
 type Props = {
   count: number,
   expanded: boolean,
   handleClose?: (e: SyntheticMouseEvent<HTMLDivElement>) => void,
   handleNext?: (e: SyntheticMouseEvent<HTMLButtonElement>) => void,
   handlePrev?: (e: SyntheticMouseEvent<HTMLButtonElement>) => void,
   hasPrefix?: boolean,
-  onBlur?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
   onChange: (e: SyntheticInputEvent<HTMLInputElement>) => void,
-  onFocus?: (e: SyntheticFocusEvent<HTMLInputElement>) => void,
   onKeyDown: (e: SyntheticKeyboardEvent<HTMLInputElement>) => void,
   onKeyUp?: (e: SyntheticKeyboardEvent<HTMLInputElement>) => void,
   onHistoryScroll?: (historyValue: string) => void,
   placeholder: string,
   query: string,
   selectedItemId?: string,
-  shouldFocus?: boolean,
   showErrorEmoji: boolean,
   size: string,
   summaryMsg: string,
   showClose: boolean,
   isLoading: boolean
 };
 
 type State = {
-  inputFocused: boolean,
   history: Array<string>
 };
 
 class SearchInput extends Component<Props, State> {
   displayName: "SearchInput";
   $input: ?HTMLInputElement;
 
   static defaultProps = {
@@ -68,31 +64,24 @@ class SearchInput extends Component<Prop
     size: "",
     showClose: true
   };
 
   constructor(props: Props) {
     super(props);
 
     this.state = {
-      inputFocused: false,
       history: []
     };
   }
 
   componentDidMount() {
     this.setFocus();
   }
 
-  componentDidUpdate(prevProps: Props) {
-    if (this.props.shouldFocus && !prevProps.shouldFocus) {
-      this.setFocus();
-    }
-  }
-
   setFocus() {
     if (this.$input) {
       const input = this.$input;
       input.focus();
 
       if (!input.value) {
         return;
       }
@@ -121,34 +110,16 @@ class SearchInput extends Component<Prop
         handleNext,
         "arrow-down",
         classnames("nav-btn", "next"),
         L10N.getFormatStr("editor.searchResults.nextResult")
       )
     ];
   }
 
-  onFocus = (e: SyntheticFocusEvent<HTMLInputElement>) => {
-    const { onFocus } = this.props;
-
-    this.setState({ inputFocused: true });
-    if (onFocus) {
-      onFocus(e);
-    }
-  };
-
-  onBlur = (e: SyntheticFocusEvent<HTMLInputElement>) => {
-    const { onBlur } = this.props;
-
-    this.setState({ inputFocused: false });
-    if (onBlur) {
-      onBlur(e);
-    }
-  };
-
   onKeyDown = (e: any) => {
     const { onHistoryScroll, onKeyDown } = this.props;
     if (!onHistoryScroll) {
       return onKeyDown(e);
     }
 
     const inputValue = e.target.value;
     const { history } = this.state;
@@ -233,34 +204,28 @@ class SearchInput extends Component<Prop
 
     const inputProps = {
       className: classnames({
         empty: showErrorEmoji
       }),
       onChange,
       onKeyDown: e => this.onKeyDown(e),
       onKeyUp,
-      onFocus: e => this.onFocus(e),
-      onBlur: e => this.onBlur(e),
       "aria-autocomplete": "list",
       "aria-controls": "result-list",
       "aria-activedescendant":
         expanded && selectedItemId ? `${selectedItemId}-title` : "",
       placeholder,
       value: query,
       spellCheck: false,
       ref: c => (this.$input = c)
     };
 
     return (
-      <div
-        className={classnames("search-outline", {
-          focused: this.state.inputFocused
-        })}
-      >
+      <div className="search-outline">
         <div
           className={classnames("search-field", size)}
           role="combobox"
           aria-haspopup="listbox"
           aria-owns="result-list"
           aria-expanded={expanded}
         >
           {this.renderSvg()}
--- a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap
@@ -25,32 +25,32 @@ exports[`Popover mount popover 1`] = `
   type="popover"
 >
   <div
     className="popover orientation-right"
     onKeyDown={[MockFunction]}
     onMouseLeave={[MockFunction]}
     style={
       Object {
-        "left": 510,
+        "left": 505,
         "top": 250,
       }
     }
   >
     <BracketArrow
-      left={-14}
+      left={-9}
       orientation="left"
       top={-202}
     >
       <div
         className="bracket-arrow left"
         style={
           Object {
             "bottom": undefined,
-            "left": -14,
+            "left": -9,
             "top": -202,
           }
         }
       />
     </BracketArrow>
     <div
       className="gap"
       key="gap"
@@ -155,32 +155,32 @@ exports[`Popover render 1`] = `
   type="popover"
 >
   <div
     className="popover orientation-right"
     onKeyDown={[MockFunction]}
     onMouseLeave={[MockFunction]}
     style={
       Object {
-        "left": 510,
+        "left": 505,
         "top": 250,
       }
     }
   >
     <BracketArrow
-      left={-14}
+      left={-9}
       orientation="left"
       top={-202}
     >
       <div
         className="bracket-arrow left"
         style={
           Object {
             "bottom": undefined,
-            "left": -14,
+            "left": -9,
             "top": -202,
           }
         }
       />
     </BracketArrow>
     <div
       className="gap"
       key="gap"
--- a/devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap
@@ -14,19 +14,17 @@ exports[`SearchInput renders 1`] = `
     <AccessibleImage
       className="search"
     />
     <input
       aria-activedescendant=""
       aria-autocomplete="list"
       aria-controls="result-list"
       className=""
-      onBlur={[Function]}
       onChange={[Function]}
-      onFocus={[Function]}
       onKeyDown={[Function]}
       placeholder="A placeholder"
       spellCheck={false}
       value=""
     />
     <div
       className="search-field-summary"
     >
@@ -53,19 +51,17 @@ exports[`SearchInput shows nav buttons 1
     <AccessibleImage
       className="search"
     />
     <input
       aria-activedescendant=""
       aria-autocomplete="list"
       aria-controls="result-list"
       className=""
-      onBlur={[Function]}
       onChange={[Function]}
-      onFocus={[Function]}
       onKeyDown={[Function]}
       placeholder="A placeholder"
       spellCheck={false}
       value=""
     />
     <div
       className="search-field-summary"
     >
@@ -118,19 +114,17 @@ exports[`SearchInput shows svg error emo
     <AccessibleImage
       className="search"
     />
     <input
       aria-activedescendant=""
       aria-autocomplete="list"
       aria-controls="result-list"
       className="empty"
-      onBlur={[Function]}
       onChange={[Function]}
-      onFocus={[Function]}
       onKeyDown={[Function]}
       placeholder="A placeholder"
       spellCheck={false}
       value=""
     />
     <div
       className="search-field-summary"
     >
@@ -183,19 +177,17 @@ exports[`SearchInput shows svg magnifyin
     <AccessibleImage
       className="search"
     />
     <input
       aria-activedescendant=""
       aria-autocomplete="list"
       aria-controls="result-list"
       className=""
-      onBlur={[Function]}
       onChange={[Function]}
-      onFocus={[Function]}
       onKeyDown={[Function]}
       placeholder="A placeholder"
       spellCheck={false}
       value=""
     />
     <div
       className="search-field-summary"
     >
--- a/devtools/client/debugger/src/components/test/QuickOpenModal.spec.js
+++ b/devtools/client/debugger/src/components/test/QuickOpenModal.spec.js
@@ -1,31 +1,37 @@
 /* eslint max-nested-callbacks: ["error", 4] */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import React from "react";
+import lodash from "lodash";
+
 import { shallow, mount } from "enzyme";
 import { QuickOpenModal } from "../QuickOpenModal";
 import { mockcx } from "../../utils/test-mockup";
 
 jest.mock("fuzzaldrin-plus");
+jest.unmock("lodash");
 
 import { filter } from "fuzzaldrin-plus";
 
+// $FlowIgnore
+lodash.throttle = jest.fn(fn => fn);
+
 function generateModal(propOverrides, renderType = "shallow") {
   const props = {
     cx: mockcx,
     enabled: false,
     query: "",
     searchType: "sources",
-    sources: [],
+    displayedSources: [],
     tabs: [],
     selectSpecificLocation: jest.fn(),
     setQuickOpenQuery: jest.fn(),
     highlightLineRange: jest.fn(),
     clearHighlightLineRange: jest.fn(),
     closeQuickOpen: jest.fn(),
     shortcutsModalEnabled: false,
     symbols: { functions: [] },
@@ -108,22 +114,34 @@ describe("QuickOpenModal", () => {
     expect(props.toggleShortcutsModal).toHaveBeenCalled();
   });
 
   test("shows top sources", () => {
     const { wrapper } = generateModal(
       {
         enabled: true,
         query: "",
-        sources: [{ url: "mozilla.com" }],
+        displayedSources: [
+          // $FlowIgnore
+          { url: "mozilla.com", relativeUrl: true }
+        ],
         tabs: [generateTab("mozilla.com")]
       },
       "shallow"
     );
-    expect(wrapper.state("results")).toEqual([{ url: "mozilla.com" }]);
+    expect(wrapper.state("results")).toEqual([
+      {
+        id: undefined,
+        icon: "tab result-item-icon",
+        subtitle: "true",
+        title: "mozilla.com",
+        url: "mozilla.com",
+        value: "true"
+      }
+    ]);
   });
 
   describe("shows loading", () => {
     it("loads with function type search", () => {
       const { wrapper } = generateModal(
         {
           enabled: true,
           query: "",
--- a/devtools/client/debugger/src/components/test/__snapshots__/ProjectSearch.spec.js.snap
+++ b/devtools/client/debugger/src/components/test/__snapshots__/ProjectSearch.spec.js.snap
@@ -11,19 +11,17 @@ exports[`ProjectSearch found no search r
       className="header"
     >
       <SearchInput
         count={0}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={false}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query="foo"
         selectedItemId=""
         showClose={true}
         showErrorEmoji={true}
         size="big"
@@ -114,19 +112,17 @@ exports[`ProjectSearch found search resu
         className="header"
       >
         <SearchInput
           count={5}
           expanded={false}
           handleClose={[MockFunction]}
           hasPrefix={false}
           isLoading={false}
-          onBlur={[Function]}
           onChange={[Function]}
-          onFocus={[Function]}
           onHistoryScroll={[Function]}
           onKeyDown={[Function]}
           placeholder="Find in files…"
           query="match"
           selectedItemId=""
           showClose={true}
           showErrorEmoji={false}
           size="big"
@@ -149,19 +145,17 @@ exports[`ProjectSearch found search resu
                   className="img search"
                 />
               </AccessibleImage>
               <input
                 aria-activedescendant=""
                 aria-autocomplete="list"
                 aria-controls="result-list"
                 className=""
-                onBlur={[Function]}
                 onChange={[Function]}
-                onFocus={[Function]}
                 onKeyDown={[Function]}
                 placeholder="Find in files…"
                 spellCheck={false}
                 value="match"
               />
               <div
                 className="search-field-summary"
               >
@@ -745,19 +739,17 @@ exports[`ProjectSearch should display lo
       className="header"
     >
       <SearchInput
         count={0}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={true}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query="match"
         selectedItemId=""
         showClose={true}
         showErrorEmoji={false}
         size="big"
@@ -784,19 +776,17 @@ exports[`ProjectSearch showErrorEmoji fa
       className="header"
     >
       <SearchInput
         count={0}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={true}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query="foo"
         selectedItemId=""
         showClose={true}
         showErrorEmoji={false}
         size="big"
@@ -823,19 +813,17 @@ exports[`ProjectSearch showErrorEmoji fa
       className="header"
     >
       <SearchInput
         count={5}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={true}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query="foo"
         selectedItemId=""
         showClose={true}
         showErrorEmoji={false}
         size="big"
@@ -870,19 +858,17 @@ exports[`ProjectSearch turns off shortcu
       className="header"
     >
       <SearchInput
         count={0}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={false}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query=""
         selectedItemId=""
         showClose={true}
         showErrorEmoji={true}
         size="big"
@@ -904,19 +890,17 @@ exports[`ProjectSearch where <Enter> has
       className="header"
     >
       <SearchInput
         count={0}
         expanded={false}
         handleClose={[MockFunction]}
         hasPrefix={false}
         isLoading={false}
-        onBlur={[Function]}
         onChange={[Function]}
-        onFocus={[Function]}
         onHistoryScroll={[Function]}
         onKeyDown={[Function]}
         placeholder="Find in files…"
         query=""
         selectedItemId=""
         showClose={true}
         showErrorEmoji={true}
         size="big"
--- a/devtools/client/debugger/src/components/test/__snapshots__/QuickOpenModal.spec.js.snap
+++ b/devtools/client/debugger/src/components/test/__snapshots__/QuickOpenModal.spec.js.snap
@@ -4,25 +4,25 @@ exports[`QuickOpenModal Basic render wit
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query="@"
   searchType="functions"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
       "variables": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
@@ -93,19 +93,17 @@ exports[`QuickOpenModal Basic render wit
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className="empty"
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value="@"
                   />
                 </div>
               </div>
             </SearchInput>
@@ -137,25 +135,25 @@ exports[`QuickOpenModal Basic render wit
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query="#"
   searchType="variables"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
       "variables": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
@@ -226,19 +224,17 @@ exports[`QuickOpenModal Basic render wit
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className="empty"
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value="#"
                   />
                 </div>
               </div>
             </SearchInput>
@@ -254,25 +250,25 @@ exports[`QuickOpenModal Basic render wit
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=""
   searchType="sources"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -342,19 +338,17 @@ exports[`QuickOpenModal Basic render wit
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className=""
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=""
                   />
                 </div>
               </div>
             </SearchInput>
@@ -421,25 +415,25 @@ exports[`QuickOpenModal Simple goto sear
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=":abc"
   searchType="goto"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
       "variables": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
@@ -510,19 +504,17 @@ exports[`QuickOpenModal Simple goto sear
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className="empty"
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=":abc"
                   />
                   <div
                     className="search-field-summary"
                   >
@@ -543,25 +535,25 @@ exports[`QuickOpenModal showErrorEmoji f
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query="dasdasdas"
   searchType="sources"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -631,19 +623,17 @@ exports[`QuickOpenModal showErrorEmoji f
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className=""
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value="dasdasdas"
                   />
                 </div>
               </div>
             </SearchInput>
@@ -707,25 +697,25 @@ exports[`QuickOpenModal showErrorEmoji f
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=":2222"
   searchType="goto"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -795,19 +785,17 @@ exports[`QuickOpenModal showErrorEmoji f
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className=""
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=":2222"
                   />
                   <div
                     className="search-field-summary"
                   >
@@ -828,25 +816,25 @@ exports[`QuickOpenModal showErrorEmoji f
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=""
   searchType=""
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -916,19 +904,17 @@ exports[`QuickOpenModal showErrorEmoji f
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className=""
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=""
                   />
                 </div>
               </div>
             </SearchInput>
@@ -960,25 +946,25 @@ exports[`QuickOpenModal showErrorEmoji t
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=":22k22"
   searchType="goto"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -1048,19 +1034,17 @@ exports[`QuickOpenModal showErrorEmoji t
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className="empty"
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=":22k22"
                   />
                   <div
                     className="search-field-summary"
                   >
@@ -1081,25 +1065,25 @@ exports[`QuickOpenModal showErrorEmoji t
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query="test"
   searchType=""
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -1169,19 +1153,17 @@ exports[`QuickOpenModal showErrorEmoji t
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className="empty"
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value="test"
                   />
                 </div>
               </div>
             </SearchInput>
@@ -1230,25 +1212,25 @@ exports[`QuickOpenModal updateResults on
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={false}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=""
   searchType="sources"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -1260,25 +1242,25 @@ exports[`QuickOpenModal updateResults on
 <QuickOpenModal
   clearHighlightLineRange={[MockFunction]}
   closeQuickOpen={[MockFunction]}
   cx={
     Object {
       "navigateCounter": 0,
     }
   }
+  displayedSources={Array []}
   enabled={true}
   highlightLineRange={[MockFunction]}
   isOriginal={false}
   query=""
   searchType="sources"
   selectSpecificLocation={[MockFunction]}
   setQuickOpenQuery={[MockFunction]}
   shortcutsModalEnabled={false}
-  sources={Array []}
   symbols={
     Object {
       "functions": Array [],
     }
   }
   symbolsLoading={false}
   tabs={Array []}
   thread="FakeThread"
@@ -1348,19 +1330,17 @@ exports[`QuickOpenModal updateResults on
                       className="img search"
                     />
                   </AccessibleImage>
                   <input
                     aria-activedescendant=""
                     aria-autocomplete="list"
                     aria-controls="result-list"
                     className=""
-                    onBlur={[Function]}
                     onChange={[Function]}
-                    onFocus={[Function]}
                     onKeyDown={[Function]}
                     placeholder="Go to file…"
                     spellCheck={false}
                     value=""
                   />
                 </div>
               </div>
             </SearchInput>
--- a/devtools/client/debugger/src/utils/quick-open.js
+++ b/devtools/client/debugger/src/utils/quick-open.js
@@ -10,17 +10,17 @@ import {
   getFilename,
   getSourceClassnames,
   getSourceQueryString
 } from "./source";
 
 import type { Location as BabelLocation } from "@babel/types";
 import type { Symbols } from "../reducers/ast";
 import type { QuickOpenType } from "../reducers/quick-open";
-import type { TabList } from "../reducers/tabs";
+import type { Tab } from "../reducers/tabs";
 import type { Source } from "../types";
 import type {
   SymbolDeclaration,
   IdentifierDeclaration
 } from "../workers/parser";
 
 export const MODIFIERS = {
   "@": "functions",
@@ -54,28 +54,31 @@ export function parseLineColumn(query: s
   if (!isNaN(lineNumber)) {
     return {
       line: lineNumber,
       ...(!isNaN(columnNumber) ? { column: columnNumber } : null)
     };
   }
 }
 
-export function formatSourcesForList(source: Source, tabs: TabList) {
+export function formatSourcesForList(
+  source: Source,
+  tabUrls: Set<$PropertyType<Tab, "url">>
+) {
   const title = getFilename(source);
   const relativeUrlWithQuery = `${source.relativeUrl}${getSourceQueryString(
     source
   ) || ""}`;
   const subtitle = endTruncateStr(relativeUrlWithQuery, 100);
   const value = relativeUrlWithQuery;
   return {
     value,
     title,
     subtitle,
-    icon: tabs.some(tab => tab.url == source.url)
+    icon: tabUrls.has(source.url)
       ? "tab result-item-icon"
       : classnames(getSourceClassnames(source), "result-item-icon"),
     id: source.id,
     url: source.url
   };
 }
 
 export type QuickOpenResult = {|
@@ -133,15 +136,15 @@ export function formatShortcutResults():
       title: `: ${L10N.getStr("gotoLineModal.placeholder")}`,
       id: ":"
     }
   ];
 }
 
 export function formatSources(
   sources: Source[],
-  tabs: TabList
+  tabUrls: Set<$PropertyType<Tab, "url">>
 ): Array<QuickOpenResult> {
   return sources
     .filter(source => !isPretty(source))
-    .filter(({ relativeUrl }) => !!relativeUrl)
-    .map(source => formatSourcesForList(source, tabs));
+    .filter(source => !!source.relativeUrl && !isPretty(source))
+    .map(source => formatSourcesForList(source, tabUrls));
 }
--- a/devtools/client/debugger/src/utils/result-list.js
+++ b/devtools/client/debugger/src/utils/result-list.js
@@ -37,17 +37,23 @@ export function scrollList(
 
 function chromeScrollList(elem: Element, index: number): void {
   const resultsEl: any = elem.parentNode;
 
   if (!resultsEl || resultsEl.children.length === 0) {
     return;
   }
 
-  const resultsHeight: number = resultsEl.clientHeight;
-  const itemHeight: number = resultsEl.children[0].clientHeight;
-  const numVisible: number = resultsHeight / itemHeight;
-  const positionsToScroll: number = index - numVisible + 1;
-  const itemOffset: number = resultsHeight % itemHeight;
-  const scroll: number = positionsToScroll * (itemHeight + 2) + itemOffset;
+  // Avoid expensive DOM computations (reading clientHeight)
+  // https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web/
+  requestAnimationFrame(() => {
+    setTimeout(() => {
+      const resultsHeight: number = resultsEl.clientHeight;
+      const itemHeight: number = resultsEl.children[0].clientHeight;
+      const numVisible: number = resultsHeight / itemHeight;
+      const positionsToScroll: number = index - numVisible + 1;
+      const itemOffset: number = resultsHeight % itemHeight;
+      const scroll: number = positionsToScroll * (itemHeight + 2) + itemOffset;
 
-  resultsEl.scrollTop = Math.max(0, scroll);
+      resultsEl.scrollTop = Math.max(0, scroll);
+    });
+  });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-quick-open.js
@@ -35,20 +35,22 @@ async function waitToClose(dbg) {
   pressKey(dbg, "Escape");
   return new Promise(r => setTimeout(r, 200));
 }
 
 function resultCount(dbg) {
   return findAllElements(dbg, "resultItems").length;
 }
 
-function quickOpen(dbg, query, shortcut = "quickOpen") {
+async function quickOpen(dbg, query, shortcut = "quickOpen") {
   pressKey(dbg, shortcut);
   assertEnabled(dbg);
   query !== "" && type(dbg, query);
+
+  await waitForTime(150);
 }
 
 function findResultEl(dbg, index = 1) {
   return waitForElementWithSelector(dbg, `.result-item:nth-child(${index})`);
 }
 
 async function assertResultIsTab(dbg, index) {
   const el = await findResultEl(dbg, index);
@@ -58,67 +60,68 @@ async function assertResultIsTab(dbg, in
   );
 }
 
 // Testing quick open
 add_task(async function() {
   const dbg = await initDebugger("doc-script-switching.html");
 
   info("test opening and closing");
-  quickOpen(dbg, "");
+  await quickOpen(dbg, "");
   pressKey(dbg, "Escape");
   assertDisabled(dbg);
 
   info("Testing the number of results for source search");
-  quickOpen(dbg, "sw");
+  await quickOpen(dbg, "sw");
   is(resultCount(dbg), 2, "two file results");
   pressKey(dbg, "Escape");
 
   info("Testing source search and check to see if source is selected");
   await waitForSource(dbg, "switching-01");
-  quickOpen(dbg, "sw1");
+  await quickOpen(dbg, "sw1");
   is(resultCount(dbg), 1, "one file results");
   pressKey(dbg, "Enter");
   await waitForSelectedSource(dbg, "switching-01");
 
   info("Test that results show tab icons");
-  quickOpen(dbg, "sw1");
+  await quickOpen(dbg, "sw1");
   await assertResultIsTab(dbg, 1);
   pressKey(dbg, "Tab");
 
   info(
     "Testing arrow keys in source search and check to see if source is selected"
   );
-  quickOpen(dbg, "sw2");
+  await quickOpen(dbg, "sw2");
   is(resultCount(dbg), 1, "one file results");
   pressKey(dbg, "Down");
   pressKey(dbg, "Enter");
   await waitForSelectedSource(dbg, "switching-02");
 
   info("Testing tab closes the search");
-  quickOpen(dbg, "sw");
+  await quickOpen(dbg, "sw");
   pressKey(dbg, "Tab");
   assertDisabled(dbg);
 
   info("Testing function search");
-  quickOpen(dbg, "", "quickOpenFunc");
+  await quickOpen(dbg, "", "quickOpenFunc");
   is(resultCount(dbg), 2, "two function results");
 
   type(dbg, "@x");
+  await waitForTime(150);
   is(resultCount(dbg), 0, "no functions with 'x' in name");
 
   pressKey(dbg, "Escape");
   assertDisabled(dbg);
 
   info("Testing goto line:column");
   assertLine(dbg, 0);
   assertColumn(dbg, null);
-  quickOpen(dbg, ":7:12");
+  await quickOpen(dbg, ":7:12");
   pressKey(dbg, "Enter");
   assertLine(dbg, 7);
   assertColumn(dbg, 12);
 
   info("Testing gotoSource");
-  quickOpen(dbg, "sw1:5");
+  await quickOpen(dbg, "sw1:5");
   pressKey(dbg, "Enter");
   await waitForSelectedSource(dbg, "switching-01");
   assertLine(dbg, 5);
 });
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -126,16 +126,20 @@ table.value=Values
 # LOCALIZATION NOTE (level.error, level.warn, level.info, level.log, level.debug):
 # tooltip for icons next to console output
 level.error=Error
 level.warn=Warning
 level.info=Info
 level.log=Log
 level.debug=Debug
 
+# LOCALIZATION NOTE (logpoint.title)
+# Tooltip shown for logpoints sent from the debugger
+logpoint.title=Logpoints from the debugger
+
 # LOCALIZATION NOTE (webconsole.find.key)
 # Key shortcut used to focus the search box on upper right of the console
 webconsole.find.key=CmdOrCtrl+F
 
 # LOCALIZATION NOTE (webconsole.close.key)
 # Key shortcut used to close the Browser console (doesn't work in regular web console)
 webconsole.close.key=CmdOrCtrl+W
 
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -228,16 +228,21 @@ a {
 }
 
 .message:hover > .icon.rewindable {
   background-image: url(chrome://devtools/skin/images/next-circle.svg);
   cursor: pointer;
   transform: rotate(180deg);
 }
 
+.message > .icon.logpoint {
+  background-image: url(resource://devtools/client/debugger/images/webconsole-logpoint.svg);
+  color: var(--theme-graphs-purple);
+}
+
 /*
  * we flip the next.svg icon by default because when we're
  * not paused, we would jump back. We remove the transform here
  * because we want to jump forward.
  */
 .message.paused ~ .message:hover .icon.rewindable {
   transform: none;
 }
--- a/devtools/client/webconsole/components/Message.js
+++ b/devtools/client/webconsole/components/Message.js
@@ -139,27 +139,29 @@ class Message extends Component {
 
   renderIcon() {
     const {
       level,
       messageId,
       executionPoint,
       serviceContainer,
       inWarningGroup,
+      type,
     } = this.props;
 
     if (inWarningGroup) {
       return undefined;
     }
 
     return MessageIcon({
       level,
       onRewindClick: (serviceContainer.canRewind() && executionPoint)
         ? () => serviceContainer.jumpToExecutionPoint(executionPoint, messageId)
         : null,
+      type,
     });
   }
 
   render() {
     const {
       open,
       collapsible,
       collapseTitle,
--- a/devtools/client/webconsole/components/MessageIcon.js
+++ b/devtools/client/webconsole/components/MessageIcon.js
@@ -20,40 +20,52 @@ const l10nLevels = {
 
 // Store common icons so they can be used without recreating the element
 // during render.
 const CONSTANT_ICONS = Object.entries(l10nLevels).reduce((acc, [key, l10nLabel]) => {
   acc[key] = getIconElement(l10nLabel);
   return acc;
 }, {});
 
-function getIconElement(level, onRewindClick) {
+function getIconElement(level, onRewindClick, type) {
   let title = l10n.getStr(l10nLevels[level] || level);
   const classnames = ["icon"];
 
   if (onRewindClick) {
     title = l10n.getFormatStr("webconsole.jumpButton.tooltip", [title]);
     classnames.push("rewindable");
   }
 
-  return dom.span({
+  if (type && type === "logPoint") {
+    title = l10n.getStr("logpoint.title");
+    classnames.push("logpoint");
+  }
+
+  { return dom.span({
     className: classnames.join(" "),
     onClick: onRewindClick,
     title,
     "aria-live": "off",
-  });
+  }); }
 }
 
 MessageIcon.displayName = "MessageIcon";
 MessageIcon.propTypes = {
   level: PropTypes.string.isRequired,
   onRewindClick: PropTypes.function,
+  type: PropTypes.string,
 };
 
 function MessageIcon(props) {
-  const { level, onRewindClick } = props;
+  const { level, onRewindClick, type } = props;
+
+  if (onRewindClick) {
+    return getIconElement(level, onRewindClick, type);
+  }
 
-  return onRewindClick
-    ? getIconElement(level, onRewindClick)
-    : CONSTANT_ICONS[level] || getIconElement(level);
+  if (type) {
+    return getIconElement(level, null, type);
+  }
+
+  return CONSTANT_ICONS[level] || getIconElement(level);
 }
 
 module.exports = MessageIcon;
--- a/devtools/client/webconsole/test/components/message-icon.test.js
+++ b/devtools/client/webconsole/test/components/message-icon.test.js
@@ -13,9 +13,17 @@ const MessageIcon = createFactory(requir
 
 describe("MessageIcon component:", () => {
   it("renders icon based on level", () => {
     const rendered = render(MessageIcon({ level: MESSAGE_LEVEL.ERROR }));
     expect(rendered.hasClass("icon")).toBe(true);
     expect(rendered.attr("title")).toBe("Error");
     expect(rendered.attr("aria-live")).toBe("off");
   });
+
+  it("renders logpoint items", () => {
+    const rendered = render(MessageIcon({
+      level: MESSAGE_LEVEL.LOG,
+      type: "logPoint",
+    }));
+    expect(rendered.hasClass("logpoint")).toBe(true);
+  });
 });
--- a/dom/base/DOMPrefsInternal.h
+++ b/dom/base/DOMPrefsInternal.h
@@ -22,13 +22,12 @@ DOM_WEBIDL_PREF(dom_storageManager_enabl
 DOM_WEBIDL_PREF(dom_testing_structuredclonetester_enabled)
 DOM_WEBIDL_PREF(dom_promise_rejection_events_enabled)
 DOM_WEBIDL_PREF(dom_push_enabled)
 DOM_WEBIDL_PREF(gfx_offscreencanvas_enabled)
 DOM_WEBIDL_PREF(dom_webkitBlink_dirPicker_enabled)
 DOM_WEBIDL_PREF(dom_netinfo_enabled)
 DOM_WEBIDL_PREF(dom_fetchObserver_enabled)
 DOM_WEBIDL_PREF(dom_enable_performance_observer)
-DOM_WEBIDL_PREF(dom_performance_enable_scheduler_timing)
 DOM_WEBIDL_PREF(dom_reporting_enabled)
 DOM_WEBIDL_PREF(dom_reporting_testing_enabled)
 DOM_WEBIDL_PREF(dom_reporting_featurePolicy_enabled)
 DOM_WEBIDL_PREF(javascript_options_streams)
--- a/dom/base/DocGroup.cpp
+++ b/dom/base/DocGroup.cpp
@@ -47,20 +47,18 @@ void DocGroup::RemoveDocument(Document* 
   MOZ_ASSERT(mDocuments.Contains(aDocument));
   mDocuments.RemoveElement(aDocument);
 }
 
 DocGroup::DocGroup(TabGroup* aTabGroup, const nsACString& aKey)
     : mKey(aKey), mTabGroup(aTabGroup) {
   // This method does not add itself to mTabGroup->mDocGroups as the caller does
   // it for us.
-  if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
-    mPerformanceCounter =
-        new mozilla::PerformanceCounter(NS_LITERAL_CSTRING("DocGroup:") + aKey);
-  }
+  mPerformanceCounter =
+      new mozilla::PerformanceCounter(NS_LITERAL_CSTRING("DocGroup:") + aKey);
 }
 
 DocGroup::~DocGroup() {
   MOZ_ASSERT(mDocuments.IsEmpty());
   if (!NS_IsMainThread()) {
     nsIEventTarget* target = EventTargetFor(TaskCategory::Other);
     NS_ProxyRelease("DocGroup::mReactionsStack", target,
                     mReactionsStack.forget());
--- a/dom/base/DocGroup.h
+++ b/dom/base/DocGroup.h
@@ -109,17 +109,15 @@ class DocGroup final {
   DocGroup(TabGroup* aTabGroup, const nsACString& aKey);
   ~DocGroup();
 
   nsCString mKey;
   RefPtr<TabGroup> mTabGroup;
   nsTArray<Document*> mDocuments;
   RefPtr<mozilla::dom::CustomElementReactionsStack> mReactionsStack;
   nsTArray<RefPtr<HTMLSlotElement>> mSignalSlotList;
-  // This pointer will be null if dom.performance.enable_scheduler_timing is
-  // false (default value)
   RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // defined(DocGroup_h)
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -159,16 +159,17 @@
 #include "nsIDOMXULMultSelectCntrlEl.h"
 #include "nsIDOMXULSelectCntrlEl.h"
 #include "nsIDOMXULSelectCntrlItemEl.h"
 #include "nsIBrowser.h"
 #include "nsIAutoCompletePopup.h"
 
 #include "nsISpeculativeConnect.h"
 #include "nsIIOService.h"
+#include "nsBlockFrame.h"
 
 #include "DOMMatrix.h"
 
 using mozilla::gfx::Matrix4x4;
 
 namespace mozilla {
 namespace dom {
 
@@ -3151,18 +3152,18 @@ nsresult Element::PostHandleEventForLink
         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
       }
     } break;
 
     case eKeyPress: {
       WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
       if (keyEvent && keyEvent->mKeyCode == NS_VK_RETURN) {
         nsEventStatus status = nsEventStatus_eIgnore;
-        rv = DispatchClickEvent(MOZ_KnownLive(aVisitor.mPresContext), keyEvent, this, false,
-                                nullptr, &status);
+        rv = DispatchClickEvent(MOZ_KnownLive(aVisitor.mPresContext), keyEvent,
+                                this, false, nullptr, &status);
         if (NS_SUCCEEDED(rv)) {
           aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
         }
       }
     } break;
 
     default:
       // switch not in sync with the optimization switch earlier in this
@@ -4395,10 +4396,22 @@ void Element::NoteDescendantsNeedFramesF
   // Since lazy frame construction can be required for non-element nodes, this
   // Note() method operates on the parent of the frame-requiring content, unlike
   // the other Note() methods above (which operate directly on the element that
   // needs processing).
   NoteDirtyElement(this, NODE_DESCENDANTS_NEED_FRAMES);
   SetFlags(NODE_DESCENDANTS_NEED_FRAMES);
 }
 
+double Element::FirstLineBoxBSize() const {
+  const nsBlockFrame* frame = do_QueryFrame(GetPrimaryFrame());
+  if (!frame) {
+    return 0.0;
+  }
+  nsBlockFrame::ConstLineIterator line = frame->LinesBegin();
+  nsBlockFrame::ConstLineIterator lineEnd = frame->LinesEnd();
+  return line != lineEnd
+             ? nsPresContext::AppUnitsToDoubleCSSPixels(line->BSize())
+             : 0.0;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1295,16 +1295,31 @@ class Element : public FragmentOrElement
   }
   MOZ_CAN_RUN_SCRIPT int32_t ScrollLeftMax() {
     nsIScrollableFrame* sf = GetScrollFrame();
     return sf ? nsPresContext::AppUnitsToIntCSSPixels(
                     sf->GetScrollRange().XMost())
               : 0;
   }
 
+  MOZ_CAN_RUN_SCRIPT double ClientHeightDouble() {
+    return nsPresContext::AppUnitsToDoubleCSSPixels(
+        GetClientAreaRect().Height());
+  }
+
+  MOZ_CAN_RUN_SCRIPT double ClientWidthDouble() {
+    return nsPresContext::AppUnitsToDoubleCSSPixels(
+        GetClientAreaRect().Width());
+  }
+
+  // This function will return the block size of first line box, no matter if
+  // the box is 'block' or 'inline'. The return unit is pixel. If the element
+  // can't get a primary frame, we will return be zero.
+  double FirstLineBoxBSize() const;
+
   already_AddRefed<Flex> GetAsFlexContainer();
   void GetGridFragments(nsTArray<RefPtr<Grid>>& aResult);
 
   already_AddRefed<DOMMatrixReadOnly> GetTransformToAncestor(
       Element& aAncestor);
   already_AddRefed<DOMMatrixReadOnly> GetTransformToParent();
   already_AddRefed<DOMMatrixReadOnly> GetTransformToViewport();
 
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -329,36 +329,28 @@ TimeDuration TimeoutManager::CalculateDe
     result = TimeDuration::Max(
         result, TimeDuration::FromMilliseconds(gMinClampTimeoutValue));
   }
 
   return result;
 }
 
 PerformanceCounter* TimeoutManager::GetPerformanceCounter() {
-  if (!StaticPrefs::dom_performance_enable_scheduler_timing()) {
-    return nullptr;
-  }
   Document* doc = mWindow.GetDocument();
   if (doc) {
     dom::DocGroup* docGroup = doc->GetDocGroup();
     if (docGroup) {
       return docGroup->GetPerformanceCounter();
     }
   }
   return nullptr;
 }
 
 void TimeoutManager::RecordExecution(Timeout* aRunningTimeout,
                                      Timeout* aTimeout) {
-  if (!StaticPrefs::dom_performance_enable_scheduler_timing() &&
-      mWindow.IsChromeWindow()) {
-    return;
-  }
-
   TimeoutBudgetManager& budgetManager = TimeoutBudgetManager::Get();
   TimeStamp now = TimeStamp::Now();
 
   if (aRunningTimeout) {
     // If we're running a timeout callback, record any execution until
     // now.
     TimeDuration duration = budgetManager.RecordExecution(now, aRunningTimeout);
 
--- a/dom/base/nsDOMDataChannel.cpp
+++ b/dom/base/nsDOMDataChannel.cpp
@@ -11,16 +11,17 @@
 
 #include "nsDOMDataChannelDeclarations.h"
 #include "nsDOMDataChannel.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Blob.h"
 
 #include "nsError.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsProxyRelease.h"
 
 #include "DataChannel.h"
@@ -165,17 +166,17 @@ void nsDOMDataChannel::SetBufferedAmount
 void nsDOMDataChannel::Close() {
   mDataChannel->Close();
   UpdateMustKeepAlive();
 }
 
 // All of the following is copy/pasted from WebSocket.cpp.
 void nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv) {
   NS_ConvertUTF16toUTF8 msgString(aData);
-  Send(nullptr, msgString, false, aRv);
+  Send(nullptr, &msgString, false, aRv);
 }
 
 void nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
   nsCOMPtr<nsIInputStream> msgStream;
   aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -187,50 +188,50 @@ void nsDOMDataChannel::Send(Blob& aData,
     return;
   }
 
   if (msgLength > UINT32_MAX) {
     aRv.Throw(NS_ERROR_FILE_TOO_BIG);
     return;
   }
 
-  Send(msgStream, EmptyCString(), true, aRv);
+  Send(&aData, nullptr, true, aRv);
 }
 
 void nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
   aData.ComputeLengthAndData();
 
   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
 
   uint32_t len = aData.Length();
   char* data = reinterpret_cast<char*>(aData.Data());
 
   nsDependentCSubstring msgString(data, len);
-  Send(nullptr, msgString, true, aRv);
+  Send(nullptr, &msgString, true, aRv);
 }
 
 void nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
   aData.ComputeLengthAndData();
 
   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
 
   uint32_t len = aData.Length();
   char* data = reinterpret_cast<char*>(aData.Data());
 
   nsDependentCSubstring msgString(data, len);
-  Send(nullptr, msgString, true, aRv);
+  Send(nullptr, &msgString, true, aRv);
 }
 
-void nsDOMDataChannel::Send(nsIInputStream* aMsgStream,
-                            const nsACString& aMsgString, bool aIsBinary,
-                            ErrorResult& aRv) {
+void nsDOMDataChannel::Send(mozilla::dom::Blob* aMsgBlob,
+                            const nsACString* aMsgString, bool aIsBinary,
+                            mozilla::ErrorResult& aRv) {
   MOZ_ASSERT(NS_IsMainThread());
   uint16_t state = mozilla::DataChannel::CLOSED;
   if (!mSentClose) {
     state = mDataChannel->GetReadyState();
   }
 
   // In reality, the DataChannel protocol allows this, but we want it to
   // look like WebSockets
@@ -242,23 +243,23 @@ void nsDOMDataChannel::Send(nsIInputStre
   if (state == mozilla::DataChannel::CLOSING ||
       state == mozilla::DataChannel::CLOSED) {
     return;
   }
 
   MOZ_ASSERT(state == mozilla::DataChannel::OPEN,
              "Unknown state in nsDOMDataChannel::Send");
 
-  if (aMsgStream) {
-    mDataChannel->SendBinaryStream(aMsgStream, aRv);
+  if (aMsgBlob) {
+    mDataChannel->SendBinaryBlob(*aMsgBlob, aRv);
   } else {
     if (aIsBinary) {
-      mDataChannel->SendBinaryMsg(aMsgString, aRv);
+      mDataChannel->SendBinaryMsg(*aMsgString, aRv);
     } else {
-      mDataChannel->SendMsg(aMsgString, aRv);
+      mDataChannel->SendMsg(*aMsgString, aRv);
     }
   }
 }
 
 nsresult nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,
                                                 bool aBinary) {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/base/nsDOMDataChannel.h
+++ b/dom/base/nsDOMDataChannel.h
@@ -7,17 +7,16 @@
 #ifndef nsDOMDataChannel_h
 #define nsDOMDataChannel_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/RTCDataChannelBinding.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/net/DataChannelListener.h"
-#include "nsIInputStream.h"
 
 namespace mozilla {
 namespace dom {
 class Blob;
 }
 
 class DataChannel;
 };  // namespace mozilla
@@ -105,17 +104,17 @@ class nsDOMDataChannel final : public mo
   // ATTENTION, when calling this method the object can be released
   // (and possibly collected).
   void DontKeepAliveAnyMore();
 
  protected:
   ~nsDOMDataChannel();
 
  private:
-  void Send(nsIInputStream* aMsgStream, const nsACString& aMsgString,
+  void Send(mozilla::dom::Blob* aMsgBlob, const nsACString* aMsgString,
             bool aIsBinary, mozilla::ErrorResult& aRv);
 
   void ReleaseSelf();
 
   // to keep us alive while we have listeners
   RefPtr<nsDOMDataChannel> mSelfRef;
   // Owning reference
   RefPtr<mozilla::DataChannel> mDataChannel;
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -354,17 +354,17 @@ partial namespace ChromeUtils {
    * that it belongs to.
    */
   [Throws]
   object createError(DOMString message, optional object? stack = null);
 
   /**
    * Request performance metrics to the current process & all content processes.
    */
-  [Throws, Func="DOMPrefs::dom_performance_enable_scheduler_timing"]
+  [Throws]
   Promise<sequence<PerformanceInfoDictionary>> requestPerformanceMetrics();
 
   /**
   * Returns a Promise containing a sequence of I/O activities
   */
   [Throws]
   Promise<sequence<IOActivityDataDictionary>> requestIOActivity();
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1870,20 +1870,16 @@ void HTMLMediaElement::AbortExistingLoad
   }
 
   // We may have changed mPaused, mAutoplaying, and other
   // things which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
   mIsRunningSelectResource = false;
 
-  if (mTextTrackManager) {
-    mTextTrackManager->NotifyReset();
-  }
-
   mEventDeliveryPaused = false;
   mPendingEvents.Clear();
   mCurrentLoadPlayTime.Reset();
 
   AssertReadyStateIsNothing();
 }
 
 void HTMLMediaElement::NoSupportedMediaSourceError(
@@ -5516,16 +5512,23 @@ void HTMLMediaElement::ChangeReadyState(
 
   nsMediaReadyState oldState = mReadyState;
   mReadyState = aState;
   LOG(LogLevel::Debug,
       ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
 
   DDLOG(DDLogCategory::Property, "ready_state", gReadyStateToString[aState]);
 
+  // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
+  // The user agent must synchronously unset cues' active flag whenever the
+  // media element's readyState is changed back to HAVE_NOTHING.
+  if (mReadyState == HAVE_NOTHING && mTextTrackManager) {
+    mTextTrackManager->NotifyReset();
+  }
+
   if (mNetworkState == NETWORK_EMPTY) {
     return;
   }
 
   UpdateAudioChannelPlayingState();
 
   // Handle raising of "waiting" event during seek (see 4.8.10.9)
   // or
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -615,47 +615,52 @@ void TextTrackManager::TimeMarchesOn() {
     // stable state. So we dispatch a task to perform such operation later
     // instead.
     DispatchTimeMarchesOn();
     return;
   }
   WEBVTT_LOG("TimeMarchesOn");
 
   // Early return if we don't have any TextTracks or shutting down.
-  if (!mTextTracks || mTextTracks->Length() == 0 || IsShutdown()) {
+  if (!mTextTracks || mTextTracks->Length() == 0 || IsShutdown() ||
+      !mMediaElement) {
     return;
   }
 
+  if (mMediaElement->ReadyState() == HTMLMediaElement_Binding::HAVE_NOTHING) {
+    WEBVTT_LOG(
+        "TimeMarchesOn return because media doesn't contain any data yet");
+    return;
+  }
+
+  if (mMediaElement->Seeking()) {
+    WEBVTT_LOG("TimeMarchesOn return during seeking");
+    return;
+  }
+
+  // Step 1, 2.
   nsISupports* parentObject = mMediaElement->OwnerDoc()->GetParentObject();
   if (NS_WARN_IF(!parentObject)) {
     return;
   }
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
-
-  if (mMediaElement &&
-      (!(mMediaElement->GetPlayedOrSeeked()) || mMediaElement->Seeking())) {
-    WEBVTT_LOG("TimeMarchesOn seeking or post return");
-    return;
-  }
+  RefPtr<TextTrackCueList> currentCues = new TextTrackCueList(window);
+  RefPtr<TextTrackCueList> otherCues = new TextTrackCueList(window);
 
   // Step 3.
   auto currentPlaybackTime =
       media::TimeUnit::FromSeconds(mMediaElement->CurrentTime());
   bool hasNormalPlayback = !mHasSeeked;
   mHasSeeked = false;
   WEBVTT_LOG(
       "TimeMarchesOn mLastTimeMarchesOnCalled %lf currentPlaybackTime %lf "
       "hasNormalPlayback %d",
       mLastTimeMarchesOnCalled.ToSeconds(), currentPlaybackTime.ToSeconds(),
       hasNormalPlayback);
 
-  // Step 1, 2.
-  RefPtr<TextTrackCueList> currentCues = new TextTrackCueList(window);
-  RefPtr<TextTrackCueList> otherCues = new TextTrackCueList(window);
-
   // The reason we collect other cues is (1) to change active cues to inactive,
   // (2) find missing cues, so we actually no need to process all cues. We just
   // need to handle cues which are in the time interval [lastTime:currentTime]
   // or [currentTime:lastTime] (seeking forward). That can help us to reduce the
   // size of other cues, which can improve execution time.
   auto start = std::min(mLastTimeMarchesOnCalled, currentPlaybackTime);
   auto end = std::max(mLastTimeMarchesOnCalled, currentPlaybackTime);
   media::TimeInterval interval(start, end);
@@ -819,18 +824,24 @@ void TextTrackManager::NotifyCueUpdated(
   TimeMarchesOn();
   // For the case "Texttrack.mode = hidden/showing", if the mode
   // changing between showing and hidden, TimeMarchesOn
   // doesn't render the cue. Call DispatchUpdateCueDisplay() explicitly.
   DispatchUpdateCueDisplay();
 }
 
 void TextTrackManager::NotifyReset() {
+  // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
+  // This will unset all cues' active flag and update the cue display.
   WEBVTT_LOG("NotifyReset");
   mLastTimeMarchesOnCalled = media::TimeUnit::Zero();
+  for (uint32_t idx = 0; idx < mTextTracks->Length(); ++idx) {
+    (*mTextTracks)[idx]->SetCuesInactive();
+  }
+  UpdateCueDisplay();
 }
 
 void TextTrackManager::ReportTelemetryForTrack(TextTrack* aTextTrack) const {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aTextTrack);
   MOZ_ASSERT(mTextTracks->Length() > 0);
 
   TextTrackKind kind = aTextTrack->Kind();
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1425,17 +1425,16 @@ mozilla::ipc::IPCResult ContentChild::Ge
   // If we are talking to the GPU process, then we should recover from this on
   // the next ContentChild::RecvReinitRendering call.
   gfxCriticalNote << "Could not initialize rendering with GPU process";
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvRequestPerformanceMetrics(
     const nsID& aID) {
-  MOZ_ASSERT(mozilla::StaticPrefs::dom_performance_enable_scheduler_timing());
   RefPtr<ContentChild> self = this;
   RefPtr<AbstractThread> mainThread =
       SystemGroup::AbstractMainThreadFor(TaskCategory::Performance);
   nsTArray<RefPtr<PerformanceInfoPromise>> promises = CollectPerformanceInfo();
 
   PerformanceInfoPromise::All(mainThread, promises)
       ->Then(
           mainThread, __func__,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3490,21 +3490,16 @@ mozilla::ipc::IPCResult ContentParent::R
     mMemoryReportRequest->Finish(aGeneration);
     mMemoryReportRequest = nullptr;
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvAddPerformanceMetrics(
     const nsID& aID, nsTArray<PerformanceInfo>&& aMetrics) {
-  if (!mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
-    // The pref is off, we should not get a performance metrics from the content
-    // child
-    return IPC_OK();
-  }
   nsresult rv = PerformanceMetricsCollector::DataReceived(aID, aMetrics);
   Unused << NS_WARN_IF(NS_FAILED(rv));
   return IPC_OK();
 }
 
 PCycleCollectWithLogsParent* ContentParent::AllocPCycleCollectWithLogsParent(
     const bool& aDumpAllTraces, const FileDescriptor& aGCLog,
     const FileDescriptor& aCCLog) {
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -1649,16 +1649,24 @@ class RTCPeerConnection {
       const byteCounter = new TextEncoder("utf-8");
 
       if (byteCounter.encode(protocol).length > 65535) {
         throw new this._win.DOMException(
             "protocol cannot be longer than 65535 bytes", "TypeError");
       }
     }
 
+    if (label.length > 32767) {
+      const byteCounter = new TextEncoder("utf-8");
+      if (byteCounter.encode(label).length > 65535) {
+        throw new this._win.DOMException(
+            "label cannot be longer than 65535 bytes", "TypeError");
+      }
+    }
+
     if (!negotiated) {
       id = null;
     } else if (id === null) {
       throw new this._win.DOMException(
           "id is required when negotiated is true", "TypeError");
     }
     if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
       throw new this._win.DOMException(
--- a/dom/media/TextTrack.cpp
+++ b/dom/media/TextTrack.cpp
@@ -217,17 +217,20 @@ void TextTrack::SetTextTrackList(TextTra
 }
 
 HTMLTrackElement* TextTrack::GetTrackElement() { return mTrackElement; }
 
 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) {
   mTrackElement = aTrackElement;
 }
 
-void TextTrack::SetCuesInactive() { mCueList->SetCuesInactive(); }
+void TextTrack::SetCuesInactive() {
+  WEBVTT_LOG("SetCuesInactive");
+  mCueList->SetCuesInactive();
+}
 
 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) {
   WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
   mCueList->NotifyCueUpdated(aCue);
   HTMLMediaElement* mediaElement = GetMediaElement();
   if (mediaElement) {
     mediaElement->NotifyCueUpdated(aCue);
   }
--- a/dom/media/ipc/GpuDecoderModule.cpp
+++ b/dom/media/ipc/GpuDecoderModule.cpp
@@ -46,20 +46,16 @@ static inline bool IsRemoteAcceleratedCo
 already_AddRefed<MediaDataDecoder> GpuDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
   if (!StaticPrefs::MediaGpuProcessDecoder() || !aParams.mKnowsCompositor ||
       !IsRemoteAcceleratedCompositor(aParams.mKnowsCompositor)) {
     return mWrapped->CreateVideoDecoder(aParams);
   }
 
   RefPtr<VideoDecoderChild> child = new VideoDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, VideoDecoderManagerChild::GetManagerThread(),
-      VideoDecoderManagerChild::GetManagerAbstractThread());
-
   SynchronousTask task("InitIPDL");
   MediaResult result(NS_OK);
   VideoDecoderManagerChild::GetManagerThread()->Dispatch(
       NS_NewRunnableFunction(
           "dom::GpuDecoderModule::CreateVideoDecoder",
           [&, child]() {
             AutoCompleteTask complete(&task);
             result = child->InitIPDL(
@@ -71,12 +67,16 @@ already_AddRefed<MediaDataDecoder> GpuDe
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, VideoDecoderManagerChild::GetManagerThread(),
+      VideoDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 }  // namespace mozilla
--- a/dom/media/ipc/IRemoteDecoderChild.h
+++ b/dom/media/ipc/IRemoteDecoderChild.h
@@ -2,33 +2,34 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef include_dom_media_ipc_IRemoteDecoderChild_h
 #define include_dom_media_ipc_IRemoteDecoderChild_h
 
 #include "PlatformDecoderModule.h"
+#include "mozilla/TaskQueue.h"
 
 namespace mozilla {
 
 // This interface mirrors the MediaDataDecoder plus a bit (DestroyIPDL)
 // to allow proxying to a remote decoder in RemoteDecoderModule or
 // GpuDecoderModule. RemoteAudioDecoderChild, RemoteVideoDecoderChild,
 // and VideoDecoderChild (for GPU) implement this interface.
 class IRemoteDecoderChild {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IRemoteDecoderChild);
 
   virtual RefPtr<MediaDataDecoder::InitPromise> Init() = 0;
   virtual RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) = 0;
   virtual RefPtr<MediaDataDecoder::DecodePromise> Drain() = 0;
   virtual RefPtr<MediaDataDecoder::FlushPromise> Flush() = 0;
-  virtual void Shutdown() = 0;
+  virtual RefPtr<ShutdownPromise> Shutdown() = 0;
   virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const {
     return false;
   }
   virtual nsCString GetDescriptionName() const = 0;
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
   virtual MediaDataDecoder::ConversionRequired NeedsConversion() const {
     return MediaDataDecoder::ConversionRequired::kNeedNone;
   }
--- a/dom/media/ipc/PRemoteDecoder.ipdl
+++ b/dom/media/ipc/PRemoteDecoder.ipdl
@@ -60,16 +60,17 @@ parent:
 
 child:
   async InitComplete(TrackType trackType,
                      nsCString decoderDescription,
                      ConversionRequired conversion);
   async InitFailed(nsresult reason);
 
   async FlushComplete();
+  async ShutdownComplete();
 
   async Output(DecodedOutputIPDL data);
   async InputExhausted();
   async DrainComplete();
   async Error(nsresult error);
 };
 
 } // namespace mozilla
--- a/dom/media/ipc/PVideoDecoder.ipdl
+++ b/dom/media/ipc/PVideoDecoder.ipdl
@@ -40,16 +40,17 @@ parent:
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription, bool hardware, nsCString hardwareReason, uint32_t conversion);
   async InitFailed(nsresult reason);
 
   async FlushComplete();
+  async ShutdownComplete();
 
   // Each output includes a SurfaceDescriptorGPUVideo that represents the decoded
   // frame. This SurfaceDescriptor can be used on the Layers IPDL protocol, but
   // must be released explicitly using DeallocateSurfaceDescriptorGPUVideo
   // on the manager protocol.
   async Output(VideoDataIPDL data);
   async InputExhausted();
   async DrainComplete();
--- a/dom/media/ipc/RemoteDecoderChild.cpp
+++ b/dom/media/ipc/RemoteDecoderChild.cpp
@@ -32,16 +32,18 @@ mozilla::ipc::IPCResult RemoteDecoderChi
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvError(const nsresult& aError) {
   AssertOnManagerThread();
   mDecodedData = MediaDataDecoder::DecodedData();
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
   mFlushPromise.RejectIfExists(aError, __func__);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvInitComplete(
     const TrackInfo::TrackType& trackType, const nsCString& aDecoderDescription,
     const ConversionRequired& aConversion) {
   AssertOnManagerThread();
   mInitPromise.ResolveIfExists(trackType, __func__);
@@ -59,21 +61,40 @@ mozilla::ipc::IPCResult RemoteDecoderChi
 }
 
 mozilla::ipc::IPCResult RemoteDecoderChild::RecvFlushComplete() {
   AssertOnManagerThread();
   mFlushPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult RemoteDecoderChild::RecvShutdownComplete() {
+  AssertOnManagerThread();
+  MOZ_ASSERT(mShutdownSelfRef);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
 void RemoteDecoderChild::ActorDestroy(ActorDestroyReason aWhy) {
+  MOZ_ASSERT(mCanSend);
+  // If the IPC channel is gone pending promises need to be resolved/rejected.
+  if (aWhy == AbnormalShutdown) {
+    MediaResult error(NS_ERROR_DOM_MEDIA_DECODE_ERR);
+    mDecodePromise.RejectIfExists(error, __func__);
+    mDrainPromise.RejectIfExists(error, __func__);
+    mFlushPromise.RejectIfExists(error, __func__);
+    mShutdownSelfRef = nullptr;
+    mShutdownPromise.ResolveIfExists(true, __func__);
+  }
   mCanSend = false;
 }
 
 void RemoteDecoderChild::DestroyIPDL() {
+  AssertOnManagerThread();
   if (mCanSend) {
     PRemoteDecoderChild::Send__delete__(this);
   }
 }
 
 void RemoteDecoderChild::IPDLActorDestroyed() { mIPDLSelfRef = nullptr; }
 
 // MediaDataDecoder methods
@@ -138,23 +159,27 @@ RefPtr<MediaDataDecoder::DecodePromise> 
   if (!mCanSend) {
     return MediaDataDecoder::DecodePromise::CreateAndReject(
         NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
   }
   SendDrain();
   return mDrainPromise.Ensure(__func__);
 }
 
-void RemoteDecoderChild::Shutdown() {
+RefPtr<ShutdownPromise> RemoteDecoderChild::Shutdown() {
   AssertOnManagerThread();
   mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  if (mCanSend) {
-    SendShutdown();
+  mInitialized = false;
+  if (!mCanSend) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
   }
-  mInitialized = false;
+  SendShutdown();
+  MOZ_ASSERT(!mShutdownSelfRef);
+  mShutdownSelfRef = this;
+  return mShutdownPromise.Ensure(__func__);
 }
 
 bool RemoteDecoderChild::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   AssertOnManagerThread();
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
--- a/dom/media/ipc/RemoteDecoderChild.h
+++ b/dom/media/ipc/RemoteDecoderChild.h
@@ -26,26 +26,27 @@ class RemoteDecoderChild : public PRemot
   IPCResult RecvInputExhausted();
   IPCResult RecvDrainComplete();
   IPCResult RecvError(const nsresult& aError);
   IPCResult RecvInitComplete(const TrackInfo::TrackType& trackType,
                              const nsCString& aDecoderDescription,
                              const ConversionRequired& aConversion);
   IPCResult RecvInitFailed(const nsresult& aReason);
   IPCResult RecvFlushComplete();
+  IPCResult RecvShutdownComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   // IRemoteDecoderChild
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
-  void Shutdown() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
   nsCString GetDescriptionName() const override;
   void SetSeekThreshold(const media::TimeUnit& aTime) override;
   MediaDataDecoder::ConversionRequired NeedsConversion() const override;
   void DestroyIPDL() override;
 
   // Called from IPDL when our actor has been destroyed
   void IPDLActorDestroyed();
@@ -62,20 +63,24 @@ class RemoteDecoderChild : public PRemot
 
  private:
   RefPtr<nsIThread> mThread;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+  MozPromiseHolder<ShutdownPromise> mShutdownPromise;
 
   nsCString mHardwareAcceleratedReason;
   nsCString mDescription;
   bool mInitialized = false;
   bool mIsHardwareAccelerated = false;
   MediaDataDecoder::ConversionRequired mConversion =
       MediaDataDecoder::ConversionRequired::kNeedNone;
+  // Keep this instance alive during SendShutdown RecvShutdownComplete
+  // handshake.
+  RefPtr<RemoteDecoderChild> mShutdownSelfRef;
 };
 
 }  // namespace mozilla
 
 #endif  // include_dom_media_ipc_RemoteDecoderChild_h
--- a/dom/media/ipc/RemoteDecoderModule.cpp
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -88,61 +88,61 @@ already_AddRefed<MediaDataDecoder> Remot
     const CreateDecoderParams& aParams) {
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteAudioDecoderChild> child = new RemoteAudioDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, mManagerThread,
-      RemoteDecoderManagerChild::GetManagerAbstractThread());
-
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateAudioDecoder", [&, child]() {
         result = child->InitIPDL(aParams.AudioConfig(), aParams.mOptions);
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, mManagerThread,
+      RemoteDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 already_AddRefed<MediaDataDecoder> RemoteDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteVideoDecoderChild> child = new RemoteVideoDecoderChild();
-  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, mManagerThread,
-      RemoteDecoderManagerChild::GetManagerAbstractThread());
-
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateVideoDecoder", [&, child]() {
         result = child->InitIPDL(aParams.VideoConfig(), aParams.mRate.mValue,
                                  aParams.mOptions);
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
+  RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
+      child, mManagerThread,
+      RemoteDecoderManagerChild::GetManagerAbstractThread());
+
   return object.forget();
 }
 
 }  // namespace mozilla
--- a/dom/media/ipc/RemoteDecoderParent.cpp
+++ b/dom/media/ipc/RemoteDecoderParent.cpp
@@ -134,17 +134,25 @@ mozilla::ipc::IPCResult RemoteDecoderPar
       [self](const MediaResult& aError) { self->Error(aError); });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderParent::RecvShutdown() {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
-    mDecoder->Shutdown();
+    RefPtr<RemoteDecoderParent> self = this;
+    mDecoder->Shutdown()->Then(
+        mManagerTaskQueue, __func__,
+        [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+          MOZ_ASSERT(aValue.IsResolve());
+          if (!self->mDestroyed) {
+            Unused << self->SendShutdownComplete();
+          }
+        });
   }
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult RemoteDecoderParent::RecvSetSeekThreshold(
     const TimeUnit& aTime) {
   MOZ_ASSERT(!mDestroyed);
--- a/dom/media/ipc/RemoteMediaDataDecoder.cpp
+++ b/dom/media/ipc/RemoteMediaDataDecoder.cpp
@@ -16,33 +16,18 @@ using base::Thread;
 RemoteMediaDataDecoder::RemoteMediaDataDecoder(
     IRemoteDecoderChild* aChild, nsIThread* aManagerThread,
     AbstractThread* aAbstractManagerThread)
     : mChild(aChild),
       mManagerThread(aManagerThread),
       mAbstractManagerThread(aAbstractManagerThread) {}
 
 RemoteMediaDataDecoder::~RemoteMediaDataDecoder() {
-  // We're about to be destroyed and drop our ref to
-  // *DecoderChild. Make sure we put a ref into the
-  // task queue for the *DecoderChild thread to keep
-  // it alive until we send the delete message.
-  RefPtr<IRemoteDecoderChild> child = mChild.forget();
-
-  RefPtr<Runnable> task = NS_NewRunnableFunction(
-      "dom::RemoteMediaDataDecoder::~RemoteMediaDataDecoder", [child]() {
-        MOZ_ASSERT(child);
-        child->DestroyIPDL();
-      });
-
-  // Drop our references to the child so that the last ref
-  // always gets released on the manager thread.
-  child = nullptr;
-
-  mManagerThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+  /* Shutdown method should have been called. */
+  MOZ_ASSERT(!mChild);
 }
 
 RefPtr<MediaDataDecoder::InitPromise> RemoteMediaDataDecoder::Init() {
   RefPtr<RemoteMediaDataDecoder> self = this;
   return InvokeAsync(mAbstractManagerThread, __func__,
                      [self]() { return self->mChild->Init(); })
       ->Then(
           mAbstractManagerThread, __func__,
@@ -76,20 +61,25 @@ RefPtr<MediaDataDecoder::FlushPromise> R
 RefPtr<MediaDataDecoder::DecodePromise> RemoteMediaDataDecoder::Drain() {
   RefPtr<RemoteMediaDataDecoder> self = this;
   return InvokeAsync(mAbstractManagerThread, __func__,
                      [self]() { return self->mChild->Drain(); });
 }
 
 RefPtr<ShutdownPromise> RemoteMediaDataDecoder::Shutdown() {
   RefPtr<RemoteMediaDataDecoder> self = this;
-  return InvokeAsync(mAbstractManagerThread, __func__, [self]() {
-    self->mChild->Shutdown();
-    return ShutdownPromise::CreateAndResolve(true, __func__);
-  });
+  return InvokeAsync(mAbstractManagerThread, __func__,
+                     [self]() { return self->mChild->Shutdown(); })
+      ->Then(mAbstractManagerThread, __func__,
+             [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+               self->mChild->DestroyIPDL();
+               self->mChild = nullptr;
+               return ShutdownPromise::CreateAndResolveOrReject(aValue,
+                                                                __func__);
+             });
 }
 
 bool RemoteMediaDataDecoder::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
 
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -81,16 +81,18 @@ mozilla::ipc::IPCResult VideoDecoderChil
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvError(const nsresult& aError) {
   AssertOnManagerThread();
   mDecodedData = MediaDataDecoder::DecodedData();
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
   mFlushPromise.RejectIfExists(aError, __func__);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvInitComplete(
     const nsCString& aDecoderDescription, const bool& aHardware,
     const nsCString& aHardwareReason, const uint32_t& aConversion) {
   AssertOnManagerThread();
   mInitPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
@@ -110,16 +112,24 @@ mozilla::ipc::IPCResult VideoDecoderChil
 }
 
 mozilla::ipc::IPCResult VideoDecoderChild::RecvFlushComplete() {
   AssertOnManagerThread();
   mFlushPromise.ResolveIfExists(true, __func__);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult VideoDecoderChild::RecvShutdownComplete() {
+  AssertOnManagerThread();
+  MOZ_ASSERT(mShutdownSelfRef);
+  mShutdownSelfRef = nullptr;
+  mShutdownPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
 void VideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy) {
   if (aWhy == AbnormalShutdown) {
     // GPU process crashed, record the time and send back to MFR for telemetry.
     mGPUCrashTime = TimeStamp::Now();
 
     // Defer reporting an error until we've recreated the manager so that
     // it'll be safe for MediaFormatReader to recreate decoders
     RefPtr<VideoDecoderChild> ref = this;
@@ -127,16 +137,18 @@ void VideoDecoderChild::ActorDestroy(Act
         NS_NewRunnableFunction("VideoDecoderChild::ActorDestroy", [=]() {
           MediaResult error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
           error.SetGPUCrashTimeStamp(ref->mGPUCrashTime);
           if (ref->mInitialized) {
             mDecodedData = MediaDataDecoder::DecodedData();
             mDecodePromise.RejectIfExists(error, __func__);
             mDrainPromise.RejectIfExists(error, __func__);
             mFlushPromise.RejectIfExists(error, __func__);
+            mShutdownSelfRef = nullptr;
+            mShutdownPromise.ResolveIfExists(true, __func__);
             // Make sure the next request will be rejected accordingly if ever
             // called.
             mNeedNewDecoder = true;
           } else {
             ref->mInitPromise.RejectIfExists(error, __func__);
           }
         }));
   }
@@ -184,16 +196,17 @@ MediaResult VideoDecoderChild::InitIPDL(
     mCanSend = true;
   }
 
   return success ? MediaResult(NS_OK)
                  : MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, errorDescription);
 }
 
 void VideoDecoderChild::DestroyIPDL() {
+  AssertOnManagerThread();
   if (mCanSend) {
     PVideoDecoderChild::Send__delete__(this);
   }
 }
 
 void VideoDecoderChild::IPDLActorDestroyed() { mIPDLSelfRef = nullptr; }
 
 // MediaDataDecoder methods
@@ -272,23 +285,32 @@ RefPtr<MediaDataDecoder::DecodePromise> 
     return MediaDataDecoder::DecodePromise::CreateAndReject(error, __func__);
   }
   if (mCanSend) {
     SendDrain();
   }
   return mDrainPromise.Ensure(__func__);
 }
 
-void VideoDecoderChild::Shutdown() {
+RefPtr<ShutdownPromise> VideoDecoderChild::Shutdown() {
   AssertOnManagerThread();
   mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  if (mCanSend) {
-    SendShutdown();
+  mInitialized = false;
+  if (mNeedNewDecoder) {
+    MediaResult error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
+    error.SetGPUCrashTimeStamp(mGPUCrashTime);
+    return ShutdownPromise::CreateAndResolve(true, __func__);
   }
-  mInitialized = false;
+  if (!mCanSend) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
+  SendShutdown();
+  MOZ_ASSERT(!mShutdownSelfRef);
+  mShutdownSelfRef = this;
+  return mShutdownPromise.Ensure(__func__);
 }
 
 bool VideoDecoderChild::IsHardwareAccelerated(
     nsACString& aFailureReason) const {
   AssertOnManagerThread();
   aFailureReason = mHardwareAcceleratedReason;
   return mIsHardwareAccelerated;
 }
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -31,25 +31,26 @@ class VideoDecoderChild final : public P
   IPCResult RecvDrainComplete();
   IPCResult RecvError(const nsresult& aError);
   IPCResult RecvInitComplete(const nsCString& aDecoderDescription,
                              const bool& aHardware,
                              const nsCString& aHardwareReason,
                              const uint32_t& aConversion);
   IPCResult RecvInitFailed(const nsresult& aReason);
   IPCResult RecvFlushComplete();
+  IPCResult RecvShutdownComplete();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   RefPtr<MediaDataDecoder::InitPromise> Init() override;
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override;
   RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
-  void Shutdown() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
   nsCString GetDescriptionName() const override;
   void SetSeekThreshold(const media::TimeUnit& aTime) override;
   MediaDataDecoder::ConversionRequired NeedsConversion() const override;
   void DestroyIPDL() override;
 
   MOZ_IS_CLASS_INIT
   MediaResult InitIPDL(const VideoInfo& aVideoInfo, float aFramerate,
@@ -68,29 +69,33 @@ class VideoDecoderChild final : public P
 
   RefPtr<VideoDecoderChild> mIPDLSelfRef;
   RefPtr<nsIThread> mThread;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+  MozPromiseHolder<ShutdownPromise> mShutdownPromise;
 
   nsCString mHardwareAcceleratedReason;
   nsCString mDescription;
   bool mCanSend;
   bool mInitialized;
   bool mIsHardwareAccelerated;
   MediaDataDecoder::ConversionRequired mConversion;
 
   // Set to true if the actor got destroyed and we haven't yet notified the
   // caller.
   bool mNeedNewDecoder;
   MediaDataDecoder::DecodedData mDecodedData;
 
   nsCString mBlacklistedD3D11Driver;
   nsCString mBlacklistedD3D9Driver;
   TimeStamp mGPUCrashTime;
+  // Keep this instance alive during SendShutdown RecvShutdownComplete
+  // handshake.
+  RefPtr<VideoDecoderChild> mShutdownSelfRef;
 };
 
 }  // namespace mozilla
 
 #endif  // include_ipc_VideoDecoderChild_h
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -96,17 +96,16 @@ VideoDecoderParent::VideoDecoderParent(
 }
 
 VideoDecoderParent::~VideoDecoderParent() {
   MOZ_COUNT_DTOR(VideoDecoderParent);
 }
 
 void VideoDecoderParent::Destroy() {
   MOZ_ASSERT(OnManagerThread());
-  mDecodeTaskQueue->AwaitShutdownAndIdle();
   mDestroyed = true;
   mIPDLSelfRef = nullptr;
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvInit() {
   MOZ_ASSERT(OnManagerThread());
   RefPtr<VideoDecoderParent> self = this;
   mDecoder->Init()->Then(
@@ -239,17 +238,25 @@ mozilla::ipc::IPCResult VideoDecoderPare
       [self](const MediaResult& aError) { self->Error(aError); });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvShutdown() {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
-    mDecoder->Shutdown();
+    RefPtr<VideoDecoderParent> self = this;
+    mDecoder->Shutdown()->Then(
+        mManagerTaskQueue, __func__,
+        [self](const ShutdownPromise::ResolveOrRejectValue& aValue) {
+          MOZ_ASSERT(aValue.IsResolve());
+          if (!self->mDestroyed) {
+            Unused << self->SendShutdownComplete();
+          }
+        });
   }
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvSetSeekThreshold(
     const TimeUnit& aTime) {
   MOZ_ASSERT(!mDestroyed);
@@ -260,19 +267,16 @@ mozilla::ipc::IPCResult VideoDecoderPare
 
 void VideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nullptr;
   }
-  if (mDecodeTaskQueue) {
-    mDecodeTaskQueue->BeginShutdown();
-  }
 }
 
 void VideoDecoderParent::Error(const MediaResult& aError) {
   MOZ_ASSERT(OnManagerThread());
   if (!mDestroyed) {
     Unused << SendError(aError);
   }
 }
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -85,17 +85,17 @@ void WMFDecoderModule::Init() {
     testForVPx = sDXVAEnabled = true;
   } else {
     // Only allow DXVA in the UI process if we aren't in e10s Firefox
     testForVPx = sDXVAEnabled = !mozilla::BrowserTabsRemoteAutostart();
   }
 
   sDXVAEnabled = sDXVAEnabled && gfx::gfxVars::CanUseHardwareVideoDecoding();
   testForVPx = testForVPx && gfx::gfxVars::CanUseHardwareVideoDecoding();
-  if (testForVPx && StaticPrefs::MediaWmfVp9Enabled()) {
+  if (testForVPx && gfxPrefs::MediaWmfVp9Enabled()) {
     gfx::WMFVPXVideoCrashGuard guard;
     if (!guard.Crashed()) {
       sUsableVPXMFT = CanCreateMFTDecoder(CLSID_WebmMfVpxDec);
     }
   }
 }
 
 /* static */
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1261,16 +1261,19 @@ skip-if = android_version == '17' # andr
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_vp9_superframes.html]
 [test_vttparser.html]
 skip-if = android_version == '22' # android(bug 1368010)
 tags = webvtt
 [test_webvtt_empty_displaystate.html]
 skip-if = android_version == '17' || android_version == '22' # android(bug 1368010, bug 1372457)
 tags = webvtt
+[test_webvtt_update_display_after_adding_or_removing_cue.html]
+skip-if = android_version == '22' # android(bug 1368010)
+tags = webvtt
 [test_webvtt_positionalign.html]
 skip-if = android_version == '22' # android(bug 1368010)
 tags = webvtt
 [test_webvtt_seeking.html]
 skip-if = android_version == '22' # android(bug 1368010)
 tags = webvtt
 # The tests below contain backend-specific tests. Write backend independent
 # tests rather than adding to this list.
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_webvtt_update_display_after_adding_or_removing_cue.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebVTT : cue display should be updated immediately after adding or removing cue</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="manifest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+/**
+ * This test is used to ensure that we will update cue display immediately after
+ * adding or removing cue, even if the video hasn't started yet. In this test,
+ * we start with adding a cue [0:5] to video, which should be showed in the
+ * beginning, and then remove the cue later.
+ */
+async function startTest() {
+  const video = await createVideo();
+
+  info(`cue should be showed immediately after it was added.`);
+  const cue = createCueAndAddCueToVideo(video);
+  await waitUntilCueShows(cue);
+
+  info(`cue should be hid immediately after it was removed.`);
+  removeCueFromVideo(cue, video);
+  checkIfCueHides(cue);
+
+  removeNodeAndSource(video);
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+onload = startTest;
+
+/**
+ * The following are test helper functions.
+ */
+async function createVideo() {
+  let video = document.createElement("video");
+  video.src = "gizmo.mp4";
+  video.controls = true;
+  document.body.appendChild(video);
+  // wait until media has loaded any data, because we won't update cue if it has
+  // not got any data.
+  await once(video, "loadedmetadata");
+  return video;
+}
+
+function createCueAndAddCueToVideo(video) {
+  let track = video.addTextTrack("subtitles");
+  track.mode = "showing";
+  let cue = new VTTCue(0, 5, "Test");
+  track.addCue(cue);
+  return cue;
+}
+
+function removeCueFromVideo(cue, video) {
+  let track = video.textTracks[0];
+  track.removeCue(cue);
+}
+
+async function waitUntilCueShows(cue) {
+  // cue has not been showed yet.
+  cue = SpecialPowers.wrap(cue);
+  if (!cue.getActive) {
+    await once(cue, "enter");
+  }
+  ok(cue.getActive, `cue has been showed,`);
+}
+
+function checkIfCueHides(cue) {
+  ok(!SpecialPowers.wrap(cue).getActive, `cue has been hidden.`);
+}
+
+</script>
+</body>
+</html>
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -232,41 +232,53 @@ PeerConnectionTest.prototype.closeDataCh
  *        Data to send to the other peer. For Blobs the MIME type will be lost.
  * @param {Object} [options={ }]
  *        Options to specify the data channels to be used
  * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
  *        Data channel to use for sending the message
  * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
  *        Data channel to use for receiving the message
  */
-PeerConnectionTest.prototype.send = function(data, options) {
+PeerConnectionTest.prototype.send = async function(data, options) {
   options = options || { };
-  var source = options.sourceChannel ||
+  const source = options.sourceChannel ||
            this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
-  var target = options.targetChannel ||
+  const target = options.targetChannel ||
            this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
-  var bufferedamount = options.bufferedAmountLowThreshold || 0;
-  var bufferlow_fired = true; // to make testing later easier
-  if (bufferedamount != 0) {
-    source.bufferedAmountLowThreshold = bufferedamount;
-    bufferlow_fired = false;
-    source.onbufferedamountlow = function() {
-      bufferlow_fired = true;
-    };
-  }
+  source.bufferedAmountLowThreshold = options.bufferedAmountLowThreshold || 0;
+
+  const getSizeInBytes = d => {
+    if (d instanceof Blob) {
+      return d.size;
+    } else if (d instanceof ArrayBuffer) {
+      return d.byteLength;
+    } else if (d instanceof String || typeof d === "string") {
+      return (new TextEncoder('utf-8')).encode(d).length;
+    } else {
+      ok(false);
+    }
+  };
+
+  const expectedSizeInBytes = getSizeInBytes(data);
+  const bufferedAmount = source.bufferedAmount;
+
+  source.send(data);
+  is(source.bufferedAmount, expectedSizeInBytes + bufferedAmount,
+    `Buffered amount should be ${expectedSizeInBytes}`);
+
+  await new Promise(resolve => source.onbufferedamountlow = resolve);
 
   return new Promise(resolve => {
     // Register event handler for the target channel
       target.onmessage = e => {
-        ok(bufferlow_fired, "bufferedamountlow event fired");
+        is(getSizeInBytes(e.data), expectedSizeInBytes,
+          `Expected to receive the same number of bytes as we sent (${expectedSizeInBytes})`);
 	resolve({ channel: target, data: e.data });
     };
-
-    source.send(data);
-  });
+  });;
 };
 
 /**
  * Create a data channel
  *
  * @param {Dict} options
  *        Options for the data channel (see nsIPeerConnection)
  */
@@ -712,16 +724,20 @@ DataChannelWrapper.prototype = {
    * Returns the readyState bit of the data channel
    *
    * @returns {String} The state of the channel
    */
   get readyState() {
     return this._channel.readyState;
   },
 
+  get bufferedAmount() {
+    return this._channel.bufferedAmount;
+  },
+
   /**
    * Sets the bufferlowthreshold of the channel
    *
    * @param {integer} amoutn
    *        The new threshold for the chanel
    */
   set bufferedAmountLowThreshold(amount) {
     this._channel.bufferedAmountLowThreshold = amount;
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -543,23 +543,30 @@ XPCOMUtils.defineLazyPreferenceGetter(th
       // number according to the container's size.
       const isWritingDirectionHorizontal = this.cue.vertical == "";
       let top =
             this.containerHeight * this._tranferPercentageToFloat(this.div.style.top),
           left =
             this.containerWidth * this._tranferPercentageToFloat(this.div.style.left),
           width = isWritingDirectionHorizontal ?
             this.containerWidth * this._tranferPercentageToFloat(this.div.style.width) :
-            this.div.offsetWidth,
+            this.div.clientWidthDouble,
           height = isWritingDirectionHorizontal ?
-            this.div.offsetHeight :
+            this.div.clientHeightDouble :
             this.containerHeight * this._tranferPercentageToFloat(this.div.style.height);
       return { top, left, width, height };
     }
 
+    getFirstLineBoxSize() {
+      // This size would be automatically adjusted by writing direction. When
+      // direction is horizontal, it represents box's height. When direction is
+      // vertical, it represents box's width.
+      return this.div.firstLineBoxBSize;
+    }
+
     /**
      * Following methods are private functions, should not use them outside this
      * class.
      */
     _tranferPercentageToFloat(input) {
       return input.replace("%", "") / 100.0;
     }
 
@@ -843,30 +850,29 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     // Check if this box is within another box.
     within(container) {
       return this.top >= container.top &&
              this.bottom <= container.bottom &&
              this.left >= container.left &&
              this.right <= container.right;
     }
 
-    // Check if this box is entirely within the container or it is overlapping
-    // on the edge opposite of the axis direction passed. For example, if "+x" is
-    // passed and the box is overlapping on the left edge of the container, then
-    // return true.
-    overlapsOppositeAxis(container, axis) {
+    // Check whether this box is passed over the specfic axis boundary. The axis
+    // is based on the canvas coordinates, the `+x` is rightward and `+y` is
+    // downward.
+    isOutsideTheAxisBoundary(container, axis) {
       switch (axis) {
       case "+x":
-        return this.left < container.left;
+        return this.right > container.right;
       case "-x":
-        return this.right > container.right;
+        return this.left < container.left;
       case "+y":
-        return this.top < container.top;
+        return this.bottom > container.bottom;
       case "-y":
-        return this.bottom > container.bottom;
+        return this.top < container.top;
       }
     }
 
     // Find the percentage of the area that this box is overlapping with another
     // box.
     intersectPercentage(b2) {
       let x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
           y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
@@ -891,17 +897,17 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     const fullDimension = isWritingDirectionHorizontal ?
       containerBox.height : containerBox.width;
     if (cue.snapToLines) {
       // The step is the height or width of the line box. We should use font
       // size directly, instead of using text box's width or height, because the
       // width or height of the box would be changed when the text is wrapped to
       // different line. Ex. if text is wrapped to two line, the height or width
       // of the box would become 2 times of font size.
-      let step = parseFloat(styleBox.fontSize.replace("px", ""));
+      let step = styleBox.getFirstLineBoxSize();
       if (step == 0) {
         return;
       }
 
       // spec 7.2.10.4 ~ 7.2.10.6
       let line = Math.floor(cue.computedLine + 0.5);
       if (cue.vertical == "rl") {
         line = -1 * (line + 1);
@@ -991,19 +997,19 @@ XPCOMUtils.defineLazyPreferenceGetter(th
       }
 
       // spec 7.2.10.3
       let bestPosition = {},
           specifiedPosition = box.clone(),
           outsideAreaPercentage = 1; // Highest possible so the first thing we get is better.
       let hasFoundBestPosition = false;
       const axis = ["-y", "-x", "+x", "+y"];
-      const toMove = parseFloat(styleBox.fontSize.replace("px", ""));
+      const toMove = styleBox.getFirstLineBoxSize();
       for (let i = 0; i < axis.length && !hasFoundBestPosition; i++) {
-        while (box.overlapsOppositeAxis(containerBox, axis[i]) ||
+        while (!box.isOutsideTheAxisBoundary(containerBox, axis[i]) &&
                (!box.within(containerBox) || box.overlapsAny(outputBoxes))) {
           box.move(axis[i], toMove);
         }
         // We found a spot where we aren't overlapping anything. This is our
         // best position.
         if (box.within(containerBox)) {
           bestPosition = box.clone();
           hasFoundBestPosition = true;
--- a/dom/network/UDPSocket.cpp
+++ b/dom/network/UDPSocket.cpp
@@ -222,18 +222,17 @@ void UDPSocket::JoinMulticastGroup(const
     aRv = mSocket->JoinMulticast(address, EmptyCString());
     NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
 
     return;
   }
 
   MOZ_ASSERT(mSocketChild);
 
-  aRv = mSocketChild->JoinMulticast(address, EmptyCString());
-  NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
+  mSocketChild->JoinMulticast(address, EmptyCString());
 }
 
 void UDPSocket::LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
                                     ErrorResult& aRv) {
   if (mReadyState == SocketReadyState::Closed) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
@@ -253,18 +252,17 @@ void UDPSocket::LeaveMulticastGroup(cons
 
     aRv = mSocket->LeaveMulticast(address, EmptyCString());
     NS_WARNING_ASSERTION(!aRv.Failed(), "mSocket->LeaveMulticast failed");
     return;
   }
 
   MOZ_ASSERT(mSocketChild);
 
-  aRv = mSocketChild->LeaveMulticast(address, EmptyCString());
-  NS_WARNING_ASSERTION(!aRv.Failed(), "mSocketChild->LeaveMulticast failed");
+  mSocketChild->LeaveMulticast(address, EmptyCString());
 }
 
 nsresult UDPSocket::DoPendingMcastCommand() {
   MOZ_ASSERT(mReadyState == SocketReadyState::Open,
              "Multicast command can only be executed after socket opened");
 
   for (uint32_t i = 0; i < mPendingMcastCommands.Length(); ++i) {
     MulticastCommand& command = mPendingMcastCommands[i];
@@ -462,17 +460,17 @@ nsresult UDPSocket::InitLocal(const nsAS
 
   return NS_OK;
 }
 
 nsresult UDPSocket::InitRemote(const nsAString& aLocalAddress,
                                const uint16_t& aLocalPort) {
   nsresult rv;
 
-  nsCOMPtr<nsIUDPSocketChild> sock = new dom::UDPSocketChild();
+  RefPtr<UDPSocketChild> sock = new UDPSocketChild();
 
   mListenerProxy = new ListenerProxy(this);
 
   nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(GetOwner(), &rv);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
@@ -559,47 +557,44 @@ nsresult UDPSocket::Init(const nsString&
 
   nsCOMPtr<nsIRunnable> runnable = new OpenSocketRunnable(this);
 
   return NS_DispatchToMainThread(runnable);
 }
 
 void UDPSocket::HandleReceivedData(const nsACString& aRemoteAddress,
                                    const uint16_t& aRemotePort,
-                                   const uint8_t* aData,
-                                   const uint32_t& aDataLength) {
+                                   const nsTArray<uint8_t>& aData) {
   if (mReadyState != SocketReadyState::Open) {
     return;
   }
 
   if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
     return;
   }
 
-  if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData,
-                                     aDataLength))) {
+  if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData))) {
     CloseWithReason(NS_ERROR_TYPE_ERR);
   }
 }
 
 nsresult UDPSocket::DispatchReceivedData(const nsACString& aRemoteAddress,
                                          const uint16_t& aRemotePort,
-                                         const uint8_t* aData,
-                                         const uint32_t& aDataLength) {
+                                         const nsTArray<uint8_t>& aData) {
   AutoJSAPI jsapi;
 
   if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
 
   // Copy packet data to ArrayBuffer
-  JS::Rooted<JSObject*> arrayBuf(cx,
-                                 ArrayBuffer::Create(cx, aDataLength, aData));
+  JS::Rooted<JSObject*> arrayBuf(
+      cx, ArrayBuffer::Create(cx, aData.Length(), aData.Elements()));
 
   if (NS_WARN_IF(!arrayBuf)) {
     return NS_ERROR_FAILURE;
   }
 
   JS::Rooted<JS::Value> jsData(cx, JS::ObjectValue(*arrayBuf));
 
   // Create DOM event
@@ -643,18 +638,17 @@ UDPSocket::OnPacketReceived(nsIUDPSocket
     return NS_OK;
   }
 
   uint16_t remotePort;
   if (NS_WARN_IF(NS_FAILED(addr->GetPort(&remotePort)))) {
     return NS_OK;
   }
 
-  HandleReceivedData(remoteAddress, remotePort, buffer.Elements(),
-                     buffer.Length());
+  HandleReceivedData(remoteAddress, remotePort, buffer);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UDPSocket::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) {
   // nsIUDPSocketListener callbacks should be invoked on main thread.
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
@@ -671,39 +665,35 @@ UDPSocket::CallListenerError(const nsACS
                              uint32_t aLineNumber) {
   CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UDPSocket::CallListenerReceivedData(const nsACString& aRemoteAddress,
-                                    uint16_t aRemotePort, const uint8_t* aData,
-                                    uint32_t aDataLength) {
-  HandleReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength);
+                                    uint16_t aRemotePort,
+                                    const nsTArray<uint8_t>& aData) {
+  HandleReceivedData(aRemoteAddress, aRemotePort, aData);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UDPSocket::CallListenerOpened() {
   if (mReadyState != SocketReadyState::Opening) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mSocketChild);
 
   // Get real local address and local port
-  nsCString localAddress;
-  mSocketChild->GetLocalAddress(localAddress);
-  mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
+  mLocalAddress = NS_ConvertUTF8toUTF16(mSocketChild->LocalAddress());
 
-  uint16_t localPort;
-  mSocketChild->GetLocalPort(&localPort);
-  mLocalPort.SetValue(localPort);
+  mLocalPort.SetValue(mSocketChild->LocalPort());
 
   mReadyState = SocketReadyState::Open;
   nsresult rv = DoPendingMcastCommand();
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     CloseWithReason(rv);
     return NS_OK;
   }
--- a/dom/network/UDPSocket.h
+++ b/dom/network/UDPSocket.h
@@ -28,16 +28,17 @@ extern LazyLogModule gUDPSocketLog;
 #define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
 #define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
 }  // namespace net
 
 namespace dom {
 
 struct UDPOptions;
 class StringOrBlobOrArrayBufferOrArrayBufferView;
+class UDPSocketChild;
 
 class UDPSocket final : public DOMEventTargetHelper,
                         public nsIUDPSocketListener,
                         public nsIUDPSocketInternal {
  public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UDPSocket, DOMEventTargetHelper)
   NS_DECL_NSIUDPSOCKETLISTENER
@@ -123,40 +124,39 @@ class UDPSocket final : public DOMEventT
 
   nsresult InitLocal(const nsAString& aLocalAddress,
                      const uint16_t& aLocalPort);
 
   nsresult InitRemote(const nsAString& aLocalAddress,
                       const uint16_t& aLocalPort);
 
   void HandleReceivedData(const nsACString& aRemoteAddress,
-                          const uint16_t& aRemotePort, const uint8_t* aData,
-                          const uint32_t& aDataLength);
+                          const uint16_t& aRemotePort,
+                          const nsTArray<uint8_t>& aData);
 
   nsresult DispatchReceivedData(const nsACString& aRemoteAddress,
                                 const uint16_t& aRemotePort,
-                                const uint8_t* aData,
-                                const uint32_t& aDataLength);
+                                const nsTArray<uint8_t>& aData);
 
   void CloseWithReason(nsresult aReason);
 
   nsresult DoPendingMcastCommand();
 
   nsString mLocalAddress;
   Nullable<uint16_t> mLocalPort;
   nsCString mRemoteAddress;
   Nullable<uint16_t> mRemotePort;
   bool mAddressReuse;
   bool mLoopback;
   SocketReadyState mReadyState;
   RefPtr<Promise> mOpened;
   RefPtr<Promise> mClosed;
 
   nsCOMPtr<nsIUDPSocket> mSocket;
-  nsCOMPtr<nsIUDPSocketChild> mSocketChild;
+  RefPtr<UDPSocketChild> mSocketChild;
   RefPtr<ListenerProxy> mListenerProxy;
 
   struct MulticastCommand {
     enum CommandType { Join, Leave };
 
     MulticastCommand(CommandType aCommand, const nsAString& aAddress)
         : mCommand(aCommand), mAddress(aAddress) {}
 
--- a/dom/network/UDPSocketChild.cpp
+++ b/dom/network/UDPSocketChild.cpp
@@ -15,17 +15,17 @@
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 
 using mozilla::net::gNeckoChild;
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_ISUPPORTS(UDPSocketChildBase, nsIUDPSocketChild)
+NS_IMPL_ISUPPORTS(UDPSocketChildBase, nsISupports)
 
 UDPSocketChildBase::UDPSocketChildBase() : mIPCOpen(false) {}
 
 UDPSocketChildBase::~UDPSocketChildBase() {}
 
 void UDPSocketChildBase::ReleaseIPDLReference() {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
@@ -47,36 +47,33 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
   }
   return refcnt;
 }
 
 UDPSocketChild::UDPSocketChild() : mBackgroundManager(nullptr), mLocalPort(0) {}
 
 UDPSocketChild::~UDPSocketChild() {}
 
-// nsIUDPSocketChild Methods
-
-NS_IMETHODIMP
-UDPSocketChild::SetBackgroundSpinsEvents() {
+nsresult UDPSocketChild::SetBackgroundSpinsEvents() {
   using mozilla::ipc::BackgroundChild;
 
   mBackgroundManager = BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!mBackgroundManager)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket, nsIPrincipal* aPrincipal,
-                     const nsACString& aHost, uint16_t aPort,
-                     bool aAddressReuse, bool aLoopback,
-                     uint32_t recvBufferSize, uint32_t sendBufferSize,
-                     nsIEventTarget* aMainThreadEventTarget) {
+nsresult UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket,
+                              nsIPrincipal* aPrincipal, const nsACString& aHost,
+                              uint16_t aPort, bool aAddressReuse,
+                              bool aLoopback, uint32_t recvBufferSize,
+                              uint32_t sendBufferSize,
+                              nsIEventTarget* aMainThreadEventTarget) {
   UDPSOCKET_LOG(
       ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
 
   NS_ENSURE_ARG(aSocket);
 
   if (NS_IsMainThread()) {
     if (aMainThreadEventTarget) {
       gNeckoChild->SetEventTargetForActor(this, aMainThreadEventTarget);
@@ -102,63 +99,31 @@ UDPSocketChild::Bind(nsIUDPSocketInterna
   mSocket = aSocket;
   AddIPDLReference();
 
   SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback,
            recvBufferSize, sendBufferSize);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::Connect(nsIUDPSocketInternal* aSocket, const nsACString& aHost,
-                        uint16_t aPort) {
+void UDPSocketChild::Connect(nsIUDPSocketInternal* aSocket,
+                             const nsACString& aHost, uint16_t aPort) {
   UDPSOCKET_LOG(
       ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
 
   mSocket = aSocket;
 
   SendConnect(UDPAddressInfo(nsCString(aHost), aPort));
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-UDPSocketChild::Close() {
-  SendClose();
-  return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::Send(const nsACString& aHost, uint16_t aPort,
-                     const uint8_t* aData, uint32_t aByteLength) {
-  NS_ENSURE_ARG(aData);
-
-  UDPSOCKET_LOG(("%s: %s:%u - %u bytes", __FUNCTION__,
-                 PromiseFlatCString(aHost).get(), aPort, aByteLength));
-  return SendDataInternal(
-      UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)), aData,
-      aByteLength);
-}
+void UDPSocketChild::Close() { SendClose(); }
 
-NS_IMETHODIMP
-UDPSocketChild::SendWithAddr(nsINetAddr* aAddr, const uint8_t* aData,
-                             uint32_t aByteLength) {
-  NS_ENSURE_ARG(aAddr);
-  NS_ENSURE_ARG(aData);
-
-  NetAddr addr;
-  aAddr->GetNetAddr(&addr);
-
-  UDPSOCKET_LOG(("%s: %u bytes", __FUNCTION__, aByteLength));
-  return SendDataInternal(UDPSocketAddr(addr), aData, aByteLength);
-}
-
-NS_IMETHODIMP
-UDPSocketChild::SendWithAddress(const NetAddr* aAddr, const uint8_t* aData,
-                                uint32_t aByteLength) {
+nsresult UDPSocketChild::SendWithAddress(const NetAddr* aAddr,
+                                         const uint8_t* aData,
+                                         uint32_t aByteLength) {
   NS_ENSURE_ARG(aAddr);
   NS_ENSURE_ARG(aData);
 
   UDPSOCKET_LOG(("%s: %u bytes", __FUNCTION__, aByteLength));
   return SendDataInternal(UDPSocketAddr(*aAddr), aData, aByteLength);
 }
 
 nsresult UDPSocketChild::SendDataInternal(const UDPSocketAddr& aAddr,
@@ -174,77 +139,52 @@ nsresult UDPSocketChild::SendDataInterna
   InfallibleTArray<uint8_t> array;
   array.SwapElements(fallibleArray);
 
   SendOutgoingData(array, aAddr);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::SendBinaryStream(const nsACString& aHost, uint16_t aPort,
-                                 nsIInputStream* aStream) {
+nsresult UDPSocketChild::SendBinaryStream(const nsACString& aHost,
+                                          uint16_t aPort,
+                                          nsIInputStream* aStream) {
   NS_ENSURE_ARG(aStream);
 
   mozilla::ipc::AutoIPCStream autoStream;
   autoStream.Serialize(aStream, static_cast<mozilla::dom::ContentChild*>(
                                     gNeckoChild->Manager()));
 
   UDPSOCKET_LOG(
       ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
   SendOutgoingData(UDPData(autoStream.TakeValue()),
                    UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)));
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
-                              const nsACString& aInterface) {
+void UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
+                                   const nsACString& aInterface) {
   SendJoinMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-UDPSocketChild::LeaveMulticast(const nsACString& aMulticastAddress,
-                               const nsACString& aInterface) {
-  SendLeaveMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
-  return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::GetLocalPort(uint16_t* aLocalPort) {
-  NS_ENSURE_ARG_POINTER(aLocalPort);
-
-  *aLocalPort = mLocalPort;
-  return NS_OK;
+void UDPSocketChild::LeaveMulticast(const nsACString& aMulticastAddress,
+                                    const nsACString& aInterface) {
+  SendLeaveMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
 }
 
-NS_IMETHODIMP
-UDPSocketChild::GetLocalAddress(nsACString& aLocalAddress) {
-  aLocalAddress = mLocalAddress;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-UDPSocketChild::SetFilterName(const nsACString& aFilterName) {
+nsresult UDPSocketChild::SetFilterName(const nsACString& aFilterName) {
   if (!mFilterName.IsEmpty()) {
     // filter name can only be set once.
     return NS_ERROR_FAILURE;
   }
   mFilterName = aFilterName;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UDPSocketChild::GetFilterName(nsACString& aFilterName) {
-  aFilterName = mFilterName;
-  return NS_OK;
-}
-
 // PUDPSocketChild Methods
 mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackOpened(
     const UDPAddressInfo& aAddressInfo) {
   mLocalAddress = aAddressInfo.addr();
   mLocalPort = aAddressInfo.port();
 
   UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, mLocalAddress.get(), mLocalPort));
   nsresult rv = mSocket->CallListenerOpened();
@@ -273,19 +213,18 @@ mozilla::ipc::IPCResult UDPSocketChild::
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackReceivedData(
     const UDPAddressInfo& aAddressInfo, InfallibleTArray<uint8_t>&& aData) {
   UDPSOCKET_LOG(("%s: %s:%u length %zu", __FUNCTION__,
                  aAddressInfo.addr().get(), aAddressInfo.port(),
                  aData.Length()));
-  nsresult rv = mSocket->CallListenerReceivedData(
-      aAddressInfo.addr(), aAddressInfo.port(), aData.Elements(),
-      aData.Length());
+  nsresult rv = mSocket->CallListenerReceivedData(aAddressInfo.addr(),
+                                                  aAddressInfo.port(), aData);
   mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackError(
     const nsCString& aMessage, const nsCString& aFilename,
     const uint32_t& aLineNumber) {
--- a/dom/network/UDPSocketChild.h
+++ b/dom/network/UDPSocketChild.h
@@ -17,39 +17,76 @@
     0xb47e5a0f, 0xd384, 0x48ef, {                    \
       0x88, 0x85, 0x42, 0x59, 0x79, 0x3d, 0x9c, 0xf0 \
     }                                                \
   }
 
 namespace mozilla {
 namespace dom {
 
-class UDPSocketChildBase : public nsIUDPSocketChild {
+class UDPSocketChildBase : public nsISupports {
  public:
   NS_DECL_ISUPPORTS
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
  protected:
   UDPSocketChildBase();
   virtual ~UDPSocketChildBase();
   nsCOMPtr<nsIUDPSocketInternal> mSocket;
   bool mIPCOpen;
 };
 
 class UDPSocketChild : public mozilla::net::PUDPSocketChild,
                        public UDPSocketChildBase {
  public:
-  NS_DECL_NSIUDPSOCKETCHILD
   NS_IMETHOD_(MozExternalRefCountType) Release() override;
 
   UDPSocketChild();
   virtual ~UDPSocketChild();
 
+  uint16_t LocalPort() const { return mLocalPort; }
+  // Local address as UTF-8.
+  const nsACString& LocalAddress() const { return mLocalAddress; }
+
+  nsresult SetFilterName(const nsACString& aFilterName);
+
+  // Allow hosting this over PBackground instead of PNecko
+  nsresult SetBackgroundSpinsEvents();
+
+  // Tell the chrome process to bind the UDP socket to a given local host and
+  // port
+  nsresult Bind(nsIUDPSocketInternal* aSocket, nsIPrincipal* aPrincipal,
+                const nsACString& aHost, uint16_t aPort, bool aAddressReuse,
+                bool aLoopback, uint32_t recvBufferSize,
+                uint32_t sendBufferSize,
+                nsIEventTarget* aMainThreadEventTarget);
+
+  // Tell the chrome process to connect the UDP socket to a given remote host
+  // and port
+  void Connect(nsIUDPSocketInternal* aSocket, const nsACString& aHost,
+               uint16_t aPort);
+
+  // Send the given data to the given address.
+  nsresult SendWithAddress(const NetAddr* aAddr, const uint8_t* aData,
+                           uint32_t aByteLength);
+
+  // Send input stream. This must be a buffered stream implementation.
+  nsresult SendBinaryStream(const nsACString& aHost, uint16_t aPort,
+                            nsIInputStream* aStream);
+
+  void Close();
+
+  // Address and interface are both UTF-8.
+  void JoinMulticast(const nsACString& aMulticastAddress,
+                     const nsACString& aInterface);
+  void LeaveMulticast(const nsACString& aMulticastAddress,
+                      const nsACString& aInterface);
+
   mozilla::ipc::IPCResult RecvCallbackOpened(
       const UDPAddressInfo& aAddressInfo);
   mozilla::ipc::IPCResult RecvCallbackConnected(
       const UDPAddressInfo& aAddressInfo);
   mozilla::ipc::IPCResult RecvCallbackClosed();
   mozilla::ipc::IPCResult RecvCallbackReceivedData(
       const UDPAddressInfo& aAddressInfo, InfallibleTArray<uint8_t>&& aData);
   mozilla::ipc::IPCResult RecvCallbackError(const nsCString& aMessage,
--- a/dom/network/interfaces/nsIUDPSocketChild.idl
+++ b/dom/network/interfaces/nsIUDPSocketChild.idl
@@ -5,76 +5,26 @@
 #include "nsISupports.idl"
 #include "nsINetAddr.idl"
 
 interface nsIUDPSocketInternal;
 interface nsIInputStream;
 interface nsIPrincipal;
 interface nsIEventTarget;
 
-%{ C++
-namespace mozilla {
-namespace net {
-union NetAddr;
-}
-}
-%}
-native NetAddr(mozilla::net::NetAddr);
-[ptr] native NetAddrPtr(mozilla::net::NetAddr);
-
-[scriptable, uuid(1e6ad73b-6c05-4d78-9a88-2d357b88f58b)]
-interface nsIUDPSocketChild : nsISupports
-{
-  readonly attribute unsigned short localPort;
-  readonly attribute AUTF8String localAddress;
-  attribute AUTF8String filterName;
-
-  // Allow hosting this over PBackground instead of PNecko
-  [noscript] void setBackgroundSpinsEvents();
-
-  // Tell the chrome process to bind the UDP socket to a given local host and port
-  void bind(in nsIUDPSocketInternal socket, in nsIPrincipal principal,
-            in AUTF8String host, in unsigned short port,
-            in bool addressReuse, in bool loopback, in uint32_t recvBufferSize,
-            in uint32_t sendBufferSize,
-            [optional] in nsIEventTarget mainThreadTarget);
-
-  // Tell the chrome process to connect the UDP socket to a given remote host and port
-  void connect(in nsIUDPSocketInternal socket, in AUTF8String host, in unsigned short port);
-
-  // Tell the chrome process to perform equivalent operations to all following methods
-  void send(in AUTF8String host, in unsigned short port,
-            [const, array, size_is(byteLength)] in uint8_t bytes,
-            in unsigned long byteLength);
-  // Send without DNS query
-  void sendWithAddr(in nsINetAddr addr,
-                    [const, array, size_is(byteLength)] in uint8_t bytes,
-                    in unsigned long byteLength);
-  [noscript] void sendWithAddress([const] in NetAddrPtr addr,
-                                  [const, array, size_is(byteLength)] in uint8_t bytes,
-                                  in unsigned long byteLength);
-  // Send input stream. This must be a buffered stream implementation.
-  void sendBinaryStream(in AUTF8String host, in unsigned short port, in nsIInputStream stream);
-
-  void close();
-  void joinMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
-  void leaveMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
-};
-
 /*
  * Internal interface for callback from chrome process
  */
-[scriptable, uuid(613dd3ad-598b-4da9-ad63-bbda50c20098)]
+[scriptable, builtinclass, uuid(613dd3ad-598b-4da9-ad63-bbda50c20098)]
 interface nsIUDPSocketInternal : nsISupports
 {
   // callback while socket is opened. localPort and localAddress is ready until this time.
   void callListenerOpened();
   // callback while socket is connected.
   void callListenerConnected();
   // callback while socket is closed.
   void callListenerClosed();
   // callback while incoming packet is received.
   void callListenerReceivedData(in AUTF8String host, in unsigned short port,
-                                [const, array, size_is(dataLength)] in uint8_t data,
-                                in unsigned long dataLength);
+                                in Array<uint8_t> data);
   // callback while any error happened.
   void callListenerError(in AUTF8String message, in AUTF8String filename, in uint32_t lineNumber);
 };
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
-prefs = dom.performance.enable_scheduler_timing=false
 support-files =
   browser_frame_elements.html
   page_privatestorageevent.html
   page_localstorage_e10s.html
   page_localstorage_snapshotting_e10s.html
   position.html
   test-console-api.html
   test_bug1004814.html
@@ -82,13 +81,11 @@ support-files =
   test_new_window_from_content_child.html
 [browser_xhr_sandbox.js]
 [browser_noopener.js]
 skip-if = (verify && debug && (os == 'linux'))
 support-files =
   test_noopener_source.html
   test_noopener_target.html
 [browser_noopener_null_uri.js]
-[browser_test_performance_metrics_off.js]
-skip-if = verify
 [browser_wakelock.js]
 [browser_keypressTelemetry.js]
 skip-if = webrender
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/browser/browser_test_performance_metrics_off.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
-
-add_task(async function testNotActivated() {
-  // dom.performance.enable_scheduler_timing is set to false in browser.ini
-  waitForExplicitFinish();
-  // make sure we throw a JS exception in case the pref is off and
-  // we call requestPerformanceMetrics()
-  let failed = false;
-  try {
-    await ChromeUtils.requestPerformanceMetrics();
-  } catch (e) {
-    failed = true;
-  }
-  Assert.ok(failed, "We should get an exception if preffed off");
-});
--- a/dom/tests/browser/perfmetrics/browser.ini
+++ b/dom/tests/browser/perfmetrics/browser.ini
@@ -1,11 +1,10 @@
 [DEFAULT]
 prefs =
-  dom.performance.enable_scheduler_timing=true
   dom.performance.children_results_ipc_timeout=2000
 
 support-files =
   dummy.html
   ping_worker.html
   ping_worker2.html
   ping_worker.js
   setinterval.html
--- a/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js
+++ b/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js
@@ -39,17 +39,16 @@ function jsonrpc(tab, method, params) {
   });
 }
 
 function postMessageToWorker(tab, message) {
   return jsonrpc(tab, "postMessageToWorker", [WORKER_URL, message]);
 }
 
 add_task(async function test() {
-  // dom.performance.enable_scheduler_timing is set to true in browser.ini
   waitForExplicitFinish();
 
   // Load 3 pages and wait. The 3rd one has a worker
   let page1 = await BrowserTestUtils.openNewForegroundTab({
     gBrowser, opening: "about:about", forceNewProcess: false,
   });
 
   let page2 = await BrowserTestUtils.openNewForegroundTab({
--- a/dom/tests/browser/perfmetrics/browser_test_unresponsive.js
+++ b/dom/tests/browser/perfmetrics/browser_test_unresponsive.js
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const ROOT_URL = "http://example.com/browser/dom/tests/browser/perfmetrics";
 const PAGE_URL = ROOT_URL + "/unresponsive.html";
 
 add_task(async function test() {
-  // dom.performance.enable_scheduler_timing is set to true in browser.ini
   waitForExplicitFinish();
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: PAGE_URL },
     async function(browser) {
     let dataBack = 0;
     let tabId = gBrowser.selectedBrowser.outerWindowID;
 
     function exploreResults(data, filterByWindowId) {
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -308,8 +308,23 @@ partial interface Element {
   /**
    * Returns a sequence of all the descendent elements of this element
    * that have display:grid or display:inline-grid style and generate
    * a frame.
    */
   [ChromeOnly, Pure]
   sequence<Element> getElementsWithGrid();
 };
+
+// These variables are used in vtt.js, they are used for positioning vtt cues.
+partial interface Element {
+  // These two attributes are a double version of the clientHeight and the
+  // clientWidth.
+  [ChromeOnly]
+  readonly attribute double clientHeightDouble;
+  [ChromeOnly]
+  readonly attribute double clientWidthDouble;
+  // This attribute returns the block size of the first line box under the different
+  // writing directions. If the direction is horizontal, it represents box's
+  // height. If the direction is vertical, it represents box's width.
+  [ChromeOnly]
+  readonly attribute double firstLineBoxBSize;
+};
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -90,19 +90,17 @@ class CompileDebuggerScriptRunnable fina
 
     if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
       return false;
     }
 
     // Initialize performance state which might be used on the main thread, as
     // in CompileScriptRunnable. This runnable might execute first.
     aWorkerPrivate->EnsurePerformanceStorage();
-    if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
-      aWorkerPrivate->EnsurePerformanceCounter();
-    }
+    aWorkerPrivate->EnsurePerformanceCounter();
 
     JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
 
     ErrorResult rv;
     JSAutoRealm ar(aCx, global);
     workerinternals::LoadMainScript(aWorkerPrivate, nullptr, mScriptURL,
                                     DebuggerScript, rv);
     rv.WouldReportJSException();
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -328,20 +328,17 @@ class CompileScriptRunnable final : publ
       return false;
     }
 
     // PerformanceStorage & PerformanceCounter both need to be initialized
     // on the worker thread before being used on main-thread.
     // Let's be sure that it is created before any
     // content loading.
     aWorkerPrivate->EnsurePerformanceStorage();
-
-    if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
-      aWorkerPrivate->EnsurePerformanceCounter();
-    }
+    aWorkerPrivate->EnsurePerformanceCounter();
 
     ErrorResult rv;
     workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
                                     mScriptURL, WorkerScript, rv);
     rv.WouldReportJSException();
     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
     // case, but don't throw anything on aCx.  The idea is to not dispatch error
@@ -4763,17 +4760,16 @@ void WorkerPrivate::DumpCrashInformation
     WorkerHolder* holder = iter.GetNext();
     aString.Append("|");
     aString.Append(holder->Name());
   }
 }
 
 void WorkerPrivate::EnsurePerformanceCounter() {
   AssertIsOnWorkerThread();
-  MOZ_ASSERT(mozilla::StaticPrefs::dom_performance_enable_scheduler_timing());
   if (!mPerformanceCounter) {
     nsPrintfCString workerName("Worker:%s",
                                NS_ConvertUTF16toUTF8(mWorkerName).get());
     mPerformanceCounter = new PerformanceCounter(workerName);
   }
 }
 
 PerformanceCounter* WorkerPrivate::GetPerformanceCounter() {
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1135,18 +1135,16 @@ class WorkerPrivate : public RelativeTim
   // Protected by mMutex.
   bool mDebuggerReady;
   nsTArray<RefPtr<WorkerRunnable>> mDelayedDebuggeeRunnables;
 
   // mIsInAutomation is true when we're running in test automation.
   // We expose some extra testing functions in that case.
   bool mIsInAutomation;
 
-  // This pointer will be null if dom.performance.enable_scheduler_timing is
-  // false (default value)
   RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
 
   nsString mID;
 };
 
 class AutoSyncLoopHolder {
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsIEventTarget> mTarget;
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -138,19 +138,16 @@ void WorkerThread::SetWorker(const Worke
       mAcceptingNonWorkerRunnables = true;
 #endif
       mWorkerPrivate = nullptr;
     }
   }
 }
 
 void WorkerThread::IncrementDispatchCounter() {
-  if (!mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
-    return;
-  }
   MutexAutoLock lock(mLock);
   if (mWorkerPrivate) {
     PerformanceCounter* performanceCounter =
         mWorkerPrivate->GetPerformanceCounter();
     if (performanceCounter) {
       performanceCounter->IncrementDispatchCounter(DispatchCategory::Worker);
     }
   }
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -737,17 +737,18 @@ class gfxPrefs final {
   DECL_GFX_PREF(Live, "media.wmf.use-nv12-format", PDMWMFUseNV12Format, bool, true);
   DECL_GFX_PREF(Live, "media.wmf.force.allow-p010-format", PDMWMFForceAllowP010Format, bool, false);
   DECL_GFX_PREF(Once, "media.wmf.use-sync-texture", PDMWMFUseSyncTexture, bool, true);
   DECL_GFX_PREF(Live, "media.wmf.low-latency.enabled", PDMWMFLowLatencyEnabled, bool, false);
   DECL_GFX_PREF(Live, "media.wmf.low-latency.force-disabled", PDMWMFLowLatencyForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "media.wmf.skip-blacklist", PDMWMFSkipBlacklist, bool, false);
   DECL_GFX_PREF(Live, "media.wmf.deblacklisting-for-telemetry-in-gpu-process", PDMWMFDeblacklistingForTelemetryInGPUProcess, bool, false);
   DECL_GFX_PREF(Live, "media.wmf.amd.highres.enabled", PDMWMFAMDHighResEnabled, bool, true);
-  DECL_GFX_PREF(Live, "media.wmf.allow-unsupported-resolutions",  PDMWMFAllowUnsupportedResolutions, bool, false);
+  DECL_GFX_PREF(Live, "media.wmf.allow-unsupported-resolutions", PDMWMFAllowUnsupportedResolutions, bool, false);
+  DECL_GFX_PREF(Once, "media.wmf.vp9.enabled", MediaWmfVp9Enabled, bool, true);
 #endif
 
   // These affect how line scrolls from wheel events will be accelerated.
   DECL_GFX_PREF(Live, "mousewheel.acceleration.factor",        MouseWheelAccelerationFactor, int32_t, -1);
   DECL_GFX_PREF(Live, "mousewheel.acceleration.start",         MouseWheelAccelerationStart, int32_t, -1);
 
   // This affects whether events will be routed through APZ or not.
   DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.enabled",
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -267,17 +267,17 @@ impl ClipNodeInfo {
     fn create_instance(
         &self,
         node: &ClipNode,
         clipped_rect: &LayoutRect,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         clip_scroll_tree: &ClipScrollTree,
         request_resources: bool,
-    ) -> ClipNodeInstance {
+    ) -> Option<ClipNodeInstance> {
         // Calculate some flags that are required for the segment
         // building logic.
         let mut flags = match self.conversion {
             ClipSpaceConversion::Local => {
                 ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
             }
             ClipSpaceConversion::ScaleOffset(..) => {
                 ClipNodeFlags::SAME_COORD_SYSTEM
@@ -353,26 +353,31 @@ impl ClipNodeInfo {
                                 tile_rect: tile.rect,
                             });
                         }
                     }
                     visible_tiles = Some(mask_tiles);
                 } else if request_resources {
                     resource_cache.request_image(request, gpu_cache);
                 }
+            } else {
+                // If the supplied image key doesn't exist in the resource cache,
+                // skip the clip node since there is nothing to mask with.
+                warn!("Clip mask with missing image key {:?}", request.key);
+                return None;
             }
         }
 
-        ClipNodeInstance {
+        Some(ClipNodeInstance {
             handle: self.handle,
             flags,
             spatial_node_index: self.spatial_node_index,
             local_pos: self.local_pos,
             visible_tiles,
-        }
+        })
     }
 }
 
 impl ClipNode {
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
         device_pixel_scale: DevicePixelScale,
@@ -673,46 +678,46 @@ impl ClipStore {
 
                     // TODO(gw): Ensure this only runs once on each node per frame?
                     node.update(
                         gpu_cache,
                         device_pixel_scale,
                     );
 
                     // Create the clip node instance for this clip node
-                    let instance = node_info.create_instance(
+                    if let Some(instance) = node_info.create_instance(
                         node,
                         &local_bounding_rect,
                         gpu_cache,
                         resource_cache,
                         clip_scroll_tree,
                         request_resources,
-                    );
+                    ) {
+                        // As a special case, a partial accept of a clip rect that is
+                        // in the same coordinate system as the primitive doesn't need
+                        // a clip mask. Instead, it can be handled by the primitive
+                        // vertex shader as part of the local clip rect. This is an
+                        // important optimization for reducing the number of clip
+                        // masks that are allocated on common pages.
+                        needs_mask |= match node.item {
+                            ClipItem::Rectangle(_, ClipMode::ClipOut) |
+                            ClipItem::RoundedRectangle(..) |
+                            ClipItem::Image { .. } |
+                            ClipItem::BoxShadow(..) => {
+                                true
+                            }
 
-                    // As a special case, a partial accept of a clip rect that is
-                    // in the same coordinate system as the primitive doesn't need
-                    // a clip mask. Instead, it can be handled by the primitive
-                    // vertex shader as part of the local clip rect. This is an
-                    // important optimization for reducing the number of clip
-                    // masks that are allocated on common pages.
-                    needs_mask |= match node.item {
-                        ClipItem::Rectangle(_, ClipMode::ClipOut) |
-                        ClipItem::RoundedRectangle(..) |
-                        ClipItem::Image { .. } |
-                        ClipItem::BoxShadow(..) => {
-                            true
-                        }
+                            ClipItem::Rectangle(_, ClipMode::Clip) => {
+                                !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
+                            }
+                        };
 
-                        ClipItem::Rectangle(_, ClipMode::Clip) => {
-                            !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
-                        }
-                    };
-
-                    // Store this in the index buffer for this clip chain instance.
-                    self.clip_node_instances.push(instance);
+                        // Store this in the index buffer for this clip chain instance.
+                        self.clip_node_instances.push(instance);
+                    }
                 }
             }
         }
 
         // Get the range identifying the clip nodes in the index buffer.
         let clips_range = ClipNodeRange {
             first: first_clip_node_index,
             count: self.clip_node_instances.len() as u32 - first_clip_node_index,
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/mask/missing-mask-ref.yaml
@@ -0,0 +1,7 @@
+# Don't crash when supplied an invalid image key for the mask!
+---
+root:
+  items:
+    - type: rect
+      bounds: [0, 0, 35, 35]
+      color: blue
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/mask/missing-mask.yaml
@@ -0,0 +1,14 @@
+# Don't crash when supplied an invalid image key for the mask!
+---
+root:
+  items:
+    - type: clip
+      bounds: [0, 0, 95, 88]
+      image-mask:
+        image: invalid
+        rect: [0, 0, 35, 35]
+        repeat: false
+      items:
+      - type: rect
+        bounds: [0, 0, 95, 88]
+        color: blue
--- a/gfx/wr/wrench/reftests/mask/reftest.list
+++ b/gfx/wr/wrench/reftests/mask/reftest.list
@@ -8,8 +8,9 @@
 platform(linux,mac) == rounded-corners.yaml rounded-corners.png
 != mask.yaml out-of-bounds.yaml
 platform(linux,mac) fuzzy(1,8750) color_targets(2) alpha_targets(1) == mask-atomicity.yaml mask-atomicity-ref.yaml
 platform(linux,mac) fuzzy(1,8750) == mask-atomicity-tiling.yaml mask-atomicity-ref.yaml
 platform(linux,mac) == mask-perspective.yaml mask-perspective.png
 == fuzzy(1,6) mask-perspective-tiling.yaml mask-perspective.yaml
 platform(linux,mac) == checkerboard.yaml checkerboard.png
 == checkerboard.yaml checkerboard-tiling.yaml
+== missing-mask.yaml missing-mask-ref.yaml
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -694,31 +694,34 @@ impl YamlFrameReader {
             })
     }
 
     fn to_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option<ImageMask> {
         if item.as_hash().is_none() {
             return None;
         }
 
-        let file = match item["image"].as_str() {
+        let tiling = item["tile-size"].as_i64();
+
+        let (image_key, image_dims) = match item["image"].as_str() {
             Some(filename) => {
-                let mut file = self.aux_dir.clone();
-                file.push(filename);
-                file
+                if filename == "invalid" {
+                    (ImageKey::DUMMY, LayoutSize::new(100.0, 100.0))
+                } else {
+                    let mut file = self.aux_dir.clone();
+                    file.push(filename);
+                    self.add_or_get_image(&file, tiling, wrench)
+                }
             }
             None => {
                 warn!("No image provided for the image-mask!");
                 return None;
             }
         };
 
-        let tiling = item["tile-size"].as_i64();
-        let (image_key, image_dims) =
-            self.add_or_get_image(&file, tiling, wrench);
         let image_rect = item["rect"]
             .as_rect()
             .unwrap_or(LayoutRect::new(LayoutPoint::zero(), image_dims));
         let image_repeat = item["repeat"].as_bool().expect("Expected boolean");
         Some(ImageMask {
             image: image_key,
             rect: image_rect,
             repeat: image_repeat,
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -18,58 +18,127 @@
 #include "gc/Barrier.h"
 #include "gc/NurseryAwareHashMap.h"
 #include "js/UniquePtr.h"
 #include "vm/JSObject.h"
 #include "vm/JSScript.h"
 
 namespace js {
 
+// A key in a WrapperMap, a compartment's map from entities in other
+// compartments to the local values the compartment's own code must use to refer
+// to them.
+//
+// WrapperMaps have a complex key type because, in addition to mapping JSObjects
+// to their cross-compartment wrappers, they must also map non-atomized
+// JSStrings to their copies in the local compartment, and debuggee entities
+// (objects, scripts, etc.) to their representative objects in the Debugger API.
 class CrossCompartmentKey {
  public:
-  enum DebuggerObjectKind : uint8_t {
-    DebuggerSource,
-    DebuggerEnvironment,
-    DebuggerObject,
-    DebuggerWasmScript,
-    DebuggerWasmSource
+  // [SMDOC]: Cross-compartment wrapper map entries for Debugger API objects
+  //
+  // The Debugger API creates objects like Debugger.Object, Debugger.Script,
+  // Debugger.Environment, etc. to refer to things in the debuggee. Each
+  // Debugger gets at most one Debugger.Mumble for each referent:
+  // Debugger.Mumbles are unique per referent per Debugger.
+  //
+  // Since a Debugger and its debuggee must be in different compartments, a
+  // Debugger.Mumble's pointer to its referent is a cross-compartment edge, from
+  // the debugger's compartment into the debuggee compartment. Like any other
+  // sort of cross-compartment edge, the GC needs to be able to find all of
+  // these edges readily.
+  //
+  // Our solution is to treat Debugger.Mumble objects as wrappers stored in
+  // JSCompartment::crossCompartmentWrappers, where the GC already looks when it
+  // needs to find any other sort of cross-compartment edges. This also meshes
+  // nicely with existing sanity checks that trace the heap looking for
+  // cross-compartment edges and check that each one has an entry in the right
+  // wrapper map.
+  //
+  // That approach means that a given referent may have multiple entries in the
+  // wrapper map: its ordinary cross-compartment wrapper, and then any
+  // Debugger.Mumbles referring to it. If there are multiple Debuggers in a
+  // compartment, each needs its own Debugger.Mumble for the referent, and each
+  // of those needs its own entry in the WrapperMap. And some referents may have
+  // more than one type of Debugger.Mumble that can refer to them: for example,
+  // a WasmInstanceObject can be the referent of both a Debugger.Script and a
+  // Debugger.Source.
+  //
+  // Hence, to look up a Debugger.Mumble in the WrapperMap, we need a key that
+  // includes 1) the referent, 2) the Debugger to which the Mumble belongs, and
+  // 3) the specific type of Mumble we're looking for. Since mozilla::Variant
+  // distinguishes alternatives by type only, we include a distinct type in
+  // WrappedType for each sort of Debugger.Mumble.
+
+  // Common structure for all Debugger.Mumble keys.
+  template <typename Referent>
+  struct Debuggee {
+    Debuggee(NativeObject* debugger, Referent* referent)
+        : debugger(debugger), referent(referent) {}
+
+    bool operator==(const Debuggee& other) const {
+      return debugger == other.debugger && referent == other.referent;
+    }
+
+    bool operator!=(const Debuggee& other) const { return !(*this == other); }
+
+    NativeObject* debugger;
+    Referent* referent;
   };
-  using DebuggerAndObject =
-      mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
-  using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
-  using DebuggerAndLazyScript = mozilla::Tuple<NativeObject*, LazyScript*>;
+
+  // Key under which we find debugger's Debugger.Object referring to referent.
+  struct DebuggeeObject : Debuggee<JSObject> {
+    DebuggeeObject(NativeObject* debugger, JSObject* referent)
+        : Debuggee(debugger, referent) {}
+  };
+
+  // Keys under which we find Debugger.Scripts.
+  using DebuggeeJSScript = Debuggee<JSScript>;
+  using DebuggeeWasmScript = Debuggee<NativeObject>;  // WasmInstanceObject
+  using DebuggeeLazyScript = Debuggee<LazyScript>;
+
+  // Key under which we find debugger's Debugger.Environment referring to
+  // referent.
+  struct DebuggeeEnvironment : Debuggee<JSObject> {
+    DebuggeeEnvironment(NativeObject* debugger, JSObject* referent)
+        : Debuggee(debugger, referent) {}
+  };
+
+  // Key under which we find debugger's Debugger.Source referring to referent.
+  struct DebuggeeSource : Debuggee<NativeObject> {
+    DebuggeeSource(NativeObject* debugger, NativeObject* referent)
+        : Debuggee(debugger, referent) {}
+  };
+
   using WrappedType =
-      mozilla::Variant<JSObject*, JSString*, DebuggerAndScript,
-                       DebuggerAndLazyScript, DebuggerAndObject>;
+      mozilla::Variant<JSObject*, JSString*, DebuggeeObject, DebuggeeJSScript,
+                       DebuggeeWasmScript, DebuggeeLazyScript,
+                       DebuggeeEnvironment, DebuggeeSource>;
 
   explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) {
     MOZ_RELEASE_ASSERT(obj);
   }
   explicit CrossCompartmentKey(JSString* str) : wrapped(str) {
     MOZ_RELEASE_ASSERT(str);
   }
   explicit CrossCompartmentKey(const JS::Value& v)
       : wrapped(v.isString() ? WrappedType(v.toString())
                              : WrappedType(&v.toObject())) {}
-  explicit CrossCompartmentKey(NativeObject* debugger, JSObject* obj,
-                               DebuggerObjectKind kind)
-      : wrapped(DebuggerAndObject(debugger, obj, kind)) {
-    MOZ_RELEASE_ASSERT(debugger);
-    MOZ_RELEASE_ASSERT(obj);
-  }
-  explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script)
-      : wrapped(DebuggerAndScript(debugger, script)) {
-    MOZ_RELEASE_ASSERT(debugger);
-    MOZ_RELEASE_ASSERT(script);
-  }
-  explicit CrossCompartmentKey(NativeObject* debugger, LazyScript* lazyScript)
-      : wrapped(DebuggerAndLazyScript(debugger, lazyScript)) {
-    MOZ_RELEASE_ASSERT(debugger);
-    MOZ_RELEASE_ASSERT(lazyScript);
-  }
+
+  // For most debuggee keys, we must let the caller choose the key type
+  // themselves. But for JSScript and LazyScript, there is only one key type
+  // that makes sense, so we provide an overloaded constructor.
+  explicit CrossCompartmentKey(DebuggeeObject&& key) : wrapped(key) {}
+  explicit CrossCompartmentKey(DebuggeeSource&& key) : wrapped(key) {}
+  explicit CrossCompartmentKey(DebuggeeEnvironment&& key) : wrapped(key) {}
+  explicit CrossCompartmentKey(DebuggeeWasmScript&& key) : wrapped(key) {}
+  explicit CrossCompartmentKey(NativeObject* debugger, JSScript* referent)
+      : wrapped(DebuggeeJSScript(debugger, referent)) {}
+  explicit CrossCompartmentKey(NativeObject* debugger, LazyScript* referent)
+      : wrapped(DebuggeeLazyScript(debugger, referent)) {}
 
   bool operator==(const CrossCompartmentKey& other) const {
     return wrapped == other.wrapped;
   }
   bool operator!=(const CrossCompartmentKey& other) const {
     return wrapped != other.wrapped;
   }
 
@@ -77,73 +146,64 @@ class CrossCompartmentKey {
   bool is() const {
     return wrapped.is<T>();
   }
   template <typename T>
   const T& as() const {
     return wrapped.as<T>();
   }
 
+ private:
+  template <typename F>
+  struct ApplyToWrappedMatcher {
+    F f_;
+    explicit ApplyToWrappedMatcher(F f) : f_(f) {}
+    auto operator()(JSObject*& obj) { return f_(&obj); }
+    auto operator()(JSString*& str) { return f_(&str); }
+    template <typename Referent>
+    auto operator()(Debuggee<Referent>& dbg) {
+      return f_(&dbg.referent);
+    }
+  };
+
+  template <typename F>
+  struct ApplyToDebuggerMatcher {
+    F f_;
+    explicit ApplyToDebuggerMatcher(F f) : f_(f) {}
+
+    using ReturnType = decltype(f_(static_cast<NativeObject**>(nullptr)));
+    ReturnType operator()(JSObject*& obj) { return ReturnType(); }
+    ReturnType operator()(JSString*& str) { return ReturnType(); }
+    template <typename Referent>
+    ReturnType operator()(Debuggee<Referent>& dbg) {
+      return f_(&dbg.debugger);
+    }
+  };
+
+ public:
   template <typename F>
   auto applyToWrapped(F f) {
-    struct WrappedMatcher {
-      F f_;
-      explicit WrappedMatcher(F f) : f_(f) {}
-      auto operator()(JSObject*& obj) { return f_(&obj); }
-      auto operator()(JSString*& str) { return f_(&str); }
-      auto operator()(DebuggerAndScript& tpl) {
-        return f_(&mozilla::Get<1>(tpl));
-      }
-      auto operator()(DebuggerAndLazyScript& tpl) {
-        return f_(&mozilla::Get<1>(tpl));
-      }
-      auto operator()(DebuggerAndObject& tpl) {
-        return f_(&mozilla::Get<1>(tpl));
-      }
-    } matcher(f);
-    return wrapped.match(matcher);
+    return wrapped.match(ApplyToWrappedMatcher<F>(f));
   }
 
   template <typename F>
   auto applyToDebugger(F f) {
-    using ReturnType = decltype(f(static_cast<NativeObject**>(nullptr)));
-    struct DebuggerMatcher {
-      F f_;
-      explicit DebuggerMatcher(F f) : f_(f) {}
-      ReturnType operator()(JSObject*& obj) { return ReturnType(); }
-      ReturnType operator()(JSString*& str) { return ReturnType(); }
-      ReturnType operator()(DebuggerAndScript& tpl) {
-        return f_(&mozilla::Get<0>(tpl));
-      }
-      ReturnType operator()(DebuggerAndLazyScript& tpl) {
-        return f_(&mozilla::Get<0>(tpl));
-      }
-      ReturnType operator()(DebuggerAndObject& tpl) {
-        return f_(&mozilla::Get<0>(tpl));
-      }
-    } matcher(f);
-    return wrapped.match(matcher);
+    return wrapped.match(ApplyToDebuggerMatcher<F>(f));
   }
 
-  bool isDebuggerKey() const {
-    struct DebuggerMatcher {
-      bool operator()(JSObject* const& obj) { return false; }
-      bool operator()(JSString* const& str) { return false; }
-      bool operator()(const DebuggerAndScript& tpl) {
-        return true;
-      }
-      bool operator()(const DebuggerAndLazyScript& tpl) {
-        return true;
-      }
-      bool operator()(const DebuggerAndObject& tpl) {
-        return true;
-      }
-    } matcher;
-    return wrapped.match(matcher);
-  }
+  struct IsDebuggerKeyMatcher {
+    bool operator()(JSObject* const& obj) { return false; }
+    bool operator()(JSString* const& str) { return false; }
+    template <typename Referent>
+    bool operator()(Debuggee<Referent> const& dbg) {
+      return true;
+    }
+  };
+
+  bool isDebuggerKey() const { return wrapped.match(IsDebuggerKeyMatcher()); }
 
   JS::Compartment* compartment() {
     return applyToWrapped([](auto tp) { return (*tp)->maybeCompartment(); });
   }
 
   JS::Zone* zone() {
     return applyToWrapped([](auto tp) { return (*tp)->zone(); });
   }
@@ -151,32 +211,23 @@ class CrossCompartmentKey {
   struct Hasher : public DefaultHasher<CrossCompartmentKey> {
     struct HashFunctor {
       HashNumber operator()(JSObject* obj) {
         return DefaultHasher<JSObject*>::hash(obj);
       }
       HashNumber operator()(JSString* str) {
         return DefaultHasher<JSString*>::hash(str);
       }
-      HashNumber operator()(const DebuggerAndScript& tpl) {
-        return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
-               DefaultHasher<JSScript*>::hash(mozilla::Get<1>(tpl));
-      }
-      HashNumber operator()(const DebuggerAndLazyScript& tpl) {
-        return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
-               DefaultHasher<LazyScript*>::hash(mozilla::Get<1>(tpl));
-      }
-      HashNumber operator()(const DebuggerAndObject& tpl) {
-        return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
-               DefaultHasher<JSObject*>::hash(mozilla::Get<1>(tpl)) ^
-               (mozilla::Get<2>(tpl) << 5);
+      template <typename Referent>
+      HashNumber operator()(const Debuggee<Referent>& dbg) {
+        return mozilla::HashGeneric(dbg.debugger, dbg.referent);
       }
     };
     static HashNumber hash(const CrossCompartmentKey& key) {
-      return key.wrapped.match(HashFunctor());
+      return key.wrapped.addTagToHash(key.wrapped.match(HashFunctor()));
     }
 
     static bool match(const CrossCompartmentKey& l,
                       const CrossCompartmentKey& k) {
       return l.wrapped == k.wrapped;
     }
   };
 
@@ -185,16 +236,18 @@ class CrossCompartmentKey {
     return self->applyToWrapped([](auto tp) { return (*tp)->isTenured(); });
   }
 
   void trace(JSTracer* trc);
   bool needsSweep();
 
  private:
   CrossCompartmentKey() = delete;
+  explicit CrossCompartmentKey(WrappedType&& wrapped)
+      : wrapped(std::move(wrapped)) {}
   WrappedType wrapped;
 };
 
 // The data structure for storing CCWs, which has a map per target compartment
 // so we can access them easily. Note string CCWs are stored separately from the
 // others because they have target compartment nullptr.
 class WrapperMap {
   static const size_t InitialInnerMapSize = 4;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1269,18 +1269,18 @@ bool Debugger::wrapEnvironment(JSContext
       return false;
     }
 
     if (!p.add(cx, environments, env, envobj)) {
       NukeDebuggerWrapper(envobj);
       return false;
     }
 
-    CrossCompartmentKey key(object, env,
-                            CrossCompartmentKey::DebuggerEnvironment);
+    CrossCompartmentKey key(
+        CrossCompartmentKey::DebuggeeEnvironment(object, env));
     if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
       NukeDebuggerWrapper(envobj);
       environments.remove(env);
       return false;
     }
 
     result.set(envobj);
   }
@@ -1367,17 +1367,17 @@ bool Debugger::wrapDebuggeeObject(JSCont
     }
 
     if (!p.add(cx, objects, obj, dobj)) {
       NukeDebuggerWrapper(dobj);
       return false;
     }
 
     if (obj->compartment() != object->compartment()) {
-      CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject);
+      CrossCompartmentKey key(CrossCompartmentKey::DebuggeeObject(object, obj));
       if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
         NukeDebuggerWrapper(dobj);
         objects.remove(obj);
         ReportOutOfMemory(cx);
         return false;
       }
     }
 
@@ -6077,19 +6077,17 @@ JSObject* Debugger::wrapVariantReferent(
         cx, CrossCompartmentKey(object, untaggedReferent));
     obj =
         wrapVariantReferent<DebuggerScriptReferent, LazyScript*,
                             LazyScriptWeakMap>(cx, lazyScripts, key, referent);
   } else {
     Handle<WasmInstanceObject*> untaggedReferent =
         referent.template as<WasmInstanceObject*>();
     Rooted<CrossCompartmentKey> key(
-        cx, CrossCompartmentKey(
-                object, untaggedReferent,
-                CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmScript));
+        cx, CrossCompartmentKey::DebuggeeWasmScript(object, untaggedReferent));
     obj = wrapVariantReferent<DebuggerScriptReferent, WasmInstanceObject*,
                               WasmInstanceWeakMap>(cx, wasmInstanceScripts, key,
                                                    referent);
   }
   MOZ_ASSERT_IF(obj, GetScriptReferent(obj) == referent);
   return obj;
 }
 
@@ -8363,28 +8361,24 @@ NativeObject* Debugger::newDebuggerSourc
 
 JSObject* Debugger::wrapVariantReferent(
     JSContext* cx, Handle<DebuggerSourceReferent> referent) {
   JSObject* obj;
   if (referent.is<ScriptSourceObject*>()) {
     Handle<ScriptSourceObject*> untaggedReferent =
         referent.template as<ScriptSourceObject*>();
     Rooted<CrossCompartmentKey> key(
-        cx, CrossCompartmentKey(
-                object, untaggedReferent,
-                CrossCompartmentKey::DebuggerObjectKind::DebuggerSource));
+        cx, CrossCompartmentKey::DebuggeeSource(object, untaggedReferent));
     obj = wrapVariantReferent<DebuggerSourceReferent, ScriptSourceObject*,
                               SourceWeakMap>(cx, sources, key, referent);
   } else {
     Handle<WasmInstanceObject*> untaggedReferent =
         referent.template as<WasmInstanceObject*>();
     Rooted<CrossCompartmentKey> key(
-        cx, CrossCompartmentKey(
-                object, untaggedReferent,
-                CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmSource));
+        cx, CrossCompartmentKey::DebuggeeSource(object, untaggedReferent));
     obj = wrapVariantReferent<DebuggerSourceReferent, WasmInstanceObject*,
                               WasmInstanceWeakMap>(cx, wasmInstanceSources, key,
                                                    referent);
   }
   MOZ_ASSERT_IF(obj, GetSourceReferent(obj) == referent);
   return obj;
 }
 
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -1055,19 +1055,18 @@ NrUdpSocketIpcProxy::~NrUdpSocketIpcProx
 NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerError(const nsACString& message,
                                                      const nsACString& filename,
                                                      uint32_t line_number) {
   return socket_->CallListenerError(message, filename, line_number);
 }
 
 // callback while receiving UDP packet
 NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerReceivedData(
-    const nsACString& host, uint16_t port, const uint8_t* data,
-    uint32_t data_length) {
-  return socket_->CallListenerReceivedData(host, port, data, data_length);
+    const nsACString& host, uint16_t port, const nsTArray<uint8_t>& data) {
+  return socket_->CallListenerReceivedData(host, port, data);
 }
 
 // callback while UDP socket is opened
 NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerOpened() {
   return socket_->CallListenerOpened();
 }
 
 // callback while UDP socket is connected
@@ -1114,20 +1113,18 @@ NS_IMETHODIMP NrUdpSocketIpc::CallListen
   ReentrantMonitorAutoEnter mon(monitor_);
   err_ = true;
   monitor_.NotifyAll();
 
   return NS_OK;
 }
 
 // callback while receiving UDP packet
-NS_IMETHODIMP NrUdpSocketIpc::CallListenerReceivedData(const nsACString& host,
-                                                       uint16_t port,
-                                                       const uint8_t* data,
-                                                       uint32_t data_length) {
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerReceivedData(
+    const nsACString& host, uint16_t port, const nsTArray<uint8_t>& data) {
   ASSERT_ON_THREAD(io_thread_);
 
   PRNetAddr addr;
   memset(&addr, 0, sizeof(addr));
 
   {
     ReentrantMonitorAutoEnter mon(monitor_);
 
@@ -1142,40 +1139,30 @@ NS_IMETHODIMP NrUdpSocketIpc::CallListen
         PR_SetNetAddr(PR_IpAddrNull, addr.raw.family, port, &addr)) {
       err_ = true;
       MOZ_ASSERT(false, "Failed to set port in PRNetAddr");
       return NS_OK;
     }
   }
 
   nsAutoPtr<MediaPacket> buf(new MediaPacket);
-  buf->Copy(data, data_length);
+  buf->Copy(data.Elements(), data.Length());
   RefPtr<nr_udp_message> msg(new nr_udp_message(addr, buf));
 
   RUN_ON_THREAD(sts_thread_,
                 mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
                                       &NrUdpSocketIpc::recv_callback_s, msg),
                 NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
 nsresult NrUdpSocketIpc::SetAddress() {
-  uint16_t port;
-  if (NS_FAILED(socket_child_->GetLocalPort(&port))) {
-    err_ = true;
-    MOZ_ASSERT(false, "Failed to get local port");
-    return NS_OK;
-  }
+  uint16_t port = socket_child_->LocalPort();
 
-  nsAutoCString address;
-  if (NS_FAILED(socket_child_->GetLocalAddress(address))) {
-    err_ = true;
-    MOZ_ASSERT(false, "Failed to get local address");
-    return NS_OK;
-  }
+  nsAutoCString address(socket_child_->LocalAddress());
 
   PRNetAddr praddr;
   if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, port, &praddr)) {
     err_ = true;
     MOZ_ASSERT(false, "Failed to set port in PRNetAddr");
     return NS_OK;
   }
 
@@ -1477,17 +1464,17 @@ int NrUdpSocketIpc::accept(nr_transport_
   return R_INTERNAL;
 }
 
 // IO thread executors
 void NrUdpSocketIpc::create_i(const nsACString& host, const uint16_t port) {
   ASSERT_ON_THREAD(io_thread_);
 
   uint32_t minBuffSize = 0;
-  nsCOMPtr<nsIUDPSocketChild> socketChild = new dom::UDPSocketChild();
+  RefPtr<dom::UDPSocketChild> socketChild = new dom::UDPSocketChild();
 
   // This can spin the event loop; don't do that with the monitor held
   socketChild->SetBackgroundSpinsEvents();
 
   ReentrantMonitorAutoEnter mon(monitor_);
   if (!socket_child_) {
     socket_child_ = socketChild;
     socket_child_->SetFilterName(
@@ -1537,22 +1524,17 @@ void NrUdpSocketIpc::connect_i(const nsA
   RefPtr<NrUdpSocketIpcProxy> proxy(new NrUdpSocketIpcProxy);
   rv = proxy->Init(this);
   if (NS_FAILED(rv)) {
     err_ = true;
     mon.NotifyAll();
     return;
   }
 
-  if (NS_FAILED(socket_child_->Connect(proxy, host, port))) {
-    err_ = true;
-    MOZ_ASSERT(false, "Failed to connect UDP socket");
-    mon.NotifyAll();
-    return;
-  }
+  socket_child_->Connect(proxy, host, port);
 }
 
 void NrUdpSocketIpc::sendto_i(const net::NetAddr& addr,
                               nsAutoPtr<MediaPacket> buf) {
   ASSERT_ON_THREAD(io_thread_);
 
   ReentrantMonitorAutoEnter mon(monitor_);
 
@@ -1577,20 +1559,20 @@ void NrUdpSocketIpc::close_i() {
 }
 
 #if defined(MOZILLA_INTERNAL_API)
 
 static void ReleaseIOThread_s() { sThread->ReleaseUse(); }
 
 // close(), but transfer the socket_child_ reference to die as well
 // static
-void NrUdpSocketIpc::destroy_i(nsIUDPSocketChild* aChild,
+void NrUdpSocketIpc::destroy_i(dom::UDPSocketChild* aChild,
                                nsCOMPtr<nsIEventTarget>& aStsThread) {
-  RefPtr<nsIUDPSocketChild> socket_child_ref =
-      already_AddRefed<nsIUDPSocketChild>(aChild);
+  RefPtr<dom::UDPSocketChild> socket_child_ref =
+      already_AddRefed<dom::UDPSocketChild>(aChild);
   if (socket_child_ref) {
     socket_child_ref->Close();
   }
 
   RUN_ON_THREAD(aStsThread, WrapRunnableNM(&ReleaseIOThread_s),
                 NS_DISPATCH_NORMAL);
 }
 #endif
--- a/media/mtransport/nr_socket_prsock.h
+++ b/media/mtransport/nr_socket_prsock.h
@@ -89,16 +89,20 @@ class TCPSocketChild;
 #endif
 
 namespace mozilla {
 
 namespace net {
 union NetAddr;
 }
 
+namespace dom {
+class UDPSocketChild;
+}  // namespace dom
+
 class NrSocketBase {
  public:
   NrSocketBase() : connect_invoked_(false), poll_flags_(0) {
     memset(cbs_, 0, sizeof(cbs_));
     memset(cb_args_, 0, sizeof(cb_args_));
     memset(&my_addr_, 0, sizeof(my_addr_));
   }
   virtual ~NrSocketBase() {}
@@ -237,18 +241,17 @@ class NrSocketIpc : public NrSocketBase 
 class NrUdpSocketIpc : public NrSocketIpc {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrUdpSocketIpc, override)
 
   NS_IMETHODIMP CallListenerError(const nsACString& message,
                                   const nsACString& filename,
                                   uint32_t line_number);
   NS_IMETHODIMP CallListenerReceivedData(const nsACString& host, uint16_t port,
-                                         const uint8_t* data,
-                                         uint32_t data_length);
+                                         const nsTArray<uint8_t>& data);
   NS_IMETHODIMP CallListenerOpened();
   NS_IMETHODIMP CallListenerConnected();
   NS_IMETHODIMP CallListenerClosed();
 
   NrUdpSocketIpc();
 
   // Implementations of the NrSocketBase APIs
   virtual int create(nr_transport_addr* addr) override;
@@ -272,29 +275,30 @@ class NrUdpSocketIpc : public NrSocketIp
   nsresult SetAddress();  // Set the local address from parent info.
 
   // Main or private thread executors of the NrSocketBase APIs
   void create_i(const nsACString& host, const uint16_t port);
   void connect_i(const nsACString& host, const uint16_t port);
   void sendto_i(const net::NetAddr& addr, nsAutoPtr<MediaPacket> buf);
   void close_i();
 #if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API)
-  static void destroy_i(nsIUDPSocketChild* aChild,
+  static void destroy_i(dom::UDPSocketChild* aChild,
                         nsCOMPtr<nsIEventTarget>& aStsThread);
 #endif
   // STS thread executor
   void recv_callback_s(RefPtr<nr_udp_message> msg);
 
   ReentrantMonitor monitor_;  // protects err_and state_
   bool err_;
   NrSocketIpcState state_;
 
   std::queue<RefPtr<nr_udp_message>> received_msgs_;
 
-  RefPtr<nsIUDPSocketChild> socket_child_;  // only accessed from the io_thread
+  // only accessed from the io_thread
+  RefPtr<dom::UDPSocketChild> socket_child_;
 };
 
 // The socket child holds onto one of these, which just passes callbacks
 // through and makes sure the ref to the NrSocketIpc is released on STS.
 class NrUdpSocketIpcProxy : public nsIUDPSocketInternal {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIUDPSOCKETINTERNAL
--- a/mobile/android/gradle.py
+++ b/mobile/android/gradle.py
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import print_function
 
 import buildconfig
+import os
 import subprocess
 import sys
 
 from mozbuild.util import (
     ensureParentDir,
     lock_file,
 )
 import mozpack.path as mozpath
@@ -25,17 +26,21 @@ def android(verb, *args):
     try:
         cmd = [
             sys.executable,
             mozpath.join(buildconfig.topsrcdir, 'mach'),
             'android',
             verb,
         ]
         cmd.extend(args)
-        subprocess.check_call(cmd)
+        env = dict(os.environ)
+        # Confusingly, `MACH` is set only within `mach build`.
+        if env.get('MACH'):
+            env['GRADLE_INVOKED_WITHIN_MACH_BUILD'] = '1'
+        subprocess.check_call(cmd, env=env)
 
         return 0
     finally:
         del lock_instance
 
 
 def assemble_app(dummy_output_file, *inputs):
     return android('assemble-app')
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -275,22 +275,16 @@ VARCACHE_PREF(
 // Historical behavior is the second, the first is being discussed at:
 // https://github.com/whatwg/html/issues/3840
 VARCACHE_PREF(
   "dom.link.disabled_attribute.enabled",
    dom_link_disabled_attribute_enabled,
   bool, true
 )
 
-VARCACHE_PREF(
-  "dom.performance.enable_scheduler_timing",
-  dom_performance_enable_scheduler_timing,
-  RelaxedAtomicBool, true
-)
-
 // Should we defer timeouts and intervals while loading a page.  Released
 // on Idle or when the page is loaded.
 VARCACHE_PREF(
   "dom.timeout.defer_during_load",
   dom_timeout_defer_during_load,
   bool, true
 )
 
@@ -1793,22 +1787,16 @@ VARCACHE_PREF(
 
 // Whether DD should consider WMF-disabled a WMF failure, useful for testing.
 VARCACHE_PREF(
   "media.decoder-doctor.wmf-disabled-is-failure",
    MediaDecoderDoctorWmfDisabledIsFailure,
   bool, false
 )
 
-VARCACHE_PREF(
-  "media.wmf.vp9.enabled",
-   MediaWmfVp9Enabled,
-  RelaxedAtomicBool, true
-)
-
 #endif // MOZ_WMF
 
 // Whether to check the decoder supports recycling.
 #ifdef ANDROID
 # define PREF_VALUE true
 #else
 # define PREF_VALUE false
 #endif
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -149,19 +149,16 @@ pref("dom.serviceWorkers.idle_extended_t
 
 // The amount of time (milliseconds) an update request is delayed when triggered
 // by a service worker that doesn't control any clients.
 pref("dom.serviceWorkers.update_delay", 1000);
 
 // Enable test for 24 hours update, service workers will always treat last update check time is over 24 hours
 pref("dom.serviceWorkers.testUpdateOverOneDay", false);
 
-// Enable collecting of docgroup activity in the scheduler
-pref("dom.performance.enable_scheduler_timing", true);
-
 // Enable Permission API's .revoke() method
 pref("dom.permissions.revoke.enable", false);
 
 // Enable exposing timeToNonBlankPaint
 pref("dom.performance.time_to_non_blank_paint.enabled", false);
 
 // Enable exposing timeToContentfulPaint
 pref("dom.performance.time_to_contentful_paint.enabled", false);
@@ -2377,16 +2374,21 @@ pref("network.proxy.autoconfig_retry_int
 pref("network.proxy.enable_wpad_over_dhcp", true);
 
 // Use the HSTS preload list by default
 pref("network.stricttransportsecurity.preloadlist", true);
 
 // Use JS mDNS as a fallback
 pref("network.mdns.use_js_fallback", false);
 
+// Cache SSL resumption tokens in necko
+pref("network.ssl_tokens_cache_enabled", false);
+// Capacity of the cache in kilobytes
+pref("network.ssl_tokens_cache_capacity", 2048);
+
 pref("converter.html2txt.structs",          true); // Output structured phrases (strong, em, code, sub, sup, b, i, u)
 pref("converter.html2txt.header_strategy",  1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention
 // Whether we include ruby annotation in the text despite whether it
 // is requested. This was true because we didn't explicitly strip out
 // annotations. Set false by default to provide a better behavior, but
 // we want to be able to pref-off it if user doesn't like it.
 pref("converter.html2txt.always_include_ruby", false);
 
new file mode 100644
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.cpp
@@ -0,0 +1,253 @@
+/* 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 "SSLTokensCache.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Logging.h"
+#include "ssl.h"
+#include "sslexp.h"
+
+namespace mozilla {
+namespace net {
+
+static bool const kDefaultEnabled = false;
+Atomic<bool, Relaxed> SSLTokensCache::sEnabled(kDefaultEnabled);
+
+static uint32_t const kDefaultCapacity = 2048;  // 2MB
+Atomic<uint32_t, Relaxed> SSLTokensCache::sCapacity(kDefaultCapacity);
+
+static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
+#undef LOG
+#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
+
+class ExpirationComparator {
+ public:
+  bool Equals(SSLTokensCache::HostRecord* a,
+              SSLTokensCache::HostRecord* b) const {
+    return a->mExpirationTime == b->mExpirationTime;
+  }
+  bool LessThan(SSLTokensCache::HostRecord* a,
+                SSLTokensCache::HostRecord* b) const {
+    return a->mExpirationTime < b->mExpirationTime;
+  }
+};
+
+StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
+StaticMutex SSLTokensCache::sLock;
+
+NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
+
+// static
+nsresult SSLTokensCache::Init() {
+  StaticMutexAutoLock lock(sLock);
+
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!gInstance);
+
+  gInstance = new SSLTokensCache();
+  gInstance->InitPrefs();
+
+  RegisterWeakMemoryReporter(gInstance);
+
+  return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Shutdown() {
+  StaticMutexAutoLock lock(sLock);
+
+  if (!gInstance) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  UnregisterWeakMemoryReporter(gInstance);
+
+  gInstance = nullptr;
+
+  return NS_OK;
+}
+
+SSLTokensCache::SSLTokensCache() : mCacheSize(0) {
+  LOG(("SSLTokensCache::SSLTokensCache"));
+}
+
+SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aHost, const uint8_t* aToken,
+                             uint32_t aTokenLen) {
+  StaticMutexAutoLock lock(sLock);
+
+  LOG(("SSLTokensCache::Put [host=%s, tokenLen=%u]",
+       PromiseFlatCString(aHost).get(), aTokenLen));
+
+  if (!gInstance) {
+    LOG(("  service not initialized"));
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  PRUint32 expirationTime;
+  SSLResumptionTokenInfo tokenInfo;
+  if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
+                                 sizeof(tokenInfo)) != SECSuccess) {
+    LOG(("  cannot get expiration time from the token, NSS error %d",
+         PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+  expirationTime = tokenInfo.expirationTime;
+  SSL_DestroyResumptionTokenInfo(&tokenInfo);
+
+  HostRecord* rec = nullptr;
+
+  if (!gInstance->mHostRecs.Get(aHost, &rec)) {
+    rec = new HostRecord();
+    rec->mHost = aHost;
+    gInstance->mHostRecs.Put(aHost, rec);
+    gInstance->mExpirationArray.AppendElement(rec);
+  } else {
+    gInstance->mCacheSize -= rec->mToken.Length();
+    rec->mToken.Clear();
+  }
+
+  rec->mExpirationTime = expirationTime;
+  MOZ_ASSERT(rec->mToken.IsEmpty());
+  rec->mToken.AppendElements(aToken, aTokenLen);
+  gInstance->mCacheSize += rec->mToken.Length();
+
+  gInstance->LogStats();
+
+  gInstance->EvictIfNecessary();
+
+  return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Get(const nsACString& aHost,
+                             nsTArray<uint8_t>& aToken) {
+  StaticMutexAutoLock lock(sLock);
+
+  LOG(("SSLTokensCache::Get [host=%s]", PromiseFlatCString(aHost).get()));
+
+  if (!gInstance) {
+    LOG(("  service not initialized"));
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  HostRecord* rec = nullptr;
+
+  if (gInstance->mHostRecs.Get(aHost, &rec)) {
+    if (rec->mToken.Length()) {
+      aToken = rec->mToken;
+      return NS_OK;
+    }
+  }
+
+  LOG(("  token not found"));
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+nsresult SSLTokensCache::Remove(const nsACString& aHost) {
+  StaticMutexAutoLock lock(sLock);
+
+  LOG(("SSLTokensCache::Remove [host=%s]", PromiseFlatCString(aHost).get()));
+
+  if (!gInstance) {
+    LOG(("  service not initialized"));
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  return gInstance->RemoveLocked(aHost);
+}
+
+nsresult SSLTokensCache::RemoveLocked(const nsACString& aHost) {
+  sLock.AssertCurrentThreadOwns();
+
+  LOG(("SSLTokensCache::RemoveLocked [host=%s]",
+       PromiseFlatCString(aHost).get()));
+
+  nsAutoPtr<HostRecord> rec;
+
+  if (!mHostRecs.Remove(aHost, &rec)) {
+    LOG(("  token not found"));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mCacheSize -= rec->mToken.Length();
+
+  if (!mExpirationArray.RemoveElement(rec)) {
+    MOZ_ASSERT(false, "token not found in mExpirationArray");
+  }
+
+  LogStats();
+
+  return NS_OK;
+}
+
+void SSLTokensCache::InitPrefs() {
+  Preferences::AddAtomicBoolVarCache(
+      &sEnabled, "network.ssl_tokens_cache_enabled", kDefaultEnabled);
+  Preferences::AddAtomicUintVarCache(
+      &sCapacity, "network.ssl_tokens_cache_capacity", kDefaultCapacity);
+}
+
+void SSLTokensCache::EvictIfNecessary() {
+  uint32_t capacity = sCapacity << 10;  // kilobytes to bytes
+  if (mCacheSize <= capacity) {
+    return;
+  }
+
+  LOG(("SSLTokensCache::EvictIfNecessary - evicting"));
+
+  mExpirationArray.Sort(ExpirationComparator());
+
+  while (mCacheSize > capacity && mExpirationArray.Length() > 0) {
+    if (NS_FAILED(RemoveLocked(mExpirationArray[0]->mHost))) {
+      MOZ_ASSERT(false, "mExpirationArray and mHostRecs are out of sync!");
+      mExpirationArray.RemoveElementAt(0);
+    }
+  }
+}
+
+void SSLTokensCache::LogStats() {
+  LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
+       mExpirationArray.Length(), mCacheSize));
+}
+
+size_t SSLTokensCache::SizeOfIncludingThis(
+    mozilla::MallocSizeOf mallocSizeOf) const {
+  size_t n = mallocSizeOf(this);
+
+  n += mHostRecs.ShallowSizeOfExcludingThis(mallocSizeOf);
+  n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+  for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) {
+    n += mallocSizeOf(mExpirationArray[i]);
+    n += mExpirationArray[i]->mHost.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+    n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf);
+  }
+
+  return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf)
+
+NS_IMETHODIMP
+SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+                               nsISupports* aData, bool aAnonymize) {
+  StaticMutexAutoLock lock(sLock);
+
+  MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP,
+                     UNITS_BYTES,
+                     SizeOfIncludingThis(SSLTokensCacheMallocSizeOf),
+                     "Memory used for the SSL tokens cache.");
+
+  return NS_OK;
+}
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.h
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SSLTokensCache_h_
+#define SSLTokensCache_h_
+
+#include "nsIMemoryReporter.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace net {
+
+class SSLTokensCache : public nsIMemoryReporter {
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIMEMORYREPORTER
+
+  friend class ExpirationComparator;
+
+  static nsresult Init();
+  static nsresult Shutdown();
+
+  static bool IsEnabled() { return sEnabled; }
+
+  static nsresult Put(const nsACString& aHost, const uint8_t* aToken,
+                      uint32_t aTokenLen);
+  static nsresult Get(const nsACString& aHost, nsTArray<uint8_t>& aToken);
+  static nsresult Remove(const nsACString& aHost);
+
+ private:
+  SSLTokensCache();
+  virtual ~SSLTokensCache();
+
+  nsresult RemoveLocked(const nsACString& aHost);
+
+  void InitPrefs();
+  void EvictIfNecessary();
+  void LogStats();
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+  static mozilla::StaticRefPtr<SSLTokensCache> gInstance;
+  static StaticMutex sLock;
+
+  static Atomic<bool, Relaxed> sEnabled;
+  // Capacity of the cache in kilobytes
+  static Atomic<uint32_t, Relaxed> sCapacity;
+
+  uint32_t mCacheSize;  // Actual cache size in bytes
+
+  class HostRecord {
+   public:
+    nsCString mHost;
+    PRUint32 mExpirationTime;
+    nsTArray<uint8_t> mToken;
+  };
+
+  nsClassHashtable<nsCStringHashKey, HostRecord> mHostRecs;
+  nsTArray<HostRecord*> mExpirationArray;
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif  // SSLTokensCache_h_
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -168,16 +168,17 @@ EXPORTS.mozilla.net += [
     'MemoryDownloader.h',
     'NetworkConnectivityService.h',
     'PartiallySeekableInputStream.h',
     'Predictor.h',
     'RedirectChannelRegistrar.h',
     'ReferrerPolicy.h',
     'RequestContextService.h',
     'SimpleChannelParent.h',
+    'SSLTokensCache.h',
     'TCPFastOpen.h',
 ]
 
 UNIFIED_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
     'CaptivePortalService.cpp',
     'ChannelDiverterChild.cpp',
@@ -236,16 +237,17 @@ UNIFIED_SOURCES += [
     'PollableEvent.cpp',
     'Predictor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
     'RequestContextService.cpp',
     'SimpleBuffer.cpp',
     'SimpleChannel.cpp',
     'SimpleChannelParent.cpp',
+    'SSLTokensCache.cpp',
     'TCPFastOpenLayer.cpp',
     'ThrottleQueue.cpp',
     'Tickler.cpp',
     'TLSServerSocket.cpp',
 ]
 
 if CONFIG['FUZZING_INTERFACES'] and CONFIG['LIBFUZZER']:
     include('/tools/fuzzing/libfuzzer-flags.mozbuild')
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -50,16 +50,17 @@
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "mozilla/net/CaptivePortalService.h"
 #include "mozilla/net/NetworkConnectivityService.h"
 #include "mozilla/net/SocketProcessHost.h"
 #include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/SSLTokensCache.h"
 #include "mozilla/Unused.h"
 #include "ReferrerPolicy.h"
 #include "nsContentSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsExceptionHandler.h"
 
 namespace mozilla {
 namespace net {
@@ -224,16 +225,18 @@ static const char* gCallbackPrefsForSock
 
 nsresult nsIOService::Init() {
   // XXX hack until xpidl supports error info directly (bug 13423)
   nsCOMPtr<nsIErrorService> errorService = nsErrorService::GetOrCreate();
   MOZ_ALWAYS_TRUE(errorService);
   errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_NETWORK,
                                           NECKO_MSGS_URL);
 
+  SSLTokensCache::Init();
+
   InitializeCaptivePortalService();
 
   // setup our bad port list stuff
   for (int i = 0; gBadPortList[i]; i++)
     mRestrictedPortList.AppendElement(gBadPortList[i]);
 
   // Further modifications to the port list come from prefs
   Preferences::RegisterPrefixCallbacks(
@@ -1443,16 +1446,18 @@ nsIOService::Observe(nsISupports* subjec
 
     SetOffline(true);
 
     if (mCaptivePortalService) {
       static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
       mCaptivePortalService = nullptr;
     }
 
+    SSLTokensCache::Shutdown();
+
     DestroySocketProcess();
   } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
     OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
   } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
     // coming back alive from sleep
     // this indirection brought to you by:
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
     nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -90,26 +90,30 @@
 #include "nsQueryObject.h"
 #include "mozIThirdPartyUtil.h"
 #include "../mime/nsMIMEHeaderParamImpl.h"
 #include "nsStandardURL.h"
 #include "nsChromeProtocolHandler.h"
 #include "nsJSProtocolHandler.h"
 #include "nsDataHandler.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "nsStreamUtils.h"
+#include "nsSocketTransportService2.h"
 
 #include <limits>
 
 using namespace mozilla;
 using namespace mozilla::net;
 using mozilla::dom::BlobURLProtocolHandler;
 using mozilla::dom::ClientInfo;
 using mozilla::dom::PerformanceStorage;
 using mozilla::dom::ServiceWorkerDescriptor;
 
+#define MAX_RECURSION_COUNT 50
+
 already_AddRefed<nsIIOService> do_GetIOService(nsresult* error /* = 0 */) {
   nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
   if (error) *error = io ? NS_OK : NS_ERROR_FAILURE;
   return io.forget();
 }
 
 nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file,
                                     int32_t ioFlags /* = -1 */,
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -33,16 +33,18 @@
 #include "nsIClassInfoImpl.h"
 #include "nsURLHelper.h"
 #include "nsIDNSService.h"
 #include "nsIDNSRecord.h"
 #include "nsIDNSByTypeRecord.h"
 #include "nsICancelable.h"
 #include "TCPFastOpenLayer.h"
 #include <algorithm>
+#include "sslexp.h"
+#include "mozilla/net/SSLTokensCache.h"
 
 #include "nsPrintfCString.h"
 #include "xpcpublic.h"
 
 #if defined(FUZZING)
 #  include "FuzzyLayer.h"
 #endif
 
@@ -729,17 +731,18 @@ nsSocketTransport::nsSocketTransport()
       mKeepaliveEnabled(false),
       mKeepaliveIdleTimeS(-1),
       mKeepaliveRetryIntervalS(-1),
       mKeepaliveProbeCount(-1),
       mFastOpenCallback(nullptr),
       mFastOpenLayerHasBufferedData(false),
       mFastOpenStatus(TFO_NOT_SET),
       mFirstRetryError(NS_OK),
-      mDoNotRetryToConnect(false) {
+      mDoNotRetryToConnect(false),
+      mSSLCallbackSet(false) {
   this->mNetAddr.raw.family = 0;
   this->mNetAddr.inet = {};
   this->mSelfAddr.raw.family = 0;
   this->mSelfAddr.inet = {};
   SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
 
   mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX;     // no timeout
   mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX;  // no timeout
@@ -1241,16 +1244,32 @@ nsresult nsSocketTransport::BuildSocket(
             fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
       }
     }
   }
 
   return rv;
 }
 
+// static
+SECStatus nsSocketTransport::StoreResumptionToken(
+    PRFileDesc* fd, const PRUint8* resumptionToken, unsigned int len,
+    void* ctx) {
+  PRIntn val;
+  if (SSL_OptionGet(fd, SSL_ENABLE_SESSION_TICKETS, &val) != SECSuccess ||
+      val == 0) {
+    return SECFailure;
+  }
+
+  SSLTokensCache::Put(static_cast<nsSocketTransport*>(ctx)->mHost,
+                      resumptionToken, len);
+
+  return SECSuccess;
+}
+
 nsresult nsSocketTransport::InitiateSocket() {
   SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
 
   nsresult rv;
   bool isLocal;
   IsLocal(&isLocal);
 
   if (gIOService->IsNetTearingDown()) {
@@ -1525,16 +1544,33 @@ nsresult nsSocketTransport::InitiateSock
       tfo = true;
       SOCKET_LOG(
           ("nsSocketTransport::InitiateSocket TCP Fast Open "
            "started [this=%p]\n",
            this));
     }
   }
 
+  if (usingSSL && SSLTokensCache::IsEnabled()) {
+    nsTArray<uint8_t> token;
+    nsresult rv2 = SSLTokensCache::Get(mHost, token);
+    if (NS_SUCCEEDED(rv2) && token.Length() != 0) {
+      SECStatus srv =
+          SSL_SetResumptionToken(fd, token.Elements(), token.Length());
+      if (srv == SECFailure) {
+        SOCKET_LOG(("Setting token failed with NSS error %d [host=%s]",
+                    PORT_GetError(), PromiseFlatCString(mHost).get()));
+        SSLTokensCache::Remove(mHost);
+      }
+    }
+
+    SSL_SetResumptionTokenCallback(fd, &StoreResumptionToken, this);
+    mSSLCallbackSet = true;
+  }
+
   bool connectCalled = true;  // This is only needed for telemetry.
   status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
   PRErrorCode code = PR_GetError();
   if (status == PR_SUCCESS) {
     PR_SetFDInheritable(fd, false);
   }
   if ((status == PR_SUCCESS) && tfo) {
     {
@@ -2011,16 +2047,21 @@ void STS_PRCloseOnSocketTransport(PRFile
 }
 
 void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) {
   mLock.AssertCurrentThreadOwns();
 
   NS_ASSERTION(mFD == fd, "wrong fd");
 
   if (--mFDref == 0) {
+    if (mSSLCallbackSet) {
+      SSL_SetResumptionTokenCallback(fd, nullptr, nullptr);
+      mSSLCallbackSet = false;
+    }
+
     if (gIOService->IsNetTearingDown() &&
         ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
          gSocketTransportService->MaxTimeForPrClosePref())) {
       // If shutdown last to long, let the socket leak and do not close it.
       SOCKET_LOG(("Intentional leak"));
     } else {
       if (mLingerPolarity || mLingerTimeout) {
         PRSocketOptionData socket_linger;
--- a/netwerk/base/nsSocketTransport2.h
+++ b/netwerk/base/nsSocketTransport2.h
@@ -22,16 +22,17 @@
 #include "nsIClassInfo.h"
 #include "TCPFastOpen.h"
 #include "mozilla/net/DNS.h"
 #include "nsASocketHandler.h"
 #include "mozilla/Telemetry.h"
 
 #include "prerror.h"
 #include "nsAutoPtr.h"
+#include "ssl.h"
 
 class nsICancelable;
 class nsIDNSRecord;
 class nsIInterfaceRequestor;
 
 //-----------------------------------------------------------------------------
 
 // after this short interval, we will return to PR_Poll
@@ -171,16 +172,20 @@ class nsSocketTransport final : public n
       Telemetry::HistogramID aIDConnectivityChange,
       Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline);
 
  protected:
   virtual ~nsSocketTransport();
   void CleanupTypes();
 
  private:
+  static SECStatus StoreResumptionToken(PRFileDesc* fd,
+                                        const PRUint8* resumptionToken,
+                                        unsigned int len, void* ctx);
+
   // event types
   enum {
     MSG_ENSURE_CONNECT,
     MSG_DNS_LOOKUP_COMPLETE,
     MSG_RETRY_INIT_SOCKET,
     MSG_TIMEOUT_CHANGED,
     MSG_INPUT_CLOSED,
     MSG_INPUT_PENDING,
@@ -468,14 +473,19 @@ class nsSocketTransport final : public n
 
   // A Fast Open callback.
   TCPFastOpen* mFastOpenCallback;
   bool mFastOpenLayerHasBufferedData;
   uint8_t mFastOpenStatus;
   nsresult mFirstRetryError;
 
   bool mDoNotRetryToConnect;
+
+  // True if SSL_SetResumptionTokenCallback was called. We need to clear the
+  // callback when mFD is nulled out to make sure the ssl layer cannot call
+  // the callback after nsSocketTransport is destroyed.
+  bool mSSLCallbackSet;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // !nsSocketTransport_h__
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -3915,31 +3915,31 @@ void CacheIndex::UpdateTotalBytesWritten
   index->mTotalBytesWritten += aBytesWritten;
 
   // Do telemetry report if enough data has been written and the index is
   // in READY state. The data is available also in WRITING state, but we would
   // need to deal with pending updates.
   if (index->mTotalBytesWritten >= kTelemetryReportBytesLimit &&
       index->mState == READY && !index->mIndexNeedsUpdate &&
       !index->mShuttingDown) {
-    index->DoBaseDomainAccessTelemetryReport();
+    index->DoTelemetryReport();
 
     index->mTotalBytesWritten = 0;
     CacheObserver::SetCacheAmountWritten(0);
     return;
   }
 
   uint64_t writtenKB = index->mTotalBytesWritten >> 10;
   // Store number of written kilobytes to prefs after writing at least 10MB.
   if ((writtenKB - CacheObserver::CacheAmountWritten()) > (10 * 1024)) {
     CacheObserver::SetCacheAmountWritten(writtenKB);
   }
 }
 
-void CacheIndex::DoBaseDomainAccessTelemetryReport() {
+void CacheIndex::DoTelemetryReport() {
   static const nsLiteralCString
       contentTypeNames[nsICacheEntry::CONTENT_TYPE_LAST] = {
           NS_LITERAL_CSTRING("UNKNOWN"),    NS_LITERAL_CSTRING("OTHER"),
           NS_LITERAL_CSTRING("JAVASCRIPT"), NS_LITERAL_CSTRING("IMAGE"),
           NS_LITERAL_CSTRING("MEDIA"),      NS_LITERAL_CSTRING("STYLESHEET"),
           NS_LITERAL_CSTRING("WASM")};
 
   // size in kB of all entries
@@ -4016,18 +4016,41 @@ void CacheIndex::DoBaseDomainAccessTelem
 
     if (countByType[i] > 0) {
       Telemetry::Accumulate(
           Telemetry::NETWORK_CACHE_ISOLATION_ENTRY_COUNT_INCREASE,
           contentTypeNames[i],
           round(static_cast<double>(countIncByType[i]) * 100.0 /
                 static_cast<double>(countByType[i])));
     }
+
+    if (size > 0) {
+      Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_SHARE,
+                            contentTypeNames[i],
+                            round(static_cast<double>(sizeByType[i]) * 100.0 /
+                                  static_cast<double>(size)));
+    }
+
+    if (count > 0) {
+      Telemetry::Accumulate(Telemetry::NETWORK_CACHE_ENTRY_COUNT_SHARE,
+                            contentTypeNames[i],
+                            round(static_cast<double>(countByType[i]) * 100.0 /
+                                  static_cast<double>(count)));
+    }
   }
 
+  nsCString probeKey;
+  if (CacheObserver::SmartCacheSizeEnabled()) {
+    probeKey = NS_LITERAL_CSTRING("SMARTSIZE");
+  } else {
+    probeKey = NS_LITERAL_CSTRING("USERDEFINEDSIZE");
+  }
+  Telemetry::Accumulate(Telemetry::NETWORK_CACHE_ENTRY_COUNT, probeKey, count);
+  Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE, probeKey, size >> 10);
+
   // Change telemetry report ID. This will invalidate eTLD+1 access data stored
   // in all cache entries.
   CacheObserver::SetTelemetryReportID(CacheObserver::TelemetryReportID() + 1);
 }
 
 // static
 void CacheIndex::OnAsyncEviction(bool aEvicting) {
   StaticMutexAutoLock lock(sLock);
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -1009,19 +1009,20 @@ class CacheIndex final : public CacheFil
   void ReplaceRecordInIterators(CacheIndexRecord* aOldRecord,
                                 CacheIndexRecord* aNewRecord);
 
   // Memory reporting (private part)
   size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
 
   void ReportHashStats();
 
-  // Reports first party cache isolation telemetry, clears data stored in the
-  // index entries and bumps a telemetry report ID.
-  void DoBaseDomainAccessTelemetryReport();
+  // Reports telemetry about cache, i.e. size, entry count, content type stats
+  // and first party cache isolation stats. Clears first party cache isolation
+  // counters stored in the index entries and bumps a telemetry report ID.
+  void DoTelemetryReport();
 
   static mozilla::StaticRefPtr<CacheIndex> gInstance;
   static StaticMutex sLock;
 
   nsCOMPtr<nsIFile> mCacheDirectory;
 
   EState mState;
   // Timestamp of time when the index was initialized. We use it to delay
--- a/netwerk/cache2/CacheObserver.cpp
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -48,17 +48,18 @@ static uint32_t const kDefaultDiskFreeSp
 uint32_t CacheObserver::sDiskFreeSpaceSoftLimit =
     kDefaultDiskFreeSpaceSoftLimit;
 
 static uint32_t const kDefaultDiskFreeSpaceHardLimit = 1024;  // 1MB
 uint32_t CacheObserver::sDiskFreeSpaceHardLimit =
     kDefaultDiskFreeSpaceHardLimit;
 
 static bool const kDefaultSmartCacheSizeEnabled = false;
-bool CacheObserver::sSmartCacheSizeEnabled = kDefaultSmartCacheSizeEnabled;
+Atomic<bool, Relaxed> CacheObserver::sSmartCacheSizeEnabled(
+    kDefaultSmartCacheSizeEnabled);
 
 static uint32_t const kDefaultPreloadChunkCount = 4;
 uint32_t CacheObserver::sPreloadChunkCount = kDefaultPreloadChunkCount;
 
 static int32_t const kDefaultMaxMemoryEntrySize = 4 * 1024;  // 4 MB
 int32_t CacheObserver::sMaxMemoryEntrySize = kDefaultMaxMemoryEntrySize;
 
 static int32_t const kDefaultMaxDiskEntrySize = 50 * 1024;  // 50 MB
@@ -152,19 +153,19 @@ void CacheObserver::AttachToPreferences(
 
   mozilla::Preferences::AddUintVarCache(
       &sMetadataMemoryLimit, "browser.cache.disk.metadata_memory_limit",
       kDefaultMetadataMemoryLimit);
 
   mozilla::Preferences::AddAtomicUintVarCache(&sDiskCacheCapacity,
                                               "browser.cache.disk.capacity",
                                               kDefaultDiskCacheCapacity);
-  mozilla::Preferences::AddBoolVarCache(&sSmartCacheSizeEnabled,
-                                        "browser.cache.disk.smart_size.enabled",
-                                        kDefaultSmartCacheSizeEnabled);
+  mozilla::Preferences::AddAtomicBoolVarCache(
+      &sSmartCacheSizeEnabled, "browser.cache.disk.smart_size.enabled",
+      kDefaultSmartCacheSizeEnabled);
   mozilla::Preferences::AddIntVarCache(&sMemoryCacheCapacity,
                                        "browser.cache.memory.capacity",
                                        kDefaultMemoryCacheCapacity);
 
   mozilla::Preferences::AddUintVarCache(
       &sDiskFreeSpaceSoftLimit, "browser.cache.disk.free_space_soft_limit",
       kDefaultDiskFreeSpaceSoftLimit);
   mozilla::Preferences::AddUintVarCache(
--- a/netwerk/cache2/CacheObserver.h
+++ b/netwerk/cache2/CacheObserver.h
@@ -101,17 +101,17 @@ class CacheObserver : public nsIObserver
   static bool sUseMemoryCache;
   static bool sUseDiskCache;
   static uint32_t sMetadataMemoryLimit;
   static int32_t sMemoryCacheCapacity;
   static int32_t sAutoMemoryCacheCapacity;
   static Atomic<uint32_t, Relaxed> sDiskCacheCapacity;
   static uint32_t sDiskFreeSpaceSoftLimit;
   static uint32_t sDiskFreeSpaceHardLimit;
-  static bool sSmartCacheSizeEnabled;
+  static Atomic<bool, Relaxed> sSmartCacheSizeEnabled;
   static uint32_t sPreloadChunkCount;
   static int32_t sMaxMemoryEntrySize;
   static int32_t sMaxDiskEntrySize;
   static uint32_t sMaxDiskChunksMemoryUsage;
   static uint32_t sMaxDiskPriorityChunksMemoryUsage;
   static uint32_t sCompressionLevel;
   static float sHalfLifeHours;
   static bool sSanitizeOnShutdown;
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -294,17 +294,18 @@ DataChannelConnection::DataChannelConnec
                                              MediaTransportHandler* aHandler)
     : NeckoTargetHolder(aTarget),
       mLock("netwerk::sctp::DataChannelConnection"),
       mSendInterleaved(false),
       mPpidFragmentation(false),
       mMaxMessageSizeSet(false),
       mMaxMessageSize(0),
       mAllocateEven(false),
-      mTransportHandler(aHandler) {
+      mTransportHandler(aHandler),
+      mDeferSend(false) {
   mCurrentStream = 0;
   mState = CLOSED;
   mSocket = nullptr;
   mMasterSocket = nullptr;
   mListener = listener;
   mLocalPort = 0;
   mRemotePort = 0;
   mPendingType = PENDING_NONE;
@@ -839,21 +840,26 @@ void DataChannelConnection::SctpDtlsInpu
       usrsctp_freedumpbuffer(buf);
     }
   }
   // Pass the data to SCTP
   MutexAutoLock lock(mLock);
   usrsctp_conninput(static_cast<void*>(this), packet.data(), packet.len(), 0);
 }
 
-void DataChannelConnection::SendPacket(nsAutoPtr<MediaPacket> packet) {
-  // LOG(("%p: SCTP/DTLS sent %ld bytes", this, len));
-  if (!mTransportId.empty() && mTransportHandler) {
-    mTransportHandler->SendPacket(mTransportId, std::move(*packet));
-  }
+void DataChannelConnection::SendPacket(std::unique_ptr<MediaPacket>&& packet) {
+  mSTS->Dispatch(NS_NewRunnableFunction(
+      "DataChannelConnection::SendPacket",
+      [this, self = RefPtr<DataChannelConnection>(this),
+       packet = std::move(packet)]() mutable {
+        // LOG(("%p: SCTP/DTLS sent %ld bytes", this, len));
+        if (!mTransportId.empty() && mTransportHandler) {
+          mTransportHandler->SendPacket(mTransportId, std::move(*packet));
+        }
+      }));
 }
 
 /* static */
 int DataChannelConnection::SctpDtlsOutput(void* addr, void* buffer,
                                           size_t length, uint8_t tos,
                                           uint8_t set_df) {
   DataChannelConnection* peer = static_cast<DataChannelConnection*>(addr);
   MOZ_DIAGNOSTIC_ASSERT(!peer->mShutdown);
@@ -868,27 +874,26 @@ int DataChannelConnection::SctpDtlsOutpu
     }
   }
 
   // We're async proxying even if on the STSThread because this is called
   // with internal SCTP locks held in some cases (such as in usrsctp_connect()).
   // SCTP has an option for Apple, on IP connections only, to release at least
   // one of the locks before calling a packet output routine; with changes to
   // the underlying SCTP stack this might remove the need to use an async proxy.
-  nsAutoPtr<MediaPacket> packet(new MediaPacket);
+  std::unique_ptr<MediaPacket> packet(new MediaPacket);
   packet->SetType(MediaPacket::SCTP);
   packet->Copy(static_cast<const uint8_t*>(buffer), length);
 
-  // XXX It might be worthwhile to add an assertion against the thread
-  // somehow getting into the DataChannel/SCTP code again, as
-  // DISPATCH_SYNC is not fully blocking.  This may be tricky, as it
-  // needs to be a per-thread check, not a global.
-  peer->mSTS->Dispatch(WrapRunnable(RefPtr<DataChannelConnection>(peer),
-                                    &DataChannelConnection::SendPacket, packet),
-                       NS_DISPATCH_NORMAL);
+  if (NS_IsMainThread() && peer->mDeferSend) {
+    peer->mDeferredSend.emplace_back(std::move(packet));
+    return 0;
+  }
+
+  peer->SendPacket(std::move(packet));
   return 0;  // cheat!  Packets can always be dropped later anyways
 }
 #endif
 
 #ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT
 // listen for incoming associations
 // Blocks! - Don't call this from main thread!
 
@@ -1140,17 +1145,17 @@ int DataChannelConnection::SendControlMe
   // Note: Main-thread IO, but doesn't block
 #if (UINT32_MAX > SIZE_MAX)
   if (len > SIZE_MAX) {
     return EMSGSIZE;
   }
 #endif
   OutgoingMsg msg(info, data, (size_t)len);
   bool buffered;
-  int error = SendMsgInternalOrBuffer(mBufferedControl, msg, buffered);
+  int error = SendMsgInternalOrBuffer(mBufferedControl, msg, buffered, nullptr);
 
   // Set pending type (if buffered)
   if (!error && buffered && !mPendingType) {
     mPendingType = PENDING_DCEP;
   }
   return error;
 }
 
@@ -1235,17 +1240,17 @@ bool DataChannelConnection::SendDeferred
   }
 
   // Send pending control messages
   // Note: If ndata is not active, check if DCEP messages are currently
   // outstanding. These need to
   //       be sent first before other streams can be used for sending.
   if (!mBufferedControl.IsEmpty() &&
       (mSendInterleaved || mPendingType == PENDING_DCEP)) {
-    if (SendBufferedMessages(mBufferedControl)) {
+    if (SendBufferedMessages(mBufferedControl, nullptr)) {
       return true;
     }
 
     // Note: There may or may not be pending data messages
     mPendingType = PENDING_DATA;
   }
 
   bool blocked = false;
@@ -1260,45 +1265,35 @@ bool DataChannelConnection::SendDeferred
 
     // Clear if closing/closed
     if (channel->mState == CLOSED || channel->mState == CLOSING) {
       channel->mBufferedData.Clear();
       i = UpdateCurrentStreamIndex();
       continue;
     }
 
-    size_t bufferedAmount = channel->GetBufferedAmountLocked();
-    size_t threshold = channel->mBufferedThreshold;
-    bool wasOverThreshold = bufferedAmount >= threshold;
-
     // Send buffered data messages
     // Warning: This will fail in case ndata is inactive and a previously
     //          deallocated data channel has not been closed properly. If you
     //          ever see that no messages can be sent on any channel, this is
     //          likely the cause (an explicit EOR message partially sent whose
     //          remaining chunks are still being waited for).
-    blocked = SendBufferedMessages(channel->mBufferedData);
-    bufferedAmount = channel->GetBufferedAmountLocked();
-
-    // can never fire with default threshold of 0
-    if (wasOverThreshold && bufferedAmount < threshold) {
-      LOG(("%s: sending BUFFER_LOW_THRESHOLD for %s/%s: %u", __FUNCTION__,
-           channel->mLabel.get(), channel->mProtocol.get(), channel->mStream));
-      Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
-          DataChannelOnMessageAvailable::BUFFER_LOW_THRESHOLD, this, channel)));
+    size_t written = 0;
+    mDeferSend = true;
+    blocked = SendBufferedMessages(channel->mBufferedData, &written);
+    mDeferSend = false;
+    if (written) {
+      channel->DecrementBufferedAmount(written);
     }
 
-    if (bufferedAmount == 0) {
-      // buffered-to-not-buffered transition; tell the DOM code in case this
-      // makes it available for GC
-      LOG(("%s: sending NO_LONGER_BUFFERED for %s/%s: %u", __FUNCTION__,
-           channel->mLabel.get(), channel->mProtocol.get(), channel->mStream));
-      Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
-          DataChannelOnMessageAvailable::NO_LONGER_BUFFERED, this, channel)));
+    for (auto&& packet : mDeferredSend) {
+      MOZ_ASSERT(written);
+      SendPacket(std::move(packet));
     }
+    mDeferredSend.clear();
 
     // Update current stream index
     // Note: If ndata is not active, the outstanding data messages on this
     //       stream need to be sent first before other streams can be used for
     //       sending.
     if (mSendInterleaved || !blocked) {
       i = UpdateCurrentStreamIndex();
     }
@@ -1309,20 +1304,20 @@ bool DataChannelConnection::SendDeferred
   }
   return blocked;
 }
 
 // Called with mLock locked!
 // buffer MUST have at least one item!
 // returns if we're still blocked (true)
 bool DataChannelConnection::SendBufferedMessages(
-    nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer) {
+    nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer, size_t* aWritten) {
   do {
     // Re-send message
-    int error = SendMsgInternal(*buffer[0]);
+    int error = SendMsgInternal(*buffer[0], aWritten);
     switch (error) {
       case 0:
         buffer.RemoveElementAt(0);
         break;
       case EAGAIN:
 #if (EAGAIN != EWOULDBLOCK)
       case EWOULDBLOCK:
 #endif
@@ -1339,17 +1334,16 @@ bool DataChannelConnection::SendBuffered
 
 // Caller must ensure that length <= SIZE_MAX
 void DataChannelConnection::HandleOpenRequestMessage(
     const struct rtcweb_datachannel_open_request* req, uint32_t length,
     uint16_t stream) {
   RefPtr<DataChannel> channel;
   uint32_t prValue;
   uint16_t prPolicy;
-  uint32_t flags;
 
   mLock.AssertCurrentThreadOwns();
 
   const size_t requiredLength = (sizeof(*req) - 1) + ntohs(req->label_length) +
                                 ntohs(req->protocol_length);
   if (((size_t)length) != requiredLength) {
     LOG(("%s: Inconsistent length: %u, should be %zu", __FUNCTION__, length,
          requiredLength));
@@ -1373,37 +1367,35 @@ void DataChannelConnection::HandleOpenRe
       prPolicy = SCTP_PR_SCTP_TTL;
       break;
     default:
       LOG(("Unknown channel type %d", req->channel_type));
       /* XXX error handling */
       return;
   }
   prValue = ntohl(req->reliability_param);
-  flags =
-      (req->channel_type & 0x80) ? DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED : 0;
+  bool ordered = !(req->channel_type & 0x80);
 
   if ((channel = FindChannelByStream(stream))) {
     if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
       LOG(
           ("ERROR: HandleOpenRequestMessage: channel for stream %u is in state "
            "%d instead of CLOSED.",
            stream, channel->mState));
       /* XXX: some error handling */
     } else {
       LOG(("Open for externally negotiated channel %u", stream));
       // XXX should also check protocol, maybe label
       if (prPolicy != channel->mPrPolicy || prValue != channel->mPrValue ||
-          flags !=
-              (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED)) {
+          ordered != channel->mOrdered) {
         LOG(
             ("WARNING: external negotiation mismatch with OpenRequest:"
-             "channel %u, policy %u/%u, value %u/%u, flags %x/%x",
+             "channel %u, policy %u/%u, value %u/%u, ordered %d/%d",
              stream, prPolicy, channel->mPrPolicy, prValue, channel->mPrValue,
-             flags, channel->mFlags));
+             static_cast<int>(ordered), static_cast<int>(channel->mOrdered)));
       }
     }
     return;
   }
   if (stream >= mStreams.Length()) {
     LOG(("%s: stream %u out of bounds (%zu)", __FUNCTION__, stream,
          mStreams.Length()));
     return;
@@ -1411,17 +1403,17 @@ void DataChannelConnection::HandleOpenRe
 
   nsCString label(
       nsDependentCSubstring(&req->label[0], ntohs(req->label_length)));
   nsCString protocol(nsDependentCSubstring(
       &req->label[ntohs(req->label_length)], ntohs(req->protocol_length)));
 
   channel =
       new DataChannel(this, stream, DataChannel::CONNECTING, label, protocol,
-                      prPolicy, prValue, flags, nullptr, nullptr);
+                      prPolicy, prValue, ordered, false, nullptr, nullptr);
   mStreams[stream] = channel;
 
   channel->mState = DataChannel::WAITING_TO_OPEN;
 
   LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u (state %u)", __FUNCTION__,
        channel->mLabel.get(), channel->mProtocol.get(), stream,
        channel->mState));
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
@@ -2341,17 +2333,16 @@ already_AddRefed<DataChannel> DataChanne
     const nsACString& label, const nsACString& protocol, Type type,
     bool inOrder, uint32_t prValue, DataChannelListener* aListener,
     nsISupports* aContext, bool aExternalNegotiated, uint16_t aStream) {
   if (!aExternalNegotiated) {
     // aStream == INVALID_STREAM to have the protocol allocate
     aStream = INVALID_STREAM;
   }
   uint16_t prPolicy = SCTP_PR_SCTP_NONE;
-  uint32_t flags;
 
   LOG(
       ("DC Open: label %s/%s, type %u, inorder %d, prValue %u, listener %p, "
        "context %p, external: %s, stream %u",
        PromiseFlatCString(label).get(), PromiseFlatCString(protocol).get(),
        type, inOrder, prValue, aListener, aContext,
        aExternalNegotiated ? "true" : "false", aStream));
   switch (type) {
@@ -2377,24 +2368,19 @@ already_AddRefed<DataChannel> DataChanne
   if (aStream != INVALID_STREAM && aStream < mStreams.Length() &&
       mStreams[aStream]) {
     LOG(("ERROR: external negotiation of already-open channel %u", aStream));
     // XXX How do we indicate this up to the application?  Probably the
     // caller's job, but we may need to return an error code.
     return nullptr;
   }
 
-  flags = !inOrder ? DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED : 0;
-
-  RefPtr<DataChannel> channel(
-      new DataChannel(this, aStream, DataChannel::CONNECTING, label, protocol,
-                      prPolicy, prValue, flags, aListener, aContext));
-  if (aExternalNegotiated) {
-    channel->mFlags |= DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED;
-  }
+  RefPtr<DataChannel> channel(new DataChannel(
+      this, aStream, DataChannel::CONNECTING, label, protocol, prPolicy,
+      prValue, inOrder, aExternalNegotiated, aListener, aContext));
 
   MutexAutoLock lock(mLock);  // OpenFinish assumes this
   return OpenFinish(channel.forget());
 }
 
 // Separate routine so we can also call it to finish up from pending opens
 already_AddRefed<DataChannel> DataChannelConnection::OpenFinish(
     already_AddRefed<DataChannel>&& aChannel) {
@@ -2556,17 +2542,17 @@ request_error_cleanup:
   // we'll be destroying the channel, but it never really got set up
   // Alternative would be to RUN_ON_THREAD(channel.forget(),::Destroy,...) and
   // Dispatch it to ourselves
   return nullptr;
 }
 
 // Requires mLock to be locked!
 // Returns a POSIX error code directly instead of setting errno.
-int DataChannelConnection::SendMsgInternal(OutgoingMsg& msg) {
+int DataChannelConnection::SendMsgInternal(OutgoingMsg& msg, size_t* aWritten) {
   auto& info = msg.GetInfo().sendv_sndinfo;
   int error;
 
   // EOR set?
   bool eor_set = info.snd_flags & SCTP_EOR ? true : false;
 
   // Send until buffer is empty
   size_t left = msg.GetLeft();
@@ -2590,20 +2576,25 @@ int DataChannelConnection::SendMsgIntern
 
     // Send (or try at least)
     // SCTP will return EMSGSIZE if the message is bigger than the buffer
     // size (or EAGAIN if there isn't space). However, we can avoid EMSGSIZE
     // by carefully crafting small enough message chunks.
     ssize_t written = usrsctp_sendv(
         mSocket, msg.GetData(), length, nullptr, 0, (void*)&msg.GetInfo(),
         (socklen_t)sizeof(struct sctp_sendv_spa), SCTP_SENDV_SPA, 0);
+
     if (written < 0) {
       error = errno;
       goto out;
     }
+
+    if (aWritten) {
+      *aWritten += written;
+    }
     LOG(("Sent buffer (written=%zu, len=%zu, left=%zu)", (size_t)written,
          length, left - (size_t)written));
 
     // TODO: Remove once resolved
     // (https://github.com/sctplab/usrsctp/issues/132)
     if (written == 0) {
       LOG(("@tuexen: usrsctp_sendv returned 0"));
       error = EAGAIN;
@@ -2637,17 +2628,17 @@ out:
   return error;
 }
 
 // Requires mLock to be locked!
 // Returns a POSIX error code directly instead of setting errno.
 // IMPORTANT: Ensure that the buffer passed is guarded by mLock!
 int DataChannelConnection::SendMsgInternalOrBuffer(
     nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer, OutgoingMsg& msg,
-    bool& buffered) {
+    bool& buffered, size_t* aWritten) {
   NS_WARNING_ASSERTION(msg.GetLength() > 0, "Length is 0?!");
 
   int error = 0;
   bool need_buffering = false;
 
   // Note: Main-thread IO, but doesn't block!
   // XXX FIX!  to deal with heavy overruns of JS trying to pass data in
   // (more than the buffersize) queue data onto another thread to do the
@@ -2661,17 +2652,17 @@ int DataChannelConnection::SendMsgIntern
   // queue - which would sit there.  Also, if we later send more data, it
   // would arrive ahead of the buffered message, but if the buffer ever
   // got to 1/2 full, the message would get sent - but at a semi-random
   // time, after other data it was supposed to be in front of.
 
   // Must lock before empty check for similar reasons!
   mLock.AssertCurrentThreadOwns();
   if (buffer.IsEmpty() && (mSendInterleaved || !mPendingType)) {
-    error = SendMsgInternal(msg);
+    error = SendMsgInternal(msg, aWritten);
     switch (error) {
       case 0:
         break;
       case EAGAIN:
 #if (EAGAIN != EWOULDBLOCK)
       case EWOULDBLOCK:
 #endif
         need_buffering = true;
@@ -2734,17 +2725,30 @@ int DataChannelConnection::SendDataMsgIn
     info.sendv_prinfo.pr_value = channel.mPrValue;
     info.sendv_flags |= SCTP_SEND_PRINFO_VALID;
   }
 
   // Create message instance and send
   OutgoingMsg msg(info, data, len);
   MutexAutoLock lock(mLock);
   bool buffered;
-  int error = SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered);
+  size_t written = 0;
+  mDeferSend = true;
+  int error =
+      SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered, &written);
+  mDeferSend = false;
+  if (written) {
+    channel.DecrementBufferedAmount(written);
+  }
+
+  for (auto&& packet : mDeferredSend) {
+    MOZ_ASSERT(written);
+    SendPacket(std::move(packet));
+  }
+  mDeferredSend.clear();
 
   // Set pending type and stream index (if buffered)
   if (!error && buffered && !mPendingType) {
     mPendingType = PENDING_DATA;
     mCurrentStream = channel.mStream;
   }
   return error;
 }
@@ -3122,38 +3126,96 @@ void DataChannel::SendErrnoToErrorResult
       aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
       break;
     default:
       aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
       break;
   }
 }
 
+void DataChannel::IncrementBufferedAmount(uint32_t aSize, ErrorResult& aRv) {
+  ASSERT_WEBRTC(NS_IsMainThread());
+  if (mBufferedAmount > UINT32_MAX - aSize) {
+    aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+    return;
+  }
+
+  mBufferedAmount += aSize;
+}
+
+void DataChannel::DecrementBufferedAmount(uint32_t aSize) {
+  mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
+      "DataChannel::DecrementBufferedAmount",
+      [this, self = RefPtr<DataChannel>(this), aSize] {
+        MOZ_ASSERT(aSize <= mBufferedAmount);
+        bool wasLow = mBufferedAmount <= mBufferedThreshold;
+        mBufferedAmount -= aSize;
+        if (!wasLow && mBufferedAmount <= mBufferedThreshold) {
+          LOG(("%s: sending BUFFER_LOW_THRESHOLD for %s/%s: %u", __FUNCTION__,
+               mLabel.get(), mProtocol.get(), mStream));
+          mListener->OnBufferLow(mContext);
+        }
+        if (mBufferedAmount == 0) {
+          LOG(("%s: sending NO_LONGER_BUFFERED for %s/%s: %u", __FUNCTION__,
+               mLabel.get(), mProtocol.get(), mStream));
+          mListener->NotBuffered(mContext);
+        }
+      }));
+}
+
 void DataChannel::SendMsg(const nsACString& aMsg, ErrorResult& aRv) {
   if (!EnsureValidStream(aRv)) {
     return;
   }
 
   SendErrnoToErrorResult(mConnection->SendMsg(mStream, aMsg), aRv);
+  if (!aRv.Failed()) {
+    IncrementBufferedAmount(aMsg.Length(), aRv);
+  }
 }
 
 void DataChannel::SendBinaryMsg(const nsACString& aMsg, ErrorResult& aRv) {
   if (!EnsureValidStream(aRv)) {
     return;
   }
 
   SendErrnoToErrorResult(mConnection->SendBinaryMsg(mStream, aMsg), aRv);
+  if (!aRv.Failed()) {
+    IncrementBufferedAmount(aMsg.Length(), aRv);
+  }
 }
 
-void DataChannel::SendBinaryStream(nsIInputStream* aBlob, ErrorResult& aRv) {
+void DataChannel::SendBinaryBlob(dom::Blob& aBlob, ErrorResult& aRv) {
   if (!EnsureValidStream(aRv)) {
     return;
   }
 
-  SendErrnoToErrorResult(mConnection->SendBlob(mStream, aBlob), aRv);
+  uint64_t msgLength = aBlob.GetSize(aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  if (msgLength > UINT32_MAX) {
+    aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+    return;
+  }
+
+  // We convert to an nsIInputStream here, because Blob is not threadsafe, and
+  // we don't convert it earlier because we need to know how large this is so we
+  // can update bufferedAmount.
+  nsCOMPtr<nsIInputStream> msgStream;
+  aBlob.CreateInputStream(getter_AddRefs(msgStream), aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  SendErrnoToErrorResult(mConnection->SendBlob(mStream, msgStream), aRv);
+  if (!aRv.Failed()) {
+    IncrementBufferedAmount(msgLength, aRv);
+  }
 }
 
 dom::Nullable<uint16_t> DataChannel::GetMaxPacketLifeTime() const {
   if (mPrPolicy == SCTP_PR_SCTP_TTL) {
     return dom::Nullable<uint16_t>(mPrValue);
   }
   return dom::Nullable<uint16_t>();
 }
@@ -3188,32 +3250,16 @@ void DataChannel::AppReady() {
                  "Shouldn't have queued messages if not WAITING_TO_OPEN");
   }
   mQueuedMessages.Clear();
   mQueuedMessages.Compact();
   // We never use it again...  We could even allocate the array in the odd
   // cases we need it.
 }
 
-size_t DataChannel::GetBufferedAmountLocked() const {
-  size_t buffered = 0;
-
-  for (auto& msg : mBufferedData) {
-    buffered += msg->GetLeft();
-  }
-  // XXX Note: per Michael Tuexen, there's no way to currently get the buffered
-  // amount from the SCTP stack for a single stream.  It is on their to-do
-  // list, and once we import a stack with support for that, we'll need to
-  // add it to what we buffer.  Also we'll need to ask for notification of a
-  // per- stream buffer-low event and merge that into the handling of buffer-low
-  // (the equivalent to TCP_NOTSENT_LOWAT on TCP sockets)
-
-  return buffered;
-}
-
 uint32_t DataChannel::GetBufferedAmountLowThreshold() {
   return mBufferedThreshold;
 }
 
 // Never fire immediately, as it's defined to fire on transitions, not state
 void DataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold) {
   mBufferedThreshold = aThreshold;
 }
--- a/netwerk/sctp/datachannel/DataChannel.h
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -6,26 +6,28 @@
 
 #ifndef NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_
 #define NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_
 
 #ifdef MOZ_WEBRTC_SIGNALING
 #  define SCTP_DTLS_SUPPORTED 1
 #endif
 
+#include <memory>
 #include <string>
+#include <vector>
 #include <errno.h>
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsTArray.h"
 #include "nsDeque.h"
-#include "nsIInputStream.h"
+#include "mozilla/dom/Blob.h"
 #include "mozilla/Mutex.h"
 #include "DataChannelProtocol.h"
 #include "DataChannelListener.h"
 #include "mozilla/net/NeckoTargetHolder.h"
 #ifdef SCTP_DTLS_SUPPORTED
 #  include "mtransport/sigslot.h"
 #  include "mtransport/transportlayer.h"  // For TransportLayer::State
 #endif
@@ -224,36 +226,38 @@ class DataChannelConnection final : publ
   // Use from main thread only as WeakPtr is not threadsafe
   WeakPtr<DataConnectionListener> mListener;
 
  private:
   friend class DataChannelConnectRunnable;
 
 #ifdef SCTP_DTLS_SUPPORTED
   static void DTLSConnectThread(void* data);
-  void SendPacket(nsAutoPtr<MediaPacket> packet);
+  void SendPacket(std::unique_ptr<MediaPacket>&& packet);
   void SctpDtlsInput(const std::string& aTransportId, MediaPacket& packet);
   static int SctpDtlsOutput(void* addr, void* buffer, size_t length,
                             uint8_t tos, uint8_t set_df);
 #endif
   DataChannel* FindChannelByStream(uint16_t stream);
   uint16_t FindFreeStream();
   bool RequestMoreStreams(int32_t aNeeded = 16);
   uint32_t UpdateCurrentStreamIndex();
   uint32_t GetCurrentStreamIndex();
   int SendControlMessage(const uint8_t* data, uint32_t len, uint16_t stream);
   int SendOpenAckMessage(uint16_t stream);
   int SendOpenRequestMessage(const nsACString& label,
                              const nsACString& protocol, uint16_t stream,
                              bool unordered, uint16_t prPolicy,
                              uint32_t prValue);
-  bool SendBufferedMessages(nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer);
-  int SendMsgInternal(OutgoingMsg& msg);
+  bool SendBufferedMessages(nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer,
+                            size_t* aWritten);
+  int SendMsgInternal(OutgoingMsg& msg, size_t* aWritten);
   int SendMsgInternalOrBuffer(nsTArray<nsAutoPtr<BufferedOutgoingMsg>>& buffer,
-                              OutgoingMsg& msg, bool& buffered);
+                              OutgoingMsg& msg, bool& buffered,
+                              size_t* aWritten);
   int SendDataMsgInternalOrBuffer(DataChannel& channel, const uint8_t* data,
                                   size_t len, uint32_t ppid);
   int SendDataMsg(DataChannel& channel, const uint8_t* data, size_t len,
                   uint32_t ppidPartial, uint32_t ppidFinal);
   int SendDataMsgCommon(uint16_t stream, const nsACString& aMsg, bool isBinary);
 
   void DeliverQueuedData(uint16_t stream);
 
@@ -332,16 +336,21 @@ class DataChannelConnection final : publ
 #endif
   uint16_t mLocalPort;  // Accessed from connect thread
   uint16_t mRemotePort;
 
   nsCOMPtr<nsIThread> mInternalIOThread;
   uint8_t mPendingType;
   nsCString mRecvBuffer;
 
+  // Workaround to prevent a message from being received on main before the
+  // sender sees the decrease in bufferedAmount.
+  bool mDeferSend;
+  std::vector<std::unique_ptr<MediaPacket>> mDeferredSend;
+
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   bool mShutdown;
 #endif
 };
 
 #define ENSURE_DATACONNECTION \
   do {                        \
     MOZ_ASSERT(mConnection);  \
@@ -358,34 +367,41 @@ class DataChannel {
     CLOSING = 2U,
     CLOSED = 3U,
     WAITING_TO_OPEN = 4U
   };
 
   DataChannel(DataChannelConnection* connection, uint16_t stream,
               uint16_t state, const nsACString& label,
               const nsACString& protocol, uint16_t policy, uint32_t value,
-              uint32_t flags, DataChannelListener* aListener,
+              bool ordered, bool negotiated, DataChannelListener* aListener,
               nsISupports* aContext)
       : mListenerLock("netwerk::sctp::DataChannel"),
         mListener(aListener),
         mContext(aContext),
         mConnection(connection),
         mLabel(label),
         mProtocol(protocol),
         mState(state),
         mStream(stream),
         mPrPolicy(policy),
         mPrValue(value),
-        mFlags(flags),
+        mOrdered(ordered),
+        mFlags(0),
         mId(0),
         mIsRecvBinary(false),
-        mBufferedThreshold(0)  // default from spec
-        ,
+        mBufferedThreshold(0),  // default from spec
+        mBufferedAmount(0),
         mMainThreadEventTarget(connection->GetNeckoTarget()) {
+    if (!ordered) {
+      mFlags |= DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED;
+    }
+    if (negotiated) {
+      mFlags |= DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED;
+    }
     NS_ASSERTION(mConnection, "NULL connection");
   }
 
  private:
   ~DataChannel();
 
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannel)
@@ -410,45 +426,33 @@ class DataChannel {
 
   // Send a string
   void SendMsg(const nsACString& aMsg, ErrorResult& aRv);
 
   // Send a binary message (TypedArray)
   void SendBinaryMsg(const nsACString& aMsg, ErrorResult& aRv);
 
   // Send a binary blob
-  void SendBinaryStream(nsIInputStream* aBlob, ErrorResult& aRv);
+  void SendBinaryBlob(dom::Blob& aBlob, ErrorResult& aRv);
 
   uint16_t GetType() { return mPrPolicy; }
 
   dom::Nullable<uint16_t> GetMaxPacketLifeTime() const;
 
   dom::Nullable<uint16_t> GetMaxRetransmits() const;
 
-  bool GetOrdered() {
-    return !(mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED);
-  }
+  bool GetOrdered() { return mOrdered; }
+
+  void IncrementBufferedAmount(uint32_t aSize, ErrorResult& aRv);
+  void DecrementBufferedAmount(uint32_t aSize);
 
   // Amount of data buffered to send
   uint32_t GetBufferedAmount() {
-    if (!mConnection) {
-      return 0;
-    }
-
-    MutexAutoLock lock(mConnection->mLock);
-    size_t buffered = GetBufferedAmountLocked();
-
-#if (SIZE_MAX > UINT32_MAX)
-    if (buffered >
-        UINT32_MAX) {  // paranoia - >4GB buffered is very very unlikely
-      buffered = UINT32_MAX;
-    }
-#endif
-
-    return buffered;
+    MOZ_ASSERT(NS_IsMainThread());
+    return mBufferedAmount;
   }
 
   // Trigger amount for generating BufferedAmountLow events
   uint32_t GetBufferedAmountLowThreshold();
   void SetBufferedAmountLowThreshold(uint32_t aThreshold);
 
   // Find out state
   uint16_t GetReadyState() {
@@ -475,30 +479,33 @@ class DataChannel {
   DataChannelListener* mListener;
   nsCOMPtr<nsISupports> mContext;
 
  private:
   friend class DataChannelOnMessageAvailable;
   friend class DataChannelConnection;
 
   nsresult AddDataToBinaryMsg(const char* data, uint32_t size);
-  size_t GetBufferedAmountLocked() const;
   bool EnsureValidStream(ErrorResult& aRv);
 
   RefPtr<DataChannelConnection> mConnection;
   nsCString mLabel;
   nsCString mProtocol;
   uint16_t mState;
   uint16_t mStream;
   uint16_t mPrPolicy;
   uint32_t mPrValue;
+  const bool mOrdered;
   uint32_t mFlags;
   uint32_t mId;
   bool mIsRecvBinary;
   size_t mBufferedThreshold;
+  // Read/written on main only. Decremented via message-passing, because the
+  // spec requires us to queue a task for this.
+  size_t mBufferedAmount;
   nsCString mRecvBuffer;
   nsTArray<nsAutoPtr<BufferedOutgoingMsg>>
       mBufferedData;  // GUARDED_BY(mConnection->mLock)
   nsTArray<nsCOMPtr<nsIRunnable>> mQueuedMessages;
   nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
 };
 
 // used to dispatch notifications of incoming data to the main thread
@@ -509,18 +516,16 @@ class DataChannelOnMessageAvailable : pu
   enum {
     ON_CONNECTION,
     ON_DISCONNECTED,
     ON_CHANNEL_CREATED,
     ON_CHANNEL_OPEN,
     ON_CHANNEL_CLOSED,
     ON_DATA_STRING,
     ON_DATA_BINARY,
-    BUFFER_LOW_THRESHOLD,
-    NO_LONGER_BUFFERED,
   }; /* types */
 
   DataChannelOnMessageAvailable(
       int32_t aType, DataChannelConnection* aConnection, DataChannel* aChannel,
       nsCString& aData)  // XXX this causes inefficiency
       : Runnable("DataChannelOnMessageAvailable"),
         mType(aType),
         mChannel(aChannel),
@@ -556,19 +561,17 @@ class DataChannelOnMessageAvailable : pu
     // Note: calling the listeners can indirectly cause the listeners to be
     // made available for GC (by removing event listeners), especially for
     // OnChannelClosed().  We hold a ref to the Channel and the listener
     // while calling this.
     switch (mType) {
       case ON_DATA_STRING:
       case ON_DATA_BINARY:
       case ON_CHANNEL_OPEN:
-      case ON_CHANNEL_CLOSED:
-      case BUFFER_LOW_THRESHOLD:
-      case NO_LONGER_BUFFERED: {
+      case ON_CHANNEL_CLOSED: {
         MutexAutoLock lock(mChannel->mListenerLock);
         if (!mChannel->mListener) {
           DATACHANNEL_LOG((
               "DataChannelOnMessageAvailable (%d) with null Listener!", mType));
           return NS_OK;
         }
 
         switch (mType) {
@@ -580,22 +583,16 @@ class DataChannelOnMessageAvailable : pu
                                                           mData);
             break;
           case ON_CHANNEL_OPEN:
             mChannel->mListener->OnChannelConnected(mChannel->mContext);
             break;
           case ON_CHANNEL_CLOSED:
             mChannel->mListener->OnChannelClosed(mChannel->mContext);
             break;
-          case BUFFER_LOW_THRESHOLD:
-            mChannel->mListener->OnBufferLow(mChannel->mContext);
-            break;
-          case NO_LONGER_BUFFERED:
-            mChannel->mListener->NotBuffered(mChannel->mContext);
-            break;
         }
         break;
       }
       case ON_DISCONNECTED:
         // If we've disconnected, make sure we close all the streams - from
         // mainthread!
         mConnection->CloseAll();
         MOZ_FALLTHROUGH;
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -659,16 +659,17 @@ SSL_GetPreliminaryChannelInfo
 SSL_GetSRTPCipher
 SSL_GetStatistics
 SSL_HandshakeCallback
 SSL_HandshakeNegotiatedExtension
 SSL_ImplementedCiphers @DATA@
 SSL_ImportFD
 SSL_NamedGroupConfig
 SSL_NumImplementedCiphers @DATA@
+SSL_OptionGet
 SSL_OptionSet
 SSL_OptionSetDefault
 SSL_PeerCertificate
 SSL_PeerCertificateChain
 SSL_PeerSignedCertTimestamps
 SSL_PeerStapledOCSPResponses
 SSL_ResetHandshake
 SSL_SendAdditionalKeyShares
--- a/servo/components/servo_arc/lib.rs
+++ b/servo/components/servo_arc/lib.rs
@@ -682,17 +682,20 @@ impl<H, T> Arc<HeaderSlice<H, [T]>> {
                         items
                             .next()
                             .expect("ExactSizeIterator over-reported length"),
                     );
                     current = current.offset(1);
                 }
                 // We should have consumed the buffer exactly, maybe accounting
                 // for some padding from the alignment.
-                debug_assert!((buffer.offset(size as isize) as usize - current as *mut u8 as usize) < align_of::<Self>());
+                debug_assert!(
+                    (buffer.offset(size as isize) as usize - current as *mut u8 as usize) <
+                        align_of::<Self>()
+                );
             }
             assert!(
                 items.next().is_none(),
                 "ExactSizeIterator under-reported length"
             );
         }
 
         // Return the fat Arc.
@@ -787,17 +790,16 @@ type HeaderSliceWithLength<H, T> = Heade
 /// `ThinArc` solves this by storing the length in the allocation itself,
 /// via `HeaderSliceWithLength`.
 #[repr(C)]
 pub struct ThinArc<H, T> {
     ptr: ptr::NonNull<ArcInner<HeaderSliceWithLength<H, [T; 0]>>>,
     phantom: PhantomData<(H, T)>,
 }
 
-
 impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for ThinArc<H, T> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         fmt::Debug::fmt(self.deref(), f)
     }
 }
 
 unsafe impl<H: Sync + Send, T: Sync + Send> Send for ThinArc<H, T> {}
 unsafe impl<H: Sync + Send, T: Sync + Send> Sync for ThinArc<H, T> {}
@@ -904,17 +906,20 @@ impl<H, T> Clone for ThinArc<H, T> {
     fn clone(&self) -> Self {
         ThinArc::with_arc(self, |a| Arc::into_thin(a.clone()))
     }
 }
 
 impl<H, T> Drop for ThinArc<H, T> {
     #[inline]
     fn drop(&mut self) {
-        let _ = Arc::from_thin(ThinArc { ptr: self.ptr, phantom: PhantomData, });
+        let _ = Arc::from_thin(ThinArc {
+            ptr: self.ptr,
+            phantom: PhantomData,
+        });
     }
 }
 
 impl<H, T> Arc<HeaderSliceWithLength<H, [T]>> {
     /// Converts an `Arc` into a `ThinArc`. This consumes the `Arc`, so the refcount
     /// is not modified.
     #[inline]
     pub fn into_thin(a: Self) -> ThinArc<H, T> {
@@ -923,17 +928,19 @@ impl<H, T> Arc<HeaderSliceWithLength<H, 
             a.slice.len(),
             "Length needs to be correct for ThinArc to work"
         );
         let fat_ptr: *mut ArcInner<HeaderSliceWithLength<H, [T]>> = a.ptr();
         mem::forget(a);
         let thin_ptr = fat_ptr as *mut [usize] as *mut usize;
         ThinArc {
             ptr: unsafe {
-                ptr::NonNull::new_unchecked(thin_ptr as *mut ArcInner<HeaderSliceWithLength<H, [T; 0]>>)
+                ptr::NonNull::new_unchecked(
+                    thin_ptr as *mut ArcInner<HeaderSliceWithLength<H, [T; 0]>>,
+                )
             },
             phantom: PhantomData,
         }
     }
 
     /// Converts a `ThinArc` into an `Arc`. This consumes the `ThinArc`, so the refcount
     /// is not modified.
     #[inline]
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -531,22 +531,20 @@ impl nsStyleImage {
 }
 
 pub mod basic_shape {
     //! Conversions from and to CSS shape representations.
     use crate::gecko_bindings::structs::{
         StyleGeometryBox, StyleShapeSource, StyleShapeSourceType,
     };
     use crate::gecko_bindings::sugar::refptr::RefPtr;
-    use crate::values::computed::basic_shape::{
-        BasicShape, ClippingShape, FloatAreaShape,
-    };
+    use crate::values::computed::basic_shape::{BasicShape, ClippingShape, FloatAreaShape};
     use crate::values::computed::motion::OffsetPath;
     use crate::values::computed::url::ComputedUrl;
-    use crate::values::generics::basic_shape::{Path, GeometryBox, ShapeBox, ShapeSource};
+    use crate::values::generics::basic_shape::{GeometryBox, Path, ShapeBox, ShapeSource};
     use crate::values::specified::SVGPathData;
 
     impl StyleShapeSource {
         /// Convert StyleShapeSource to ShapeSource except URL and Image
         /// types.
         fn into_shape_source<ReferenceBox, ImageOrUrl>(
             &self,
         ) -> Option<ShapeSource<BasicShape, ReferenceBox, ImageOrUrl>>
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -2,18 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #![allow(unsafe_code)]
 
 //! Different kind of helpers to interact with Gecko values.
 
 use crate::counter_style::{Symbol, Symbols};
+use crate::gecko_bindings::structs::StyleGridTrackBreadth;
 use crate::gecko_bindings::structs::{nsStyleCoord, CounterStylePtr};
-use crate::gecko_bindings::structs::StyleGridTrackBreadth;
 use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
 use crate::values::computed::{Angle, Length, LengthPercentage};
 use crate::values::computed::{Number, NumberOrPercentage, Percentage};
 use crate::values::generics::gecko::ScrollSnapPoint;
 use crate::values::generics::grid::{TrackBreadth, TrackKeyword};
 use crate::values::generics::length::LengthPercentageOrAuto;
 use crate::values::generics::{CounterStyleOrNone, NonNegative};
 use crate::values::Either;
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -675,10 +675,11 @@
 // property for now, but the spec defines line-clamp as a shorthand for separate
 // max-lines, block-ellipsis, and continue properties.
 ${helpers.predefined_type(
     "-webkit-line-clamp",
     "PositiveIntegerOrNone",
     "Either::Second(None_)",
     gecko_pref="layout.css.webkit-line-clamp.enabled",
     animation_value_type="Integer",
+    products="gecko",
     spec="https://drafts.csswg.org/css-overflow-3/#line-clamp",
 )}
--- a/servo/components/style/values/animated/mod.rs
+++ b/servo/components/style/values/animated/mod.rs
@@ -293,27 +293,25 @@ where
     }
 
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
         Box::new(T::from_animated_value(*animated))
     }
 }
 
-
 impl<T> ToAnimatedValue for Box<[T]>
 where
     T: ToAnimatedValue,
 {
     type AnimatedValue = Box<[<T as ToAnimatedValue>::AnimatedValue]>;
 
     #[inline]
     fn to_animated_value(self) -> Self::AnimatedValue {
-        self
-            .into_vec()
+        self.into_vec()
             .into_iter()
             .map(T::to_animated_value)
             .collect::<Vec<_>>()
             .into_boxed_slice()
     }
 
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -347,22 +347,24 @@ impl Polygon {
         let fill = input
             .try(|i| -> Result<_, ParseError> {
                 let fill = FillRule::parse(i)?;
                 i.expect_comma()?; // only eat the comma if there is something before it
                 Ok(fill)
             })
             .unwrap_or_default();
 
-        let coordinates = input.parse_comma_separated(|i| {
-            Ok(PolygonCoord(
-                LengthPercentage::parse(context, i)?,
-                LengthPercentage::parse(context, i)?,
-            ))
-        })?.into();
+        let coordinates = input
+            .parse_comma_separated(|i| {
+                Ok(PolygonCoord(
+                    LengthPercentage::parse(context, i)?,
+                    LengthPercentage::parse(context, i)?,
+                ))
+            })?
+            .into();
 
         Ok(Polygon { fill, coordinates })
     }
 }
 
 impl Parse for Path {
     fn parse<'i, 't>(
         context: &ParserContext,
--- a/servo/components/style/values/specified/svg_path.rs
+++ b/servo/components/style/values/specified/svg_path.rs
@@ -28,18 +28,17 @@ use style_traits::{CssWriter, ParseError
     ToComputedValue,
     ToResolvedValue,
     ToShmem,
 )]
 #[repr(C)]
 pub struct SVGPathData(
     // TODO(emilio): Should probably measure this somehow only from the
     // specified values.
-    #[ignore_malloc_size_of = "Arc"]
-    pub crate::ArcSlice<PathCommand>
+    #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
 );
 
 impl SVGPathData {
     /// Get the array of PathCommand.
     #[inline]
     pub fn commands(&self) -> &[PathCommand] {
         debug_assert!(!self.0.is_empty());
         &self.0
@@ -98,17 +97,19 @@ impl Parse for SVGPathData {
         // Parse the svg path string as multiple sub-paths.
         let mut path_parser = PathParser::new(path_string);
         while skip_wsp(&mut path_parser.chars) {
             if path_parser.parse_subpath().is_err() {
                 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
             }
         }
 
-        Ok(SVGPathData(crate::ArcSlice::from_iter(path_parser.path.into_iter())))
+        Ok(SVGPathData(crate::ArcSlice::from_iter(
+            path_parser.path.into_iter(),
+        )))
     }
 }
 
 impl Animate for SVGPathData {
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         if self.0.len() != other.0.len() {
             return Err(());
         }
--- a/servo/components/style_traits/owned_slice.rs
+++ b/servo/components/style_traits/owned_slice.rs
@@ -1,21 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #![allow(unsafe_code)]
 
 //! A replacement for `Box<[T]>` that cbindgen can understand.
 
+use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
 use std::marker::PhantomData;
-use std::{fmt, mem, slice};
+use std::ops::{Deref, DerefMut};
 use std::ptr::NonNull;
-use std::ops::{Deref, DerefMut};
-use malloc_size_of::{MallocSizeOf, MallocShallowSizeOf, MallocSizeOfOps};
+use std::{fmt, mem, slice};
 use to_shmem::{SharedMemoryBuilder, ToShmem};
 
 /// A struct that basically replaces a `Box<[T]>`, but which cbindgen can
 /// understand.
 ///
 /// We could rely on the struct layout of `Box<[T]>` per:
 ///
 ///   https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/layout/pointers.md
@@ -81,19 +81,17 @@ impl<T: Sized> OwnedSlice<T> {
     #[inline]
     pub fn into_box(self) -> Box<[T]> {
         self.into_vec().into_boxed_slice()
     }
 
     /// Convert the OwnedSlice into a Vec.
     #[inline]
     pub fn into_vec(self) -> Vec<T> {
-        let ret = unsafe {
-            Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len)
-        };
+        let ret = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len) };
         mem::forget(self);
         ret
     }
 
     /// Iterate over all the elements in the slice taking ownership of them.
     #[inline]
     pub fn into_iter(self) -> impl Iterator<Item = T> {
         self.into_vec().into_iter()
--- a/settings.gradle
+++ b/settings.gradle
@@ -56,8 +56,17 @@ project(':thirdparty').projectDir = new 
 // The Gradle instance is shared between settings.gradle and all the
 // other build.gradle files (see
 // http://forums.gradle.org/gradle/topics/define_extension_properties_from_settings_xml).
 // We use this ext property to pass the per-object-directory mozconfig
 // between scripts.  This lets us execute set-up code before we gradle
 // tries to configure the project even once, and as a side benefit
 // saves invoking |mach environment| multiple times.
 gradle.ext.mozconfig = json
+
+// Produced by `mach build`.  Bug 1543982: the mozconfig determined by `mach
+// environment` above can be different because `mach build` itself sets certain
+// critical environment variables including MOZ_OBJDIR, CC, and CXX.  We use
+// this record to patch up the environment when we recursively invoke `mach
+// build ...` commands from within Gradle.  This avoids invalidating configure
+// based on the changed environment variables.
+def orig = slurper.parse(new File(json.topobjdir, '.mozconfig.json'))
+gradle.ext.mozconfig.orig_mozconfig = orig.mozconfig
--- a/taskcluster/ci/build/android.yml
+++ b/taskcluster/ci/build/android.yml
@@ -604,16 +604,17 @@ android-aarch64/debug:
         - linux64-rust-size
         - linux64-cbindgen
         - linux64-sccache
         - linux64-nasm
         - linux64-node
 
 android-aarch64-nightly/opt:
     description: "Android 5.0 AArch64 Nightly"
+    use-pgo: android-api-16/pgo
     attributes:
         nightly: true
         enable-full-crashsymbols: true
     shipping-phase: build
     shipping-product: fennec
     index:
         product: mobile
         job-name: android-aarch64-opt
@@ -648,17 +649,17 @@ android-aarch64-nightly/opt:
     run:
         using: mozharness
         actions: [get-secrets, build, multi-l10n]
         config:
             - builds/releng_base_android_64_builds.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
-        custom-build-variant-cfg: aarch64
+        custom-build-variant-cfg: aarch64-pgo
         tooltool-downloads: internal
     toolchains:
         - android-gradle-dependencies
         - android-ndk-linux
         - android-sdk-linux
         - linux64-clang
         - linux64-rust-android
         - linux64-rust-size
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -30,16 +30,18 @@ treeherder:
         'R-sw': 'Reftests with serviceworker redesign'
         'R-sw-1proc': 'Reftests with serviceworker redesign enabled without e10s'
         'Rap': 'Raptor performance tests on Firefox'
         'Rap-1proc': 'Raptor performance tests on Firefox without e10s'
         'Rap-Prof': 'Raptor performance tests on Firefox with Gecko Profiling'
         'Rap-Prof-1proc': 'Raptor performance tests on Firefox with Gecko Profiling and without e10s'
         'Rap-ChC': 'Raptor performance tests on Google Chrome Canary'
         'Rap-ChD': 'Raptor performance tests on Google Chrome Dev'
+        'Rap-Cpu': 'Raptor CPU tests on Android'
+        'Rap-CPU-1proc': 'Reaptor CPU tests on Android without e10s'
         'Rap-Cr': 'Raptor performance tests on Google Chromium'
         'Rap-P': 'Raptor power tests on Firefox'
         'Rap-P-1proc': 'Raptor power tests on Firefox without e10s'
         'Rap-fenix': 'Raptor performance tests on Fenix'
         'Rap-refbrow': 'Raptor performance tests on the reference browser'
         'T': 'Talos performance tests'
         'T-1proc': 'Talos performance tests without e10s'
         'Tsd': 'Talos performance tests with e10s, Stylo disabled'
--- a/taskcluster/ci/test/awsy.yml
+++ b/taskcluster/ci/test/awsy.yml
@@ -39,16 +39,17 @@ awsy-tp6:
         by-test-platform:
             .*-devedition/.*: []  # don't run on devedition
             .*-ccov/.*: []  # don't run on coverage
             windows10-aarch64/opt: ['try', 'mozilla-central']
             default: built-projects
     mozharness:
         extra-options:
             - --tp6
+    tier: 3
 
 awsy-dmd:
     description: "Are we slim yet - dmd enabled"
     treeherder-symbol: SY(sy-d)
     run-on-projects: ['try']
     mozharness:
         extra-options:
             - --dmd
--- a/taskcluster/ci/test/mochitest.yml
+++ b/taskcluster/ci/test/mochitest.yml
@@ -217,21 +217,21 @@ mochitest-devtools-chrome:
     max-run-time:
         by-test-platform:
             windows10-64-ccov/debug: 7200
             macosx64-ccov/debug: 9000
             linux64-ccov/debug: 7200
             default: 5400
     chunks:
         by-test-platform:
-            linux64/debug: 16
-            linux64-ccov/debug: 16
-            windows10-64-ccov/debug: 10
-            macosx64-ccov/debug: 15
-            default: 8
+            .*-ccov/debug: 16
+            linux64/debug: 12
+            macosx64/debug: 8
+            .*-asan/opt: 8
+            default: 5
     mozharness:
         mochitest-flavor: chrome
         chunked: true
     instance-size:
         by-test-platform:
             linux64-asan/opt: xlarge  # runs out of memory on default/m3.large
             default: default
     # Bug 1296086: high number of intermittents observed with software GL and large instances
@@ -307,18 +307,22 @@ mochitest-media:
             default: virtual
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: large
     chunks:
         by-test-platform:
             android-em-7.*: 1
-            macosx64.*: 1
             windows10-64.*: 1
+            macosx64.*/opt: 2
+            macosx64.*/debug: 4
+            windows10-aarch64/.*: 2
+            windows7-32-shippable/.*: 2
+            linux64(-shippable|-devedition|-.*qr)/opt: 2
             default: 3
     mozharness:
         mochitest-flavor: plain
         chunked:
             by-test-platform:
                 android.*: false
                 macosx64.*: false
                 windows10-64.*: false
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -1021,16 +1021,36 @@ raptor-speedometer-geckoview-power:
             - --test=raptor-speedometer
             - --app=geckoview
             - --binary=org.mozilla.geckoview_example
             - --power-test
             - --page-cycles 5
             - --host HOST_IP
             - --activity=GeckoViewActivity
 
+raptor-speedometer-geckoview-cpu:
+    description: "Raptor Speedometer CPU on Geckoview"
+    try-name: raptor-speedometer-geckoview-cpu
+    treeherder-symbol: Rap-Cpu(sp)
+    target: geckoview_example.apk
+    run-on-projects:
+        by-test-platform:
+            android-hw.*/pgo: ['try', 'mozilla-central']
+            android-hw-.*-aarch64/opt: ['try', 'mozilla-central']
+            android-hw-.*-api-16/opt: ['try']
+    tier: 2
+    max-run-time: 1800
+    mozharness:
+        extra-options:
+            - --test=raptor-speedometer
+            - --app=geckoview
+            - --binary=org.mozilla.geckoview_example
+            - --cpu-test
+            - --activity=GeckoViewActivity
+
 raptor-speedometer-fennec:
     description: "Raptor Speedometer on Fennec"
     try-name: raptor-speedometer-fennec
     treeherder-symbol: Rap(sp)
     target: target.apk
     run-on-projects:
         by-test-platform:
             android-hw-.*-api-16/opt: ['try']
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -555,19 +555,21 @@ android-hw-aarch64-raptor:
     - raptor-tp6m-10-geckoview-cold
     - raptor-tp6m-11-geckoview-cold
     - raptor-tp6m-12-geckoview-cold
     - raptor-tp6m-13-geckoview-cold
     - raptor-tp6m-14-geckoview-cold
 
 android-hw-arm7-raptor-power:
     - raptor-speedometer-geckoview-power
+    - raptor-speedometer-geckoview-cpu
 
 android-hw-aarch64-raptor-power:
     - raptor-speedometer-geckoview-power
+    - raptor-speedometer-geckoview-cpu
 
 android-hw-arm7-raptor-nightly:
     - raptor-speedometer-fennec
     - raptor-tp6m-1-fennec
     - raptor-tp6m-2-fennec
     - raptor-tp6m-3-fennec
     - raptor-tp6m-4-fennec
     - raptor-tp6m-5-fennec
--- a/taskcluster/ci/test/web-platform.yml
+++ b/taskcluster/ci/test/web-platform.yml
@@ -100,17 +100,25 @@ web-platform-tests-headless:
                     - --headless
 
 web-platform-tests-reftests:
     description: "Web platform reftest run"
     suite:
         name: web-platform-tests-reftests
     schedules-component: web-platform-tests-reftests
     treeherder-symbol: W(Wr)
-    chunks: 6
+    chunks:
+        by-test-platform:
+            .*-ccov/debug: 8
+            linux64(-qr|-asan)/.*: 6
+            linux64(-shippable|-devedition)?/opt: 3
+            macosx64/debug: 6
+            windows.*-(32|64)(-qr)?/debug: 5
+            android.*: 6
+            default: 4
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     run-on-projects:
         by-test-platform:
             android.*: ['mozilla-central', 'try']
             windows10-aarch64/opt: ['try', 'mozilla-central']
--- a/taskcluster/ci/test/xpcshell.yml
+++ b/taskcluster/ci/test/xpcshell.yml
@@ -36,26 +36,25 @@ xpcshell:
     run-on-projects:
         by-test-platform:
             windows10-64-asan/opt: []  # No XPCShell on ASAN yet
             windows10-aarch64/opt: ['try', 'mozilla-central']
             android-em-4.3-arm7-api-16/opt: ['try']
             default: built-projects
     chunks:
         by-test-platform:
-            linux32/debug: 12
-            linux64/debug: 10
-            android-em-4.3-arm7-api-16/debug: 12
+            android-em-4.3-arm7-api-16/.*: 8
             android-em-7.*: 3
-            macosx.*: 1
-            windows.*: 1
+            macosx64-ccov/debug: 8
+            macosx.*/.*: 2
+            linux64(-qr)?/debug: 6
+            (linux.*|windows.*)-ccov/debug: 6
+            windows(7-32|10-64)(-shippable|-devedition|-asan|.*-qr)?/.*: 2
             windows10-aarch64/opt: 3
-            windows10-64-ccov/debug: 8
-            macosx64-ccov/debug: 8
-            default: 8
+            default: 5
     instance-size:
         by-test-platform:
             android-em.*: xlarge
             default: default
     max-run-time:
         by-test-platform:
             android-em-4.3-arm7-api-16/debug: 7200
             default: 5400
--- a/testing/config/tooltool-manifests/linux32/hostutils.manifest
+++ b/testing/config/tooltool-manifests/linux32/hostutils.manifest
@@ -1,10 +1,10 @@
 [
   {
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "host-utils-68.0a1.en-US.linux-i686.tar.gz",
     "unpack": true,
-    "digest": "96c4d1588a2f5aabeed219146f01a4c7da4a93a6b9a1a5e15387173bf92d9b5014a4a73f75e3925f4db7fe278a7f665268660d43354ffc1c09431a93122206b8",
-    "size": 78395774
+    "digest": "39940d5873ff5cbef9046fd1ac0a3e9fd1e696fc3cad5a8948507fc205c060080b94537205bf3726afd93dfdd1bcc8047f25cce7d6385e3b071944efc13a4a72",
+    "size": 79137774
   }
 ]
--- a/testing/config/tooltool-manifests/linux64/hostutils.manifest
+++ b/testing/config/tooltool-manifests/linux64/hostutils.manifest
@@ -1,10 +1,10 @@
 [
   {
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "host-utils-68.0a1.en-US.linux-x86_64.tar.gz",
     "unpack": true,
-    "digest": "83d907f71209adc3b14be1298fe7691c4bc48ed73a768b71f6cdfd1cdebcce659a81373587b943a88eda7018113c08682e1671fcb3964c77d5652e68458add9e",
-    "size": 78213104
+    "digest": "b8cd4021a3e76c35e090c6bb1a6728062cc5185e3e0a0f353aa582c2dc68615c1b8d8289bfb424b82d1ca77ab3c46c8a818803ada4e0088aa554c99a6c07edce",
+    "size": 79036396
   }
 ]
--- a/testing/gtest/rungtests.py
+++ b/testing/gtest/rungtests.py
@@ -91,16 +91,17 @@ class GTests(object):
         env["MOZ_XRE_DIR"] = self.xre_path
         env["MOZ_GMP_PATH"] = os.pathsep.join(
             os.path.join(self.xre_path, p, "1.0")
             for p in ('gmp-fake', 'gmp-fakeopenh264')
         )
         env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
         env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
         env["MOZ_CRASHREPORTER"] = "1"
+        env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
         env["MOZ_RUN_GTEST"] = "1"
         # Normally we run with GTest default output, override this to use the TBPL test format.
         env["MOZ_TBPL_PARSER"] = "1"
 
         if not mozinfo.has_sandbox:
             # Bug 1082193 - This is horrible. Our linux build boxes run CentOS 6,
             # which is too old to support sandboxing. Disable sandbox for gtests
             # on machines which don't support sandboxing until they can be
--- a/testing/mozharness/mozharness/mozilla/testing/raptor.py
+++ b/testing/mozharness/mozharness/mozilla/testing/raptor.py
@@ -151,16 +151,22 @@ class Raptor(TestingMixin, MercurialScri
                     "argument.",
         }],
         [["--memory-test"], {
             "dest": "memory_test",
             "action": "store_true",
             "default": False,
             "help": "Use Raptor to measure memory usage.",
         }],
+        [["--cpu-test"], {
+            "dest": "cpu_test",
+            "action": "store_true",
+            "default": False,
+            "help": "Use Raptor to measure CPU usage"
+        }],
         [["--debug-mode"], {
             "dest": "debug_mode",
             "action": "store_true",
             "default": False,
             "help": "Run Raptor in debug mode (open browser console, limited page-cycles, etc.)",
         }],
 
     ] + testing_config_options + copy.deepcopy(code_coverage_config_options)
@@ -242,16 +248,17 @@ class Raptor(TestingMixin, MercurialScri
         self.gecko_profile_interval = self.config.get('gecko_profile_interval')
         self.gecko_profile_entries = self.config.get('gecko_profile_entries')
         self.test_packages_url = self.config.get('test_packages_url')
         self.host = self.config.get('host')
         if self.host == 'HOST_IP':
             self.host = os.environ['HOST_IP']
         self.power_test = self.config.get('power_test')
         self.memory_test = self.config.get('memory_test')
+        self.cpu_test = self.config.get('cpu_test')
         self.is_release_build = self.config.get('is_release_build')
         self.debug_mode = self.config.get('debug_mode', False)
         self.firefox_android_browsers = ["fennec", "geckoview", "refbrow", "fenix"]
 
     # We accept some configuration options from the try commit message in the
     # format mozharness: <options>. Example try commit message: mozharness:
     # --geckoProfile try: <stuff>
     def query_gecko_profile_options(self):
@@ -377,16 +384,18 @@ class Raptor(TestingMixin, MercurialScri
         if self.config.get('code_coverage', False):
             options.extend(['--code-coverage'])
         if self.config.get('is_release_build', False):
             options.extend(['--is-release-build'])
         if self.config.get('power_test', False):
             options.extend(['--power-test'])
         if self.config.get('memory_test', False):
             options.extend(['--memory-test'])
+        if self.config.get('cpu_test', False):
+            options.extend(['--cpu-test'])
         for key, value in kw_options.items():
             options.extend(['--%s' % key, value])
 
         return options
 
     def populate_webroot(self):
         """Populate the production test slaves' webroots"""
         self.raptor_path = os.path.join(
@@ -490,16 +499,18 @@ class Raptor(TestingMixin, MercurialScri
         # late import is required, because install is done in create_virtualenv
         import jsonschema
 
         expected_perfherder = 1
         if self.config.get('power_test', None):
             expected_perfherder += 1
         if self.config.get('memory_test', None):
             expected_perfherder += 1
+        if self.config.get('cpu_test', None):
+            expected_perfherder += 1
         if len(parser.found_perf_data) != expected_perfherder:
             self.critical("PERFHERDER_DATA was seen %d times, expected %d."
                           % (len(parser.found_perf_data), expected_perfherder))
             return
 
         schema_path = os.path.join(external_tools_path,
                                    'performance-artifact-schema.json')
         self.info("Validating PERFHERDER_DATA against %s" % schema_path)
@@ -629,16 +640,20 @@ class Raptor(TestingMixin, MercurialScri
                 if self.power_test:
                     src = os.path.join(self.query_abs_dirs()['abs_work_dir'], 'raptor-power.json')
                     self._artifact_perf_data(src, dest)
 
                 if self.memory_test:
                     src = os.path.join(self.query_abs_dirs()['abs_work_dir'], 'raptor-memory.json')
                     self._artifact_perf_data(src, dest)
 
+                if self.cpu_test:
+                    src = os.path.join(self.query_abs_dirs()['abs_work_dir'], 'raptor-cpu.json')
+                    self._artifact_perf_data(src, dest)
+
                 src = os.path.join(self.query_abs_dirs()['abs_work_dir'], 'screenshots.html')
                 if os.path.exists(src):
                     dest = os.path.join(env['MOZ_UPLOAD_DIR'], 'screenshots.html')
                     self.info(str(dest))
                     self._artifact_perf_data(src, dest)
 
 
 class RaptorOutputParser(OutputParser):
--- a/testing/raptor/raptor/cmdline.py
+++ b/testing/raptor/raptor/cmdline.py
@@ -83,16 +83,18 @@ def create_parser(mach_interface=False):
             "The value HOST_IP will cause the value of host to be "
             "loaded from the environment variable HOST_IP.",
             default='127.0.0.1')
     add_arg('--power-test', dest="power_test", action="store_true",
             help="Use Raptor to measure power usage. Currently supported for Geckoview. "
             "The host ip address must be specified via the --host command line argument.")
     add_arg('--memory-test', dest="memory_test", action="store_true",
             help="Use Raptor to measure memory usage.")
+    add_arg('--cpu-test', dest="cpu_test", action="store_true",
+            help="Use Raptor to measure CPU usage. Currently supported for Android only.")
     add_arg('--is-release-build', dest="is_release_build", default=False,
             action='store_true',
             help="Whether the build is a release build which requires work arounds "
             "using MOZ_DISABLE_NONLOCAL_CONNECTIONS to support installing unsigned "
             "webextensions. Defaults to False.")
     add_arg('--geckoProfile', action="store_true", dest="gecko_profile",
             help=argparse.SUPPRESS)
     add_arg('--geckoProfileInterval', dest='gecko_profile_interval', type=float,
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/cpu.py
@@ -0,0 +1,46 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+from __future__ import absolute_import
+
+
+def get_app_cpu_usage(raptor):
+    cpu_usage = 0
+    app_name = raptor.config['binary']
+    verbose = raptor.device._verbose
+    raptor.device._verbose = False
+    cpuinfo = raptor.device.shell_output("top -O %CPU -n 1").split("\n")
+    raptor.device._verbose = verbose
+    '''
+    When parsing the output of the shell command, you will
+    get a line that looks like this:
+
+    17504 u0_a83       93.7 93.7  14.2   0:12.12 org.mozilla.geckoview_example
+
+    When you split on whitespace you end up with the
+    name of the process at index 6 and the
+    amount of CPU being used at index 3
+    (Remember that indexes start at 0 because COMPUTERS)
+    '''
+    for line in cpuinfo:
+        data = line.split()
+        if len(data) == 7 and data[6] == app_name:
+            cpu_usage = data[3]
+    return cpu_usage
+
+
+def generate_android_cpu_profile(raptor, test_name):
+    if not raptor.device or not raptor.config['cpu_test']:
+        return
+
+    result = get_app_cpu_usage(raptor)
+
+    cpuinfo_data = {
+        u'type': u'cpu',
+        u'test': test_name,
+        u'unit': u'%',
+        u'values': {
+            u'browser_cpu_usage': result
+        }
+    }
+    raptor.control_server.submit_supporting_data(cpuinfo_data)
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -59,16 +59,17 @@ from gecko_profile import GeckoProfile
 from gen_test_config import gen_test_config
 from outputhandler import OutputHandler
 from manifest import get_raptor_test_list
 from memory import generate_android_memory_profile
 from mozproxy import get_playback
 from power import init_android_power_test, finish_android_power_test
 from results import RaptorResultsHandler
 from utils import view_gecko_profile
+from cpu import generate_android_cpu_profile
 
 
 class SignalHandler:
 
     def __init__(self):
         signal.signal(signal.SIGINT, self.handle_signal)
         signal.signal(signal.SIGTERM, self.handle_signal)
 
@@ -80,42 +81,41 @@ class SignalHandlerException(Exception):
     pass
 
 
 class Raptor(object):
     """Container class for Raptor"""
 
     def __init__(self, app, binary, run_local=False, obj_path=None,
                  gecko_profile=False, gecko_profile_interval=None, gecko_profile_entries=None,
-                 symbols_path=None, host=None, power_test=False, memory_test=False,
-                 is_release_build=False, debug_mode=False, post_startup_delay=None,
-                 interrupt_handler=None, **kwargs):
+                 symbols_path=None, host=None, power_test=False, cpu_test=False, memory_test=False,
+                 is_release_build=False, debug_mode=False, post_startup_delay=None, activity=None,
+                 interrupt_handler=None, intent=None, **kwargs):
 
         # Override the magic --host HOST_IP with the value of the environment variable.
         if host == 'HOST_IP':
             host = os.environ['HOST_IP']
 
-        self.config = {
-            'app': app,
-            'binary': binary,
-            'platform': mozinfo.os,
-            'processor': mozinfo.processor,
-            'run_local': run_local,
-            'obj_path': obj_path,
-            'gecko_profile': gecko_profile,
-            'gecko_profile_interval': gecko_profile_interval,
-            'gecko_profile_entries': gecko_profile_entries,
-            'symbols_path': symbols_path,
-            'host': host,
-            'power_test': power_test,
-            'memory_test': memory_test,
-            'is_release_build': is_release_build,
-            'enable_control_server_wait': memory_test,
-        }
-
+        self.config = {}
+        self.config['app'] = app
+        self.config['binary'] = binary
+        self.config['platform'] = mozinfo.os
+        self.config['processor'] = mozinfo.processor
+        self.config['run_local'] = run_local
+        self.config['obj_path'] = obj_path
+        self.config['gecko_profile'] = gecko_profile
+        self.config['gecko_profile_interval'] = gecko_profile_interval
+        self.config['gecko_profile_entries'] = gecko_profile_entries
+        self.config['symbols_path'] = symbols_path
+        self.config['host'] = host
+        self.config['power_test'] = power_test
+        self.config['cpu_test'] = cpu_test
+        self.config['memory_test'] = memory_test
+        self.config['is_release_build'] = is_release_build
+        self.config['enable_control_server_wait'] = memory_test
         self.raptor_venv = os.path.join(os.getcwd(), 'raptor-venv')
         self.log = get_default_logger(component='raptor-main')
         self.control_server = None
         self.playback = None
         self.benchmark = None
         self.benchmark_port = 0
         self.gecko_profiler = None
         self.post_startup_delay = post_startup_delay
@@ -337,16 +337,17 @@ class Raptor(object):
         # and the time an exception is propagated through the framework
         timeout += (int(self.post_startup_delay / 1000) + 10)
 
         # if geckoProfile enabled, give browser more time for profiling
         if self.config['gecko_profile'] is True:
             timeout += 5 * 60
 
         elapsed_time = 0
+
         while not self.control_server._finished:
             if self.config['enable_control_server_wait']:
                 response = self.control_server_wait_get()
                 if response == 'webext_status/__raptor_shutdownBrowser':
                     if self.config['memory_test']:
                         generate_android_memory_profile(self, test['name'])
                     self.control_server_wait_continue()
             time.sleep(1)
@@ -583,16 +584,25 @@ class RaptorDesktopFirefox(RaptorDesktop
 
     def set_browser_test_prefs(self, raw_prefs):
         # add test specific preferences
         self.log.info("setting test-specific Firefox preferences")
         self.profile.set_preferences(json.loads(raw_prefs))
 
 
 class RaptorDesktopChrome(RaptorDesktop):
+    def __init__(self, app, binary, run_local=False, obj_path=None,
+                 gecko_profile=False, gecko_profile_interval=None, gecko_profile_entries=None,
+                 symbols_path=None, host=None, power_test=False, cpu_test=False, memory_test=False,
+                 is_release_build=False, debug_mode=False, post_startup_delay=None,
+                 activity=None, intent=None):
+        RaptorDesktop.__init__(self, app, binary, run_local, obj_path, gecko_profile,
+                               gecko_profile_interval, gecko_profile_entries, symbols_path,
+                               host, power_test, cpu_test, memory_test, is_release_build,
+                               debug_mode, post_startup_delay)
 
     def setup_chrome_desktop_for_playback(self):
         # if running a pageload test on google chrome, add the cmd line options
         # to turn on the proxy and ignore security certificate errors
         # if using host localhost, 127.0.0.1.
         chrome_args = [
             '--proxy-server=127.0.0.1:8080',
             '--proxy-bypass-list=localhost;127.0.0.1',
@@ -614,18 +624,25 @@ class RaptorDesktopChrome(RaptorDesktop)
 
         if test.get('playback') is not None:
             self.setup_chrome_desktop_for_playback()
 
         self.start_runner_proc()
 
 
 class RaptorAndroid(Raptor):
-    def __init__(self, app, binary, activity=None, intent=None, **kwargs):
-        super(RaptorAndroid, self).__init__(app, binary, **kwargs)
+    def __init__(self, app, binary, run_local=False, obj_path=None,
+                 gecko_profile=False, gecko_profile_interval=None, gecko_profile_entries=None,
+                 symbols_path=None, host=None, power_test=False, cpu_test=False, memory_test=False,
+                 is_release_build=False, debug_mode=False, post_startup_delay=None, activity=None,
+                 intent=None, interrupt_handler=None):
+        Raptor.__init__(self, app, binary, run_local, obj_path, gecko_profile,
+                        gecko_profile_interval, gecko_profile_entries, symbols_path, host,
+                        power_test, cpu_test, memory_test, is_release_build, debug_mode,
+                        post_startup_delay)
 
         # on android, when creating the browser profile, we want to use a 'firefox' type profile
         self.profile_class = "firefox"
         self.config.update({
             'activity': activity,
             'intent': intent,
         })
 
@@ -659,16 +676,169 @@ class RaptorAndroid(Raptor):
 
     def create_browser_handler(self):
         # create the android device handler; it gets initiated and sets up adb etc
         self.log.info("creating android device handler using mozdevice")
         self.device = ADBDevice(verbose=True)
         self.device.clear_logcat()
         self.clear_app_data()
 
+    def tune_performance(self):
+        """Sets various performance-oriented parameters, to reduce jitter.
+
+        For more information, see https://bugzilla.mozilla.org/show_bug.cgi?id=1547135.
+        """
+        self.log.info("tuning android device performance")
+        self.set_scheduler()
+        self.set_svc_power_stayon()
+        device_name = self.device.shell_output('getprop ro.product.model')
+        if (self.device._have_su or self.device._have_android_su):
+            # all commands require root shell from here on
+            self.set_virtual_memory_parameters()
+            self.turn_off_services()
+            self.set_cpu_performance_parameters(device_name)
+            self.set_gpu_performance_parameters(device_name)
+            self.set_kernel_performance_parameters()
+        self.device.clear_logcat()
+
+    def _set_value_and_check_exitcode(self, file_name, value):
+        self.log.info('setting {} to {}'.format(file_name, value))
+        process = self.device.shell(' '.join(['echo', str(value), '>', str(file_name)]), root=True)
+        if process.exitcode == 0:
+            self.log.info('successfully set {} to {}'.format(file_name, value))
+        else:
+            self.log.warning('command failed with exitcode {}'.format(str(process.exitcode)))
+
+    def set_svc_power_stayon(self):
+        self.log.info('set device to stay awake on usb')
+        self.device.shell('svc power stayon usb')
+
+    def set_scheduler(self):
+        self.log.info('setting scheduler to noop')
+        scheduler_location = '/sys/block/sda/queue/scheduler'
+
+        self._set_value_and_check_exitcode(scheduler_location, 'noop')
+
+    def turn_off_services(self):
+        services = [
+            'mpdecision',
+            'thermal-engine',
+            'thermald',
+        ]
+        for service in services:
+            self.log.info(' '.join(['turning off service:', service]))
+            self.device.shell(' '.join(['stop', service]), root=True)
+
+        services_list_output = self.device.shell_output('service list')
+        for service in services:
+            if service not in services_list_output:
+                self.log.info(' '.join(['successfully terminated:', service]))
+            else:
+                self.log.warning(' '.join(['failed to terminate:', service]))
+
+    def disable_animations(self):
+        self.log.info('disabling animations')
+        commands = {
+            'animator_duration_scale': 0.0,
+            'transition_animation_scale': 0.0,
+            'window_animation_scale': 0.0
+        }
+
+        for key, value in commands.items():
+            command = ' '.join(['settings', 'put', 'global', key, str(value)])
+            self.log.info('setting {} to {}'.format(key, value))
+            self.device.shell(command)
+
+    def restore_animations(self):
+        # animation settings are not restored to default by reboot
+        self.log.info('restoring animations')
+        commands = {
+            'animator_duration_scale': 1.0,
+            'transition_animation_scale': 1.0,
+            'window_animation_scale': 1.0
+        }
+
+        for key, value in commands.items():
+            command = ' '.join(['settings', 'put', 'global', key, str(value)])
+            self.device.shell(command)
+
+    def set_virtual_memory_parameters(self):
+        self.log.info('setting virtual memory parameters')
+        commands = {
+            '/proc/sys/vm/swappiness': 0,
+            '/proc/sys/vm/dirty_ratio': 85,
+            '/proc/sys/vm/dirty_background_ratio': 70
+        }
+
+        for key, value in commands.items():
+            self._set_value_and_check_exitcode(key, value)
+
+    def set_cpu_performance_parameters(self, device_name):
+        self.log.info('setting cpu performance parameters')
+        commands = {}
+
+        if device_name == 'Pixel 2':
+            # MSM8998 (4x 2.35GHz, 4x 1.9GHz)
+            # values obtained from:
+            #   /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies
+            #   /sys/devices/system/cpu/cpufreq/policy4/scaling_available_frequencies
+            commands.update({
+                '/sys/devices/system/cpu/cpufreq/policy0/scaling_governor': 'performance',
+                '/sys/devices/system/cpu/cpufreq/policy4/scaling_governor': 'performance',
+                '/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq': '1900800',
+                '/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq': '2457600',
+            })
+        elif device_name == 'Moto G (5)':
+            pass
+        else:
+            pass
+
+        for key, value in commands.items():
+            self._set_value_and_check_exitcode(key, value)
+
+    def set_gpu_performance_parameters(self, device_name):
+        self.log.info('setting gpu performance parameters')
+        commands = {
+            '/sys/class/kgsl/kgsl-3d0/bus_split': '0',
+            '/sys/class/kgsl/kgsl-3d0/force_bus_on': '1',
+            '/sys/class/kgsl/kgsl-3d0/force_rail_on': '1',
+            '/sys/class/kgsl/kgsl-3d0/force_clk_on': '1',
+            '/sys/class/kgsl/kgsl-3d0/force_no_nap': '1',
+            '/sys/class/kgsl/kgsl-3d0/idle_timer': '1000000',
+        }
+
+        if device_name == 'Pixel 2':
+            # Adreno 540 (710MHz)
+            # values obtained from:
+            #   /sys/devices/soc/5000000.qcom,kgsl-3d0/kgsl/kgsl-3d0/max_clk_mhz
+            commands.update({
+                '/sys/devices/soc/5000000.qcom,kgsl-3d0/devfreq/'
+                '5000000.qcom,kgsl-3d0/governor': 'performance',
+                '/sys/devices/soc/soc:qcom,kgsl-busmon/devfreq/'
+                'soc:qcom,kgsl-busmon/governor': 'performance',
+                '/sys/devices/soc/5000000.qcom,kgsl-3d0/kgsl/kgsl-3d0/min_clock_mhz': '710',
+            })
+        elif device_name == 'Moto G (5)':
+            pass
+        else:
+            pass
+        for key, value in commands.items():
+            self._set_value_and_check_exitcode(key, value)
+
+    def set_kernel_performance_parameters(self):
+        self.log.info('setting kernel performance parameters')
+        commands = {
+            '/sys/kernel/debug/msm-bus-dbg/shell-client/update_request': '1',
+            '/sys/kernel/debug/msm-bus-dbg/shell-client/mas': '1',
+            '/sys/kernel/debug/msm-bus-dbg/shell-client/ab': '0',
+            '/sys/kernel/debug/msm-bus-dbg/shell-client/slv': '512',
+        }
+        for key, value in commands.items():
+            self._set_value_and_check_exitcode(key, value)
+
     def clear_app_data(self):
         self.log.info("clearing %s app data" % self.config['binary'])
         self.device.shell("pm clear %s" % self.config['binary'])
 
     def create_raptor_sdcard_folder(self):
         # for android/geckoview, create a top-level raptor folder on the device
         # sdcard; if it already exists remove it so we start fresh each time
         self.device_raptor_dir = "/sdcard/raptor"
@@ -736,16 +906,18 @@ class RaptorAndroid(Raptor):
                                             url='about:blank',
                                             e10s=True,
                                             fail_if_running=False)
         except Exception as e:
             self.log.error("Exception launching %s" % self.config['binary'])
             self.log.error("Exception: %s %s" % (type(e).__name__, str(e)))
             if self.config['power_test']:
                 finish_android_power_test(self, test_name)
+            if self.config['cpu_test']:
+                generate_android_cpu_profile(self, test_name)
             raise
 
         # give our control server the device and app info
         self.control_server.device = self.device
         self.control_server.app_name = self.config['binary']
 
     def copy_cert_db(self, source_dir, target_dir):
         # copy browser cert db (that was previously created via certutil) from source to target
@@ -769,17 +941,18 @@ class RaptorAndroid(Raptor):
                 self.run_test_warm(test, timeout)
 
         except SignalHandlerException:
             self.device.stop_application(self.config['binary'])
 
         finally:
             if self.config['power_test']:
                 finish_android_power_test(self, test['name'])
-
+            if self.config['cpu_test']:
+                generate_android_cpu_profile(self, test['name'])
             self.run_test_teardown()
 
     def run_test_cold(self, test, timeout=None):
         '''
         Run the Raptor test but restart the entire browser app between page-cycles.
 
         Note: For page-load tests, playback will only be started once - at the beginning of all
         browser cycles, and then stopped after all cycles are finished. The proxy is set via prefs
@@ -802,16 +975,19 @@ class RaptorAndroid(Raptor):
         The default will be to run in warm mode; unless 'cold = true' is set in the test INI.
         '''
         self.log.info("test %s is running in cold mode; browser WILL be restarted between "
                       "page cycles" % test['name'])
 
         if self.config['power_test']:
             init_android_power_test(self)
 
+        if self.config['cpu_test']:
+            generate_android_cpu_profile(self, test['name'])
+
         for test['browser_cycle'] in range(1, test['expected_browser_cycles'] + 1):
 
             self.log.info("begin browser cycle %d of %d for test %s"
                           % (test['browser_cycle'], test['expected_browser_cycles'], test['name']))
 
             self.run_test_setup(test)
 
             if test['browser_cycle'] == 1:
@@ -871,16 +1047,19 @@ class RaptorAndroid(Raptor):
                 break
 
     def run_test_warm(self, test, timeout=None):
         self.log.info("test %s is running in warm mode; browser will NOT be restarted between "
                       "page cycles" % test['name'])
         if self.config['power_test']:
             init_android_power_test(self)
 
+        if self.config['cpu_test']:
+            generate_android_cpu_profile(self, test['name'])
+
         self.run_test_setup(test)
         self.create_raptor_sdcard_folder()
 
         if test.get('playback') is not None:
             self.start_playback(test)
 
         if self.config['host'] not in ('localhost', '127.0.0.1'):
             self.delete_proxy_settings_from_profile()
@@ -972,27 +1151,31 @@ def main(args=sys.argv[1:]):
                           run_local=args.run_local,
                           obj_path=args.obj_path,
                           gecko_profile=args.gecko_profile,
                           gecko_profile_interval=args.gecko_profile_interval,
                           gecko_profile_entries=args.gecko_profile_entries,
                           symbols_path=args.symbols_path,
                           host=args.host,
                           power_test=args.power_test,
+                          cpu_test=args.cpu_test,
                           memory_test=args.memory_test,
                           is_release_build=args.is_release_build,
                           debug_mode=args.debug_mode,
                           post_startup_delay=args.post_startup_delay,
                           activity=args.activity,
                           intent=args.intent,
                           interrupt_handler=SignalHandler(),
                           )
 
     raptor.create_browser_profile()
     raptor.create_browser_handler()
+    if type(raptor) == RaptorAndroid:
+        # only Raptor Android supports device performance tuning
+        raptor.tune_performance()
     raptor.start_control_server()
 
     try:
         for next_test in raptor_test_list:
             raptor.run_test(next_test, timeout=int(next_test['page_timeout']))
 
         success = raptor.process_results(raptor_test_names)
 
new file mode 100644
--- /dev/null
+++ b/testing/raptor/test/files/top-info.txt
@@ -0,0 +1,41 @@
+Tasks: 142 total,   1 running, 140 sleeping,   0 stopped,   1 zombie
+Mem:   1548824k total,  1234756k used,   314068k free,    37080k buffers
+Swap:        0k total,        0k used,        0k free,   552360k cached
+200%cpu 122%user   9%nice  50%sys  13%idle   0%iow   0%irq   6%sirq   0%host
+  PID USER        [%CPU]%CPU  %MEM     TIME+ ARGS
+17504 u0_a83       93.7 93.7  14.2   0:12.12 org.mozilla.geckoview_example
+17529 u0_a83       43.7 43.7  19.3   0:11.80 org.mozilla.geckoview_example:tab
+ 7030 u0_a54       28.1 28.1   5.6   0:05.47 com.google.android.tts
+ 1598 root          9.3  9.3   0.1   0:13.73 dhcpclient -i eth0
+ 1667 system        6.2  6.2   9.6  16:10.78 system_server
+ 1400 system        6.2  6.2   0.2   8:15.20 android.hardware.sensors@1.0-service
+17729 shell         3.1  3.1   0.1   0:00.02 top -O %CPU -n 1
+ 1411 system        3.1  3.1   0.7  23:06.11 surfaceflinger
+17497 shell         0.0  0.0   0.1   0:00.01 sh -
+17321 root          0.0  0.0   0.0   0:00.13 [kworker/0:1]
+17320 root          0.0  0.0   0.0   0:00.15 [kworker/u4:1]
+17306 root          0.0  0.0   0.0   0:00.21 [kworker/u5:1]
+16545 root          0.0  0.0   0.0   0:00.17 [kworker/0:0]
+16543 root          0.0  0.0   0.0   0:00.15 [kworker/u4:2]
+16411 root          0.0  0.0   0.0   0:00.41 [kworker/u5:2]
+15827 root          0.0  0.0   0.0   0:00.04 [kworker/1:2]
+14998 root          0.0  0.0   0.0   0:00.03 [kworker/1:1]
+14996 root          0.0  0.0   0.0   0:00.38 [kworker/0:2]
+14790 root          0.0  0.0   0.0   0:01.04 [kworker/u5:0]
+14167 root          0.0  0.0   0.0   0:01.32 [kworker/u4:0]
+11922 u0_a50        0.0  0.0   6.9   0:00.80 com.google.android.apps.docs
+11906 u0_a67        0.0  0.0   5.0   0:00.25 com.google.android.apps.photos
+11887 u0_a11        0.0  0.0   4.3   0:00.25 com.android.documentsui
+11864 u0_a6         0.0  0.0   3.3   0:00.19 com.android.defcontainer
+10866 u0_a15        0.0  0.0   3.3   0:00.04 com.google.android.partnersetup
+ 8956 u0_a1         0.0  0.0   3.7   0:00.40 com.android.providers.calendar
+ 8070 u0_a10        0.0  0.0   6.7   0:01.21 com.google.android.gms.unstable
+ 6638 u0_a10        0.0  0.0   7.4   0:12.89 com.google.android.gms
+ 2291 u0_a30        0.0  0.0   9.0   5:45.93 com.google.android.googlequicksearchbox:search
+ 2230 u0_a10        0.0  0.0   3.9   0:02.00 com.google.process.gapps
+ 2213 u0_a22        0.0  0.0   7.2   4:12.95 com.google.android.apps.nexuslauncher
+ 2195 u0_a30        0.0  0.0   4.1   0:00.37 com.google.android.googlequicksearchbox:interactor
+ 2163 u0_a10        0.0  0.0   8.2   1:49.32 com.google.android.gms.persistent
+ 1882 radio         0.0  0.0   5.1   0:53.61 com.android.phone
+ 1875 wifi          0.0  0.0   0.4   0:02.25 wpa_supplicant -Dnl80211 -iwlan0 -c/vendor/etc/wifi/wpa_supplicant.conf -g@android:wpa_wla+
+ 1828 webview_zyg+  0.0  0.0   3.0   0:00.45 webview_zygote32
\ No newline at end of file
--- a/testing/raptor/test/python.ini
+++ b/testing/raptor/test/python.ini
@@ -4,8 +4,9 @@ skip-if = python == 3
 
 [test_cmdline.py]
 [test_manifest.py]
 [test_control_server.py]
 [test_utils.py]
 [test_playback.py]
 [test_print_tests.py]
 [test_raptor.py]
+[test_cpu.py]
--- a/testing/raptor/test/test_cmdline.py
+++ b/testing/raptor/test/test_cmdline.py
@@ -12,16 +12,17 @@ from raptor.cmdline import verify_option
 def test_verify_options(filedir):
     args = Namespace(app='firefox',
                      binary='invalid/path',
                      gecko_profile='False',
                      page_cycles=1,
                      page_timeout=60000,
                      debug='True',
                      power_test=False,
+                     cpu_test=False,
                      memory_test=False)
     parser = ArgumentParser()
 
     with pytest.raises(SystemExit):
         verify_options(parser, args)
 
     args.binary = os.path.join(filedir, 'fake_binary.exe')
     verify_options(parser, args)  # assert no exception
@@ -29,49 +30,65 @@ def test_verify_options(filedir):
     args = Namespace(app='geckoview',
                      binary='org.mozilla.geckoview_example',
                      activity='GeckoViewActivity',
                      intent='android.intent.action.MAIN',
                      gecko_profile='False',
                      is_release_build=False,
                      host='sophie',
                      power_test=False,
+                     cpu_test=False,
                      memory_test=False)
     verify_options(parser, args)  # assert no exception
 
     args = Namespace(app='refbrow',
                      binary='org.mozilla.reference.browser',
                      activity='BrowserTestActivity',
                      intent='android.intent.action.MAIN',
                      gecko_profile='False',
                      is_release_build=False,
                      host='sophie',
                      power_test=False,
+                     cpu_test=False,
                      memory_test=False)
     verify_options(parser, args)  # assert no exception
 
     args = Namespace(app='fenix',
                      binary='org.mozilla.fenix.browser',
                      activity='BrowserTestActivity',
                      intent='android.intent.action.VIEW',
                      gecko_profile='False',
                      is_release_build=False,
                      host='sophie',
                      power_test=False,
+                     cpu_test=False,
+                     memory_test=False)
+    verify_options(parser, args)  # assert no exception
+
+    args = Namespace(app='geckoview',
+                     binary='org.mozilla.geckoview_example',
+                     activity='GeckoViewActivity',
+                     intent='android.intent.action.MAIN',
+                     gecko_profile='False',
+                     is_release_build=False,
+                     host='sophie',
+                     power_test=False,
+                     cpu_test=True,
                      memory_test=False)
     verify_options(parser, args)  # assert no exception
 
     args = Namespace(app='refbrow',
                      binary='org.mozilla.reference.browser',
                      activity=None,
                      intent='android.intent.action.MAIN',
                      gecko_profile='False',
                      is_release_build=False,
                      host='sophie',
                      power_test=False,
+                     cpu_test=False,
                      memory_test=False)
     parser = ArgumentParser()
 
     verify_options(parser, args)  # also will work as uses default activity
 
 
 if __name__ == '__main__':
     mozunit.main()
new file mode 100644
--- /dev/null
+++ b/testing/raptor/test/test_cpu.py
@@ -0,0 +1,98 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+from __future__ import absolute_import, unicode_literals
+
+import mozunit
+import os
+import mock
+import sys
+
+from raptor import cpu
+from raptor.raptor import Raptor
+
+# need this so raptor imports work both from /raptor and via mach
+here = os.path.abspath(os.path.dirname(__file__))
+if os.environ.get('SCRIPTSPATH', None) is not None:
+    # in production it is env SCRIPTS_PATH
+    mozharness_dir = os.environ['SCRIPTSPATH']
+else:
+    # locally it's in source tree
+    mozharness_dir = os.path.join(here, '../../mozharness')
+sys.path.insert(0, mozharness_dir)
+
+
+def test_no_device():
+    raptor = Raptor('geckoview', 'org.mozilla.org.mozilla.geckoview_example', cpu_test=True)
+    raptor.device = None
+    resp = cpu.generate_android_cpu_profile(raptor, 'no_control_server_device')
+
+    assert resp is None
+
+
+def test_usage_with_invalid_data_returns_zero():
+    with mock.patch('mozdevice.adb.ADBDevice') as device:
+        with mock.patch('raptor.raptor.RaptorControlServer') as control_server:
+            # Create a device that returns invalid data
+            device.shell_output.return_value = 'geckoview'
+            device._verbose = True
+
+            # Create a control server
+            control_server.cpu_test = True
+            control_server.device = device
+            raptor = Raptor('geckoview', 'org.mozilla.geckoview_example', cpu_test=True)
+            raptor.config['cpu_test'] = True
+            raptor.control_server = control_server
+            raptor.device = device
+
+            # Verify the call to submit data was made
+            cpuinfo_data = {
+                'type': 'cpu',
+                'test': 'usage_with_invalid_data_returns_zero',
+                'unit': '%',
+                'values': {
+                    'browser_cpu_usage': float(0)
+                }
+            }
+            cpu.generate_android_cpu_profile(
+                raptor,
+                "usage_with_invalid_data_returns_zero")
+            control_server.submit_supporting_data.assert_called_once_with(cpuinfo_data)
+
+
+def test_usage_with_output():
+    with mock.patch('mozdevice.adb.ADBDevice') as device:
+        with mock.patch('raptor.raptor.RaptorControlServer') as control_server:
+            # Override the shell output with sample CPU usage details
+            filepath = os.path.abspath(os.path.dirname(__file__)) + '/files/'
+            f = open(filepath + 'top-info.txt', 'r')
+            device.shell_output.return_value = f.read()
+            device._verbose = True
+
+            # Create a control server
+            control_server.cpu_test = True
+            control_server.test_name = 'cpuunittest'
+            control_server.device = device
+            control_server.app_name = 'org.mozilla.geckoview_example'
+            raptor = Raptor('geckoview', 'org.mozilla.geckoview_example', cpu_test=True)
+            raptor.device = device
+            raptor.config['cpu_test'] = True
+            raptor.control_server = control_server
+
+            # Verify the response contains our expected CPU % of 93.7
+            cpuinfo_data = {
+                u'type': u'cpu',
+                u'test': u'usage_with_integer_cpu_info_output',
+                u'unit': u'%',
+                u'values': {
+                    u'browser_cpu_usage': '93.7'
+                }
+            }
+            cpu.generate_android_cpu_profile(
+                raptor,
+                "usage_with_integer_cpu_info_output")
+            control_server.submit_supporting_data.assert_called_once_with(cpuinfo_data)
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -229,21 +229,27 @@ CPP_UNIT_TEST_BINS=$(wildcard $(DIST)/cp
 
 stage-cppunittests: make-stage-dir
 	$(NSINSTALL) -D $(PKG_STAGE)/cppunittest
 ifdef STRIP_COMPILED_TESTS
 	$(foreach bin,$(CPP_UNIT_TEST_BINS),$(OBJCOPY) $(or $(STRIP_FLAGS),--strip-unneeded) $(bin) $(bin:$(DIST)/cppunittests/%=$(PKG_STAGE)/cppunittest/%);)
 else
 	cp -RL $(CPP_UNIT_TEST_BINS) $(PKG_STAGE)/cppunittest
 endif
+ifdef MOZ_COPY_PDBS
+	cp -RL $(addsuffix .pdb,$(basename $(CPP_UNIT_TEST_BINS))) $(PKG_STAGE)/cppunittest
+endif
 ifdef STRIP_COMPILED_TESTS
 	$(OBJCOPY) $(or $(STRIP_FLAGS),--strip-unneeded) $(DIST)/bin/jsapi-tests$(BIN_SUFFIX) $(PKG_STAGE)/cppunittest/jsapi-tests$(BIN_SUFFIX)
 else
 	cp -RL $(DIST)/bin/jsapi-tests$(BIN_SUFFIX) $(PKG_STAGE)/cppunittest
 endif
+ifdef MOZ_COPY_PDBS
+	cp -RL $(DIST)/bin/jsapi-tests.pdb $(PKG_STAGE)/cppunittest
+endif
 
 stage-steeplechase: make-stage-dir
 	$(NSINSTALL) -D $(PKG_STAGE)/steeplechase/
 	cp -RL $(DEPTH)/_tests/steeplechase $(PKG_STAGE)/steeplechase/tests
 	cp -RL $(DIST)/xpi-stage/specialpowers $(PKG_STAGE)/steeplechase
 	cp -RL $(topsrcdir)/testing/profiles/common/user.js $(PKG_STAGE)/steeplechase/prefs_general.js
 
 TEST_EXTENSIONS := \
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[track-cue-negative-timestamp-events.html]
-
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini
@@ -1,3 +1,4 @@
 [track-cue-rendering-after-controls-removed.html]
   disabled:
     if (os == "win") and (processor == "aarch64"): https://bugzilla.mozilla.org/show_bug.cgi?id=1534423
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html.ini
@@ -1,6 +1,7 @@
 [track-cue-rendering-line-doesnt-fit.html]
   disabled:
     if (os == "win") and (processor == "aarch64"): https://bugzilla.mozilla.org/show_bug.cgi?id=1548066
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
   fuzzy:
     if webrender and os == "win": maxDifference=1;totalPixels=0-68
 
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html.ini
@@ -1,3 +1,4 @@
 [track-cue-rendering-transformed-video.html]
   disabled:
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
     if (os == "win") and (processor == "aarch64"): https://bugzilla.mozilla.org/show_bug.cgi?id=1548066
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[track-cues-pause-on-exit.html]
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[track-cues-sorted-before-dispatch.html]
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[track-disabled-addcue.html]
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[track-disabled.html]
-
--- a/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini
+++ b/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini
@@ -1,13 +1,14 @@
 [sub-sample-buffer-stitching.html]
   disabled:
     if (os == 'win' and processor == 'aarch64'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533911
-    if (os == 'win' and version == '6.1.7601'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
-    if (os == 'linux' and bits == 32): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
+    if (os == 'linux'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
+    if (os == 'win'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
+    if (os == 'mac'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
   [# AUDIT TASK RUNNER FINISHED: 2 out of 2 tasks were failed.]
     expected: FAIL
 
   [X Stitched sine-wave buffers at sample rate 44100 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732956767082214,0.4248766303062439,0.4807544946670532,0.5347436666488647,0.5866319537162781,0.6362155675888062,0.683299720287323,0.7276993989944458,0.7692402005195618,0.8077588677406311...\] with an element-wise tolerance of {"absoluteThreshold":0.000090957,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[2003\]\t-9.6732087433338165e-2\t-9.6823699772357941e-2\t9.1612339019775391e-5\t9.4617680624852212e-4\t9.0957000000000003e-5\n\t[2004\]\t-3.4187544137239456e-2\t-3.4279607236385345e-2\t9.2063099145889282e-5\t2.6856520995424621e-3\t9.0957000000000003e-5\n\t[2005\]\t2.8491314500570297e-2\t2.8398986905813217e-2\t9.2327594757080078e-5\t3.2510876202481997e-3\t9.0957000000000003e-5\n\t[2006\]\t9.1058239340782166e-2\t9.0966261923313141e-2\t9.1977417469024658e-5\t1.0111157205356415e-3\t9.0957000000000003e-5\n\t[2007\]\t1.5326742827892303e-1\t1.5317615866661072e-1\t9.1269612312316895e-5\t5.9584737668585898e-4\t9.0957000000000003e-5\n\t...and 38045 more errors.\n\tMax AbsError of 2.0274701528251171e-3 at index of 44050.\n\t[44050\]\t-7.1237324737012386e-3\t-5.0962623208761215e-3\t2.0274701528251171e-3\t3.9783473164634225e-1\t9.0957000000000003e-5\n\tMax RelError of 5.5714977262789269e+1 at index of 30419.\n\t[30419\]\t-1.4247581129893661e-3\t-2.5121373255387880e-5\t1.3996367397339782e-3\t5.5714977262789269e+1\t9.0957000000000003e-5\n]
     expected: FAIL
 
   [< [buffer-stitching-2\] 1 out of 3 assertions were failed.]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/webrtc/RTCDataChannel-bufferedAmount.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[RTCDataChannel-bufferedAmount.html]
-  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1529612
-
--- a/testing/web-platform/meta/webrtc/RTCPeerConnection-createDataChannel.html.ini
+++ b/testing/web-platform/meta/webrtc/RTCPeerConnection-createDataChannel.html.ini
@@ -14,53 +14,48 @@
   [createDataChannel with priority "high" should succeed]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531100
 
   [createDataChannel with invalid priority should throw TypeError]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531100
 
-  [createDataChannel with negotiated false and long label should throw TypeError]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531908
-
-  [createDataChannel with negotiated true and long label and long protocol should succeed]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531908
-
   [Channels created after SCTP transport is established should have id assigned]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1526253
 
   [createDataChannel with negotiated false should succeed]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529695
 
   [createDataChannel with negotiated false and id 42 should ignore the id]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529695
 
   [Reusing a data channel id that is in use (after setRemoteDescription, negotiated via DCEP) should throw OperationError]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1547106
 
   [Reusing a data channel id that is in use (after setRemoteDescription) should throw OperationError]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1547106
 
   [New data channel should be in the connecting state after creation (after connection establishment)]
     expected: FAIL
-
-  [createDataChannel with too long label should throw TypeError]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1548636
 
   [Reusing a data channel id that is in use should throw OperationError]
     expected: FAIL
-
-  [createDataChannel with too long label (2 byte unicode) should throw TypeError]
-    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1547106
 
   [createDataChannel with negotiated true and id null should throw TypeError]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1550497
 
   [createDataChannel with both maxPacketLifeTime and maxRetransmits null should succeed]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1550497
 
   [Channels created (after setRemoteDescription) should have id assigned]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=797135
 
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_center.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_center.html.ini
@@ -1,2 +1,5 @@
 [align_center.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: align_center-ref.html:maxDifference=0-2;totalPixels=0-5000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_center_position_50.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/align_center_position_50.html.ini
@@ -1,2 +1,5 @@
 [align_center_position_50.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: align_center_position_50-ref.html:maxDifference=0-2;totalPixels=0-5000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/basic.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/basic.html.ini
@@ -1,5 +1,5 @@
 [basic.html]
-  expected: FAIL
   disabled:
-    if debug and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1534541
-    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1534541
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: basic-ref.html:maxDifference=0-2;totalPixels=0-41200
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/bidi_ruby.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/bidi_ruby.html.ini
@@ -1,2 +1,5 @@
 [bidi_ruby.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: bidi_ruby-ref.html:maxDifference=0-2;totalPixels=0-43000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u0041_first.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u0041_first.html.ini
@@ -1,2 +1,5 @@
 [u0041_first.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: u0041_first-ref.html:maxDifference=0-2;totalPixels=0-100
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u06E9_no_strong_dir.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/bidi/u06E9_no_strong_dir.html.ini
@@ -1,2 +1,5 @@
 [u06E9_no_strong_dir.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: u06E9_no_strong_dir-ref.html:maxDifference=0-2;totalPixels=0-43100
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely.html.ini
@@ -1,6 +1,2 @@
 [9_cues_overlapping_completely.html]
-  disabled:
-    if debug and (os == "linux"): bug1488673
-  expected:
-    if os == "android" and not e10s: ERROR
-    TIMEOUT
+  expected: FAIL
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely_all_cues_have_same_timestamp.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely_all_cues_have_same_timestamp.html.ini
@@ -1,6 +1,2 @@
 [9_cues_overlapping_completely_all_cues_have_same_timestamp.html]
-  disabled:
-    if debug and (os == "linux"): bug1488673
-  expected:
-    if os == "android" and not e10s: ERROR
-    TIMEOUT
+  expected: FAIL
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_90.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_90.html.ini
@@ -1,4 +1,5 @@
 [size_90.html]
-  expected:
-    if (os == "android") and e10s: TIMEOUT
-    FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: size_90-ref.html:maxDifference=0-2;totalPixels=0-100
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_99.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/evil/size_99.html.ini
@@ -1,4 +1,5 @@
 [size_99.html]
-  expected:
-    if (os == "android") and e10s: TIMEOUT
-    FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: size_99-ref.html:maxDifference=0-2;totalPixels=0-100
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_hex.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_hex.html.ini
@@ -1,2 +1,5 @@
 [color_hex.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: color_hex-ref.html:maxDifference=0-2;totalPixels=0-41200
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_hsla.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_hsla.html.ini
@@ -1,2 +1,5 @@
 [color_hsla.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: color_hsla-ref.html:maxDifference=0-2;totalPixels=0-41200
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_rgba.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_rgba.html.ini
@@ -1,2 +1,5 @@
 [color_rgba.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: color_rgba-ref.html:maxDifference=0-2;totalPixels=0-41200
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/cue_selector_single_colon.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/cue_selector_single_colon.html.ini
@@ -1,3 +1,5 @@
 [cue_selector_single_colon.html]
-  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1536319
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: cue_selector_single_colon-ref.html:maxDifference=0-2;totalPixels=0-1100
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/outline_properties.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/outline_properties.html.ini
@@ -1,2 +1,5 @@
 [outline_properties.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: outline_properties-ref.html:maxDifference=0-2;totalPixels=0-2700
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/outline_shorthand.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/outline_shorthand.html.ini
@@ -1,2 +1,5 @@
 [outline_shorthand.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: outline_shorthand-ref.html:maxDifference=0-2;totalPixels=0-2700
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_overline.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_overline.html.ini
@@ -1,2 +1,5 @@
 [text-decoration_overline.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: text-decoration_overline-ref.html:maxDifference=0-2;totalPixels=0-1600
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_overline_underline_line-through.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_overline_underline_line-through.html.ini
@@ -1,2 +1,5 @@
 [text-decoration_overline_underline_line-through.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: text-decoration_overline_underline_line-ref.html:maxDifference=0-2;totalPixels=0-42000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_underline.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_underline.html.ini
@@ -1,2 +1,5 @@
 [text-decoration_underline.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: text-decoration_underline-ref.html:maxDifference=0-2;totalPixels=0-42000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/white-space_normal_wrapped.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/white-space_normal_wrapped.html.ini
@@ -1,2 +1,5 @@
 [white-space_normal_wrapped.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: white-space_normal_wrapped-ref.html:maxDifference=0-2;totalPixels=0-500
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/white-space_nowrap_wrapped.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue/white-space_nowrap_wrapped.html.ini
@@ -1,2 +1,5 @@
 [white-space_nowrap_wrapped.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: white-space_nowrap_wrapped-ref.html:maxDifference=0-2;totalPixels=0-500
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/background_box.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/background_box.html.ini
@@ -1,2 +1,5 @@
 [background_box.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: background_box-ref.html:maxDifference=0-2;totalPixels=0-4000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_namespace.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_namespace.html.ini
@@ -1,2 +1,5 @@
 [bold_namespace.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: bold_namespace-ref.html:maxDifference=0-2;totalPixels=0-300
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_namespace.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_namespace.html.ini
@@ -1,2 +1,5 @@
 [class_namespace.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: class_namespace-ref.html:maxDifference=0-2;totalPixels=0-300
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/cue_func_selector_single_colon.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/cue_func_selector_single_colon.html.ini
@@ -1,2 +1,4 @@
 [cue_func_selector_single_colon.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_namespace.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_namespace.html.ini
@@ -1,2 +1,5 @@
 [italic_namespace.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: italic_namespace-ref.html:maxDifference=0-2;totalPixels=0-300
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/not_allowed_properties.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/not_allowed_properties.html.ini
@@ -1,2 +1,5 @@
 [not_allowed_properties.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: not_allowed_properties-ref.html:maxDifference=0-2;totalPixels=0-2200
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/type_selector_root.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/type_selector_root.html.ini
@@ -1,2 +1,4 @@
 [type_selector_root.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_namespace.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_namespace.html.ini
@@ -1,2 +1,5 @@
 [underline_namespace.html]
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: underline_namespace-ref.html:maxDifference=0-2;totalPixels=0-300
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_namespace.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_namespace.html.ini
@@ -1,4 +1,5 @@
 [voice_namespace.html]
   disabled:
-    if (os == "win") and (processor == "aarch64"): https://bugzilla.mozilla.org/show_bug.cgi?id=1519947
-  expected: FAIL
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: voice_namespace-ref.html:maxDifference=0-2;totalPixels=0-300
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/bold_object_default_font-style.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/bold_object_default_font-style.html.ini
@@ -1,4 +1,5 @@
 [bold_object_default_font-style.html]
   disabled:
-    if (os == "win") and (processor == "aarch64"): https://bugzilla.mozilla.org/show_bug.cgi?id=1534755
-  expected: FAIL
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: bold_object_default_font-style-ref.html:maxDifference=0-2;totalPixels=0-1000
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/italic_object_default_font-style.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/italic_object_default_font-style.html.ini
@@ -1,3 +1,5 @@
 [italic_object_default_font-style.html]
-  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1536319
-  expected: FAIL
+  disabled:
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: italic_object_default_font-style-ref.html:maxDifference=0-2;totalPixels=0-1800
--- a/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/underline_object_default_font-style.html.ini
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/underline_object_default_font-style.html.ini
@@ -1,4 +1,5 @@
 [underline_object_default_font-style.html]
   disabled:
-    if verify and (os == "mac"): fails in verify mode
-  expected: FAIL
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1536762
+  fuzzy: underline_object_default_font-style-ref.html:maxDifference=0-2;totalPixels=0-1800
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5530,513 +5530,502 @@
     "kind": "exponential",
     "low": 7,
     "high": 60,
     "n_buckets" : 10,
     "description": "PLACES: Days from last maintenance"
   },
   "UPDATE_CHECK_NO_UPDATE_EXTERNAL" : {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: count of no updates were found for a background update check (externally initiated)"
   },
   "UPDATE_CHECK_NO_UPDATE_NOTIFY" : {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: count of no updates were found for a background update check (timer initiated)"
   },
   "UPDATE_CHECK_CODE_EXTERNAL": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: background update check result code except for no updates found (externally initiated)"
   },
   "UPDATE_CHECK_CODE_NOTIFY": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: background update check result code except for no updates found (timer initiated)"
   },
   "UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (externally initiated)"
   },
   "UPDATE_CHECK_EXTENDED_ERROR_NOTIFY": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (timer initiated)"
   },
   "UPDATE_INVALID_LASTUPDATETIME_EXTERNAL": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: count of systems that have a last update time greater than the current time (externally initiated)"
   },
   "UPDATE_INVALID_LASTUPDATETIME_NOTIFY": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: count of systems that have a last update time greater than the current time (timer initiated)"
   },
   "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "n_buckets": 60,
     "high": 365,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: interval in days since the last background update check (externally initiated)"
   },
   "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "n_buckets": 30,
     "high": 180,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1137447],
     "description": "Update: interval in days since the last background update check (timer initiated)"
   },
   "UPDATE_PING_COUNT_EXTERNAL": {
     "record_in_processes": ["main"],
-    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com", "rstrong@mozilla.com"],
     "expires_in_version": "never",