Merge autoland to mozilla-central. a=merge
authorNorisz Fay <nfay@mozilla.com>
Fri, 08 Apr 2022 12:22:39 +0300
changeset 684162 f9afaaf33533d59d88eb31c159cf5e6676808c30
parent 684160 80c34c70f1aecc16049554c92dac66975493998c (current diff)
parent 684153 ed124d06d67623a6227a41ecffd8533733755976 (diff)
child 684179 d62c69b51528bc1a7c555d666ec3b6d5ca22cc01
push id16598
push userffxbld-merge
push dateMon, 02 May 2022 14:23:32 +0000
treeherdermozilla-beta@de86a81c7a63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone101.0a1
first release with
nightly linux32
f9afaaf33533 / 101.0a1 / 20220408094506 / files
nightly linux64
f9afaaf33533 / 101.0a1 / 20220408094506 / files
nightly mac
f9afaaf33533 / 101.0a1 / 20220408094506 / files
nightly win32
f9afaaf33533 / 101.0a1 / 20220408094506 / files
nightly win64
f9afaaf33533 / 101.0a1 / 20220408094506 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
js/src/tests/test262/built-ins/Array/prototype/join/S15.4.4.5_A6.7.js
js/src/tests/test262/built-ins/Array/prototype/pop/S15.4.4.6_A5.7.js
js/src/tests/test262/built-ins/Array/prototype/push/S15.4.4.7_A6.7.js
js/src/tests/test262/built-ins/Array/prototype/reverse/S15.4.4.8_A5.7.js
js/src/tests/test262/built-ins/Array/prototype/shift/S15.4.4.9_A5.7.js
js/src/tests/test262/built-ins/Array/prototype/slice/S15.4.4.10_A5.7.js
js/src/tests/test262/built-ins/Array/prototype/sort/S15.4.4.11_A7.7.js
js/src/tests/test262/built-ins/Array/prototype/splice/S15.4.4.12_A5.7.js
js/src/tests/test262/built-ins/Array/prototype/toLocaleString/S15.4.4.3_A4.7.js
js/src/tests/test262/built-ins/Array/prototype/toString/S15.4.4.2_A4.7.js
js/src/tests/test262/built-ins/Array/prototype/unshift/S15.4.4.13_A5.7.js
js/src/tests/test262/built-ins/Boolean/prototype/S15.6.3.1_A3.js
js/src/tests/test262/built-ins/Boolean/prototype/S15.6.4_A1.js
js/src/tests/test262/built-ins/Function/prototype/apply/S15.3.4.3_A8_T1.js
js/src/tests/test262/built-ins/Function/prototype/apply/S15.3.4.3_A8_T2.js
js/src/tests/test262/built-ins/Function/prototype/call/S15.3.4.4_A7_T1.js
js/src/tests/test262/built-ins/Function/prototype/call/S15.3.4.4_A7_T2.js
js/src/tests/test262/built-ins/Realm/prototype/importValue/import-value_FIXTURE.js
js/src/tests/test262/built-ins/Realm/shell.js
js/src/tests/test262/built-ins/Set/symbol-as-entry.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/throw-range-error-from-ToTemporalOverflow.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateAdd/throw-type-error-from-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateFromFields/throw-range-error-from-ISODateFromFields.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateFromFields/throw-type-error-fields-is-not-object.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateFromFields/throw-type-error-from-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dateUntil/throws-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/day/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfWeek/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/dayOfYear/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInMonth/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInWeek/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/daysInYear/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/era/branding.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/eraYear/branding.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/month/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/monthCode/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/Calendar/prototype/year/throw-type-error-RequireInternalSlot.js
js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/era/branding.js
js/src/tests/test262/built-ins/Temporal/PlainDate/prototype/eraYear/branding.js
js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/era/branding.js
js/src/tests/test262/built-ins/Temporal/PlainDateTime/prototype/eraYear/branding.js
js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/era/branding.js
js/src/tests/test262/built-ins/Temporal/PlainYearMonth/prototype/eraYear/branding.js
js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/era/branding.js
js/src/tests/test262/built-ins/Temporal/ZonedDateTime/prototype/eraYear/branding.js
js/src/tests/test262/built-ins/TypedArray/prototype/set/array-arg-targetbuffer-detached-on-get-src-value-throws.js
js/src/tests/test262/built-ins/TypedArray/prototype/sort/BigInt/detached-buffer-comparefn.js
js/src/tests/test262/built-ins/TypedArray/prototype/sort/detached-buffer-comparefn.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/detached-when-species-retrieved-different-type.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/detached-when-species-retrieved-same-type.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-custom-species.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-not-object-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-species-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-species-not-ctor-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-species-prototype-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-custom.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-not-ctor.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-prototype-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-value-not-obj-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/detached-when-species-retrieved-different-type.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/detached-when-species-retrieved-same-type.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-custom-species.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-not-object-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-species-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-species-not-ctor-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-species-null.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-species-prototype-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-species-undefined.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-access-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-custom.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-not-ctor.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-prototype-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-throws.js
js/src/tests/test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-value-not-obj-throws.js
js/src/tests/test262/language/expressions/call/tco-cross-realm-class-construct.js
js/src/tests/test262/language/expressions/call/tco-cross-realm-class-derived-construct.js
js/src/tests/test262/language/expressions/call/tco-cross-realm-fun-call.js
js/src/tests/test262/language/expressions/call/tco-cross-realm-fun-construct.js
layout/style/test/test_condition_text_assignment.html
testing/web-platform/meta/css/css-contain/container-queries/query-content-box.html.ini
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -191,18 +191,16 @@ module.exports = {
         "modules/libjar/zipwriter/test/unit/test_alignment.js",
         "modules/libjar/zipwriter/test/unit/test_bug419769_2.js",
         "modules/libjar/zipwriter/test/unit/test_storedata.js",
         "modules/libjar/zipwriter/test/unit/test_zippermissions.js",
         "modules/libpref/test/unit/test_changeType.js",
         "modules/libpref/test/unit/test_dirtyPrefs.js",
         "toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js",
         "toolkit/mozapps/update/tests/unit_aus_update/testConstants.js",
-        "xpcom/tests/unit/test_hidden_files.js",
-        "xpcom/tests/unit/test_localfile.js",
 
         // These are more complicated bugs which may require some in-depth
         // investigation or different solutions. They are also likely to be
         // a reasonable size.
         "browser/components/**",
         "browser/modules/**",
         "dom/**",
         "netwerk/**",
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -424,16 +424,24 @@ pref("browser.urlbar.quicksuggest.nonSpo
 
 // Whether Remote Settings is enabled as a quick suggest source.
 pref("browser.urlbar.quicksuggest.remoteSettings.enabled", true);
 
 // Whether quick suggest results can be shown in position specified in the
 // suggestions.
 pref("browser.urlbar.quicksuggest.allowPositionInSuggestions", true);
 
+// Whether non-sponsored quick suggest results are subject to impression
+// frequency caps.
+pref("browser.urlbar.quicksuggest.impressionCaps.nonSponsoredEnabled", false);
+
+// Whether sponsored quick suggest results are subject to impression frequency
+// caps.
+pref("browser.urlbar.quicksuggest.impressionCaps.sponsoredEnabled", false);
+
 // Whether unit conversion is enabled.
 #ifdef NIGHTLY_BUILD
 pref("browser.urlbar.unitConversion.enabled", true);
 #else
 pref("browser.urlbar.unitConversion.enabled", false);
 #endif
 
 // Whether to show search suggestions before general results like history and
@@ -2696,8 +2704,13 @@ pref("svg.context-properties.content.all
 // last decimal are prefixed by `_score` and reference the functions called in
 // SnapshotScorer.
 pref("browser.snapshots.score.Visit", 1);
 pref("browser.snapshots.score.CurrentSession", 1);
 pref("browser.snapshots.score.InNavigation", 3);
 pref("browser.snapshots.score.IsOverlappingVisit", 3);
 pref("browser.snapshots.score.IsUserPersisted", 1);
 pref("browser.snapshots.score.IsUsedRemoved", -10);
+
+// Expiration days for snapshots.
+pref("browser.places.snapshots.expiration.days", 210);
+// For user managed snapshots we use more than a year, to support yearly tasks.
+pref("browser.places.snapshots.expiration.userManaged.days", 420);
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -2048,72 +2048,16 @@ var BookmarkingUI = {
   },
 
   onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
     this._panelMenuView.uninit();
     delete this._panelMenuView;
     aEvent.target.removeEventListener("ViewHiding", this);
   },
 
-  toggleMenuButtonInToolbar(triggerNode) {
-    let placement = CustomizableUI.getPlacementOfWidget(
-      this.BOOKMARK_BUTTON_ID
-    );
-    const area = CustomizableUI.AREA_NAVBAR;
-    if (!placement) {
-      // Button is in the palette, so we can move it to the navbar.
-      let pos;
-      let widgetIDs = CustomizableUI.getWidgetIdsInArea(
-        CustomizableUI.AREA_NAVBAR
-      );
-      // If there's a spring inside the navbar, find it and use that as the
-      // placement marker.
-      let lastSpringID = null;
-      for (let i = widgetIDs.length - 1; i >= 0; --i) {
-        let id = widgetIDs[i];
-        if (CustomizableUI.isSpecialWidget(id) && /spring/.test(id)) {
-          lastSpringID = id;
-          break;
-        }
-      }
-      if (lastSpringID) {
-        pos = CustomizableUI.getPlacementOfWidget(lastSpringID).position + 1;
-      } else {
-        // Next alternative is to use the searchbar as the placement marker.
-        const searchWidgetID = "search-container";
-        if (widgetIDs.includes(searchWidgetID)) {
-          pos =
-            CustomizableUI.getPlacementOfWidget(searchWidgetID).position + 1;
-        } else {
-          // Last alternative is to use the navbar as the placement marker.
-          pos =
-            CustomizableUI.getPlacementOfWidget("urlbar-container").position +
-            1;
-        }
-      }
-
-      CustomizableUI.addWidgetToArea(this.BOOKMARK_BUTTON_ID, area, pos);
-      BrowserUsageTelemetry.recordWidgetChange(
-        this.BOOKMARK_BUTTON_ID,
-        area,
-        "bookmark-tools"
-      );
-    } else {
-      // Move it back to the palette.
-      CustomizableUI.removeWidgetFromArea(this.BOOKMARK_BUTTON_ID);
-      BrowserUsageTelemetry.recordWidgetChange(
-        this.BOOKMARK_BUTTON_ID,
-        null,
-        "bookmark-tools"
-      );
-    }
-    triggerNode.setAttribute("checked", !placement);
-    updateToggleControlLabel(triggerNode);
-  },
-
   handlePlacesEvents(aEvents) {
     let isStarUpdateNeeded = false;
     let affectsOtherBookmarksFolder = false;
 
     for (let ev of aEvents) {
       switch (ev.type) {
         case "bookmark-added":
           // Only need to update the UI if it wasn't marked as starred before:
--- a/browser/base/content/tabbrowser-tabs.js
+++ b/browser/base/content/tabbrowser-tabs.js
@@ -2088,17 +2088,20 @@
         return this.tabbox.tabpanels.firstElementChild;
       }
 
       // If the tab's browser is lazy, we need to `_insertBrowser` in order
       // to have a linkedPanel.  This will also serve to bind the browser
       // and make it ready to use. We only do this if the tab is selected
       // because otherwise, callers might end up unintentionally binding the
       // browser for lazy background tabs.
-      if (aTab.selected) {
+      if (!aTab.linkedPanel) {
+        if (!aTab.selected) {
+          return null;
+        }
         gBrowser._insertBrowser(aTab);
       }
       return document.getElementById(aTab.linkedPanel);
     }
 
     _updateNewTabVisibility() {
       // Helper functions to help deal with customize mode wrapping some items
       let wrap = n =>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -231,16 +231,17 @@ skip-if = toolkit == "windows" # Disable
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_menuButtonFitts.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_middleMouse_noJSPaste.js]
 https_first_disabled = true
 skip-if = apple_silicon && !debug # Bug 1724711
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_minimize.js]
+skip-if = apple_silicon && !debug # Bug 1725756
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 fail-if = (os == 'linux' && os_version == '18.04')  # Bug 1600177
 [browser_modifiedclick_inherit_principal.js]
 https_first_disabled = true
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_new_http_window_opened_from_file_tab.js]
 https_first_disabled = true
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
--- a/browser/components/aboutlogins/content/aboutLogins.css
+++ b/browser/components/aboutlogins/content/aboutLogins.css
@@ -88,8 +88,12 @@ login-item[data-editing="true"] + login-
 }
 
 body > section {
   display: grid;
   grid-template-rows: auto 1fr;
   overflow-y: hidden;
   overflow-x: auto;
 }
+
+login-intro {
+  overflow-y: scroll;
+}
--- a/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini
@@ -1,8 +1,7 @@
 [DEFAULT]
 prefs =
   browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json'
 support-files =
-  ../head.js
   config_disable_developer_tools.json
 
 [browser_policy_disable_developer_tools.js]
--- a/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js
+++ b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* import-globals-from ../head.js */
+"use strict";
 
-"use strict";
+const { EnterprisePolicyTesting } = ChromeUtils.import(
+  "resource://testing-common/EnterprisePolicyTesting.jsm"
+);
 var updateService = Cc["@mozilla.org/updates/update-service;1"].getService(
   Ci.nsIApplicationUpdateService
 );
 
 add_task(async function test_updates_post_policy() {
   is(
     Services.policies.isAllowed("devtools"),
     false,
@@ -53,8 +55,33 @@ add_task(async function test_updates_pos
     document.getElementById("appmenu-developer-tools-view").children.length,
     2,
     "The developer tools are properly populated"
   );
   window.PanelUI.hide();
 
   BrowserTestUtils.removeTab(tab);
 });
+
+// Copied from ../head.js. head.js was never intended to be used with tests
+// that use a JSON file versus calling setupPolicyEngineWithJson so I have
+// to copy this function here versus including it.
+async function testPageBlockedByPolicy(page, policyJSON) {
+  if (policyJSON) {
+    await EnterprisePolicyTesting.setupPolicyEngineWithJson(policyJSON);
+  }
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: "about:blank" },
+    async browser => {
+      BrowserTestUtils.loadURI(browser, page);
+      await BrowserTestUtils.browserLoaded(browser, false, page, true);
+      await SpecialPowers.spawn(browser, [page], async function(innerPage) {
+        ok(
+          content.document.documentURI.startsWith(
+            "about:neterror?e=blockedByPolicy"
+          ),
+          content.document.documentURI +
+            " should start with about:neterror?e=blockedByPolicy"
+        );
+      });
+    }
+  );
+}
--- a/browser/components/enterprisepolicies/tests/browser/head.js
+++ b/browser/components/enterprisepolicies/tests/browser/head.js
@@ -168,20 +168,16 @@ async function check_homepage({
         '"Restore defaults" button disabled status should match expected'
       );
     }
   );
   await BrowserTestUtils.removeTab(tab);
 }
 
 add_setup(async function policies_headjs_startWithCleanSlate() {
-  if (Services.prefs.getPrefType("browser.policies.alternatePath")) {
-    // This allows tests that use a JSON File to use head.js
-    return;
-  }
   if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) {
     await setupPolicyEngineWithJson("");
   }
   is(
     Services.policies.status,
     Ci.nsIEnterprisePolicies.INACTIVE,
     "Engine is inactive at the start of the test"
   );
--- a/browser/components/extensions/ext-browser.json
+++ b/browser/components/extensions/ext-browser.json
@@ -113,16 +113,18 @@
   "menusChild": {
     "schema": "chrome://browser/content/schemas/menus_child.json",
     "scopes": ["addon_child", "content_child", "devtools_child"]
   },
   "menusInternal": {
     "url": "chrome://browser/content/parent/ext-menus.js",
     "schema": "chrome://browser/content/schemas/menus.json",
     "scopes": ["addon_parent"],
+    "events": ["startup"],
+    "permissions": ["menus", "contextMenus"],
     "paths": [
       ["contextMenus"],
       ["menus"],
       ["menusInternal"]
     ]
   },
   "normandyAddonStudy": {
     "url": "chrome://browser/content/parent/ext-normandyAddonStudy.js",
--- a/browser/components/extensions/parent/ext-menus.js
+++ b/browser/components/extensions/parent/ext-menus.js
@@ -19,25 +19,31 @@ ChromeUtils.defineModuleGetter(
 );
 
 var { DefaultMap, ExtensionError, parseMatchPatterns } = ExtensionUtils;
 
 var { ExtensionParent } = ChromeUtils.import(
   "resource://gre/modules/ExtensionParent.jsm"
 );
 
-var { IconDetails } = ExtensionParent;
+var { IconDetails, StartupCache } = ExtensionParent;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gMenuMap = new Map();
 
+// Map[Extension -> Map[ID -> MenuCreateProperties]]
+// The map object for each extension is a reference to the same
+// object in StartupCache.menus.  This provides a non-async
+// getter for that object.
+var gStartupCache = new Map();
+
 // Map[Extension -> MenuItem]
 var gRootItems = new Map();
 
 // Map[Extension -> ID[]]
 // Menu IDs that were eligible for being shown in the current menu.
 var gShownMenuItems = new DefaultMap(() => []);
 
 // Map[Extension -> Set[Contexts]]
@@ -706,48 +712,52 @@ function addMenuEventInfo(info, contextD
   // If the context was overridden, then frameUrl should be the URL of the
   // document in which the menu was opened (instead of undefined, even if that
   // document is not in a frame).
   if (contextData.originalViewUrl) {
     info.frameUrl = contextData.originalViewUrl;
   }
 }
 
-function MenuItem(extension, createProperties, isRoot = false) {
-  this.extension = extension;
-  this.children = [];
-  this.parent = null;
-  this.tabManager = extension.tabManager;
+class MenuItem {
+  constructor(extension, createProperties, isRoot = false) {
+    this.extension = extension;
+    this.children = [];
+    this.parent = null;
+    this.tabManager = extension.tabManager;
 
-  this.setDefaults();
-  this.setProps(createProperties);
+    this.setDefaults();
+    this.setProps(createProperties);
 
-  if (!this.hasOwnProperty("_id")) {
-    this.id = gNextMenuItemID++;
+    if (!this.hasOwnProperty("_id")) {
+      this.id = gNextMenuItemID++;
+    }
+    // If the item is not the root and has no parent
+    // it must be a child of the root.
+    if (!isRoot && !this.parent) {
+      this.root.addChild(this);
+    }
   }
-  // If the item is not the root and has no parent
-  // it must be a child of the root.
-  if (!isRoot && !this.parent) {
-    this.root.addChild(this);
-  }
-}
 
-MenuItem.prototype = {
-  setProps(createProperties) {
-    for (let propName in createProperties) {
-      if (createProperties[propName] === null) {
+  static mergeProps(obj, properties) {
+    for (let propName in properties) {
+      if (properties[propName] === null) {
         // Omitted optional argument.
         continue;
       }
-      this[propName] = createProperties[propName];
+      obj[propName] = properties[propName];
     }
 
-    if ("icons" in createProperties && createProperties.icons === null) {
-      this.icons = null;
+    if ("icons" in properties && properties.icons === null && obj.icons) {
+      obj.icons = null;
     }
+  }
+
+  setProps(createProperties) {
+    MenuItem.mergeProps(this, createProperties);
 
     if (createProperties.documentUrlPatterns != null) {
       this.documentUrlMatchPattern = parseMatchPatterns(
         this.documentUrlPatterns,
         {
           restrictSchemes: this.extension.restrictSchemes,
         }
       );
@@ -761,54 +771,54 @@ MenuItem.prototype = {
       });
     }
 
     // If a child MenuItem does not specify any contexts, then it should
     // inherit the contexts specified from its parent.
     if (createProperties.parentId && !createProperties.contexts) {
       this.contexts = this.parent.contexts;
     }
-  },
+  }
 
   setDefaults() {
     this.setProps({
       type: "normal",
       checked: false,
       contexts: ["all"],
       enabled: true,
       visible: true,
     });
-  },
+  }
 
   set id(id) {
     if (this.hasOwnProperty("_id")) {
       throw new ExtensionError("ID of a MenuItem cannot be changed");
     }
     let isIdUsed = gMenuMap.get(this.extension).has(id);
     if (isIdUsed) {
       throw new ExtensionError(`ID already exists: ${id}`);
     }
     this._id = id;
-  },
+  }
 
   get id() {
     return this._id;
-  },
+  }
 
   get elementId() {
     let id = this.id;
     // If the ID is an integer, it is auto-generated and globally unique.
     // If the ID is a string, it is only unique within one extension and the
     // ID needs to be concatenated with the extension ID.
     if (typeof id !== "number") {
       // To avoid collisions with numeric IDs, add a prefix to string IDs.
       id = `_${id}`;
     }
     return `${makeWidgetId(this.extension.id)}-menuitem-${id}`;
-  },
+  }
 
   ensureValidParentId(parentId) {
     if (parentId === undefined) {
       return;
     }
     let menuMap = gMenuMap.get(this.extension);
     if (!menuMap.has(parentId)) {
       throw new ExtensionError(
@@ -817,83 +827,105 @@ MenuItem.prototype = {
     }
     for (let item = menuMap.get(parentId); item; item = item.parent) {
       if (item === this) {
         throw new ExtensionError(
           "MenuItem cannot be an ancestor (or self) of its new parent."
         );
       }
     }
-  },
+  }
+
+  /**
+   * When updating menu properties we need to ensure parents exist
+   * in the cache map before children.  That allows the menus to be
+   * created in the correct sequence on startup.  This reparents the
+   * tree starting from this instance of MenuItem.
+   */
+  reparentInCache() {
+    let { id, extension } = this;
+    let cachedMap = gStartupCache.get(extension);
+    let createProperties = cachedMap.get(id);
+    cachedMap.delete(id);
+    cachedMap.set(id, createProperties);
+
+    for (let child of this.children) {
+      child.reparentInCache();
+    }
+  }
 
   set parentId(parentId) {
     this.ensureValidParentId(parentId);
 
     if (this.parent) {
       this.parent.detachChild(this);
     }
 
     if (parentId === undefined) {
       this.root.addChild(this);
     } else {
       let menuMap = gMenuMap.get(this.extension);
       menuMap.get(parentId).addChild(this);
     }
-  },
+  }
 
   get parentId() {
     return this.parent ? this.parent.id : undefined;
-  },
+  }
 
   addChild(child) {
     if (child.parent) {
       throw new Error("Child MenuItem already has a parent.");
     }
     this.children.push(child);
     child.parent = this;
-  },
+  }
 
   detachChild(child) {
     let idx = this.children.indexOf(child);
     if (idx < 0) {
       throw new Error("Child MenuItem not found, it cannot be removed.");
     }
     this.children.splice(idx, 1);
     child.parent = null;
-  },
+  }
 
   get root() {
     let extension = this.extension;
     if (!gRootItems.has(extension)) {
       let root = new MenuItem(
         extension,
         { title: extension.name },
         /* isRoot = */ true
       );
       gRootItems.set(extension, root);
     }
 
     return gRootItems.get(extension);
-  },
+  }
 
   remove() {
     if (this.parent) {
       this.parent.detachChild(this);
     }
     let children = this.children.slice(0);
     for (let child of children) {
       child.remove();
     }
 
     let menuMap = gMenuMap.get(this.extension);
     menuMap.delete(this.id);
+    // Menu items are saved if !extension.persistentBackground.
+    if (gStartupCache.get(this.extension)?.delete(this.id)) {
+      StartupCache.save();
+    }
     if (this.root == this) {
       gRootItems.delete(this.extension);
     }
-  },
+  }
 
   getClickInfo(contextData, wasChecked) {
     let info = {
       menuItemId: this.id,
     };
     if (this.parent) {
       info.parentMenuItemId = this.parentId;
     }
@@ -901,17 +933,17 @@ MenuItem.prototype = {
     addMenuEventInfo(info, contextData, this.extension, true);
 
     if (this.type === "checkbox" || this.type === "radio") {
       info.checked = this.checked;
       info.wasChecked = wasChecked;
     }
 
     return info;
-  },
+  }
 
   enabledForContext(contextData) {
     if (!this.visible) {
       return false;
     }
     let contexts = getMenuContexts(contextData);
     if (!this.contexts.some(n => contexts.has(n))) {
       return false;
@@ -963,18 +995,18 @@ MenuItem.prototype = {
         targetURIs.push(contextData.linkURI);
       }
       if (!targetURIs.some(targetURI => targetPattern.matches(targetURI))) {
         return false;
       }
     }
 
     return true;
-  },
-};
+  }
+}
 
 // windowTracker only looks as browser windows, but we're also interested in
 // the Library window.  Helper for menuTracker below.
 const libraryTracker = {
   libraryWindowType: "Places:Organizer",
 
   isLibraryWindow(window) {
     let winType = window.document.documentElement.getAttribute("windowtype");
@@ -1194,23 +1226,56 @@ this.menusInternal = class extends Exten
     super(extension);
 
     if (!gMenuMap.size) {
       menuTracker.register();
     }
     gMenuMap.set(extension, new Map());
   }
 
+  restoreFromCache() {
+    let { extension } = this;
+    // ensure extension has not shutdown
+    if (!this.extension) {
+      return;
+    }
+    for (let createProperties of gStartupCache.get(extension).values()) {
+      // The order of menu creation is significant, see reparentInCache.
+      let menuItem = new MenuItem(extension, createProperties);
+      gMenuMap.get(extension).set(menuItem.id, menuItem);
+    }
+    // Used for testing
+    extension.emit("webext-menus-created", gMenuMap.get(extension));
+  }
+
+  async onStartup() {
+    let { extension } = this;
+    if (extension.persistentBackground) {
+      return;
+    }
+    // Using the map retains insertion order.
+    let cachedMenus = await StartupCache.menus.get(extension.id, () => {
+      return new Map();
+    });
+    gStartupCache.set(extension, cachedMenus);
+    if (!cachedMenus.size) {
+      return;
+    }
+
+    this.restoreFromCache();
+  }
+
   onShutdown() {
     let { extension } = this;
 
     if (gMenuMap.has(extension)) {
       gMenuMap.delete(extension);
       gRootItems.delete(extension);
       gShownMenuItems.delete(extension);
+      gStartupCache.delete(extension);
       gOnShownSubscribers.delete(extension);
       if (!gMenuMap.size) {
         menuTracker.unregister();
       }
     }
   }
 
   PERSISTENT_EVENTS = {
@@ -1328,57 +1393,89 @@ this.menusInternal = class extends Exten
         extensionApi: this,
       }).api(),
     };
 
     return {
       contextMenus: menus,
       menus,
       menusInternal: {
-        create: function(createProperties) {
+        create(createProperties) {
           // event pages require id
           if (!extension.persistentBackground) {
             if (!createProperties.id) {
               throw new ExtensionError(
                 "menus.create requires an id for non-persistent background scripts."
               );
             }
             if (gMenuMap.get(extension).has(createProperties.id)) {
               throw new ExtensionError(
                 `The menu id ${createProperties.id} already exists in menus.create.`
               );
             }
           }
 
           // Note that the id is required by the schema. If the addon did not set
-          // it, the implementation of menus.create in the child should
-          // have added it.
+          // it, the implementation of menus.create in the child will add it for
+          // extensions with persistent backgrounds, but not otherwise.
           let menuItem = new MenuItem(extension, createProperties);
           gMenuMap.get(extension).set(menuItem.id, menuItem);
-        },
-
-        update: function(id, updateProperties) {
-          let menuItem = gMenuMap.get(extension).get(id);
-          if (menuItem) {
-            menuItem.setProps(updateProperties);
+          if (!extension.persistentBackground) {
+            // Only cache properties that are necessary.
+            let cached = {};
+            MenuItem.mergeProps(cached, createProperties);
+            gStartupCache.get(extension).set(menuItem.id, cached);
+            StartupCache.save();
           }
         },
 
-        remove: function(id) {
+        update(id, updateProperties) {
+          let menuItem = gMenuMap.get(extension).get(id);
+          if (!menuItem) {
+            return;
+          }
+          menuItem.setProps(updateProperties);
+
+          // Update the startup cache for non-persistent extensions.
+          if (extension.persistentBackground) {
+            return;
+          }
+
+          let cached = gStartupCache.get(extension).get(id);
+          let reparent =
+            updateProperties.parentId != null &&
+            cached.parentId != updateProperties.parentId;
+          MenuItem.mergeProps(cached, updateProperties);
+          if (reparent) {
+            // The order of menu creation is significant, see reparentInCache.
+            menuItem.reparentInCache();
+          }
+          StartupCache.save();
+        },
+
+        remove(id) {
           let menuItem = gMenuMap.get(extension).get(id);
           if (menuItem) {
             menuItem.remove();
           }
         },
 
-        removeAll: function() {
+        removeAll() {
           let root = gRootItems.get(extension);
           if (root) {
             root.remove();
           }
+          // Should be empty, just extra assurance.
+          if (!extension.persistentBackground) {
+            let cached = gStartupCache.get(extension);
+            if (cached.size) {
+              cached.clear();
+              StartupCache.save();
+            }
+          }
         },
 
         onClicked: new EventManager({
           context,
           module: "menusInternal",
           event: "onClicked",
           name: "menus.onClicked",
           extensionApi: this,
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_menu_startup.js
@@ -0,0 +1,439 @@
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "ExtensionParent",
+  "resource://gre/modules/ExtensionParent.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "Management",
+  "resource://gre/modules/Extension.jsm"
+);
+
+const { AddonTestUtils } = ChromeUtils.import(
+  "resource://testing-common/AddonTestUtils.jsm"
+);
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+AddonTestUtils.createAppInfo(
+  "xpcshell@tests.mozilla.org",
+  "XPCShell",
+  "42",
+  "42"
+);
+
+Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
+
+function getExtension(id, background, useAddonManager) {
+  return ExtensionTestUtils.loadExtension({
+    useAddonManager,
+    manifest: {
+      applications: { gecko: { id } },
+      permissions: ["menus"],
+      background: { persistent: false },
+    },
+    background,
+  });
+}
+
+async function expectCached(extension, expect) {
+  let { StartupCache } = ExtensionParent;
+  let cached = await StartupCache.menus.get(extension.id);
+  let createProperties = Array.from(cached.values());
+  equal(cached.size, expect.length, "menus saved in cache");
+  // The menus startupCache is a map and the order is significant
+  // for recreating menus on startup.  Ensure that they are in
+  // the expected order.  We only verify specific keys here rather
+  // than all menu properties.
+  for (let i in createProperties) {
+    Assert.deepEqual(
+      createProperties[i],
+      expect[i],
+      "expected cached properties exist"
+    );
+  }
+}
+
+function promiseExtensionEvent(wrapper, event) {
+  return new Promise(resolve => {
+    wrapper.extension.once(event, (kind, data) => {
+      resolve(data);
+    });
+  });
+}
+
+add_setup(async () => {
+  await AddonTestUtils.promiseStartupManager();
+});
+
+add_task(async function test_menu_onInstalled() {
+  async function background() {
+    browser.runtime.onInstalled.addListener(async () => {
+      const parentId = browser.menus.create({
+        contexts: ["all"],
+        title: "parent",
+        id: "test-parent",
+      });
+      browser.menus.create({
+        parentId,
+        title: "click A",
+        id: "test-click-a",
+      });
+      browser.menus.create(
+        {
+          parentId,
+          title: "click B",
+          id: "test-click-b",
+        },
+        () => {
+          browser.test.sendMessage("onInstalled");
+        }
+      );
+    });
+    browser.menus.create(
+      {
+        contexts: ["tab"],
+        title: "top-level",
+        id: "test-top-level",
+      },
+      () => {
+        browser.test.sendMessage("create", browser.runtime.lastError?.message);
+      }
+    );
+
+    browser.test.onMessage.addListener(async msg => {
+      browser.test.log(`onMessage ${msg}`);
+      if (msg == "updatemenu") {
+        await browser.menus.update("test-click-a", { title: "click updated" });
+      } else if (msg == "removemenu") {
+        await browser.menus.remove("test-click-b");
+      } else if (msg == "removeall") {
+        await browser.menus.removeAll();
+      }
+      browser.test.sendMessage("updated");
+    });
+  }
+
+  const extension = getExtension(
+    "test-persist@mochitest",
+    background,
+    "permanent"
+  );
+
+  await extension.startup();
+  let lastError = await extension.awaitMessage("create");
+  Assert.equal(lastError, undefined, "no error creating menu");
+  await extension.awaitMessage("onInstalled");
+  await extension.terminateBackground();
+
+  await expectCached(extension, [
+    {
+      contexts: ["tab"],
+      id: "test-top-level",
+      title: "top-level",
+    },
+    { contexts: ["all"], id: "test-parent", title: "parent" },
+    {
+      id: "test-click-a",
+      parentId: "test-parent",
+      title: "click A",
+    },
+    {
+      id: "test-click-b",
+      parentId: "test-parent",
+      title: "click B",
+    },
+  ]);
+
+  await extension.wakeupBackground();
+  lastError = await extension.awaitMessage("create");
+  Assert.equal(
+    lastError,
+    "The menu id test-top-level already exists in menus.create.",
+    "correct error creating menu"
+  );
+
+  await AddonTestUtils.promiseRestartManager();
+  await extension.awaitStartup();
+
+  // verify the startupcache
+  await expectCached(extension, [
+    {
+      contexts: ["tab"],
+      id: "test-top-level",
+      title: "top-level",
+    },
+    { contexts: ["all"], id: "test-parent", title: "parent" },
+    {
+      id: "test-click-a",
+      parentId: "test-parent",
+      title: "click A",
+    },
+    {
+      id: "test-click-b",
+      parentId: "test-parent",
+      title: "click B",
+    },
+  ]);
+
+  equal(
+    extension.extension.backgroundState,
+    "stopped",
+    "background is not running"
+  );
+  await extension.wakeupBackground();
+  lastError = await extension.awaitMessage("create");
+  Assert.equal(
+    lastError,
+    "The menu id test-top-level already exists in menus.create.",
+    "correct error creating menu"
+  );
+
+  extension.sendMessage("updatemenu");
+  await extension.awaitMessage("updated");
+  await extension.terminateBackground();
+
+  // Title change is cached
+  await expectCached(extension, [
+    {
+      contexts: ["tab"],
+      id: "test-top-level",
+      title: "top-level",
+    },
+    { contexts: ["all"], id: "test-parent", title: "parent" },
+    {
+      id: "test-click-a",
+      parentId: "test-parent",
+      title: "click updated",
+    },
+    {
+      id: "test-click-b",
+      parentId: "test-parent",
+      title: "click B",
+    },
+  ]);
+
+  await extension.wakeupBackground();
+  lastError = await extension.awaitMessage("create");
+  Assert.equal(
+    lastError,
+    "The menu id test-top-level already exists in menus.create.",
+    "correct error creating menu"
+  );
+
+  extension.sendMessage("removemenu");
+  await extension.awaitMessage("updated");
+  await extension.terminateBackground();
+
+  // menu removed
+  await expectCached(extension, [
+    {
+      contexts: ["tab"],
+      id: "test-top-level",
+      title: "top-level",
+    },
+    { contexts: ["all"], id: "test-parent", title: "parent" },
+    {
+      id: "test-click-a",
+      parentId: "test-parent",
+      title: "click updated",
+    },
+  ]);
+
+  await extension.wakeupBackground();
+  lastError = await extension.awaitMessage("create");
+  Assert.equal(
+    lastError,
+    "The menu id test-top-level already exists in menus.create.",
+    "correct error creating menu"
+  );
+
+  extension.sendMessage("removeall");
+  await extension.awaitMessage("updated");
+  await extension.terminateBackground();
+
+  // menus removed
+  await expectCached(extension, []);
+
+  await extension.unload();
+});
+
+add_task(async function test_menu_nested() {
+  async function background() {
+    browser.test.onMessage.addListener(async (action, properties) => {
+      browser.test.log(`onMessage ${action}`);
+      switch (action) {
+        case "create":
+          await new Promise(resolve => {
+            browser.menus.create(properties, resolve);
+          });
+          break;
+        case "update":
+          {
+            let { id, ...update } = properties;
+            await browser.menus.update(id, update);
+          }
+          break;
+        case "remove":
+          {
+            let { id } = properties;
+            await browser.menus.remove(id);
+          }
+          break;
+        case "removeAll":
+          await browser.menus.removeAll();
+          break;
+      }
+      browser.test.sendMessage("updated");
+    });
+  }
+
+  const extension = getExtension(
+    "test-nesting@mochitest",
+    background,
+    "permanent"
+  );
+  await extension.startup();
+
+  extension.sendMessage("create", {
+    id: "first",
+    contexts: ["all"],
+    title: "first",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "first", title: "first" },
+  ]);
+
+  extension.sendMessage("create", {
+    id: "second",
+    contexts: ["all"],
+    title: "second",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "first", title: "first" },
+    { contexts: ["all"], id: "second", title: "second" },
+  ]);
+
+  extension.sendMessage("create", {
+    id: "third",
+    contexts: ["all"],
+    title: "third",
+    parentId: "first",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "first", title: "first" },
+    { contexts: ["all"], id: "second", title: "second" },
+    {
+      contexts: ["all"],
+      id: "third",
+      parentId: "first",
+      title: "third",
+    },
+  ]);
+
+  extension.sendMessage("create", {
+    id: "fourth",
+    contexts: ["all"],
+    title: "fourth",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "first", title: "first" },
+    { contexts: ["all"], id: "second", title: "second" },
+    {
+      contexts: ["all"],
+      id: "third",
+      parentId: "first",
+      title: "third",
+    },
+    { contexts: ["all"], id: "fourth", title: "fourth" },
+  ]);
+
+  extension.sendMessage("update", {
+    id: "first",
+    parentId: "second",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "second", title: "second" },
+    { contexts: ["all"], id: "fourth", title: "fourth" },
+    {
+      contexts: ["all"],
+      id: "first",
+      title: "first",
+      parentId: "second",
+    },
+    {
+      contexts: ["all"],
+      id: "third",
+      parentId: "first",
+      title: "third",
+    },
+  ]);
+
+  await AddonTestUtils.promiseShutdownManager();
+  // We need to attach an event listener before the
+  // startup event is emitted.  Fortunately, we
+  // emit via Management before emitting on extension.
+  let promiseMenus;
+  Management.once("startup", (kind, ext) => {
+    info(`management ${kind} ${ext.id}`);
+    promiseMenus = promiseExtensionEvent(
+      { extension: ext },
+      "webext-menus-created"
+    );
+  });
+  await AddonTestUtils.promiseStartupManager();
+  await extension.awaitStartup();
+  await extension.wakeupBackground();
+
+  await expectCached(extension, [
+    { contexts: ["all"], id: "second", title: "second" },
+    { contexts: ["all"], id: "fourth", title: "fourth" },
+    {
+      contexts: ["all"],
+      id: "first",
+      title: "first",
+      parentId: "second",
+    },
+    {
+      contexts: ["all"],
+      id: "third",
+      parentId: "first",
+      title: "third",
+    },
+  ]);
+  // validate nesting
+  let menus = await promiseMenus;
+  equal(menus.get("first").parentId, "second", "menuitem parent is correct");
+  equal(
+    menus.get("second").children.length,
+    1,
+    "menuitem parent has correct number of children"
+  );
+  equal(
+    menus.get("second").root.children.length,
+    2, // second and forth
+    "menuitem root has correct number of children"
+  );
+
+  extension.sendMessage("remove", {
+    id: "second",
+  });
+  await extension.awaitMessage("updated");
+  await expectCached(extension, [
+    { contexts: ["all"], id: "fourth", title: "fourth" },
+  ]);
+
+  extension.sendMessage("removeAll");
+  await extension.awaitMessage("updated");
+  await expectCached(extension, []);
+
+  await extension.unload();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -15,16 +15,17 @@ skip-if = tsan # Times out, bug 1612707
 [test_ext_distribution_popup.js]
 [test_ext_history.js]
 [test_ext_homepage_overrides_private.js]
 [test_ext_manifest.js]
 [test_ext_manifest_commands.js]
 [test_ext_manifest_omnibox.js]
 [test_ext_manifest_permissions.js]
 [test_ext_menu_caller.js]
+[test_ext_menu_startup.js]
 [test_ext_normandyAddonStudy.js]
 [test_ext_pageAction_shutdown.js]
 [test_ext_pkcs11_management.js]
 [test_ext_settings_overrides_defaults.js]
 support-files =
   data/test/manifest.json
   data/test2/manifest.json
 [test_ext_settings_overrides_search.js]
--- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
@@ -137,16 +137,27 @@ body[lwt-newtab-brighttext] {
   background-color: rgba(21, 20, 26, 0.5);
   display: flex;
   position: relative;
   flex-flow: row nowrap;
   height: 100%;
   min-height: 500px;
   overflow: hidden;
 }
+.onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .brand-logo {
+  margin-top: 120px;
+}
+@media (prefers-reduced-motion: reduce) {
+  .onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .brand-logo {
+    background-image: url("chrome://activity-stream/content/data/content/assets/heart.svg") !important;
+  }
+}
+.onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .no-steps {
+  padding-bottom: 12px;
+}
 .onboardingContainer .screen.light-text {
   --in-content-page-color: rgb(251, 251, 254);
   --in-content-primary-button-text-color: rgb(43, 42, 51);
   --in-content-primary-button-text-color-hover: rgb(43, 42, 51);
   --in-content-primary-button-background: rgb(0, 221, 255);
   --in-content-primary-button-background-hover: rgb(128, 235, 255);
   --in-content-primary-button-background-active: rgb(170, 242, 255);
   --in-content-link-color: var(--in-content-primary-button-background);
@@ -228,19 +239,17 @@ body[lwt-newtab-brighttext] {
   font-size: 36px;
 }
 .onboardingContainer .welcome-text.slim h1 {
   font-weight: 276;
 }
 .onboardingContainer .welcome-text.fancy h1 {
   background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
   background-size: 400% auto;
-  color: #000;
   background-clip: text;
-  -webkit-background-clip: text;
   animation: shine 50s linear infinite;
 }
 @media (prefers-contrast: no-preference) {
   .onboardingContainer .welcome-text.fancy h1 {
     -webkit-text-fill-color: transparent;
   }
 }
 @media (prefers-color-scheme: dark) {
@@ -664,16 +673,17 @@ body[lwt-newtab-brighttext] {
 .onboardingContainer .mobile-download-buttons li button {
   display: inline-block;
   height: 45px;
   width: 152px;
   background-repeat: no-repeat;
   background-size: contain;
   background-position: center;
   box-shadow: none;
+  border: 0;
 }
 .onboardingContainer .mobile-download-buttons li:not(:first-child) {
   margin-inline: 5px 0;
 }
 .onboardingContainer .dismiss-button {
   padding: 0;
   margin-block: 30px -45px;
   margin-inline: 0 30px;
--- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss
+++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss
@@ -142,16 +142,31 @@ body {
     background-color: rgba(21, 20, 26, 0.5);
     display: flex;
     position: relative;
     flex-flow: row nowrap;
     height: 100%;
     min-height: 500px;
     overflow: hidden;
 
+    &:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) {
+      .brand-logo {
+        margin-top: 120px;
+
+        @media (prefers-reduced-motion: reduce) {
+          // sass-lint:disable-block no-important
+          background-image: url('chrome://activity-stream/content/data/content/assets/heart.svg') !important;
+        }
+      }
+
+      .no-steps {
+        padding-bottom: 12px;
+      }
+    }
+
     &.light-text {
       --in-content-page-color: rgb(251, 251, 254);
       --in-content-primary-button-text-color: rgb(43, 42, 51);
       --in-content-primary-button-text-color-hover: rgb(43, 42, 51);
       --in-content-primary-button-background: rgb(0, 221, 255);
       --in-content-primary-button-background-hover: rgb(128, 235, 255);
       --in-content-primary-button-background-active: rgb(170, 242, 255);
       --in-content-link-color: var(--in-content-primary-button-background);
@@ -252,19 +267,17 @@ body {
         font-weight: 276;
       }
     }
 
     &.fancy {
       h1 {
         background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
         background-size: 400% auto;
-        color: #000;
         background-clip: text;
-        -webkit-background-clip: text;
         animation: shine 50s linear infinite;
         @media (prefers-contrast: no-preference) {
           -webkit-text-fill-color: transparent;
         }
         @media (prefers-color-scheme: dark) {
           background-image: linear-gradient(90deg, #C688FF, #FF84C0, #FFBD4F, #FF84C0, #C688FF);
         }
       }
@@ -821,16 +834,17 @@ body {
       button {
         display: inline-block;
         height: 45px;
         width: 152px;
         background-repeat: no-repeat;
         background-size: contain;
         background-position: center;
         box-shadow: none;
+        border: 0;
       }
 
       &:not(:first-child) {
         margin-inline: 5px 0;
       }
     }
   }
 
deleted file mode 100644
index 31f8c74dca5d6c76125efdf60cdd550a1ab563d1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/heart.svg
@@ -0,0 +1,48 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="83" height="67" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M76.675 6.774A21.93 21.93 0 0 0 60.989 0h-.18a21.902 21.902 0 0 0-15.592 6.438L27.549 24.126a7.533 7.533 0 0 0 0 10.658l3.276 3.269a7.568 7.568 0 0 0 10.68 0l-8.591-8.505L50.56 11.86a14.355 14.355 0 0 1 10.25-4.242h.136a14.356 14.356 0 0 1 10.278 4.427c5.43 5.63 5.15 14.9-.615 20.672L45.467 57.931a5.544 5.544 0 0 1-7.868 0l4.041 4.048.651.658a6.602 6.602 0 0 0 8.805.45l24.82-24.991c8.748-8.676 9.041-22.724.759-31.322Z" fill="url(#a)"/>
+  <path d="M70.58 32.724 45.439 57.938a5.543 5.543 0 0 1-7.868 0l4.07 4.041.651.658a6.602 6.602 0 0 0 8.805.45l24.82-24.991-5.335-5.372Z" fill="url(#b)"/>
+  <path d="M41.533 38.032h-.036a7.56 7.56 0 0 1-10.672 0l5.344 5.336a7.548 7.548 0 0 0 10.671 0l8.584-8.584a7.567 7.567 0 0 0 0-10.679L37.806 6.481A21.923 21.923 0 0 0 22.213.043h-.207A21.945 21.945 0 0 0 6.32 6.773c-8.283 8.584-7.961 22.646.715 31.33L31.483 62.53a14.305 14.305 0 0 0 19.67.565 6.602 6.602 0 0 1-8.804-.45l-5.48-5.487-24.448-24.434C6.65 26.952 6.37 17.704 11.8 12.053a14.342 14.342 0 0 1 10.279-4.435h.107a14.355 14.355 0 0 1 10.235 4.234l17.66 17.575-8.533 8.583-.014.022Z" fill="url(#c)"/>
+  <path d="M41.533 38.032h-.036a7.56 7.56 0 0 1-10.672 0l5.344 5.336a7.548 7.548 0 0 0 10.671 0l8.584-8.584a7.567 7.567 0 0 0 0-10.679L37.806 6.481A21.923 21.923 0 0 0 22.213.043h-.207A21.945 21.945 0 0 0 6.32 6.773c-8.283 8.584-7.961 22.646.715 31.33L31.483 62.53a14.305 14.305 0 0 0 19.67.565 6.602 6.602 0 0 1-8.804-.45l-5.48-5.487-24.448-24.434C6.65 26.952 6.37 17.704 11.8 12.053a14.342 14.342 0 0 1 10.279-4.435h.107a14.355 14.355 0 0 1 10.235 4.234l17.66 17.575-8.533 8.583-.014.022Z" fill="url(#d)"/>
+  <path d="M45.188 6.48 27.55 24.127a7.533 7.533 0 0 0 0 10.658l3.276 3.269a7.568 7.568 0 0 0 10.68 0l-8.591-8.505L50.56 11.86l-5.372-5.378Z" fill="url(#e)"/>
+  <defs>
+    <linearGradient id="a" x1="54.008" y1="64.583" x2="54.008" y2="0" gradientUnits="userSpaceOnUse">
+      <stop stop-color="#3A8EE6"/>
+      <stop offset=".24" stop-color="#5C79F0"/>
+      <stop offset=".63" stop-color="#9059FF"/>
+      <stop offset="1" stop-color="#C139E6"/>
+    </linearGradient>
+    <linearGradient id="b" x1="75.959" y1="48.653" x2="37.613" y2="48.653" gradientUnits="userSpaceOnUse">
+      <stop offset=".14" stop-color="#6A2BEA" stop-opacity="0"/>
+      <stop offset=".34" stop-color="#642DE4" stop-opacity=".03"/>
+      <stop offset=".55" stop-color="#5131D3" stop-opacity=".12"/>
+      <stop offset=".76" stop-color="#3139B7" stop-opacity=".27"/>
+      <stop offset=".98" stop-color="#054490" stop-opacity=".48"/>
+      <stop offset="1" stop-color="#00458B" stop-opacity=".5"/>
+    </linearGradient>
+    <linearGradient id="c" x1="43.421" y1="8.848" x2="16.942" y2="54.719" gradientUnits="userSpaceOnUse">
+      <stop stop-color="#FF980E"/>
+      <stop offset=".27" stop-color="#FF851B"/>
+      <stop offset=".56" stop-color="#FF7F1F"/>
+      <stop offset=".77" stop-color="#FF3750"/>
+      <stop offset=".9" stop-color="#F92261"/>
+      <stop offset="1" stop-color="#F5156C"/>
+    </linearGradient>
+    <linearGradient id="d" x1="43.421" y1="8.848" x2="16.942" y2="54.719" gradientUnits="userSpaceOnUse">
+      <stop stop-color="#FFF261" stop-opacity=".8"/>
+      <stop offset=".06" stop-color="#FFF261" stop-opacity=".68"/>
+      <stop offset=".19" stop-color="#FFF261" stop-opacity=".48"/>
+      <stop offset=".31" stop-color="#FFF261" stop-opacity=".31"/>
+      <stop offset=".42" stop-color="#FFF261" stop-opacity=".17"/>
+      <stop offset=".53" stop-color="#FFF261" stop-opacity=".08"/>
+      <stop offset=".63" stop-color="#FFF261" stop-opacity=".02"/>
+      <stop offset=".72" stop-color="#FFF261" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="e" x1="37.95" y1="40.285" x2="37.95" y2="6.481" gradientUnits="userSpaceOnUse">
+      <stop stop-color="#6E008B" stop-opacity=".5"/>
+      <stop offset=".5" stop-color="#C846CB" stop-opacity="0"/>
+    </linearGradient>
+  </defs>
+</svg>
new file mode 100644
index 0000000000000000000000000000000000000000..fb9f7fdca58bd737b603713acbf1d8e718fc997a
GIT binary patch
literal 100396
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
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -1427,24 +1427,22 @@ class _ASRouter {
 
   resetGroupsState() {
     const newGroupImpressions = {};
     for (let { id } of this.state.groups) {
       newGroupImpressions[id] = [];
     }
     // Update storage
     this._storage.set("groupImpressions", newGroupImpressions);
-    // The groups parameter below can be removed once this method has test coverage
     return this.setState(({ groups }) => ({
       groupImpressions: newGroupImpressions,
     }));
   }
 
-  // Until this method has test coverage, it should only be used for testing
-  _resetMessageState() {
+  resetMessageState() {
     const newMessageImpressions = {};
     for (let { id } of this.state.messages) {
       newMessageImpressions[id] = [];
     }
     // Update storage
     this._storage.set("messageImpressions", newMessageImpressions);
     return this.setState(() => ({
       messageImpressions: newMessageImpressions,
--- a/browser/components/newtab/lib/OnboardingMessageProvider.jsm
+++ b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
@@ -55,17 +55,17 @@ const ONBOARDING_MESSAGES = () => [
       transitions: true,
       screens: [
         {
           id: "UPGRADE_PIN_FIREFOX",
           order: 0,
           content: {
             logo: {
               imageURL:
-                "chrome://activity-stream/content/data/content/assets/heart.gif",
+                "chrome://activity-stream/content/data/content/assets/heart.webp",
               height: "73px",
             },
             has_noodles: true,
             title: {
               string_id: "fx100-upgrade-thanks-header",
             },
             title_style: "fancy larger",
             background:
--- a/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
+++ b/browser/components/newtab/test/browser/browser_aboutwelcome_configurable_ui.js
@@ -173,20 +173,16 @@ add_task(async function test_aboutwelcom
   await test_element_styles(
     browser,
     "#mainContentHeader",
     // Expected styles:
     {
       "font-weight": "276",
       "font-size": "36px",
       animation: "50s linear 0s infinite normal none running shine",
-    },
-    // Unexpected styles:
-    {
-      color: "rgb(21, 20, 26)",
     }
   );
 });
 
 /**
  * Test rendering a screen with an image for the dialog window's background
  */
 add_task(async function test_aboutwelcome_with_background() {
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -2863,9 +2863,51 @@ describe("ASRouter", () => {
         .get(() => "unkown");
       sandbox.stub(global.RemoteL10n, "isLocaleSupported").returns(false);
 
       await MessageLoaderUtils._remoteSettingsLoader(provider, {});
 
       assert.notCalled(spy);
     });
   });
+  describe("#resetMessageState", () => {
+    it("should reset all message impressions", async () => {
+      await Router.setState({
+        messages: [{ id: "1" }, { id: "2" }],
+      });
+      await Router.setState({
+        messageImpressions: { "1": [0, 1, 2], "2": [0, 1, 2] },
+      }); // Add impressions for test messages
+      let impressions = Object.values(Router.state.messageImpressions);
+      assert.equal(impressions.filter(i => i.length).length, 2); // Both messages have impressions
+
+      Router.resetMessageState();
+      impressions = Object.values(Router.state.messageImpressions);
+
+      assert.isEmpty(impressions.filter(i => i.length)); // Both messages now have zero impressions
+      assert.calledWithExactly(Router._storage.set, "messageImpressions", {
+        "1": [],
+        "2": [],
+      });
+    });
+  });
+  describe("#resetGroupsState", () => {
+    it("should reset all group impressions", async () => {
+      await Router.setState({
+        groups: [{ id: "1" }, { id: "2" }],
+      });
+      await Router.setState({
+        groupImpressions: { "1": [0, 1, 2], "2": [0, 1, 2] },
+      }); // Add impressions for test groups
+      let impressions = Object.values(Router.state.groupImpressions);
+      assert.equal(impressions.filter(i => i.length).length, 2); // Both groups have impressions
+
+      Router.resetGroupsState();
+      impressions = Object.values(Router.state.groupImpressions);
+
+      assert.isEmpty(impressions.filter(i => i.length)); // Both groups now have zero impressions
+      assert.calledWithExactly(Router._storage.set, "groupImpressions", {
+        "1": [],
+        "2": [],
+      });
+    });
+  });
 });
--- a/browser/components/places/SnapshotMonitor.jsm
+++ b/browser/components/places/SnapshotMonitor.jsm
@@ -15,26 +15,48 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Services: "resource://gre/modules/Services.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   Snapshots: "resource:///modules/Snapshots.jsm",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "SNAPSHOT_ADDED_TIMER_DELAY",
-  "browser.places.snapshot.monitorDelayAdded",
+  "browser.places.snapshots.monitorDelayAdded",
   5000
 );
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "SNAPSHOT_REMOVED_TIMER_DELAY",
-  "browser.places.snapshot.monitorDelayRemoved",
+  "browser.places.snapshots.monitorDelayRemoved",
   1000
 );
 
+// Expiration days for automatic and user managed snapshots.
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "SNAPSHOT_EXPIRE_DAYS",
+  "browser.places.snapshots.expiration.days",
+  210
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "SNAPSHOT_USERMANAGED_EXPIRE_DAYS",
+  "browser.places.snapshots.expiration.userManaged.days",
+  420
+);
+// We expire on the next idle after a snapshot was added or removed, and
+// idle-daily, but we don't want to expire too often or rarely.
+// Thus we define both a mininum and maximum time in the session among which
+// we'll expire chunks of snapshots.
+const EXPIRE_EVERY_MIN_MS = 60 * 60000; // 1 Hour.
+const EXPIRE_EVERY_MAX_MS = 120 * 60000; // 2 Hours.
+// The number of snapshots to expire at once.
+const EXPIRE_CHUNK_SIZE = 10;
+
 /**
  * Monitors changes in snapshots (additions, deletions, etc) and triggers
  * the snapshot group builders to run as necessary.
  */
 const SnapshotMonitor = new (class SnapshotMonitor {
   /**
    * @type {number}
    */
@@ -77,16 +99,25 @@ const SnapshotMonitor = new (class Snaps
    * Test-only. Used to specify one or more builders to use instead of the
    * built-in group builders.
    *
    * @type {object[]}
    */
   testGroupBuilders = null;
 
   /**
+   * The time of the last snapshots expiration.
+   */
+  #lastExpirationTime = 0;
+  /**
+   * How many snapshots to expire per chunk.
+   */
+  #expirationChunkSize = EXPIRE_CHUNK_SIZE;
+
+  /**
    * Internal getter to get the builders used.
    *
    * @returns {object[]}
    */
   get #groupBuilders() {
     if (this.testGroupBuilders) {
       return this.testGroupBuilders;
     }
@@ -165,16 +196,91 @@ const SnapshotMonitor = new (class Snaps
       }
     }
 
     this.#addedItems.clear();
     this.#removedUrls.clear();
   }
 
   /**
+   * Triggers expiration of a chunk of snapshots.
+   * We differentiate snapshots depending on whether they are user managed:
+   *  1. manually created by the user
+   *  2. part of a group
+   *     TODO: evaluate whether we want to consider user managed only snapshots
+   *           that are part of a user curated group, rather than any group.
+   * User managed snapshots will expire if their last interaction is older than
+   * browser.snapshots.expiration.userManaged.days, while others will expire
+   * after browser.snapshots.expiration.days.
+   * Snapshots that have a tombstone (removed_at is set) should not be expired.
+   *
+   * @param {boolean} onIdle
+   *   Whether this is running on idle. When it's false expiration is
+   *   rescheduled for the next idle.
+   */
+  async #expireSnapshotsChunk(onIdle = false) {
+    let now = Date.now();
+    if (now - this.#lastExpirationTime < EXPIRE_EVERY_MIN_MS) {
+      return;
+    }
+    let instance = (this._expireInstance = {});
+    let skip = false;
+    if (!onIdle) {
+      // Wait for the next idle.
+      skip = await new Promise(resolve =>
+        ChromeUtils.idleDispatch(deadLine => {
+          // Skip if we couldn't find an idle, unless we're over max waiting time.
+          resolve(
+            deadLine.didTimeout &&
+              now - this.#lastExpirationTime < EXPIRE_EVERY_MAX_MS
+          );
+        })
+      );
+    }
+    if (skip || instance != this._expireInstance) {
+      return;
+    }
+
+    this.#lastExpirationTime = now;
+    let urls = (
+      await Snapshots.query({
+        includeUserPersisted: false,
+        includeTombstones: false,
+        group: null,
+        lastInteractionBefore: now - SNAPSHOT_EXPIRE_DAYS * 86400000,
+        limit: this.#expirationChunkSize,
+      })
+    ).map(s => s.url);
+    if (instance != this._expireInstance) {
+      return;
+    }
+
+    if (urls.length < this.#expirationChunkSize) {
+      // If we couldn't find enough automatic snapshots, check if there's any
+      // user managed ones we can expire.
+      urls.push(
+        ...(
+          await Snapshots.query({
+            includeUserPersisted: true,
+            includeTombstones: false,
+            lastInteractionBefore:
+              now - SNAPSHOT_USERMANAGED_EXPIRE_DAYS * 86400000,
+            limit: this.#expirationChunkSize - urls.length,
+          })
+        ).map(s => s.url)
+      );
+    }
+    if (instance != this._expireInstance) {
+      return;
+    }
+
+    await Snapshots.delete([...new Set(urls)], true);
+  }
+
+  /**
    * Sets a timer ensuring that if the new timeout would occur sooner than the
    * current target time, the timer is changed to the sooner time.
    *
    * @param {number} timeout
    *   The timeout in milliseconds to use.
    */
   #setTimer(timeout) {
     let targetTime = Date.now() + timeout;
@@ -183,37 +289,49 @@ const SnapshotMonitor = new (class Snaps
       return;
     }
 
     if (this.#timer) {
       clearTimeout(this.#timer);
     }
 
     this.#currentTargetTime = targetTime;
-    this.#timer = setTimeout(
-      () => this.#triggerBuilders().catch(console.error),
-      timeout
-    );
+    this.#timer = setTimeout(() => {
+      this.#expireSnapshotsChunk().catch(console.error);
+      this.#triggerBuilders().catch(console.error);
+    }, timeout);
   }
 
   /**
    * observe function for nsIObserver. This is async so that we can call it in
    * tests and know that the triggerBuilders for idle-daily has finished.
    *
    * @param {object} subject
    * @param {string} topic
    * @param {nsISupports} data
    */
   async observe(subject, topic, data) {
-    if (topic == "places-snapshots-added") {
-      this.#onSnapshotAdded(JSON.parse(data));
-    } else if (topic == "places-snapshots-deleted") {
-      this.#onSnapshotRemoved(JSON.parse(data));
-    } else if (topic == "idle-daily") {
-      await this.#triggerBuilders(true);
+    switch (topic) {
+      case "places-snapshots-added":
+        this.#onSnapshotAdded(JSON.parse(data));
+        break;
+      case "places-snapshots-deleted":
+        this.#onSnapshotRemoved(JSON.parse(data));
+        break;
+      case "idle-daily":
+        await this.#expireSnapshotsChunk(true);
+        await this.#triggerBuilders(true);
+        break;
+      case "test-expiration":
+        this.#lastExpirationTime =
+          subject.lastExpirationTime || this.#lastExpirationTime;
+        this.#expirationChunkSize =
+          subject.expirationChunkSize || this.#expirationChunkSize;
+        await this.#expireSnapshotsChunk(subject.onIdle);
+        break;
     }
   }
 
   /**
    * Handles snapshots being added - adds to the internal list and sets the
    * timer.
    *
    * @param {object[]} items
--- a/browser/components/places/Snapshots.jsm
+++ b/browser/components/places/Snapshots.jsm
@@ -391,43 +391,68 @@ const Snapshots = new (class Snapshots {
     if (placeId) {
       await this.#addPageData([{ placeId, url }]);
 
       this.#notify("places-snapshots-added", [{ url, userPersisted }]);
     }
   }
 
   /**
-   * Deletes a snapshot, creating a tombstone. Note, the caller is expected
-   * to take account of the userPersisted value for a Snapshot when appropriate.
+   * Deletes one or more snapshots.
+   * By default this creates a tombstone rather than removing the entry, so that
+   * heuristics can take into account user removed snapshots.
+   * Note, the caller is expected to take account of the userPersisted value
+   * for a Snapshot when appropriate.
    *
-   * @param {string} url
-   *   The url of the snapshot to delete.
+   * @param {string|Array<string>} urls
+   *   The url of the snapshot to delete, or an Array of urls.
+   * @param {boolean} removeFromStore
+   *   Whether the snapshot should actually be removed rather than tombston-ed.
    */
-  async delete(url) {
-    url = this.stripFragments(url);
+  async delete(urls, removeFromStore = false) {
+    if (!Array.isArray(urls)) {
+      urls = [urls];
+    }
+    urls = urls.map(this.stripFragments);
+
+    let placeIdsSQLFragment = `
+    SELECT id FROM moz_places
+    WHERE url_hash IN (${PlacesUtils.sqlBindPlaceholders(
+      urls,
+      "hash(",
+      ")"
+    )}) AND url IN (${PlacesUtils.sqlBindPlaceholders(urls)})`;
+    let queryArgs = removeFromStore
+      ? [
+          `DELETE FROM moz_places_metadata_snapshots
+         WHERE place_id IN (${placeIdsSQLFragment})
+         RETURNING place_id`,
+          [...urls, ...urls],
+        ]
+      : [
+          `UPDATE moz_places_metadata_snapshots
+         SET removed_at = ?
+         WHERE place_id IN (${placeIdsSQLFragment})
+         RETURNING place_id`,
+          [Date.now(), ...urls, ...urls],
+        ];
+
     await PlacesUtils.withConnectionWrapper("Snapshots: delete", async db => {
-      let placeId = (
-        await db.executeCached(
-          `UPDATE moz_places_metadata_snapshots
-           SET removed_at = :removedAt
-           WHERE place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)
-           RETURNING place_id`,
-          { removedAt: Date.now(), url }
-        )
-      )[0].getResultByName("place_id");
+      let placeIds = (await db.executeCached(...queryArgs)).map(r =>
+        r.getResultByName("place_id")
+      );
       // Remove orphan page data.
       await db.executeCached(
         `DELETE FROM moz_places_metadata_snapshots_extra
-         WHERE place_id = :placeId`,
-        { placeId }
+         WHERE place_id IN (${PlacesUtils.sqlBindPlaceholders(placeIds)})`,
+        placeIds
       );
     });
 
-    this.#notify("places-snapshots-deleted", [url]);
+    this.#notify("places-snapshots-deleted", urls);
   }
 
   /**
    * Gets the details for a particular snapshot based on the url.
    *
    * @param {string} url
    *   The url of the snapshot to obtain.
    * @param {boolean} [includeTombstones]
@@ -474,54 +499,75 @@ const Snapshots = new (class Snapshots {
    * @param {number} [options.limit]
    *   A numerical limit to the number of snapshots to retrieve, defaults to 100.
    *   -1 may be used to get all snapshots, e.g. for use by the group builders.
    * @param {boolean} [options.includeTombstones]
    *   Whether to include tombstones in the snapshots to obtain.
    * @param {number} [options.type]
    *   Restrict the snapshots to those with a particular type of page data available.
    * @param {number} [options.group]
-   *   Restrict the snapshots to those within a particular group.
+   *   Restrict the snapshots to those within a particular group. Pass null
+   *   to get all the snapshots that are not part of a group.
    * @param {boolean} [options.includeHiddenInGroup]
    *   Only applies when querying a particular group. Pass true to include
    *   snapshots that are hidden in the group.
+   * @param {boolean} [options.includeUserPersisted]
+   *   Whether to include user persisted snapshots.
+   * @param {number} [options.lastInteractionBefore]
+   *   Restrict to snaphots whose last interaction was before the given time.
    * @param {boolean} [options.sortDescending]
    *   Whether or not to sortDescending. Defaults to true.
    * @param {string} [options.sortBy]
    *   A string to choose what to sort the snapshots by, e.g. "last_interaction_at"
    *   By default results are sorted by last_interaction_at.
    * @returns {Snapshot[]}
    *   Returns snapshots in order of descending last interaction time.
    */
   async query({
     limit = 100,
     includeTombstones = false,
     type = undefined,
     group = undefined,
     includeHiddenInGroup = false,
+    includeUserPersisted = true,
+    lastInteractionBefore = undefined,
     sortDescending = true,
     sortBy = "last_interaction_at",
   } = {}) {
     let db = await PlacesUtils.promiseDBConnection();
 
     let clauses = [];
     let bindings = {};
     let joins = [];
     let limitStatement = "";
 
     if (!includeTombstones) {
       clauses.push("removed_at IS NULL");
     }
 
+    if (!includeUserPersisted) {
+      clauses.push("user_persisted = :user_persisted");
+      bindings.user_persisted = this.USER_PERSISTED.NO;
+    }
+    if (lastInteractionBefore) {
+      clauses.push("last_interaction_at < :last_interaction_before");
+      bindings.last_interaction_before = lastInteractionBefore;
+    }
+
     if (type) {
       clauses.push("type = :type");
       bindings.type = type;
     }
 
-    if (group) {
+    if (group === null) {
+      clauses.push("group_id IS NULL");
+      joins.push(
+        "LEFT JOIN moz_places_metadata_groups_to_snapshots g USING(place_id)"
+      );
+    } else if (group) {
       clauses.push("group_id = :group");
       if (!includeHiddenInGroup) {
         clauses.push("g.hidden = 0");
       }
       bindings.group = group;
       joins.push(
         "LEFT JOIN moz_places_metadata_groups_to_snapshots g USING(place_id)"
       );
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/unit/interactions/test_snapshots_expiration.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for the expiration of snapshots.
+ */
+
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "SNAPSHOT_EXPIRE_DAYS",
+  "browser.places.snapshots.expiration.days",
+  210
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "SNAPSHOT_USERMANAGED_EXPIRE_DAYS",
+  "browser.places.snapshots.expiration.userManaged.days",
+  420
+);
+
+// For each snapshot define its url, whether it should be expired, whether it
+// should be a tomstone and a type.
+// The type may be:
+//   - manual: user_persisted
+//   - auto: created automatically by some heuristic
+//   - group: part of a group
+let gSnapshots = [
+  {
+    url: "https://example.com/1",
+    type: "manual",
+    expired: true,
+    tombstone: false,
+  },
+  {
+    url: "https://example.com/2",
+    type: "manual",
+    expired: false,
+    tombstone: false,
+  },
+  {
+    url: "https://example.com/3",
+    type: "group",
+    expired: true,
+    tombstone: false,
+  },
+  {
+    url: "https://example.com/4",
+    type: "auto",
+    expired: true,
+    tombstone: false,
+  },
+  {
+    url: "https://example.com/5",
+    type: "auto",
+    expired: false,
+    tombstone: false,
+  },
+  {
+    url: "https://example.com/6",
+    type: "auto",
+    expired: true,
+    tombstone: true,
+  },
+];
+
+add_task(async function setup() {
+  let now = Date.now();
+  let interactions = gSnapshots.map(s => {
+    if (s.expired) {
+      s.created_at =
+        now -
+        (1 + s.type != "auto"
+          ? SNAPSHOT_USERMANAGED_EXPIRE_DAYS
+          : SNAPSHOT_EXPIRE_DAYS) *
+          86400000;
+    } else {
+      s.created_at =
+        now - (s.type != "auto" ? SNAPSHOT_EXPIRE_DAYS : 1) * 86400000;
+    }
+    return s;
+  });
+  await addInteractions(interactions);
+
+  let groupSerial = 1;
+  for (let snapshot of gSnapshots) {
+    if (snapshot.type == "manual") {
+      snapshot.userPersisted = Snapshots.USER_PERSISTED.MANUAL;
+    }
+    await Snapshots.add(snapshot);
+    if (snapshot.type == "group") {
+      snapshot.group = await SnapshotGroups.add(
+        {
+          title: `test-group-${groupSerial++}`,
+          builder: "test",
+        },
+        [snapshot.url]
+      );
+    }
+    if (snapshot.tombstone) {
+      await Snapshots.delete(snapshot.url);
+    }
+  }
+
+  Services.prefs.setBoolPref("browser.places.interactions.enabled", true);
+
+  SnapshotMonitor.init();
+});
+
+add_task(async function test_idle_expiration() {
+  await SnapshotMonitor.observe({ onIdle: true }, "test-expiration");
+
+  let remaining = await Snapshots.query({ includeTombstones: true });
+  for (let snapshot of gSnapshots) {
+    let index = remaining.findIndex(s => s.url == snapshot.url);
+    if (!snapshot.expired || snapshot.tombstone) {
+      Assert.greater(index, -1, `${snapshot.url} should not have been removed`);
+      remaining.splice(index, 1);
+    } else {
+      Assert.equal(index, -1, `${snapshot.url} should have been removed`);
+    }
+  }
+  Assert.ok(
+    !remaining.length,
+    `All the snapshots should be processed: ${JSON.stringify(remaining)}`
+  );
+});
+
+add_task(async function test_active_limited_expiration() {
+  // Add 2 expirable snapshots.
+  let now = Date.now();
+  let expiredSnapshots = [
+    {
+      url: "https://example.com/7",
+      created_at: now - (SNAPSHOT_USERMANAGED_EXPIRE_DAYS + 1) * 86400000,
+    },
+    {
+      url: "https://example.com/8",
+      created_at: now - (SNAPSHOT_USERMANAGED_EXPIRE_DAYS + 1) * 86400000,
+    },
+  ];
+  for (let snapshot of expiredSnapshots) {
+    await addInteractions([snapshot]);
+    await Snapshots.add(snapshot);
+  }
+
+  let snapshots = await Snapshots.query({ includeTombstones: true });
+
+  info("expire again without setting lastExpirationTime, should be a no-op");
+  let expirationChunkSize = 1;
+  await SnapshotMonitor.observe(
+    {
+      expirationChunkSize,
+    },
+    "test-expiration"
+  );
+
+  // Since expiration just ran, nothing should have been expired.
+  Assert.equal(
+    (await Snapshots.query({ includeTombstones: true })).length,
+    snapshots.length,
+    "No snapshot should have been expired."
+  );
+
+  info("expire again, for real");
+  await SnapshotMonitor.observe(
+    {
+      expirationChunkSize,
+      lastExpirationTime: now - 24 * 86400000,
+    },
+    "test-expiration"
+  );
+
+  let remaining = await Snapshots.query({ includeTombstones: true });
+
+  let count = 0;
+  for (let snapshot of expiredSnapshots) {
+    let index = remaining.findIndex(s => s.url == snapshot.url);
+    if (index == -1) {
+      count++;
+    }
+  }
+  Assert.equal(
+    count,
+    expiredSnapshots.length - expirationChunkSize,
+    "Check the expected number of snapshots have been expired"
+  );
+});
--- a/browser/components/places/tests/unit/interactions/xpcshell.ini
+++ b/browser/components/places/tests/unit/interactions/xpcshell.ini
@@ -15,16 +15,17 @@ skip-if = toolkit == 'android' # bug 173
 [test_pinnedgroupbuilder.js]
 [test_snapshot_added_no_interaction.js]
 [test_snapshot_groups.js]
 [test_snapshot_group_triggers.js]
 [test_snapshots_basics.js]
 [test_snapshots_common_referrer_queries.js]
 [test_snapshots_create_allow_protocols.js]
 [test_snapshots_create_criteria.js]
+[test_snapshots_expiration.js]
 [test_snapshots_fragments.js]
 [test_snapshots_overlapping_queries.js]
 [test_snapshots_pagedata.js]
 [test_snapshots_page_image.js]
 [test_snapshots_protocols.js]
 [test_snapshots_queries.js]
 [test_snapshotscorer_combining.js]
 [test_snapshotscorer.js]
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_focus_promo.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_focus_promo.js
@@ -8,17 +8,17 @@ const intialCurrentRegion = Region._curr
 
 // Helper to run tests for specific regions
 async function setupRegions(home, current) {
   Region._setHomeRegion(home || "");
   Region._setCurrentRegion(current || "");
 }
 
 add_task(async function test_focus_promo_in_allowed_region() {
-  await ASRouter._resetMessageState();
+  ASRouter.resetMessageState();
 
   const allowedRegion = "ES"; // Spain
   setupRegions(allowedRegion, allowedRegion);
 
   const { win, tab } = await openTabAndWaitForRender();
 
   await SpecialPowers.spawn(tab, [], async function() {
     const promoContainer = content.document.querySelector(".promo"); // container which is present if promo is enabled and should show
@@ -26,17 +26,17 @@ add_task(async function test_focus_promo
     ok(promoContainer, "Focus promo is shown for allowed region");
   });
 
   await BrowserTestUtils.closeWindow(win);
   setupRegions(initialHomeRegion, intialCurrentRegion); // revert changes to regions
 });
 
 add_task(async function test_focus_promo_in_disallowed_region() {
-  await ASRouter._resetMessageState();
+  ASRouter.resetMessageState();
 
   const disallowedRegion = "CN"; // China
   setupRegions(disallowedRegion);
 
   const { win, tab } = await openTabAndWaitForRender();
 
   await SpecialPowers.spawn(tab, [], async function() {
     const promoContainer = content.document.querySelector(".promo"); // container which is removed if promo is disabled and/or should not show
--- a/browser/components/sessionstore/SessionFile.jsm
+++ b/browser/components/sessionstore/SessionFile.jsm
@@ -335,45 +335,48 @@ var SessionFileInternal = {
     }
     this._readOrigin = result.origin;
 
     result.noFilesFound = noFilesFound;
 
     return result;
   },
 
-  // Initialize SessionWriter.
-  // This should be called _before_ any other methods on SessionWriter (see
-  // `_callWriter()`).
-  _initWriter() {
-    if (this._initialized) {
-      return;
-    }
+  // Initialize SessionWriter and return it as a resolved promise.
+  getWriter() {
+    if (!this._initialized) {
+      if (!this._readOrigin) {
+        return Promise.reject(
+          "SessionFileInternal.getWriter() called too early! Please read the session file from disk first."
+        );
+      }
 
-    if (!this._readOrigin) {
-      throw new Error(
-        "_initWriter called too early! Please read the session file from disk first."
+      this._initialized = true;
+      SessionWriter.init(
+        this._readOrigin,
+        this._usingOldExtension,
+        this.Paths,
+        {
+          maxUpgradeBackups: Services.prefs.getIntPref(
+            PREF_MAX_UPGRADE_BACKUPS,
+            3
+          ),
+          maxSerializeBack: Services.prefs.getIntPref(
+            PREF_MAX_SERIALIZE_BACK,
+            10
+          ),
+          maxSerializeForward: Services.prefs.getIntPref(
+            PREF_MAX_SERIALIZE_FWD,
+            -1
+          ),
+        }
       );
     }
 
-    this._initialized = true;
-    SessionWriter.init(this._readOrigin, this._usingOldExtension, this.Paths, {
-      maxUpgradeBackups: Services.prefs.getIntPref(PREF_MAX_UPGRADE_BACKUPS, 3),
-      maxSerializeBack: Services.prefs.getIntPref(PREF_MAX_SERIALIZE_BACK, 10),
-      maxSerializeForward: Services.prefs.getIntPref(
-        PREF_MAX_SERIALIZE_FWD,
-        -1
-      ),
-    });
-  },
-
-  // Call a method of SessionWriter, making sure that it has been initialized first.
-  _callWriter(method, args = []) {
-    this._initWriter();
-    return SessionWriter[method](...args);
+    return Promise.resolve(SessionWriter);
   },
 
   write(aData) {
     if (RunState.isClosed) {
       return Promise.reject(new Error("SessionFile is closed"));
     }
 
     let isFinalWrite = false;
@@ -383,17 +386,17 @@ var SessionFileInternal = {
       isFinalWrite = true;
       RunState.setClosed();
     }
 
     let performShutdownCleanup = isFinalWrite && !SessionStore.willAutoRestore;
 
     this._attempts++;
     let options = { isFinalWrite, performShutdownCleanup };
-    let promise = this._callWriter("write", [aData, options]);
+    let promise = this.getWriter().then(writer => writer.write(aData, options));
 
     // Wait until the write is done.
     promise = promise.then(
       msg => {
         // Record how long the write took.
         this._recordTelemetry(msg.telemetry);
         this._successes++;
         if (msg.result.upgradeBackup) {
@@ -441,22 +444,22 @@ var SessionFileInternal = {
         Services.obs.notifyObservers(
           null,
           "sessionstore-final-state-write-complete"
         );
       }
     });
   },
 
-  wipe() {
-    return this._callWriter("wipe").then(() => {
-      // After a wipe, we need to make sure to re-initialize upon the next read(),
-      // because the state variables as sent to the writer have changed.
-      this._initialized = false;
-    });
+  async wipe() {
+    const writer = await this.getWriter();
+    await writer.wipe();
+    // After a wipe, we need to make sure to re-initialize upon the next read(),
+    // because the state variables as sent to the writer have changed.
+    this._initialized = false;
   },
 
   _recordTelemetry(telemetry) {
     for (let id of Object.keys(telemetry)) {
       let value = telemetry[id];
       let samples = [];
       if (Array.isArray(value)) {
         samples.push(...value);
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -223,16 +223,30 @@ const PREF_URLBAR_DEFAULTS = new Map([
 
   // JSON'ed array of blocked quick suggest URL digests.
   ["quickSuggest.blockedDigests", ""],
 
   // Global toggle for whether the quick suggest feature is enabled, i.e.,
   // sponsored and recommended results related to the user's search string.
   ["quicksuggest.enabled", false],
 
+  // Whether non-sponsored quick suggest results are subject to impression
+  // frequency caps. This pref is a fallback for the Nimbus variable
+  // `quickSuggestImpressionCapsNonSponsoredEnabled`.
+  ["quicksuggest.impressionCaps.nonSponsoredEnabled", false],
+
+  // Whether sponsored quick suggest results are subject to impression frequency
+  // caps. This pref is a fallback for the Nimbus variable
+  // `quickSuggestImpressionCapsSponsoredEnabled`.
+  ["quicksuggest.impressionCaps.sponsoredEnabled", false],
+
+  // JSON'ed object of quick suggest impression stats. Used for implementing
+  // impression frequency caps for quick suggest suggestions.
+  ["quicksuggest.impressionCaps.stats", ""],
+
   // Whether to show QuickSuggest related logs.
   ["quicksuggest.log", false],
 
   // The user's response to the Firefox Suggest online opt-in dialog.
   ["quicksuggest.onboardingDialogChoice", ""],
 
   // If the user has gone through a quick suggest prefs migration, then this
   // pref will have a user-branch value that records the latest prefs version.
--- a/browser/components/urlbar/UrlbarProviderQuickSuggest.jsm
+++ b/browser/components/urlbar/UrlbarProviderQuickSuggest.jsm
@@ -60,32 +60,52 @@ const TELEMETRY_SCALARS = {
   HELP_SPONSORED_BEST_MATCH:
     "contextual.services.quicksuggest.help_sponsored_bestmatch",
   HELP_NONSPONSORED_BEST_MATCH:
     "contextual.services.quicksuggest.help_nonsponsored_bestmatch",
 };
 
 const TELEMETRY_EVENT_CATEGORY = "contextservices.quicksuggest";
 
+// This object maps impression stats object keys to their corresponding keys in
+// the `extra` object of impression cap telemetry events. The main reason this
+// is necessary is because the keys of the `extra` object are limited to 15
+// characters in length, which some stats object keys exceed. It also forces us
+// to be deliberate about keys we add to the `extra` object, since the `extra`
+// object is limited to 10 keys.
+let TELEMETRY_IMPRESSION_CAP_EXTRA_KEYS = {
+  // stats object key -> `extra` telemetry event object key
+  intervalSeconds: "intervalSeconds",
+  startDateMs: "startDate",
+  count: "count",
+  maxCount: "maxCount",
+  impressionDateMs: "impressionDate",
+};
+
 // Identifies the source of the QuickSuggest suggestion.
 const QUICK_SUGGEST_SOURCE = {
   REMOTE_SETTINGS: "remote-settings",
   MERINO: "merino",
 };
 
 /**
  * A provider that returns a suggested url to the user based on what
  * they have currently typed so they can navigate directly.
  */
 class ProviderQuickSuggest extends UrlbarProvider {
   constructor(...args) {
     super(...args);
-    this._updateExperimentState();
+
+    UrlbarQuickSuggest.init();
+    UrlbarQuickSuggest.on("config-set", () => this._validateImpressionStats());
+
+    this._updateFeatureState();
+    NimbusFeatures.urlbar.onUpdate(() => this._updateFeatureState());
+
     UrlbarPrefs.addObserver(this);
-    NimbusFeatures.urlbar.onUpdate(() => this._updateExperimentState());
   }
 
   /**
    * Returns the name of this provider.
    * @returns {string} the name of this provider.
    */
   get name() {
     return "UrlbarProviderQuickSuggest";
@@ -189,16 +209,26 @@ class ProviderQuickSuggest extends Urlba
     if (
       UrlbarPrefs.get("merinoEnabled") &&
       UrlbarPrefs.get("quicksuggest.dataCollection.enabled") &&
       queryContext.allowRemoteResults()
     ) {
       promises.push(this._fetchMerinoSuggestions(queryContext, searchString));
     }
 
+    // While we're waiting on suggestions, opportunistically reset elapsed
+    // impression counters and record "reset" telemetry as appropriate. If we
+    // didn't need to record telemetry for periods with no impressions, then we
+    // could simply reset elapsed counters on each impression instead of doing
+    // it here. But since we do need to record telemetry for periods with no
+    // impressions, we need to reset counters more often. Doing it here means no
+    // telemetry will be recorded as long as the user doesn't do any searches,
+    // but the alternative is to use one or more long-lived timers.
+    this._resetElapsedImpressionCounters();
+
     // Wait for both sources to finish before adding a suggestion.
     let allSuggestions = await Promise.all(promises);
     if (instance != this.queryInstance) {
       return;
     }
 
     // Filter suggestions, keeping in mind both the remote settings and Merino
     // fetches return null when there are no matches. Take the remaining one
@@ -416,17 +446,17 @@ class ProviderQuickSuggest extends Urlba
    *   it describes the search string and picked result.
    */
   onEngagement(isPrivate, state, queryContext, details) {
     if (!this._addedResultInLastQuery) {
       return;
     }
     this._addedResultInLastQuery = false;
 
-    // Per spec, we update telemetry only when the user picks a result, i.e.,
+    // Per spec, we count impressions only when the user picks a result, i.e.,
     // when `state` is "engagement".
     if (state != "engagement") {
       return;
     }
 
     // Get the index of the quick suggest result. Usually it will be last, so to
     // avoid an O(n) lookup in the common case, check the last result first. It
     // may not be last if `browser.urlbar.showSearchSuggestionsFirst` is false
@@ -439,16 +469,19 @@ class ProviderQuickSuggest extends Urlba
       );
       if (resultIndex < 0) {
         this.logger.error(`Could not find quick suggest result`);
         return;
       }
       result = queryContext.results[resultIndex];
     }
 
+    // Update impression stats.
+    this._updateImpressionStats(result.payload.isSponsored);
+
     // Record telemetry.  We want to record the 1-based index of the result, so
     // add 1 to the 0-based resultIndex.
     let telemetryResultIndex = resultIndex + 1;
 
     // impression scalars
     Services.telemetry.keyedScalarAdd(
       TELEMETRY_SCALARS.IMPRESSION,
       telemetryResultIndex,
@@ -541,21 +574,27 @@ class ProviderQuickSuggest extends Urlba
    * Called when a urlbar pref changes.
    *
    * @param {string} pref
    *   The name of the pref relative to `browser.urlbar`.
    */
   onPrefChanged(pref) {
     switch (pref) {
       case "quickSuggest.blockedDigests":
-        this.logger.debug(
-          "browser.urlbar.quickSuggest.blockedDigests changed, loading digests"
-        );
+        this.logger.info("browser.urlbar.quickSuggest.blockedDigests changed");
         this._loadBlockedDigests();
         break;
+      case "quicksuggest.impressionCaps.stats":
+        if (!this._updatingImpressionStats) {
+          this.logger.info(
+            "browser.urlbar.quicksuggest.impressionCaps.stats changed"
+          );
+          this._loadImpressionStats();
+        }
+        break;
       case "quicksuggest.dataCollection.enabled":
         if (!UrlbarPrefs.updatingFirefoxSuggestScenario) {
           Services.telemetry.recordEvent(
             TELEMETRY_EVENT_CATEGORY,
             "data_collect_toggled",
             UrlbarPrefs.get(pref) ? "enabled" : "disabled"
           );
         }
@@ -836,28 +875,61 @@ class ProviderQuickSuggest extends Urlba
     }));
   }
 
   /**
    * Returns whether a given suggestion can be added for a query, assuming the
    * provider itself should be active.
    *
    * @param {object} suggestion
-   *   A suggestion object fetched from UrlbarQuickSuggest.
    * @returns {boolean}
    *   Whether the suggestion can be added.
    */
   async _canAddSuggestion(suggestion) {
-    return (
-      ((suggestion.is_sponsored &&
-        UrlbarPrefs.get("suggest.quicksuggest.sponsored")) ||
-        (!suggestion.is_sponsored &&
-          UrlbarPrefs.get("suggest.quicksuggest.nonsponsored"))) &&
-      !(await this.isSuggestionBlocked(suggestion.url))
-    );
+    this.logger.info("Checking if suggestion can be added");
+    this.logger.debug(JSON.stringify({ suggestion }));
+
+    // Return false if suggestions are disabled.
+    if (
+      (suggestion.is_sponsored &&
+        !UrlbarPrefs.get("suggest.quicksuggest.sponsored")) ||
+      (!suggestion.is_sponsored &&
+        !UrlbarPrefs.get("suggest.quicksuggest.nonsponsored"))
+    ) {
+      this.logger.info("Suggestions disabled, not adding suggestion");
+      return false;
+    }
+
+    // Return false if an impression cap has been hit.
+    if (
+      (suggestion.is_sponsored &&
+        UrlbarPrefs.get("quickSuggestImpressionCapsSponsoredEnabled")) ||
+      (!suggestion.is_sponsored &&
+        UrlbarPrefs.get("quickSuggestImpressionCapsNonSponsoredEnabled"))
+    ) {
+      let type = suggestion.is_sponsored ? "sponsored" : "nonsponsored";
+      let stats = this._impressionStats?.[type];
+      if (stats) {
+        let hitStats = stats.filter(s => s.maxCount <= s.count);
+        if (hitStats.length) {
+          this.logger.info("Impression cap(s) hit, not adding suggestion");
+          this.logger.debug(JSON.stringify({ type, hitStats }));
+          return false;
+        }
+      }
+    }
+
+    // Return false if the suggestion is blocked.
+    if (await this.isSuggestionBlocked(suggestion.url)) {
+      this.logger.info("Suggestion blocked, not adding suggestion");
+      return false;
+    }
+
+    this.logger.info("Suggestion can be added");
+    return true;
   }
 
   /**
    * Some suggestion properties like `url` and `click_url` include template
    * substrings that must be replaced with real values. This method replaces
    * templates with appropriate values in place.
    *
    * @param {object} suggestion
@@ -893,16 +965,332 @@ class ProviderQuickSuggest extends Urlba
           value.substring(0, timestampIndex) +
           timestamp +
           value.substring(timestampIndex + TIMESTAMP_TEMPLATE.length);
       }
     }
   }
 
   /**
+   * Increments the user's impression stats counters for the given type of
+   * suggestion. This should be called only when a suggestion impression is
+   * recorded.
+   *
+   * @param {boolean} isSponsored
+   *   Whether the impression was recorded for a sponsored suggestion.
+   */
+  _updateImpressionStats(isSponsored) {
+    this.logger.info("Starting impression stats update");
+    this.logger.debug(
+      JSON.stringify({
+        isSponsored,
+        currentStats: this._impressionStats,
+        impression_caps: UrlbarQuickSuggest.config.impression_caps,
+      })
+    );
+
+    // Don't bother recording anything if caps are disabled.
+    if (
+      (isSponsored &&
+        !UrlbarPrefs.get("quickSuggestImpressionCapsSponsoredEnabled")) ||
+      (!isSponsored &&
+        !UrlbarPrefs.get("quickSuggestImpressionCapsNonSponsoredEnabled"))
+    ) {
+      this.logger.info("Impression caps disabled, skipping update");
+      return;
+    }
+
+    // Get the user's impression stats. Since stats are synced from caps, if the
+    // stats don't exist then the caps don't exist, and don't bother recording
+    // anything in that case.
+    let type = isSponsored ? "sponsored" : "nonsponsored";
+    let stats = this._impressionStats?.[type];
+    if (!stats) {
+      this.logger.info("Impression caps undefined, skipping update");
+      return;
+    }
+
+    // Increment counters.
+    for (let stat of stats) {
+      stat.count++;
+      stat.impressionDateMs = Date.now();
+
+      // Record a telemetry event for each newly hit cap.
+      if (stat.count == stat.maxCount) {
+        this.logger.info(`'${type}' impression cap hit`);
+        this.logger.debug(JSON.stringify({ type, hitStat: stat }));
+        this._recordImpressionCapEvent({
+          stat,
+          eventType: "hit",
+          suggestionType: type,
+        });
+      }
+    }
+
+    // Save the stats.
+    this._updatingImpressionStats = true;
+    try {
+      UrlbarPrefs.set(
+        "quicksuggest.impressionCaps.stats",
+        JSON.stringify(this._impressionStats)
+      );
+    } finally {
+      this._updatingImpressionStats = false;
+    }
+
+    this.logger.info("Finished impression stats update");
+    this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
+  }
+
+  /**
+   * Loads and validates impression stats.
+   */
+  _loadImpressionStats() {
+    let json = UrlbarPrefs.get("quicksuggest.impressionCaps.stats");
+    if (!json) {
+      this._impressionStats = null;
+    } else {
+      try {
+        this._impressionStats = JSON.parse(
+          json,
+          // Infinity, which is the `intervalSeconds` for the lifetime cap, is
+          // stringified as `null` in the JSON, so convert it back to Infinity.
+          (key, value) =>
+            key == "intervalSeconds" && value === null ? Infinity : value
+        );
+      } catch (error) {}
+    }
+    this._validateImpressionStats();
+  }
+
+  /**
+   * Validates impression stats, which includes two things:
+   *
+   * - Type checks stats and discards any that are invalid. We do this because
+   *   stats are stored in prefs where anyone can modify them.
+   * - Syncs stats with impression caps so that there is one stats object
+   *   corresponding to each impression cap. See the `_impressionStats` comment
+   *   for more info.
+   */
+  _validateImpressionStats() {
+    let { impression_caps } = UrlbarQuickSuggest.config;
+
+    this.logger.info("Validating impression stats");
+    this.logger.debug(
+      JSON.stringify({
+        impression_caps,
+        currentStats: this._impressionStats,
+      })
+    );
+
+    if (!this._impressionStats || typeof this._impressionStats != "object") {
+      this._impressionStats = {};
+    }
+
+    for (let [type, cap] of Object.entries(impression_caps || {})) {
+      // Build a map from interval seconds to max counts in the caps.
+      let maxCapCounts = (cap.custom || []).reduce(
+        (map, { interval_s, max_count }) => {
+          map.set(interval_s, max_count);
+          return map;
+        },
+        new Map()
+      );
+      if (typeof cap.lifetime == "number") {
+        maxCapCounts.set(Infinity, cap.lifetime);
+      }
+
+      let stats = this._impressionStats[type];
+      if (!Array.isArray(stats)) {
+        stats = [];
+        this._impressionStats[type] = stats;
+      }
+
+      // Validate existing stats:
+      //
+      // * Discard stats with invalid properties.
+      // * Collect and remove stats with intervals that aren't in the caps. This
+      //   should only happen when caps are changed or removed.
+      // * For stats with intervals that are in the caps:
+      //   * Keep track of the max `stat.count` across all stats so we can
+      //     update the lifetime stat below.
+      //   * Set `stat.maxCount` to the max count in the corresponding cap.
+      let orphanStats = [];
+      let maxCountInStats = 0;
+      for (let i = 0; i < stats.length; ) {
+        let stat = stats[i];
+        if (
+          typeof stat.intervalSeconds != "number" ||
+          typeof stat.startDateMs != "number" ||
+          typeof stat.count != "number" ||
+          typeof stat.maxCount != "number" ||
+          typeof stat.impressionDateMs != "number"
+        ) {
+          stats.splice(i, 1);
+        } else {
+          maxCountInStats = Math.max(maxCountInStats, stat.count);
+          let maxCount = maxCapCounts.get(stat.intervalSeconds);
+          if (maxCount === undefined) {
+            stats.splice(i, 1);
+            orphanStats.push(stat);
+          } else {
+            stat.maxCount = maxCount;
+            i++;
+          }
+        }
+      }
+
+      // Create stats for caps that don't already have corresponding stats.
+      for (let [intervalSeconds, maxCount] of maxCapCounts.entries()) {
+        if (!stats.some(s => s.intervalSeconds == intervalSeconds)) {
+          stats.push({
+            maxCount,
+            intervalSeconds,
+            startDateMs: Date.now(),
+            count: 0,
+            impressionDateMs: 0,
+          });
+        }
+      }
+
+      // Merge orphaned stats into other ones if possible. For each orphan, if
+      // its interval is no bigger than an existing stat's interval, then the
+      // orphan's count can contribute to the existing stat's count, so merge
+      // the two.
+      for (let orphan of orphanStats) {
+        for (let stat of stats) {
+          if (orphan.intervalSeconds <= stat.intervalSeconds) {
+            stat.count = Math.max(stat.count, orphan.count);
+            stat.startDateMs = Math.min(stat.startDateMs, orphan.startDateMs);
+            stat.impressionDateMs = Math.max(
+              stat.impressionDateMs,
+              orphan.impressionDateMs
+            );
+          }
+        }
+      }
+
+      // If the lifetime stat exists, make its count the max count found above.
+      // This is only necessary when the lifetime cap wasn't present before, but
+      // it doesn't hurt to always do it.
+      let lifetimeStat = stats.find(s => s.intervalSeconds == Infinity);
+      if (lifetimeStat) {
+        lifetimeStat.count = maxCountInStats;
+      }
+
+      // Sort the stats by interval ascending. This isn't necessary except that
+      // it guarantees an ordering for tests.
+      stats.sort((a, b) => a.intervalSeconds - b.intervalSeconds);
+    }
+
+    this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
+  }
+
+  /**
+   * Resets the counters of impression stats whose intervals have elapased.
+   */
+  _resetElapsedImpressionCounters() {
+    this.logger.info("Checking for elapsed impression cap intervals");
+    this.logger.debug(
+      JSON.stringify({
+        currentStats: this._impressionStats,
+        impression_caps: UrlbarQuickSuggest.config.impression_caps,
+      })
+    );
+
+    let now = Date.now();
+    for (let [type, stats] of Object.entries(this._impressionStats)) {
+      for (let stat of stats) {
+        let elapsedMs = now - stat.startDateMs;
+        let intervalMs = 1000 * stat.intervalSeconds;
+        let elapsedIntervalCount = Math.floor(elapsedMs / intervalMs);
+        if (elapsedIntervalCount) {
+          this.logger.info(
+            `Resetting impression counter for interval ${stat.intervalSeconds}s`
+          );
+          this.logger.debug(
+            JSON.stringify({ type, stat, elapsedMs, elapsedIntervalCount })
+          );
+
+          // Record a telemetry event for each elapsed interval period.
+          let startDateMs = stat.startDateMs;
+          for (let i = 0; i < elapsedIntervalCount; i++) {
+            let endDateMs = startDateMs + intervalMs;
+            this._recordImpressionCapEvent({
+              eventType: "reset",
+              suggestionType: type,
+              eventDateMs: endDateMs,
+              stat: {
+                ...stat,
+                startDateMs,
+                // There were `stat.count` impressions in the first elapsed
+                // period and zero in all subsequent periods because if that
+                // were not the case then we would have recorded telemetry for
+                // the subsequent impression(s).
+                count: i == 0 ? stat.count : 0,
+              },
+            });
+            startDateMs += intervalMs;
+          }
+
+          // Reset the stat.
+          let remainderMs = elapsedMs - elapsedIntervalCount * intervalMs;
+          stat.startDateMs = now - remainderMs;
+          stat.count = 0;
+        }
+      }
+    }
+
+    this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
+  }
+
+  /**
+   * Records an impression cap telemetry event.
+   *
+   * @param {string} eventType
+   *   One of: "hit", "reset"
+   * @param {string} suggestionType
+   *   One of: "sponsored", "nonsponsored"
+   * @param {object} stat
+   *   The stats object whose max count was hit or whose counter was reset.
+   * @param {number} eventDateMs
+   *   The `eventDate` that should be recorded in the event's `extra` object.
+   *   We include this in `extra` even though events are timestamped because
+   *   "reset" events are batched during periods where the user doesn't perform
+   *   any searches and therefore impression counters are not reset.
+   */
+  _recordImpressionCapEvent({
+    eventType,
+    suggestionType,
+    stat,
+    eventDateMs = Date.now(),
+  }) {
+    // All `extra` object values must be strings.
+    let extra = {
+      type: suggestionType,
+      eventDate: String(eventDateMs),
+      endDate: String(stat.startDateMs + 1000 * stat.intervalSeconds),
+    };
+    for (let [statKey, value] of Object.entries(stat)) {
+      let extraKey = TELEMETRY_IMPRESSION_CAP_EXTRA_KEYS[statKey];
+      if (!extraKey) {
+        throw new Error("Unrecognized stats object key: " + statKey);
+      }
+      extra[extraKey] = String(value);
+    }
+    Services.telemetry.recordEvent(
+      TELEMETRY_EVENT_CATEGORY,
+      "impression_cap",
+      eventType,
+      "",
+      extra
+    );
+  }
+
+  /**
    * Loads blocked suggestion digests from the pref into `_blockedDigests`.
    */
   async _loadBlockedDigests() {
     this.logger.debug(`Queueing _loadBlockedDigests`);
     await this._blockTaskQueue.queue(() => {
       this.logger.info(`Loading blocked suggestion digests`);
       let json = UrlbarPrefs.get("quickSuggest.blockedDigests");
       this.logger.debug(
@@ -935,37 +1323,96 @@ class ProviderQuickSuggest extends Urlba
     let stringArray = new TextEncoder().encode(string);
     let hashBuffer = await crypto.subtle.digest("SHA-1", stringArray);
     let hashArray = new Uint8Array(hashBuffer);
     return Array.from(hashArray, b => b.toString(16).padStart(2, "0")).join("");
   }
 
   /**
    * Updates state based on the `browser.urlbar.quicksuggest.enabled` pref.
-   * Enable/disable event telemetry and ensure QuickSuggest module is loaded
-   * when enabled.
    */
-  _updateExperimentState() {
+  _updateFeatureState() {
+    let enabled = UrlbarPrefs.get("quickSuggestEnabled");
+    if (enabled == this._quickSuggestEnabled) {
+      // This method is a Nimbus `onUpdate()` callback, which means it's called
+      // each time any pref is changed that is a fallback for a Nimbus variable.
+      // We have many such prefs. The point of this method is to set up and tear
+      // down state when quick suggest's enabled status changes, so ignore
+      // updates that do not modify `quickSuggestEnabled`.
+      return;
+    }
+
+    this._quickSuggestEnabled = enabled;
+    this.logger.info("Updating feature state, feature enabled: " + enabled);
+
     Services.telemetry.setEventRecordingEnabled(
       TELEMETRY_EVENT_CATEGORY,
-      UrlbarPrefs.get("quickSuggestEnabled")
+      enabled
     );
-
-    // QuickSuggest is only loaded by the UrlBar on it's first query, however
-    // there is work it can preload when idle instead of starting it on user
-    // input. Referencing it here will trigger its import and init.
-    if (UrlbarPrefs.get("quickSuggestEnabled")) {
-      UrlbarQuickSuggest; // eslint-disable-line no-unused-expressions
+    if (enabled) {
+      this._loadImpressionStats();
       this._loadBlockedDigests();
     }
   }
 
+  // The most recently cached value of `UrlbarPrefs.get("quickSuggestEnabled")`.
+  // The purpose of this property is only to detect changes in the feature's
+  // enabled status. To determine the current status, call
+  // `UrlbarPrefs.get("quickSuggestEnabled")` directly instead.
+  _quickSuggestEnabled = false;
+
   // Whether we added a result during the most recent query.
   _addedResultInLastQuery = false;
 
+  // An object that keeps track of impression stats per sponsored and
+  // non-sponsored suggestion types. It looks like this:
+  //
+  //   { sponsored: statsArray, nonsponsored: statsArray }
+  //
+  // The `statsArray` values are arrays of stats objects, one per impression
+  // cap, which look like this:
+  //
+  //   { intervalSeconds, startDateMs, count, maxCount, impressionDateMs }
+  //
+  //   {number} intervalSeconds
+  //     The number of seconds in the corresponding cap's time interval.
+  //   {number} startDateMs
+  //     The timestamp at which the current interval period started and the
+  //     object's `count` was reset to zero. This is a value returned from
+  //     `Date.now()`.  When the current date/time advances past `startDateMs +
+  //     1000 * intervalSeconds`, a new interval period will start and `count`
+  //     will be reset to zero.
+  //   {number} count
+  //     The number of impressions during the current interval period.
+  //   {number} maxCount
+  //     The maximum number of impressions allowed during an interval period.
+  //     This value is the same as the `max_count` value in the corresponding
+  //     cap. It's stored in the stats object for convenience.
+  //   {number} impressionDateMs
+  //     The timestamp of the most recent impression, i.e., when `count` was
+  //     last incremented.
+  //
+  // There are two types of impression caps: interval and lifetime. Interval
+  // caps are periodically reset, and lifetime caps are never reset. For stats
+  // objects corresponding to interval caps, `intervalSeconds` will be the
+  // `interval_s` value of the cap. For stats objects corresponding to lifetime
+  // caps, `intervalSeconds` will be `Infinity`.
+  //
+  // `_impressionStats` is kept in sync with impression caps, and there is a
+  // one-to-one relationship between stats objects and caps. A stats object's
+  // corresponding cap is the one with the same suggestion type (sponsored or
+  // non-sponsored) and interval. See `_validateImpressionStats()` for more.
+  //
+  // Impression caps are stored in the remote settings config. See
+  // `UrlbarQuickSuggest.confg.impression_caps`.
+  _impressionStats = null;
+
+  // Whether impression stats are currently being updated.
+  _updatingImpressionStats = false;
+
   // Set of digests of the original URLs of blocked suggestions. A suggestion's
   // "original URL" is its URL straight from the source with an unreplaced
   // timestamp template. For details on the digests, see `_getDigest()`.
   //
   // The only reason we use URL digests is that suggestions currently do not
   // have persistent IDs. We could use the URLs themselves but SHA-1 digests are
   // only 40 chars long, so they save a little space. This is also consistent
   // with how blocked tiles on the newtab page are stored, but they use MD5. We
--- a/browser/components/urlbar/UrlbarQuickSuggest.jsm
+++ b/browser/components/urlbar/UrlbarQuickSuggest.jsm
@@ -7,16 +7,17 @@
 const EXPORTED_SYMBOLS = ["ONBOARDING_CHOICE", "UrlbarQuickSuggest"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+  EventEmitter: "resource://gre/modules/EventEmitter.jsm",
   NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
   QUICK_SUGGEST_SOURCE: "resource:///modules/UrlbarProviderQuickSuggest.jsm",
   RemoteSettings: "resource://services-settings/remote-settings.js",
   Services: "resource://gre/modules/Services.jsm",
   TaskQueue: "resource:///modules/UrlbarUtils.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProviderQuickSuggest:
     "resource:///modules/UrlbarProviderQuickSuggest.jsm",
@@ -61,18 +62,18 @@ const ONBOARDING_URI =
 // suggestions don't have a natural score so we hardcode a value, and we choose
 // a low value to allow Merino to experiment with a broad range of scores.
 const SUGGESTION_SCORE = 0.2;
 
 /**
  * Fetches the suggestions data from RemoteSettings and builds the structures
  * to provide suggestions for UrlbarProviderQuickSuggest.
  */
-class Suggestions {
-  constructor() {
+class QuickSuggest extends EventEmitter {
+  init() {
     UrlbarPrefs.addObserver(this);
     NimbusFeatures.urlbar.onUpdate(() => this._queueSettingsSetup());
 
     this._settingsTaskQueue.queue(() => {
       return new Promise(resolve => {
         Services.tm.idleDispatchToMainThread(() => {
           this._queueSettingsSetup();
           resolve();
@@ -97,21 +98,36 @@ class Suggestions {
    */
   get readyPromise() {
     return this._settingsTaskQueue.emptyPromise;
   }
 
   /**
    * @returns {object}
    *   Global quick suggest configuration from remote settings:
+   *
    *   {
    *     best_match: {
    *       min_search_string_length,
    *       blocked_suggestion_ids,
    *     },
+   *     impression_caps: {
+   *       nonsponsored: {
+   *         lifetime,
+   *         custom: [
+   *           { interval_s, max_count },
+   *         ],
+   *       },
+   *       sponsored: {
+   *         lifetime,
+   *         custom: [
+   *           { interval_s, max_count },
+   *         ],
+   *       },
+   *     },
    *   }
    */
   get config() {
     return this._config;
   }
 
   /**
    * Handle queries from the Urlbar.
@@ -344,17 +360,17 @@ class Suggestions {
   // Task queue for serializing access to remote settings and related data.
   // Methods in this class should use this when they need to to modify or access
   // the settings client. It ensures settings accesses are serialized, do not
   // overlap, and happen only one at a time. It also lets clients, especially
   // tests, use this class without having to worry about whether a settings sync
   // or initialization is ongoing; see `readyPromise`.
   _settingsTaskQueue = new TaskQueue();
 
-  // Configuration data synced from remote settings.
+  // Configuration data synced from remote settings. See the `config` getter.
   _config = {};
 
   // Maps from keywords to their corresponding results. Keywords are unique in
   // the underlying data, so a keyword will only ever map to one result.
   _resultsByKeyword = new Map();
 
   /**
    * Queues a task to ensure our remote settings client is initialized or torn
@@ -404,31 +420,41 @@ class Suggestions {
         this._rs
           .get({ filters: { type: "icon" } })
           .then(icons =>
             Promise.all(icons.map(i => this._rs.attachments.download(i)))
           ),
       ]);
 
       log.debug("Got configuration:", configArray);
-      this._config = configArray?.[0]?.configuration || {};
+      this._setConfig(configArray?.[0]?.configuration || {});
 
       this._resultsByKeyword.clear();
 
       for (let record of data) {
         let { buffer } = await this._rs.attachments.download(record, {
           useCache: true,
         });
         let results = JSON.parse(new TextDecoder("utf-8").decode(buffer));
         this._addResults(results);
       }
     });
   }
 
   /**
+   * Sets the quick suggest config and emits a "config-set" event.
+   *
+   * @param {object} config
+   */
+  _setConfig(config) {
+    this._config = config || {};
+    this.emit("config-set");
+  }
+
+  /**
    * Adds a list of result objects to the results map. This method is also used
    * by tests to set up mock suggestions.
    *
    * @param {array} results
    *   Array of result objects.
    */
   _addResults(results) {
     for (let result of results) {
@@ -455,9 +481,9 @@ class Suggestions {
     ).pop();
     if (!record) {
       return null;
     }
     return this._rs.attachments.download(record);
   }
 }
 
-let UrlbarQuickSuggest = new Suggestions();
+let UrlbarQuickSuggest = new QuickSuggest();
--- a/browser/components/urlbar/docs/firefox-suggest-telemetry.rst
+++ b/browser/components/urlbar/docs/firefox-suggest-telemetry.rst
@@ -328,16 +328,62 @@ Changelog
     The event is no longer recorded when the user interacts with the online
     modal dialog since the ``browser.urlbar.suggest.quicksuggest.nonsponsored``
     pref is no longer set when the user opts in or out. [Bug 1740965_]
 
 .. _1693126: https://bugzilla.mozilla.org/show_bug.cgi?id=1693126
 .. _1735976: https://bugzilla.mozilla.org/show_bug.cgi?id=1735976
 .. _1740965: https://bugzilla.mozilla.org/show_bug.cgi?id=1740965
 
+contextservices.quicksuggest.impression_cap
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This event is recorded when an event related to an impression cap occurs. The
+event's objects are the following possible values:
+
+:hit:
+  Recorded when an impression cap is hit.
+:reset:
+  Recorded when a cap's counter is reset because its interval period has
+  elapsed.
+
+The event's ``extra`` object value contains the following properties:
+
+:count:
+  The number of impressions during the cap's interval period.
+:endDate:
+  The timestamp at which the cap's interval period will end (for "hit" events)
+  or did end (for "reset" events), in number of milliseconds since Unix epoch.
+  For lifetime caps, this value will be "Infinity".
+:eventDate:
+  The event's timestamp, in number of milliseconds since Unix epoch. For "reset"
+  events, this may be earlier than the timestamp on the event itself because the
+  implementation sometimes batches and records these events at a later date.
+  This ``eventDate`` value should be preferred over the timestamp on the event
+  itself.
+:impressionDate:
+  The timestamp of the most recent impression, in number of milliseconds since
+  Unix epoch.
+:intervalSeconds:
+  The number of seconds in the cap's interval period. For lifetime caps, this
+  value will be "Infinity".
+:maxCount:
+  The maximum number of impressions allowed in the cap's interval period.
+:startDate:
+  The timestamp at which the cap's interval period started, in number of seconds
+  since Unix epoch.
+:type:
+  The type of cap, one of: "sponsored", "nonsponsored"
+
+Changelog
+  Firefox 101.0
+    Introduced. [Bug 1761058_]
+
+.. _1761058: https://bugzilla.mozilla.org/show_bug.cgi?id=1761058
+
 contextservices.quicksuggest.opt_in_dialog
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 This event is recorded when the user interacts with the online modal dialog.
 The event's objects are the following:
 
 :accept:
   The user accepted the dialog and opted in. This object was removed in Firefox
--- a/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.jsm
+++ b/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.jsm
@@ -196,17 +196,17 @@ class QSTestUtils {
 
   /**
    * Sets the quick suggest configuration. You should call this again with
    * `DEFAULT_CONFIG` before your test finishes. See also `withConfig()`.
    *
    * @param {object} config
    */
   setConfig(config) {
-    UrlbarQuickSuggest._config = config;
+    UrlbarQuickSuggest._setConfig(config);
   }
 
   /**
    * Sets the quick suggest configuration, calls your callback, and restores the
    * default configuration.
    *
    * @param {object} config
    * @param {function} callback
--- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_configuration.js
+++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_configuration.js
@@ -20,66 +20,66 @@ const POLICY_PREF = "suggest.quicksugges
 
 let gDefaultBranch = Services.prefs.getDefaultBranch("browser.urlbar.");
 let gUserBranch = Services.prefs.getBranch("browser.urlbar.");
 
 add_task(async function init() {
   await QuickSuggestTestUtils.ensureQuickSuggestInit();
 });
 
-// Makes sure `UrlbarProviderQuickSuggest._updateExperimentState()` is called
+// Makes sure `UrlbarProviderQuickSuggest._updateFeatureState()` is called
 // when the `browser.urlbar.quicksuggest.enabled` pref is changed.
-add_task(async function test_updateExperimentState_pref() {
+add_task(async function test_updateFeatureState_pref() {
   Assert.ok(
     UrlbarPrefs.get("quicksuggest.enabled"),
     "Sanity check: quicksuggest.enabled is true by default"
   );
 
   let sandbox = sinon.createSandbox();
-  let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateExperimentState");
+  let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateFeatureState");
 
   UrlbarPrefs.set("quicksuggest.enabled", false);
   await UrlbarQuickSuggest.readyPromise;
   Assert.equal(
     spy.callCount,
     1,
-    "_updateExperimentState called once after changing pref"
+    "_updateFeatureState called once after changing pref"
   );
 
   UrlbarPrefs.clear("quicksuggest.enabled");
   await UrlbarQuickSuggest.readyPromise;
   Assert.equal(
     spy.callCount,
     2,
-    "_updateExperimentState called again after clearing pref"
+    "_updateFeatureState called again after clearing pref"
   );
 
   sandbox.restore();
 });
 
-// Makes sure `UrlbarProviderQuickSuggest._updateExperimentState()` is called
+// Makes sure `UrlbarProviderQuickSuggest._updateFeatureState()` is called
 // when a Nimbus experiment is installed and uninstalled.
-add_task(async function test_updateExperimentState_experiment() {
+add_task(async function test_updateFeatureState_experiment() {
   let sandbox = sinon.createSandbox();
-  let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateExperimentState");
+  let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateFeatureState");
 
   await QuickSuggestTestUtils.withExperiment({
     callback: () => {
       Assert.equal(
         spy.callCount,
         1,
-        "_updateExperimentState called once after installing experiment"
+        "_updateFeatureState called once after installing experiment"
       );
     },
   });
 
   Assert.equal(
     spy.callCount,
     2,
-    "_updateExperimentState called again after uninstalling experiment"
+    "_updateFeatureState called again after uninstalling experiment"
   );
 
   sandbox.restore();
 });
 
 add_task(async function test_indexes() {
   await QuickSuggestTestUtils.withExperiment({
     valueOverrides: {
--- a/browser/components/urlbar/tests/quicksuggest/unit/head.js
+++ b/browser/components/urlbar/tests/quicksuggest/unit/head.js
@@ -1,13 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../unit/head.js */
 
+XPCOMUtils.defineLazyModuleGetters(this, {
+  TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
+  UrlbarProviderQuickSuggest:
+    "resource:///modules/UrlbarProviderQuickSuggest.jsm",
+  UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
+});
+
 /**
  * Tests quick suggest prefs migrations.
  *
  * @param {object} testOverrides
  *   An object that modifies how migration is performed. It has the following
  *   properties, and all are optional:
  *
  *   {number} migrationVersion
--- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js
+++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js
@@ -2,22 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Basic tests for the quick suggest provider using the remote settings source.
 // See also test_quicksuggest_merino.js.
 
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  UrlbarProviderQuickSuggest:
-    "resource:///modules/UrlbarProviderQuickSuggest.jsm",
-  UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
-});
-
 const TELEMETRY_REMOTE_SETTINGS_LATENCY =
   "FX_URLBAR_QUICK_SUGGEST_REMOTE_SETTINGS_LATENCY_MS";
 
 const SPONSORED_SEARCH_STRING = "frab";
 const NONSPONSORED_SEARCH_STRING = "nonspon";
 
 const HTTP_SEARCH_STRING = "http prefix";
 const HTTPS_SEARCH_STRING = "https prefix";
--- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js
+++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_bestMatch.js
@@ -1,21 +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/. */
 
 // Tests best match quick suggest results.
 
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  UrlbarProviderQuickSuggest:
-    "resource:///modules/UrlbarProviderQuickSuggest.jsm",
-});
-
 const MAX_RESULT_COUNT = UrlbarPrefs.get("maxRichResults");
 
 // This search string length needs to be >= 4 to trigger its suggestion as a
 // best match instead of a usual quick suggest.
 const BEST_MATCH_POSITION_SEARCH_STRING = "bestmatchposition";
 const BEST_MATCH_POSITION = Math.round(MAX_RESULT_COUNT / 2);
 
 const SUGGESTIONS = [
new file mode 100644
--- /dev/null
+++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js
@@ -0,0 +1,3336 @@
+/* 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/. */
+
+// Tests impression frequency capping for quick suggest results.
+
+"use strict";
+
+const SUGGESTIONS = [
+  {
+    id: 1,
+    url: "http://example.com/sponsored",
+    title: "Sponsored suggestion",
+    keywords: ["sponsored"],
+    click_url: "http://example.com/click",
+    impression_url: "http://example.com/impression",
+    advertiser: "TestAdvertiser",
+  },
+  {
+    id: 2,
+    url: "http://example.com/nonsponsored",
+    title: "Non-sponsored suggestion",
+    keywords: ["nonsponsored"],
+    click_url: "http://example.com/click",
+    impression_url: "http://example.com/impression",
+    advertiser: "TestAdvertiser",
+    iab_category: "5 - Education",
+  },
+];
+
+const EXPECTED_SPONSORED_RESULT = {
+  type: UrlbarUtils.RESULT_TYPE.URL,
+  source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+  heuristic: false,
+  payload: {
+    url: "http://example.com/sponsored",
+    originalUrl: "http://example.com/sponsored",
+    displayUrl: "http://example.com/sponsored",
+    title: "Sponsored suggestion",
+    qsSuggestion: "sponsored",
+    icon: null,
+    isSponsored: true,
+    sponsoredImpressionUrl: "http://example.com/impression",
+    sponsoredClickUrl: "http://example.com/click",
+    sponsoredBlockId: 1,
+    sponsoredAdvertiser: "TestAdvertiser",
+    helpUrl: UrlbarProviderQuickSuggest.helpUrl,
+    helpL10nId: "firefox-suggest-urlbar-learn-more",
+    source: "remote-settings",
+  },
+};
+
+const EXPECTED_NONSPONSORED_RESULT = {
+  type: UrlbarUtils.RESULT_TYPE.URL,
+  source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+  heuristic: false,
+  payload: {
+    url: "http://example.com/nonsponsored",
+    originalUrl: "http://example.com/nonsponsored",
+    displayUrl: "http://example.com/nonsponsored",
+    title: "Non-sponsored suggestion",
+    qsSuggestion: "nonsponsored",
+    icon: null,
+    isSponsored: false,
+    sponsoredImpressionUrl: "http://example.com/impression",
+    sponsoredClickUrl: "http://example.com/click",
+    sponsoredBlockId: 2,
+    sponsoredAdvertiser: "TestAdvertiser",
+    helpUrl: UrlbarProviderQuickSuggest.helpUrl,
+    helpL10nId: "firefox-suggest-urlbar-learn-more",
+    source: "remote-settings",
+  },
+};
+
+let gSandbox;
+let gDateNowStub;
+
+add_task(async function init() {
+  UrlbarPrefs.set("quicksuggest.enabled", true);
+  UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", true);
+  UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", true);
+  UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
+  UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
+  UrlbarPrefs.set("bestMatch.enabled", false);
+
+  // Disable search suggestions so we don't hit the network.
+  Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+
+  await QuickSuggestTestUtils.ensureQuickSuggestInit(SUGGESTIONS);
+
+  // Set up a sinon stub for the `Date.now()` implementation inside of
+  // UrlbarProviderQuickSuggest. This lets us test searches performed at
+  // specific times. See `doTimedCallbacks()` for more info.
+  gSandbox = sinon.createSandbox();
+  gDateNowStub = gSandbox.stub(
+    Cu.getGlobalForObject(UrlbarProviderQuickSuggest).Date,
+    "now"
+  );
+});
+
+// Tests a single interval.
+add_task(async function oneInterval() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 1 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("sponsored", {
+        0: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "3",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "3000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        1: {
+          results: [[]],
+        },
+        2: {
+          results: [[]],
+        },
+        3: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "3",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "3000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "3",
+                  maxCount: "1",
+                  startDate: "3000",
+                  endDate: "6000",
+                  impressionDate: "3000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        4: {
+          results: [[]],
+        },
+        5: {
+          results: [[]],
+        },
+      });
+    },
+  });
+});
+
+// Tests multiple intervals.
+add_task(async function multipleIntervals() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [
+            { interval_s: 1, max_count: 1 },
+            { interval_s: 5, max_count: 3 },
+            { interval_s: 10, max_count: 5 },
+          ],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("sponsored", {
+        // 0s: 1 new impression; 1 impression total
+        0: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 1s: 1 new impression; 2 impressions total
+        1: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 2s: 1 new impression; 3 impressions total
+        2: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 5, max_count: 3
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 3s: no new impressions; 3 impressions total
+        3: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 4s: no new impressions; 3 impressions total
+        4: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "4000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "3000",
+                  endDate: "4000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 5s: 1 new impression; 4 impressions total
+        5: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "4000",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 6s: 1 new impression; 5 impressions total
+        6: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "6000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "6000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "6000",
+                  endDate: "7000",
+                  impressionDate: "6000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 10, max_count: 5
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "6000",
+                  intervalSeconds: "10",
+                  maxCount: "5",
+                  startDate: "0",
+                  endDate: "10000",
+                  impressionDate: "6000",
+                  count: "5",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 7s: no new impressions; 5 impressions total
+        7: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "7000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "6000",
+                  endDate: "7000",
+                  impressionDate: "6000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 8s: no new impressions; 5 impressions total
+        8: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "8000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "7000",
+                  endDate: "8000",
+                  impressionDate: "6000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 9s: no new impressions; 5 impressions total
+        9: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "9000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "8000",
+                  endDate: "9000",
+                  impressionDate: "6000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 10s: 1 new impression; 6 impressions total
+        10: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "10000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "9000",
+                  endDate: "10000",
+                  impressionDate: "6000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "10000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "5000",
+                  endDate: "10000",
+                  impressionDate: "6000",
+                  count: "2",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 10, max_count: 5
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "10000",
+                  intervalSeconds: "10",
+                  maxCount: "5",
+                  startDate: "0",
+                  endDate: "10000",
+                  impressionDate: "6000",
+                  count: "5",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "10000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "10000",
+                  endDate: "11000",
+                  impressionDate: "10000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 11s: 1 new impression; 7 impressions total
+        11: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "11000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "10000",
+                  endDate: "11000",
+                  impressionDate: "10000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "11000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "11000",
+                  endDate: "12000",
+                  impressionDate: "11000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 12s: 1 new impression; 8 impressions total
+        12: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "12000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "11000",
+                  endDate: "12000",
+                  impressionDate: "11000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "12000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "12000",
+                  endDate: "13000",
+                  impressionDate: "12000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 5, max_count: 3
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "12000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "10000",
+                  endDate: "15000",
+                  impressionDate: "12000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 13s: no new impressions; 8 impressions total
+        13: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "13000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "12000",
+                  endDate: "13000",
+                  impressionDate: "12000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 14s: no new impressions; 8 impressions total
+        14: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "14000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "13000",
+                  endDate: "14000",
+                  impressionDate: "12000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 15s: 1 new impression; 9 impressions total
+        15: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "15000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "14000",
+                  endDate: "15000",
+                  impressionDate: "12000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "15000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "10000",
+                  endDate: "15000",
+                  impressionDate: "12000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "15000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "15000",
+                  endDate: "16000",
+                  impressionDate: "15000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 16s: 1 new impression; 10 impressions total
+        16: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "16000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "15000",
+                  endDate: "16000",
+                  impressionDate: "15000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "16000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "16000",
+                  endDate: "17000",
+                  impressionDate: "16000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 10, max_count: 5
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "16000",
+                  intervalSeconds: "10",
+                  maxCount: "5",
+                  startDate: "10000",
+                  endDate: "20000",
+                  impressionDate: "16000",
+                  count: "5",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 17s: no new impressions; 10 impressions total
+        17: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "17000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "16000",
+                  endDate: "17000",
+                  impressionDate: "16000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 18s: no new impressions; 10 impressions total
+        18: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "18000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "17000",
+                  endDate: "18000",
+                  impressionDate: "16000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 19s: no new impressions; 10 impressions total
+        19: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "19000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "18000",
+                  endDate: "19000",
+                  impressionDate: "16000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 20s: 1 new impression; 11 impressions total
+        20: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "20000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "19000",
+                  endDate: "20000",
+                  impressionDate: "16000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "20000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "15000",
+                  endDate: "20000",
+                  impressionDate: "16000",
+                  count: "2",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 10, max_count: 5
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "20000",
+                  intervalSeconds: "10",
+                  maxCount: "5",
+                  startDate: "10000",
+                  endDate: "20000",
+                  impressionDate: "16000",
+                  count: "5",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "20000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "20000",
+                  endDate: "21000",
+                  impressionDate: "20000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+      });
+    },
+  });
+});
+
+// Tests a lifetime cap.
+add_task(async function lifetime() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 3,
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("sponsored", {
+        0: {
+          results: [
+            [EXPECTED_SPONSORED_RESULT],
+            [EXPECTED_SPONSORED_RESULT],
+            [EXPECTED_SPONSORED_RESULT],
+            [],
+          ],
+          telemetry: {
+            events: [
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "Infinity",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "Infinity",
+                  impressionDate: "0",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        1: {
+          results: [[]],
+        },
+      });
+    },
+  });
+});
+
+// Tests one interval and a lifetime cap together.
+add_task(async function intervalAndLifetime() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 3,
+          custom: [{ interval_s: 1, max_count: 1 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("sponsored", {
+        // 0s: 1 new impression; 1 impression total
+        0: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 1s: 1 new impression; 2 impressions total
+        1: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 2s: 1 new impression; 3 impressions total
+        2: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: Infinity, max_count: 3
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "Infinity",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "Infinity",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        3: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+      });
+    },
+  });
+});
+
+// Tests multiple intervals and a lifetime cap together.
+add_task(async function multipleIntervalsAndLifetime() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 4,
+          custom: [
+            { interval_s: 1, max_count: 1 },
+            { interval_s: 5, max_count: 3 },
+          ],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("sponsored", {
+        // 0s: 1 new impression; 1 impression total
+        0: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 1s: 1 new impression; 2 impressions total
+        1: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 2s: 1 new impression; 3 impressions total
+        2: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 5, max_count: 3
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 3s: no new impressions; 3 impressions total
+        3: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 4s: no new impressions; 3 impressions total
+        4: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "4000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "3000",
+                  endDate: "4000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 5s: 1 new impression; 4 impressions total
+        5: {
+          results: [[EXPECTED_SPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "4000",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+              // hit: interval_s: Infinity, max_count: 4
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "Infinity",
+                  maxCount: "4",
+                  startDate: "0",
+                  endDate: "Infinity",
+                  impressionDate: "5000",
+                  count: "4",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 6s: no new impressions; 4 impressions total
+        6: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "6000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 7s: no new impressions; 4 impressions total
+        7: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "7000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "6000",
+                  endDate: "7000",
+                  impressionDate: "5000",
+                  count: "0",
+                  type: "sponsored",
+                },
+              },
+            ],
+          },
+        },
+      });
+    },
+  });
+});
+
+// Smoke test for non-sponsored caps. Most tasks use sponsored results and caps,
+// but sponsored and non-sponsored should behave the same since they use the
+// same code paths.
+add_task(async function nonsponsored() {
+  await doTest({
+    config: {
+      impression_caps: {
+        nonsponsored: {
+          lifetime: 4,
+          custom: [
+            { interval_s: 1, max_count: 1 },
+            { interval_s: 5, max_count: 3 },
+          ],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedSearches("nonsponsored", {
+        // 0s: 1 new impression; 1 impression total
+        0: {
+          results: [[EXPECTED_NONSPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "0",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 1s: 1 new impression; 2 impressions total
+        1: {
+          results: [[EXPECTED_NONSPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "0",
+                  endDate: "1000",
+                  impressionDate: "0",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "1000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 2s: 1 new impression; 3 impressions total
+        2: {
+          results: [[EXPECTED_NONSPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "1000",
+                  endDate: "2000",
+                  impressionDate: "1000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+              // hit: interval_s: 5, max_count: 3
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "2000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 3s: no new impressions; 3 impressions total
+        3: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "3000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "2000",
+                  endDate: "3000",
+                  impressionDate: "2000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 4s: no new impressions; 3 impressions total
+        4: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "4000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "3000",
+                  endDate: "4000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 5s: 1 new impression; 4 impressions total
+        5: {
+          results: [[EXPECTED_NONSPONSORED_RESULT], []],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "4000",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "0",
+                  type: "nonsponsored",
+                },
+              },
+              // reset: interval_s: 5, max_count: 3
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "5",
+                  maxCount: "3",
+                  startDate: "0",
+                  endDate: "5000",
+                  impressionDate: "2000",
+                  count: "3",
+                  type: "nonsponsored",
+                },
+              },
+              // hit: interval_s: 1, max_count: 1
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+              // hit: interval_s: Infinity, max_count: 4
+              {
+                object: "hit",
+                extra: {
+                  eventDate: "5000",
+                  intervalSeconds: "Infinity",
+                  maxCount: "4",
+                  startDate: "0",
+                  endDate: "Infinity",
+                  impressionDate: "5000",
+                  count: "4",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 6s: no new impressions; 4 impressions total
+        6: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "6000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "5000",
+                  endDate: "6000",
+                  impressionDate: "5000",
+                  count: "1",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+        // 7s: no new impressions; 4 impressions total
+        7: {
+          results: [[]],
+          telemetry: {
+            events: [
+              // reset: interval_s: 1, max_count: 1
+              {
+                object: "reset",
+                extra: {
+                  eventDate: "7000",
+                  intervalSeconds: "1",
+                  maxCount: "1",
+                  startDate: "6000",
+                  endDate: "7000",
+                  impressionDate: "5000",
+                  count: "0",
+                  type: "nonsponsored",
+                },
+              },
+            ],
+          },
+        },
+      });
+    },
+  });
+});
+
+// Smoke test for sponsored and non-sponsored caps together. Most tasks use only
+// sponsored results and caps, but sponsored and non-sponsored should behave the
+// same since they use the same code paths.
+add_task(async function sponsoredAndNonsponsored() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 2,
+        },
+        nonsponsored: {
+          lifetime: 3,
+        },
+      },
+    },
+    callback: async () => {
+      // 1st searches
+      await checkSearch({
+        name: "sponsored 1",
+        searchString: "sponsored",
+        expectedResults: [EXPECTED_SPONSORED_RESULT],
+      });
+      await checkSearch({
+        name: "nonsponsored 1",
+        searchString: "nonsponsored",
+        expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+      });
+      await checkTelemetryEvents([]);
+
+      // 2nd searches
+      await checkSearch({
+        name: "sponsored 2",
+        searchString: "sponsored",
+        expectedResults: [EXPECTED_SPONSORED_RESULT],
+      });
+      await checkSearch({
+        name: "nonsponsored 2",
+        searchString: "nonsponsored",
+        expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+      });
+      await checkTelemetryEvents([
+        {
+          object: "hit",
+          extra: {
+            eventDate: "0",
+            intervalSeconds: "Infinity",
+            maxCount: "2",
+            startDate: "0",
+            endDate: "Infinity",
+            impressionDate: "0",
+            count: "2",
+            type: "sponsored",
+          },
+        },
+      ]);
+
+      // 3rd searches
+      await checkSearch({
+        name: "sponsored 3",
+        searchString: "sponsored",
+        expectedResults: [],
+      });
+      await checkSearch({
+        name: "nonsponsored 3",
+        searchString: "nonsponsored",
+        expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+      });
+      await checkTelemetryEvents([
+        {
+          object: "hit",
+          extra: {
+            eventDate: "0",
+            intervalSeconds: "Infinity",
+            maxCount: "3",
+            startDate: "0",
+            endDate: "Infinity",
+            impressionDate: "0",
+            count: "3",
+            type: "nonsponsored",
+          },
+        },
+      ]);
+
+      // 4th searches
+      await checkSearch({
+        name: "sponsored 4",
+        searchString: "sponsored",
+        expectedResults: [],
+      });
+      await checkSearch({
+        name: "nonsponsored 4",
+        searchString: "nonsponsored",
+        expectedResults: [],
+      });
+      await checkTelemetryEvents([]);
+    },
+  });
+});
+
+// Tests with an empty config to make sure results are not capped.
+add_task(async function emptyConfig() {
+  await doTest({
+    config: {},
+    callback: async () => {
+      for (let i = 0; i < 2; i++) {
+        await checkSearch({
+          name: "sponsored " + i,
+          searchString: "sponsored",
+          expectedResults: [EXPECTED_SPONSORED_RESULT],
+        });
+        await checkSearch({
+          name: "nonsponsored " + i,
+          searchString: "nonsponsored",
+          expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+        });
+      }
+      await checkTelemetryEvents([]);
+    },
+  });
+});
+
+// Tests with sponsored caps disabled. Non-sponsored should still be capped.
+add_task(async function sponsoredCapsDisabled() {
+  UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", false);
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 0,
+        },
+        nonsponsored: {
+          lifetime: 3,
+        },
+      },
+    },
+    callback: async () => {
+      for (let i = 0; i < 3; i++) {
+        await checkSearch({
+          name: "sponsored " + i,
+          searchString: "sponsored",
+          expectedResults: [EXPECTED_SPONSORED_RESULT],
+        });
+        await checkSearch({
+          name: "nonsponsored " + i,
+          searchString: "nonsponsored",
+          expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+        });
+      }
+      await checkTelemetryEvents([
+        {
+          object: "hit",
+          extra: {
+            eventDate: "0",
+            intervalSeconds: "Infinity",
+            maxCount: "3",
+            startDate: "0",
+            endDate: "Infinity",
+            impressionDate: "0",
+            count: "3",
+            type: "nonsponsored",
+          },
+        },
+      ]);
+
+      await checkSearch({
+        name: "sponsored additional",
+        searchString: "sponsored",
+        expectedResults: [EXPECTED_SPONSORED_RESULT],
+      });
+      await checkSearch({
+        name: "nonsponsored additional",
+        searchString: "nonsponsored",
+        expectedResults: [],
+      });
+      await checkTelemetryEvents([]);
+    },
+  });
+  UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", true);
+});
+
+// Tests with non-sponsored caps disabled. Sponsored should still be capped.
+add_task(async function nonsponsoredCapsDisabled() {
+  UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", false);
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 3,
+        },
+        nonsponsored: {
+          lifetime: 0,
+        },
+      },
+    },
+    callback: async () => {
+      for (let i = 0; i < 3; i++) {
+        await checkSearch({
+          name: "sponsored " + i,
+          searchString: "sponsored",
+          expectedResults: [EXPECTED_SPONSORED_RESULT],
+        });
+        await checkSearch({
+          name: "nonsponsored " + i,
+          searchString: "nonsponsored",
+          expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+        });
+      }
+      await checkTelemetryEvents([
+        {
+          object: "hit",
+          extra: {
+            eventDate: "0",
+            intervalSeconds: "Infinity",
+            maxCount: "3",
+            startDate: "0",
+            endDate: "Infinity",
+            impressionDate: "0",
+            count: "3",
+            type: "sponsored",
+          },
+        },
+      ]);
+
+      await checkSearch({
+        name: "sponsored additional",
+        searchString: "sponsored",
+        expectedResults: [],
+      });
+      await checkSearch({
+        name: "nonsponsored additional",
+        searchString: "nonsponsored",
+        expectedResults: [EXPECTED_NONSPONSORED_RESULT],
+      });
+      await checkTelemetryEvents([]);
+    },
+  });
+  UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", true);
+});
+
+// Tests a config change: 1 interval -> same interval with lower cap, with the
+// old cap already reached
+add_task(async function configChange_sameIntervalLowerCap_1() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "0s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "3",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [{ interval_s: 3, max_count: 1 }],
+              },
+            },
+          });
+        },
+        1: async () => {
+          await checkSearch({
+            name: "1s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+        3: async () => {
+          await checkSearch({
+            name: "3s 0",
+            searchString: "sponsored",
+            expectedResults: [EXPECTED_SPONSORED_RESULT],
+          });
+          await checkSearch({
+            name: "3s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "1",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "1",
+                startDate: "3000",
+                endDate: "6000",
+                impressionDate: "3000",
+                count: "1",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 1 interval -> same interval with lower cap, with the
+// old cap not reached
+add_task(async function configChange_sameIntervalLowerCap_2() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [{ interval_s: 3, max_count: 1 }],
+              },
+            },
+          });
+        },
+        1: async () => {
+          await checkSearch({
+            name: "1s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+        },
+        3: async () => {
+          await checkSearch({
+            name: "3s 0",
+            searchString: "sponsored",
+            expectedResults: [EXPECTED_SPONSORED_RESULT],
+          });
+          await checkSearch({
+            name: "3s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "1",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "2",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "1",
+                startDate: "3000",
+                endDate: "6000",
+                impressionDate: "3000",
+                count: "1",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 1 interval -> same interval with higher cap
+add_task(async function configChange_sameIntervalHigherCap() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "0s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "3",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [{ interval_s: 3, max_count: 5 }],
+              },
+            },
+          });
+        },
+        1: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "1s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "1s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "1000",
+                intervalSeconds: "3",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "1000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+        3: async () => {
+          for (let i = 0; i < 5; i++) {
+            await checkSearch({
+              name: "3s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "3s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "1000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "3000",
+                intervalSeconds: "3",
+                maxCount: "5",
+                startDate: "3000",
+                endDate: "6000",
+                impressionDate: "3000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 1 interval -> 2 new intervals with higher timeouts.
+// Impression counts for the old interval should contribute to the new
+// intervals.
+add_task(async function configChange_1IntervalTo2NewIntervalsHigher() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "3",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [
+                  { interval_s: 5, max_count: 3 },
+                  { interval_s: 10, max_count: 5 },
+                ],
+              },
+            },
+          });
+        },
+        3: async () => {
+          await checkSearch({
+            name: "3s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+        4: async () => {
+          await checkSearch({
+            name: "4s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+        5: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "5s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "5s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "5000",
+                intervalSeconds: "5",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "5000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "5000",
+                intervalSeconds: "10",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "10000",
+                impressionDate: "5000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 2 intervals -> 1 new interval with higher timeout.
+// Impression counts for the old intervals should contribute to the new
+// interval.
+add_task(async function configChange_2IntervalsTo1NewIntervalHigher() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [
+            { interval_s: 2, max_count: 2 },
+            { interval_s: 4, max_count: 4 },
+          ],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "2",
+                maxCount: "2",
+                startDate: "0",
+                endDate: "2000",
+                impressionDate: "0",
+                count: "2",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+        2: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "2s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "2000",
+                intervalSeconds: "2",
+                maxCount: "2",
+                startDate: "0",
+                endDate: "2000",
+                impressionDate: "0",
+                count: "2",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "2000",
+                intervalSeconds: "2",
+                maxCount: "2",
+                startDate: "2000",
+                endDate: "4000",
+                impressionDate: "2000",
+                count: "2",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "2000",
+                intervalSeconds: "4",
+                maxCount: "4",
+                startDate: "0",
+                endDate: "4000",
+                impressionDate: "2000",
+                count: "4",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [{ interval_s: 6, max_count: 5 }],
+              },
+            },
+          });
+        },
+        4: async () => {
+          await checkSearch({
+            name: "4s 0",
+            searchString: "sponsored",
+            expectedResults: [EXPECTED_SPONSORED_RESULT],
+          });
+          await checkSearch({
+            name: "4s 1",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "4000",
+                intervalSeconds: "6",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "6000",
+                impressionDate: "4000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+        5: async () => {
+          await checkSearch({
+            name: "5s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+        6: async () => {
+          for (let i = 0; i < 5; i++) {
+            await checkSearch({
+              name: "6s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "6s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "reset",
+              extra: {
+                eventDate: "6000",
+                intervalSeconds: "6",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "6000",
+                impressionDate: "4000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+            {
+              object: "hit",
+              extra: {
+                eventDate: "6000",
+                intervalSeconds: "6",
+                maxCount: "5",
+                startDate: "6000",
+                endDate: "12000",
+                impressionDate: "6000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 1 interval -> 1 new interval with lower timeout.
+// Impression counts for the old interval should not contribute to the new
+// interval since the new interval has a lower timeout.
+add_task(async function configChange_1IntervalTo1NewIntervalLower() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 5, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "5",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "5000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                custom: [{ interval_s: 3, max_count: 3 }],
+              },
+            },
+          });
+        },
+        1: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "3s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "3s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "1000",
+                intervalSeconds: "3",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "1000",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: 1 interval -> lifetime.
+// Impression counts for the old interval should contribute to the new lifetime
+// cap.
+add_task(async function configChange_1IntervalToLifetime() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "3",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "3000",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                lifetime: 3,
+              },
+            },
+          });
+        },
+        3: async () => {
+          await checkSearch({
+            name: "3s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: lifetime cap -> higher lifetime cap
+add_task(async function configChange_lifetimeCapHigher() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 3,
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "0s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "Infinity",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "Infinity",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                lifetime: 5,
+              },
+            },
+          });
+        },
+        1: async () => {
+          for (let i = 0; i < 2; i++) {
+            await checkSearch({
+              name: "1s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "1s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "1000",
+                intervalSeconds: "Infinity",
+                maxCount: "5",
+                startDate: "0",
+                endDate: "Infinity",
+                impressionDate: "1000",
+                count: "5",
+                type: "sponsored",
+              },
+            },
+          ]);
+        },
+      });
+    },
+  });
+});
+
+// Tests a config change: lifetime cap -> lower lifetime cap
+add_task(async function configChange_lifetimeCapLower() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 3,
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        0: async () => {
+          for (let i = 0; i < 3; i++) {
+            await checkSearch({
+              name: "0s " + i,
+              searchString: "sponsored",
+              expectedResults: [EXPECTED_SPONSORED_RESULT],
+            });
+          }
+          await checkSearch({
+            name: "0s additional",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([
+            {
+              object: "hit",
+              extra: {
+                eventDate: "0",
+                intervalSeconds: "Infinity",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "Infinity",
+                impressionDate: "0",
+                count: "3",
+                type: "sponsored",
+              },
+            },
+          ]);
+          QuickSuggestTestUtils.setConfig({
+            impression_caps: {
+              sponsored: {
+                lifetime: 1,
+              },
+            },
+          });
+        },
+        1: async () => {
+          await checkSearch({
+            name: "1s",
+            searchString: "sponsored",
+            expectedResults: [],
+          });
+          await checkTelemetryEvents([]);
+        },
+      });
+    },
+  });
+});
+
+// Makes sure stats are serialized to and from the pref correctly.
+add_task(async function prefSync() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 5,
+          custom: [
+            { interval_s: 3, max_count: 2 },
+            { interval_s: 5, max_count: 4 },
+          ],
+        },
+      },
+    },
+    callback: async () => {
+      for (let i = 0; i < 2; i++) {
+        await checkSearch({
+          name: i,
+          searchString: "sponsored",
+          expectedResults: [EXPECTED_SPONSORED_RESULT],
+        });
+      }
+
+      let json = UrlbarPrefs.get("quicksuggest.impressionCaps.stats");
+      Assert.ok(json, "JSON is non-empty");
+      Assert.deepEqual(
+        JSON.parse(json),
+        {
+          sponsored: [
+            {
+              intervalSeconds: 3,
+              count: 2,
+              maxCount: 2,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: 5,
+              count: 2,
+              maxCount: 4,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: null,
+              count: 2,
+              maxCount: 5,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+          ],
+        },
+        "JSON is correct"
+      );
+
+      UrlbarProviderQuickSuggest._impressionStats = null;
+      UrlbarProviderQuickSuggest._loadImpressionStats();
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        {
+          sponsored: [
+            {
+              intervalSeconds: 3,
+              count: 2,
+              maxCount: 2,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: 5,
+              count: 2,
+              maxCount: 4,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: Infinity,
+              count: 2,
+              maxCount: 5,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+          ],
+        },
+        "Impression stats were properly restored from the pref"
+      );
+    },
+  });
+});
+
+// Tests direct changes to the stats pref.
+add_task(async function prefDirectlyChanged() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          lifetime: 5,
+          custom: [{ interval_s: 3, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      let expectedStats = {
+        sponsored: [
+          {
+            intervalSeconds: 3,
+            count: 0,
+            maxCount: 3,
+            startDateMs: 0,
+            impressionDateMs: 0,
+          },
+          {
+            intervalSeconds: Infinity,
+            count: 0,
+            maxCount: 5,
+            startDateMs: 0,
+            impressionDateMs: 0,
+          },
+        ],
+      };
+
+      UrlbarPrefs.set("quicksuggest.impressionCaps.stats", "bogus");
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        expectedStats,
+        "Expected stats for 'bogus'"
+      );
+
+      UrlbarPrefs.set("quicksuggest.impressionCaps.stats", JSON.stringify({}));
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        expectedStats,
+        "Expected stats for {}"
+      );
+
+      UrlbarPrefs.set(
+        "quicksuggest.impressionCaps.stats",
+        JSON.stringify({ sponsored: "bogus" })
+      );
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        expectedStats,
+        "Expected stats for { sponsored: 'bogus' }"
+      );
+
+      UrlbarPrefs.set(
+        "quicksuggest.impressionCaps.stats",
+        JSON.stringify({
+          sponsored: [
+            {
+              intervalSeconds: 3,
+              count: 0,
+              maxCount: 3,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: "bogus",
+              count: 0,
+              maxCount: 99,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: Infinity,
+              count: 0,
+              maxCount: 5,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+          ],
+        })
+      );
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        expectedStats,
+        "Expected stats with intervalSeconds: 'bogus'"
+      );
+
+      UrlbarPrefs.set(
+        "quicksuggest.impressionCaps.stats",
+        JSON.stringify({
+          sponsored: [
+            {
+              intervalSeconds: 3,
+              count: 0,
+              maxCount: 123,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+            {
+              intervalSeconds: Infinity,
+              count: 0,
+              maxCount: 456,
+              startDateMs: 0,
+              impressionDateMs: 0,
+            },
+          ],
+        })
+      );
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        expectedStats,
+        "Expected stats with `maxCount` values different from caps"
+      );
+
+      let stats = {
+        sponsored: [
+          {
+            intervalSeconds: 3,
+            count: 1,
+            maxCount: 3,
+            startDateMs: 99,
+            impressionDateMs: 99,
+          },
+          {
+            intervalSeconds: Infinity,
+            count: 7,
+            maxCount: 5,
+            startDateMs: 1337,
+            impressionDateMs: 1337,
+          },
+        ],
+      };
+      UrlbarPrefs.set(
+        "quicksuggest.impressionCaps.stats",
+        JSON.stringify(stats)
+      );
+      Assert.deepEqual(
+        UrlbarProviderQuickSuggest._impressionStats,
+        stats,
+        "Expected stats with valid JSON"
+      );
+    },
+  });
+});
+
+// Tests multiple interval periods where the cap is not hit. Telemetry should be
+// recorded for these periods.
+add_task(async function intervalsElapsedButCapNotHit() {
+  await doTest({
+    config: {
+      impression_caps: {
+        sponsored: {
+          custom: [{ interval_s: 1, max_count: 3 }],
+        },
+      },
+    },
+    callback: async () => {
+      await doTimedCallbacks({
+        // 1s
+        1: async () => {
+          await checkSearch({
+            name: "1s",
+            searchString: "sponsored",
+            expectedResults: [EXPECTED_SPONSORED_RESULT],
+          });
+        },
+        // 10s
+        10: async () => {
+          // Impression counter resets are only triggered by `startQuery()` in
+          // the provider, so we need to do a search to trigger the events.
+          await checkSearch({
+            name: "reset trigger",
+            searchString: "this shouldn't match any suggestion",
+            expectedResults: [],
+          });
+
+          let expectedEvents = [
+            // 1s: reset with count = 0
+            {
+              object: "reset",
+              extra: {
+                eventDate: "1000",
+                intervalSeconds: "1",
+                maxCount: "3",
+                startDate: "0",
+                endDate: "1000",
+                impressionDate: "0",
+                count: "0",
+                type: "sponsored",
+              },
+            },
+            // 2s: reset with count = 1
+            {
+              object: "reset",
+              extra: {
+                eventDate: "2000",
+                intervalSeconds: "1",
+                maxCount: "3",
+                startDate: "1000",
+                endDate: "2000",
+                impressionDate: "1000",
+                count: "1",
+                type: "sponsored",
+              },
+            },
+          ];
+          // 3s to 10s: reset with count = 0
+          for (let i = 3; i <= 10; i++) {
+            expectedEvents.push({
+              object: "reset",
+              extra: {
+                eventDate: String(1000 * i),
+                intervalSeconds: "1",
+                maxCount: "3",
+                startDate: String(1000 * (i - 1)),
+                endDate: String(1000 * i),
+                impressionDate: "1000",
+                count: "0",
+                type: "sponsored",
+              },
+            });
+          }
+          await checkTelemetryEvents(expectedEvents);
+        },
+      });
+    },
+  });
+});
+
+/**
+ * Main test helper. Sets up state, calls your callback, and resets state.
+ *
+ * @param {object} config
+ *   The quick suggest config to use during the test.
+ * @param {function} callback
+ */
+async function doTest({ config, callback }) {
+  Services.telemetry.clearEvents();
+
+  // Make `Date.now()` return 0 to start with. It's necessary to do this before
+  // calling `withConfig()` because when a new config is set, the provider
+  // validates its impression stats, whose `startDateMs` values depend on
+  // `Date.now()`.
+  gDateNowStub.returns(0);
+
+  await QuickSuggestTestUtils.withConfig({ config, callback });
+  UrlbarPrefs.clear("quicksuggest.impressionCaps.stats");
+}
+
+/**
+ * Does a series of timed searches and checks their results and telemetry. This
+ * function relies on `doTimedCallbacks()`, so it may be helpful to look at it
+ * too.
+ *
+ * @param {string} searchString
+ * @param {object} expectedBySecond
+
+ *   An object that maps from seconds to objects that describe the searches to
+ *   perform, their expected results, and the expected telemetry. For a given
+ *   entry `S -> E` in this object, searches are performed S seconds after this
+ *   function is called. `E` is an object that looks like this:
+ *
+ *     { results, telemetry }
+ *
+ *     {array} results
+ *       An array of arrays. A search is performed for each sub-array in
+ *       `results`, and the contents of the sub-array are the expected results
+ *       for that search.
+ *     {object} telemetry
+ *       An object like this: { events }
+ *       {array} events
+ *         An array of expected telemetry events after all searches are done.
+ *         Telemetry events are cleared after checking these. If not present,
+ *         then it will be asserted that no events were recorded.
+ *
+ *   Example:
+ *
+ *     {
+ *       0: {
+ *         results: [[R1], []],
+ *         telemetry: {
+ *           events: [
+ *             someExpectedEvent,
+ *           ],
+ *         },
+ *       }
+ *       1: {
+ *         results: [[]],
+ *       },
+ *     }
+ *
+ *     0 seconds after `doTimedSearches()` is called, two searches are
+ *     performed. The first one is expected to return a single result R1, and
+ *     the second search is expected to return no results. After the searches
+ *     are done, one telemetry event is expected to be recorded.
+ *
+ *     1 second after `doTimedSearches()` is called, one search is performed.
+ *     It's expected to return no results, and no telemetry is expected to be
+ *     recorded.
+ */
+async function doTimedSearches(searchString, expectedBySecond) {
+  await doTimedCallbacks(
+    Object.entries(expectedBySecond).reduce(
+      (memo, [second, { results, telemetry }]) => {
+        memo[second] = async () => {
+          for (let i = 0; i < results.length; i++) {
+            let expectedResults = results[i];
+            await checkSearch({
+              searchString,
+              expectedResults,
+              name: `${second}s search ${i + 1} of ${results.length}`,
+            });
+          }
+          let { events } = telemetry || {};
+          await checkTelemetryEvents(events || []);
+        };
+        return memo;
+      },
+      {}
+    )
+  );
+}
+
+/**
+ * Takes a series a callbacks and times at which they should be called, and
+ * calls them accordingly. This function is specifically designed for
+ * UrlbarProviderQuickSuggest and its impression capping implementation because
+ * it works by stubbing `Date.now()` within UrlbarProviderQuickSuggest. The
+ * callbacks are not actually called at the given times but instead `Date.now()`
+ * is stubbed so that UrlbarProviderQuickSuggest will think they are being
+ * called at the given times.
+ *
+ * A more general implementation of this helper function that isn't tailored to
+ * UrlbarProviderQuickSuggest is commented out below, and unfortunately it
+ * doesn't work properly on macOS.
+ *
+ * @param {object} callbacksBySecond
+ *   An object that maps from seconds to callback functions. For a given entry
+ *   `S -> F` in this object, the callback F is called S seconds after
+ *   `doTimedCallbacks()` is called.
+ */
+async function doTimedCallbacks(callbacksBySecond) {
+  let entries = Object.entries(callbacksBySecond).sort(([t1], [t2]) => t1 - t2);
+  for (let [timeoutSeconds, callback] of entries) {
+    gDateNowStub.returns(1000 * timeoutSeconds);
+    await callback();
+  }
+}
+
+/*
+// This is the original implementation of `doTimedCallbacks()`, left here for
+// reference or in case the macOS problem described below is fixed. Instead of
+// stubbing `Date.now()` within UrlbarProviderQuickSuggest, it starts parallel
+// timers so that the callbacks are actually called at appropriate times. This
+// version of `doTimedCallbacks()` is therefore more generally useful, but it
+// has the drawback that your test has to run in real time. e.g., if one of your
+// callbacks needs to run 10s from now, the test must actually wait 10s.
+//
+// Unfortunately macOS seems to have some kind of limit of ~33 total 1-second
+// timers during any xpcshell test -- not 33 simultaneous timers but 33 total
+// timers. After that, timers fire randomly and with huge timeout periods that
+// are often exactly 10s greater than the specified period, as if some 10s
+// timeout internal to macOS is being hit. This problem does not seem to happen
+// when running the full browser, only during xpcshell tests. In fact the
+// problem can be reproduced in an xpcshell test that simply creates an interval
+// timer whose period is 1s (e.g., using `setInterval()` from Timer.jsm). After
+// ~33 ticks, the timer's period jumps to ~10s.
+async function doTimedCallbacks(callbacksBySecond) {
+  await Promise.all(
+    Object.entries(callbacksBySecond).map(
+      ([timeoutSeconds, callback]) => new Promise(
+        resolve => setTimeout(
+          () => callback().then(resolve),
+          1000 * parseInt(timeoutSeconds)
+        )
+      )
+    )
+  );
+}
+*/
+
+/**
+ * Does a search, triggers an engagement, and checks the results.
+ *
+ * @param {string} name
+ *   This value is the name of the search and will be logged in messages to make
+ *   debugging easier.
+ * @param {string} searchString
+ * @param {array} expectedResults
+ */
+async function checkSearch({ name, searchString, expectedResults }) {
+  info(`Preparing search "${name}" with search string "${searchString}"`);
+  let context = createContext(searchString, {
+    providers: [UrlbarProviderQuickSuggest.name],
+    isPrivate: false,
+  });
+  info(`Doing search: ${name}`);
+  await check_results({
+    context,
+    matches: expectedResults,
+  });
+  info(`Finished search: ${name}`);
+
+  // Impression stats are updated only on engagement, so force one now.
+  // `selIndex` doesn't really matter but since we're not trying to simulate a
+  // click on the suggestion, pass in -1 to ensure we don't record a click. Pass
+  // in true for `isPrivate` so we don't attempt to record the impression ping
+  // because otherwise the following PingCentre error is logged:
+  // "Structured Ingestion ping failure with error: undefined"
+  let isPrivate = true;
+  UrlbarProviderQuickSuggest.onEngagement(isPrivate, "engagement", context, {
+    selIndex: -1,
+  });
+}
+
+async function checkTelemetryEvents(expectedEvents) {
+  TelemetryTestUtils.assertEvents(
+    expectedEvents.map(event => ({
+      ...event,
+      category: QuickSuggestTestUtils.TELEMETRY_EVENT_CATEGORY,
+      method: "impression_cap",
+    }))
+  );
+  Services.telemetry.clearEvents();
+}
--- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js
+++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js
@@ -3,19 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Tests Merino integration with the quick suggest feature/provider.
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
-  UrlbarProviderQuickSuggest:
-    "resource:///modules/UrlbarProviderQuickSuggest.jsm",
-  UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
 });
 
 // We set the Merino timeout to a large value to avoid intermittent failures in
 // CI, especially TV tests, where the Merino fetch unexpectedly doesn't finish
 // before the default timeout.
 const TEST_MERINO_TIMEOUT_MS = 1000;
 
 // relative to `browser.urlbar`
--- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js
+++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js
@@ -6,18 +6,16 @@
 /**
  * Tests for quick suggest result position specified in suggestions.
  */
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   UrlbarProviderHeuristicFallback:
     "resource:///modules/UrlbarProviderHeuristicFallback.jsm",
   UrlbarProviderPlaces: "resource:///modules/UrlbarProviderPlaces.jsm",
-  UrlbarProviderQuickSuggest:
-    "resource:///modules/UrlbarProviderQuickSuggest.jsm",
   UrlbarProviderTabToSearch:
     "resource:///modules/UrlbarProviderTabToSearch.jsm",
 });
 
 const SPONSORED_SECOND_POSITION_SUGGEST = {
   id: 1,
   url: "http://example.com/?q=sponsored-second",
   title: "sponsored second",
--- a/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.ini
+++ b/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 skip-if = toolkit == 'android' # bug 1730213
-head = head.js ../../unit/head.js
+head = ../../unit/head.js head.js
 firefox-appdir = browser
 
 [test_quicksuggest.js]
 [test_quicksuggest_bestMatch.js]
+[test_quicksuggest_impressionCaps.js]
 [test_quicksuggest_merino.js]
 [test_quicksuggest_migrate_v1.js]
 [test_quicksuggest_migrate_v2.js]
 [test_quicksuggest_offlineDefault.js]
 [test_quicksuggest_positionInSuggestions.js]
--- a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
+++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
@@ -344,24 +344,24 @@ p {
   background: url("chrome://global/skin/icons/close.svg") center no-repeat;
   cursor: pointer;
   -moz-context-properties: fill;
   fill: currentColor;
   position: relative;
   top: -90px;
 }
 
-.promo-dismiss:hover {
-  background-color: var(--in-content-button-background) !important;
-}
-
 @media not (prefers-contrast) {
   .promo-dismiss {
     opacity: 0.5;
   }
+
+  .promo-dismiss:hover {
+    background-color: var(--in-content-button-background) !important;
+  }
 }
 
 .promo-content {
   width: 100%;
 }
 
 .promo-image-large img {
   width: 100%;
@@ -378,16 +378,17 @@ p {
   padding: 11px 15px;
   margin: 8px 0;
   appearance: button;
   background-color: var(--in-content-primary-button-background);
   color: var(--in-content-primary-button-text-color);
   border-radius: 4px;
   font-weight: 600;
   cursor: pointer;
+  border: 1px solid var(--in-content-primary-button-background);
 }
 
 .promo-cta .button:hover {
   background-color: var(--in-content-primary-button-background-hover);
   color: var(--in-content-primary-button-text-color-hover);
 }
 
 .promo.bottom .promo-cta {
--- a/build/win32/orderfile.txt
+++ b/build/win32/orderfile.txt
@@ -6175,17 +6175,16 @@ XPCOMService_GetSocketTransportService
 ?GetAsAUTF8String@?$Variant@V?$nsTString@D@@$0A@@storage@mozilla@@UAG?AW4nsresult@@AAV?$nsTSubstring@D@@@Z
 ?GetDetailedDescription@nsLocalHandlerApp@@UAG?AW4nsresult@@AAV?$nsTSubstring@_S@@@Z
 ?Add@SharedStringMapBuilder@ipc@dom@mozilla@@QAEXABV?$nsTString@D@@ABV?$nsTString@_S@@@Z
 ??0SharedStringMap@ipc@dom@mozilla@@QAE@$$QAVSharedStringMapBuilder@123@@Z
 ??4FileDescriptor@ipc@mozilla@@QAEAAV012@$$QAV012@@Z
 ?BroadcastStringBundle@ContentParent@dom@mozilla@@SAXABVStringBundleDescriptor@23@@Z
 ?Get@SharedStringMap@ipc@dom@mozilla@@QAE_NABV?$nsTString@D@@AAV?$nsTSubstring@_S@@@Z
 ?ReinitForContent@ImageBridgeChild@layers@mozilla@@SA_N$$QAV?$Endpoint@VPImageBridgeChild@layers@mozilla@@@ipc@3@I@Z
-?RecvAudioSessionData@widget@mozilla@@YA?AW4nsresult@@ABUnsID@@ABV?$nsTString@_S@@1@Z
 ?Release@WakeLock@dom@mozilla@@UAGKXZ
 ?GetInstance@PowerManagerService@power@dom@mozilla@@SA?AU?$already_AddRefed@VPowerManagerService@power@dom@mozilla@@@@XZ
 ?RegisterWakeLockObserver@hal@mozilla@@YAXPAV?$Observer@VWakeLockInformation@hal@mozilla@@@2@@Z
 ?EndAnimationFrame@XRFrame@dom@mozilla@@QAEXXZ
 ?EnableWakeLockNotifications@hal_impl@mozilla@@YAXXZ
 ?FirePendingEvents@nsDOMOfflineResourceList@@QAEXXZ
 ?Run@nsBaseAppShell@@UAG?AW4nsresult@@XZ
 ??0AutoNoJSAPI@dom@mozilla@@AAE@PAUJSContext@@@Z
--- a/build/win64/orderfile.txt
+++ b/build/win64/orderfile.txt
@@ -6459,17 +6459,16 @@ ZN11encoding_rs3mem17utf16_valid_up_to17
 ?GetAsAUTF8String@?$Variant@V?$nsTString@D@@$0A@@storage@mozilla@@UEAA?AW4nsresult@@AEAV?$nsTSubstring@D@@@Z
 ?GetDetailedDescription@nsLocalHandlerApp@@UEAA?AW4nsresult@@AEAV?$nsTSubstring@_S@@@Z
 ?Add@SharedStringMapBuilder@ipc@dom@mozilla@@QEAAXAEBV?$nsTString@D@@AEBV?$nsTString@_S@@@Z
 ??0SharedStringMap@ipc@dom@mozilla@@QEAA@$$QEAVSharedStringMapBuilder@123@@Z
 ??4FileDescriptor@ipc@mozilla@@QEAAAEAV012@$$QEAV012@@Z
 ?BroadcastStringBundle@ContentParent@dom@mozilla@@SAXAEBVStringBundleDescriptor@23@@Z
 ?Get@SharedStringMap@ipc@dom@mozilla@@QEAA_NAEBV?$nsTString@D@@AEAV?$nsTSubstring@_S@@@Z
 ?GetAndResetReleaseFence@RenderCompositor@wr@mozilla@@UEAA?AVFileDescriptor@ipc@3@XZ
-?RecvAudioSessionData@widget@mozilla@@YA?AW4nsresult@@AEBUnsID@@AEBV?$nsTString@_S@@1@Z
 ?Release@WakeLock@dom@mozilla@@UEAAKXZ
 ?GetInstance@PowerManagerService@power@dom@mozilla@@SA?AU?$already_AddRefed@VPowerManagerService@power@dom@mozilla@@@@XZ
 ?RegisterWakeLockObserver@hal@mozilla@@YAXPEAV?$Observer@VWakeLockInformation@hal@mozilla@@@2@@Z
 ??4?$nsTArray_Impl@_KUnsTArrayInfallibleAllocator@@@@QEAAAEAV0@$$QEAV0@@Z
 ?EnableWakeLockNotifications@hal_impl@mozilla@@YAXXZ
 ?FirePendingEvents@nsDOMOfflineResourceList@@QEAAXXZ
 ?Run@nsBaseAppShell@@UEAA?AW4nsresult@@XZ
 ?BeforeProcessTask@CycleCollectedJSContext@mozilla@@UEAAX_N@Z
--- a/dom/bindings/test/test_large_imageData.html
+++ b/dom/bindings/test/test_large_imageData.html
@@ -46,14 +46,23 @@ https://bugzilla.mozilla.org/show_bug.cg
       try {
         ctx.getImageData(0, 0, 23175, 23175);
       } catch (e) {
         ex = e;
       }
       ok(ex.toString().includes("negative or greater than the allowed amount"),
          "Expected getImageData exception");
 
+      ex = null;
+      try {
+        new ImageData(23175, 23175);
+      } catch (e) {
+        ex = e;
+      }
+      ok(ex.toString().includes("negative or greater than the allowed amount"),
+         "Expected ImageData constructor exception");
+
       SimpleTest.finish();
     }
     go();
   </script>
 </body>
 </html>
--- a/dom/canvas/ImageData.cpp
+++ b/dom/canvas/ImageData.cpp
@@ -45,18 +45,21 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 already_AddRefed<ImageData> ImageData::Constructor(const GlobalObject& aGlobal,
                                                    const uint32_t aWidth,
                                                    const uint32_t aHeight,
                                                    ErrorResult& aRv) {
   if (aWidth == 0 || aHeight == 0) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
+
+  // Restrict the typed array length to INT32_MAX because that's all we support
+  // in dom::TypedArray::ComputeState.
   CheckedInt<uint32_t> length = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
-  if (!length.isValid()) {
+  if (!length.isValid() || length.value() > INT32_MAX) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
   js::AssertSameCompartment(aGlobal.Context(), aGlobal.Get());
   JSObject* data = Uint8ClampedArray::Create(aGlobal.Context(), length.value());
   if (!data) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -221,17 +221,16 @@
 #if defined(MOZ_WIDGET_ANDROID)
 #  include "APKOpen.h"
 #endif
 
 #ifdef XP_WIN
 #  include <process.h>
 #  define getpid _getpid
 #  include "mozilla/WinDllServices.h"
-#  include "mozilla/widget/AudioSession.h"
 #  include "mozilla/widget/WinContentSystemParameters.h"
 #endif
 
 #if defined(XP_MACOSX)
 #  include "nsMacUtilsImpl.h"
 #endif /* XP_MACOSX */
 
 #ifdef MOZ_X11
@@ -3087,20 +3086,16 @@ void ContentChild::ShutdownInternal() {
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     CrashReporter::AnnotateCrashReport(
         CrashReporter::Annotation::IPCShutdownState,
         "content-child-shutdown started"_ns);
     os->NotifyObservers(ToSupports(this), "content-child-shutdown", nullptr);
   }
 
-#if defined(XP_WIN)
-  mozilla::widget::StopAudioSession();
-#endif
-
   GetIPCChannel()->SetAbortOnError(false);
 
   if (mProfilerController) {
     const bool isProfiling = profiler_is_active();
     CrashReporter::AnnotateCrashReport(
         CrashReporter::Annotation::ProfilerChildShutdownPhase,
         isProfiling ? "Profiling - GrabShutdownProfileAndShutdown"_ns
                     : "Not profiling - GrabShutdownProfileAndShutdown"_ns);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -12,16 +12,17 @@
 #include "mediasink/AudioSinkWrapper.h"
 #include "mediasink/DecodedStream.h"
 #include "mediasink/VideoSink.h"
 #include "mozilla/Logging.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
 #include "mozilla/ProfilerMarkerTypes.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StaticPrefs_media.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Tuple.h"
 #include "nsIMemoryReporter.h"
@@ -2521,24 +2522,33 @@ void MediaDecoderStateMachine::DecodingS
 
   // Don't enter buffering while prerolling so that the decoder has a chance to
   // enqueue some decoded data before we give up and start buffering.
   if (!mMaster->IsPlaying()) {
     return;
   }
 
   // Note we could have a wait promise pending when playing non-MSE EME.
-  if ((mMaster->OutOfDecodedAudio() && mMaster->IsWaitingAudioData()) ||
-      (mMaster->OutOfDecodedVideo() && mMaster->IsWaitingVideoData())) {
+  if (mMaster->OutOfDecodedAudio() && mMaster->IsWaitingAudioData()) {
+    PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
+                         "OutOfDecodedAudio");
+    SetState<BufferingState>();
+    return;
+  }
+  if (mMaster->OutOfDecodedVideo() && mMaster->IsWaitingVideoData()) {
+    PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
+                         "OutOfDecodedVideo");
     SetState<BufferingState>();
     return;
   }
 
   if (Reader()->UseBufferingHeuristics() && mMaster->HasLowDecodedData() &&
       mMaster->HasLowBufferedData() && !mMaster->mCanPlayThrough) {
+    PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
+                         "BufferingHeuristics");
     SetState<BufferingState>();
   }
 }
 
 void MediaDecoderStateMachine::LoopingDecodingState::HandleError(
     const MediaResult& aError) {
   SLOG("audio looping failed, aError=%s", aError.ErrorName().get());
   switch (aError.Code()) {
--- a/dom/media/webvtt/test/reftest/reftest.list
+++ b/dom/media/webvtt/test/reftest/reftest.list
@@ -1,2 +1,3 @@
 skip-if(Android) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&/^aarch64-msvc/.test(xulRuntime.XPCOMABI),0-136,0-427680) == vtt_update_display_after_removed_cue.html vtt_update_display_after_removed_cue_ref.html
 skip-if(Android) fuzzy-if(winWidget,0-170,0-170) == vtt_overlapping_time.html vtt_overlapping_time-ref.html
+skip-if(Android) != vtt_reflow_display.html vtt_reflow_display-ref.html
new file mode 100644
--- /dev/null
+++ b/dom/media/webvtt/test/reftest/vtt_reflow_display-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<link rel="stylesheet" href="vtt_reflow_display.css">
+<body>
+<div class="video-player">
+  <div class="video-layer">
+    <video id="v1" autoplay controls></video>
+  </div>
+</div>
+<script>
+/**
+ * Simply play and pause a video without any cues.
+ */
+async function testDisplayCueDuringFrequentReflowRef() {
+  const video = document.getElementById("v1");
+  video.src = "white.webm";
+  video.onplay = _ => {
+    video.onplay = null;
+    video.pause();
+    document.documentElement.removeAttribute('class');
+  }
+};
+
+window.addEventListener("MozReftestInvalidate",
+                        testDisplayCueDuringFrequentReflowRef);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/webvtt/test/reftest/vtt_reflow_display.css
@@ -0,0 +1,33 @@
+body {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  max-height: 100%;
+  width: 100vw;
+  height: 100vh;
+}
+.video-player {
+  display: flex;
+  max-height: calc(100% - 400px);
+  flex: 1 1 0;
+  flex-direction: column;
+  position: relative;
+  max-width: 100%;
+  height: 0;
+}
+.video-layer {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  flex: 1 1 0;
+}
+video {
+  object-fit: contain;
+  display: flex;
+  flex: auto;
+  max-width: 100%;
+  min-height: 0;
+  min-width: 0;
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/webvtt/test/reftest/vtt_reflow_display.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+</head>
+<link rel="stylesheet" href="vtt_reflow_display.css">
+<body>
+<div class="video-player">
+  <div class="video-layer">
+    <video id="v1" autoplay controls></video>
+  </div>
+</div>
+<script>
+/**
+ * In bug 1733232, setting some CSS properties (from bug 1733232 comment17)
+ * would cause video frame's reflow called very frequently, which crashed the
+ * video control and caused no cue showing. We compare this test with another
+ * white video without any cues, and they should NOT be equal.
+ */
+function testDisplayCueDuringFrequentReflow() {
+  let video = document.getElementById("v1");
+  video.src = "white.webm";
+  let cue = new VTTCue(0, 4, "hello testing");
+  cue.onenter = _ => {
+    cue.onenter = null;
+    video.pause();
+    document.documentElement.removeAttribute('class');
+  }
+  let track = video.addTextTrack("captions");
+  track.mode = "showing";
+  track.addCue(cue);
+};
+
+window.addEventListener("MozReftestInvalidate",
+                        testDisplayCueDuringFrequentReflow);
+</script>
+</body>
+</html>
new file mode 100644
index 0000000000000000000000000000000000000000..bbacad7ffd8244de8d8082b21818aa5ab77021c4
GIT binary patch
literal 10880
zc%1FpTS!xJ9KiA4nR6F{HkVVAV6&Id#7h!MMIEP6k;7m^_ogeC4c*0UmR;F7Iy*f?
zzM5UE86n=0E;OM)qyht_2cZiUK`ZFNXkjm9&Yj`*a(<6J^gHa~u+RUuKl?v@AO86V
z-iH}jd$cA#blP#u1*S`!%oO5ed6r;H!isEJ=Kg@V&TEc{Z#v6xxBl?Z3CoC(_yW$S
zS&ntAb=`iY*%nJ=-3q6_T}v^Yb*5$2R>UX1MRITdU~h56{pe$D`Dp3h-35mRhU$tI
zC%ZHzw&t?H&Ln=%9K3zd67V_fE{D(PZR==px{aqCKEKoBHkOr?mz7kMLqk>H`Wmx6
z;Pd!f?M7Eer^5#|c0nF{n)&H-4aEO~xc8nh@ny#%$8>Rwe^$`UwZC?`PG0yRigJQy
z*vH&bPfvYg{n1LWxO?iI%?=M3svmSD)~M7`4#I4h0|=Js;09B~GD{#=szM<l)Gg?F
zP~^y$6<qVJ);ImzD$o6k&)FFK`MDen-{hbG7G$8+jzAvd*-B%e3d$=QM}2kI{Qz;V
zM!XsE5aRa`e~$Q9(X*St-t=T1wu1KM#rrSzwa=|Nvq5^EDMs9kxCilG#3vDdg?LQb
zWqQPSA#RgCU)dRCz8~>v#NQ#lBJCMQ#H$cLj(9iXcM*RkeZCoAr03;o#J3|}i?|E%
ztB6k^zJT}-Y0u0?e5dsJW;P)6orw1#9zpyq;=iR`VL+Tk+>Uq`;)BxXtC&URqtdQa
zAzq015yaaNzk>L<gr`Ko#wf3xALdnhDg!D5Dg!D5Dg!D5Dg!Emco}F0R?8rt%7Ds%
z%7Ds%%7Ds%%7DrsUIw{dDg!D5Dg!D5Dg!D5Dg!EmWEmJ(Dg!D5Dg!D5Dg!D5Due&I
z4B}TU#S4nP$t#$frZ{+*ytTN@G2eM+f6}t2c;$RLs;w!g8A!)0cM9fu({Zzqf(AAn
H3-0^{T6}6q
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -172,29 +172,33 @@
 // The amount of time, in milliseconds, that our IO thread will stay alive
 // after the last event it processes.
 #define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 /**
  * If shutdown takes this long, kill actors of a quota client, to avoid reaching
  * the crash timeout.
  */
-#define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
+#define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
 
 /**
  * Automatically crash the browser if shutdown of a quota client takes this
  * long. We've chosen a value that is long enough that it is unlikely for the
  * problem to be falsely triggered by slow system I/O.  We've also chosen a
  * value long enough so that automated tests should time out and fail if
  * shutdown of a quota client takes too long.  Also, this value is long enough
  * so that testers can notice the timeout; we want to know about the timeouts,
  * not hide them. On the other hand this value is less than 60 seconds which is
  * used by nsTerminator to crash a hung main process.
  */
-#define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
+#define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
+
+static_assert(
+    SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
+    "The kill actors timeout must be shorter than the crash browser one.");
 
 // profile-before-change, when we need to shut down quota manager
 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
 
 #define KB *1024ULL
 #define MB *1024ULL KB
 #define GB *1024ULL MB
 
@@ -3695,23 +3699,16 @@ nsresult QuotaManager::Init() {
       do_Init(mDefaultStoragePath),
       GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
 
   QM_TRY_UNWRAP(do_Init(mIOThread),
                 MOZ_TO_RESULT_INVOKE_TYPED(
                     nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
                     "QuotaManager IO"));
 
-  // Make a timer here to avoid potential failures later. We don't actually
-  // initialize the timer until shutdown.
-  nsCOMPtr shutdownTimer = NS_NewTimer();
-  QM_TRY(OkIf(shutdownTimer), Err(NS_ERROR_FAILURE));
-
-  mShutdownTimer.init(WrapNotNullUnchecked(std::move(shutdownTimer)));
-
   static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
                     Client::LS == 3 && Client::TYPE_MAX == 4,
                 "Fix the registration!");
 
   // Register clients.
   auto clients = decltype(mClients)::ValueType{};
   clients.AppendElement(indexedDB::CreateQuotaClient());
   clients.AppendElement(cache::CreateQuotaClient());
@@ -3756,22 +3753,30 @@ void QuotaManager::SafeMaybeRecordQuotaC
 
   auto* const quotaManager = QuotaManager::Get();
 
   if (quotaManager && quotaManager->ShutdownStarted()) {
     quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
   }
 }
 
+void QuotaManager::RecordQuotaManagerShutdownStep(
+    const nsACString& aStepDescription) {
+  // Callable on any thread.
+  MOZ_ASSERT(mShutdownStarted);
+
+  RecordShutdownStep(Nothing{}, aStepDescription);
+}
+
 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
     const nsACString& aStepDescription) {
   // Callable on any thread.
 
   if (ShutdownStarted()) {
-    RecordShutdownStep(Nothing{}, aStepDescription);
+    RecordQuotaManagerShutdownStep(aStepDescription);
   }
 }
 
 bool QuotaManager::ShutdownStarted() const { return mShutdownStarted; }
 
 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
                                       const nsACString& aStepDescription) {
   MOZ_ASSERT(mShutdownStarted);
@@ -3806,150 +3811,215 @@ void QuotaManager::RecordShutdownStep(co
 #endif
 }
 
 void QuotaManager::Shutdown() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mShutdownStarted);
   MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
 
-  // Setting this flag prevents the service from being recreated and prevents
-  // further storagess from being created.
-  gShutdown = true;
-
+  // Define some local helper functions
+
+  auto flagShutdownStarted = [this]() {
+    // Setting this flag prevents the service from being recreated and prevents
+    // further storages from being created.
+    // XXX: Harmonize QM shutdown flags, see bug 1726714
+    gShutdown = true;
+
+    // StopIdleMaintenance used to happen before mShutdownStarted is set true
+    // but it is just an internal flag for the recording of shutdown steps
+    // and not evaluated elsewhere.
+
+    mShutdownStartedAt.init(TimeStamp::NowLoRes());
+    mShutdownStarted = true;
+  };
+
+  nsCOMPtr<nsITimer> crashBrowserTimer;
+
+  auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
+    auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
+
+    nsCString annotation;
+
+    for (Client::Type type : quotaManager->AllClientTypes()) {
+      auto& quotaClient = *(*quotaManager->mClients)[type];
+
+      if (!quotaClient.IsShutdownCompleted()) {
+        annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
+                                Client::TypeToText(type).get(),
+                                quotaClient.GetShutdownStatus().get(),
+                                quotaManager->mShutdownSteps[type].get());
+      }
+    }
+
+    {
+      MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+      annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
+                              gNormalOriginOps->Length());
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+      for (const auto& op : *gNormalOriginOps) {
+        nsCString name;
+        op->GetName(name);
+        annotation.AppendPrintf("Op: %s pending\n", name.get());
+      }
+#endif
+      annotation.AppendPrintf("Intermediate steps:\n%s\n",
+                              quotaManager->mQuotaManagerShutdownSteps.get());
+    }
+
+    CrashReporter::AnnotateCrashReport(
+        CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
+
+    MOZ_CRASH("Quota manager shutdown timed out");
+  };
+
+  auto startCrashBrowserTimer = [&]() {
+    crashBrowserTimer = NS_NewTimer();
+    MOZ_ASSERT(crashBrowserTimer);
+    if (crashBrowserTimer) {
+      RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
+      MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
+          crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
+          nsITimer::TYPE_ONE_SHOT,
+          "quota::QuotaManager::Shutdown::crashBrowserTimer"));
+    }
+  };
+
+  auto stopCrashBrowserTimer = [&]() {
+    if (crashBrowserTimer) {
+      RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
+      QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
+    }
+  };
+
+  auto initiateShutdownWorkThreads = [this]() {
+    RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
+    bool needsToWait = false;
+    for (Client::Type type : AllClientTypes()) {
+      // Clients are supposed to also AbortAllOperations from this point on
+      // to speed up shutdown, if possible. Thus pending operations
+      // might not be executed anymore.
+      needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
+    }
+
+    return needsToWait;
+  };
+
+  nsCOMPtr<nsITimer> killActorsTimer;
+
+  auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
+    auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
+
+    quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
+
+    // XXX: This abort is a workaround to unblock shutdown, which
+    // ought to be removed by bug 1682326. We probably need more
+    // checks to immediately abort new operations during
+    // shutdown.
+    quotaManager->GetClient(Client::IDB)->AbortAllOperations();
+
+    for (Client::Type type : quotaManager->AllClientTypes()) {
+      quotaManager->GetClient(type)->ForceKillActors();
+    }
+  };
+
+  auto startKillActorsTimer = [&]() {
+    killActorsTimer = NS_NewTimer();
+    MOZ_ASSERT(killActorsTimer);
+    if (killActorsTimer) {
+      RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
+      MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
+          killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
+          nsITimer::TYPE_ONE_SHOT,
+          "quota::QuotaManager::Shutdown::killActorsTimer"));
+    }
+  };
+
+  auto stopKillActorsTimer = [&]() {
+    if (killActorsTimer) {
+      RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
+      QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
+    }
+  };
+
+  auto isAllClientsShutdownComplete = [this] {
+    return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
+                       [&self = *this](const auto type) {
+                         return (*self.mClients)[type]->IsShutdownCompleted();
+                       });
+  };
+
+  auto shutdownAndJoinWorkThreads = [this]() {
+    RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
+    for (Client::Type type : AllClientTypes()) {
+      (*mClients)[type]->FinalizeShutdownWorkThreads();
+    }
+  };
+
+  auto shutdownAndJoinIOThread = [this]() {
+    RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
+    // NB: It's very important that runnable is destroyed on this thread
+    // (i.e. after we join the IO thread) because we can't release the
+    // QuotaManager on the IO thread. This should probably use
+    // NewNonOwningRunnableMethod ...
+    RefPtr<Runnable> runnable =
+        NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
+                          &QuotaManager::ShutdownStorage);
+    MOZ_ASSERT(runnable);
+
+    // Give clients a chance to cleanup IO thread only objects.
+    QM_WARNONLY_TRY(
+        QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
+
+    // Make sure to join with our IO thread.
+    QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
+  };
+
+  auto invalidatePendingDirectoryLocks = [this]() {
+    RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
+    for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
+      lock->Invalidate();
+    }
+  };
+
+  // Body of the function
+
+  flagShutdownStarted();
+
+  startCrashBrowserTimer();
+
+  // XXX: StopIdleMaintenance now just notifies all clients to abort any
+  // maintenance work.
+  // This could be done as part of QuotaClient::AbortAllOperations.
   StopIdleMaintenance();
 
-  mShutdownStartedAt.init(TimeStamp::NowLoRes());
-  mShutdownStarted = true;
-
-  const auto& allClientTypes = AllClientTypes();
-
-  bool needsToWait = false;
-  for (Client::Type type : allClientTypes) {
-    needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
-  }
-  needsToWait |= static_cast<bool>(gNormalOriginOps);
+  const bool needsToWait =
+      initiateShutdownWorkThreads() | static_cast<bool>(gNormalOriginOps);
 
   // If any clients cannot shutdown immediately, spin the event loop while we
-  // wait on all the threads to close. Our timer may fire during that loop.
+  // wait on all the threads to close.
   if (needsToWait) {
-    MOZ_ALWAYS_SUCCEEDS(
-        (*mShutdownTimer)
-            ->InitWithNamedFuncCallback(
-                [](nsITimer* aTimer, void* aClosure) {
-                  auto* const quotaManager =
-                      static_cast<QuotaManager*>(aClosure);
-
-                  for (Client::Type type : quotaManager->AllClientTypes()) {
-                    // XXX This is a workaround to unblock shutdown, which ought
-                    // to be removed by Bug 1682326.
-                    if (type == Client::IDB) {
-                      (*quotaManager->mClients)[type]->AbortAllOperations();
-                    }
-
-                    (*quotaManager->mClients)[type]->ForceKillActors();
-                  }
-
-                  MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
-                      [](nsITimer* aTimer, void* aClosure) {
-                        auto* const quotaManager =
-                            static_cast<QuotaManager*>(aClosure);
-
-                        nsCString annotation;
-
-                        {
-                          for (Client::Type type :
-                               quotaManager->AllClientTypes()) {
-                            auto& quotaClient =
-                                *(*quotaManager->mClients)[type];
-
-                            if (!quotaClient.IsShutdownCompleted()) {
-                              annotation.AppendPrintf(
-                                  "%s: %s\nIntermediate steps:\n%s\n\n",
-                                  Client::TypeToText(type).get(),
-                                  quotaClient.GetShutdownStatus().get(),
-                                  quotaManager->mShutdownSteps[type].get());
-                            }
-                          }
-
-                          if (gNormalOriginOps) {
-                            MutexAutoLock lock(quotaManager->mQuotaMutex);
-
-                            annotation.AppendPrintf(
-                                "QM: %zu normal origin ops pending\n",
-                                gNormalOriginOps->Length());
-#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
-                            for (const auto& op : *gNormalOriginOps) {
-                              nsCString name;
-                              op->GetName(name);
-                              annotation.AppendPrintf("Op: %s pending\n",
-                                                      name.get());
-                            }
-#endif
-                            annotation.AppendPrintf(
-                                "Intermediate steps:\n%s\n",
-                                quotaManager->mQuotaManagerShutdownSteps.get());
-                          }
-                        }
-
-                        // We expect that at least one quota client didn't
-                        // complete its shutdown.
-                        MOZ_DIAGNOSTIC_ASSERT(!annotation.IsEmpty());
-
-                        CrashReporter::AnnotateCrashReport(
-                            CrashReporter::Annotation::
-                                QuotaManagerShutdownTimeout,
-                            annotation);
-
-                        MOZ_CRASH("Quota manager shutdown timed out");
-                      },
-                      aClosure, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS,
-                      nsITimer::TYPE_ONE_SHOT,
-                      "quota::QuotaManager::ForceCrashTimer"));
-                },
-                this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
-                "quota::QuotaManager::ForceKillTimer"));
+    startKillActorsTimer();
 
     MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
-        "QuotaManager::Shutdown"_ns, [this, &allClientTypes] {
-          return !gNormalOriginOps &&
-                 std::all_of(
-                     allClientTypes.cbegin(), allClientTypes.cend(),
-                     [&self = *this](const auto type) {
-                       return (*self.mClients)[type]->IsShutdownCompleted();
-                     });
+        "QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
+          return !gNormalOriginOps && isAllClientsShutdownComplete();
         }));
-  }
-
-  for (Client::Type type : allClientTypes) {
-    (*mClients)[type]->FinalizeShutdownWorkThreads();
-  }
-
-  // Cancel the timer regardless of whether it actually fired.
-  QM_WARNONLY_TRY(QM_TO_RESULT((*mShutdownTimer)->Cancel()));
-
-  // NB: It's very important that runnable is destroyed on this thread
-  // (i.e. after we join the IO thread) because we can't release the
-  // QuotaManager on the IO thread. This should probably use
-  // NewNonOwningRunnableMethod ...
-  RefPtr<Runnable> runnable =
-      NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
-                        &QuotaManager::ShutdownStorage);
-  MOZ_ASSERT(runnable);
-
-  // Give clients a chance to cleanup IO thread only objects.
-  QM_WARNONLY_TRY(
-      QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
-
-  // Make sure to join with our IO thread.
-  QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
-
-  for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
-    lock->Invalidate();
-  }
+
+    stopKillActorsTimer();
+  }
+
+  shutdownAndJoinWorkThreads();
+
+  shutdownAndJoinIOThread();
+
+  invalidatePendingDirectoryLocks();
+
+  stopCrashBrowserTimer();
 }
 
 void QuotaManager::InitQuotaForOrigin(
     const FullOriginMetadata& aFullOriginMetadata,
     const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
   AssertIsOnIOThread();
   MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
 
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -376,16 +376,19 @@ class QuotaManager final : public Backgr
   static void MaybeRecordQuotaClientShutdownStep(
       const Client::Type aClientType, const nsACString& aStepDescription);
 
   // Record a quota client shutdown step, if shutting down.
   // Checks if the QuotaManager singleton is alive.
   static void SafeMaybeRecordQuotaClientShutdownStep(
       Client::Type aClientType, const nsACString& aStepDescription);
 
+  // Record a quota manager shutdown step, use only if shutdown is active.
+  void RecordQuotaManagerShutdownStep(const nsACString& aStepDescription);
+
   // Record a quota manager shutdown step, if shutting down.
   void MaybeRecordQuotaManagerShutdownStep(const nsACString& aStepDescription);
 
   template <typename F>
   void MaybeRecordQuotaManagerShutdownStepWith(F&& aFunc);
 
   static void GetStorageId(PersistenceType aPersistenceType,
                            const nsACString& aOrigin, Client::Type aClientType,
@@ -582,19 +585,16 @@ class QuotaManager final : public Backgr
   static OriginInfosFlatTraversable CollectLRUOriginInfosUntil(
       Collect&& aCollect, Pred&& aPred);
 
   // Thread on which IO is performed.
   LazyInitializedOnceNotNull<const nsCOMPtr<nsIThread>> mIOThread;
 
<