Repository: GoogleChrome/lighthouse
Pull request #12400 (2021-04-23 21:46:06)

core(fr): extract storage and service worker driver methods
**Summary**
Splits out the storage and service worker bits from #12366 along with a couple of the super low value driver abstractions. Again the goal here is that FR-shared logic is refactored to accept a *session* instead of a driver. Ultimately everything left in `driver.js` and `gather-runner.js` should be specific to legacy flow with everything shared living elsewhere.

**Related Issues/PRs**
ref #11313 #12366


patrickhulce: > wdyt of async/await'ing the FR/shared code?

Which methods did you have in mind? I agree the promise logic in service workers is hard to follow, but they're mostly event based which demand that structure (could maybe remove a `.catch(reject)` or two in exchange for a try/catch if that's what you mean?)

Storage methods I believe are all async'd up already.

connorjclark: oh, the only offender was `assertNoSameOriginServiceWorkerClients` which is in gather-runner. nvm.

Patch:
diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js
index cadb3e820..facaf43ad 100644
--- a/lighthouse-core/gather/driver.js
+++ b/lighthouse-core/gather/driver.js
@@ -12,8 +12,6 @@ const LHElement = require('../lib/lh-element.js');
 const LHError = require('../lib/lh-error.js');
 const NetworkRequest = require('../lib/network-request.js');
 const EventEmitter = require('events').EventEmitter;
-const i18n = require('../lib/i18n/i18n.js');
-const URL = require('../lib/url-shim.js');
 const constants = require('../config/constants.js');
 
 const log = require('lighthouse-logger');
@@ -28,24 +26,6 @@ const Connection = require('./connections/connection.js');
 const NetworkMonitor = require('./driver/network-monitor.js');
 const {getBrowserVersion} = require('./driver/environment.js');
 
-const UIStrings = {
-  /**
-   * @description A warning that previously-saved data may have affected the measured performance and instructions on how to avoid the problem. "locations" will be a list of possible types of data storage locations, e.g. "IndexedDB",  "Local Storage", or "Web SQL".
-   * @example {IndexedDB, Local Storage} locations
-   */
-  warningData: `{locationCount, plural,
-    =1 {There may be stored data affecting loading performance in this location: {locations}. ` +
-      `Audit this page in an incognito window to prevent those resources ` +
-      `from affecting your scores.}
-    other {There may be stored data affecting loading ` +
-      `performance in these locations: {locations}. ` +
-      `Audit this page in an incognito window to prevent those resources ` +
-      `from affecting your scores.}
-  }`,
-};
-
-const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
-
 // Controls how long to wait after FCP before continuing
 const DEFAULT_PAUSE_AFTER_FCP = 0;
 // Controls how long to wait after onLoad before continuing
@@ -110,7 +90,6 @@ class Driver {
     this.on('Target.attachedToTarget', event => {
       this._handleTargetAttached(event).catch(this._handleEventError);
     });
-    this.on('Debugger.paused', () => this.sendCommand('Debugger.resume'));
 
     connection.on('protocolevent', this._handleProtocolEvent.bind(this));
 
@@ -397,93 +376,6 @@ class Driver {
     return !!this._domainEnabledCounts.get(domain);
   }
 
-  /**
-   * @return {Promise<LH.Crdp.ServiceWorker.WorkerVersionUpdatedEvent>}
-   */
-  getServiceWorkerVersions() {
-    return new Promise((resolve, reject) => {
-      /**
-       * @param {LH.Crdp.ServiceWorker.WorkerVersionUpdatedEvent} data
-       */
-      const versionUpdatedListener = data => {
-        // find a service worker with runningStatus that looks like active
-        // on slow connections the serviceworker might still be installing
-        const activateCandidates = data.versions.filter(sw => {
-          return sw.status !== 'redundant';
-        });
-        const hasActiveServiceWorker = activateCandidates.find(sw => {
-          return sw.status === 'activated';
-        });
-
-        if (!activateCandidates.length || hasActiveServiceWorker) {
-          this.off('ServiceWorker.workerVersionUpdated', versionUpdatedListener);
-          this.sendCommand('ServiceWorker.disable')
-            .then(_ => resolve(data), reject);
-        }
-      };
-
-      this.on('ServiceWorker.workerVersionUpdated', versionUpdatedListener);
-
-      this.sendCommand('ServiceWorker.enable').catch(reject);
-    });
-  }
-
-  /**
-   * @return {Promise<LH.Crdp.ServiceWorker.WorkerRegistrationUpdatedEvent>}
-   */
-  getServiceWorkerRegistrations() {
-    return new Promise((resolve, reject) => {
-      this.once('ServiceWorker.workerRegistrationUpdated', data => {
-        this.sendCommand('ServiceWorker.disable')
-          .then(_ => resolve(data), reject);
-      });
-
-      this.sendCommand('ServiceWorker.enable').catch(reject);
-    });
-  }
-
-  /**
-   * Rejects if any open tabs would share a service worker with the target URL.
-   * This includes the target tab, so navigation to something like about:blank
-   * should be done before calling.
-   * @param {string} pageUrl
-   * @return {Promise<void>}
-   */
-  assertNoSameOriginServiceWorkerClients(pageUrl) {
-    /** @type {Array<LH.Crdp.ServiceWorker.ServiceWorkerRegistration>} */
-    let registrations;
-    /** @type {Array<LH.Crdp.ServiceWorker.ServiceWorkerVersion>} */
-    let versions;
-
-    return this.getServiceWorkerRegistrations().then(data => {
-      registrations = data.registrations;
-    }).then(_ => this.getServiceWorkerVersions()).then(data => {
-      versions = data.versions;
-    }).then(_ => {
-      const origin = new URL(pageUrl).origin;
-
-      registrations
-        .filter(reg => {
-          const swOrigin = new URL(reg.scopeURL).origin;
-
-          return origin === swOrigin;
-        })
-        .forEach(reg => {
-          versions.forEach(ver => {
-            // Ignore workers unaffiliated with this registration
-            if (ver.registrationId !== reg.registrationId) {
-              return;
-            }
-
-            // Throw if service worker for this origin has active controlledClients.
-            if (ver.controlledClients && ver.controlledClients.length > 0) {
-              throw new Error('You probably have multiple tabs open to the same origin.');
-            }
-          });
-        });
-    });
-  }
-
   /**
    * Navigate to the given URL. Direct use of this method isn't advised: if
    * the current page is already at the given URL, navigation will not occur and
@@ -731,119 +623,6 @@ class Driver {
     return this._devtoolsLog.messages;
   }
 
-  /**
-   * @return {Promise<void>}
-   */
-  enableRuntimeEvents() {
-    return this.sendCommand('Runtime.enable');
-  }
-
-  /**
-   * Enables `Debugger` domain to receive async stacktrace information on network request initiators.
-   * This is critical for tracing certain performance simulation situations.
-   *
-   * @return {Promise<void>}
-   */
-  async enableAsyncStacks() {
-    await this.sendCommand('Debugger.enable');
-    await this.sendCommand('Debugger.setSkipAllPauses', {skip: true});
-    await this.sendCommand('Debugger.setAsyncCallStackDepth', {maxDepth: 8});
-  }
-
-  /**
-   * Clear the network cache on disk and in memory.
-   * @return {Promise<void>}
-   */
-  async cleanBrowserCaches() {
-    const status = {msg: 'Cleaning browser cache', id: 'lh:driver:cleanBrowserCaches'};
-    log.time(status);
-
-    // Wipe entire disk cache
-    await this.sendCommand('Network.clearBrowserCache');
-    // Toggle 'Disable Cache' to evict the memory cache
-    await this.sendCommand('Network.setCacheDisabled', {cacheDisabled: true});
-    await this.sendCommand('Network.setCacheDisabled', {cacheDisabled: false});
-
-    log.timeEnd(status);
-  }
-
-  /**
-   * @param {LH.Crdp.Network.Headers|null} headers key/value pairs of HTTP Headers.
-   * @return {Promise<void>}
-   */
-  async setExtraHTTPHeaders(headers) {
-    if (!headers) {
-      return;
-    }
-
-    return this.sendCommand('Network.setExtraHTTPHeaders', {headers});
-  }
-
-  /**
-   * @param {string} url
-   * @return {Promise<void>}
-   */
-  async clearDataForOrigin(url) {
-    const origin = new URL(url).origin;
-
-    // Clear some types of storage.
-    // Cookies are not cleared, so the user isn't logged out.
-    // indexeddb, websql, and localstorage are not cleared to prevent loss of potentially important data.
-    //   https://chromedevtools.github.io/debugger-protocol-viewer/tot/Storage/#type-StorageType
-    const typesToClear = [
-      'appcache',
-      // 'cookies',
-      'file_systems',
-      'shader_cache',
-      'service_workers',
-      'cache_storage',
-    ].join(',');
-
-    // `Storage.clearDataForOrigin` is one of our PROTOCOL_TIMEOUT culprits and this command is also
-    // run in the context of PAGE_HUNG to cleanup. We'll keep the timeout low and just warn if it fails.
-    this.setNextProtocolTimeout(5000);
-
-    try {
-      await this.sendCommand('Storage.clearDataForOrigin', {
-        origin: origin,
-        storageTypes: typesToClear,
-      });
-    } catch (err) {
-      if (/** @type {LH.LighthouseError} */(err).code === 'PROTOCOL_TIMEOUT') {
-        log.warn('Driver', 'clearDataForOrigin timed out');
-      } else {
-        throw err;
-      }
-    }
-  }
-
-  /**
-   * @param {string} url
-   * @return {Promise<LH.IcuMessage | undefined>}
-   */
-  async getImportantStorageWarning(url) {
-    const usageData = await this.sendCommand('Storage.getUsageAndQuota', {
-      origin: url,
-    });
-    /** @type {Record<string, string>} */
-    const storageTypeNames = {
-      local_storage: 'Local Storage',
-      indexeddb: 'IndexedDB',
-      websql: 'Web SQL',
-    };
-    const locations = usageData.usageBreakdown
-      .filter(usage => usage.usage)
-      .map(usage => storageTypeNames[usage.storageType] || '')
-      .filter(Boolean);
-    if (locations.length) {
-      // TODO(#11495): Use Intl.ListFormat with Node 12
-      return str_(
-        UIStrings.warningData,
-        {locations: locations.join(', '), locationCount: locations.length}
-      );
-    }
-  }
-
   /**
    * Use a RequestIdleCallback shim for tests run with simulated throttling, so that the deadline can be used without
    * a penalty
@@ -859,20 +638,6 @@ class Driver {
     }
   }
 
-  /**
-   * @param {Array<string>} urls URL patterns to block. Wildcards ('*') are allowed.
-   * @return {Promise<void>}
-   */
-  blockUrlPatterns(urls) {
-    return this.sendCommand('Network.setBlockedURLs', {urls})
-      .catch(err => {
-        // TODO(COMPAT): remove this handler once m59 hits stable
-        if (!/wasn't found/.test(err.message)) {
-          throw err;
-        }
-      });
-  }
-
   /**
    * Dismiss JavaScript dialogs (alert, confirm, prompt), providing a
    * generic promptText in case the dialog is a prompt.
@@ -893,4 +658,3 @@ class Driver {
 }
 
 module.exports = Driver;
-module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/gather/driver/execution-context.js b/lighthouse-core/gather/driver/execution-context.js
index 05e127373..66af4b662 100644
--- a/lighthouse-core/gather/driver/execution-context.js
+++ b/lighthouse-core/gather/driver/execution-context.js
@@ -19,6 +19,7 @@ class ExecutionContext {
 
     // We use isolated execution contexts for `evaluateAsync` that can be destroyed through navigation
     // and other page actions. Cleanup our relevant bookkeeping as we see those events.
+    // Domains are enabled when a dedicated execution context is requested.
     session.on('Page.frameNavigated', () => this.clearContextId());
     session.on('Runtime.executionContextDestroyed', event => {
       if (event.executionContextId === this._executionContextId) {
@@ -51,6 +52,9 @@ class ExecutionContext {
   async _getOrCreateIsolatedContextId() {
     if (typeof this._executionContextId === 'number') return this._executionContextId;
 
+    await this._session.sendCommand('Page.enable');
+    await this._session.sendCommand('Runtime.enable');
+
     const resourceTreeResponse = await this._session.sendCommand('Page.getResourceTree');
     const mainFrameId = resourceTreeResponse.frameTree.frame.id;
 
diff --git a/lighthouse-core/gather/driver/service-workers.js b/lighthouse-core/gather/driver/service-workers.js
new file mode 100644
index 000000000..5de97fd32
--- /dev/null
+++ b/lighthouse-core/gather/driver/service-workers.js
@@ -0,0 +1,53 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+/**
+ * @param {LH.Gatherer.FRProtocolSession} session
+ * @return {Promise<LH.Crdp.ServiceWorker.WorkerVersionUpdatedEvent>}
+ */
+function getServiceWorkerVersions(session) {
+  return new Promise((resolve, reject) => {
+    /**
+     * @param {LH.Crdp.ServiceWorker.WorkerVersionUpdatedEvent} data
+     */
+    const versionUpdatedListener = data => {
+      // find a service worker with runningStatus that looks like active
+      // on slow connections the serviceworker might still be installing
+      const activateCandidates = data.versions.filter(sw => {
+        return sw.status !== 'redundant';
+      });
+
+      const hasActiveServiceWorker = activateCandidates.find(sw => {
+        return sw.status === 'activated';
+      });
+
+      if (!activateCandidates.length || hasActiveServiceWorker) {
+        session.off('ServiceWorker.workerVersionUpdated', versionUpdatedListener);
+        session.sendCommand('ServiceWorker.disable').then(_ => resolve(data), reject);
+      }
+    };
+
+    session.on('ServiceWorker.workerVersionUpdated', versionUpdatedListener);
+
+    session.sendCommand('ServiceWorker.enable').catch(reject);
+  });
+}
+
+/**
+ * @param {LH.Gatherer.FRProtocolSession} session
+ * @return {Promise<LH.Crdp.ServiceWorker.WorkerRegistrationUpdatedEvent>}
+ */
+function getServiceWorkerRegistrations(session) {
+  return new Promise((resolve, reject) => {
+    session.once('ServiceWorker.workerRegistrationUpdated', data => {
+      session.sendCommand('ServiceWorker.disable').then(_ => resolve(data), reject);
+    });
+    session.sendCommand('ServiceWorker.enable').catch(reject);
+  });
+}
+
+module.exports = {getServiceWorkerVersions, getServiceWorkerRegistrations};
diff --git a/lighthouse-core/gather/driver/storage.js b/lighthouse-core/gather/driver/storage.js
new file mode 100644
index 000000000..4e47c856f
--- /dev/null
+++ b/lighthouse-core/gather/driver/storage.js
@@ -0,0 +1,121 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const log = require('lighthouse-logger');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /**
+   * @description A warning that previously-saved data may have affected the measured performance and instructions on how to avoid the problem. "locations" will be a list of possible types of data storage locations, e.g. "IndexedDB",  "Local Storage", or "Web SQL".
+   * @example {IndexedDB, Local Storage} locations
+   */
+  warningData: `{locationCount, plural,
+    =1 {There may be stored data affecting loading performance in this location: {locations}. ` +
+      `Audit this page in an incognito window to prevent those resources ` +
+      `from affecting your scores.}
+    other {There may be stored data affecting loading ` +
+      `performance in these locations: {locations}. ` +
+      `Audit this page in an incognito window to prevent those resources ` +
+      `from affecting your scores.}
+  }`,
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
+
+
+/**
+ * @param {LH.Gatherer.FRProtocolSession} session
+ * @param {string} url
+ * @return {Promise<void>}
+ */
+async function clearDataForOrigin(session, url) {
+  const origin = new URL(url).origin;
+
+  // Clear some types of storage.
+  // Cookies are not cleared, so the user isn't logged out.
+  // indexeddb, websql, and localstorage are not cleared to prevent loss of potentially important data.
+  //   https://chromedevtools.github.io/debugger-protocol-viewer/tot/Storage/#type-StorageType
+  const typesToClear = [
+    'appcache',
+    // 'cookies',
+    'file_systems',
+    'shader_cache',
+    'service_workers',
+    'cache_storage',
+  ].join(',');
+
+  // `Storage.clearDataForOrigin` is one of our PROTOCOL_TIMEOUT culprits and this command is also
+  // run in the context of PAGE_HUNG to cleanup. We'll keep the timeout low and just warn if it fails.
+  session.setNextProtocolTimeout(5000);
+
+  try {
+    await session.sendCommand('Storage.clearDataForOrigin', {
+      origin: origin,
+      storageTypes: typesToClear,
+    });
+  } catch (err) {
+    if (/** @type {LH.LighthouseError} */ (err).code === 'PROTOCOL_TIMEOUT') {
+      log.warn('Driver', 'clearDataForOrigin timed out');
+    } else {
+      throw err;
+    }
+  }
+}
+
+/**
+ * @param {LH.Gatherer.FRProtocolSession} session
+ * @param {string} url
+ * @return {Promise<LH.IcuMessage | undefined>}
+ */
+async function getImportantStorageWarning(session, url) {
+  const usageData = await session.sendCommand('Storage.getUsageAndQuota', {
+    origin: url,
+  });
+  /** @type {Record<string, string>} */
+  const storageTypeNames = {
+    local_storage: 'Local Storage',
+    indexeddb: 'IndexedDB',
+    websql: 'Web SQL',
+  };
+  const locations = usageData.usageBreakdown
+    .filter(usage => usage.usage)
+    .map(usage => storageTypeNames[usage.storageType] || '')
+    .filter(Boolean);
+  if (locations.length) {
+    // TODO(#11495): Use Intl.ListFormat with Node 12
+    return str_(UIStrings.warningData, {
+      locations: locations.join(', '),
+      locationCount: locations.length,
+    });
+  }
+}
+
+
+/**
+ * Clear the network cache on disk and in memory.
+ * @param {LH.Gatherer.FRProtocolSession} session
+ * @return {Promise<void>}
+ */
+async function cleanBrowserCaches(session) {
+  const status = {msg: 'Cleaning browser cache', id: 'lh:storage:cleanBrowserCaches'};
+  log.time(status);
+
+  // Wipe entire disk cache
+  await session.sendCommand('Network.clearBrowserCache');
+  // Toggle 'Disable Cache' to evict the memory cache
+  await session.sendCommand('Network.setCacheDisabled', {cacheDisabled: true});
+  await session.sendCommand('Network.setCacheDisabled', {cacheDisabled: false});
+
+  log.timeEnd(status);
+}
+
+module.exports = {
+  clearDataForOrigin,
+  cleanBrowserCaches,
+  getImportantStorageWarning,
+  UIStrings,
+};
diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js
index 4e084a3db..84cfca127 100644
--- a/lighthouse-core/gather/gather-runner.js
+++ b/lighthouse-core/gather/gather-runner.js
@@ -14,6 +14,8 @@ const constants = require('../config/constants.js');
 const i18n = require('../lib/i18n/i18n.js');
 const URL = require('../lib/url-shim.js');
 const {getBenchmarkIndex} = require('./driver/environment.js');
+const storage = require('./driver/storage.js');
+const serviceWorkers = require('./driver/service-workers.js');
 const WebAppManifest = require('./gatherers/web-app-manifest.js');
 const InstallabilityErrors = require('./gatherers/installability-errors.js');
 const NetworkUserAgent = require('./gatherers/network-user-agent.js');
@@ -119,6 +121,53 @@ class GatherRunner {
     return {};
   }
 
+  /**
+   * Rejects if any open tabs would share a service worker with the target URL.
+   * This includes the target tab, so navigation to something like about:blank
+   * should be done before calling.
+   * @param {LH.Gatherer.FRProtocolSession} session
+   * @param {string} pageUrl
+   * @return {Promise<void>}
+   */
+  static assertNoSameOriginServiceWorkerClients(session, pageUrl) {
+    /** @type {Array<LH.Crdp.ServiceWorker.ServiceWorkerRegistration>} */
+    let registrations;
+    /** @type {Array<LH.Crdp.ServiceWorker.ServiceWorkerVersion>} */
+    let versions;
+
+    return serviceWorkers.getServiceWorkerRegistrations(session)
+      .then(data => {
+        registrations = data.registrations;
+      })
+      .then(_ => serviceWorkers.getServiceWorkerVersions(session))
+      .then(data => {
+        versions = data.versions;
+      })
+      .then(_ => {
+        const origin = new URL(pageUrl).origin;
+
+        registrations
+          .filter(reg => {
+            const swOrigin = new URL(reg.scopeURL).origin;
+
+            return origin === swOrigin;
+          })
+          .forEach(reg => {
+            versions.forEach(ver => {
+              // Ignore workers unaffiliated with this registration
+              if (ver.registrationId !== reg.registrationId) {
+                return;
+              }
+
+              // Throw if service worker for this origin has active controlledClients.
+              if (ver.controlledClients && ver.controlledClients.length > 0) {
+                throw new Error('You probably have multiple tabs open to the same origin.');
+              }
+            });
+          });
+      });
+  }
+
   /**
    * @param {Driver} driver
    * @param {{requestedUrl: string, settings: LH.Config.Settings}} options
@@ -129,19 +178,38 @@ class GatherRunner {
     const status = {msg: 'Initializing…', id: 'lh:gather:setupDriver'};
     log.time(status);
     const resetStorage = !options.settings.disableStorageReset;
-    await driver.assertNoSameOriginServiceWorkerClients(options.requestedUrl);
+    const session = driver.defaultSession;
+
+    // Assert no service workers are still installed, so we test that they would actually be installed for a new user.
+    // TODO(FR-COMPAT): re-evaluate the necessity of this check
+    await GatherRunner.assertNoSameOriginServiceWorkerClients(session, options.requestedUrl);
+
+    // Emulate our target device screen and user agent.
     await emulation.emulate(driver.defaultSession, options.settings);
-    await driver.enableRuntimeEvents();
-    await driver.enableAsyncStacks();
+
+    // Enable `Debugger` domain to receive async stacktrace information on network request initiators.
+    // This is critical for tracing certain performance simulation situations.
+    session.on('Debugger.paused', () => session.sendCommand('Debugger.resume'));
+    await session.sendCommand('Debugger.enable');
+    await session.sendCommand('Debugger.setSkipAllPauses', {skip: true});
+    await session.sendCommand('Debugger.setAsyncCallStackDepth', {maxDepth: 8});
+
+    // Inject our snippet to cache important web platform APIs before they're (possibly) ponyfilled by the page.
     await driver.executionContext.cacheNativesOnNewDocument();
+
+    // Automatically handle any JavaScript dialogs to prevent a hung renderer.
     await driver.dismissJavaScriptDialogs();
+
+    // Wrap requestIdleCallback so pages under simulation receive the correct rIC deadlines.
     await driver.registerRequestIdleCallbackWrap(options.settings);
+
+    // Reset the storage and warn if there appears to be other important data.
     if (resetStorage) {
-      const warning = await driver.getImportantStorageWarning(options.requestedUrl);
+      const warning = await storage.getImportantStorageWarning(session, options.requestedUrl);
       if (warning) {
         LighthouseRunWarnings.push(warning);
       }
-      await driver.clearDataForOrigin(options.requestedUrl);
+      await storage.clearDataForOrigin(session, options.requestedUrl);
     }
     log.timeEnd(status);
   }
@@ -158,8 +226,9 @@ class GatherRunner {
     log.time(status);
     try {
       // If storage was cleared for the run, clear at the end so Lighthouse specifics aren't cached.
+      const session = driver.defaultSession;
       const resetStorage = !options.settings.disableStorageReset;
-      if (resetStorage) await driver.clearDataForOrigin(options.requestedUrl);
+      if (resetStorage) await storage.clearDataForOrigin(session, options.requestedUrl);
 
       // Disable fetcher, in case a gatherer enabled it.
       // This cleanup should be removed once the only usage of
@@ -354,10 +423,12 @@ class GatherRunner {
       .concat(passContext.settings.blockedUrlPatterns || []);
 
     // Set request blocking before any network activity
-    // No "clearing" is done at the end of the pass since blockUrlPatterns([]) will unset all if
+    // No "clearing" is done at the end of the pass since Network.setBlockedURLs([]) will unset all if
     // neccessary at the beginning of the next pass.
-    await passContext.driver.blockUrlPatterns(blockedUrls);
-    await passContext.driver.setExtraHTTPHeaders(passContext.settings.extraHeaders);
+    await session.sendCommand('Network.setBlockedURLs', {urls: blockedUrls});
+
+    const headers = passContext.settings.extraHeaders;
+    if (headers) await session.sendCommand('Network.setExtraHTTPHeaders', {headers});
 
     log.timeEnd(status);
   }
@@ -744,7 +815,7 @@ class GatherRunner {
     await GatherRunner.loadBlank(driver, passConfig.blankPage);
     await GatherRunner.setupPassNetwork(passContext);
     if (GatherRunner.shouldClearCaches(passContext)) {
-      await driver.cleanBrowserCaches(); // Clear disk & memory cache if it's a perf run
+      await storage.cleanBrowserCaches(driver.defaultSession); // Clear disk & memory cache if it's a perf run
     }
     await GatherRunner.beforePass(passContext, gathererResults);
 
diff --git a/lighthouse-core/gather/gatherers/service-worker.js b/lighthouse-core/gather/gatherers/service-worker.js
index b26ee103a..8d636b341 100644
--- a/lighthouse-core/gather/gatherers/service-worker.js
+++ b/lighthouse-core/gather/gatherers/service-worker.js
@@ -6,6 +6,7 @@
 'use strict';
 
 const Gatherer = require('./gatherer.js');
+const serviceWorkers = require('../driver/service-workers.js');
 
 class ServiceWorker extends Gatherer {
   /**
@@ -13,8 +14,9 @@ class ServiceWorker extends Gatherer {
    * @return {Promise<LH.Artifacts['ServiceWorker']>}
    */
   async beforePass(passContext) {
-    const {versions} = await passContext.driver.getServiceWorkerVersions();
-    const {registrations} = await passContext.driver.getServiceWorkerRegistrations();
+    const session = passContext.driver.defaultSession;
+    const {versions} = await serviceWorkers.getServiceWorkerVersions(session);
+    const {registrations} = await serviceWorkers.getServiceWorkerRegistrations(session);
 
     return {
       versions,
diff --git a/lighthouse-core/lib/i18n/locales/ar-XB.json b/lighthouse-core/lib/i18n/locales/ar-XB.json
index 902d4cd9c..931dc7abb 100644
--- a/lighthouse-core/lib/i18n/locales/ar-XB.json
+++ b/lighthouse-core/lib/i18n/locales/ar-XB.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "‏‮Mobile‬‏ ‏‮Friendly‬‏"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮this‬‏ ‏‮location‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}zero{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮these‬‏ ‏‮locations‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}two{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮these‬‏ ‏‮locations‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}few{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮these‬‏ ‏‮locations‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}many{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮these‬‏ ‏‮locations‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}other{‏‮There‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮stored‬‏ ‏‮data‬‏ ‏‮affecting‬‏ ‏‮loading‬‏ ‏‮performance‬‏ ‏‮in‬‏ ‏‮these‬‏ ‏‮locations‬‏: {locations}. ‏‮Audit‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮in‬‏ ‏‮an‬‏ ‏‮incognito‬‏ ‏‮window‬‏ ‏‮to‬‏ ‏‮prevent‬‏ ‏‮those‬‏ ‏‮resources‬‏ ‏‮from‬‏ ‏‮affecting‬‏ ‏‮your‬‏ ‏‮scores‬‏.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "‏‮The‬‏ ‏‮page‬‏ ‏‮may‬‏ ‏‮not‬‏ ‏‮be‬‏ ‏‮loading‬‏ ‏‮as‬‏ ‏‮expected‬‏ ‏‮because‬‏ ‏‮your‬‏ ‏‮test‬‏ ‏‮URL‬‏ ({requested}) ‏‮was‬‏ ‏‮redirected‬‏ ‏‮to‬‏ {final}. ‏‮Try‬‏ ‏‮testing‬‏ ‏‮the‬‏ ‏‮second‬‏ ‏‮URL‬‏ ‏‮directly‬‏."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ar.json b/lighthouse-core/lib/i18n/locales/ar.json
index 3cde0fa21..7e634a2ea 100644
--- a/lighthouse-core/lib/i18n/locales/ar.json
+++ b/lighthouse-core/lib/i18n/locales/ar.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "متوافق مع الجوّال"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في مكان تخزين البيانات التالي: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}zero{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في أماكن تخزين البيانات التالية: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}two{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في مكانَي تخزين البيانات التاليَين: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}few{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في أماكن تخزين البيانات التالية: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}many{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في أماكن تخزين البيانات التالية: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}other{قد تكون هناك بيانات مخزَّنة تؤثر على أداء التحميل في أماكن تخزين البيانات التالية: {locations} يُرجى إجراء تدقيق لهذه الصفحة في نافذة التصفح المتخفي لمنع هذه المصادر من التأثير على نتيجتك.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "قد لا يتم تحميل الصفحة كما هو متوقَع لأن عنوان URL للاختبار ({requested}) تمت إعادة توجيهه إلى {final}. يُرجى محاولة اختبار عنوان URL الثاني مباشرةً."
   },
diff --git a/lighthouse-core/lib/i18n/locales/bg.json b/lighthouse-core/lib/i18n/locales/bg.json
index 2c459df59..a7d413153 100644
--- a/lighthouse-core/lib/i18n/locales/bg.json
+++ b/lighthouse-core/lib/i18n/locales/bg.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Удобство за мобилни устройства"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Възможно е в следното място да се съхраняват данни, които засягат ефективността при зареждане: {locations}. Проверете тази страница в прозорец в режим „инкогнито“, така че тези ресурси да не влияят върху резултатите.}other{Възможно е в следните места да се съхраняват данни, които засягат ефективността при зареждане: {locations}. Проверете тази страница в прозорец в режим „инкогнито“, така че тези ресурси да не влияят върху резултатите.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Възможно е страницата да не се зарежда нормално, тъй като тестовият ви URL адрес ({requested}) бе пренасочен към {final}. Тествайте директно втория URL адрес."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ca.json b/lighthouse-core/lib/i18n/locales/ca.json
index 903da24b6..817e16afe 100644
--- a/lighthouse-core/lib/i18n/locales/ca.json
+++ b/lighthouse-core/lib/i18n/locales/ca.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Adaptació per a mòbils"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Pot ser que hi hagi dades emmagatzemades que afectin el rendiment de càrrega en aquesta ubicació: {locations}. Audita aquesta pàgina en una finestra d'incògnit per impedir que aquests recursos afectin els teus resultats.}other{Pot ser que hi hagi dades emmagatzemades que afectin el rendiment de càrrega en aquestes ubicacions: {locations}. Audita aquesta pàgina en una finestra d'incògnit per impedir que aquests recursos afectin els teus resultats.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "És possible que la pàgina no s'estigui carregant com s'esperava perquè s'ha redirigit el teu URL de prova ({requested}) a {final}. Intenta provar el segon URL directament."
   },
diff --git a/lighthouse-core/lib/i18n/locales/cs.json b/lighthouse-core/lib/i18n/locales/cs.json
index 1cda392c1..4a2bd0b09 100644
--- a/lighthouse-core/lib/i18n/locales/cs.json
+++ b/lighthouse-core/lib/i18n/locales/cs.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Optimalizováno pro mobily"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Rychlost načítání může být ovlivněna daty uloženými v následujícím umístění: {locations}. Spusťte audit této stránky v anonymním okně, aby tyto zdroje na skóre neměly vliv.}few{Rychlost načítání může být ovlivněna daty uloženými v následujících umístěních: {locations}. Spusťte audit této stránky v anonymním okně, aby tyto zdroje na skóre neměly vliv.}many{Rychlost načítání může být ovlivněna daty uloženými v následujících umístěních: {locations}. Spusťte audit této stránky v anonymním okně, aby tyto zdroje na skóre neměly vliv.}other{Rychlost načítání může být ovlivněna daty uloženými v následujících umístěních: {locations}. Spusťte audit této stránky v anonymním okně, aby tyto zdroje na skóre neměly vliv.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Stránka se možná nenačítá očekávaným způsobem, protože testovací adresa URL ({requested}) byla přesměrována na adresu {final}. Zkuste přímo otestovat druhou URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/da.json b/lighthouse-core/lib/i18n/locales/da.json
index c6d7ef602..7edd2b593 100644
--- a/lighthouse-core/lib/i18n/locales/da.json
+++ b/lighthouse-core/lib/i18n/locales/da.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobilvenlig"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Der kan være gemte data, som påvirker indlæsningseffektiviteten på følgende placering: {locations}. Gennemgå denne side i et inkognitovindue for at forhindre, at disse ressourcer påvirker dine resultater.}one{Der kan være gemte data, som påvirker indlæsningseffektiviteten på følgende placering: {locations}. Gennemgå denne side i et inkognitovindue for at forhindre, at disse ressourcer påvirker dine resultater.}other{Der kan være gemte data, som påvirker indlæsningseffektiviteten på følgende placeringer: {locations}. Gennemgå denne side i et inkognitovindue for at forhindre, at disse ressourcer påvirker dine resultater.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Siden indlæses muligvis ikke som forventet, da din testwebadresse ({requested}) blev omdirigeret til {final}. Prøv at teste den anden webadresse direkte."
   },
diff --git a/lighthouse-core/lib/i18n/locales/de.json b/lighthouse-core/lib/i18n/locales/de.json
index a846a7bd4..7f7210e5d 100644
--- a/lighthouse-core/lib/i18n/locales/de.json
+++ b/lighthouse-core/lib/i18n/locales/de.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Für Mobilgeräte optimiert"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Gespeicherte Daten haben möglicherweise die Ladeleistung an diesem Ort beeinflusst: {locations}. Testen Sie diese Seite in einem Inkognitofenster, um zu verhindern, dass sich diese Ressourcen auf Ihre Ergebnisse auswirken.}other{Gespeicherte Daten haben möglicherweise die Ladeleistungen an diesen Orten beeinflusst: {locations}. Testen Sie diese Seite in einem Inkognitofenster, um zu verhindern, dass sich diese Ressourcen auf Ihre Ergebnisse auswirken.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Die Seite lädt möglicherweise nicht wie erwartet, weil Ihre Test-URL ({requested}) auf {final} weitergeleitet wurde. Versuchen Sie, die zweite URL direkt zu testen."
   },
diff --git a/lighthouse-core/lib/i18n/locales/el.json b/lighthouse-core/lib/i18n/locales/el.json
index 156be2909..30b03b6ba 100644
--- a/lighthouse-core/lib/i18n/locales/el.json
+++ b/lighthouse-core/lib/i18n/locales/el.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Φιλική προς κινητά"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Ενδέχεται να υπάρχουν αποθηκευμένα δεδομένα που επηρεάζουν την απόδοση φόρτωσης σε αυτήν την τοποθεσία: {locations}. Ελέγξτε αυτήν τη σελίδα σε παράθυρο για ανώνυμη περιήγηση για να εμποδίσετε αυτούς τους πόρους από το να επηρεάσουν τις βαθμολογίες σας.}other{Ενδέχεται να υπάρχουν αποθηκευμένα δεδομένα που επηρεάζουν την απόδοση φόρτωσης σε αυτές τις τοποθεσίες: {locations}. Ελέγξτε αυτήν τη σελίδα σε παράθυρο για ανώνυμη περιήγηση για να εμποδίσετε αυτούς τους πόρους από το να επηρεάσουν τις βαθμολογίες σας.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Η σελίδα μπορεί να μην φορτώνεται με τον αναμενόμενο τρόπο επειδή το URL δοκιμής ({requested}) ανακατευθύνθηκε στο {final}. Προσπαθήστε να δοκιμάσετε απευθείας το δεύτερο URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/en-GB.json b/lighthouse-core/lib/i18n/locales/en-GB.json
index f9989a21f..70986cb7f 100644
--- a/lighthouse-core/lib/i18n/locales/en-GB.json
+++ b/lighthouse-core/lib/i18n/locales/en-GB.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobile friendly"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{There may be stored data affecting loading performance in this location: {locations}. Audit this page in an incognito window to prevent those resources from affecting your scores.}other{There may be stored data affecting loading performance in these locations: {locations}. Audit this page in an incognito window to prevent those resources from affecting your scores.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "The page may not be loading as expected because your test URL ({requested}) was redirected to {final}. Try testing the second URL directly."
   },
diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json
index f6958b62d..0ced62626 100644
--- a/lighthouse-core/lib/i18n/locales/en-US.json
+++ b/lighthouse-core/lib/i18n/locales/en-US.json
@@ -1703,7 +1703,7 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobile Friendly"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
+  "lighthouse-core/gather/driver/storage.js | warningData": {
     "message": "{locationCount, plural,\n    =1 {There may be stored data affecting loading performance in this location: {locations}. Audit this page in an incognito window to prevent those resources from affecting your scores.}\n    other {There may be stored data affecting loading performance in these locations: {locations}. Audit this page in an incognito window to prevent those resources from affecting your scores.}\n  }"
   },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
diff --git a/lighthouse-core/lib/i18n/locales/en-XA.json b/lighthouse-core/lib/i18n/locales/en-XA.json
index 0c9471a6b..fb23e672b 100644
--- a/lighthouse-core/lib/i18n/locales/en-XA.json
+++ b/lighthouse-core/lib/i18n/locales/en-XA.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "[Möбîļé Fŕîéñðļý one two]"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{[Ţĥéŕé måý бé šţöŕéð ðåţå åƒƒéçţîñĝ ļöåðîñĝ þéŕƒöŕmåñçé îñ ţĥîš ļöçåţîöñ: ᐅ{locations}ᐊ. Åûðîţ ţĥîš þåĝé îñ åñ îñçöĝñîţö ŵîñðöŵ ţö þŕévéñţ ţĥöšé ŕéšöûŕçéš ƒŕöm åƒƒéçţîñĝ ýöûŕ šçöŕéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree twentyfour]}other{[Ţĥéŕé måý бé šţöŕéð ðåţå åƒƒéçţîñĝ ļöåðîñĝ þéŕƒöŕmåñçé îñ ţĥéšé ļöçåţîöñš: ᐅ{locations}ᐊ. Åûðîţ ţĥîš þåĝé îñ åñ îñçöĝñîţö ŵîñðöŵ ţö þŕévéñţ ţĥöšé ŕéšöûŕçéš ƒŕöm åƒƒéçţîñĝ ýöûŕ šçöŕéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree twentyfour]}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "[Ţĥé þåĝé måý ñöţ бé ļöåðîñĝ åš éxþéçţéð бéçåûšé ýöûŕ ţéšţ ÛŔĻ (ᐅ{requested}ᐊ) ŵåš ŕéðîŕéçţéð ţö ᐅ{final}ᐊ. Ţŕý ţéšţîñĝ ţĥé šéçöñð ÛŔĻ ðîŕéçţļý. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty]"
   },
diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json
index d95db3b11..775cb3a4f 100644
--- a/lighthouse-core/lib/i18n/locales/en-XL.json
+++ b/lighthouse-core/lib/i18n/locales/en-XL.json
@@ -1703,7 +1703,7 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "M̂ób̂íl̂é F̂ŕîén̂d́l̂ý"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
+  "lighthouse-core/gather/driver/storage.js | warningData": {
     "message": "{locationCount, plural,\n    =1 {T̂h́êŕê ḿâý b̂é ŝt́ôŕêd́ d̂át̂á âf́f̂éĉt́îńĝ ĺôád̂ín̂ǵ p̂ér̂f́ôŕm̂án̂ćê ín̂ t́ĥíŝ ĺôćât́îón̂: {locations}. Áûd́ît́ t̂h́îś p̂áĝé îń âń îńĉóĝńît́ô ẃîńd̂óŵ t́ô ṕr̂év̂én̂t́ t̂h́ôśê ŕêśôúr̂ćêś f̂ŕôḿ âf́f̂éĉt́îńĝ ýôúr̂ śĉór̂éŝ.}\n    other {T́ĥér̂é m̂áŷ b́ê śt̂ór̂éd̂ d́ât́â áf̂f́êćt̂ín̂ǵ l̂óâd́îńĝ ṕêŕf̂ór̂ḿâńĉé îń t̂h́êśê ĺôćât́îón̂ś: {locations}. Âúd̂ít̂ t́ĥíŝ ṕâǵê ín̂ án̂ ín̂ćôǵn̂ít̂ó ŵín̂d́ôẃ t̂ó p̂ŕêv́êńt̂ t́ĥóŝé r̂éŝóûŕĉéŝ f́r̂óm̂ áf̂f́êćt̂ín̂ǵ ŷóûŕ ŝćôŕêś.}\n  }"
   },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
diff --git a/lighthouse-core/lib/i18n/locales/es-419.json b/lighthouse-core/lib/i18n/locales/es-419.json
index 4705964ef..000e4449e 100644
--- a/lighthouse-core/lib/i18n/locales/es-419.json
+++ b/lighthouse-core/lib/i18n/locales/es-419.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Optimizada para dispositivos móviles"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Es posible que los datos almacenados afecten el rendimiento de carga de esta ubicación: {locations}. Realiza una auditoría de esta página en una ventana de incógnito para evitar que estos recursos afecten el resultado.}other{Es posible que los datos almacenados afecten el rendimiento de carga de estas ubicaciones: {locations}. Realiza una auditoría de esta página en una ventana de incógnito para evitar que estos recursos afecten el resultado.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Es posible que la página no se cargue como se espera porque se redireccionó la URL de prueba ({requested}) a {final}. Como alternativa, prueba directamente la segunda URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/es.json b/lighthouse-core/lib/i18n/locales/es.json
index 6fafd5481..1dc3540e7 100644
--- a/lighthouse-core/lib/i18n/locales/es.json
+++ b/lighthouse-core/lib/i18n/locales/es.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Optimización para móviles"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Es posible que algunos datos almacenados hayan afectado al rendimiento de las subidas en esta ubicación: {locations}. Haz una auditoría de esta página en una ventana de incógnito para evitar que esos recursos afecten a tus puntuaciones.}other{Es posible que algunos datos almacenados hayan afectado al rendimiento de las subidas en estas ubicaciones: {locations}. Haz una auditoría de esta página en una ventana de incógnito para evitar que esos recursos afecten a tus puntuaciones.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Es posible que la página no se esté cargando correctamente porque la URL de prueba ({requested}) se ha redirigido a {final}. Prueba directamente la segunda URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/fi.json b/lighthouse-core/lib/i18n/locales/fi.json
index b6029f27d..2084cf50a 100644
--- a/lighthouse-core/lib/i18n/locales/fi.json
+++ b/lighthouse-core/lib/i18n/locales/fi.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobiiliystävällinen"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Tähän sijaintiin on voitu tallentaa dataa, joka vaikuttaa lataukseen: {locations}. Tarkista sivu incognito-ikkunassa, jotta voit estää kyseisten resurssien vaikutuksen tuloksiisi.}other{Näihin sijainteihin on voitu tallentaa dataa, joka vaikuttaa lataukseen: {locations}. Tarkista sivu incognito-ikkunassa, jotta voit estää kyseisten resurssien vaikutuksen tuloksiisi.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Sivun lataus ei ehkä onnistu odotetusti, koska testi-URL ({requested}) ohjattiin uudelleen osoitteeseen {final}. Kokeile suoraan toista URL-osoitetta."
   },
diff --git a/lighthouse-core/lib/i18n/locales/fil.json b/lighthouse-core/lib/i18n/locales/fil.json
index d90176211..20e083f9c 100644
--- a/lighthouse-core/lib/i18n/locales/fil.json
+++ b/lighthouse-core/lib/i18n/locales/fil.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Pang-mobile"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Posibleng may naka-store na data na nakakaapekto sa bilis ng pag-load sa lokasyong ito: {locations}. I-audit ang page na ito sa incognito window para hindi maapektuhan ng mga resource na iyon ang iyong mga score.}one{Posibleng may naka-store na data na nakakaapekto sa bilis ng pag-load sa mga lokasyong ito: {locations}. I-audit ang page na ito sa incognito window para hindi maapektuhan ng mga resource na iyon ang iyong mga score.}other{Posibleng may naka-store na data na nakakaapekto sa bilis ng pag-load sa mga lokasyong ito: {locations}. I-audit ang page na ito sa incognito window para hindi maapektuhan ng mga resource na iyon ang iyong mga score.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Posibleng hindi naglo-load ang page gaya ng inaasahan dahil na-redirect ang iyong pansubok na URL ({requested}) sa {final}. Direktang subukan ang pangalawang URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/fr.json b/lighthouse-core/lib/i18n/locales/fr.json
index 45899db06..70b394673 100644
--- a/lighthouse-core/lib/i18n/locales/fr.json
+++ b/lighthouse-core/lib/i18n/locales/fr.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Adapté aux mobiles"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{L'emplacement suivant peut contenir des données qui affectent les performances de chargement : {locations}. Effectuez un audit de cette page dans une fenêtre de navigation privée afin que ces ressources n'affectent pas vos scores.}one{L'emplacement suivant peut contenir des données qui affectent les performances de chargement : {locations}. Effectuez un audit de cette page dans une fenêtre de navigation privée afin que ces ressources n'affectent pas vos scores.}other{Les emplacements suivants peuvent contenir des données qui affectent les performances de chargement : : {locations}. Effectuez un audit de cette page dans une fenêtre de navigation privée afin que ces ressources n'affectent pas vos scores.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Il est possible que cette page ne se charge pas comme prévu, car votre URL de test ({requested}) redirige vers {final}. Essayez de tester directement la seconde URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/he.json b/lighthouse-core/lib/i18n/locales/he.json
index cf2aae39c..eba8558ab 100644
--- a/lighthouse-core/lib/i18n/locales/he.json
+++ b/lighthouse-core/lib/i18n/locales/he.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "התאמה לניידים"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{ייתכן שיש נתונים שמורים שמשפיעים על ביצועי הטעינה במיקום הזה: {locations}. יש לבדוק את הדף הזה בחלון אנונימי כדי למנוע מהמשאבים האלה להשפיע על תוצאות הניקוד.}two{ייתכן שיש נתונים שמורים שמשפיעים על ביצועי הטעינה במיקומים האלה: {locations}. יש לבדוק את הדף הזה בחלון אנונימי כדי למנוע מהמשאבים האלה להשפיע על תוצאות הניקוד.}many{ייתכן שיש נתונים שמורים שמשפיעים על ביצועי הטעינה במיקומים האלה: {locations}. יש לבדוק את הדף הזה בחלון אנונימי כדי למנוע מהמשאבים האלה להשפיע על תוצאות הניקוד.}other{ייתכן שיש נתונים שמורים שמשפיעים על ביצועי הטעינה במיקומים האלה: {locations}. יש לבדוק את הדף הזה בחלון אנונימי כדי למנוע מהמשאבים האלה להשפיע על תוצאות הניקוד.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "ייתכן שהדף לא נטען כמו שציפית כי כתובת ה-URL הנבדקת ({requested}) הופנתה לכתובת {final}. כדאי לנסות לבדוק ישירות את כתובת ה-URL השנייה."
   },
diff --git a/lighthouse-core/lib/i18n/locales/hi.json b/lighthouse-core/lib/i18n/locales/hi.json
index 3a46c964a..01d2be080 100644
--- a/lighthouse-core/lib/i18n/locales/hi.json
+++ b/lighthouse-core/lib/i18n/locales/hi.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "मोबाइल फ़्रेंडली"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{हो सकता है कि सेव किया गया डेटा इस जगह पर, लोड होने की परफ़ॉर्मेंस पर असर डाल रहा हो: {locations}. इस पेज को गुप्त विंडो में ऑडिट करें, ताकि उन संसाधनों का आपके स्कोर पर कोई असर न पड़े.}one{हो सकता है कि सेव किया गया डेटा इस जगह पर, लोड होने की परफ़ॉर्मेंस पर असर डाल रहा हो: {locations}. इस पेज को गुप्त विंडो में ऑडिट करें, ताकि उन संसाधनों का आपके स्कोर पर कोई असर न पड़े.}other{हो सकता है कि सेव किया गया डेटा इन जगहों पर, लोड होने की परफ़ॉर्मेंस पर असर डाल रहा हो: {locations}. इस पेज को गुप्त विंडो में ऑडिट करें, ताकि उन संसाधनों का आपके स्कोर पर कोई असर न पड़े.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "शायद पेज उम्मीद के मुताबिक लोड नहीं हो रहा है, क्योंकि आपके टेस्ट यूआरएल ({requested}) को {final} की ओर रीडायरेक्ट किया गया था. टेस्टिंग के लिए दूसरा यूआरएल आज़माएं."
   },
diff --git a/lighthouse-core/lib/i18n/locales/hr.json b/lighthouse-core/lib/i18n/locales/hr.json
index 6ac2f0945..c7273e78f 100644
--- a/lighthouse-core/lib/i18n/locales/hr.json
+++ b/lighthouse-core/lib/i18n/locales/hr.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Prilagođeno mobilnim uređajima"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Na ovoj lokaciji možda ima pohranjenih podataka koji utječu na izvedbu učitavanja: {locations}. Provjerite stranicu u anonimnom prozoru da ti resursi ne bi utjecali na rezultate.}one{Na ovim lokacijama možda ima pohranjenih podataka koji utječu na izvedbu učitavanja: {locations}. Provjerite stranicu u anonimnom prozoru da ti resursi ne bi utjecali na rezultate.}few{Na ovim lokacijama možda ima pohranjenih podataka koji utječu na izvedbu učitavanja: {locations}. Provjerite stranicu u anonimnom prozoru da ti resursi ne bi utjecali na rezultate.}other{Na ovim lokacijama možda ima pohranjenih podataka koji utječu na izvedbu učitavanja: {locations}. Provjerite stranicu u anonimnom prozoru da ti resursi ne bi utjecali na rezultate.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Stranica se možda ne učitava kako je očekivano jer je vaš testni URL ({requested}) bio preusmjeren na {final}. Pokušajte izravno testirati drugi URL"
   },
diff --git a/lighthouse-core/lib/i18n/locales/hu.json b/lighthouse-core/lib/i18n/locales/hu.json
index 6472d6d65..30fbc9b73 100644
--- a/lighthouse-core/lib/i18n/locales/hu.json
+++ b/lighthouse-core/lib/i18n/locales/hu.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobilbarát"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Előfordulhat, hogy a tárolt adatok befolyásolják a betöltési sebességet ezen a helyen: {locations}. Ellenőrizze ezt az oldalt inkognitó ablakban, hogy az érintett források ne lehessenek hatással a teljesítmény értékelésére.}other{Előfordulhat, hogy a tárolt adatok befolyásolják a betöltési sebességet ezeken a helyeken: {locations}. Ellenőrizze ezt az oldalt inkognitó ablakban, hogy az érintett források ne lehessenek hatással a teljesítmény értékelésére.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Előfordulhat, hogy az oldal nem az elvártaknak megfelelően töltődik be, mert a teszt-URL ({requested}) átirányít ide: {final}. Próbálkozzon a második URL közvetlen tesztelésével."
   },
diff --git a/lighthouse-core/lib/i18n/locales/id.json b/lighthouse-core/lib/i18n/locales/id.json
index caffc5d83..1796e2edc 100644
--- a/lighthouse-core/lib/i18n/locales/id.json
+++ b/lighthouse-core/lib/i18n/locales/id.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobile Friendly"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Mungkin terdapat data tersimpan yang memengaruhi performa pemuatan di lokasi ini: {locations}. Audit halaman ini di jendela Samaran untuk mencegah resource tersebut memengaruhi performa Anda.}other{Mungkin terdapat data tersimpan yang memengaruhi performa pemuatan di lokasi ini {locations}. Audit halaman ini di jendela Samaran untuk mencegah resource tersebut memengaruhi performa Anda.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Halaman tersebut mungkin tidak dimuat sesuai harapan karena URL uji Anda ({requested}) dialihkan ke {final}. Coba uji URL kedua secara langsung."
   },
diff --git a/lighthouse-core/lib/i18n/locales/it.json b/lighthouse-core/lib/i18n/locales/it.json
index b55844e72..412aa4ea1 100644
--- a/lighthouse-core/lib/i18n/locales/it.json
+++ b/lighthouse-core/lib/i18n/locales/it.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Ottimizzata per i dispositivi mobili"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Potrebbero esserci dei dati memorizzati che incidono sulle prestazioni di caricamento in questa posizione: {locations}. Controlla questa pagina in una finestra di navigazione in incognito per evitare che queste risorse incidano sui tuoi punteggi.}other{Potrebbero esserci dei dati memorizzati che incidono sulle prestazioni di caricamento in queste posizioni: {locations}. Controlla questa pagina in una finestra di navigazione in incognito per evitare che queste risorse incidano sui tuoi punteggi.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "La pagina potrebbe non essere caricata come previsto perché l'URL di prova ({requested}) è stato reindirizzato a {final}. Prova a testare direttamente il secondo URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ja.json b/lighthouse-core/lib/i18n/locales/ja.json
index def9d795f..0ab0a7bf9 100644
--- a/lighthouse-core/lib/i18n/locales/ja.json
+++ b/lighthouse-core/lib/i18n/locales/ja.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "スマホ対応"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{次の場所に保存されているデータが読み込みパフォーマンスに影響を与えている可能性があります: {locations}。これらのリソースがスコアに影響しないようにするためには、シークレット ウィンドウでこのページを監査してください。}other{次の場所に保存されているデータが読み込みパフォーマンスに影響を与えている可能性があります: {locations}。これらのリソースがスコアに影響しないようにするためには、シークレット ウィンドウでこのページを監査してください。}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "テスト URL（{requested}）が「{final}」にリダイレクトされているため、このページは想定どおりに読み込まれない可能性があります。2 番目の URL を直接テストしてみてください。"
   },
diff --git a/lighthouse-core/lib/i18n/locales/ko.json b/lighthouse-core/lib/i18n/locales/ko.json
index cbe1c5bd9..43a2e1bfe 100644
--- a/lighthouse-core/lib/i18n/locales/ko.json
+++ b/lighthouse-core/lib/i18n/locales/ko.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "모바일 친화적"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{{locations}에 저장된 데이터가 로드 성능에 영향을 줄 수 있습니다. 시크릿 창에서 페이지를 감사하여 이러한 리소스가 점수에 영향을 주지 않도록 하세요.}other{{locations}에 저장된 데이터가 로드 성능에 영향을 줄 수 있습니다. 시크릿 창에서 페이지를 감사하여 이러한 리소스가 점수에 영향을 주지 않도록 하세요.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "테스트 URL({requested})이 {final}(으)로 리디렉션되었기 때문에 페이지가 예상대로 로드되지 않을 수 있습니다. 두 번째 URL을 직접 테스트해 보세요."
   },
diff --git a/lighthouse-core/lib/i18n/locales/lt.json b/lighthouse-core/lib/i18n/locales/lt.json
index ebef6d4c8..237ad5b4a 100644
--- a/lighthouse-core/lib/i18n/locales/lt.json
+++ b/lighthouse-core/lib/i18n/locales/lt.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Pritaikyta mobiliesiems"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Šioje vietoje gali būti išsaugotų duomenų, turinčių poveikio įkėlimo našumui: {locations}. Patikrinkite šį puslapį inkognito lange, kad šie šaltiniai nepaveiktų rezultatų.}one{Šiose vietose gali būti išsaugotų duomenų, turinčių poveikio įkėlimo našumui: {locations}. Patikrinkite šį puslapį inkognito lange, kad šie šaltiniai nepaveiktų rezultatų.}few{Šiose vietose gali būti išsaugotų duomenų, turinčių poveikio įkėlimo našumui: {locations}. Patikrinkite šį puslapį inkognito lange, kad šie šaltiniai nepaveiktų rezultatų.}many{Šiose vietose gali būti išsaugotų duomenų, turinčių poveikio įkėlimo našumui: {locations}. Patikrinkite šį puslapį inkognito lange, kad šie šaltiniai nepaveiktų rezultatų.}other{Šiose vietose gali būti išsaugotų duomenų, turinčių poveikio įkėlimo našumui: {locations}. Patikrinkite šį puslapį inkognito lange, kad šie šaltiniai nepaveiktų rezultatų.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Puslapis gali būti neįkeliamas, kaip numatyta, nes bandomasis URL ({requested}) peradresavo į {final}. Išbandykite antrąjį URL tiesiogiai."
   },
diff --git a/lighthouse-core/lib/i18n/locales/lv.json b/lighthouse-core/lib/i18n/locales/lv.json
index 30b250f38..387f0982e 100644
--- a/lighthouse-core/lib/i18n/locales/lv.json
+++ b/lighthouse-core/lib/i18n/locales/lv.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Piemērota mobilajām ierīcēm"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Iespējams, ielādes veiktspēju ietekmē dati, kas tiek glabāti šajā atrašanās vietā: {locations}. Lai šie resursi neietekmētu jūsu rezultātus, pārbaudiet šo lapu inkognito logā.}zero{Iespējams, ielādes veiktspēju ietekmē dati, kas tiek glabāti šajās atrašanās vietās: {locations}. Lai šie resursi neietekmētu jūsu rezultātus, pārbaudiet šo lapu inkognito logā.}one{Iespējams, ielādes veiktspēju ietekmē dati, kas tiek glabāti šajās atrašanās vietās: {locations}. Lai šie resursi neietekmētu jūsu rezultātus, pārbaudiet šo lapu inkognito logā.}other{Iespējams, ielādes veiktspēju ietekmē dati, kas tiek glabāti šajās atrašanās vietās: {locations}. Lai šie resursi neietekmētu jūsu rezultātus, pārbaudiet šo lapu inkognito logā.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Iespējams, lapa netiek ielādēta, kā paredzēts, jo jūsu testa URL ({requested}) tika novirzīts uz: {final}. Mēģiniet tieši testēt otro URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/nl.json b/lighthouse-core/lib/i18n/locales/nl.json
index fea5163cb..deb1bf672 100644
--- a/lighthouse-core/lib/i18n/locales/nl.json
+++ b/lighthouse-core/lib/i18n/locales/nl.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Geschikt voor mobiele apparaten"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Er kunnen op de volgende locatie gegevens zijn opgeslagen die van invloed zijn op de laadprestaties: {locations}. Controleer deze pagina in een incognitovenster om te voorkomen dat die bronnen van invloed zijn op je scores.}other{Er kunnen op de volgende locaties gegevens zijn opgeslagen die van invloed zijn op de laadprestaties: {locations}. Controleer deze pagina in een incognitovenster om te voorkomen dat die bronnen van invloed zijn op je scores.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "De pagina wordt mogelijk niet geladen zoals verwacht omdat je test-URL ({requested}) is omgeleid naar {final}. Probeer de tweede URL rechtstreeks te testen."
   },
diff --git a/lighthouse-core/lib/i18n/locales/no.json b/lighthouse-core/lib/i18n/locales/no.json
index f4b98dcf1..1d0c69388 100644
--- a/lighthouse-core/lib/i18n/locales/no.json
+++ b/lighthouse-core/lib/i18n/locales/no.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobilvennlig"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Det kan være lagrede data som påvirker innlastingsytelsen, på denne plasseringen: {locations}. Utfør en revisjon på denne siden i et inkognitovindu for å forhindre disse ressursene fra å påvirke poengsummene.}other{Det kan være lagrede data som påvirker innlastingsytelsen, på disse plasseringene: {locations}. Utfør en revisjon på denne siden i et inkognitovindu for å forhindre disse ressursene fra å påvirke poengsummene.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Siden lastes kanskje ikke inn som forventet, fordi testnettadressen ({requested}) ble viderekoblet til {final}. Prøv å teste den andre nettadressen direkte."
   },
diff --git a/lighthouse-core/lib/i18n/locales/pl.json b/lighthouse-core/lib/i18n/locales/pl.json
index e0ac2dd31..e35f01e97 100644
--- a/lighthouse-core/lib/i18n/locales/pl.json
+++ b/lighthouse-core/lib/i18n/locales/pl.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Na komórki"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Możliwe, że przechowywane są dane, które mają wpływ na szybkość wczytywania w tej lokalizacji: {locations}. Aby uchronić się przed wpływem tych zasobów na wyniki, sprawdź tę stronę w oknie incognito.}few{Możliwe, że przechowywane są dane, które mają wpływ na szybkość wczytywania w tych lokalizacjach: {locations}. Aby uchronić się przed wpływem tych zasobów na wyniki, sprawdź tę stronę w oknie incognito.}many{Możliwe, że przechowywane są dane, które mają wpływ na szybkość wczytywania w tych lokalizacjach: {locations}. Aby uchronić się przed wpływem tych zasobów na wyniki, sprawdź tę stronę w oknie incognito.}other{Możliwe, że przechowywane są dane, które mają wpływ na szybkość wczytywania w tych lokalizacjach: {locations}. Aby uchronić się przed wpływem tych zasobów na wyniki, sprawdź tę stronę w oknie incognito.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Strona może nie wczytywać się prawidłowo, ponieważ testowy URL ({requested}) został przekierowany pod adres {final}. Przetestuj drugi adres URL bezpośrednio."
   },
diff --git a/lighthouse-core/lib/i18n/locales/pt-PT.json b/lighthouse-core/lib/i18n/locales/pt-PT.json
index 5e7e5a93f..032e3c391 100644
--- a/lighthouse-core/lib/i18n/locales/pt-PT.json
+++ b/lighthouse-core/lib/i18n/locales/pt-PT.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Compatível com dispositivos móveis"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Podem existir dados armazenados que estão a afetar o desempenho do carregamento nesta localização: {locations}. Audite esta página numa janela de navegação anónima para impedir esses recursos de afetarem as suas pontuações.}other{Podem existir dados armazenados que estão a afetar o desempenho do carregamento nestas localizações: {locations}. Audite esta página numa janela de navegação anónima para impedir esses recursos de afetarem as suas pontuações.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "A página pode não estar a ser carregada conforme o esperado porque o seu URL de teste ({requested}) foi redirecionado para {final}. Experimente testar o segundo URL diretamente."
   },
diff --git a/lighthouse-core/lib/i18n/locales/pt.json b/lighthouse-core/lib/i18n/locales/pt.json
index ce799cbcc..beccc2a62 100644
--- a/lighthouse-core/lib/i18n/locales/pt.json
+++ b/lighthouse-core/lib/i18n/locales/pt.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Otimizada para dispositivos móveis"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Pode haver dados armazenados afetando o desempenho de carregamento neste local: {locations}. Examine esta página em uma janela anônima para evitar que esses recursos afetem suas pontuações.}one{Pode haver dados armazenados afetando o desempenho de carregamento neste local: {locations}. Examine esta página em uma janela anônima para evitar que esses recursos afetem suas pontuações.}other{Pode haver dados armazenados afetando o desempenho de carregamento nestes locais: {locations}. Examine esta página em uma janela anônima para evitar que esses recursos afetem suas pontuações.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "A página pode não ser carregada conforme esperado porque seu URL de teste ({requested}) foi redirecionado para {final}. Teste o segundo URL diretamente."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ro.json b/lighthouse-core/lib/i18n/locales/ro.json
index d7f7de514..ae823791c 100644
--- a/lighthouse-core/lib/i18n/locales/ro.json
+++ b/lighthouse-core/lib/i18n/locales/ro.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Pentru dispozitivele mobile"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Este posibil să existe date stocate care afectează performanța de încărcare în următoarea locație: {locations}. Verifică pagina într-o fereastră incognito pentru a împiedica resursele să îți afecteze scorurile.}few{Este posibil să existe date stocate care afectează performanța de încărcare în următoarele locații: {locations}. Verifică pagina într-o fereastră incognito pentru a împiedica resursele să îți afecteze scorurile.}other{Este posibil să existe date stocate care afectează performanța de încărcare în următoarele locații: {locations}. Verifică pagina într-o fereastră incognito pentru a împiedica resursele să îți afecteze scorurile.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Este posibil ca pagina să nu se încarce conform așteptărilor, deoarece adresa URL de testare ({requested}) a fost redirecționată spre {final}. Încearcă să testezi direct a doua adresă URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ru.json b/lighthouse-core/lib/i18n/locales/ru.json
index 6640d21e2..663e684da 100644
--- a/lighthouse-core/lib/i18n/locales/ru.json
+++ b/lighthouse-core/lib/i18n/locales/ru.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Оптимизация для мобильных устройств"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{На скорость загрузки могут влиять данные из этого хранилища: {locations}. Попробуйте открыть страницу в режиме инкогнито.}one{На скорость загрузки могут влиять данные из этих хранилищ: {locations}. Попробуйте открыть страницу в режиме инкогнито.}few{На скорость загрузки могут влиять данные из этих хранилищ: {locations}. Попробуйте открыть страницу в режиме инкогнито.}many{На скорость загрузки могут влиять данные из этих хранилищ: {locations}. Попробуйте открыть страницу в режиме инкогнито.}other{На скорость загрузки могут влиять данные из этих хранилищ: {locations}. Попробуйте открыть страницу в режиме инкогнито.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Эта страница может загружаться неправильно, потому что тестовый URL ({requested}) был перенаправлен на {final}. Попробуйте напрямую проверить второй URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/sk.json b/lighthouse-core/lib/i18n/locales/sk.json
index 7931ef744..4521f1349 100644
--- a/lighthouse-core/lib/i18n/locales/sk.json
+++ b/lighthouse-core/lib/i18n/locales/sk.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Vhodné pre mobilné zariadenia"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{V nasledujúcom umiestnení môžu byť uložené údaje, ktoré ovplyvňujú výkonnosť načítavania: {locations}. Skontrolujte túto stránku v okne inkognito, aby príslušné zdroje neovplyvnili vaše skóre.}few{V nasledujúcich umiestneniach môžu byť uložené údaje, ktoré ovplyvňujú výkonnosť načítavania: {locations}. Skontrolujte túto stránku v okne inkognito, aby príslušné zdroje neovplyvnili vaše skóre.}many{V nasledujúcich umiestneniach môžu byť uložené údaje, ktoré ovplyvňujú výkonnosť načítavania: {locations}. Skontrolujte túto stránku v okne inkognito, aby príslušné zdroje neovplyvnili vaše skóre.}other{V nasledujúcich umiestneniach môžu byť uložené údaje, ktoré ovplyvňujú výkonnosť načítavania: {locations}. Skontrolujte túto stránku v okne inkognito, aby príslušné zdroje neovplyvnili vaše skóre.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Táto stránka sa nemusí načítavať podľa očakávaní, pretože testovacia webová adresa ({requested}) bola presmerovaná na {final}. Skúste priamo otestovať druhú webovú adresu."
   },
diff --git a/lighthouse-core/lib/i18n/locales/sl.json b/lighthouse-core/lib/i18n/locales/sl.json
index b646f705d..9589fc8da 100644
--- a/lighthouse-core/lib/i18n/locales/sl.json
+++ b/lighthouse-core/lib/i18n/locales/sl.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Prilagojeno za mobilne naprave"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Na tej lokaciji so morda shranjeni podatki, ki vplivajo na uspešnost nalaganja: {locations}. Preglejte to stran v anonimnem oknu, da preprečite, da bi ti viri vplivali na vaše rezultate.}one{Na teh lokacijah so morda shranjeni podatki, ki vplivajo na uspešnost nalaganja: {locations}. Preglejte to stran v anonimnem oknu, da preprečite, da bi ti viri vplivali na vaše rezultate.}two{Na teh lokacijah so morda shranjeni podatki, ki vplivajo na uspešnost nalaganja: {locations}. Preglejte to stran v anonimnem oknu, da preprečite, da bi ti viri vplivali na vaše rezultate.}few{Na teh lokacijah so morda shranjeni podatki, ki vplivajo na uspešnost nalaganja: {locations}. Preglejte to stran v anonimnem oknu, da preprečite, da bi ti viri vplivali na vaše rezultate.}other{Na teh lokacijah so morda shranjeni podatki, ki vplivajo na uspešnost nalaganja: {locations}. Preglejte to stran v anonimnem oknu, da preprečite, da bi ti viri vplivali na vaše rezultate.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Stran se morda ne naloži po pričakovanjih, ker je bil preizkusni URL ({requested}) preusmerjen na {final}. Poskusite neposredno preizkusiti drugi URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/sr-Latn.json b/lighthouse-core/lib/i18n/locales/sr-Latn.json
index 6a9ca4b47..393e0bc8e 100644
--- a/lighthouse-core/lib/i18n/locales/sr-Latn.json
+++ b/lighthouse-core/lib/i18n/locales/sr-Latn.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Prilagođeno mobilnim uređajima"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Na ovoj lokaciji možda ima sačuvanih podataka koji utiču na efikasnost učitavanja: {locations}. Proverite tu stranicu u prozoru bez arhiviranja da biste sprečili da ti resursi utiču na rezultate.}one{Na ovim lokacijama možda ima sačuvanih podataka koji utiču na efikasnost učitavanja: {locations}. Proverite tu stranicu u prozoru bez arhiviranja da biste sprečili da ti resursi utiču na rezultate.}few{Na ovim lokacijama možda ima sačuvanih podataka koji utiču na efikasnost učitavanja: {locations}. Proverite tu stranicu u prozoru bez arhiviranja da biste sprečili da ti resursi utiču na rezultate.}other{Na ovim lokacijama možda ima sačuvanih podataka koji utiču na efikasnost učitavanja: {locations}. Proverite tu stranicu u prozoru bez arhiviranja da biste sprečili da ti resursi utiču na rezultate.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Stranica se možda ne učitava na očekivan način zato što je probni URL ({requested}) preusmeren na {final}. Probajte direktno da testirate drugi URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/sr.json b/lighthouse-core/lib/i18n/locales/sr.json
index a90cdb339..e0d14d00a 100644
--- a/lighthouse-core/lib/i18n/locales/sr.json
+++ b/lighthouse-core/lib/i18n/locales/sr.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Прилагођено мобилним уређајима"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{На овој локацији можда има сачуваних података који утичу на ефикасност учитавања: {locations}. Проверите ту страницу у прозору без архивирања да бисте спречили да ти ресурси утичу на резултате.}one{На овим локацијама можда има сачуваних података који утичу на ефикасност учитавања: {locations}. Проверите ту страницу у прозору без архивирања да бисте спречили да ти ресурси утичу на резултате.}few{На овим локацијама можда има сачуваних података који утичу на ефикасност учитавања: {locations}. Проверите ту страницу у прозору без архивирања да бисте спречили да ти ресурси утичу на резултате.}other{На овим локацијама можда има сачуваних података који утичу на ефикасност учитавања: {locations}. Проверите ту страницу у прозору без архивирања да бисте спречили да ти ресурси утичу на резултате.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Страница се можда не учитава на очекиван начин зато што је пробни URL ({requested}) преусмерен на {final}. Пробајте директно да тестирате други URL."
   },
diff --git a/lighthouse-core/lib/i18n/locales/sv.json b/lighthouse-core/lib/i18n/locales/sv.json
index a9080e2e4..98acb1956 100644
--- a/lighthouse-core/lib/i18n/locales/sv.json
+++ b/lighthouse-core/lib/i18n/locales/sv.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobilanpassad"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{På följande plats kan det finnas lagrad data som påverkar inläsningen: {locations}. Granska sidan i ett inkognitofönster för att förhindra att resurserna påverkar dina resultat.}other{På följande platser kan det finnas lagrad data som påverkar inläsningen: {locations}. Granska sidan i ett inkognitofönster för att förhindra att resurserna påverkar dina resultat.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Sidan kanske inte läses in eftersom testwebbadressen ({requested}) omdirigerades till {final}. Testa den andra webbadressen direkt i stället."
   },
diff --git a/lighthouse-core/lib/i18n/locales/ta.json b/lighthouse-core/lib/i18n/locales/ta.json
index e6e806a5d..f7f3cbee3 100644
--- a/lighthouse-core/lib/i18n/locales/ta.json
+++ b/lighthouse-core/lib/i18n/locales/ta.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "மொபைலுக்கேற்றது"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{செயல்திறன் அளவீட்டைப் பாதிக்கக்கூடிய, சேமித்தத் தரவு இந்த இடத்தில் இருக்கலாம்: {locations}. இவை உங்கள் ஸ்கோர்களைப் பாதிப்பதைத் தவிர்க்கும் வகையில், மறைநிலைச் சாளரத்தில் இந்தப் பக்கத்தின் செயல்பாட்டை ஆய்வு செய்யவும்.}other{செயல்திறன் அளவீட்டைப் பாதிக்கக்கூடிய, சேமித்தத் தரவு இந்த இடத்தில் இருக்கலாம்: {locations}. இவை உங்கள் ஸ்கோர்களைப் பாதிப்பதைத் தவிர்க்கும் வகையில், மறைநிலைச் சாளரத்தில் இந்தப் பக்கத்தின் செயல்பாட்டை ஆய்வு செய்யவும்.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "உங்கள் சோதனை URL ({requested}) {final}என்பதற்குத் திசைதிருப்பப்பட்டதால் எதிர்பரர்த்த அளவிற்குப் பக்கம் ஏற்றப்படாமல் போகக்கூடும். நேரடியாக இரண்டாவது URLலை சோதித்துப் பாருங்கள்."
   },
diff --git a/lighthouse-core/lib/i18n/locales/te.json b/lighthouse-core/lib/i18n/locales/te.json
index e700a8f2e..fb89447bd 100644
--- a/lighthouse-core/lib/i18n/locales/te.json
+++ b/lighthouse-core/lib/i18n/locales/te.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "మొబైల్-అనుకూలం"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{ఈ లొకేషన్‌లో 'లోడింగ్ పనితీరు'ను ప్రభావితం చేసే స్టోర్డ్ డేటా ఉండవచ్చు: {locations}. మీ స్కోర్‌లను ఆ రిసోర్స్‌లు ప్రభావితం చేయకుండా నివారించడానికి అజ్ఞాత విండోలో ఈ పేజీని ఆడిట్ చేయండి.}other{ఈ లొకేషన్‌లలో ఉండే స్టోర్డ్ డేటా వల్ల 'లోడింగ్ పనితీరు' ప్రభావితం కావచ్చు: {locations}. మీ స్కోర్‌లను ఆ రిసోర్స్‌లు ప్రభావితం చేయకుండా నివారించడానికి అజ్ఞాత విండోలో ఈ పేజీని ఆడిట్ చేయండి.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "మీ పరీక్ష URL ({requested})ను {final}కు మళ్లించినందున, పేజీ అనుకున్న విధంగా లోడ్ కాలేకపోతుండవచ్చు. నేరుగా రెండవ URLను పరీక్షించడం ట్రై చేయండి."
   },
diff --git a/lighthouse-core/lib/i18n/locales/th.json b/lighthouse-core/lib/i18n/locales/th.json
index 2407dab20..485d8237f 100644
--- a/lighthouse-core/lib/i18n/locales/th.json
+++ b/lighthouse-core/lib/i18n/locales/th.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "เหมาะกับอุปกรณ์เคลื่อนที่"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{อาจมีข้อมูลที่จัดเก็บไว้ที่ส่งผลกระทบต่อประสิทธิภาพการโหลดในตำแหน่ง {locations} ทดสอบหน้านี้ในหน้าต่างที่ไม่ระบุตัวตนเพื่อป้องกันไม่ให้ทรัพยากรเหล่านั้นส่งผลกระทบต่อคะแนนของคุณ}other{อาจมีข้อมูลที่จัดเก็บไว้ที่ส่งผลกระทบต่อประสิทธิภาพการโหลดในตำแหน่ง {locations} ทดสอบหน้านี้ในหน้าต่างที่ไม่ระบุตัวตนเพื่อป้องกันไม่ให้ทรัพยากรเหล่านั้นส่งผลกระทบต่อคะแนนของคุณ}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "หน้านี้อาจไม่โหลดขึ้นตามที่คาดไว้เนื่องจาก URL ทดสอบของคุณ ({requested}) มีการเปลี่ยนเส้นทางไปยัง {final} ลองทดสอบ URL ที่ 2 โดยตรง"
   },
diff --git a/lighthouse-core/lib/i18n/locales/tr.json b/lighthouse-core/lib/i18n/locales/tr.json
index 98a3db603..c3b5cabe2 100644
--- a/lighthouse-core/lib/i18n/locales/tr.json
+++ b/lighthouse-core/lib/i18n/locales/tr.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Mobil Uyumlu"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Şu konumda yükleme performansını etkileyen depolanmış veriler olabilir: {locations}. Bu kaynakların puanlarınızı etkilemesini engellemek için bu sayfayı gizli pencerede denetleyin.}other{Şu konumlarda yükleme performansını etkileyen depolanmış veriler olabilir: {locations}. Bu kaynakların puanlarınızı etkilemesini engellemek için bu sayfayı gizli pencerede denetleyin.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Sayfa, test URL'niz ({requested}) {final} adresine yönlendirildiğinden beklendiği gibi yüklenmiyor olabilir. İkinci URL'yi doğrudan test etmeyi deneyin."
   },
diff --git a/lighthouse-core/lib/i18n/locales/uk.json b/lighthouse-core/lib/i18n/locales/uk.json
index bb5c0d6c8..f196963a2 100644
--- a/lighthouse-core/lib/i18n/locales/uk.json
+++ b/lighthouse-core/lib/i18n/locales/uk.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Для мобільних пристроїв"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{У цьому місці можуть зберігатися дані, що впливають на тривалість завантаження: {locations}. Щоб такі ресурси не впливали на ваші показники, перевірте цю сторінку у вікні в режимі анонімного перегляду.}one{У цих місцях можуть зберігатися дані, що впливають на тривалість завантаження: {locations}. Щоб такі ресурси не впливали на ваші показники, перевірте цю сторінку у вікні в режимі анонімного перегляду.}few{У цих місцях можуть зберігатися дані, що впливають на тривалість завантаження: {locations}. Щоб такі ресурси не впливали на ваші показники, перевірте цю сторінку у вікні в режимі анонімного перегляду.}many{У цих місцях можуть зберігатися дані, що впливають на тривалість завантаження: {locations}. Щоб такі ресурси не впливали на ваші показники, перевірте цю сторінку у вікні в режимі анонімного перегляду.}other{У цих місцях можуть зберігатися дані, що впливають на тривалість завантаження: {locations}. Щоб такі ресурси не впливали на ваші показники, перевірте цю сторінку у вікні в режимі анонімного перегляду.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Ця сторінка може не завантажуватися належним чином, оскільки тестову URL-адресу ({requested}) було переспрямовано на {final}. Спробуйте перейти безпосередньо на другу URL-адресу."
   },
diff --git a/lighthouse-core/lib/i18n/locales/vi.json b/lighthouse-core/lib/i18n/locales/vi.json
index e0589d7d0..6292c30df 100644
--- a/lighthouse-core/lib/i18n/locales/vi.json
+++ b/lighthouse-core/lib/i18n/locales/vi.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "Thân thiện với thiết bị di động"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{Dữ liệu đã lưu trữ có thể ảnh hưởng đến hiệu suất tải ở vị trí này: {locations}. Hãy kiểm tra trang này trong một cửa sổ ẩn danh để ngăn các tài nguyên đó ảnh hưởng đến điểm hiệu suất của bạn.}other{Dữ liệu đã lưu trữ có thể ảnh hưởng đến hiệu suất tải ở các vị trí này: {locations}. Hãy kiểm tra trang này trong một cửa sổ ẩn danh để ngăn các tài nguyên đó ảnh hưởng đến điểm hiệu suất của bạn.}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "Trang có thể không tải như dự kiến do URL kiểm tra của bạn ({requested}) đã được chuyển hướng tới {final}. Hãy thử kiểm tra trực tiếp URL thứ hai."
   },
diff --git a/lighthouse-core/lib/i18n/locales/zh-HK.json b/lighthouse-core/lib/i18n/locales/zh-HK.json
index dc4aae044..7b8e88f91 100644
--- a/lighthouse-core/lib/i18n/locales/zh-HK.json
+++ b/lighthouse-core/lib/i18n/locales/zh-HK.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "適合透過流動裝置瀏覽"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{此位置可能有儲存資料正在影響載入效能：{locations}。請透過無痕式視窗審核此頁面，以免這些資源影響分數。}other{這些位置可能有儲存資料正在影響載入效能：{locations}。請透過無痕式視窗審核此頁面，以免這些資源影響分數。}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "您的測試網址 ({requested}) 已重新導向至 {final}，因此頁面未必可以如預期載入。請嘗試直接測試第二個網址。"
   },
diff --git a/lighthouse-core/lib/i18n/locales/zh-TW.json b/lighthouse-core/lib/i18n/locales/zh-TW.json
index 958e2ca7e..00443f239 100644
--- a/lighthouse-core/lib/i18n/locales/zh-TW.json
+++ b/lighthouse-core/lib/i18n/locales/zh-TW.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "適合透過行動裝置瀏覽"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{下列位置可能有影響載入效能的儲存資料：{locations}。在無痕式視窗中審核這個頁面可避免這些資源影響你的分數。}other{下列位置可能有影響載入效能的儲存資料：{locations}。在無痕式視窗中審核這個頁面可避免這些資源影響你的分數。}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "你的測試網址 ({requested}) 已重新導向至 {final}，因此頁面可能無法如預期載入。請直接測試第二個網址。"
   },
diff --git a/lighthouse-core/lib/i18n/locales/zh.json b/lighthouse-core/lib/i18n/locales/zh.json
index b3592591c..960325654 100644
--- a/lighthouse-core/lib/i18n/locales/zh.json
+++ b/lighthouse-core/lib/i18n/locales/zh.json
@@ -1703,9 +1703,6 @@
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
     "message": "适合移动设备"
   },
-  "lighthouse-core/gather/driver.js | warningData": {
-    "message": "{locationCount,plural, =1{下列位置可能存储了会影响加载性能的数据：{locations}。请在无痕式窗口中审核此页面，以防止这些资源影响您的得分。}other{下列位置可能存储了会影响加载性能的数据：{locations}。请在无痕式窗口中审核此页面，以防止这些资源影响您的得分。}}"
-  },
   "lighthouse-core/gather/gather-runner.js | warningRedirected": {
     "message": "相应网页可能无法按预期加载，因为您所测试的网址 ({requested}) 重定向到了 {final}。请尝试直接测试第二个网址。"
   },


Test patch:
diff --git a/lighthouse-core/test/gather/driver-test.js b/lighthouse-core/test/gather/driver-test.js
index b6855d6f9..4b44b5ee8 100644
--- a/lighthouse-core/test/gather/driver-test.js
+++ b/lighthouse-core/test/gather/driver-test.js
@@ -202,30 +202,6 @@ describe('.beginTrace', () => {
   });
 });
 
-describe('.setExtraHTTPHeaders', () => {
-  it('should Network.setExtraHTTPHeaders when there are extra-headers', async () => {
-    connectionStub.sendCommand = createMockSendCommandFn()
-      .mockResponse('Network.setExtraHTTPHeaders');
-
-    await driver.setExtraHTTPHeaders({
-      'Cookie': 'monster',
-      'x-men': 'wolverine',
-    });
-
-    expect(connectionStub.sendCommand).toHaveBeenCalledWith(
-      'Network.setExtraHTTPHeaders',
-      undefined,
-      expect.anything()
-    );
-  });
-
-  it('should not call Network.setExtraHTTPHeaders when there are not extra-headers', async () => {
-    connectionStub.sendCommand = createMockSendCommandFn();
-    await driver.setExtraHTTPHeaders(null);
-    expect(connectionStub.sendCommand).not.toHaveBeenCalled();
-  });
-});
-
 describe('.gotoURL', () => {
   beforeEach(() => {
     connectionStub.sendCommand = createMockSendCommandFn()
@@ -304,194 +280,6 @@ describe('.gotoURL', () => {
   });
 });
 
-describe('.assertNoSameOriginServiceWorkerClients', () => {
-  beforeEach(() => {
-    connectionStub.sendCommand = createMockSendCommandFn()
-      .mockResponse('ServiceWorker.enable')
-      .mockResponse('ServiceWorker.disable')
-      .mockResponse('ServiceWorker.enable')
-      .mockResponse('ServiceWorker.disable');
-  });
-
-  /**
-   * @param {number} id
-   * @param {string} url
-   * @param {boolean=} isDeleted
-   */
-  function createSWRegistration(id, url, isDeleted) {
-    return {
-      isDeleted: !!isDeleted,
-      registrationId: String(id),
-      scopeURL: url,
-    };
-  }
-
-  /**
-   * @param {number} id
-   * @param {string} url
-   * @param {string[]} controlledClients
-   * @param {LH.Crdp.ServiceWorker.ServiceWorkerVersionStatus=} status
-   */
-  function createActiveWorker(id, url, controlledClients, status = 'activated') {
-    return {
-      registrationId: String(id),
-      scriptURL: url,
-      controlledClients,
-      status,
-    };
-  }
-
-  it('will pass if there are no current service workers', async () => {
-    const pageUrl = 'https://example.com/';
-
-    driver.on = driver.once = createMockOnceFn()
-      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations: []})
-      .mockEvent('ServiceWorker.workerVersionUpdated', {versions: []});
-
-    const assertPromise = driver.assertNoSameOriginServiceWorkerClients(pageUrl);
-    await flushAllTimersAndMicrotasks();
-    await assertPromise;
-  });
-
-  it('will pass if there is an active service worker for a different origin', async () => {
-    const pageUrl = 'https://example.com/';
-    const secondUrl = 'https://example.edu';
-    const swUrl = `${secondUrl}sw.js`;
-
-    const registrations = [createSWRegistration(1, secondUrl)];
-    const versions = [createActiveWorker(1, swUrl, ['uniqueId'])];
-
-    driver.on = driver.once = createMockOnceFn()
-      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
-      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
-
-    const assertPromise = driver.assertNoSameOriginServiceWorkerClients(pageUrl);
-    await flushAllTimersAndMicrotasks();
-    await assertPromise;
-  });
-
-  it('will fail if a service worker with a matching origin has a controlled client', async () => {
-    const pageUrl = 'https://example.com/';
-    const swUrl = `${pageUrl}sw.js`;
-    const registrations = [createSWRegistration(1, pageUrl)];
-    const versions = [createActiveWorker(1, swUrl, ['uniqueId'])];
-
-    driver.on = driver.once = createMockOnceFn()
-      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
-      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
-
-    expect.assertions(1);
-
-    try {
-      const assertPromise = driver.assertNoSameOriginServiceWorkerClients(pageUrl);
-      await flushAllTimersAndMicrotasks();
-      await assertPromise;
-    } catch (err) {
-      expect(err.message.toLowerCase()).toContain('multiple tabs');
-    }
-  });
-
-  it('will succeed if a service worker with has no controlled clients', async () => {
-    const pageUrl = 'https://example.com/';
-    const swUrl = `${pageUrl}sw.js`;
-    const registrations = [createSWRegistration(1, pageUrl)];
-    const versions = [createActiveWorker(1, swUrl, [])];
-
-    driver.on = driver.once = createMockOnceFn()
-      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
-      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
-
-    const assertPromise = driver.assertNoSameOriginServiceWorkerClients(pageUrl);
-    await flushAllTimersAndMicrotasks();
-    await assertPromise;
-  });
-
-  it('will wait for serviceworker to be activated', async () => {
-    const pageUrl = 'https://example.com/';
-    const swUrl = `${pageUrl}sw.js`;
-    const registrations = [createSWRegistration(1, pageUrl)];
-    const versions = [createActiveWorker(1, swUrl, [], 'installing')];
-    const activatedVersions = [createActiveWorker(1, swUrl, [], 'activated')];
-
-    driver.on = driver.once = createMockOnceFn()
-      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
-      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
-
-    const assertPromise = driver.assertNoSameOriginServiceWorkerClients(pageUrl);
-    const inspectable = makePromiseInspectable(assertPromise);
-
-    // After receiving the empty versions the promise still shouldn't be resolved
-    await flushAllTimersAndMicrotasks();
-    expect(inspectable).not.toBeDone();
-
-    // Use `findListener` instead of `mockEvent` so we can control exactly when the promise resolves
-    // After we invoke the listener with the activated versions we expect the promise to have resolved
-    const listener = driver.on.findListener('ServiceWorker.workerVersionUpdated');
-    listener({versions: activatedVersions});
-    await flushAllTimersAndMicrotasks();
-    expect(inspectable).toBeDone();
-    await assertPromise;
-  });
-});
-
-describe('.clearDataForOrigin', () => {
-  it('only clears data from certain locations', async () => {
-    let foundStorageTypes;
-    connectionStub.sendCommand = createMockSendCommandFn()
-      .mockResponse('Storage.clearDataForOrigin', ({storageTypes}) => {
-        foundStorageTypes = storageTypes;
-      });
-    await driver.clearDataForOrigin('https://example.com');
-    // Should not see cookies, websql, indexeddb, or local_storage.
-    // Cookies are not cleared to preserve login.
-    // websql, indexeddb, and local_storage are not cleared to preserve important user data.
-    expect(foundStorageTypes).toMatchInlineSnapshot(
-      `"appcache,file_systems,shader_cache,service_workers,cache_storage"`
-    );
-  });
-});
-
-describe('.getImportantDataWarning', () => {
-  it('properly returns warning', async () => {
-    connectionStub.sendCommand = createMockSendCommandFn()
-      .mockResponse('Storage.getUsageAndQuota', {usageBreakdown: [
-        {storageType: 'local_storage', usage: 5},
-        {storageType: 'indexeddb', usage: 5},
-        {storageType: 'websql', usage: 0},
-        {storageType: 'appcache', usage: 5},
-        {storageType: 'cookies', usage: 5},
-        {storageType: 'file_systems', usage: 5},
-        {storageType: 'shader_cache', usage: 5},
-        {storageType: 'service_workers', usage: 5},
-        {storageType: 'cache_storage', usage: 0},
-      ]});
-    const warning = await driver.getImportantStorageWarning('https://example.com');
-    expect(warning).toBeDisplayString(
-      'There may be stored data affecting loading performance in ' +
-      'these locations: Local Storage, IndexedDB. ' +
-      'Audit this page in an incognito window to prevent those resources ' +
-      'from affecting your scores.'
-    );
-  });
-
-  it('only warn for certain locations', async () => {
-    connectionStub.sendCommand = createMockSendCommandFn()
-      .mockResponse('Storage.getUsageAndQuota', {usageBreakdown: [
-        {storageType: 'local_storage', usage: 0},
-        {storageType: 'indexeddb', usage: 0},
-        {storageType: 'websql', usage: 0},
-        {storageType: 'appcache', usage: 5},
-        {storageType: 'cookies', usage: 5},
-        {storageType: 'file_systems', usage: 5},
-        {storageType: 'shader_cache', usage: 5},
-        {storageType: 'service_workers', usage: 5},
-        {storageType: 'cache_storage', usage: 5},
-      ]});
-    const warning = await driver.getImportantStorageWarning('https://example.com');
-    expect(warning).toBeUndefined();
-  });
-});
-
 describe('Domain.enable/disable State', () => {
   it('dedupes (simple)', async () => {
     connectionStub.sendCommand = createMockSendCommandFn()
diff --git a/lighthouse-core/test/gather/driver/execution-context-test.js b/lighthouse-core/test/gather/driver/execution-context-test.js
index bc00a83d3..93cb1e8a3 100644
--- a/lighthouse-core/test/gather/driver/execution-context-test.js
+++ b/lighthouse-core/test/gather/driver/execution-context-test.js
@@ -44,6 +44,8 @@ describe('ExecutionContext', () => {
 
     forceNewContextId = async (executionContext, executionContextId) => {
       executionContext._session.sendCommand = createMockSendCommandFn()
+        .mockResponse('Page.enable')
+        .mockResponse('Runtime.enable')
         .mockResponse('Page.getResourceTree', {frameTree: {frame: {id: '1337'}}})
         .mockResponse('Page.createIsolatedWorld', {executionContextId})
         .mockResponse('Runtime.evaluate', {result: {value: 2}});
@@ -139,6 +141,8 @@ describe('.evaluateAsync', () => {
 
   it('evaluates an expression in isolation', async () => {
     let sendCommand = (sessionMock.sendCommand = createMockSendCommandFn()
+      .mockResponse('Page.enable')
+      .mockResponse('Runtime.enable')
       .mockResponse('Page.getResourceTree', {frameTree: {frame: {id: '1337'}}})
       .mockResponse('Page.createIsolatedWorld', {executionContextId: 1})
       .mockResponse('Runtime.evaluate', {result: {value: 2}}));
@@ -168,9 +172,13 @@ describe('.evaluateAsync', () => {
 
   it('recovers from isolation failures', async () => {
     sessionMock.sendCommand = createMockSendCommandFn()
+      .mockResponse('Page.enable')
+      .mockResponse('Runtime.enable')
       .mockResponse('Page.getResourceTree', {frameTree: {frame: {id: '1337'}}})
       .mockResponse('Page.createIsolatedWorld', {executionContextId: 9001})
       .mockResponse('Runtime.evaluate', Promise.reject(new Error('Cannot find context')))
+      .mockResponse('Page.enable')
+      .mockResponse('Runtime.enable')
       .mockResponse('Page.getResourceTree', {frameTree: {frame: {id: '1337'}}})
       .mockResponse('Page.createIsolatedWorld', {executionContextId: 9002})
       .mockResponse('Runtime.evaluate', {result: {value: 'mocked value'}});
@@ -251,8 +259,8 @@ const performance = globalThis.__nativePerformance || globalThis.performance;
     const value = await executionContext.evaluate(mainFn, {args: [1]}); // eslint-disable-line no-unused-vars
 
     const code = mockFn.mock.calls[0][0];
-    expect(code).toBe(`(() => {
-      
+    expect(trimTrailingWhitespace(code)).toBe(`(() => {
+
       return (function mainFn(value) {
       return value;
     })(1);
@@ -274,8 +282,8 @@ const performance = globalThis.__nativePerformance || globalThis.performance;
     const value = await executionContext.evaluate(mainFn, {args: [1]}); // eslint-disable-line no-unused-vars
 
     const code = mockFn.mock.calls[0][0];
-    expect(code).toBe(`(() => {
-      
+    expect(trimTrailingWhitespace(code)).toBe(`(() => {
+
       return ((value) => {
       return value;
     })(1);
diff --git a/lighthouse-core/test/gather/driver/service-workers-test.js b/lighthouse-core/test/gather/driver/service-workers-test.js
new file mode 100644
index 000000000..ad7e7ea8d
--- /dev/null
+++ b/lighthouse-core/test/gather/driver/service-workers-test.js
@@ -0,0 +1,86 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+/* eslint-env jest */
+
+const {createMockSession} = require('../../fraggle-rock/gather/mock-driver.js');
+const {makePromiseInspectable, flushAllTimersAndMicrotasks} = require('../../test-utils.js');
+const serviceWorkers = require('../../../gather/driver/service-workers.js');
+
+let sessionMock = createMockSession();
+
+beforeEach(() => {
+  sessionMock = createMockSession();
+});
+
+afterEach(() => {
+  jest.useRealTimers();
+});
+
+describe('.getServiceWorkerVersions', () => {
+  it('returns the data from service worker events', async () => {
+    sessionMock.sendCommand
+      .mockResponse('ServiceWorker.enable')
+      .mockResponse('ServiceWorker.disable');
+    sessionMock.on.mockEvent('ServiceWorker.workerVersionUpdated', {
+      versions: [{registrationId: '1', status: 'activated'}],
+    });
+
+    const results = await serviceWorkers.getServiceWorkerVersions(sessionMock.asSession());
+    expect(results).toEqual({versions: [{registrationId: '1', status: 'activated'}]});
+  });
+
+  it('returns when there are no active candidates', async () => {
+    sessionMock.sendCommand
+      .mockResponse('ServiceWorker.enable')
+      .mockResponse('ServiceWorker.disable');
+    sessionMock.on.mockEvent('ServiceWorker.workerVersionUpdated', {
+      versions: [{registrationId: '1', status: 'redundant'}],
+    });
+
+    const results = await serviceWorkers.getServiceWorkerVersions(sessionMock.asSession());
+    expect(results).toEqual({versions: [{registrationId: '1', status: 'redundant'}]});
+  });
+
+  it('waits for currently installing workers', async () => {
+    jest.useFakeTimers();
+    sessionMock.sendCommand
+      .mockResponse('ServiceWorker.enable')
+      .mockResponse('ServiceWorker.disable');
+
+    const resultPromise = makePromiseInspectable(
+      serviceWorkers.getServiceWorkerVersions(sessionMock.asSession())
+    );
+    await flushAllTimersAndMicrotasks();
+    expect(resultPromise.isDone()).toBe(false);
+
+    const workerVersionUpdated = sessionMock.on.findListener('ServiceWorker.workerVersionUpdated');
+    workerVersionUpdated({versions: [{status: 'installing'}]});
+    await flushAllTimersAndMicrotasks();
+    expect(resultPromise.isDone()).toBe(false);
+
+    const versions = {versions: [{registrationId: '3', status: 'activated'}]};
+    workerVersionUpdated(versions);
+    await flushAllTimersAndMicrotasks();
+    expect(resultPromise.isDone()).toBe(true);
+    expect(await resultPromise).toEqual(versions);
+  });
+});
+
+describe('.getServiceWorkerRegistrations', () => {
+  it('returns the data from service worker events', async () => {
+    sessionMock.sendCommand
+      .mockResponse('ServiceWorker.enable')
+      .mockResponse('ServiceWorker.disable');
+    sessionMock.once.mockEvent('ServiceWorker.workerRegistrationUpdated', {
+      registrations: [{registrationId: '2'}],
+    });
+
+    const results = await serviceWorkers.getServiceWorkerRegistrations(sessionMock.asSession());
+    expect(results).toEqual({registrations: [{registrationId: '2'}]});
+  });
+});
diff --git a/lighthouse-core/test/gather/driver/storage-test.js b/lighthouse-core/test/gather/driver/storage-test.js
new file mode 100644
index 000000000..bbad50768
--- /dev/null
+++ b/lighthouse-core/test/gather/driver/storage-test.js
@@ -0,0 +1,82 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+/* eslint-env jest */
+
+const {createMockSession} = require('../../fraggle-rock/gather/mock-driver.js');
+const storage = require('../../../gather/driver/storage.js');
+
+let sessionMock = createMockSession();
+
+beforeEach(() => {
+  sessionMock = createMockSession();
+});
+
+describe('.clearDataForOrigin', () => {
+  it('only clears data from certain locations', async () => {
+    let foundStorageTypes;
+    sessionMock.sendCommand.mockResponse('Storage.clearDataForOrigin', ({storageTypes}) => {
+      foundStorageTypes = storageTypes;
+    });
+    await storage.clearDataForOrigin(sessionMock.asSession(), 'https://example.com');
+    // Should not see cookies, websql, indexeddb, or local_storage.
+    // Cookies are not cleared to preserve login.
+    // websql, indexeddb, and local_storage are not cleared to preserve important user data.
+    expect(foundStorageTypes).toMatchInlineSnapshot(
+      `"appcache,file_systems,shader_cache,service_workers,cache_storage"`
+    );
+  });
+});
+
+describe('.getImportantDataWarning', () => {
+  it('properly returns warning', async () => {
+    sessionMock.sendCommand.mockResponse('Storage.getUsageAndQuota', {
+      usageBreakdown: [
+        {storageType: 'local_storage', usage: 5},
+        {storageType: 'indexeddb', usage: 5},
+        {storageType: 'websql', usage: 0},
+        {storageType: 'appcache', usage: 5},
+        {storageType: 'cookies', usage: 5},
+        {storageType: 'file_systems', usage: 5},
+        {storageType: 'shader_cache', usage: 5},
+        {storageType: 'service_workers', usage: 5},
+        {storageType: 'cache_storage', usage: 0},
+      ],
+    });
+    const warning = await storage.getImportantStorageWarning(
+      sessionMock.asSession(),
+      'https://example.com'
+    );
+    expect(warning).toBeDisplayString(
+      'There may be stored data affecting loading performance in ' +
+        'these locations: Local Storage, IndexedDB. ' +
+        'Audit this page in an incognito window to prevent those resources ' +
+        'from affecting your scores.'
+    );
+  });
+
+  it('only warn for certain locations', async () => {
+    sessionMock.sendCommand.mockResponse('Storage.getUsageAndQuota', {
+      usageBreakdown: [
+        {storageType: 'local_storage', usage: 0},
+        {storageType: 'indexeddb', usage: 0},
+        {storageType: 'websql', usage: 0},
+        {storageType: 'appcache', usage: 5},
+        {storageType: 'cookies', usage: 5},
+        {storageType: 'file_systems', usage: 5},
+        {storageType: 'shader_cache', usage: 5},
+        {storageType: 'service_workers', usage: 5},
+        {storageType: 'cache_storage', usage: 5},
+      ],
+    });
+    const warning = await storage.getImportantStorageWarning(
+      sessionMock.asSession(),
+      'https://example.com'
+    );
+    expect(warning).toBeUndefined();
+  });
+});
diff --git a/lighthouse-core/test/gather/fake-driver.js b/lighthouse-core/test/gather/fake-driver.js
index 55e8f3729..7545321b6 100644
--- a/lighthouse-core/test/gather/fake-driver.js
+++ b/lighthouse-core/test/gather/fake-driver.js
@@ -5,6 +5,8 @@
  */
 'use strict';
 
+/* eslint-env jest */
+
 /**
  * @param {{protocolGetVersionResponse: LH.CrdpCommands['Browser.getVersion']['returnType']}} param0
  */
@@ -17,6 +19,11 @@ function makeFakeDriver({protocolGetVersionResponse}) {
         disableRequestInterception: () => Promise.resolve(),
       };
     },
+    get defaultSession() {
+      return this;
+    },
+    on: jest.fn(),
+    sendCommand: jest.fn().mockResolvedValue(undefined),
     getBrowserVersion() {
       return Promise.resolve(Object.assign({}, protocolGetVersionResponse, {milestone: 71}));
     },
@@ -45,17 +52,6 @@ function makeFakeDriver({protocolGetVersionResponse}) {
     reloadForCleanStateIfNeeded() {
       return Promise.resolve();
     },
-    enableRuntimeEvents() {
-      return Promise.resolve();
-    },
-    enableAsyncStacks() {
-      return Promise.resolve();
-    },
-    cleanBrowserCaches() {},
-    clearDataForOrigin() {},
-    getImportantStorageWarning() {
-      return Promise.resolve(undefined);
-    },
     executionContext: {
       evaluateAsync() {
         return Promise.resolve({});
@@ -89,12 +85,6 @@ function makeFakeDriver({protocolGetVersionResponse}) {
       const modulePath = '../fixtures/artifacts/perflog/defaultPass.devtoolslog.json';
       return require(modulePath);
     },
-    blockUrlPatterns() {
-      return Promise.resolve();
-    },
-    setExtraHTTPHeaders() {
-      return Promise.resolve();
-    },
     registerRequestIdleCallbackWrap() {
       return Promise.resolve();
     },
diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js
index 8cf543f19..e42e47371 100644
--- a/lighthouse-core/test/gather/gather-runner-test.js
+++ b/lighthouse-core/test/gather/gather-runner-test.js
@@ -20,8 +20,12 @@ const LHError = require('../../lib/lh-error.js');
 const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js');
 const Driver = require('../../gather/driver.js');
 const Connection = require('../../gather/connections/connection.js');
-const {createMockSendCommandFn} = require('./mock-commands.js');
-const {makeParamsOptional} = require('../test-utils.js');
+const {createMockSendCommandFn, createMockOnceFn} = require('./mock-commands.js');
+const {
+  makeParamsOptional,
+  makePromiseInspectable,
+  flushAllTimersAndMicrotasks,
+} = require('../test-utils.js');
 
 const GatherRunner = {
   afterPass: makeParamsOptional(GatherRunner_.afterPass),
@@ -39,6 +43,9 @@ const GatherRunner = {
   runPass: makeParamsOptional(GatherRunner_.runPass),
   setupDriver: makeParamsOptional(GatherRunner_.setupDriver),
   setupPassNetwork: makeParamsOptional(GatherRunner_.setupPassNetwork),
+  // Spies that should have mock implemenations most of the time.
+  assertNoSameOriginServiceWorkerClients: jest.spyOn(GatherRunner_,
+    'assertNoSameOriginServiceWorkerClients'),
 };
 
 /**
@@ -79,24 +86,6 @@ class TestGathererNoArtifact extends Gatherer {
 }
 
 class EmulationDriver extends Driver {
-  enableRuntimeEvents() {
-    return Promise.resolve();
-  }
-  enableAsyncStacks() {
-    return Promise.resolve();
-  }
-  assertNoSameOriginServiceWorkerClients() {
-    return Promise.resolve();
-  }
-  registerPerformanceObserver() {
-    return Promise.resolve();
-  }
-  cleanBrowserCaches() {
-    return Promise.resolve();
-  }
-  clearDataForOrigin() {
-    return Promise.resolve();
-  }
   registerRequestIdleCallbackWrap() {
     return Promise.resolve();
   }
@@ -113,7 +102,15 @@ let driver;
 let connectionStub;
 
 function resetDefaultMockResponses() {
+  GatherRunner.assertNoSameOriginServiceWorkerClients = jest.spyOn(GatherRunner_,
+    'assertNoSameOriginServiceWorkerClients');
+  GatherRunner.assertNoSameOriginServiceWorkerClients.mockReset();
+  GatherRunner.assertNoSameOriginServiceWorkerClients.mockResolvedValue();
+
   connectionStub.sendCommand = createMockSendCommandFn()
+    .mockResponse('Debugger.enable')
+    .mockResponse('Debugger.setSkipAllPauses')
+    .mockResponse('Debugger.setAsyncCallStackDepth')
     .mockResponse('Emulation.setCPUThrottlingRate')
     .mockResponse('Emulation.setDeviceMetricsOverride')
     .mockResponse('Emulation.setTouchEmulationEnabled')
@@ -139,6 +136,7 @@ function restoreActualEmulation() {
 }
 
 beforeEach(() => {
+  jest.useFakeTimers();
   // @ts-expect-error - connectionStub has a mocked version of sendCommand implemented in each test
   connectionStub = new Connection();
   // @ts-expect-error
@@ -152,6 +150,14 @@ beforeEach(() => {
   emulation.emulate = jest.fn();
   emulation.throttle = jest.fn();
   emulation.clearThrottling = jest.fn();
+
+  const storage = require('../../gather/driver/storage.js');
+  storage.clearDataForOrigin = jest.fn();
+});
+
+afterEach(() => {
+  GatherRunner.assertNoSameOriginServiceWorkerClients.mockRestore();
+  jest.useRealTimers();
 });
 
 describe('GatherRunner', function() {
@@ -307,63 +313,157 @@ describe('GatherRunner', function() {
       .toMatchObject({mobile: false});
   });
 
-  it('clears origin storage', () => {
-    const asyncFunc = () => Promise.resolve();
-    /** @type {Record<string, boolean>} */
-    const tests = {
-      calledCleanBrowserCaches: false,
-      calledClearStorage: false,
-    };
-    /** @param {string} variable */
-    const createCheck = variable => () => {
-      tests[variable] = true;
-      return Promise.resolve();
-    };
-    const driver = {
-      assertNoSameOriginServiceWorkerClients: asyncFunc,
-      dismissJavaScriptDialogs: asyncFunc,
-      enableRuntimeEvents: asyncFunc,
-      enableAsyncStacks: asyncFunc,
-      executionContext: {cacheNativesOnNewDocument: asyncFunc},
-      gotoURL: asyncFunc,
-      registerRequestIdleCallbackWrap: asyncFunc,
-      cleanBrowserCaches: createCheck('calledCleanBrowserCaches'),
-      clearDataForOrigin: createCheck('calledClearStorage'),
-      blockUrlPatterns: asyncFunc,
-      setExtraHTTPHeaders: asyncFunc,
-      getImportantStorageWarning: asyncFunc,
-    };
+  describe('.assertNoSameOriginServiceWorkerClients', () => {
+    /** @type {LH.Gatherer.FRProtocolSession} */
+    let session;
+    /** @type {typeof GatherRunner_['assertNoSameOriginServiceWorkerClients']} */
+    let assertNoSameOriginServiceWorkerClients;
+
+    beforeEach(() => {
+      GatherRunner.assertNoSameOriginServiceWorkerClients.mockRestore();
+      assertNoSameOriginServiceWorkerClients = GatherRunner_.assertNoSameOriginServiceWorkerClients;
+      session = driver.defaultSession;
+      connectionStub.sendCommand = createMockSendCommandFn()
+        .mockResponse('ServiceWorker.enable')
+        .mockResponse('ServiceWorker.disable')
+        .mockResponse('ServiceWorker.enable')
+        .mockResponse('ServiceWorker.disable');
+    });
+
+    /**
+   * @param {number} id
+   * @param {string} url
+   * @param {boolean=} isDeleted
+   */
+    function createSWRegistration(id, url, isDeleted) {
+      return {
+        isDeleted: !!isDeleted,
+        registrationId: String(id),
+        scopeURL: url,
+      };
+    }
+
+    /**
+   * @param {number} id
+   * @param {string} url
+   * @param {string[]} controlledClients
+   * @param {LH.Crdp.ServiceWorker.ServiceWorkerVersionStatus=} status
+   */
+    function createActiveWorker(id, url, controlledClients, status = 'activated') {
+      return {
+        registrationId: String(id),
+        scriptURL: url,
+        controlledClients,
+        status,
+      };
+    }
+
+    it('will pass if there are no current service workers', async () => {
+      const pageUrl = 'https://example.com/';
+
+      driver.on = driver.once = createMockOnceFn()
+      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations: []})
+      .mockEvent('ServiceWorker.workerVersionUpdated', {versions: []});
+
+      const assertPromise = assertNoSameOriginServiceWorkerClients(session, pageUrl);
+      await flushAllTimersAndMicrotasks();
+      await assertPromise;
+    });
+
+    it('will pass if there is an active service worker for a different origin', async () => {
+      const pageUrl = 'https://example.com/';
+      const secondUrl = 'https://example.edu';
+      const swUrl = `${secondUrl}sw.js`;
+
+      const registrations = [createSWRegistration(1, secondUrl)];
+      const versions = [createActiveWorker(1, swUrl, ['uniqueId'])];
 
-    return GatherRunner.setupDriver(driver, {settings: {}}).then(_ => {
-      assert.equal(tests.calledCleanBrowserCaches, false);
-      assert.equal(tests.calledClearStorage, true);
+      driver.on = driver.once = createMockOnceFn()
+      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
+      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
+
+      const assertPromise = assertNoSameOriginServiceWorkerClients(session, pageUrl);
+      await flushAllTimersAndMicrotasks();
+      await assertPromise;
+    });
+
+    it('will fail if a service worker with a matching origin has a controlled client', async () => {
+      const pageUrl = 'https://example.com/';
+      const swUrl = `${pageUrl}sw.js`;
+      const registrations = [createSWRegistration(1, pageUrl)];
+      const versions = [createActiveWorker(1, swUrl, ['uniqueId'])];
+
+      driver.on = driver.once = createMockOnceFn()
+      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
+      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
+
+      expect.assertions(1);
+
+      try {
+        const assertPromise = assertNoSameOriginServiceWorkerClients(session, pageUrl);
+        await flushAllTimersAndMicrotasks();
+        await assertPromise;
+      } catch (err) {
+        expect(err.message.toLowerCase()).toContain('multiple tabs');
+      }
     });
+
+    it('will succeed if a service worker with has no controlled clients', async () => {
+      const pageUrl = 'https://example.com/';
+      const swUrl = `${pageUrl}sw.js`;
+      const registrations = [createSWRegistration(1, pageUrl)];
+      const versions = [createActiveWorker(1, swUrl, [])];
+
+      driver.on = driver.once = createMockOnceFn()
+      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
+      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
+
+      const assertPromise = assertNoSameOriginServiceWorkerClients(session, pageUrl);
+      await flushAllTimersAndMicrotasks();
+      await assertPromise;
+    });
+
+    it('will wait for serviceworker to be activated', async () => {
+      const pageUrl = 'https://example.com/';
+      const swUrl = `${pageUrl}sw.js`;
+      const registrations = [createSWRegistration(1, pageUrl)];
+      const versions = [createActiveWorker(1, swUrl, [], 'installing')];
+      const activatedVersions = [createActiveWorker(1, swUrl, [], 'activated')];
+
+      const mockOn = driver.on = driver.once = createMockOnceFn()
+      .mockEvent('ServiceWorker.workerRegistrationUpdated', {registrations})
+      .mockEvent('ServiceWorker.workerVersionUpdated', {versions});
+
+      const assertPromise = assertNoSameOriginServiceWorkerClients(session, pageUrl);
+      const inspectable = makePromiseInspectable(assertPromise);
+
+      // After receiving the empty versions the promise still shouldn't be resolved
+      await flushAllTimersAndMicrotasks();
+      expect(inspectable).not.toBeDone();
+
+      // Use `findListener` instead of `mockEvent` so we can control exactly when the promise resolves
+      // After we invoke the listener with the activated versions we expect the promise to have resolved
+      const listener = mockOn.findListener('ServiceWorker.workerVersionUpdated');
+      listener({versions: activatedVersions});
+      await flushAllTimersAndMicrotasks();
+      expect(inspectable).toBeDone();
+      await assertPromise;
+    });
+  });
+
+  it('clears origin storage', async () => {
+    const storage = jest.requireMock('../../gather/driver/storage.js');
+    await GatherRunner.setupDriver(driver, {settings: {}});
+    expect(storage.clearDataForOrigin).toHaveBeenCalled();
+  });
+
+  it('does not clear origin storage with flag --disable-storage-reset', async () => {
+    const storage = jest.requireMock('../../gather/driver/storage.js');
+    await GatherRunner.setupDriver(driver, {settings: {disableStorageReset: true}});
+    expect(storage.clearDataForOrigin).not.toHaveBeenCalled();
   });
 
   it('clears the disk & memory cache on a perf run', async () => {
-    const asyncFunc = () => Promise.resolve();
-    /** @type {Record<string, boolean>} */
-    const tests = {
-      calledCleanBrowserCaches: false,
-    };
-    /** @param {string} variable */
-    const createCheck = variable => () => {
-      tests[variable] = true;
-      return Promise.resolve();
-    };
-    const driver = {
-      beginDevtoolsLog: asyncFunc,
-      beginTrace: asyncFunc,
-      gotoURL: async () => ({}),
-      cleanBrowserCaches: createCheck('calledCleanBrowserCaches'),
-      blockUrlPatterns: asyncFunc,
-      setExtraHTTPHeaders: asyncFunc,
-      endTrace: asyncFunc,
-      endDevtoolsLog: () => [],
-      getBrowserVersion: async () => ({userAgent: ''}),
-      getScrollPosition: async () => 1,
-      scrollTo: async () => {},
-    };
     const passConfig = {
       passName: 'default',
       loadFailureMode: LoadFailureMode.ignore,
@@ -376,14 +476,15 @@ describe('GatherRunner', function() {
     };
     const requestedUrl = 'https://example.com';
     const passContext = {
-      driver,
+      driver: fakeDriver,
       passConfig,
       settings,
       baseArtifacts: await GatherRunner.initializeBaseArtifacts({driver, settings, requestedUrl}),
     };
 
+    const storage = jest.requireMock('../../gather/driver/storage.js');
     await GatherRunner.runPass(passContext);
-    assert.equal(tests.calledCleanBrowserCaches, true);
+    expect(storage.cleanBrowserCaches).toHaveBeenCalled();
   });
 
   it('returns a pageLoadError and no artifacts when there is a network error', async () => {
@@ -495,41 +596,6 @@ describe('GatherRunner', function() {
     expect(artifacts.devtoolsLogs).toHaveProperty('pageLoadError-nextPass');
   });
 
-  it('does not clear origin storage with flag --disable-storage-reset', () => {
-    const asyncFunc = () => Promise.resolve();
-    /** @type {Record<string, boolean>} */
-    const tests = {
-      calledCleanBrowserCaches: false,
-      calledClearStorage: false,
-    };
-    /** @param {string} variable */
-    const createCheck = variable => () => {
-      tests[variable] = true;
-      return Promise.resolve();
-    };
-    const driver = {
-      assertNoSameOriginServiceWorkerClients: asyncFunc,
-      dismissJavaScriptDialogs: asyncFunc,
-      enableRuntimeEvents: asyncFunc,
-      enableAsyncStacks: asyncFunc,
-      executionContext: {cacheNativesOnNewDocument: asyncFunc},
-      gotoURL: asyncFunc,
-      registerPerformanceObserver: asyncFunc,
-      registerRequestIdleCallbackWrap: asyncFunc,
-      cleanBrowserCaches: createCheck('calledCleanBrowserCaches'),
-      clearDataForOrigin: createCheck('calledClearStorage'),
-      blockUrlPatterns: asyncFunc,
-      setExtraHTTPHeaders: asyncFunc,
-    };
-
-    return GatherRunner.setupDriver(driver, {
-      settings: {disableStorageReset: true},
-    }).then(_ => {
-      assert.equal(tests.calledCleanBrowserCaches, false);
-      assert.equal(tests.calledClearStorage, false);
-    });
-  });
-
   it('sets throttling appropriately', async () => {
     restoreActualEmulation();
     await GatherRunner.setupPassNetwork({
@@ -1291,6 +1357,7 @@ describe('GatherRunner', function() {
   describe('artifact collection', () => {
     // Make sure our gatherers never execute in parallel
     it('runs gatherer lifecycle methods strictly in sequence', async () => {
+      jest.useRealTimers();
       /** @type {Record<string, number>} */
       const counter = {
         beforePass: 0,
diff --git a/lighthouse-core/test/gather/gatherers/service-worker-test.js b/lighthouse-core/test/gather/gatherers/service-worker-test.js
index dabc46431..21d5fae8f 100644
--- a/lighthouse-core/test/gather/gatherers/service-worker-test.js
+++ b/lighthouse-core/test/gather/gatherers/service-worker-test.js
@@ -7,6 +7,13 @@
 
 /* eslint-env jest */
 
+const getServiceWorkerVersions = jest.fn();
+const getServiceWorkerRegistrations = jest.fn();
+jest.mock('../../../gather/driver/service-workers.js', () => ({
+  getServiceWorkerVersions,
+  getServiceWorkerRegistrations,
+}));
+
 const ServiceWorkerGather = require('../../../gather/gatherers/service-worker.js');
 const assert = require('assert').strict;
 
@@ -23,17 +30,12 @@ describe('service worker gatherer', () => {
       scopeUrl: url,
       isDeleted: false,
     }];
+    getServiceWorkerVersions.mockResolvedValue({versions});
+    getServiceWorkerRegistrations.mockResolvedValue({registrations});
 
     const serviceWorkerGatherer = new ServiceWorkerGather();
     const artifact = await serviceWorkerGatherer.beforePass({
-      driver: {
-        async getServiceWorkerVersions() {
-          return {versions};
-        },
-        async getServiceWorkerRegistrations() {
-          return {registrations};
-        },
-      },
+      driver: {},
       url,
     });
 
diff --git a/lighthouse-core/test/runner-test.js b/lighthouse-core/test/runner-test.js
index eacd4f8f3..49b8d30bf 100644
--- a/lighthouse-core/test/runner-test.js
+++ b/lighthouse-core/test/runner-test.js
@@ -7,6 +7,11 @@
 
 require('./test-utils.js').makeMocksForGatherRunner();
 
+jest.mock('../gather/driver/service-workers.js', () => ({
+  getServiceWorkerVersions: jest.fn().mockResolvedValue({versions: []}),
+  getServiceWorkerRegistrations: jest.fn().mockResolvedValue({registrations: []}),
+}));
+
 const Runner = require('../runner.js');
 const GatherRunner = require('../gather/gather-runner.js');
 const driverMock = require('./gather/fake-driver.js');
diff --git a/lighthouse-core/test/test-utils.js b/lighthouse-core/test/test-utils.js
index 2c9ea6dd3..e3c518f51 100644
--- a/lighthouse-core/test/test-utils.js
+++ b/lighthouse-core/test/test-utils.js
@@ -227,6 +227,7 @@ async function flushAllTimersAndMicrotasks(ms = 1000) {
 function makeMocksForGatherRunner() {
   jest.mock('../gather/driver/environment.js', () => ({
     getBenchmarkIndex: () => Promise.resolve(150),
+    getBrowserVersion: async () => ({userAgent: 'Chrome', milestone: 80}),
   }));
   jest.mock('../gather/gatherers/stacks.js', () => ({collectStacks: () => Promise.resolve([])}));
   jest.mock('../gather/gatherers/installability-errors.js', () => ({
@@ -240,6 +241,11 @@ function makeMocksForGatherRunner() {
     throttle: jest.fn(),
     clearThrottling: jest.fn(),
   }));
+  jest.mock('../gather/driver/storage.js', () => ({
+    clearDataForOrigin: jest.fn(),
+    cleanBrowserCaches: jest.fn(),
+    getImportantStorageWarning: jest.fn(),
+  }));
 }
 
 module.exports = {